public static double GetNormalizedCircumRadius(int p, double q) { double hypot = GetTriangleHypotenuse(p, q); switch (Geometry2D.GetGeometry(p, q)) { case Geometry.Spherical: return(Spherical2D.s2eNorm(hypot) * DiskRadius); case Geometry.Euclidean: return(EuclideanHypotenuse); case Geometry.Hyperbolic: { if (Infinity.IsInfinite(hypot)) { return(DiskRadius); } return(DonHatch.h2eNorm(hypot) * DiskRadius); } } Debug.Assert(false); return(1); }
/// <summary> /// Calculates the point of our simplex that is at the middle of a face. /// </summary> private static Vector3D FaceCenterBall(int p, int q, int r) { Geometry cellGeometry = Geometry2D.GetGeometry(p, q); switch (cellGeometry) { case Geometry.Spherical: { Vector3D cellCenter = CellCenterBall(p, q, r); Sphere[] mirrors = Mirrors(p, q, r, moveToBall: true); return(mirrors[0].ProjectToSurface(cellCenter)); } case Geometry.Euclidean: { Sphere[] mirrors = Mirrors(p, q, r, moveToBall: false); Vector3D faceCenterUHS = mirrors[0].Center; faceCenterUHS.Z += mirrors[0].Radius; return(H3Models.UHSToBall(faceCenterUHS)); } case Geometry.Hyperbolic: { throw new System.NotImplementedException(); } } throw new System.ArgumentException(); }
/// <summary> /// Calculates the point of our simplex that is at the center of a cell. /// </summary> private static Vector3D CellCenterBall(int p, int q, int r) { Geometry cellGeometry = Geometry2D.GetGeometry(p, q); switch (cellGeometry) { case Geometry.Spherical: { return(new Vector3D()); } case Geometry.Euclidean: { return(new Vector3D(0, 0, 1)); } case Geometry.Hyperbolic: { //throw new System.NotImplementedException(); return(new Vector3D(0, 0, 1)); } } throw new System.ArgumentException(); }
/// <summary> /// Calculates the point of our simplex that is at a vertex. /// </summary> public static Vector3D VertexPointBall(int p, int q, int r) { Geometry vertexGeometry = Geometry2D.GetGeometry(q, r); if (vertexGeometry == Geometry.Hyperbolic) { // Outside the ball, and not in a good way. Use the Klein version. //throw new System.NotImplementedException(); return(new Vector3D(0, 0, -1)); } // Get in UHS first. Sphere cellFacet = Mirrors(p, q, r, moveToBall: false).First(); double rSquared = Math.Pow(cellFacet.Radius, 2); double cSquared = Math.Pow(cellFacet.Center.Abs(), 2); Vector3D uhs; if (Tolerance.Equal(rSquared, cSquared)) // e.g. 363 { uhs = new Vector3D(); } else { double height = Math.Sqrt(rSquared - cSquared); uhs = new Vector3D(0, 0, height); } return(H3Models.UHSToBall(uhs)); }
public TilingConfig(int p, int q) : this() { if (Geometry2D.GetGeometry(p, q) != Geometry.Spherical) { throw new System.ArgumentException(); } SetupConfig(p, q, PlatonicSolids.NumFacets(p, q)); }
/// <summary> /// Get the side length opposite angle PI/P, /// In the induced geometry. /// </summary> public static double GetTrianglePSide(int p, int q) { Geometry g = Geometry2D.GetGeometry(p, q); double alpha = Math.PI / 2; double beta = Math.PI / q; double gamma = Math.PI / p; // The one we want. if (g == Geometry.Euclidean) { return(EuclideanHypotenuse * Math.Sin(gamma)); } return(GetTriangleSide(g, gamma, beta, alpha)); }
public static Mobius FCOrientMobius(int p, int q) { Vector3D p1, p2, p3; Segment seg = null; TilePoints(p, q, out p1, out p2, out p3, out seg); Geometry cellGeometry = Geometry2D.GetGeometry(p, q); p1.RotateXY(Math.PI / 2); // XXX - repeated from above. Implement a better way to sequence transformations. Mobius m = new Mobius(); m.Isometry(cellGeometry, 0, -p1); return(m); }
/// <summary> /// Get the side length opposite angle PI/Q, /// In the induced geometry. /// </summary> public static double GetTriangleQSide(int p, int q) { Geometry g = Geometry2D.GetGeometry(p, q); double alpha = Math.PI / 2; double beta = PiOverNSafe(q); // The one we want. double gamma = PiOverNSafe(p); if (g == Geometry.Euclidean) { return(EuclideanHypotenuse * Math.Sin(beta)); } return(GetTriangleSide(g, beta, gamma, alpha)); }
public static Vector3D VertexPointKlein(int p, int q, int r) { Geometry vertexGeometry = Geometry2D.GetGeometry(q, r); if (vertexGeometry != Geometry.Hyperbolic) { throw new System.NotImplementedException(); } Sphere[] mirrors = Mirrors(p, q, r); Sphere klein = H3Models.BallToKlein(mirrors[0]); Vector3D off = klein.Offset; double h = off.Abs() / Math.Cos(off.AngleTo(new Vector3D(0, 0, -1))); return(new Vector3D(0, 0, -h)); }
public static Mobius VertexCenteredMobius(int p, int q) { double angle = Math.PI / q; if (Utils.Even(q)) { angle *= 2; } Vector3D offset = new Vector3D(-1 * Geometry2D.GetNormalizedCircumRadius(p, q), 0, 0); offset.RotateXY(angle); Mobius m = new Mobius(); m.Isometry(Geometry2D.GetGeometry(p, q), angle, offset.ToComplex()); return(m); }
/// <summary> /// In the induced geometry. /// </summary> public static double GetTriangleHypotenuse(int p, int q) { Geometry g = Geometry2D.GetGeometry(p, q); if (g == Geometry.Euclidean) { return(EuclideanHypotenuse); } // We have a 2,q,p triangle, where the right angle alpha // is opposite the hypotenuse (the length we want). double alpha = Math.PI / 2; double beta = Math.PI / q; double gamma = Math.PI / p; return(GetTriangleSide(g, alpha, beta, gamma)); }
/// <summary> /// This Mobius transform will center the tiling on an edge. /// </summary> public Mobius EdgeMobius() { Geometry g = Geometry2D.GetGeometry(this.P, this.Q); Polygon poly = new Polygon(); poly.CreateRegular(this.P, this.Q); Segment seg = poly.Segments[0]; Vector3D offset = seg.Midpoint; double angle = Math.PI / this.P; offset.RotateXY(-angle); Mobius m = new Mobius(); m.Isometry(g, -angle, -offset); return(m); }
public static double GetNormalizedCircumRadius(int p, int q) { Geometry g = Geometry2D.GetGeometry(p, q); double hypot = GetTriangleHypotenuse(p, q); switch (g) { case Geometry.Spherical: return(Spherical2D.s2eNorm(hypot) * DiskRadius); case Geometry.Euclidean: return(EuclideanHypotenuse); case Geometry.Hyperbolic: return(DonHatch.h2eNorm(hypot) * DiskRadius); } Debug.Assert(false); return(1); }
/// <summary> /// Calculates the point of our simplex that is at the middle of an edge. /// </summary> private static Vector3D MidEdgePointBall(int p, int q, int r) { // We need the mid-radius, but we have to do the calculation // with our Euclidean simplex mirrors (to avoid infinities that happen in the formulas). Circle3D edge = HoneycombEdgeUHS(p, q, r); if (edge.Radius == 0) { return(edge.Center); } Geometry cellGeometry = Geometry2D.GetGeometry(p, q); switch (cellGeometry) { case Geometry.Spherical: { Sphere sphereInBall = H3Models.UHSToBall(new Sphere() { Center = edge.Center, Radius = edge.Radius }); Vector3D mid = sphereInBall.ProjectToSurface(new Vector3D()); // Project origin to sphere. return(mid); } case Geometry.Euclidean: { Vector3D mid = H3Models.UHSToBall(edge.Center + new Vector3D(0, 0, edge.Radius)); return(mid); } case Geometry.Hyperbolic: { throw new System.NotImplementedException(); } } throw new System.ArgumentException(); }
/// <summary> /// Calculates the 3 mirrors connected to the cell center. /// This works in all geometries and returns results in the UHS model (or the appropriate analogue). /// </summary> private static Sphere[] InteriorMirrors(int p, int q) { // Some construction points we need. Vector3D p1, p2, p3; Segment seg = null; TilePoints(p, q, out p1, out p2, out p3, out seg); Geometry cellGeometry = Geometry2D.GetGeometry(p, q); // XZ-plane Sphere s1 = new Sphere() { Center = new Vector3D(0, 1, 0), Radius = double.PositiveInfinity }; Sphere s2 = null; if (cellGeometry == Geometry.Euclidean) { s2 = new Sphere() { Center = -p2, Offset = p2, Radius = double.PositiveInfinity }; } else if ( cellGeometry == Geometry.Spherical || cellGeometry == Geometry.Hyperbolic) { s2 = new Sphere() { Center = seg.Center, Radius = seg.Radius, //Invert = true // XXX - maybe we don't invert in the case of spherical? }; // j34 /*double off = seg.Center.Abs() - seg.Radius; * off *= 1.05; * Vector3D vOff = seg.Center; * vOff.Normalize(); * vOff *= off; * s2 = H3Models.Ball.OrthogonalSphereInterior( vOff );*/ // 4j3 /*double off = seg.Center.Abs() - seg.Radius; * off = DonHatch.h2eNorm( DonHatch.e2hNorm( off ) + m_jOffset ); * Vector3D vOff = seg.Center; * vOff.Normalize(); * vOff *= off; * s2 = H3Models.Ball.OrthogonalSphereInterior( vOff );*/ } Sphere s3; if (Infinite(p) && Infinite(q) /*&& FiniteOrInfinite( r )*/) { Vector3D tempCenter = seg.Center; tempCenter.X *= -1; s3 = new Sphere() { Center = tempCenter, Radius = seg.Radius, //Invert = true, }; } else { s3 = new Sphere() { Center = p3, Radius = double.PositiveInfinity }; } // The reason for the special case ordering is to make sure // the last mirror is always the cell-reflecting mirror of the dual honeycomb. // The order we want are the mirrors opposite: cell, face, edge, vertex // NOTE: This also puts the mirrors in the same order as in standard presentations, // e.g. from Coxeter's 57-cell paper. Sphere[] surfaces; if (!Infinite(p) && Infinite(q)) { surfaces = new Sphere[] { s2, s1, s3 } } ; else { surfaces = new Sphere[] { s3, s1, s2 } }; return(surfaces); }
public void CreateRegular(int numSides, int q) { int p = numSides; Segments.Clear(); List <Vector3D> points = new List <Vector3D>(); Geometry g = Geometry2D.GetGeometry(p, q); double circumRadius = Geometry2D.GetNormalizedCircumRadius(p, q); double angle = 0; for (int i = 0; i < p; i++) { Vector3D point = new Vector3D(); point.X = (circumRadius * Math.Cos(angle)); point.Y = (circumRadius * Math.Sin(angle)); points.Add(point); angle += Utils.DegreesToRadians(360.0 / p); } // Turn this into segments. for (int i = 0; i < points.Count; i++) { int idx1 = i; int idx2 = i == points.Count - 1 ? 0 : i + 1; Segment newSegment = new Segment(); newSegment.P1 = points[idx1]; newSegment.P2 = points[idx2]; if (g != Geometry.Euclidean) { newSegment.Type = SegmentType.Arc; if (2 == p) { // Our magic formula below breaks down for digons. double factor = Math.Tan(Math.PI / 6); newSegment.Center = newSegment.P1.X > 0 ? new Vector3D(0, -circumRadius, 0) * factor : new Vector3D(0, circumRadius, 0) * factor; } else { // Our segments are arcs in Non-Euclidean geometries. // Magically, the same formula turned out to work for both. // (Maybe this is because the Poincare Disc model of the // hyperbolic plane is stereographic projection as well). double piq = q == -1 ? 0 : Math.PI / q; // Handle q infinite. double t1 = Math.PI / p; double t2 = Math.PI / 2 - piq - t1; double factor = (Math.Tan(t1) / Math.Tan(t2) + 1) / 2; newSegment.Center = (newSegment.P1 + newSegment.P2) * factor; } newSegment.Clockwise = Geometry.Spherical == g ? false : true; } // XXX - Make this configurable? // This is the color of cell boundary lines. //newSegment.m_color = CColor( 1, 1, 0, 1 ); Segments.Add(newSegment); } }
public static Sphere[] Mirrors(int p, int q, int r, ref Vector3D cellCenter, bool moveToBall = true, double scaling = -1) { Geometry g = Util.GetGeometry(p, q, r); if (g == Geometry.Spherical) { // These are in the ball model. Sphere[] result = SimplexCalcs.MirrorsSpherical(p, q, r); return(result); } else if (g == Geometry.Euclidean) { return(SimplexCalcs.MirrorsEuclidean()); } // This is a rotation we'll apply to the mirrors at the end. // This is to try to make our image outputs have vertical bi-lateral symmetry and the most consistent in all cases. // NOTE: + is CW, not CCW. (Because the way I did things, our images have been reflected vertically, and I'm too lazy to go change this.) double rotation = Math.PI / 2; // Some construction points we need. Vector3D p1, p2, p3; Segment seg = null; TilePoints(p, q, out p1, out p2, out p3, out seg); // // Construct in UHS // Geometry cellGeometry = Geometry2D.GetGeometry(p, q); Vector3D center = new Vector3D(); double radius = 0; if (cellGeometry == Geometry.Spherical) { // Finite or Infinite r // Spherical trig double halfSide = Geometry2D.GetTrianglePSide(q, p); double mag = Math.Sin(halfSide) / Math.Cos(Util.PiOverNSafe(r)); mag = Math.Asin(mag); // e.g. 43j //mag *= 0.95; // Move mag to p1. mag = Spherical2D.s2eNorm(mag); H3Models.Ball.DupinCyclideSphere(p1, mag, Geometry.Spherical, out center, out radius); } else if (cellGeometry == Geometry.Euclidean) { center = p1; radius = p1.Dist(p2) / Math.Cos(Util.PiOverNSafe(r)); } else if (cellGeometry == Geometry.Hyperbolic) { if (Infinite(p) && Infinite(q) && FiniteOrInfinite(r)) { //double iiiCellRadius = 2 - Math.Sqrt( 2 ); //Circle3D iiiCircle = new Circle3D() { Center = new Vector3D( 1 - iiiCellRadius, 0, 0 ), Radius = iiiCellRadius }; //radius = iiiCellRadius; // infinite r //center = new Vector3D( 1 - radius, 0, 0 ); // For finite r, it was easier to calculate cell facet in a more symmetric position, // then move into position with the other mirrors via a Mobius transformation. double rTemp = 1 / (Math.Cos(Util.PiOverNSafe(r)) + 1); Mobius m = new Mobius(); m.Isometry(Geometry.Hyperbolic, -Math.PI / 4, new Vector3D(0, Math.Sqrt(2) - 1)); Vector3D c1 = m.Apply(new Vector3D(1 - 2 * rTemp, 0, 0)); Vector3D c2 = c1; c2.Y *= -1; Vector3D c3 = new Vector3D(1, 0); Circle3D c = new Circle3D(c1, c2, c3); radius = c.Radius; center = c.Center; } else if (Infinite(p) && Finite(q) && FiniteOrInfinite(r)) { // http://www.wolframalpha.com/input/?i=r%2Bx+%3D+1%2C+sin%28pi%2Fp%29+%3D+r%2Fx%2C+solve+for+r // radius = 2 * Math.Sqrt( 3 ) - 3; // Appolonian gasket wiki page //radius = Math.Sin( Math.PI / q ) / ( Math.Sin( Math.PI / q ) + 1 ); //center = new Vector3D( 1 - radius, 0, 0 ); // For finite r, it was easier to calculate cell facet in a more symmetric position, // then move into position with the other mirrors via a Mobius transformation. double rTemp = 1 / (Math.Cos(Util.PiOverNSafe(r)) + 1); Mobius m = new Mobius(); m.Isometry(Geometry.Hyperbolic, 0, p2); Vector3D findingAngle = m.Inverse().Apply(new Vector3D(1, 0)); double angle = Math.Atan2(findingAngle.Y, findingAngle.X); m.Isometry(Geometry.Hyperbolic, angle, p2); Vector3D c1 = m.Apply(new Vector3D(1 - 2 * rTemp, 0, 0)); Vector3D c2 = c1; c2.Y *= -1; Vector3D c3 = new Vector3D(1, 0); Circle3D c = new Circle3D(c1, c2, c3); radius = c.Radius; center = c.Center; } else if (Finite(p) && Infinite(q) && FiniteOrInfinite(r)) { radius = p2.Abs(); // infinite r radius = DonHatch.asinh(Math.Sinh(DonHatch.e2hNorm(p2.Abs())) / Math.Cos(Util.PiOverNSafe(r))); // hyperbolic trig // 4j3 //m_jOffset = radius * 0.02; //radius += m_jOffset ; radius = DonHatch.h2eNorm(radius); center = new Vector3D(); rotation *= -1; } else if (/*Finite( p ) &&*/ Finite(q)) { // Infinite r //double mag = Geometry2D.GetTrianglePSide( q, p ); // Finite or Infinite r double halfSide = Geometry2D.GetTrianglePSide(q, p); double mag = DonHatch.asinh(Math.Sinh(halfSide) / Math.Cos(Util.PiOverNSafe(r))); // hyperbolic trig H3Models.Ball.DupinCyclideSphere(p1, DonHatch.h2eNorm(mag), out center, out radius); } else { throw new System.NotImplementedException(); } } Sphere cellBoundary = new Sphere() { Center = center, Radius = radius }; Sphere[] interior = InteriorMirrors(p, q); Sphere[] surfaces = new Sphere[] { cellBoundary, interior[0], interior[1], interior[2] }; // Apply rotations. bool applyRotations = true; if (applyRotations) { foreach (Sphere s in surfaces) { RotateSphere(s, rotation); } p1.RotateXY(rotation); } // Apply scaling bool applyScaling = scaling != -1; if (applyScaling) { //double scale = 1.0/0.34390660467269524; //scale = 0.58643550768408892; foreach (Sphere s in surfaces) { Sphere.ScaleSphere(s, scaling); } } bool facetCentered = false; if (facetCentered) { PrepForFacetCentering(p, q, surfaces, ref cellCenter); } // Move to ball if needed. if (moveToBall) { surfaces = MoveToBall(surfaces, ref cellCenter); } return(surfaces); }
/// <summary> /// Returns the 6 simplex edges in the UHS model. /// </summary> public static H3.Cell.Edge[] SimplexEdgesUHS(int p, int q, int r) { // Only implemented for honeycombs with hyperideal cells right now. if (!(Geometry2D.GetGeometry(p, q) == Geometry.Hyperbolic)) { throw new System.NotImplementedException(); } Sphere[] simplex = SimplexCalcs.Mirrors(p, q, r, moveToBall: false); Circle[] circles = simplex.Select(s => H3Models.UHS.IdealCircle(s)).ToArray(); Vector3D[] defPoints = new Vector3D[6]; Vector3D dummy; Euclidean2D.IntersectionLineCircle(circles[1].P1, circles[1].P2, circles[0], out defPoints[0], out dummy); Euclidean2D.IntersectionLineCircle(circles[2].P1, circles[2].P2, circles[0], out defPoints[1], out dummy); Euclidean2D.IntersectionLineCircle(circles[1].P1, circles[1].P2, circles[3], out defPoints[2], out dummy); Euclidean2D.IntersectionLineCircle(circles[2].P1, circles[2].P2, circles[3], out defPoints[3], out dummy); Circle3D c = simplex[0].Intersection(simplex[3]); Vector3D normal = c.Normal; normal.RotateXY(Math.PI / 2); Vector3D intersection; double height, off; Euclidean2D.IntersectionLineLine(c.Center, c.Center + normal, circles[1].P1, circles[1].P2, out intersection); off = (intersection - c.Center).Abs(); height = Math.Sqrt(c.Radius * c.Radius - off * off); intersection.Z = height; defPoints[4] = intersection; Euclidean2D.IntersectionLineLine(c.Center, c.Center + normal, circles[2].P1, circles[2].P2, out intersection); off = (intersection - c.Center).Abs(); height = Math.Sqrt(c.Radius * c.Radius - off * off); intersection.Z = height; defPoints[5] = intersection; // Hyperideal vertex too? bool order = false; H3.Cell.Edge[] edges = null; if (Geometry2D.GetGeometry(q, r) == Geometry.Hyperbolic) { edges = new H3.Cell.Edge[] { new H3.Cell.Edge(new Vector3D(), new Vector3D(0, 0, 10)), new H3.Cell.Edge(defPoints[4], defPoints[5], order), new H3.Cell.Edge(defPoints[0], defPoints[4], order), new H3.Cell.Edge(defPoints[1], defPoints[5], order), new H3.Cell.Edge(defPoints[2], defPoints[4], order), new H3.Cell.Edge(defPoints[3], defPoints[5], order), }; } else { Vector3D vPointUHS = H3Models.BallToUHS(VertexPointBall(p, q, r)); defPoints[0] = defPoints[1] = vPointUHS; edges = new H3.Cell.Edge[] { new H3.Cell.Edge(vPointUHS, new Vector3D(0, 0, 10)), new H3.Cell.Edge(defPoints[4], defPoints[5], order), new H3.Cell.Edge(defPoints[0], defPoints[4], order), new H3.Cell.Edge(defPoints[1], defPoints[5], order), new H3.Cell.Edge(defPoints[2], defPoints[4], order), new H3.Cell.Edge(defPoints[3], defPoints[5], order), }; } return(edges); }