/// <summary> /// Finds the intersection (a circle) between us and another sphere. /// Returns null if sphere centers are coincident or no intersection exists. /// Does not currently work for planes. /// </summary> public Circle3D Intersection(Sphere s) { if (this.IsPlane || s.IsPlane) { throw new System.NotImplementedException(); } double r = s.Radius; double R = this.Radius; Vector3D diff = this.Center - s.Center; double d = diff.Abs(); if (Tolerance.Zero(d) || d > r + R) { return(null); } double x = (d * d + r * r - R * R) / (2 * d); double y = Math.Sqrt(r * r - x * x); Circle3D result = new Circle3D(); diff.Normalize(); result.Normal = diff; result.Center = s.Center + diff * x; result.Radius = y; return(result); }
public H3.Cell.Edge[] Hyperboloid() { // Draw to circles of fibers, then twist them. List<H3.Cell.Edge> fiberList = new List<H3.Cell.Edge>(); Vector3D cen = new Vector3D( 0, 0, 0.5 ); double rad = .3; Circle3D c1 = new Circle3D { Center = cen, Radius = rad }; Circle3D c2 = new Circle3D { Center = -cen, Radius = rad }; int n = 50; Vector3D[] points1 = c1.Subdivide( n ); Vector3D[] points2 = c2.Subdivide( n ); double twist = 2 * Math.PI / 3; for( int i = 0; i < points2.Length; i++ ) { points2[i].RotateXY( twist ); Vector3D e1, e2; H3Models.Ball.GeodesicIdealEndpoints( points1[i], points2[i], out e1, out e2 ); e1 = Transform( e1 ); e2 = Transform( e2 ); fiberList.Add( new H3.Cell.Edge( e1, e2 ) ); } return fiberList.ToArray(); }
private static Circle3D HoneycombEdgeUHS(int p, int q, int r) { Sphere[] simplex = Mirrors(p, q, r, moveToBall: false); Sphere s1 = simplex[0].Clone(); Sphere s2 = s1.Clone(); s2.Reflect(simplex[1]); Circle3D intersection = s1.Intersection(s2); return(intersection); }
public static Circle3D FromCenterAnd2Points( Vector3D cen, Vector3D p1, Vector3D p2 ) { Circle3D circle = new Circle3D(); circle.Center = cen; circle.Radius = ( p1 - cen ).Abs(); if( !Tolerance.Equal( circle.Radius, ( p2 - cen ).Abs() ) ) throw new System.ArgumentException( "Points are not on the same circle." ); Vector3D normal = ( p2 - cen ).Cross( p1 - cen ); normal.Normalize(); circle.Normal = normal; return circle; }
/// <summary> /// NOTE: Not general, and assumes some things we know about this problem domain, /// e.g. that c1 and c2 live on the same sphere of radius 1, and have two intersection points. /// </summary> public static void IntersectionCircleCircle( Vector3D sphereCenter, Circle3D c1, Circle3D c2, out Vector3D i1, out Vector3D i2 ) { // Spherical analogue of our flat circle-circle intersection. // Spherical pythagorean theorem for sphere where r=1: cos(hypt) = cos(A)*cos(B) Circle3D clone1 = c1.Clone(), clone2 = c2.Clone(); //clone1.Center -= sphereCenter; //clone2.Center -= sphereCenter; // Great circle (denoted by normal vector), and distance between the centers. Vector3D gc = clone2.Normal.Cross( clone1.Normal ); double d = clone2.Normal.AngleTo( clone1.Normal ); double r1 = clone1.Normal.AngleTo( clone1.PointOnCircle ); double r2 = clone2.Normal.AngleTo( clone2.PointOnCircle ); // Calculate distances we need. So ugly! // http://www.wolframalpha.com/input/?i=cos%28r1%29%2Fcos%28r2%29+%3D+cos%28x%29%2Fcos%28d-x%29%2C+solve+for+x double t1 = Math.Pow( Math.Tan( d / 2 ), 2 ); double t2 = Math.Cos( r1 ) / Math.Cos( r2 ); double t3 = Math.Sqrt( (t1 + 1) * (t1 * t2 * t2 + 2 * t1 * t2 + t1 + t2 * t2 - 2 * t2 + 1) ) - 2 * t1 * t2; double x = 2 * Math.Atan( t3 / (t1 * t2 + t1 - t2 + 1) ); double y = Math.Acos( Math.Cos( r1 ) / Math.Cos( x ) ); i1 = clone1.Normal; i1.RotateAboutAxis( gc, x ); i2 = i1; // Perpendicular to gc through i1. Vector3D gc2 = i1.Cross( gc ); i1.RotateAboutAxis( gc2, y ); i2.RotateAboutAxis( gc2, -y ); i1 += sphereCenter; i2 += sphereCenter; /* // It would be nice to do the spherical analogue of circle-circle intersections, like here: // http://mathworld.wolfram.com/Circle-CircleIntersection.html // But I don't want to jump down that rabbit hole and am going to sacrifice some speed to use // my existing euclidean function. // Stereographic projection to the plane. XXX - Crap, circles may become lines, and this isn't being handled well. Circle3D c1Plane = H3Models.BallToUHS( clone1 ); Circle3D c2Plane = H3Models.BallToUHS( clone2 ); if( 2 != Euclidean2D.IntersectionCircleCircle( c1Plane.ToFlatCircle(), c2Plane.ToFlatCircle(), out i1, out i2 ) ) throw new System.Exception( "Expected two intersection points" ); i1 = H3Models.UHSToBall( i1 ); i1 += sphereCenter; i2 = H3Models.UHSToBall( i2 ); i2 += sphereCenter; */ }
/// <summary> /// Returns null if no intersection, or a Tuple. /// Currently does not work in tangent case. /// </summary> public System.Tuple <Vector3D, Vector3D> Intersection(Circle3D c) { Circle3D intersectionCircle = Intersection(new Sphere() { Center = c.Center, Radius = c.Radius }); if (intersectionCircle == null) { return(null); } Vector3D vCross = c.Normal.Cross(intersectionCircle.Normal); vCross.Normalize(); vCross *= intersectionCircle.Radius; return(new System.Tuple <Vector3D, Vector3D>(intersectionCircle.Center + vCross, intersectionCircle.Center - vCross)); }
public static Circle3D FromCenterAnd2Points(Vector3D cen, Vector3D p1, Vector3D p2) { Circle3D circle = new Circle3D(); circle.Center = cen; circle.Radius = (p1 - cen).Abs(); if (!Tolerance.Equal(circle.Radius, (p2 - cen).Abs())) { throw new System.ArgumentException("Points are not on the same circle."); } Vector3D normal = (p2 - cen).Cross(p1 - cen); normal.Normalize(); circle.Normal = normal; return(circle); }
/// <summary> /// Finds the intersection (a circle) between us and another sphere. /// Returns null if sphere centers are coincident or no intersection exists. /// Does not currently work for planes. /// </summary> public Circle3D Intersection(Sphere s) { if (this.IsPlane || s.IsPlane) { throw new System.NotImplementedException(); } double r = s.Radius; double R = this.Radius; Vector3D diff = this.Center - s.Center; double d = diff.Abs(); if (Tolerance.Equal(d, r + R)) { diff.Normalize(); return(new Circle3D() { Center = s.Center + diff * s.Radius, Radius = 0 }); } if (Tolerance.Zero(d) || d > r + R) { return(null); } // Sphere's inside spheres and not touching. //if( d < Math.Abs( R - r ) ) // return null; double x = (d * d + r * r - R * R) / (2 * d); double y = Math.Sqrt(r * r - x * x); Circle3D result = new Circle3D(); diff.Normalize(); result.Normal = diff; result.Center = s.Center + diff * x; result.Radius = y; return(result); }
/// <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(); }
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); }
/// <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 ) { // 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.04; // 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 ); }
/// <summary> /// Given a geodesic circle, find the point closest to the origin. /// </summary> public static Vector3D ClosestToOrigin( Circle3D c ) { Sphere s = new Sphere { Center = c.Center, Radius = c.Radius }; return ClosestToOrigin( s ); }
/// <summary> /// Returns intersection points between a circle and a great circle. /// There may be 0, 1, or 2 intersection points. /// Returns false if the circle is the same as gc. /// </summary> static bool IntersectionCircleGC( Circle3D c, Vector3D gc, List<Vector3D> iPoints ) { double radiusCosAngle = CosAngle( c.Normal, c.PointOnCircle ); double radiusAngle = Math.Acos( radiusCosAngle ); double radius = radiusAngle; // Since sphere radius is 1. // Find the great circle perpendicular to gc, and through the circle center. Vector3D gcPerp = c.Normal.Cross( gc ); if( !gcPerp.Normalize() ) { // Circles are parallel => Zero or infinity intersections. if( Tolerance.Equal( radius, Math.PI / 2 ) ) return false; return true; } // Calculate the offset angle from the circle normal to the gc normal. double offsetAngle = c.Normal.AngleTo( gc ); if( Tolerance.GreaterThan( offsetAngle, Math.PI / 2 ) ) { gc *= -1; offsetAngle = c.Normal.AngleTo( gc ); } double coAngle = Math.PI / 2 - offsetAngle; // No intersections. if( radiusAngle < coAngle ) return true; // Here is the perpendicular point on the great circle. Vector3D pointOnGC = c.Normal; pointOnGC.RotateAboutAxis( gcPerp, coAngle ); // 1 intersection. if( Tolerance.Equal( radiusAngle, coAngle ) ) { iPoints.Add( pointOnGC ); return true; } // 2 intersections. // Spherical pythagorean theorem // http://en.wikipedia.org/wiki/Pythagorean_theorem#Spherical_geometry // We know the hypotenuse and one side. We need the third leg. // We do this calculation on a unit sphere, to get the result as a normalized cosine of an angle. double sideCosA = radiusCosAngle / Math.Cos( coAngle ); double rot = Math.Acos( sideCosA ); Vector3D i1 = pointOnGC, i2 = pointOnGC; i1.RotateAboutAxis( gc, rot ); i2.RotateAboutAxis( gc, -rot ); iPoints.Add( i1 ); iPoints.Add( i2 ); Circle3D test = new Circle3D { Normal = gc, Center = new Vector3D(), Radius = 1 }; return true; }
static bool IsGC( Circle3D c ) { return c.Center.IsOrigin; }
public static Circle3D BallToUHS( Circle3D c ) { Vector3D[] points = c.RepresentativePoints; for( int i=0; i<3; i++ ) points[i] = H3Models.BallToUHS( points[i] ); return new Circle3D( points[0], points[1], points[2] ); }
/// <summary> /// Finds the intersection (a circle) between us and another sphere. /// Returns null if sphere centers are coincident or no intersection exists. /// Does not currently work for planes. /// </summary> public Circle3D Intersection( Sphere s ) { if( this.IsPlane || s.IsPlane ) throw new System.NotImplementedException(); double r = s.Radius; double R = this.Radius; Vector3D diff = this.Center - s.Center; double d = diff.Abs(); if( Tolerance.Zero( d ) || d > r + R ) return null; double x = ( d*d + r*r - R*R ) / ( 2*d ); double y = Math.Sqrt( r*r - x*x ); Circle3D result = new Circle3D(); diff.Normalize(); result.Normal = diff; result.Center = s.Center + diff * x; result.Radius = y; return result; }
/// <summary> /// Find the sphere defined by 3 points on the unit sphere, and orthogonal to the unit sphere. /// Returns null if points are not on the unit sphere. /// </summary> public static Sphere OrthogonalSphere( Vector3D b1, Vector3D b2, Vector3D b3 ) { Sphere unitSphere = new Sphere(); if( !unitSphere.IsPointOn( b1 ) || !unitSphere.IsPointOn( b2 ) || !unitSphere.IsPointOn( b3 ) ) return null; Circle3D c = new Circle3D( b1, b2, b3 ); // Same impl as orthogonal circles now. Vector3D center; double radius; OrthogonalCircle( b1, b1 + ( c.Center - b1 ) * 2, out center, out radius ); Sphere sphere = new Sphere(); if( Infinity.IsInfinite( radius ) ) { // Have the center act as a normal. sphere.Center = c.Normal; sphere.Radius = double.PositiveInfinity; } else { sphere.Center = center; sphere.Radius = radius; } return sphere; }
/// <summary> /// Given 2 points in the interior of the ball, calculate the center and radius of the orthogonal circle. /// One point may optionally be on the boundary, but one shoudl be in the interior. /// If both points are on the boundary, we'll fall back on our other method. /// </summary> public static void OrthogonalCircleInterior( Vector3D v1, Vector3D v2, out Circle3D circle ) { if( Tolerance.Equal( v1.Abs(), 1 ) && Tolerance.Equal( v2.Abs(), 1 ) ) { circle = OrthogonalCircle( v1, v2 ); return; } // http://www.math.washington.edu/~king/coursedir/m445w06/ortho/01-07-ortho-to3.html // http://www.youtube.com/watch?v=Bkvo09KE1zo Vector3D interior = Tolerance.Equal( v1.Abs(), 1 ) ? v2 : v1; Sphere ball = new Sphere(); Vector3D reflected = ball.ReflectPoint( interior ); circle = new Circle3D( reflected, v1, v2 ); }
/// <summary> /// Given 2 points on the boundary of a circle, calculate the orthogonal circle. /// </summary> public static Circle3D OrthogonalCircle( Circle3D c, Vector3D v1, Vector3D v2 ) { // Needs testing. throw new System.NotImplementedException(); // Move/Scale c to unit circle. Vector3D offset = c.Center; double scale = c.Radius; v1 -= offset; v2 -= offset; v1 /= scale; v2 /= scale; // Call the other method. Vector3D center; double rad; OrthogonalCircle( v1, v2, out center, out rad ); rad *= scale; center += offset; return new Circle3D() { Center = center, Radius = rad }; }
/// <summary> /// This is a 2D function for now. /// Given an input geodesic in the plane, returns an equidistant circle. /// Offset would be the offset at the origin. /// </summary> public static Circle3D EquidistantOffset( Segment seg, double offset ) { // Get the ideal endpoints of the input. Vector3D i1, i2; H3Models.Ball.GeodesicIdealEndpoints( seg.P1, seg.P2, out i1, out i2 ); // Get the offset amount at the location. Vector3D center; double radius; H3Models.Ball.OrthogonalCircle( i1, i2, out center, out radius ); Vector3D closest = center; closest.Normalize(); closest *= (center.Abs() - radius); H3Models.Ball.DupinCyclideSphere( closest, offset, out center, out radius ); double mag = center.Abs() - radius; closest.Normalize(); closest *= mag; // Resulting circle will go through i1 and i2, but will be offset slightly towards the origin. Circle3D result = new Circle3D( i1, closest, i2 ); return result; }
/// <summary> /// Given two points (in the UHS model), find the endpoints /// of the associated geodesic that lie on the z=0 plane. /// </summary> public static void GeodesicIdealEndpoints( Vector3D v1, Vector3D v2, out Vector3D z1, out Vector3D z2 ) { // We have to special case when geodesic is vertical (parallel to z axis). Vector3D diff = v2 - v1; Vector3D diffFlat = new Vector3D( diff.X, diff.Y ); if( Tolerance.Zero( diffFlat.Abs() ) ) // Vertical { Vector3D basePoint = new Vector3D( v1.X, v1.Y ); z1 = diff.Z > 0 ? basePoint : Infinity.InfinityVector; z2 = diff.Z < 0 ? basePoint : Infinity.InfinityVector; } else { if( Tolerance.Zero( v1.Z ) && Tolerance.Zero( v2.Z ) ) { z1 = v1; z2 = v2; return; } // If one point is ideal, we need to not reflect that one! bool swapped = false; if( Tolerance.Zero( v1.Z ) ) { Utils.SwapPoints( ref v1, ref v2 ); swapped = true; } Vector3D v1_reflected = v1; v1_reflected.Z *= -1; Circle3D c = new Circle3D( v1_reflected, v1, v2 ); Vector3D radial = v1 - c.Center; radial.Z = 0; if( !radial.Normalize() ) { radial = v2 - c.Center; radial.Z = 0; if( !radial.Normalize() ) System.Diagnostics.Debugger.Break(); } radial *= c.Radius; z1 = c.Center + radial; z2 = c.Center - radial; // Make sure the order will be right. // (z1 closest to v1 along arc). if( v1.Dist( z1 ) > v2.Dist( z1 ) ) Utils.SwapPoints( ref z1, ref z2 ); if( swapped ) Utils.SwapPoints( ref z1, ref z2 ); } }
/// <summary> /// Calculate the surfaces of the tetrahedron for the quadrilateral /// </summary> private static Sphere[] Tetrahedron( Vector3D[] quad ) { // NOTE: For planes, the convention is that the normal vector points "outward" // The only non-planar sphere is incident with the last two points of the quad, // as well as the antipode of the last point. Fig 1 in Lawson's paper. Circle3D c = new Circle3D( quad[3], quad[2], -quad[3] ); Vector3D plane3 = quad[3]; plane3.RotateXY( Math.PI / 2 ); return new Sphere[] { Sphere.Plane( new Vector3D( 0, -1 ) ), Sphere.Plane( new Vector3D( 0, 0, -1 ) ), Sphere.Plane( plane3 ), new Sphere( c.Center, c.Radius ) }; }
/// <summary> /// This applies the the Mobius transform to the plane at infinity. /// Any Mobius is acceptable. /// NOTE: s must be geodesic! (orthogonal to boundary). /// </summary> public static Sphere TransformInUHS( Sphere s, Mobius m ) { Vector3D s1, s2, s3; if( s.IsPlane ) { // It must be vertical (because it is orthogonal). Vector3D direction = s.Normal; direction.RotateXY( Math.PI / 2 ); s1 = s.Offset; s2 = s1 + direction; s3 = s1 - direction; } else { Vector3D offset = new Vector3D( s.Radius, 0, 0 ); s1 = s.Center + offset; s2 = s.Center - offset; s3 = offset; s3.RotateXY( Math.PI / 2 ); s3 += s.Center; } Vector3D b1 = m.Apply( s1 ); Vector3D b2 = m.Apply( s2 ); Vector3D b3 = m.Apply( s3 ); Circle3D boundaryCircle = new Circle3D( b1, b2, b3 ); Vector3D cen = boundaryCircle.Center; Vector3D off = new Vector3D(); if( Infinity.IsInfinite( boundaryCircle.Radius ) ) { boundaryCircle.Radius = double.PositiveInfinity; Vector3D normal = b2 - b1; normal.Normalize(); normal.RotateXY( -Math.PI / 2 ); // XXX - The direction isn't always correct. cen = normal; off = Euclidean2D.ProjectOntoLine( new Vector3D(), b1, b2 ); } return new Sphere { Center = cen, Radius = boundaryCircle.Radius, Offset = off }; }
/// <summary> /// Had to break the intersection method into separate cases (depending on when circles are great circles or not), /// because the spherical pythagorean theorem breaks down in GC cases. /// </summary> public static bool IntersectionSmart( Vector3D sphereCenter, Circle3D c1, Circle3D c2, out Vector3D i1, out Vector3D i2 ) { i1 = i2 = Vector3D.DneVector(); Circle3D clone1 = c1.Clone(), clone2 = c2.Clone(); clone1.Center -= sphereCenter; clone2.Center -= sphereCenter; if( IsGC( clone1 ) && IsGC( clone2 ) ) { if( !IntersectionGCGC( clone1.Normal, clone2.Normal, out i1, out i2 ) ) return false; } else if( IsGC( clone1 ) || IsGC( clone2 ) ) { bool firstIsGC = IsGC( clone1 ); Vector3D gc = firstIsGC ? clone1.Normal : clone2.Normal; Circle3D c = firstIsGC ? clone2 : clone1; List<Vector3D> iPoints = new List<Vector3D>(); if( !IntersectionCircleGC( c, gc, iPoints ) ) return false; if( iPoints.Count != 2 ) throw new System.NotImplementedException(); i1 = iPoints[0]; i2 = iPoints[1]; } else throw new System.NotImplementedException(); // Move us back to the sphere center. i1 += sphereCenter; i2 += sphereCenter; return true; }
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 ) return SimplexCalcs.MirrorsSpherical( p, q, r ); 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. Sphere[] result = surfaces.Select( s => moveToBall ? H3Models.UHSToBall( s ) : s ).ToArray(); cellCenter = moveToBall ? H3Models.UHSToBall( cellCenter ) : cellCenter; return result; }