public Apply ( System.Numerics.Complex z ) : System.Numerics.Complex | ||
z | System.Numerics.Complex | |
리턴 | System.Numerics.Complex |
/// <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; }
/// <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); }
private Complex ApplyCachedCircleInversion(Complex input) { Complex result = m_cache1.Apply(input); result = CircleInversion(result); result = m_cache2.Apply(result); return(result); }
/// <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); }
/// <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> /// 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 ); }
/// <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; }
private static Vector3D DiskToUpper( Vector3D input ) { Mobius m = new Mobius(); m.UpperHalfPlane(); return m.Apply( input ); }
/// <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(); }
public static void Test() { S3.HopfOrbit(); Mobius m = new Mobius(); m.UpperHalfPlane(); Vector3D test = m.Apply( new Vector3D() ); test *= 1; }
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> /// 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> /// 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> /// NOTE! This should only be used if m is a transform that preserves the imaginary axis! /// </summary> public static Vector3D TransformHelper( Vector3D v, Mobius m ) { Vector3D spherical = SphericalCoords.CartesianToSpherical( v ); Complex c1 = Complex.FromPolarCoordinates( spherical.X, Math.PI/2 - spherical.Y ); Complex c2 = m.Apply( c1 ); /* if( c2.Phase > Math.PI / 2 || c2.Phase < -Math.PI / 2 ) { // Happens when v is origin, and causes runtime problems in release. System.Diagnostics.Debugger.Break(); } */ Vector3D s2 = new Vector3D( c2.Magnitude, Math.PI/2 - c2.Phase, spherical.Z ); return SphericalCoords.SphericalToCartesian( s2 ); }
/// <summary> /// Same as above, but works with all geometries. /// </summary> public static Circle EquidistantOffset( Geometry g, Segment seg, double offset ) { Mobius m = new Mobius(); Vector3D direction; if( seg.Type == SegmentType.Line ) { direction = seg.P2 - seg.P1; direction.RotateXY( Math.PI / 2 ); } else { direction = seg.Circle.Center; } direction.Normalize(); m.Isometry( g, 0, direction * offset ); // Transform 3 points on segment. Vector3D p1 = m.Apply( seg.P1 ); Vector3D p2 = m.Apply( seg.Midpoint ); Vector3D p3 = m.Apply( seg.P2 ); return new Circle( p1, p2, p3 ); }
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; }