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); }
private static Isometry SetupIsometry(Cell clickedCell, Vector3D clickedPoint, Puzzle puzzle) { int p = puzzle.Config.P; Geometry g = puzzle.Config.Geometry; Isometry cellIsometry = clickedCell.Isometry.Clone(); // Take out reflections. // ZZZ - Figuring out how to deal with these reflected isometries was a bit painful to figure out. // I wish I had just taken more care to not have any cell isometries with reflections. // Maybe I can rework that to be different at some point. if (cellIsometry.Reflection != null) { cellIsometry = Isometry.ReflectX() * cellIsometry; } // Round to nearest vertex. Vector3D centered = cellIsometry.Apply(clickedPoint); double angle = Euclidean2D.AngleToCounterClock(centered, new Vector3D(1, 0)); double angleFromZeroToP = p * angle / (2 * Math.PI); angleFromZeroToP = Math.Round(angleFromZeroToP, 0); if (p == (int)angleFromZeroToP) { angleFromZeroToP = 0; } angle = 2 * Math.PI * angleFromZeroToP / p; // This will take vertex to canonical position. Mobius rotation = new Mobius(); rotation.Isometry(g, angle, new Complex()); Isometry rotIsometry = new Isometry(rotation, null); return(rotIsometry * cellIsometry); }
/// <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> /// Generate a catenoid, then move it to a point on the hemisphere in S^3. /// </summary> private static Mesh Catenoid(double scale, Vector3D loc, double rld_height, double waist) { double h = waist * 3.5; //Mesh mesh = StandardCatenoid( waist, h ); Mesh mesh = CatenoidSquared(waist, h); mesh.Scale(scale * rld_height * 2 / h); // To make the catenoid meet up with the RLD solution. // First move to north pole, then rotate down to the location. Func <Vector3D, Vector3D> transform = v => { v = H3Models.BallToUHS(v); Vector3D northPole = new Vector3D(0, 0, 1); Vector3D axis = loc.Cross(northPole); if (!axis.Normalize()) // North or south pole? { return(v); } double anglXY = Euclidean2D.AngleToCounterClock(new Vector3D(1, 0), new Vector3D(loc.X, loc.Y)); v.RotateXY(anglXY); double angleDown = loc.AngleTo(northPole); v.RotateAboutAxis(axis, angleDown); if (v.DNE || Infinity.IsInfinite(v)) { throw new System.Exception(); } return(v); }; mesh.Transform(transform); return(mesh); }
/// <summary>s /// Checks if the point is inside our hexagon, /// but takes advantage of things we know to make it faster. /// The generic polygon check is way too slow. /// This is meant to be used during puzzle building. /// </summary> /// <param name="p"></param> /// <returns></returns> public bool IsPointInsideOptimized(Vector3D p) { if (!CircumCircle.IsPointInsideFast(p)) { return(false); } // We will check that we are on the same side of every segment as the center. Vector3D cen = Hexagon.Center; foreach (Segment s in Hexagon.Segments) { if (s.Type == SegmentType.Line) { if (!Euclidean2D.SameSideOfLine(s.P1, s.P2, cen, p)) { return(false); } } else { bool inside1 = s.Circle.IsPointInside(cen); bool inside2 = s.Circle.IsPointInside(p); if (inside1 ^ inside2) { return(false); } } } return(true); }
private static string FormatSphereNoMaterialOffset(Sphere sphere, bool invert, bool includeClosingBracket = true) { bool microOffset = true; double microOff = 0.00001; //microOff = 0.000001; // Don't offset unless drawn!!! //microOff = -0.00005; if (invert) { microOff *= -1; } if (sphere.IsPlane) { Vector3D offsetOnNormal = Euclidean2D.ProjectOntoLine(sphere.Offset, new Vector3D(), sphere.Normal); double offset = offsetOnNormal.Abs(); if (offsetOnNormal.Dot(sphere.Normal) < 0) { offset *= -1; } if (microOffset) { offset -= microOff; } return(string.Format("plane {{ {0}, {1:G6}{2} {3}", FormatVec(sphere.Normal), offset, invert ? " inverse" : string.Empty, includeClosingBracket ? "}" : string.Empty)); } else { double radius = sphere.Radius; if (microOffset) { if (radius < 20) { radius -= microOff; } else { radius *= (1 - microOff); } } return(string.Format("sphere {{ {0}, {1:G6}{2} {3}", FormatVec(sphere.Center), radius, invert ? " inverse" : string.Empty, includeClosingBracket ? "}" : string.Empty)); } }
private static string H3Facet(Sphere sphere) { if (sphere.IsPlane) { Vector3D offsetOnNormal = Euclidean2D.ProjectOntoLine(sphere.Offset, new Vector3D(), sphere.Normal); return(string.Format("plane {{ {0}, {1:G6} material {{ sphereMat }} clipped_by {{ ball }} }}", FormatVec(sphere.Normal), offsetOnNormal.Abs())); } else { return(string.Format("sphere {{ {0}, {1:G6} material {{ sphereMat }} clipped_by {{ ball }} }}", FormatVec(sphere.Center), sphere.Radius)); } }
private static string FormatSphereNoMaterial(Sphere sphere, bool invert, bool includeClosingBracket = true) { if (sphere.IsPlane) { Vector3D offsetOnNormal = Euclidean2D.ProjectOntoLine(sphere.Offset, new Vector3D(), sphere.Normal); return(string.Format("plane {{ {0}, {1:G6}{2} {3}", FormatVec(sphere.Normal), offsetOnNormal.Abs(), invert ? " inverse" : string.Empty, includeClosingBracket ? "}" : string.Empty)); } else { return(string.Format("sphere {{ {0}, {1:G6}{2} {3}", FormatVec(sphere.Center), sphere.Radius, invert ? " inverse" : string.Empty, includeClosingBracket ? "}" : string.Empty)); } }
/// <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); } } }
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; }
/// <summary> /// This will return an altered facet to create true apparent 2D tilings (proper bananas) on the boundary. /// Notes: /// The input simplex must be in the ball model. /// The first mirror of the simplex (the one that mirrors across cells) is the one we end up altering. /// </summary> public static Sphere AlteredFacetForTrueApparent2DTilings(Sphere[] simplex) { //Sphere m = H3Models.BallToUHS( simplex[0] ); //Sphere t = new Sphere() { Center = m.Center, Radius = m.Radius*100 }; //return H3Models.UHSToBall( t ); // We first need to find the size of the apparent 2D disk surrounding the leg. // This is also the size of the apparent cell head disk of the dual. // So we want to get the midsphere (insphere would also work) of the dual cell with that head, // then calculate the intersection of that with the boundary. Sphere cellMirror = simplex[0]; if (cellMirror.IsPlane) { throw new System.NotImplementedException(); } // The point centered on a face is the closest point of the cell mirror to the origin. // This will be the point centered on an edge on the dual cell. Vector3D facePoint = cellMirror.ProjectToSurface(new Vector3D()); // Reflect it to get 3 more points on our midsphere. Vector3D reflected1 = simplex[1].ReflectPoint(facePoint); Vector3D reflected2 = simplex[2].ReflectPoint(reflected1); Vector3D reflected3 = simplex[0].ReflectPoint(reflected2); Sphere midSphere = Sphere.From4Points(facePoint, reflected1, reflected2, reflected3); // Get the ideal circles of the cell mirror and midsphere. // Note: The midsphere is not geodesic, so we can't calculate it the same. Sphere cellMirrorUHS = H3Models.BallToUHS(cellMirror); Circle cellMirrorIdeal = H3Models.UHS.IdealCircle(cellMirrorUHS); Sphere ball = new Sphere(); Circle3D midSphereIdealBall = ball.Intersection(midSphere); // This should exist because we've filtered for honeycombs with hyperideal verts. Circle3D midSphereIdealUHS = H3Models.BallToUHS(midSphereIdealBall); Circle midSphereIdeal = new Circle { Center = midSphereIdealUHS.Center, Radius = midSphereIdealUHS.Radius }; // The intersection point of our cell mirror and the disk of the apparent 2D tiling // gives us "ideal" points on the apparent 2D boundary. These points will be on the new cell mirror. Vector3D i1, i2; if (2 != Euclidean2D.IntersectionCircleCircle(cellMirrorIdeal, midSphereIdeal, out i1, out i2)) { //throw new System.ArgumentException( "Since we have hyperideal vertices, we should have an intersection with 2 points." ); // Somehow works in the euclidean case. // XXX - Make this better. return(H3Models.UHSToBall(new Sphere() { Center = Vector3D.DneVector(), Radius = double.NaN })); } double bananaThickness = 0.025; //bananaThickness = 0.15; bananaThickness = 0.05; // Transform the intersection points to a standard Poincare disk. // The midsphere radius is the scale of the apparent 2D tilings. double scale = midSphereIdeal.Radius; Vector3D offset = midSphereIdeal.Center; i1 -= offset; i2 -= offset; i1 /= scale; i2 /= scale; Circle3D banana = H3Models.Ball.OrthogonalCircle(i1, i2); Vector3D i3 = H3Models.Ball.ClosestToOrigin(banana); i3 = Hyperbolic2D.Offset(i3, -bananaThickness); // Transform back. i1 *= scale; i2 *= scale; i3 *= scale; i1 += offset; i2 += offset; i3 += offset; // Construct our new simplex mirror with these 3 points. Circle3D c = new Circle3D(i1, i2, i3); Sphere result = new Sphere() { Center = c.Center, Radius = c.Radius }; return(H3Models.UHSToBall(result)); }