/// <summary> /// Move from a point p1 -> p2 along a geodesic. /// Also somewhat from Don. /// factor can be used to only go some fraction of the distance from p1 to p2. /// </summary> public void Geodesic(Geometry g, Complex p1, Complex p2, double factor = 1.0) { Mobius t = new Mobius(); t.Isometry(g, 0, p1 * -1); Complex p2t = t.Apply(p2); // Only implemented for hyperbolic so far. if (factor != 1.0 && g == Geometry.Hyperbolic) { double newMag = DonHatch.h2eNorm(DonHatch.e2hNorm(p2t.Magnitude) * factor); Vector3D temp = Vector3D.FromComplex(p2t); temp.Normalize(); temp *= newMag; p2t = temp.ToComplex(); } Mobius m1 = new Mobius(), m2 = new Mobius(); m1.Isometry(g, 0, p1 * -1); m2.Isometry(g, 0, p2t); Mobius m3 = m1.Inverse(); this = m3 * m2 * m1; }
public static Mobius CreateFromIsometry(Geometry g, double angle, Complex P) { Mobius m = new Mobius(); m.Isometry(g, angle, P); return(m); }
/// <summary> /// Allow a hyperbolic transformation using an absolute offset. /// offset is specified in the respective geometry. /// </summary> public void Hyperbolic2(Geometry g, Complex fixedPlus, Complex point, double offset) { // To the origin. Mobius m = new Mobius(); m.Isometry(g, 0, fixedPlus * -1); double eRadius = m.Apply(point).Magnitude; double scale = 1; switch (g) { case Geometry.Spherical: double sRadius = Spherical2D.e2sNorm(eRadius); sRadius += offset; scale = Spherical2D.s2eNorm(sRadius) / eRadius; break; case Geometry.Euclidean: scale = (eRadius + offset) / eRadius; break; case Geometry.Hyperbolic: double hRadius = DonHatch.e2hNorm(eRadius); hRadius += offset; scale = DonHatch.h2eNorm(hRadius) / eRadius; break; } Hyperbolic(g, fixedPlus, scale); }
public static Mobius Identity() { Mobius m = new Mobius(); m.Unity(); return(m); }
/// <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 transform will map the z points to the respective w points. /// </summary> public void MapPoints(Complex z1, Complex z2, Complex z3, Complex w1, Complex w2, Complex w3) { Mobius m1 = new Mobius(), m2 = new Mobius(); m1.MapPoints(z1, z2, z3); m2.MapPoints(w1, w2, w3); this = m2.Inverse() * m1; }
/// <summary> /// Returns a new Mobius transformation that is the inverse of us. /// </summary> public Mobius Inverse() { // See http://en.wikipedia.org/wiki/Möbius_transformation Mobius result = new Mobius(D, -B, -C, A); result.Normalize(); return(result); }
/// <summary> /// Add a finite (truncated) banana to our mesh. Passed in edge should be in Ball model. /// </summary> public static void AddBanana( Shapeways mesh, Vector3D e1, Vector3D e2, H3.Settings settings ) { Vector3D e1UHS = H3Models.BallToUHS( e1 ); Vector3D e2UHS = H3Models.BallToUHS( e2 ); // Endpoints of the goedesic on the z=0 plane. Vector3D z1, z2; H3Models.UHS.GeodesicIdealEndpoints( e1UHS, e2UHS, out z1, out z2 ); // XXX - Do we want to do a better job worrying about rotation here? // (multiply by complex number with certain imaginary part as well) //Vector3D z3 = ( z1 + z2 ) / 2; //if( Infinity.IsInfinite( z3 ) ) // z3 = new Vector3D( 1, 0 ); Vector3D z3 = new Vector3D( Math.E, Math.PI ); // This should vary the rotations a bunch. // Find the Mobius we need. // We'll do this in two steps. // (1) Find a mobius taking z1,z2 to origin,inf // (2) Deal with scaling e1 to a height of 1. Mobius m1 = new Mobius( z1, z3, z2 ); Vector3D e1UHS_transformed = m1.ApplyToQuaternion( e1UHS ); double scale = 1.0 / e1UHS_transformed.Z; Mobius m2 = Mobius.Scale( scale ); Mobius m = m2 * m1; // Compose them (multiply in reverse order). Vector3D e2UHS_transformed = m.ApplyToQuaternion( e2UHS ); // Make our truncated cone. // For regular tilings, we really would only need to do this once for a given LOD. List<Vector3D> points = new List<Vector3D>(); double logHeight = Math.Log( e2UHS_transformed.Z ); if( logHeight < 0 ) throw new System.Exception( "impl issue" ); int div1, div2; H3Models.Ball.LOD_Finite( e1, e2, out div1, out div2, settings ); double increment = logHeight / div1; for( int i=0; i<=div1; i++ ) { double h = increment * i; // This is to keep different bananas from sharing exactly coincident vertices. double tinyOffset = 0.001; if( i == 0 ) h -= tinyOffset; if( i == div1 ) h += tinyOffset; Vector3D point = new Vector3D( 0, 0, Math.Exp( h ) ); points.Add( point ); } Shapeways tempMesh = new Shapeways(); tempMesh.Div = div2; tempMesh.AddCurve( points.ToArray(), v => H3Models.UHS.SizeFunc( v, settings.AngularThickness ) ); // Unwind the transforms. TakePointsBack( tempMesh.Mesh, m.Inverse(), settings ); mesh.Mesh.Triangles.AddRange( tempMesh.Mesh.Triangles ); }
/// <summary> /// Applies an isometry to a complex number. /// </summary> public Complex Apply(Complex z) { z = Mobius.Apply(z); if (Reflection != null) { z = ApplyCachedCircleInversion(z); } return(z); }
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!" ); } }
public static Mobius operator *(Mobius m1, Mobius m2) { Mobius result = new Mobius( m1.A * m2.A + m1.B * m2.C, m1.A * m2.B + m1.B * m2.D, m1.C * m2.A + m1.D * m2.C, m1.C * m2.B + m1.D * m2.D); result.Normalize(); return(result); }
/// <summary> /// Does a circle inversion in an arbitrary, generalized circle. /// IOW, the three points may be collinear, in which case we are talking about a reflection. /// </summary> private void CacheCircleInversion(Complex c1, Complex c2, Complex c3) { Mobius toUnitCircle = new Mobius(); toUnitCircle.MapPoints( c1, c2, c3, new Complex(1, 0), new Complex(-1, 0), new Complex(0, 1)); m_cache1 = toUnitCircle; m_cache2 = m_cache1.Inverse(); }
/// <summary> /// Does a Euclidean reflection across a line. /// </summary> /*public void ReflectAcrossLine( Vector3D p1, Vector3D p2 ) * { * // Do a circle inversion using a generalized circle (third point is at infinity). * //Complex p3 = Complex.ImaginaryOne * Math.Pow( 10, 10 ); * //Vector3D p3 = p1 + (p2 - p1) * Math.Pow( 10, 10 ); * Vector3D p3 = (p1 + p2) / 2; * p3 *= 1000; * //CircleInversion( p1, p2, p3 ); * //CircleInversion( new Complex( 1, 0 ), new Complex( -1, 0 ), new Complex( 0, 1 ) ); * Mobius m1 = new Mobius(); * m1.MapPoints( p1, p2, p3, * new Complex( 1, 0 ), new Complex( -1, 0 ), new Complex( 0, 1 ) ); * * this = m1; * }*/ /// <summary> /// Returns a new Isometry that is the inverse of us. /// </summary> public Isometry Inverse() { Mobius inverse = this.Mobius.Inverse(); if (Reflection == null) { return(new Isometry(inverse, null)); } else { Circle reflection = Reflection.Clone(); reflection.Transform(inverse); return(new Isometry(inverse, reflection)); } }
/// <summary> /// Move from a point p1 -> p2 along a geodesic. /// Also somewhat from Don. /// </summary> public void Geodesic(Geometry g, Complex p1, Complex p2) { Mobius t = new Mobius(); t.Isometry(g, 0, p1 * -1); Complex p2t = t.Apply(p2); Mobius m1 = new Mobius(), m2 = new Mobius(); m1.Isometry(g, 0, p1 * -1); m2.Isometry(g, 0, p2t); Mobius m3 = m1.Inverse(); this = m3 * m2 * m1; }
public void Elliptic(Geometry g, Complex fixedPlus, double angle) { // To the origin. Mobius origin = new Mobius(); origin.Isometry(g, 0, fixedPlus * -1); // Rotate. Mobius rotate = new Mobius(); rotate.Isometry(g, angle, new Complex()); // Conjugate. this = origin.Inverse() * rotate * origin; }
/// <summary> /// Composition operator. /// </summary>> public static Isometry operator *(Isometry i1, Isometry i2) { // ZZZ - Probably a better way. // We'll just apply both isometries to a canonical set of points, // Then calc which isometry makes that. Complex p1 = new Complex(1, 0); Complex p2 = new Complex(-1, 0); Complex p3 = new Complex(0, 1); Complex w1 = p1, w2 = p2, w3 = p3; // Compose (apply in reverse order). w1 = i2.Apply(w1); w2 = i2.Apply(w2); w3 = i2.Apply(w3); w1 = i1.Apply(w1); w2 = i1.Apply(w2); w3 = i1.Apply(w3); Mobius m = new Mobius(); m.MapPoints(p1, p2, p3, w1, w2, w3); Isometry result = new Isometry(); result.Mobius = m; // Need to reflect at end? bool r1 = i1.Reflection != null; bool r2 = i2.Reflection != null; if (r1 ^ r2) // One and only one reflection. { result.Reflection = new Circle( Vector3D.FromComplex(w1), Vector3D.FromComplex(w2), Vector3D.FromComplex(w3)); } return(result); }
public void Hyperbolic(Geometry g, Complex fixedPlus, double scale) { // To the origin. Mobius m1 = new Mobius(); m1.Isometry(g, 0, fixedPlus * -1); // Scale. Mobius m2 = new Mobius(); m2.A = scale; m2.B = m2.C = 0; m2.D = 1; // Back. //Mobius m3 = m1.Inverse(); // Doesn't work well if fixedPlus is on disk boundary. Mobius m3 = new Mobius(); m3.Isometry(g, 0, fixedPlus); // Compose them (multiply in reverse order). this = m3 * m2 * m1; }
/// <summary> /// Helper to apply a Mobius to the ball model. /// Vector is taken to UHS, mobius applied, then taken back. /// </summary> public static Vector3D ApplyMobius( Mobius m, Vector3D v ) { v = BallToUHS( v ); v = m.ApplyToQuaternion( v ); return UHSToBall( v ); }
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!" ); } }
public void Elliptic( Geometry g, Complex fixedPlus, double angle ) { // To the origin. Mobius origin = new Mobius(); origin.Isometry( g, 0, fixedPlus * -1 ); // Rotate. Mobius rotate = new Mobius(); rotate.Isometry( g, angle, new Complex() ); // Conjugate. this = origin.Inverse() * rotate * origin; }
private static Vector3D DiskToUpper( Vector3D input ) { Mobius m = new Mobius(); m.UpperHalfPlane(); return m.Apply( input ); }
public static void TransformInUHS2( Sphere s, Mobius m ) { Sphere newSphere = TransformInUHS( s, m ); s.Center = newSphere.Center; s.Radius = newSphere.Radius; s.Offset = newSphere.Offset; }
public static Mobius operator *( Mobius m1, Mobius m2 ) { Mobius result = new Mobius( m1.A * m2.A + m1.B * m2.C, m1.A * m2.B + m1.B * m2.D, m1.C * m2.A + m1.D * m2.C, m1.C * m2.B + m1.D * m2.D ); result.Normalize(); return result; }
/// <summary> /// Returns a new Mobius transformation that is the inverse of us. /// </summary> public Mobius Inverse() { // See http://en.wikipedia.org/wiki/Möbius_transformation Mobius result = new Mobius( D, -B, -C, A ); result.Normalize(); return result; }
public void Hyperbolic( Geometry g, Complex fixedPlus, double scale ) { // To the origin. Mobius m1 = new Mobius(); m1.Isometry( g, 0, fixedPlus * -1 ); // Scale. Mobius m2 = new Mobius(); m2.A = scale; m2.B = m2.C = 0; m2.D = 1; // Back. //Mobius m3 = m1.Inverse(); // Doesn't work well if fixedPlus is on disk boundary. Mobius m3 = new Mobius(); m3.Isometry( g, 0, fixedPlus ); // Compose them (multiply in reverse order). this = m3 * m2 * m1; }
/// <summary> /// Add an ideal banana to our mesh. Passed in edge should be in Ball model. /// </summary> public static void AddIdealBanana( Shapeways mesh, Vector3D e1, Vector3D e2, H3.Settings settings ) { Vector3D z1 = H3Models.BallToUHS( e1 ); Vector3D z2 = H3Models.BallToUHS( e2 ); // Mobius taking z1,z2 to origin,inf Complex dummy = new Complex( Math.E, Math.PI ); Mobius m = new Mobius( z1, dummy, z2 ); // Make our truncated cone. We need to deal with the two ideal endpoints specially. List<Vector3D> points = new List<Vector3D>(); double logHeight = 2; // XXX - magic number, and going to cause problems for infinity checks if too big. int div1, div2; H3Models.Ball.LOD_Ideal( e1, e2, out div1, out div2, settings ); double increment = logHeight / div1; for( int i=-div1; i<=div1; i+=2 ) points.Add( new Vector3D( 0, 0, Math.Exp( increment * i ) ) ); Shapeways tempMesh = new Shapeways(); tempMesh.Div = div2; System.Func<Vector3D, double> sizeFunc = v => H3Models.UHS.SizeFunc( v, settings.AngularThickness ); //Mesh.OpenCylinder... pass in two ideal endpoints? tempMesh.AddCurve( points.ToArray(), sizeFunc, new Vector3D(), Infinity.InfinityVector ); // Unwind the transforms. TakePointsBack( tempMesh.Mesh, m.Inverse(), settings ); mesh.Mesh.Triangles.AddRange( tempMesh.Mesh.Triangles ); }
private static void AddSymmetryTriangles( Mesh mesh, Tiling tiling, Polygon boundary ) { // Assume template centered at the origin. Polygon template = tiling.Tiles.First().Boundary; List<Triangle> templateTris = new List<Triangle>(); foreach( Segment seg in template.Segments ) { int num = 1 + (int)(seg.Length * m_divisions); Vector3D a = new Vector3D(); Vector3D b = seg.P1; Vector3D c = seg.Midpoint; Vector3D centroid = ( a + b + c ) / 3; Polygon poly = new Polygon(); Segment segA = Segment.Line( new Vector3D(), seg.P1 ); Segment segB = seg.Clone(); segB.P2 = seg.Midpoint; Segment segC = Segment.Line( seg.Midpoint, new Vector3D() ); poly.Segments.Add( segA ); poly.Segments.Add( segB ); poly.Segments.Add( segC ); Vector3D[] coords = TextureHelper.TextureCoords( poly, Geometry.Hyperbolic ); int[] elements = TextureHelper.TextureElements( 3, LOD: 3 ); for( int i = 0; i < elements.Length / 3; i++ ) { int idx1 = i * 3; int idx2 = i * 3 + 1; int idx3 = i * 3 + 2; Vector3D v1 = coords[elements[idx1]]; Vector3D v2 = coords[elements[idx2]]; Vector3D v3 = coords[elements[idx3]]; templateTris.Add( new Triangle( v1, v2, v3 ) ); } /* // Need to shrink a little, so we won't // get intersections among neighboring faces. a = Shrink( a, centroid ); b = Shrink( b, centroid ); c = Shrink( c, centroid ); Vector3D[] list = seg.Subdivide( num * 2 ); list[0] = b; list[list.Length / 2] = c; for( int i = 0; i < list.Length / 2; i++ ) templateTris.Add( new Triangle( centroid, list[i], list[i + 1] ) ); for( int i = num - 1; i >= 0; i-- ) templateTris.Add( new Triangle( centroid, a + (c - a) * (i + 1) / num, a + (c - a) * i / num ) ); for( int i = 0; i < num; i++ ) templateTris.Add( new Triangle( centroid, a + (b - a) * i / num, a + (b - a) * (i + 1) / num ) ); */ } foreach( Tile tile in tiling.Tiles ) { Vector3D a = tile.Boundary.Segments[0].P1; Vector3D b = tile.Boundary.Segments[1].P1; Vector3D c = tile.Boundary.Segments[2].P1; Mobius m = new Mobius(); if( tile.Isometry.Reflected ) m.MapPoints( template.Segments[0].P1, template.Segments[1].P1, template.Segments[2].P1, c, b, a ); else m.MapPoints( template.Segments[0].P1, template.Segments[1].P1, template.Segments[2].P1, a, b, c ); foreach( Triangle tri in templateTris ) { Triangle transformed = new Triangle( m.Apply( tri.a ), m.Apply( tri.b ), m.Apply( tri.c ) ); CheckAndAdd( mesh, transformed, boundary ); } } }
/// <summary> /// Composition operator. /// </summary>> public static Isometry operator *( Isometry i1, Isometry i2 ) { // ZZZ - Probably a better way. // We'll just apply both isometries to a canonical set of points, // Then calc which isometry makes that. Complex p1 = new Complex( 1, 0 ); Complex p2 = new Complex( -1, 0 ); Complex p3 = new Complex( 0, 1 ); Complex w1 = p1, w2 = p2, w3 = p3; // Compose (apply in reverse order). w1 = i2.Apply( w1 ); w2 = i2.Apply( w2 ); w3 = i2.Apply( w3 ); w1 = i1.Apply( w1 ); w2 = i1.Apply( w2 ); w3 = i1.Apply( w3 ); Mobius m = new Mobius(); m.MapPoints( p1, p2, p3, w1, w2, w3 ); Isometry result = new Isometry(); result.Mobius = m; // Need to reflect at end? bool r1 = i1.Reflection != null; bool r2 = i2.Reflection != null; if( r1 ^ r2 ) // One and only one reflection. { result.Reflection = new Circle( Vector3D.FromComplex( w1 ), Vector3D.FromComplex( w2 ), Vector3D.FromComplex( w3 ) ); } return result; }
public Isometry( Mobius m, Circle r ) { Mobius = m; Reflection = r; }
private static Mobius ToUpperHalfPlaneMobius() { Mobius m = new Mobius(); m.UpperHalfPlane(); return m; }
/// <summary> /// Calculate the hyperbolic midpoint of an edge. /// Only works for non-ideal edges at the moment. /// </summary> public static Vector3D Midpoint( H3.Cell.Edge edge ) { // Special case if edge has endpoint on origin. // XXX - Really this should be special case anytime edge goes through origin. Vector3D e1 = edge.Start; Vector3D e2 = edge.End; if( e1.IsOrigin || e2.IsOrigin ) { if( e2.IsOrigin ) Utils.Swap<Vector3D>( ref e1, ref e2 ); return HalfTo( e2 ); } // No doubt there is a much better way, but // work in H2 slice transformed to xy plane, with e1 on x-axis. double angle = e1.AngleTo( e2 ); // always <= 180 e1 = new Vector3D( e1.Abs(), 0 ); e2 = new Vector3D( e2.Abs(), 0 ); e2.RotateXY( angle ); // Mobius that will move e1 to origin. Mobius m = new Mobius(); m.Isometry( Geometry.Hyperbolic, 0, -e1 ); e2 = m.Apply( e2 ); Vector3D midOnPlane = HalfTo( e2 ); midOnPlane= m.Inverse().Apply( midOnPlane ); double midAngle = e1.AngleTo( midOnPlane ); Vector3D mid = edge.Start; mid.RotateAboutAxis( edge.Start.Cross( edge.End ), midAngle ); mid.Normalize( midOnPlane.Abs() ); return mid; }
/// <summary> /// Subdivides a segment from p1->p2 with the two endpoints not on the origin, in the respective geometry. /// </summary> private static Vector3D[] SubdivideSegmentInGeometry( Vector3D p1, Vector3D p2, int divisions, Geometry g ) { // Handle this specially, so we can keep things 3D if needed. if( g == Geometry.Euclidean ) { Segment seg = Segment.Line( p1, p2 ); return seg.Subdivide( divisions ); } Mobius p1ToOrigin = new Mobius(); p1ToOrigin.Isometry( g, 0, -p1 ); Mobius inverse = p1ToOrigin.Inverse(); Vector3D newP2 = p1ToOrigin.Apply( p2 ); Segment radial = Segment.Line( new Vector3D(), newP2 ); Vector3D[] temp = SubdivideRadialInGeometry( radial, divisions, g ); List<Vector3D> result = new List<Vector3D>(); foreach( Vector3D v in temp ) result.Add( inverse.Apply( v ) ); return result.ToArray(); }
/// <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> /// ZZZ - needs to be part of performance setting? /// Returns true if the tile should be included after a Mobius transformation will be applied. /// If the tile is not be included, this method avoids applying the mobious transform to the entire tile. /// </summary> public bool IncludeAfterMobius( Mobius m ) { switch( this.Geometry ) { // Spherical tilings are finite, so we can always include everything. case Geometry.Spherical: return true; case Geometry.Euclidean: return true; // We'll let the number of tiles specified in the tiling control this.. case Geometry.Hyperbolic: { //Polygon poly = Boundary.Clone(); //poly.Transform( m ); //bool use = (poly.Length > 0.01); // ZZZ - DANGER! Some transforms can cause this to lead to stackoverflow (the ones that scale the tiling up). //bool use = ( poly.Length > 0.01 ) && ( poly.Center.Abs() < 10 ); //bool use = ( poly.Center.Abs() < 0.9 ); // Only disk CircleNE c = VertexCircle; bool use = c.CenterNE.Abs() < 0.9999; /*List<Vector3D> points = poly.GetEdgePoints(); double maxdist = points.Max( point => point.Abs() ); bool use = maxdist < 0.97;*/ return use; } } Debug.Assert( false ); return false; }
public Isometry(Mobius m, Circle r) { Mobius = m; Reflection = r; }
/// <summary> /// Apply a Mobius transform to us. /// </summary> public void Transform( Mobius m ) { Boundary.Transform( m ); Drawn.Transform( m ); VertexCircle.Transform( m ); }
public static Mobius Identity() { Mobius m = new Mobius(); m.Unity(); return m; }
/// <summary> /// This will trim back the tile using an equidistant curve. /// It assumes the tile is at the origin. /// </summary> internal static void ShrinkTile( ref Tile tile, double shrinkFactor ) { // This code is not correct in non-Euclidean cases! // But it works reasonable well for small shrink factors. // For example, you can easily use this function to grow a hyperbolic tile beyond the disk. Mobius m = new Mobius(); m.Hyperbolic( tile.Geometry, new Vector3D(), shrinkFactor ); tile.Drawn.Transform( m ); return; /* // ZZZ // Wow, all the work I did below was subsumed by 4 code lines above! // I can't bring myself to delete it yet. switch( tile.Geometry ) { case Geometry.Spherical: { List<Tile> clipped = new List<Tile>(); clipped.Add( tile ); Polygon original = tile.Drawn.Clone(); foreach( Segment seg in original.Segments ) { Debug.Assert( seg.Type == SegmentType.Arc ); if( true ) { // Unproject to sphere. Vector3D p1 = Spherical2D.PlaneToSphere( seg.P1 ); Vector3D p2 = Spherical2D.PlaneToSphere( seg.P2 ); // Get the poles of the GC, and project them to the plane. Vector3D pole1, pole2; Spherical2D.GreatCirclePole( p1, p2, out pole1, out pole2 ); pole1 = Spherical2D.SphereToPlane( pole1 ); pole2 = Spherical2D.SphereToPlane( pole2 ); // Go hyperbolic, dude. double scale = 1.065; // ZZZ - needs to be configurable. Complex fixedPlus = pole1; Mobius hyperbolic = new Mobius(); hyperbolic.Hyperbolic( tile.Geometry, fixedPlus, scale ); Vector3D newP1 = hyperbolic.Apply( seg.P1 ); Vector3D newMid = hyperbolic.Apply( seg.Midpoint ); Vector3D newP2 = hyperbolic.Apply( seg.P2 ); Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( newP1, newMid, newP2 ); Slicer.Clip( ref clipped, trimmingCircle, true ); } else { // I think this block has logic flaws, but strangely it seems to work, // so I'm leaving it in commented out for posterity. Vector3D p1 = seg.P1; Vector3D mid = seg.Midpoint; Vector3D p2 = seg.P2; //double offset = .1; double factor = .9; double f1 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( p1.Abs() ) * factor) ); double f2 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( mid.Abs() ) * factor) ); double f3 = Spherical2D.s2eNorm( (Spherical2D.e2sNorm( p2.Abs() ) * factor) ); p1.Normalize(); mid.Normalize(); p2.Normalize(); p1 *= f1; mid *= f2; p2 *= f3; Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( p1, mid, p2 ); Slicer.Clip( ref clipped, trimmingCircle, true ); } } Debug.Assert( clipped.Count == 1 ); tile = clipped[0]; return; } case Geometry.Euclidean: { double scale = .95; Mobius hyperbolic = new Mobius(); hyperbolic.Hyperbolic( tile.Geometry, new Vector3D(), scale ); tile.Drawn.Transform( hyperbolic ); return; } case Geometry.Hyperbolic: { List<Tile> clipped = new List<Tile>(); clipped.Add( tile ); Circle infinity = new Circle(); infinity.Radius = 1.0; Polygon original = tile.Drawn.Clone(); foreach( Segment seg in original.Segments ) { Debug.Assert( seg.Type == SegmentType.Arc ); Circle segCircle = seg.GetCircle(); // Get the intersection points with the disk at infinity. Vector3D p1, p2; int count = Euclidean2D.IntersectionCircleCircle( infinity, segCircle, out p1, out p2 ); Debug.Assert( count == 2 ); Vector3D mid = seg.Midpoint; //mid *= 0.75; // ZZZ - needs to be configurable. double offset = .03; double f1 = DonHatch.h2eNorm( DonHatch.e2hNorm( mid.Abs() ) - offset ); mid.Normalize(); mid *= f1; Circle trimmingCircle = new Circle(); trimmingCircle.From3Points( p1, mid, p2 ); Slicer.Clip( ref clipped, trimmingCircle, false ); } Debug.Assert( clipped.Count == 1 ); tile = clipped[0]; return; } } */ }
/// <summary> /// Move from a point p1 -> p2 along a geodesic. /// Also somewhat from Don. /// </summary> public void Geodesic( Geometry g, Complex p1, Complex p2 ) { Mobius t = new Mobius(); t.Isometry( g, 0, p1 * -1 ); Complex p2t = t.Apply( p2 ); Mobius m1 = new Mobius(), m2 = new Mobius(); m1.Isometry( g, 0, p1 * -1 ); m2.Isometry( g, 0, p2t ); Mobius m3 = m1.Inverse(); this = m3 * m2 * m1; }
/// <summary> /// Does a circle inversion in an arbitrary, generalized circle. /// IOW, the three points may be collinear, in which case we are talking about a reflection. /// </summary> private void CacheCircleInversion( Complex c1, Complex c2, Complex c3 ) { Mobius toUnitCircle = new Mobius(); toUnitCircle.MapPoints( c1, c2, c3, new Complex( 1, 0 ), new Complex( -1, 0 ), new Complex( 0, 1 ) ); m_cache1 = toUnitCircle; m_cache2 = m_cache1.Inverse(); }
/// <summary> /// Allow a hyperbolic transformation using an absolute offset. /// offset is specified in the respective geometry. /// </summary> public void Hyperbolic2( Geometry g, Complex fixedPlus, Complex point, double offset ) { // To the origin. Mobius m = new Mobius(); m.Isometry( g, 0, fixedPlus * -1 ); double eRadius = m.Apply( point ).Magnitude; double scale = 1; switch( g ) { case Geometry.Spherical: double sRadius = Spherical2D.e2sNorm( eRadius ); sRadius += offset; scale = Spherical2D.s2eNorm( sRadius ) / eRadius; break; case Geometry.Euclidean: scale = (eRadius + offset) / eRadius; break; case Geometry.Hyperbolic: double hRadius = DonHatch.e2hNorm( eRadius ); hRadius += offset; scale = DonHatch.h2eNorm( hRadius ) / eRadius; break; } Hyperbolic( g, fixedPlus, scale ); }
public Vector3D Transform( Vector3D v ) { v.RotateAboutAxis( new Vector3D( 1, 0 ), Math.PI / 2 ); Mobius m = new Mobius(); m.Isometry( Geometry.Hyperbolic, 0, new Complex( 0, 0.6 ) ); v = H3Models.TransformHelper( v, m ); v.RotateAboutAxis( new Vector3D( 1, 0 ), -Math.PI / 2 ); return v; }
/// <summary> /// This transform will map the z points to the respective w points. /// </summary> public void MapPoints( Complex z1, Complex z2, Complex z3, Complex w1, Complex w2, Complex w3 ) { Mobius m1 = new Mobius(), m2 = new Mobius(); m1.MapPoints( z1, z2, z3 ); m2.MapPoints( w1, w2, w3 ); this = m2.Inverse() * m1; }
public static void Test() { S3.HopfOrbit(); Mobius m = new Mobius(); m.UpperHalfPlane(); Vector3D test = m.Apply( new Vector3D() ); test *= 1; }
public static Mobius CreateFromIsometry( Geometry g, double angle, Complex P ) { Mobius m = new Mobius(); m.Isometry( g, angle, P ); return m; }
// Rotates a dodec about a vertex or edge to get a dual dodec. private static Dodec GetDual( Dodec dodec, Vector3D rotationPoint ) { //double rot = System.Math.PI / 2; // Edge-centered double rot = 4 * ( - Math.Atan( ( 2 + Math.Sqrt( 5 ) - 2 * Math.Sqrt( 3 + Math.Sqrt( 5 ) ) ) / Math.Sqrt( 3 ) ) ); // Vertex-centered Mobius m = new Mobius(); if( Infinity.IsInfinite( rotationPoint ) ) m.Elliptic( Geometry.Spherical, new Vector3D(), -rot ); else m.Elliptic( Geometry.Spherical, rotationPoint, rot ); Dodec dual = new Dodec(); foreach( Vector3D v in dodec.Verts ) { Vector3D rotated = m.ApplyInfiniteSafe( v ); dual.Verts.Add( rotated ); } foreach( Vector3D v in dodec.Midpoints ) { Vector3D rotated = m.ApplyInfiniteSafe( v ); dual.Midpoints.Add( rotated ); } return dual; }