public static void DrawHyperbolicGeodesic(CircleNE c, Color color, System.Func <Vector3D, Vector3D> transform) { GL.Color3(color); Segment seg = null; if (c.IsLine) { // It will go through the origin. Vector3D p = c.P1; p.Normalize(); seg = Segment.Line(p, -p); } else { Vector3D p1, p2; Euclidean2D.IntersectionCircleCircle(c, new Circle(), out p1, out p2); seg = Segment.Arc(p1, H3Models.Ball.ClosestToOrigin(new Circle3D() { Center = c.Center, Radius = c.Radius, Normal = new Vector3D(0, 0, 1) }), p2); } DrawSeg(seg, 75, transform); }
/// <summary> /// Builds a segment going through the box (if we cross it). /// </summary> private static Segment BuildSegment(CircleNE c) { Vector3D[] iPoints = Box.GetIntersectionPoints(c); if (2 != iPoints.Length) { return(null); } if (c.IsLine) { return(Segment.Line(iPoints[0], iPoints[1])); } // Find the midpoint. Probably a better way. Vector3D t1 = iPoints[0] - c.Center; Vector3D t2 = iPoints[1] - c.Center; double angle1 = Euclidean2D.AngleToCounterClock(t1, t2); double angle2 = Euclidean2D.AngleToClock(t1, t2); Vector3D mid1 = t1, mid2 = t1; mid1.RotateXY(angle1 / 2); mid2.RotateXY(-angle2 / 2); mid1 += c.Center; mid2 += c.Center; Vector3D mid = mid1; if (mid2.Abs() < mid1.Abs()) { mid = mid2; } return(Segment.Arc(iPoints[0], mid, iPoints[1])); }
/// <summary> /// Slices a polygon by a circle with some thickness. /// Input circle may be a line. /// </summary> /// <remarks>The input polygon might get reversed</remarks> public static void SlicePolygon( Polygon p, CircleNE c, Geometry g, double thickness, out List<Polygon> output ) { output = new List<Polygon>(); // Setup the two slicing circles. CircleNE c1 = c.Clone(), c2 = c.Clone(); Mobius m = new Mobius(); Vector3D pointOnCircle = c.IsLine ? c.P1 : c.Center + new Vector3D( c.Radius, 0 ); m.Hyperbolic2( g, c1.CenterNE, pointOnCircle, thickness / 2 ); c1.Transform( m ); m.Hyperbolic2( g, c2.CenterNE, pointOnCircle, -thickness / 2 ); c2.Transform( m ); // ZZZ - alter Clip method to work on Polygons and use that. // Slice it up. List<Polygon> sliced1, sliced2; Slicer.SlicePolygon( p, c1, out sliced1 ); Slicer.SlicePolygon( p, c2, out sliced2 ); // Keep the ones we want. foreach( Polygon newPoly in sliced1 ) { bool outside = !c1.IsPointInsideNE( newPoly.CentroidApprox ); if( outside ) output.Add( newPoly ); } foreach( Polygon newPoly in sliced2 ) { bool inside = c2.IsPointInsideNE( newPoly.CentroidApprox ); if( inside ) output.Add( newPoly ); } }
/// <summary> /// This will setup systolic pants for the Klein Quartic. /// </summary> public static CircleNE[] SystolesForKQ() { // 0th vertex of the central heptagon will be the center of our first hexagon. // Arnaud's applet is helpful to think about this. // http://www.math.univ-toulouse.fr/~cheritat/AppletsDivers/Klein/ Polygon centralTile = new Polygon(); centralTile.CreateRegular(7, 3); Vector3D vertex0 = centralTile.Segments[0].P1; Vector3D mid1 = centralTile.Segments[1].Midpoint; Vector3D mid2 = centralTile.Segments[2].Midpoint; Circle3D orthogonal; H3Models.Ball.OrthogonalCircleInterior(mid1, mid2, out orthogonal); // We make the non-euclidean center the center of the hexagon. CircleNE c1 = new CircleNE() { Center = orthogonal.Center, Radius = orthogonal.Radius, CenterNE = vertex0 }; return(CycleCircles(c1, vertex0)); }
public Cell(Polygon boundary, CircleNE vertexCircle) { Boundary = boundary; VertexCircle = vertexCircle; Stickers = new List <Sticker>(); Isometry = new Isometry(); IndexOfMaster = -1; PickInfo = new PickInfo[] { }; }
private static CircleNE[] CycleCircles(CircleNE template, Vector3D vertex0) { Mobius m = RotMobius(vertex0); List <CircleNE> result = new List <CircleNE>(); result.Add(template); for (int i = 0; i < 3; i++) { CircleNE next = result.Last().Clone(); next.Transform(m); result.Add(next); } return(result.ToArray()); }
/// <summary> /// Helper to check if we will affect a cell. /// </summary> public bool WillAffectCell(Cell cell, bool sphericalPuzzle) { if (Earthquake) { if (Pants.TestCircle.HasVertexInside(cell.Boundary)) { return(true); } for (int i = 0; i < 3; i++) { CircleNE c = Pants.TestCircle.Clone(); c.Reflect(Pants.Hexagon.Segments[i * 2]); if (c.HasVertexInside(cell.Boundary)) { return(true); } } return(false); /* This turned out too slow, and probably not robust too. * * if( Pants.Hexagon.Intersects( cell.Boundary ) ) * return true; * * foreach( Polygon poly in Pants.AdjacentHexagons ) * if( poly.Intersects( cell.Boundary ) ) * return true; * * return false; */ } foreach (CircleNE circleNE in this.Circles) { bool inside = sphericalPuzzle ? circleNE.IsPointInsideNE(cell.Center) : circleNE.IsPointInsideFast(cell.Center); if (inside || circleNE.Intersects(cell.Boundary)) { return(true); } } return(false); }
/// <summary> /// Draws a generalized circle (may be a line) in OpenGL immediate mode, /// and safely handle circles with large radii. /// This is slower, so we only want to use it when necessary. /// </summary> public static void DrawCircleSafe(CircleNE c, Color color, System.Func <Vector3D, Vector3D> transform) { GL.Color3(color); if (c.IsLine) { Vector3D start = Euclidean2D.ProjectOntoLine(new Vector3D(), c.P1, c.P2); Vector3D d = c.P2 - c.P1; d.Normalize(); d *= 50; if (transform == null) { GL.Begin(BeginMode.Lines); GL.Vertex2(start.X + d.X, start.Y + d.Y); GL.Vertex2(start.X - d.X, start.Y - d.Y); GL.End(); } else { int divisions = 500; Vector3D begin = start + d, end = start - d, inc = (end - begin) / divisions; GL.Begin(BeginMode.LineStrip); for (int i = 0; i < divisions; i++) { Vector3D point = begin + inc * i; GL.Vertex2(point.X, point.Y); } GL.End(); } } else { Segment seg = BuildSegment(c); if (seg == null) { DrawCircleInternal(c, color, 500, transform); } else { DrawSeg(seg, 1000, transform); } } }
/// <summary> /// Helper to check if we will affect a sticker, and cache in our list if so. /// </summary> public void WillAffectSticker(Sticker sticker, bool sphericalPuzzle) { if (AffectedStickers == null) { AffectedStickers = new List <StickerList>(); for (int slice = 0; slice < this.NumSlices; slice++) { AffectedStickers.Add(new List <Sticker>()); } } if (Earthquake) { Vector3D cen = sticker.Poly.Center; if (Pants.IsPointInsideOptimized(cen)) { AffectedStickers[0].Add(sticker); } return; } // Slices are ordered by depth. // We cycle from the inner slice outward. for (int slice = 0; slice < this.Circles.Length; slice++) { CircleNE circle = this.Circles[slice]; bool isInside = sphericalPuzzle ? circle.IsPointInsideNE(sticker.Poly.Center) : circle.IsPointInsideFast(sticker.Poly.Center); if (isInside) { AffectedStickers[slice].Add(sticker); return; } } // If we made it here for spherical puzzles, we are in the last slice. // Second check was needed for {3,5} 8C if (sphericalPuzzle && (this.NumSlices != this.Circles.Length)) { AffectedStickers[this.NumSlices - 1].Add(sticker); } }
private static CircleNE[] OtherThreeSides() { Polygon centralTile = new Polygon(); centralTile.CreateRegular(7, 3); Vector3D vertex0 = centralTile.Segments[0].P1; Vector3D p1 = centralTile.Segments[3].P1; Vector3D p2 = centralTile.Segments[3].P2; Circle3D orthogonal; H3Models.Ball.OrthogonalCircleInterior(p1, p2, out orthogonal); // We make the non-euclidean center the center of the hexagon. CircleNE c1 = new CircleNE() { Center = orthogonal.Center, Radius = orthogonal.Radius, CenterNE = vertex0 }; CircleNE[] otherThreeSides = CycleCircles(c1, vertex0); return(otherThreeSides); }
public void SetupHexagonForKQ() { Polygon centralTile = new Polygon(); centralTile.CreateRegular(7, 3); Vector3D vertex0 = centralTile.Segments[0].P1; CircleNE[] otherThreeSides = OtherThreeSides(); CircleNE[] systoles = SystolesForKQ(); // Calc verts. List <Vector3D> verts = new List <Vector3D>(); Vector3D t1, t2; Euclidean2D.IntersectionCircleCircle(otherThreeSides[0], systoles[0], out t1, out t2); Vector3D intersection = t1.Abs() < 1 ? t1 : t2; verts.Add(intersection); intersection.Y *= -1; verts.Add(intersection); Mobius m = RotMobius(vertex0); verts.Add(m.Apply(verts[0])); verts.Add(m.Apply(verts[1])); verts.Add(m.Apply(verts[2])); verts.Add(m.Apply(verts[3])); // Setup all the segments. bool clockwise = true; Hexagon.Segments.AddRange(new Segment[] { Segment.Arc(verts[0], verts[1], otherThreeSides[0].Center, clockwise), Segment.Arc(verts[1], verts[2], systoles[1].Center, clockwise), Segment.Arc(verts[2], verts[3], otherThreeSides[1].Center, clockwise), Segment.Arc(verts[3], verts[4], systoles[2].Center, clockwise), Segment.Arc(verts[4], verts[5], otherThreeSides[2].Center, clockwise), Segment.Arc(verts[5], verts[0], systoles[0].Center, clockwise), }); Hexagon.Center = vertex0; // Setup the test circle. m.Isometry(Geometry.Hyperbolic, 0, -vertex0); Polygon clone = Hexagon.Clone(); clone.Transform(m); Circle temp = new Circle( clone.Segments[0].Midpoint, clone.Segments[2].Midpoint, clone.Segments[4].Midpoint); CircleNE tempNE = new CircleNE(temp, new Vector3D()); tempNE.Transform(m.Inverse()); TestCircle = tempNE; temp = new Circle( clone.Segments[0].P1, clone.Segments[1].P1, clone.Segments[2].P1); tempNE = new CircleNE(temp, new Vector3D()); tempNE.Transform(m.Inverse()); CircumCircle = tempNE; }
private void CalculateFromTwoPolygonsInternal( Polygon home, Polygon boundary, CircleNE homeVertexCircle, Geometry g ) { // ZZZ - We have to use the boundary, but that can be projected to infinity for some of the spherical tilings. // Trying to use the Drawn tile produced weird (yet interesting) results. Polygon poly1 = boundary; Polygon poly2 = home; if( poly1.Segments.Count < 3 || poly2.Segments.Count < 3 ) // Poor poor digons. { Debug.Assert( false ); return; } // Same? Vector3D p1 = poly1.Segments[0].P1, p2 = poly1.Segments[1].P1, p3 = poly1.Segments[2].P1; Vector3D w1 = poly2.Segments[0].P1, w2 = poly2.Segments[1].P1, w3 = poly2.Segments[2].P1; if( p1 == w1 && p2 == w2 && p3 == w3 ) { this.Mobius = Mobius.Identity(); return; } Mobius m = new Mobius(); m.MapPoints( p1, p2, p3, w1, w2, w3 ); this.Mobius = m; // Worry about reflections as well. if( g == Geometry.Spherical ) { // If inverted matches the orientation, we need a reflection. bool inverted = poly1.IsInverted; if( !(inverted ^ poly1.Orientation) ) this.Reflection = homeVertexCircle; } else { if( !poly1.Orientation ) this.Reflection = homeVertexCircle; } // Some testing. Vector3D test = this.Apply( boundary.Center ); if( test != home.Center ) { // ZZZ: What is happening here is that the mobius can project a point to infinity before the reflection brings it back to the origin. // It hasn't been much of a problem in practice yet, but will probably need fixing at some point. //Trace.WriteLine( "oh no!" ); } }
private void CalculateFromTwoPolygonsInternal(Polygon home, Polygon boundary, CircleNE homeVertexCircle, Geometry g) { // ZZZ - We have to use the boundary, but that can be projected to infinity for some of the spherical tilings. // Trying to use the Drawn tile produced weird (yet interesting) results. Polygon poly1 = boundary; Polygon poly2 = home; if (poly1.Segments.Count < 3 || poly2.Segments.Count < 3) // Poor poor digons. { Debug.Assert(false); return; } // Same? Vector3D p1 = poly1.Segments[0].P1, p2 = poly1.Segments[1].P1, p3 = poly1.Segments[2].P1; Vector3D w1 = poly2.Segments[0].P1, w2 = poly2.Segments[1].P1, w3 = poly2.Segments[2].P1; if (p1 == w1 && p2 == w2 && p3 == w3) { this.Mobius = Mobius.Identity(); return; } Mobius m = new Mobius(); m.MapPoints(p1, p2, p3, w1, w2, w3); this.Mobius = m; // Worry about reflections as well. if (g == Geometry.Spherical) { // If inverted matches the orientation, we need a reflection. bool inverted = poly1.IsInverted; if (!(inverted ^ poly1.Orientation)) { this.Reflection = homeVertexCircle; } } else { if (!poly1.Orientation) { this.Reflection = homeVertexCircle; } } // Some testing. Vector3D test = this.Apply(boundary.Center); if (test != home.Center) { // ZZZ: What is happening here is that the mobius can project a point to infinity before the reflection brings it back to the origin. // It hasn't been much of a problem in practice yet, but will probably need fixing at some point. //Trace.WriteLine( "oh no!" ); } }