Exemplo n.º 1
0
        // advance the track
        void advanceTrack()
        {
            timeSpline.Start();
            // we need to store the controller's position here, because Controller.AdaptOnChange doesn't handle multiple CP operations (adding,deleting)
            float pos = Controller.AbsolutePosition;

            //remove oldest section's CP
            for (int i = 0; i < SectionCPCount; i++)
            {
                pos -= TrackSpline.ControlPoints[0].Length; // update controller's position, so the ship won't jump
                TrackSpline.ControlPoints[0].Delete();
            }
            // add new section's CP
            for (int i = 0; i < SectionCPCount; i++)
            {
                addTrackCP();
            }
            // refresh the spline, so orientation will be auto-calculated
            TrackSpline.Refresh();
            TrackSpline.ControlPoints[TrackSpline.ControlPointCount - 1].OrientationAnchor = true;
            // just calling it again to fix the orientation anchor
            TrackSpline.Refresh();

            // set the controller to the old position
            Controller.AbsolutePosition = pos;
            mUpdateSpline = false;
            timeSpline.Stop();
            // update generators in 0.2 seconds
            Invoke("advanceSections", 0.2f);
        }
Exemplo n.º 2
0
        static void Main(string[] args)
        {
            Console.Title = "DataTanker Performance Tests";
            Console.WriteLine("Press Enter to run");
            Console.ReadLine();

            Console.WriteLine("BPlusTree tests");

            BPlusTreeStorageTests.StoragePath = _storagePath;

            TimeMeasure.Start("Overall");

            RunTest("BPlusTree: InsertAndReadMillionRecords", BPlusTreeStorageTests.InsertAndReadMillionRecords);
            RunTest("BPlusTree: InsertLargeValues", BPlusTreeStorageTests.InsertLargeValues);
            RunTest("BPlusTree: RandomOperations", BPlusTreeStorageTests.RandomOperations);

            TimeMeasure.Stop("Overall");
            PrintResults();
            TimeMeasure.Clear();

            Console.WriteLine("RadixTree tests");

            RadixTreeStorageTests.StoragePath = _storagePath;

            TimeMeasure.Start("Overall");

            RunTest("RadixTree: EnglishWords", RadixTreeStorageTests.EnglishWords);

            TimeMeasure.Stop("Overall");
            PrintResults();

            Console.ReadLine();
        }
Exemplo n.º 3
0
        private static void RunTest(string testName, Action <Action <string> > test)
        {
            Console.WriteLine("Running {0}", testName);

            if (!Directory.Exists(_storagePath))
            {
                Directory.CreateDirectory(_storagePath);
            }

            TimeMeasure.Start(testName);
            try
            {
                test(message =>
                {
                    TimeMeasure.Stop(testName);
                    WriteInfo($"{TimeMeasure.Result(testName):hh\\:mm\\:ss\\:fff} {message}");
                    TimeMeasure.Start(testName);
                });
            }
            finally
            {
                TimeMeasure.Stop(testName);
                Cleanup();
            }
        }
Exemplo n.º 4
0
        internal void doRefresh()
        {
#if UNITY_EDITOR || CURVY_DEBUG
            DEBUG_LastUpdateTime = System.DateTime.Now;
            DEBUG_ExecutionTime.Start();
#endif

            if (RandomizeSeed)
            {
                setSeed((int)System.DateTime.Now.Ticks);
            }
            else
            {
                setSeed(Seed);
            }
            OnBeforeRefreshEvent(new CurvyCGEventArgs(this));
            Refresh();
            setSeed((int)System.DateTime.Now.Ticks);

#if UNITY_EDITOR || CURVY_DEBUG
            DEBUG_ExecutionTime.Stop();
#endif
            OnRefreshEvent(new CurvyCGEventArgs(this));

            mDirty = false;
        }
Exemplo n.º 5
0
        /// <summary>
        /// Refreshes the Generator
        /// </summary>
        /// <param name="forceUpdate">true to force a refresh of all modules</param>
        public void Refresh(bool forceUpdate = false)
        {
            if (!IsInitialized)
            {
                return;
            }
            if (mNeedSort)
            {
                doSortModules();
            }

            CGModule firstChanged = null;

            for (int i = 0; i < Modules.Count; i++)
            {
                if (forceUpdate && Modules[i] is IOnRequestProcessing)
                {
                    Modules[i].Dirty = true; // Dirty state will be resetted to false, but last data will be deleted - forcing a recalculation
                }
                if (!(Modules[i] is INoProcessing) && (Modules[i].Dirty || (forceUpdate && !(Modules[i] is IOnRequestProcessing))))
                {
                    Modules[i].checkOnStateChangedINTERNAL();
                    if (Modules[i].IsInitialized && Modules[i].IsConfigured)
                    {
                        if (firstChanged == null)
                        {
#if UNITY_EDITOR || CURVY_DEBUG
                            DEBUG_ExecutionTime.Start();
#endif
                            firstChanged = Modules[i];
                        }

                        //Debug.Log("Refresh " + Modules[i].ModuleName);
                        Modules[i].doRefresh();
                        //Debug.Log("Updated " + Modules[i].ModuleName);
                    }

                    //else
                    //{
                    //Debug.Log("Early OUT at "+Modules[i].ModuleName);
                    //break;

                    //}
                }
            }
            if (firstChanged != null)
            {
#if UNITY_EDITOR || CURVY_DEBUG
                DEBUG_ExecutionTime.Stop();
                if (!Application.isPlaying)
                {
                    EditorUtility.UnloadUnusedAssetsImmediate();
                }
#endif
                OnRefreshEvent(new CurvyCGEventArgs(this, firstChanged));
            }
        }
Exemplo n.º 6
0
 // Update is called once per frame
 void Update()
 {
     if (Spline && Spline.IsInitialized && Lookup)
     {
         // convert Lookup position to Spline's local space
         var lookupPos = Spline.transform.InverseTransformPoint(Lookup.position);
         // get the nearest point's TF on spline
         Timer.Start();
         float nearestTF = Spline.GetNearestPointTF(lookupPos);
         Timer.Stop();
         // convert the spline pos back to world space and set
         transform.position  = Spline.transform.TransformPoint(Spline.Interpolate(nearestTF));
         StatisticsText.text =
             string.Format("Blue Curve Cache Points: {0} \nAverage Lookup (ms): {1:0.000}", Spline.CacheSize, Timer.AverageMS);
     }
 }
Exemplo n.º 7
0
        void Test_Interpolate()
        {
            var spl = getSpline();

            addRandomCP(ref spl, mControlPointCount, mTotalSplineLength);
            mTestResults.Add("Cache Points: " + spl.CacheSize);
            mTestResults.Add(string.Format("Cache Point Distance: {0:0.000}", mTotalSplineLength / (float)spl.CacheSize));

            Vector3 v = Vector3.zero;

            if (mInterpolate_UseDistance)
            {
                for (int i = 0; i < LOOPS; i++)
                {
                    float d = Random.Range(0, spl.Length);
                    if (mUseCache)
                    {
                        Timer.Start();
                        v = spl.InterpolateByDistanceFast(d);
                        Timer.Stop();
                    }
                    else
                    {
                        Timer.Start();
                        v = spl.InterpolateByDistance(d);
                        Timer.Stop();
                    }
                }
            }
            else
            {
                for (int i = 0; i < LOOPS; i++)
                {
                    float f = Random.Range(0, 1);
                    if (mUseCache)
                    {
                        Timer.Start();
                        v = spl.InterpolateFast(f);
                        Timer.Stop();
                    }
                    else
                    {
                        Timer.Start();
                        v = spl.Interpolate(f);
                        Timer.Stop();
                    }
                }
            }
            Destroy(spl.gameObject);
            // Prevent "unused variable" compiler warning
            v.Set(0, 0, 0);
        }
Exemplo n.º 8
0
        // set a CG to render only a portion of a spline
        void updateSectionGenerator(CurvyGenerator gen, int startCP, int endCP)
        {
            // Set Track segment we want to use
            var path = gen.FindModules <InputSplinePath>(true)[0];

            path.StartCP = TrackSpline.ControlPoints[startCP];
            path.EndCP   = TrackSpline.ControlPoints[endCP];
            // Set UV-Offset to match
            var vol = gen.FindModules <BuildVolumeMesh>()[0];

            vol.MaterialSetttings[0].UVOffset.y = lastSectionEndV % 1;
            timeCG.Start();
            gen.Refresh();
            timeCG.Stop();
            // fetch the ending V to be used by next section
            var vmesh = vol.OutVMesh.GetData <CGVMesh>();

            lastSectionEndV = vmesh.UV[vmesh.Count - 1].y;
        }
Exemplo n.º 9
0
        private void btnStartGeneration_Click(object sender, EventArgs e)
        {
            int start = int.Parse(nudStartKey.Value.ToString(CultureInfo.InvariantCulture));
            int count = int.Parse(nudKeyCount.Value.ToString(CultureInfo.InvariantCulture));

            TimeMeasure.Start();
            try
            {
                for (int i = start; i < start + count; i++)
                {
                    if (_storageClosing)
                    {
                        break;
                    }
                    _storage.Set(i, RandomString(_random.Next(500)));

                    if (i % 1000 == 0)
                    {
                        lblState.Text = $"Inserting value {i}...";
                        Application.DoEvents();
                    }
                }

                if (!_storageClosing)
                {
                    lblState.Text = "Flushing...";
                    Application.DoEvents();
                    _storage.Flush();
                }
            }
            finally
            {
                TimeMeasure.Stop();
            }

            lblState.Text = $"Done! Elapsed time: {TimeMeasure.Result()}";
            TimeMeasure.Reset();
        }
 // Update is called once per frame
 void Update()
 {
     if (Time.timeSinceLevelLoad - UpdateInterval * 0.001f > mLastUpdateTime)
     {
         mLastUpdateTime = Time.timeSinceLevelLoad;
         ExecTimes.Start();
         if (AlwaysClear)
         {
             mSpline.Clear();
         }
         // Remove old CP
         while (mSpline.ControlPointCount > CPCount)
         {
             mSpline.ControlPoints[0].Delete();
         }
         // Add new CP(s)
         while (mSpline.ControlPointCount <= CPCount)
         {
             addCP();
         }
         mSpline.Refresh();
         ExecTimes.Stop();
     }
 }
Exemplo n.º 11
0
    public void AlgorithmOptimized()
    {
        /* Outline of the algorithm:
         * 0. Compute the convex hull of the point set (given as input to this function) O(VlogV)
         * 1. Compute vertex adjacency data, i.e. given a vertex, return a list of its neighboring vertices. O(V)
         * 2. Precompute face normal direction vectors, since these are needed often. (does not affect big-O complexity, just a micro-opt) O(F)
         * 3. Compute edge adjacency data, i.e. given an edge, return the two indices of its neighboring faces. O(V)
         * 4. Precompute antipodal vertices for each edge. O(A*ElogV), where A is the size of antipodal vertices per edge. A ~ O(1) on average.
         * 5. Precompute all sidepodal edges for each edge. O(E*S), where S is the size of sidepodal edges per edge. S ~ O(sqrtE) on average.
         *   - Sort the sidepodal edges to a linear order so that it's possible to do fast set intersection computations on them. O(E*S*logS), or O(E*sqrtE*logE).
         * 6. Test all configurations where all three edges are on adjacent faces. O(E*S^2) = O(E^2) or if smart with graph search, O(ES) = O(E*sqrtE)?
         * 7. Test all configurations where two edges are on opposing faces, and the third one is on a face adjacent to the two. O(E*sqrtE*logV)?
         * 8. Test all configurations where two edges are on the same face (OBB aligns with a face of the convex hull). O(F*sqrtE*logV).
         * 9. Return the best found OBB.
         */

        mVolume = float.MaxValue;

        var dataCloud      = mConvexHull3.DataCloud;
        var basicTriangles = mConvexHull3.BasicTriangles;
        var edges          = mConvexHull3.BasicEdges;
        var vertices       = mConvexHull3.Vertices;
        int verticesCount  = vertices.Count;

        if (verticesCount < 4)
        {
            return;
        }

        // Precomputation: For each vertex in the convex hull, compute their neighboring vertices.
        var adjacencyData = mConvexHull3.GetVertexAdjacencyData(); // O(V)

        // Precomputation: for each triangle create and store its normal, for later use.
        List <Vector3> faceNormals = new List <Vector3>(basicTriangles.Count);

        foreach (BasicTriangle basicTriangle in basicTriangles) // O(F)
        {
            Triangle triangle = Triangle.FromBasicTriangle(basicTriangle, dataCloud);
            faceNormals.Add(triangle.N);
        }

        // Precomputation: For each edge in convex hull, compute both connected faces data.
        Dictionary <int, List <int> > facesForEdge = mConvexHull3.GetEdgeFacesData(); // edgeFaces

        HashSet <int>    internalEdges  = new HashSet <int>();
        Func <int, bool> IsInternalEdge = (int edgeIndex) => { return(internalEdges.Contains(edgeIndex)); };

        for (int edgeIndex = 0; edgeIndex < edges.Count; edgeIndex++)
        {
            float dot = Vector3.Dot(
                faceNormals[facesForEdge[edgeIndex].First()],
                faceNormals[facesForEdge[edgeIndex].Second()]
                );

            bool isInternal = ExtMathf.Equal(dot, 1f, 1e-4f);

            if (isInternal)
            {
                internalEdges.Add(edgeIndex);
            }
        }

        // For each vertex pair (v, u) through which there is an edge, specifies the index i of the edge that passes through them.
        // This map contains duplicates, so both (v, u) and (u, v) map to the same edge index.
        List <int> vertexPairToEdges = mConvexHull3.GetUniversalEdgesList();

        // Throughout the whole algorithm, this array stores an auxiliary structure for performing graph searches
        // on the vertices of the convex hull. Conceptually each index of the array stores a boolean whether we
        // have visited that vertex or not during the current search. However storing such booleans is slow, since
        // we would have to perform a linear-time scan through this array before next search to reset each boolean
        // to unvisited false state. Instead, store a number, called a "color" for each vertex to specify whether
        // that vertex has been visited, and manage a global color counter floodFillVisitColor that represents the
        // visited vertices. At any given time, the vertices that have already been visited have the value
        // floodFillVisited[i] == floodFillVisitColor in them. This gives a win that we can perform constant-time
        // clears of the floodFillVisited array, by simply incrementing the "color" counter to clear the array.

        List <int> floodFillVisited    = new List <int>().Populate(-1, verticesCount);
        int        floodFillVisitColor = 1;

        Action           ClearGraphSearch  = () => { floodFillVisitColor++; };
        Action <int>     MarkVertexVisited = (int v) => { floodFillVisited[v] = floodFillVisitColor; };
        Func <int, bool> HaveVisitedVertex = (int v) => { return(floodFillVisited[v] == floodFillVisitColor); };

        List <List <int> > antipodalPointsForEdge = new List <List <int> >().Populate(new List <int>(), edges.Count); // edgeAntipodalVertices

        // The currently best variant for establishing a spatially coherent traversal order.
        HashSet <int> spatialFaceOrder = new HashSet <int>();
        HashSet <int> spatialEdgeOrder = new HashSet <int>();

        { // Explicit scope for variables that are not needed after this.
            List <Pair <int, int> > traverseStackEdge = new List <Pair <int, int> >();
            List <bool>             visitedEdges      = new List <bool>().Populate(false, edges.Count);
            List <bool>             visitedFaces      = new List <bool>().Populate(false, basicTriangles.Count);

            traverseStackEdge.Add(new Pair <int, int>(0, adjacencyData[0].First()));
            while (traverseStackEdge.Count != 0)
            {
                Pair <int, int> e = traverseStackEdge.Last();
                traverseStackEdge.RemoveLast();

                int thisEdge = vertexPairToEdges[e.First * verticesCount + e.Second];

                if (visitedEdges[thisEdge])
                {
                    continue;
                }
                visitedEdges[thisEdge] = true;

                if (!visitedFaces[facesForEdge[thisEdge].First()])
                {
                    visitedFaces[facesForEdge[thisEdge].First()] = true;
                    spatialFaceOrder.Add(facesForEdge[thisEdge].First());
                }

                if (!visitedFaces[facesForEdge[thisEdge].Second()])
                {
                    visitedFaces[facesForEdge[thisEdge].Second()] = true;
                    spatialFaceOrder.Add(facesForEdge[thisEdge].Second());
                }

                if (!IsInternalEdge(thisEdge))
                {
                    spatialEdgeOrder.Add(thisEdge);
                }

                int v0         = e.Second;
                int sizeBefore = traverseStackEdge.Count;

                foreach (int v1 in adjacencyData[v0])
                {
                    int e1 = vertexPairToEdges[v0 * verticesCount + v1];

                    if (visitedEdges[e1])
                    {
                        continue;
                    }

                    traverseStackEdge.Add(new Pair <int, int>(v0, v1));
                }

                //Take a random adjacent edge
                int nNewEdges = (traverseStackEdge.Count - sizeBefore);
                if (nNewEdges > 0)
                {
                    int r = UnityEngine.Random.Range(0, nNewEdges - 1);

                    var swapTemp = traverseStackEdge[traverseStackEdge.Count - 1];
                    traverseStackEdge[traverseStackEdge.Count - 1] = traverseStackEdge[sizeBefore + r];
                    traverseStackEdge[sizeBefore + r] = swapTemp;
                }
            }
        }

        // Stores a memory of yet unvisited vertices for current graph search.
        List <int> traverseStack = new List <int>();

        // Since we do several extreme vertex searches, and the search directions have a lot of spatial locality,
        // always start the search for the next extreme vertex from the extreme vertex that was found during the
        // previous iteration for the previous edge. This has been profiled to improve overall performance by as
        // much as 15-25%.

        int debugCount = 0;
        var startPoint = TimeMeasure.Start();

        // Precomputation: for each edge, we need to compute the list of potential antipodal points (points on
        // the opposing face of an enclosing OBB of the face that is flush with the given edge of the polyhedron).
        // This is O(E*log(V)) ?
        foreach (int i in spatialEdgeOrder)
        {
            Vector3 f1a = faceNormals[facesForEdge[i].First()];
            Vector3 f1b = faceNormals[facesForEdge[i].Second()];

            MinMaxPair breadthData;
            int        startingVertex = mConvexHull3.GetAlongAxisData(f1a, out breadthData);
            ClearGraphSearch(); // Search through the graph for all adjacent antipodal vertices.

            traverseStack.Add(startingVertex);
            MarkVertexVisited(startingVertex);
            while (!traverseStack.Empty())
            {   // In amortized analysis, only a constant number of vertices are antipodal points for any edge?
                int v = traverseStack.Last();
                traverseStack.RemoveLast();

                var neighbours = adjacencyData[v];
                if (IsVertexAntipodalToEdge(v, neighbours, f1a, f1b))
                {
                    if (edges[i].ContainsVertex(v))
                    {
                        Debug.LogFormat("Edge {0} is antipodal to vertex {1} which is part of the same edge!" +
                                        "This should be possible only if the input is degenerate planar!",
                                        edges[i], v);

                        return;
                    }

                    antipodalPointsForEdge[i].Add(v);
                    foreach (int neighboursVertex in neighbours)
                    {
                        if (!HaveVisitedVertex(neighboursVertex))
                        {
                            traverseStack.Add(neighboursVertex);
                            MarkVertexVisited(neighboursVertex);
                        }
                        debugCount++;
                    }
                }
                debugCount++;
            }

            // Robustness: If the above search did not find any antipodal points, add the first found extreme
            // point at least, since it is always an antipodal point. This is known to occur very rarely due
            // to numerical imprecision in the above loop over adjacent edges.
            if (antipodalPointsForEdge[i].Empty())
            {
                Debug.LogFormat("Got to place which is most likely a bug...");
                // Fall back to linear scan, which is very slow.
                for (int j = 0; j < verticesCount; j++)
                {
                    if (IsVertexAntipodalToEdge(j, adjacencyData[j], f1a, f1b))
                    {
                        antipodalPointsForEdge[i].Add(j);
                    }
                }
            }

            debugCount++;
        }

        Debug.Log("@ First loop count: " + debugCount + " in time: " + TimeMeasure.Stop(startPoint));

        // Stores for each edge i the list of all sidepodal edge indices j that it can form an OBB with.
        List <HashSet <int> > compatibleEdges = new List <HashSet <int> >().Populate(new HashSet <int>(), edges.Count);

        // Use a O(E*V) data structure for sidepodal vertices.
        bool[] sidepodalVertices = new bool[edges.Count * verticesCount].Populate(false);

        // Compute all sidepodal edges for each edge by performing a graph search. The set of sidepodal edges is
        // connected in the graph, which lets us avoid having to iterate over each edge pair of the convex hull.
        // Total running time is O(E*sqrtE).
        debugCount = 0;
        startPoint = TimeMeasure.Start();
        foreach (int i in spatialEdgeOrder)
        {
            Vector3 f1a = faceNormals[facesForEdge[i].First()];
            Vector3 f1b = faceNormals[facesForEdge[i].Second()];

            Vector3 deadDirection = (f1a + f1b) * .5f;
            Vector3 basis1, basis2;
            deadDirection.PerpendicularBasis(out basis1, out basis2);

            Vector3 dir = f1a.Perpendicular();

            MinMaxPair breadthData;
            int        startingVertex = mConvexHull3.GetAlongAxisData(-dir, out breadthData);
            ClearGraphSearch();

            traverseStack.Add(startingVertex);
            while (!traverseStack.Empty()) // O(sqrt(E))
            {
                int v = traverseStack.Last();
                traverseStack.RemoveLast();

                if (HaveVisitedVertex(v))
                {
                    continue;
                }
                MarkVertexVisited(v);

                foreach (int vAdj in adjacencyData[v])
                {
                    if (HaveVisitedVertex(vAdj))
                    {
                        continue;
                    }

                    int edge = vertexPairToEdges[v * verticesCount + vAdj];
                    if (AreEdgesCompatibleForOBB(f1a, f1b, faceNormals[facesForEdge[edge].First()], faceNormals[facesForEdge[edge].Second()]))
                    {
                        if (i <= edge)
                        {
                            if (!IsInternalEdge(edge))
                            {
                                compatibleEdges[i].Add(edge);
                            }

                            sidepodalVertices[i * verticesCount + edges[edge].v[0]] = true;
                            sidepodalVertices[i * verticesCount + edges[edge].v[1]] = true;

                            if (i != edge)
                            {
                                if (!IsInternalEdge(edge))
                                {
                                    compatibleEdges[edge].Add(i);
                                }

                                sidepodalVertices[edge * verticesCount + edges[i].v[0]] = true;
                                sidepodalVertices[edge * verticesCount + edges[i].v[1]] = true;
                            }
                        }

                        traverseStack.Add(vAdj);
                    }

                    debugCount++;
                }

                debugCount++;
            }

            debugCount++;
        }

        Debug.Log("@ Second loop count: " + debugCount + " in time: " + TimeMeasure.Stop(startPoint));

        // Take advantage of spatial locality: start the search for the extreme vertex from the extreme vertex
        // that was found during the previous iteration for the previous edge. This speeds up the search since
        // edge directions have some amount of spatial locality and the next extreme vertex is often close
        // to the previous one. Track two hint variables since we are performing extreme vertex searches to
        // two opposing directions at the same time.

        // Stores a memory of yet unvisited vertices that are common sidepodal vertices to both currently chosen edges for current graph search.
        List <int> traverseStackCommonSidepodals = new List <int>();

        Debug.Log("@ Edges count: " + edges.Count);
        Debug.Log("@ Vertices count: " + verticesCount);
        debugCount = 0;
        startPoint = TimeMeasure.Start();
        Debug.Log("@ Spatial edges: " + spatialEdgeOrder.Count);
        foreach (int i in spatialEdgeOrder)
        {
            Vector3 f1a = faceNormals[facesForEdge[i].First()];
            Vector3 f1b = faceNormals[facesForEdge[i].Second()];

            Vector3 deadDirection = (f1a + f1b) * .5f;

            Debug.Log("@ Compatible edges: " + compatibleEdges[i].Count);
            foreach (int edgeJ in compatibleEdges[i])
            {
                if (edgeJ <= i) // Remove symmetry.
                {
                    continue;
                }

                Vector3 f2a = faceNormals[facesForEdge[edgeJ].First()];
                Vector3 f2b = faceNormals[facesForEdge[edgeJ].Second()];

                Vector3 deadDirection2 = (f2a + f2b) * .5f;

                Vector3 searchDir = Vector3.Cross(deadDirection, deadDirection2).normalized;
                float   length    = searchDir.magnitude;

                if (ExtMathf.Equal(length, 0f))
                {
                    searchDir = Vector3.Cross(f1a, f2a).normalized;
                    length    = searchDir.magnitude;

                    if (ExtMathf.Equal(length, 0f))
                    {
                        searchDir = f1a.Perpendicular();
                    }
                }

                ClearGraphSearch();
                MinMaxPair breadthData;
                int        extremeVertexSearchHint1 = mConvexHull3.GetAlongAxisData(-searchDir, out breadthData);

                ClearGraphSearch();
                MinMaxPair breadthData2;
                int        extremeVertexSearchHint2 = mConvexHull3.GetAlongAxisData(searchDir, out breadthData2);

                ClearGraphSearch();

                int secondSearch = -1;

                if (sidepodalVertices[edgeJ * verticesCount + extremeVertexSearchHint1])
                {
                    traverseStackCommonSidepodals.Add(extremeVertexSearchHint1);
                }
                else
                {
                    traverseStack.Add(extremeVertexSearchHint1);
                }

                if (sidepodalVertices[edgeJ * verticesCount + extremeVertexSearchHint2])
                {
                    traverseStackCommonSidepodals.Add(extremeVertexSearchHint2);
                }
                else
                {
                    secondSearch = extremeVertexSearchHint2;
                }

                // Bootstrap to a good vertex that is sidepodal to both edges.
                ClearGraphSearch();
                while (!traverseStack.Empty())
                {
                    int v = traverseStack.First();
                    traverseStack.RemoveFirst();

                    if (HaveVisitedVertex(v))
                    {
                        continue;
                    }
                    MarkVertexVisited(v);

                    foreach (int vAdj in adjacencyData[v])
                    {
                        if (!HaveVisitedVertex(vAdj) && sidepodalVertices[i * verticesCount + vAdj])
                        {
                            if (sidepodalVertices[edgeJ * verticesCount + vAdj])
                            {
                                traverseStack.Clear();
                                if (secondSearch != -1)
                                {
                                    traverseStack.Add(secondSearch);
                                    secondSearch = -1;
                                    MarkVertexVisited(vAdj);
                                }

                                traverseStackCommonSidepodals.Add(vAdj);
                                break;
                            }
                            else
                            {
                                traverseStack.Add(vAdj);
                            }
                        }

                        debugCount++;
                    }

                    debugCount++;
                }

                ClearGraphSearch();
                while (!traverseStackCommonSidepodals.Empty())
                {
                    int v = traverseStackCommonSidepodals.Last();
                    traverseStackCommonSidepodals.RemoveLast();

                    if (HaveVisitedVertex(v))
                    {
                        continue;
                    }
                    MarkVertexVisited(v);

                    foreach (int vAdj in adjacencyData[v])
                    {
                        int edgeK = vertexPairToEdges[v * verticesCount + vAdj];

                        if (IsInternalEdge(edgeK))  // Edges inside faces with 180 degrees dihedral angles can be ignored.
                        {
                            continue;
                        }

                        if (sidepodalVertices[i * verticesCount + vAdj] && sidepodalVertices[edgeJ * verticesCount + vAdj])
                        {
                            if (!HaveVisitedVertex(vAdj))
                            {
                                traverseStackCommonSidepodals.Add(vAdj);
                            }

                            if (edgeJ < edgeK)
                            {
                                Vector3 f3a = faceNormals[facesForEdge[edgeK].First()];
                                Vector3 f3b = faceNormals[facesForEdge[edgeK].Second()];

                                Vector3[] n1 = new Vector3[2];
                                Vector3[] n2 = new Vector3[2];
                                Vector3[] n3 = new Vector3[2];

                                int nSolutions = ComputeBasis(f1a, f1b, f2a, f2b, f3a, f3b, ref n1, ref n2, ref n3);
                                for (int s = 0; s < nSolutions; s++)
                                {
                                    BoxFromNormalAxes(n1[s], n2[s], n3[s]);
                                }
                            }
                        }

                        debugCount++;
                    }

                    debugCount++;
                }

                debugCount++;
            }

            debugCount++;
        }

        Debug.Log("@ Third loop count: " + debugCount + " in time: " + TimeMeasure.Stop(startPoint));

        HashSet <int>  antipodalEdges       = new HashSet <int>();
        List <Vector3> antipodalEdgeNormals = new List <Vector3>();

        // Main algorithm body for finding all search directions where the OBB is flush with the edges of the
        // convex hull from two opposing faces. This is O(E*sqrtE*logV)?
        debugCount = 0;
        startPoint = TimeMeasure.Start();
        foreach (int i in spatialEdgeOrder)
        {
            Vector3 f1a = faceNormals[facesForEdge[i].First()];
            Vector3 f1b = faceNormals[facesForEdge[i].Second()];

            antipodalEdges.Clear();
            antipodalEdgeNormals.Clear();

            foreach (int antipodalVertex in antipodalPointsForEdge[i])
            {
                foreach (int vAdj in adjacencyData[antipodalVertex])
                {
                    if (vAdj < antipodalVertex) // We search unordered edges, so no need to process edge (v1, v2) and (v2, v1)
                    {
                        continue;               // twice - take the canonical order to be antipodalVertex < vAdj
                    }
                    int edge = vertexPairToEdges[antipodalVertex * verticesCount + vAdj];
                    if (i > edge) // We search pairs of edges, so no need to process twice - take the canonical order to be i < edge.
                    {
                        continue;
                    }

                    if (IsInternalEdge(edge))
                    {
                        continue; // Edges inside faces with 180 degrees dihedral angles can be ignored.
                    }
                    Vector3 f2a = faceNormals[facesForEdge[edge].First()];
                    Vector3 f2b = faceNormals[facesForEdge[edge].Second()];

                    Vector3 normal;
                    bool    success = AreCompatibleOpposingEdges(f1a, f1b, f2a, f2b, out normal);
                    if (success)
                    {
                        antipodalEdges.Add(edge);
                        antipodalEdgeNormals.Add(normal.normalized);
                    }

                    debugCount++;
                }

                debugCount++;
            }

            var compatibleEdgesI = compatibleEdges[i];
            foreach (int edgeJ in compatibleEdgesI)
            {
                int k = 0;
                foreach (int edgeK in antipodalEdges)
                {
                    Vector3 n1 = antipodalEdgeNormals[k++];

                    MinMaxPair N1 = new MinMaxPair();
                    N1.Min = Vector3.Dot(n1, dataCloud[edges[edgeK].v[0]]);
                    N1.Max = Vector3.Dot(n1, dataCloud[edges[i].v[0]]);

                    Debug.Assert(N1.Min < N1.Max);

                    // Test all mutual compatible edges.
                    Vector3 f3a = faceNormals[facesForEdge[edgeJ].First()];
                    Vector3 f3b = faceNormals[facesForEdge[edgeJ].Second()];

                    float num   = Vector3.Dot(n1, f3b);
                    float denom = Vector3.Dot(n1, f3b - f3a);

                    RefAction <float, float> MoveSign = (ref float dst, ref float src) =>
                    {
                        if (src < 0f)
                        {
                            dst = -dst;
                            src = -src;
                        }
                    };
                    MoveSign(ref num, ref denom);

                    float epsilon = 1e-4f;
                    if (denom < epsilon)
                    {
                        num   = (Mathf.Abs(num) == 0f) ? 0f : -1f;
                        denom = 1f;
                    }

                    if (num >= denom * -epsilon && num <= denom * (1f + epsilon))
                    {
                        float v = num / denom;

                        Vector3 n3 = (f3b + (f3a - f3b) * v).normalized;
                        Vector3 n2 = Vector3.Cross(n3, n1).normalized;

                        BoxFromNormalAxes(n1, n2, n3);
                    }

                    debugCount++;
                }

                debugCount++;
            }

            debugCount++;
        }

        Debug.Log("@ Fourth loop count: " + debugCount + " in time: " + TimeMeasure.Stop(startPoint));

        // Main algorithm body for computing all search directions where the OBB touches two edges on the same face.
        // This is O(F*sqrtE*logV)?
        debugCount = 0;
        startPoint = TimeMeasure.Start();
        foreach (int i in spatialFaceOrder)
        {
            Vector3 n1 = faceNormals[i];

            // Find two edges on the face. Since we have flexibility to choose from multiple edges of the same face,
            // choose two that are possibly most opposing to each other, in the hope that their sets of sidepodal
            // edges are most mutually exclusive as possible, speeding up the search below.
            int e1 = -1;
            int v0 = basicTriangles[i].v[2];

            for (int j = 0; j < basicTriangles[i].v.Length; j++)
            {
                int v1 = basicTriangles[i].v[j];
                int e  = vertexPairToEdges[v0 * verticesCount + v1];
                if (!IsInternalEdge(e))
                {
                    e1 = e;
                    break;
                }
                v0 = v1;
            }

            if (e1 == -1)
            {
                continue; // All edges of this face were degenerate internal edges! Just skip processing the whole face.
            }
            var compatibleEdgesI = compatibleEdges[e1];
            foreach (int edge3 in compatibleEdgesI)
            {
                Vector3 f3a = faceNormals[facesForEdge[edge3].First()];
                Vector3 f3b = faceNormals[facesForEdge[edge3].Second()];

                float num   = Vector3.Dot(n1, f3b);
                float denom = Vector3.Dot(n1, f3b - f3a);
                float v;
                if (!ExtMathf.Equal(Mathf.Abs(denom), 0f))
                {
                    v = num / denom;
                }
                else
                {
                    v = ExtMathf.Equal(Mathf.Abs(num), 0f) ? 0f : -1f;
                }

                const float epsilon = 1e-4f;
                if (v >= 0f - epsilon && v <= 1f + epsilon)
                {
                    Vector3 n3 = (f3b + (f3a - f3b) * v).normalized;
                    Vector3 n2 = Vector3.Cross(n3, n1).normalized;

                    BoxFromNormalAxes(n1, n2, n3);
                }

                debugCount++;
            }

            debugCount++;
        }

        Debug.Log("@ Fifth loop count: " + debugCount + " in time: " + TimeMeasure.Stop(startPoint));
    }