public static Vector3D EdgeToPlane(Geometry g, H3.Cell.Edge edge) { if (g != Geometry.Hyperbolic) { throw new System.NotImplementedException(); } Vector3D b1, b2; H3Models.Ball.GeodesicIdealEndpoints(edge.Start, edge.End, out b1, out b2); if (((b2 + b1) / 2).IsOrigin) { Vector3D lineNormal = b2 - b1; lineNormal.RotateXY(Math.PI / 2); lineNormal.Normalize(); return(new Vector3D(lineNormal.X, lineNormal.Y, lineNormal.Z, 0)); } Vector3D center, normal; double radius, angleTot; H3Models.Ball.Geodesic(edge.Start, edge.End, out center, out radius, out normal, out angleTot); Vector3D closest = H3Models.Ball.ClosestToOrigin(new Circle3D() { Center = center, Radius = radius, Normal = normal }); Vector3D closestKlein = HyperbolicModels.PoincareToKlein(closest); center.Normalize(); return(new Vector3D(center.X, center.Y, center.Z, closestKlein.Abs())); }
public static Vector3D ToConformal(Geometry g, Vector3D[] kv, Vector3D b) { Vector3D klein = Util.BaryToEuclidean(kv, b); switch (g) { case Geometry.Spherical: return(SphericalModels.GnomonicToStereo(klein)); case Geometry.Euclidean: return(klein); case Geometry.Hyperbolic: return(HyperbolicModels.KleinToPoincare(klein)); } throw new System.ArgumentException(); }
// So that we can leverage Euclidean barycentric coordinates, we will first convert our simplex to the Klein model. // We will need to take care to properly convert back to the Ball as needed. public static Vector3D[] KleinVerts(Geometry g, Vector3D[] conformalVerts) { return(conformalVerts.Select(v => { switch (g) { case Geometry.Spherical: return SphericalModels.StereoToGnomonic(v); case Geometry.Euclidean: return v; case Geometry.Hyperbolic: return HyperbolicModels.PoincareToKlein(v); } throw new System.ArgumentException(); }).ToArray()); }
public static void Polarity() { TilingConfig config = new TilingConfig(3, 7, 50); Tiling tiling = new Tiling(); tiling.GenerateInternal(config); List <Vector3D> points = new List <Vector3D>(); List <H3.Cell.Edge> edges = new List <H3.Cell.Edge>(); foreach (Polygon p in tiling.Tiles.Select(t => t.Boundary)) { foreach (Segment s in p.Segments) { foreach (Vector3D v in s.Subdivide(25)) { Vector3D klein = HyperbolicModels.PoincareToKlein(v); H3.Cell.Edge e = Dual(klein); points.Add(klein); edges.Add(e); } } } using (StreamWriter sw = File.CreateText("polarity.pov")) { double rad = 0.01; foreach (Vector3D vert in points) { sw.WriteLine(PovRay.Sphere(new Sphere() { Center = vert, Radius = rad })); } foreach (H3.Cell.Edge edge in edges) { sw.WriteLine(PovRay.Cylinder(edge.Start, edge.End, rad / 2)); } } }
public static Circle3D GetCircleForBallPoint(Vector3D p) { Sphere ball = new Sphere(); p = SphericalModels.GnomonicToStereo(p); if (Tolerance.GreaterThanOrEqual(p.Abs(), 1)) { return(null); } Sphere t = H3Models.Ball.OrthogonalSphereInterior(p); //return H3Models.Ball.IdealCircle( t ); // Get the corresponding point on the exterior (our inversion). p = HyperbolicModels.PoincareToKlein(p); p = HyperbolicModels.PoincareToKlein(p); p = ball.ReflectPoint(p); return(GetCircle(p)); }
/// <summary> /// Helper to transform to standard model if needed. /// </summary> private Vector3D ToStandardIfNeeded(Vector3D point) { if (m_geometry == Geometry.Hyperbolic && m_settings.HyperbolicModel == HModel.Klein) { return(HyperbolicModels.KleinToPoincare(point)); } if (m_geometry == Geometry.Spherical) { if (m_settings.SphericalModel == SphericalModel.Gnomonic) { return(SphericalModels.GnomonicToStereo(point)); } if (m_settings.SphericalModel == SphericalModel.Fisheye) { return(SphericalModels.StereoToGnomonic(point)); } } return(point); }
// CHEAT! (would be better to do a geometrical construction) // We are going to iterate to the starting point that will make all edge lengths the same. public static Vector3D IterateToStartingPoint(HoneycombDef?def, int[] activeMirrors, Simplex simplex) { if (activeMirrors.Length == 1) { return(simplex.Verts[activeMirrors[0]]); } // We are minimizing the output of this function, // because we want all edge lengths to be as close as possible. // Input vector should be in the Ball Model. Func <Vector3D, double> diffFunc = v => { List <double> lengths = new List <double>(); for (int i = 0; i < activeMirrors.Length; i++) { Vector3D reflected = simplex.ReflectInFacet(v, activeMirrors[i]); lengths.Add(H3Models.Ball.HDist(v, reflected)); } double result = 0; double average = lengths.Average(); foreach (double length in lengths) { result += Math.Abs(length - average); } if (Infinity.IsInfinite(result)) { result = double.PositiveInfinity; } return(result); }; // So that we can leverage Euclidean barycentric coordinates, we will first convert our simplex to the Klein model. // We will need to take care to properly convert back to the Ball as needed. Vector3D[] kleinVerts = simplex.Verts.Select(v => HyperbolicModels.PoincareToKlein(v)).ToArray(); if (def != null) { HoneycombDef d = def.Value; Geometry vertexGeometry = Geometry2D.GetGeometry(d.Q, d.R); if (vertexGeometry == Geometry.Hyperbolic) { kleinVerts[3] = SimplexCalcs.VertexPointKlein(d.P, d.Q, d.R); } } // Normalizing barycentric coords amounts to making sure the 4 coords add to 1. Func <Vector3D, Vector3D> baryNormalize = b => { return(b / (b.X + b.Y + b.Z + b.W)); }; // Bary Coords to Euclidean Func <Vector3D[], Vector3D, Vector3D> baryToEuclidean = (kv, b) => { Vector3D result = kv[0] * b.X + kv[1] * b.Y + kv[2] * b.Z + kv[3] * b.W; return(result); }; // Our starting barycentric coords (halfway between all active mirrors). Vector3D bary = new Vector3D(); foreach (int a in activeMirrors) { bary[a] = 0.5; } bary = baryNormalize(bary); // For each iteration, we'll shrink this search offset. // NOTE: The starting offset and decrease factor I'm using don't guarantee convergence, // but it seems to be working pretty well (even when varying these parameters). //double searchOffset = 1.0 - bary[activeMirrors[0]]; //double searchOffset = bary[activeMirrors[0]]; double factor = 1.5; // Adjusting this helps get some to converge, e.g. 4353-1111 double searchOffset = bary[activeMirrors[0]] / factor; double min = double.MaxValue; int iterations = 1000; for (int i = 0; i < iterations; i++) { min = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, bary))); foreach (int a in activeMirrors) { Vector3D baryTest1 = bary, baryTest2 = bary; baryTest1[a] += searchOffset; baryTest2[a] -= searchOffset; baryTest1 = baryNormalize(baryTest1); baryTest2 = baryNormalize(baryTest2); double t1 = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, baryTest1))); double t2 = diffFunc(HyperbolicModels.KleinToPoincare(baryToEuclidean(kleinVerts, baryTest2))); if (t1 < min) { min = t1; bary = baryTest1; } if (t2 < min) { min = t2; bary = baryTest2; } } if (Tolerance.Equal(min, 0.0, 1e-14)) { System.Console.WriteLine(string.Format("Converged in {0} iterations.", i)); break; } searchOffset /= factor; } if (!Tolerance.Equal(min, 0.0, 1e-14)) { System.Console.WriteLine("Did not converge: " + min); // Be a little looser before thrown an exception. if (!Tolerance.Equal(min, 0.0, 1e-12)) { System.Console.ReadKey(true); //throw new System.Exception( "Boo. We did not converge." ); return(Vector3D.DneVector()); } } Vector3D euclidean = baryToEuclidean(kleinVerts, bary); return(HyperbolicModels.KleinToPoincare(euclidean)); }
public static H3.Cell.Edge[] OneHoneycombOrthoscheme(HoneycombDef def, int[] active, int baseHue, Settings settings = null) { // Setup parameters. int numEdges = 250000; if (settings != null) { active = settings.PovRay.Active; def = new HoneycombDef(settings.P, settings.Q, settings.R); numEdges = settings.PovRay.NumEdges; } CalcThickness(active); if (settings != null) { H3.m_settings.AngularThickness = settings.PovRay.EdgeWidth; // ZZZ - should really stop using that settings class. } string baseName = BaseName(def); string mirrorsString = ActiveMirrorsString(active); string suffix = "-" + mirrorsString; string fileName = baseName + suffix; if (ViewPath != null) { fileName += string.Format("_{0:D4}", ViewPath.Step); } if (File.Exists(fileName + ".pov")) { File.Delete(fileName + ".pov"); //Console.WriteLine( string.Format( "Skipping {0}", fileName ) ); //return; } Program.Log(string.Format("Building {0}", fileName)); // The wiki mirrors are labeled in the reverse of ours. Func <int, int> mapMirror = i => 3 - i; active = active.Select(i => mapMirror(i)).OrderBy(i => i).ToArray(); Simplex simplex = new Simplex(); simplex.Facets = SimplexCalcs.Mirrors(def.P, def.Q, def.R); simplex.Verts = SimplexCalcs.VertsBall(def.P, def.Q, def.R); Vector3D startingPoint = IterateToStartingPoint(def, active, simplex); if (startingPoint.DNE) { return(null); } List <H3.Cell.Edge> startingEdges = new List <H3.Cell.Edge>(); foreach (int a in active) { Vector3D reflected = simplex.ReflectInFacet(startingPoint, a); startingEdges.Add(new H3.Cell.Edge(startingPoint, reflected)); //startingEdges.Add( new H3.Cell.Edge( simplex.Verts[0], simplex.Verts[3] ) ); // Used for Borromean Rings complement image. } if (false) { Vector3D[] kv = simplex.Verts.Select(v => HyperbolicModels.PoincareToKlein(v)).ToArray(); kv[3] = SimplexCalcs.VertexPointKlein(def.P, def.Q, def.R); Vector3D t = (kv[3] - kv[0]) * 0.5; Sphere gSphere = H3Models.Ball.OrthogonalSphereInterior(HyperbolicModels.KleinToPoincare(t)); gSphere = H3Models.BallToKlein(gSphere); Vector3D t2 = Euclidean3D.IntersectionPlaneLine(gSphere.Normal, gSphere.Offset, kv[3] - kv[2], kv[2]); //t2 = kv[2] + ( kv[3] - kv[2]) * 0.5; t = HyperbolicModels.KleinToPoincare(t); t2 = HyperbolicModels.KleinToPoincare(t2); startingEdges.Add(new H3.Cell.Edge(t, t2)); startingEdges.Add(new H3.Cell.Edge(t, simplex.ReflectInFacet(t, 3))); } // If we are doing a view path, transform our geometry. if (ViewPath != null) { //Vector3D p = new Vector3D( 0, 0, .5 ); Vector3D p = new Vector3D(0.08, 0.12, 0.07); simplex.Facets = simplex.Facets.Select(f => H3Models.Transform_PointToOrigin(f, p)).ToArray(); simplex.Verts = simplex.Verts.Select(v => H3Models.Transform_PointToOrigin(v, p)).ToArray(); startingEdges = startingEdges.Select(e => new H3.Cell.Edge( H3Models.Transform_PointToOrigin(e.Start, p), H3Models.Transform_PointToOrigin(e.End, p))).ToList(); } SetupBaseHue(fileName, mirrorsString, baseHue); Recurse.m_background = baseHue == -1 ? new Vector3D() : new Vector3D(baseHue, 1, .1); H3.Cell.Edge[] edges = Recurse.CalcEdgesSmart2(simplex.Facets, startingEdges.ToArray(), numEdges); //H3.Cell.Edge[] edges = Recurse.CalcEdges( simplex.Facets, startingEdges.ToArray(), // new Recurse.Settings() { ThreshType = Recurse.EdgeThreshType.Radial, Threshold = H3Models.Ball.FindLocationForDesiredRadius( settings.PovRay.EdgeWidth, 0.8/100 ) } ); //edges = edges.Where( e => e.Depths[0] % 2 == 1 ).ToArray(); // Used for Borromean Rings complement image. // Shapeways truncated 436. if (false) { if (true) { Mobius m = Mobius.Scale(1.0 / H3Models.UHS.ToE(Honeycomb.InRadius(def.P, def.Q, def.R))); double a = -Math.PI / 2 + Math.Asin(1 / Math.Sqrt(3)); edges = edges.Select(e => { Vector3D v1 = e.Start; Vector3D v2 = e.End; v1.RotateAboutAxis(new Vector3D(1, 0, 0), a); v2.RotateAboutAxis(new Vector3D(1, 0, 0), a); v1 = H3Models.Ball.ApplyMobius(m, v1); v2 = H3Models.Ball.ApplyMobius(m, v2); return(new H3.Cell.Edge(v1, v2)); }).ToArray(); double thresh = -.01; Vector3D looking = new Vector3D(0, 0, -1); edges = edges.Where(e => e.Start.Dot(looking) > thresh && e.End.Dot(looking) > thresh).ToArray(); Dictionary <H3.Cell.Edge, int> edgeDict = edges.ToDictionary(e => e, e => 1); H3.RemoveDanglingEdgesRecursive(edgeDict); edges = edgeDict.Keys.ToArray(); } else { Mobius m = Mobius.Scale(2); edges = edges.Select(e => { Vector3D v1 = e.Start; Vector3D v2 = e.End; v1 = H3Models.Ball.ApplyMobius(m, v1); v2 = H3Models.Ball.ApplyMobius(m, v2); return(new H3.Cell.Edge(v1, v2)); }).ToArray(); Dictionary <H3.Cell.Edge, int> edgeDict = edges.ToDictionary(e => e, e => 1); H3.RemoveDanglingEdgesRecursive(edgeDict); edges = edgeDict.Keys.ToArray(); } } //H3.m_settings.Output = H3.Output.STL; //H3.m_settings.Scale = 50; H3.SaveToFile(fileName, edges, finite: true, append: true); bool doCells = false; H3.Cell[] cellsToHighlight = null; if (doCells) { int[] polyMirrors = new int[] { 1, 2, 3 }; active = active.Select(i => mapMirror(i)).OrderBy(i => i).ToArray(); H3.Cell startingCell = PolyhedronToHighlight(Geometry.Hyperbolic, polyMirrors, simplex, startingPoint); cellsToHighlight = Recurse.CalcCells(simplex.Facets, new H3.Cell[] { startingCell }); H3.AppendFacets(fileName, cellsToHighlight); } return(edges); }
private static void HyperidealSquares() { Mobius rot = new Mobius(); rot.Isometry(Geometry.Spherical, Math.PI / 4, new Vector3D()); List <Segment> segs = new List <Segment>(); int[] qs = new int[] { 5, -1 }; foreach (int q in qs) { TilingConfig config = new TilingConfig(4, q, 1); Tile t = Tiling.CreateBaseTile(config); List <Segment> polySegs = t.Boundary.Segments; polySegs = polySegs.Select(s => { s.Transform(rot); return(s); }).ToList(); segs.AddRange(polySegs); } Vector3D v1 = new Vector3D(1, 0); v1.RotateXY(Math.PI / 6); Vector3D v2 = v1; v2.Y *= -1; Vector3D cen; double rad; H3Models.Ball.OrthogonalCircle(v1, v2, out cen, out rad); Segment seg = Segment.Arc(v1, v2, cen, false); rot.Isometry(Geometry.Spherical, Math.PI / 2, new Vector3D()); for (int i = 0; i < 4; i++) { seg.Transform(rot); segs.Add(seg.Clone()); } SVG.WriteSegments("output1.svg", segs); System.Func <Segment, Segment> PoincareToKlein = s => { return(Segment.Line( HyperbolicModels.PoincareToKlein(s.P1), HyperbolicModels.PoincareToKlein(s.P2))); }; segs = segs.Select(s => PoincareToKlein(s)).ToList(); Vector3D v0 = new Vector3D(v1.X, v1.X); Vector3D v3 = v0; v3.Y *= -1; Segment seg1 = Segment.Line(v0, v1), seg2 = Segment.Line(v2, v3); Segment seg3 = Segment.Line(new Vector3D(1, 1), new Vector3D(1, -1)); for (int i = 0; i < 4; i++) { seg1.Transform(rot); seg2.Transform(rot); seg3.Transform(rot); segs.Add(seg1.Clone()); segs.Add(seg2.Clone()); segs.Add(seg3.Clone()); } SVG.WriteSegments("output2.svg", segs); }