// 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); }
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(); }
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(); } }
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; }
/// <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)); } }
// 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); } }
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); }
// 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; }
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(); } }
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)); }