/// <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; * } * } */ }
public static void CatenoidBasedSurface() { RLD_outputs outputs; SurfaceInternal(out outputs); double scale = m_params.Scale; // Map a point for a given k/m from the hemihypersphere to the complex plane. // You can also pass in -1 for k to get a point on the equator of the hemihypersphere. double mInc = Math.PI * 2 / m_params.M; Func <RLD_outputs, int, int, Vector3D> onPlane = (o, k, m) => { double theta = k == -1 ? 0 : outputs.x_i[k]; theta += Math.PI / 2; return (Sterographic.SphereToPlane( SphericalCoords.SphericalToCartesian( new Vector3D(1, theta, m * mInc) ) )); }; // Setup texture coords on fundamental triangle. // We'll use a fundamental triangle in the southern hemisphere, // with stereographically projected coords at (0,0), (1,0), and CCW on the unit circle depending on M. Polygon p = new Polygon(); p.Segments.Add(Segment.Line(new Vector3D(), new Vector3D(1, 0))); p.Segments.Add(Segment.Arc(new Vector3D(1, 0), onPlane(outputs, 1, 1), onPlane(outputs, -1, 1))); p.Segments.Add(Segment.Line(onPlane(outputs, -1, 1), new Vector3D())); int levels = 9; TextureHelper.SetLevels(levels); Vector3D[] coords = TextureHelper.TextureCoords(p, Geometry.Spherical, doGeodesicDome: true); int[] elementIndices = TextureHelper.TextureElements(1, levels); // Setup a nearTree for the catenoid locations (on the plane). NearTree nearTree = new NearTree(Metric.Spherical); for (int k = 1; k < outputs.x_i.Length; k++) { for (int m = 0; m <= 1; m++) { Vector3D loc = onPlane(outputs, k, m); nearTree.InsertObject(new NearTreeObject() { ID = k, Location = loc }); } } // Given a point on the plane, find the nearest catenoid center and calculate the height of the surface based on that. // This also calculates the locking of the point. Func <Vector3D, Tuple <double, Vector3D, Vector3D> > heightAndLocking = coord => { NearTreeObject closest; if (!nearTree.FindNearestNeighbor(out closest, coord, double.MaxValue)) { throw new System.Exception(); } Vector3D locked = new Vector3D(); if (p.Segments[0].IsPointOn(coord) || p.Segments[2].IsPointOn(coord)) { locked = new Vector3D(1, 1, 0, 0); } //if( p.Segments[1].IsPointOn( v ) ) // Not working right for some reason, but line below will work. if (Tolerance.Equal(coord.Abs(), 1)) { locked = new Vector3D(1, 1, 1, 0); } Vector3D vSphere = Sterographic.PlaneToSphere(coord); Vector3D cSphere = Sterographic.PlaneToSphere(closest.Location); double dist = vSphere.AngleTo(cSphere); int k = (int)closest.ID; double waist = outputs.t_i[k]; double rld_height = outputs.phi_i[k]; double h = waist * 3.5 * 2; // height where catenoid will meet rld_height. double factor = scale * rld_height * 2 / h; // Artifical scaling so we can see things. dist /= factor; double z = double.NaN; if (dist >= waist) { z = waist * DonHatch.acosh(dist / waist); } else if (dist >= 0.7 * waist) { z = 0; // Move the coord to the thinnest waist circle. Mobius m = new Mobius(); m.Hyperbolic(Geometry.Spherical, coord.ToComplex(), waist / dist); coord = m.Apply(coord); } if (dist < waist * 20) { locked = new Vector3D(1, 1, 1, 1); } return(new Tuple <double, Vector3D, Vector3D>(z * factor, locked, coord)); }; // Calculate all the coordinates. Vector3D[] locks = new Vector3D[coords.Length]; for (int i = 0; i < coords.Length; i++) { Vector3D coord = coords[i]; var hl = heightAndLocking(coord); locks[i] = hl.Item2; coord = hl.Item3; coords[i] = Normal(Sterographic.PlaneToSphere(coord), (double)hl.Item1); } // Relax it. Relax(coords, elementIndices, locks); Mesh mesh = new Mesh(); Sphere s = new Sphere(); for (int i = 0; i < elementIndices.Length; i += 3) { Vector3D a = coords[elementIndices[i]]; Vector3D b = coords[elementIndices[i + 1]]; Vector3D c = coords[elementIndices[i + 2]]; if (a.DNE || b.DNE || c.DNE) { continue; } for (int m = 0; m <= 0; m++) { mesh.Triangles.Add(new Mesh.Triangle(a, b, c)); mesh.Triangles.Add(new Mesh.Triangle( s.ReflectPoint(a), s.ReflectPoint(b), s.ReflectPoint(c))); a.RotateXY(mInc); b.RotateXY(mInc); c.RotateXY(mInc); } } PovRay.WriteMesh(mesh, "RLD.pov"); }