Esempio n. 1
0
    public static void TestExtMath()
    {
        float a = 1f;
        float b = 1f + 1e-6f;
        float c = 1f + 1e-4f;

        Debug.Assert(ExtMathf.Equal(a, b));
        Debug.Assert(!ExtMathf.Equal(a, c));
        Debug.Assert(ExtMathf.Greater(c, a));
        Debug.Assert(!ExtMathf.Greater(b, a));
        Debug.Assert(ExtMathf.Less(a, c));
        Debug.Assert(!ExtMathf.Less(a, b));

        Vector3 A = new Vector3(-1, 0, -1);
        Vector3 B = new Vector3(1, 0, 1);
        Vector3 C = new Vector3(0, 1, 0);
        Vector3 D = new Vector3(0, (1f / 3f), 0);

        Debug.Assert(ExtMathf.Centroid(A, B, C) == D);

        Vector3 xAxis = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f)).normalized;
        Vector3 yAxis;
        Vector3 zAxis;

        ExtMathf.AxesFromAxis(xAxis, out yAxis, out zAxis);
        Debug.Assert(ExtMathf.Equal(Vector3.Cross(yAxis, zAxis).normalized, xAxis));
        Debug.Assert(ExtMathf.Equal(Vector3.Cross(zAxis, xAxis).normalized, yAxis));
        Debug.Assert(ExtMathf.Equal(Vector3.Cross(xAxis, yAxis).normalized, zAxis));
    }
Esempio n. 2
0
    /// <summary>
    /// Checks whether point 'V' lies on a line that passes through this Edge.
    /// </summary>
    public bool CalculateIfIsOnLine(Vector3 P)
    {
        float dV  = (V[0] - V[1]).sqrMagnitude;
        float dP1 = (V[0] - P).sqrMagnitude;
        float dP2 = (P - V[1]).sqrMagnitude;

        float dP = dP1 + dP2;

        return(ExtMathf.Equal(dV, dP));
    }
    public static bool SolveAxb(this Matrix4x4 matrix, Vector3 b, ref Vector3 x)
    {
        float v00 = matrix.m00;
        float v10 = matrix.m10;
        float v20 = matrix.m20;

        float v01 = matrix.m01;
        float v11 = matrix.m11;
        float v21 = matrix.m21;

        float v02 = matrix.m02;
        float v12 = matrix.m12;
        float v22 = matrix.m22;

        float av00 = Mathf.Abs(v00);
        float av10 = Mathf.Abs(v10);
        float av20 = Mathf.Abs(v20);

        // Find which item in first column has largest absolute value.
        if (av10 >= av00 && av10 >= av20)
        {
            Utilities.Swap(ref v00, ref v10);
            Utilities.Swap(ref v01, ref v11);
            Utilities.Swap(ref v02, ref v12);
            Utilities.Swap(ref b.x, ref b.y);
        }
        else if (av20 >= av00)
        {
            Utilities.Swap(ref v00, ref v20);
            Utilities.Swap(ref v01, ref v21);
            Utilities.Swap(ref v02, ref v22);
            Utilities.Swap(ref b.x, ref b.z);
        }

        /* a b c | x
         * d e f | y
         * g h i | z , where |a| >= |d| && |a| >= |g| */

        if (ExtMathf.Equal(av00, 0f))
        {
            return(false);
        }

        // Scale row so that leading element is one.
        float denom = 1f / v00;

        v01 *= denom;
        v02 *= denom;
        b.x *= denom;

        /* 1 b c | x
        *  d e f | y
        *  g h i | z */

        // Zero first column of second and third rows.
        v11 -= v10 * v01;
        v12 -= v10 * v02;
        b.y -= v10 * b.x;

        v21 -= v20 * v01;
        v22 -= v20 * v02;
        b.z -= v20 * b.x;

        /* 1 b c | x
        *  0 e f | y
        *  0 h i | z */

        // Pivotize again.
        if (Mathf.Abs(v21) > Mathf.Abs(v11))
        {
            Utilities.Swap(ref v11, ref v21);
            Utilities.Swap(ref v12, ref v22);
            Utilities.Swap(ref b.y, ref b.z);
        }

        if (ExtMathf.Equal(Mathf.Abs(v11), 0f))
        {
            return(false);
        }

        /* 1 b c | x
         * 0 e f | y
         * 0 h i | z, where |e| >= |h| */

        denom = 1f / v11;
        v12  *= denom;
        b.y  *= denom;

        /* 1 b c | x
        *  0 1 f | y
        *  0 h i | z */

        v22 -= v21 * v12;
        b.z -= v21 * b.y;

        /* 1 b c | x
        *  0 1 f | y
        *  0 0 i | z */

        if (ExtMathf.Equal(Mathf.Abs(v22), 0f))
        {
            return(false);
        }

        x.z = b.z / v22;
        x.y = b.y - x.z * v12;
        x.x = b.x - x.z * v02 - x.y * v01;

        return(true);
    }
Esempio n. 4
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));
    }
    public void AlgorithmRandomIncremental()
    {
        ClearIfNecessary();

        if (mDataCloud.Count < 4)
        {
            Debugger.Get.Log("Insufficient points to generate a tetrahedron.", DebugOption.CH3);
            return;
        }

        int index = 0;
        // Pick first point.
        KeyValuePair <int, Vector3> P1 = new KeyValuePair <int, Vector3>(index, mDataCloud[index]);

        KeyValuePair <int, Vector3> P2 = new KeyValuePair <int, Vector3>(-1, Vector3.zero);

        while (P2.Key == -1 && ++index < mDataCloud.Count)
        {
            // Two points form a line if they are not equal.
            Vector3 P = mDataCloud[index];
            if (!ExtMathf.Equal(P2.Value, P))
            {
                P2 = new KeyValuePair <int, Vector3>(index, P);
            }
        }
        if (P2.Key == -1)
        {
            Debugger.Get.Log("Couldn't find two not equal points.", DebugOption.CH3);
            return;
        }

        // Points on line and on the same plane, still should be considered later on. Happens.
        List <int> skippedPoints = new List <int>();

        Edge line = new Edge(P1.Value, P2.Value);
        KeyValuePair <int, Vector3> P3 = new KeyValuePair <int, Vector3>(-1, Vector3.zero);

        while (P3.Key == -1 && ++index < mDataCloud.Count)
        {
            // Three points form a triangle if they are not on the same line.
            Vector3 P = mDataCloud[index];
            if (!line.CalculateIfIsOnLine(P))
            {
                P3 = new KeyValuePair <int, Vector3>(index, P);
            }
            else
            {
                skippedPoints.Add(index);
            }
        }
        if (P3.Key == -1)
        {
            Debugger.Get.Log("Couldn't find three not linear points.", DebugOption.CH3);
            return;
        }

        Triangle planeTriangle         = new Triangle(P1.Value, P2.Value, P3.Value);
        KeyValuePair <int, Vector3> P4 = new KeyValuePair <int, Vector3>(-1, Vector3.zero);

        while (P4.Key == -1 && ++index < mDataCloud.Count)
        {
            // Four points form a tetrahedron if they are not on the same plane.
            Vector3 P = mDataCloud[index];
            if (planeTriangle.CalculateRelativePosition(P) != 0)
            {
                P4 = new KeyValuePair <int, Vector3>(index, P);
            }
            else
            {
                skippedPoints.Add(index);
            }
        }
        if (P4.Key == -1)
        {
            Debugger.Get.Log("Couldn't find four not planar points.", DebugOption.CH3);
            return;
        }

        // Calculate reference centroid of ConvexHull.
        Vector3 centroid = ExtMathf.Centroid(P1.Value, P2.Value, P3.Value, P4.Value);

        List <BasicTriangle> tetrahedronTriangles = new List <BasicTriangle>();

        tetrahedronTriangles.Add(new BasicTriangle(P3.Key, P2.Key, P1.Key));
        tetrahedronTriangles.Add(new BasicTriangle(P1.Key, P2.Key, P4.Key));
        tetrahedronTriangles.Add(new BasicTriangle(P2.Key, P3.Key, P4.Key));
        tetrahedronTriangles.Add(new BasicTriangle(P3.Key, P1.Key, P4.Key));

        foreach (BasicTriangle basicTriangle in tetrahedronTriangles)
        {
            Triangle triangle = new Triangle(
                mDataCloud[basicTriangle.v[0]],
                mDataCloud[basicTriangle.v[1]],
                mDataCloud[basicTriangle.v[2]]
                );

            if (triangle.CalculateRelativePosition(centroid) != -1)
            {   // Centroid of ConvexHull should always be inside.
                basicTriangle.Reverse();
            }
            mBasicTriangles.Add(basicTriangle);
        }

        Dictionary <int, HashSet <BasicTriangle> > pointFacets = new Dictionary <int, HashSet <BasicTriangle> >();
        Dictionary <BasicTriangle, HashSet <int> > facetPoints = new Dictionary <BasicTriangle, HashSet <int> >();

        foreach (BasicTriangle basicTriangle in mBasicTriangles)
        {
            Triangle triangle = new Triangle(
                mDataCloud[basicTriangle.v[0]],
                mDataCloud[basicTriangle.v[1]],
                mDataCloud[basicTriangle.v[2]]
                );

            for (int p = index; p < mDataCloud.Count; p++)
            {
                if (triangle.CalculateRelativePosition(mDataCloud[p]) == 1)
                {
                    if (!pointFacets.ContainsKey(p))
                    {
                        pointFacets.Add(p, new HashSet <BasicTriangle>());
                    }
                    pointFacets[p].Add(basicTriangle);

                    if (!facetPoints.ContainsKey(basicTriangle))
                    {
                        facetPoints.Add(basicTriangle, new HashSet <int>());
                    }
                    facetPoints[basicTriangle].Add(p);
                }
            }

            foreach (int p in skippedPoints)
            {
                if (triangle.CalculateRelativePosition(mDataCloud[p]) == 1)
                {
                    if (!pointFacets.ContainsKey(p))
                    {
                        pointFacets.Add(p, new HashSet <BasicTriangle>());
                    }
                    pointFacets[p].Add(basicTriangle);
                    facetPoints[basicTriangle].Add(p);
                }
            }
        }

        while (pointFacets.Count > 0)
        {
            var firstPointFacet = pointFacets.First();
            if (firstPointFacet.Value.Count > 0)
            {
                Dictionary <BasicEdge, BasicEdge> horizon = new Dictionary <BasicEdge, BasicEdge>();

                foreach (BasicTriangle basicTriangle in firstPointFacet.Value)
                {
                    for (int a = 2, b = 0; b < 3; a = b++)
                    {
                        BasicEdge edge          = new BasicEdge(basicTriangle.v[a], basicTriangle.v[b]);
                        BasicEdge edgeUnordered = edge.Unordered();

                        if (horizon.ContainsKey(edgeUnordered))
                        {
                            horizon.Remove(edgeUnordered);
                        }
                        else
                        {
                            horizon.Add(edgeUnordered, edge);
                        }
                    }
                }

                int pointKey = firstPointFacet.Key;
                foreach (BasicTriangle facet in firstPointFacet.Value)
                {
                    foreach (int facetPointKey in facetPoints[facet])
                    {
                        if (facetPointKey != pointKey)
                        {
                            pointFacets[facetPointKey].Remove(facet);
                        }
                    }

                    facetPoints.Remove(facet);
                    mBasicTriangles.Remove(facet);
                }
                pointFacets.Remove(pointKey);

                foreach (KeyValuePair <BasicEdge, BasicEdge> edges in horizon)
                {
                    BasicTriangle newBasicTriangle = new BasicTriangle(edges.Value.v[0], edges.Value.v[1], pointKey);

                    mBasicTriangles.Add(newBasicTriangle);

                    if (pointFacets.Count > 0)
                    {
                        Triangle newTriangle = new Triangle(
                            mDataCloud[newBasicTriangle.v[0]],
                            mDataCloud[newBasicTriangle.v[1]],
                            mDataCloud[newBasicTriangle.v[2]]
                            );

                        foreach (var pointFacetsKey in pointFacets.Keys)
                        {
                            if (newTriangle.CalculateRelativePosition(mDataCloud[pointFacetsKey]) == 1)
                            {
                                pointFacets[pointFacetsKey].Add(newBasicTriangle);

                                if (!facetPoints.ContainsKey(newBasicTriangle))
                                {
                                    facetPoints.Add(newBasicTriangle, new HashSet <int>());
                                }
                                facetPoints[newBasicTriangle].Add(pointFacetsKey);
                            }
                        }
                    }
                }
            }
            else
            {
                pointFacets.Remove(firstPointFacet.Key);
            }
        }
    }