コード例 #1
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        private static void OneHoneycombGoursat( int[] active, string baseName, int baseHue )
        {
            CalcThickness( active );

            // Create the simplex.
            Simplex simplex = new Simplex();
            simplex.InitializeGoursat();

            // Map of labels for mirrors consistent with input scheme to Goursat function.
            // Map is from wikipedia labeling scheme to the indices our function generates.
            //
            // wiki == our index
            // 0100 == 0
            // 0001 == 1
            // 1000 == 2
            // 0010 == 3
            Func<int, int> mapMirror = i =>
            {
                switch( i )
                {
                    case 0: return 2;
                    case 1: return 0;
                    case 2: return 3;
                    case 3: return 1;
                }
                throw new System.ArgumentException();
            };

            // We need to set this up before converting the mirrors.
            string mirrorsString = ActiveMirrorsString( active );
            string suffix = "-" + mirrorsString;

            // Convert our active mirrors into the Goursat tet indices.
            int[] polyMirrors = new int[] { 1, 2, 3 };
            active = active.Select( i => mapMirror( i ) ).OrderBy( i => i ).ToArray();
            polyMirrors = polyMirrors.Select( i => mapMirror( i ) ).OrderBy( i => i ).ToArray();

            Vector3D startingPoint = IterateToStartingPoint( active, simplex );
            List<H3.Cell.Edge> startingEdges = new List<H3.Cell.Edge>();
            foreach( int a in active )
            {
                Vector3D reflected = simplex.ReflectInFacet( startingPoint, a );
                startingEdges.Add( new H3.Cell.Edge( startingPoint, reflected ) );
            }

            bool doEdges = true;
            bool doCells = false;

            // Generate the honeycomb.
            H3.Cell.Edge[] edges = null;
            if( doEdges )
                edges = Recurse.CalcEdgesSmart( simplex.Facets, startingEdges.ToArray() );

            // Highlighted cells.
            H3.Cell[] cellsToHighlight = null;
            if( doCells )
            {
                H3.Cell startingCell = PolyhedronToHighlight( Geometry.Hyperbolic, polyMirrors, simplex, startingPoint );
                cellsToHighlight = Recurse.CalcCells( simplex.Facets, new H3.Cell[] { startingCell } );
                //cellsToHighlight = new H3.Cell[] { startingCell };
            }

            // plugin Wendy's nonuniform calcs here...
            //Nonuniform.Wendy( simplex, edges );

            // Trim out half the edges (the ones we won't see in our Pov-Ray view).
            Vector3D lookFrom = new Vector3D( 1, 1, 1 ) * 0.7;
            Vector3D lookAt = new Vector3D( );	// pov-ray lookat
            double thresh = -.01;
            if( doEdges )
                edges = edges.Where( e => e.Start.Dot( lookAt ) > thresh || e.End.Dot( lookAt ) > thresh ).ToArray();
            //if( doCells )
            //	cellsToHighlight = cellsToHighlight.Where( c => c.Center.Dot( lookAt ) > thresh ).ToArray();	// I don't think this works right

            // Setup Pov-ray stuff.
            // We have 16 possible mirror states.  We'll calculate the hue by converting the binary state to decimal, and doubling.
            // So for a given family, the hue will range over 32 numbers.
            int hue = baseHue + 2 * Convert.ToInt32( mirrorsString, 2 );
            string fileName = baseName + suffix;
            using( StreamWriter sw = File.CreateText( fileName + ".pov" ) )
            {
                sw.WriteLine( string.Format( "#declare lookFrom = <{0},{1},{2}>;", lookFrom.X, lookFrom.Y, lookFrom.Z ) );
                sw.WriteLine( string.Format( "#declare lookAt = <{0},{1},{2}>;", lookAt.X, lookAt.Y, lookAt.Z ) );
                sw.WriteLine( "#include \"C:\\Users\\hrn\\Documents\\roice\\povray\\H3_uniform_faces\\H3_uniform_faces.pov\"" );
                //sw.WriteLine( string.Format( "background {{ CHSL2RGB( <{0}, 1, .3> ) }}", hue ) );
                //sw.WriteLine( string.Format( "background {{ rgb <.13,.37,.31> }}" ) );
                sw.WriteLine( string.Format( "background {{ rgb 1 }}" ) );
            }

            if( doEdges )
                H3.SaveToFile( fileName, edges, finite: true, append: true );
            if( doCells )
            {
                HashSet<H3.Cell.Edge> cellEdges = new HashSet<H3.Cell.Edge>( new H3.Cell.EdgeEqualityComparer() );
                foreach( H3.Cell cell in cellsToHighlight )
                    cell.AppendAllEdges( cellEdges );
                edges = cellEdges.ToArray();
                H3.SaveToFile( fileName, edges, finite: true, append: true );

                H3.AppendFacets( fileName, cellsToHighlight );
            }
        }
コード例 #2
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        private static H3.Cell PolyhedronToHighlight( Geometry g, int[] mirrors, Simplex simplex, Vector3D startingPoint )
        {
            if( mirrors.Length != 3 )
                throw new System.Exception( "We need exactly three mirrors to generate a polyhedron." );

            // In the general case, we can have 3 types of polygons, each being generated by choosing 2 of the 3 mirrors.
            // When fewer polygon types exist, GenFacet will return null, so we need to check that.
            List<H3.Cell.Facet> polyFacets = new List<H3.Cell.Facet>();
            polyFacets.Add( GenFacet( g, mirrors[0], mirrors[1], simplex, startingPoint ) );
            polyFacets.Add( GenFacet( g, mirrors[1], mirrors[2], simplex, startingPoint ) );
            polyFacets.Add( GenFacet( g, mirrors[0], mirrors[2], simplex, startingPoint ) );
            polyFacets.RemoveAll( f => f == null );

            HashSet<Vector3D> completedFacetIds = new HashSet<Vector3D>();
            foreach( H3.Cell.Facet f in polyFacets )
                completedFacetIds.Add( f.ID );

            Recurse.GenPolyhedron( mirrors.Select( m => simplex.Facets[m] ).ToArray(),
                polyFacets.ToArray(), polyFacets, completedFacetIds );

            H3.Cell cell = new H3.Cell( polyFacets.ToArray() );

            // Calc a center.  XXX - totally wrong.
            Vector3D center = new Vector3D();
            foreach( Vector3D v in cell.Verts )
                center += v;
            center /= cell.Verts.Count();
            cell.Center = center;

            // Calc the spheres.
            foreach( H3.Cell.Facet f in cell.Facets )
                f.CalcSphereFromVerts( g );

            return cell;
        }
コード例 #3
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        // CHEAT! (would be better to do a geometrical construction)
        // We are going to iterate to the starting point that will make all edge lengths the same.
        private static Vector3D IterateToStartingPoint( int[] activeMirrors, Simplex simplex )
        {
            if( activeMirrors.Length == 1 )
                return simplex.Verts[activeMirrors[0]];

            // We are minimizing the output of this function,
            // because we want all edge lengths to be as close as possible.
            // Input vector should be in the Ball Model.
            Func<Vector3D, double> diffFunc = v =>
            {
                List<double> lengths = new List<double>();
                for( int i = 0; i < activeMirrors.Length; i++ )
                {
                    Vector3D reflected = simplex.ReflectInFacet( v, activeMirrors[i] );
                    lengths.Add( H3Models.Ball.HDist( v, reflected ) );
                }

                double result = 0;
                double average = lengths.Average();
                foreach( double length in lengths )
                    result += Math.Abs( length - average );
                return result;
            };

            // So that we can leverage Euclidean barycentric coordinates, we will first convert our simplex to the Klein model.
            // We will need to take care to properly convert back to the Ball as needed.
            Vector3D[] kleinVerts = simplex.Verts.Select( v => HyperbolicModels.PoincareToKlein( v ) ).ToArray();

            // Normalizing barycentric coords amounts to making sure the 4 coords add to 1.
            Func<Vector3D, Vector3D> baryNormalize = b =>
            {
                return b / ( b.X + b.Y + b.Z + b.W );
            };

            // Bary Coords to Euclidean
            Func<Vector3D[], Vector3D, Vector3D> baryToEuclidean = ( kv, b ) =>
            {
                Vector3D result =
                    kv[0] * b.X + kv[1] * b.Y + kv[2] * b.Z + kv[3] * b.W;
                return result;
            };

            // Our starting barycentric coords (halfway between all active mirrors).
            Vector3D bary = new Vector3D();
            foreach( int a in activeMirrors )
                bary[a] = 0.5;
            bary = baryNormalize( bary );

            // For each iteration, we'll shrink this search offset.
            // NOTE: The starting offset and decrease factor I'm using don't guarantee convergence,
            // but it seems to be working pretty well (even when varying these parameters).
            //double searchOffset = 1.0 - bary[activeMirrors[0]];
            //double searchOffset = bary[activeMirrors[0]];
            double factor = 1.5;	// Adjusting this helps get some to converge, e.g. 4353-1111
            double searchOffset = bary[activeMirrors[0]] / factor;

            double min = double.MaxValue;
            int iterations = 1000;
            for( int i = 0; i < iterations; i++ )
            {
                min = diffFunc( HyperbolicModels.KleinToPoincare( baryToEuclidean( kleinVerts, bary ) ) );
                foreach( int a in activeMirrors )
                {
                    Vector3D baryTest1 = bary, baryTest2 = bary;
                    baryTest1[a] += searchOffset;
                    baryTest2[a] -= searchOffset;
                    baryTest1 = baryNormalize( baryTest1 );
                    baryTest2 = baryNormalize( baryTest2 );

                    double t1 = diffFunc( HyperbolicModels.KleinToPoincare( baryToEuclidean( kleinVerts, baryTest1 ) ) );
                    double t2 = diffFunc( HyperbolicModels.KleinToPoincare( baryToEuclidean( kleinVerts, baryTest2 ) ) );
                    if( t1 < min )
                    {
                        min = t1;
                        bary = baryTest1;
                    }
                    if( t2 < min )
                    {
                        min = t2;
                        bary = baryTest2;
                    }
                }

                if( Tolerance.Equal( min, 0.0, 1e-14 ) )
                {
                    System.Console.WriteLine( string.Format( "Converged in {0} iterations.", i ) );
                    break;
                }

                searchOffset /= factor;
            }

            if( !Tolerance.Equal( min, 0.0, 1e-14 ) )
            {
                System.Console.WriteLine( "Did not converge: " + min );

                // Be a little looser before thrown an exception.
                if( !Tolerance.Equal( min, 0.0, 1e-12 ) )
                {
                    System.Console.ReadKey( true );
                    //throw new System.Exception( "Boo. We did not converge." );
                    return Vector3D.DneVector();
                }
            }

            return HyperbolicModels.KleinToPoincare( baryToEuclidean( kleinVerts, bary ) );
        }
コード例 #4
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        private static H3.Cell.Facet GenFacet( Geometry g, int mirror1, int mirror2, Simplex simplex, Vector3D startingPoint )
        {
            List<Sphere> mirrors = new List<Sphere>();
            mirrors.Add( simplex.Facets[mirror1] );
            mirrors.Add( simplex.Facets[mirror2] );

            List<H3.Cell.Edge> startingEdges = new List<H3.Cell.Edge>();
            Vector3D reflected = simplex.ReflectInFacet( startingPoint, mirror1 );
            startingEdges.Add( new H3.Cell.Edge( startingPoint, reflected ) );
            reflected = simplex.ReflectInFacet( startingPoint, mirror2 );
            startingEdges.Add( new H3.Cell.Edge( startingPoint, reflected ) );
            startingEdges.RemoveAll( e => e.Start == e.End );
            if( startingEdges.Count == 0 )
                return null;

            H3.Cell.Edge[] completedEdges = Recurse.CalcEdges( mirrors.ToArray(), startingEdges.ToArray(), new Recurse.Settings() { G = g } );
            if( completedEdges.Length == 1 )
                return null;

            List<Vector3D> facetVerts = new List<Vector3D>();
            H3.Cell.Edge edge = completedEdges.First();
            Vector3D start = edge.Start;
            Vector3D current = edge.End;
            facetVerts.Add( edge.End );
            while( current != start )
            {
                edge = completedEdges.First( e => e != edge && ( e.Start == current || e.End == current ) );
                current = edge.Start == current ? edge.End : edge.Start;
                facetVerts.Add( current );
            }

            return new H3.Cell.Facet( facetVerts.ToArray() );
        }
コード例 #5
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        public static void Paracompact( HoneycombDef def, int[] active, int baseHue )
        {
            string baseName = BaseName( def );
            string mirrorsString = ActiveMirrorsString( active );
            string suffix = "-" + mirrorsString;
            string fileName = baseName + suffix;

            if( File.Exists( fileName + ".pov" ) )
            {
                Console.WriteLine( string.Format( "Skipping {0}", fileName ) );
                return;
            }

            Console.WriteLine( string.Format( "Building {0}", fileName ) );
            CalcThickness( active );

            // The wiki mirrors are labeled in the reverse of ours.
            Func<int, int> mapMirror = i => 3 - i;
            active = active.Select( i => mapMirror( i ) ).OrderBy( i => i ).ToArray();

            Simplex simplex = new Simplex();
            simplex.Facets = SimplexCalcs.Mirrors( def.P, def.Q, def.R );
            simplex.Verts = SimplexCalcs.VertsBall( def.P, def.Q, def.R );

            Vector3D startingPoint = IterateToStartingPoint( active, simplex );
            if( startingPoint.DNE )
                return;
            List<H3.Cell.Edge> startingEdges = new List<H3.Cell.Edge>();
            foreach( int a in active )
            {
                Vector3D reflected = simplex.ReflectInFacet( startingPoint, a );
                startingEdges.Add( new H3.Cell.Edge( startingPoint, reflected ) );
            }

            SetupBaseHue( fileName, mirrorsString, baseHue );
            Recurse.m_background = new Vector3D( baseHue, 1, .1 );

            H3.Cell.Edge[] edges = Recurse.CalcEdgesSmart2( simplex.Facets, startingEdges.ToArray() );
            H3.SaveToFile( fileName, edges, finite: true, append: true );
        }
コード例 #6
0
ファイル: HoneycombGen.cs プロジェクト: roice3/Honeycombs
        /// <summary>
        /// This generates a honeycomb by reflecting in 4 mirrors of the fundamental simplex.
        /// </summary>
        public static void OneHoneycombNew( HoneycombDef imageData )
        {
            int p = imageData.P;
            int q = imageData.Q;
            int r = imageData.R;

            double thickness = 0.1;
            double thicknessSpherical = Spherical2D.s2eNorm( thickness );
            double thicknessHyperbolic = R3.Math.DonHatch.h2eNorm( thickness );
            double threshold = 1;

            H3.Cell.Edge[] edges = null;
            H3.Cell[] cellsToHighlight = null;
            Sphere[] simplex = null;
            Vector3D vertex = new Vector3D();

            Geometry g = Util.GetGeometry( p, q, r );
            if( g == Geometry.Spherical )
            {
                thickness = thicknessSpherical /*.07 for 333*/  /* 0.05for 433*/  /*.025 for 533,335*/;
                threshold = 10000;

                simplex = SimplexCalcs.MirrorsSpherical( p, q, r );
                vertex = SimplexCalcs.VertexSpherical( p, q, r );

                // Ugly special casing for 333, since it has a vertex project to infinity.
                if( p == 3 && q == 3 && r == 3 )
                    SpecialCase333();
            }
            else if( g == Geometry.Euclidean )
            {
                thickness = thickness / 2;
                threshold = 5/*20*/;

                SimplexCalcs.CalcEScale();
                simplex = SimplexCalcs.MirrorsEuclidean();
                Vector3D[] verts = SimplexCalcs.VertsEuclidean();
                vertex = verts[2];
            }
            else
            {
                thickness = thicknessHyperbolic;
                threshold = 0.01;

                simplex = SimplexCalcs.Mirrors( p, q, r );
                Vector3D[] verts = SimplexCalcs.VertsBall( p, q, r );
                vertex = verts[2];

                //Vector3D[] simplexVerts = SimplexCalcs.VertsBall( p, q, r );
                //H3.Cell.Edge edge = new H3.Cell.Edge( simplexVerts[2], simplexVerts[3] );
                //H3.Cell.Edge edge = SimplexCalcs.HoneycombEdgeBall( p, q, r );
                //H3.Cell.Edge[] startingEdges = new H3.Cell.Edge[] { edge };

                //H3.Cell.Edge[] edges = Recurse.CalcEdgesSmart2( simplex, startingEdges );

                // Vertex Centered.
                bool vertexCentered = false;
                if( vertexCentered )
                {
                    Vector3D v = SimplexCalcs.VertexPointBall( p, q, r );
                    v = H3Models.BallToUHS( v );
                    double scale = 1.0 / v.Abs();
                    edges = edges.Select( e =>
                        {
                            Vector3D start = H3Models.UHSToBall( H3Models.BallToUHS( e.Start ) * scale );
                            Vector3D end = H3Models.UHSToBall( H3Models.BallToUHS( e.End ) * scale );
                            return new H3.Cell.Edge( start, end );
                        } ).ToArray();
                }

                // Code to show endpoints of 535
                /*using( StreamWriter sw = File.CreateText( "535_points.pov" ) )
                {
                    HashSet<Vector3D> verts = new HashSet<Vector3D>();
                    foreach( H3.Cell.Edge e in edges )
                    {
                        verts.Add( Sterographic.SphereToPlane( e.Start ) );
                        verts.Add( Sterographic.SphereToPlane( e.End ) );
                    }

                    foreach( Vector3D vert in verts )
                        if( !Infinity.IsInfinite( vert ) )
                            sw.WriteLine( PovRay.Sphere( new Sphere() { Center = vert, Radius = 0.01 } ) );
                }*/
            }

            // Recurse
            bool dual = false;
            {
                H3.Cell.Edge[] startingEdges = null;
                if( dual )
                    startingEdges = new H3.Cell.Edge[] { SimplexCalcs.DualEdgeBall( simplex ) };
                else
                    startingEdges = new H3.Cell.Edge[] { SimplexCalcs.HoneycombEdgeBall( simplex, vertex ) };

                edges = Recurse.CalcEdges( simplex, startingEdges, new Recurse.Settings() { G = g, Threshold = threshold } );

                //CullHalfOfEdges( ref edges );

                // No need to cull edges in spherical case.
                // This was just to generate some images for 350-cell paper.
                //edges = Cull120Cell( edges );

                Simplex tet = new Simplex();
                tet.Facets = simplex;

                if( dual )
                {
                    H3.Cell.Edge[] oneDualCell = edges.Where( e => e.Depths[2] == 0 ).ToArray();
                    simplex = simplex.Skip( 1 ).ToArray();
                    edges = Recurse.CalcEdges( simplex, oneDualCell, new Recurse.Settings() { G = g, Threshold = threshold } );

                    int[] polyMirrors = new int[] { 0, 1, 3 };
                    H3.Cell startingCell = PolyhedronToHighlight( g, polyMirrors, tet, new Vector3D() );
                    cellsToHighlight = Recurse.CalcCells( simplex, new H3.Cell[] { startingCell } );
                    //cellsToHighlight = new H3.Cell[] { startingCell };
                    //cellsToHighlight = cellsToHighlight.Skip( 7 ).ToArray();
                }
                else
                {
                    int[] polyMirrors = new int[] { 1, 2, 3 };
                    H3.Cell startingCell = PolyhedronToHighlight( g, polyMirrors, tet, vertex );
                    //cellsToHighlight = Recurse.CalcCells( simplex, new H3.Cell[] { startingCell } );
                    cellsToHighlight = new H3.Cell[] { startingCell };
                }

                // Include just one cell?
                bool includeOne = false;
                if( includeOne )
                {
                    edges = edges.Where( e => e.Depths[0] == 0 ).ToArray();
                    //cellsToHighlight = cellsToHighlight.Where( c => c.Depths[0] == 0 ).ToArray();
                }
            }

            // Write the file
            bool pov = false;
            if( pov )
            {
                string filename = string.Format( "{0}{1}{2}.pov", p, q, r );
                PovRay.WriteEdges( new PovRay.Parameters() { AngularThickness = thickness }, g, edges,
                    filename, append: false );
                //File.Delete( filename );
                //PovRay.AppendFacets( cellsToHighlight, filename );

                HashSet<Vector3D> verts = new HashSet<Vector3D>();
                foreach( H3.Cell.Edge e in edges )
                {
                    verts.Add( e.Start );
                    verts.Add( e.End );
                }
                foreach( Vector3D v in verts )
                {
                    Vector3D t = v;
                    t.Normalize();
                    t *= 0.9;
                    System.Diagnostics.Trace.WriteLine( string.Format( "light_source {{ <{0},{1},{2}> White*.2 }}", t.X, t.Y, t.Z ) );
                }

                /*
                // Include the standard pov stuff, so we can batch this.
                string fileName = imageData.FormatFilename( string.Empty );
                using( StreamWriter sw = File.CreateText( fileName + ".pov" ) )
                {
                    sw.WriteLine( "#include \"C:\\Users\\hrn\\Documents\\roice\\povray\\paper\\H3.pov\"" );
                }

                bool dummy = true;	// Doesn't matter for Pov-Ray, just Shapeways meshes.
                H3.SaveToFile( fileName, edges, dummy, append: true );
                */
            }
            else
            {
                if( g == Geometry.Spherical )
                {
                    edges = edges.Where( e => e.Start.Valid() && e.End.Valid() && !Infinity.IsInfinite( e.Start ) && !Infinity.IsInfinite( e.End ) ).ToArray();
                    S3.EdgesToStl( edges );
                }
                else
                    throw new System.NotImplementedException();
            }
        }
コード例 #7
0
ファイル: Nonuniform.cs プロジェクト: roice3/Honeycombs
        /// <summary>
        /// Wendy's 77
        /// </summary>
        public static void Wendy( Simplex simplex, H3.Cell.Edge[] edges )
        {
            H3.Cell startingCell = null;

            Vector3D start = startingCell.Verts.First();

            Func<Vector3D, Vector3D> findAntipode = input =>
            {
                Vector3D antipode = new Vector3D();
                double max = double.MinValue;
                foreach( Vector3D v in startingCell.Verts )
                {
                    double d = H3Models.Ball.HDist( v, input );
                    if( d > max )
                    {
                        max = d;
                        antipode = v;
                    }
                }
                return antipode;
            };

            H3.Cell.Edge[] diagonals = new H3.Cell.Edge[] { new H3.Cell.Edge( start, findAntipode( start ) ) };
            diagonals = Recurse.CalcEdges( simplex.Facets, diagonals, new Recurse.Settings() { Threshold = 0.9983 } );

            // diagonals includes too much at this point (it includes all icosahedra diagonals, but we only want one diagonal from each cell).
            // We need to begin at 4 start points, and branch out from each to find the ones we want.

            var vertsToDiagonals = FindConnectedEdges( diagonals );
            var connectedEdges = FindConnectedEdges( edges );

            // Get all edges (not diagonals) connected to start.
            List<H3.Cell.Edge> connectedToStart = connectedEdges[start];
            Vector3D startOpp = connectedToStart[0].Opp( start );
            List<H3.Cell.Edge> connectedToStartOpp = connectedEdges[startOpp];

            // We need to pick 4 of these edges, arranged in a tetrahedron for our starting points.
            List<Vector3D> startingPoints = new List<Vector3D>();

            List<double> distances = new List<double>();

            // View1
            //startingPoints.Add( start );
            //foreach( Vector3D v in connectedToStartOpp.Select( e => e.Opp( startOpp ) ) )
            //	distances.Add( H3Models.Ball.HDist( startingPoints.First(), v ) );
            //startingPoints.Add( connectedToStartOpp[10].Opp( startOpp ) );
            //startingPoints.Add( connectedToStartOpp[13].Opp( startOpp ) );
            //startingPoints.Add( connectedToStartOpp[14].Opp( startOpp ) );

            // View2
            startingPoints.Add( startOpp );
            foreach( Vector3D v in connectedToStart.Select( e => e.Opp( start ) ) )
                distances.Add( H3Models.Ball.HDist( startingPoints.First(), v ) );
            startingPoints.Add( connectedToStart[10].Opp( start ) );
            startingPoints.Add( connectedToStart[13].Opp( start ) );
            startingPoints.Add( connectedToStart[14].Opp( start ) );

            distances.Clear();
            distances.Add( H3Models.Ball.HDist( startingPoints[1], startingPoints[2] ) );
            distances.Add( H3Models.Ball.HDist( startingPoints[1], startingPoints[3] ) );
            distances.Add( H3Models.Ball.HDist( startingPoints[2], startingPoints[3] ) );
            distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[1] ) );
            distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[2] ) );
            distances.Add( H3Models.Ball.HDist( startingPoints[0], startingPoints[3] ) );
            double dist = 3.097167;

            Func<Vector3D[], H3.Cell.Edge[]> RemoveVerts = starting =>
            {
                List<H3.Cell.Edge> keepers = new List<H3.Cell.Edge>();
                HashSet<Vector3D> removedVerts = new HashSet<Vector3D>();
                Recurse.BranchAlongVerts( starting.ToArray(), vertsToDiagonals, removedVerts );
                foreach( H3.Cell.Edge e in edges )
                {
                    if( removedVerts.Contains( e.Start ) || removedVerts.Contains( e.End ) )
                        continue;
                    keepers.Add( e );
                }
                return keepers.ToArray();
            };

            edges = RemoveVerts( startingPoints.ToArray() );

            bool done = false;
            while( !done )
            {
                done = true;
                var newConnectedEdges = FindConnectedEdges( edges );
                foreach( Vector3D v in newConnectedEdges.Keys )
                {
                    List<H3.Cell.Edge> oldEdgeList = connectedEdges[v];
                    List<H3.Cell.Edge> newEdgeList = newConnectedEdges[v];

                    // Only work edges that were full originally.
                    if( oldEdgeList.Count != 20 )
                        continue;

                    // We need at least two to find the rest.
                    int newCount = newEdgeList.Count;
                    if( newCount > 16 && newCount < 19 )
                    {
                        List<H3.Cell.Edge> removed = oldEdgeList.Except( newEdgeList, new H3.Cell.EdgeEqualityComparer() ).ToList();

                        H3.Cell.Edge[] toTrim = newEdgeList.FindAll( e =>
                        {
                            foreach( H3.Cell.Edge alreadyRemoved in removed )
                            {
                                double d = H3Models.Ball.HDist( alreadyRemoved.Opp( v ), e.Opp( v ) );
                                if( !Tolerance.Equal( dist, d, 0.00001 ) )
                                    return false;
                            }

                            return true;
                        } ).ToArray();

                        edges = RemoveVerts( toTrim.Select( e => e.Opp( v ) ).ToArray() );
                        done = false;
                    }

                    if( newCount == 20 )
                        done = false;
                }
            }
        }