From c213a123edeedf9491654bdfeabfdfa5f51bfb7d Mon Sep 17 00:00:00 2001 From: Sander van Rossen Date: Sat, 24 Oct 2026 23:30:13 +0200 Subject: [PATCH] Remove the old CSG path and the BurstTriangulator dependency The exact CSG has been the default for a while; the float CSG that made polygon loops was only reachable through an internal switch. - Removed the loop CSG (PerformCSGJob, the intersection loop, merge and loop vertex cache jobs), the loop triangulation and repair in GenerateSurfaceTrianglesJob, the kExactCSG switch, the propagation rounds, interned planes and the float brush intersection test. - GenerateSurfaceTrianglesJob is Burst compiled itself. - BurstTriangulator was only used by the loop triangulation: the submodule and its reference are gone. - Tests that compared the two paths, or only covered the old one, are gone; the concrete pad area checks now run. Fixes #57 --- .gitmodules | 3 - CONTRIBUTING.md | 2 +- Components/Managers/ChiselModelManager.cs | 8 +- Core/2.Processing/CSGMath.cs | 4 +- Core/2.Processing/Cache/ChiselLookup.cs | 23 +- .../2.Processing/Containers/InternedPlanes.cs | 242 --- .../Containers/InternedPlanes.cs.meta | 2 - Core/2.Processing/Exact/ExactFace.cs | 2 +- Core/2.Processing/Jobs/CacheRemappingJob.cs | 5 - .../Jobs/CountIntersectionLoopsJob.cs | 46 - .../Jobs/CountIntersectionLoopsJob.cs.meta | 2 - .../Jobs/CreateIntersectionLoopsJob.cs | 1172 ------------ .../Jobs/CreateIntersectionLoopsJob.cs.meta | 11 - .../Jobs/FindAllBrushIntersectionPairsJob.cs | 170 +- Core/2.Processing/Jobs/FindBrushPairsJob.cs | 99 - .../Jobs/FindBrushPairsJob.cs.meta | 11 - .../Jobs/FindLoopOverlapIntersectionJob.cs | 1035 ---------- .../FindLoopOverlapIntersectionJob.cs.meta | 11 - .../Jobs/FindModifiedBrushesJob.cs | 3 +- .../Jobs/GatherOutputSurfacesJob.cs | 66 - .../Jobs/GatherOutputSurfacesJob.cs.meta | 11 - .../Jobs/GenerateSurfaceTrianglesJob.cs | 757 +------- .../2.Processing/Jobs/InternBrushPlanesJob.cs | 97 - .../Jobs/InternBrushPlanesJob.cs.meta | 2 - .../Jobs/JobData/BrushesTouchedByBrush.cs | 78 - .../Jobs/LoopVerticesCacheJobs.cs | 222 --- .../Jobs/LoopVerticesCacheJobs.cs.meta | 2 - .../Jobs/MergeTouchingBrushVerticesJob.cs | 278 --- .../MergeTouchingBrushVerticesJob.cs.meta | 11 - Core/2.Processing/Jobs/PerformCSGJob.cs | 1678 ----------------- Core/2.Processing/Jobs/PerformCSGJob.cs.meta | 11 - .../Jobs/PrepareBrushPairIntersectionsJob.cs | 623 ------ .../PrepareBrushPairIntersectionsJob.cs.meta | 11 - .../Managers/CSGManager.UpdateTreeMeshes.cs | 678 +------ Core/Tests/AlignedFaceDetectionTests.cs | 131 -- Core/Tests/AlignedFaceDetectionTests.cs.meta | 2 - Core/Tests/AlignedFaceLoopDeliveryTests.cs | 241 --- .../AlignedFaceLoopDeliveryTests.cs.meta | 2 - .../Contents/CanonicalVertexEditTests.cs | 5 +- Core/Tests/Contents/ConcretePadTests.cs | 5 +- Core/Tests/Contents/ContentsComparison.cs | 80 +- Core/Tests/Contents/ContentsHarvest.cs | 12 +- Core/Tests/Contents/ContentsTreeHarness.cs | 32 +- Core/Tests/Contents/ExactCSGProbeTests.cs | 269 +-- Core/Tests/Contents/ExactDelaunayTests.cs | 2 - Core/Tests/Contents/ExactInputTests.cs | 2 - Core/Tests/Contents/ExactJudgeCaseTests.cs | 2 - Core/Tests/Contents/ExactJudgeTests.cs | 2 - .../Tests/Contents/SharedCornerSurveyTests.cs | 609 ------ .../Contents/SharedCornerSurveyTests.cs.meta | 2 - Core/Tests/InternedPlanesTests.cs | 274 --- Core/Tests/InternedPlanesTests.cs.meta | 2 - Core/Tests/LoopSplitCapacityTests.cs | 204 -- Core/Tests/LoopSplitCapacityTests.cs.meta | 2 - Core/assembly.cs | 1 - Core/com.chisel.core.asmdef | 1 - Dependencies/BurstTriangulator | 1 - Dependencies/BurstTriangulator.meta | 8 - INSTALLATION.md | 2 +- 59 files changed, 169 insertions(+), 9100 deletions(-) delete mode 100644 Core/2.Processing/Containers/InternedPlanes.cs delete mode 100644 Core/2.Processing/Containers/InternedPlanes.cs.meta delete mode 100644 Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs delete mode 100644 Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs delete mode 100644 Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/FindBrushPairsJob.cs delete mode 100644 Core/2.Processing/Jobs/FindBrushPairsJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs delete mode 100644 Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs delete mode 100644 Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/InternBrushPlanesJob.cs delete mode 100644 Core/2.Processing/Jobs/InternBrushPlanesJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs delete mode 100644 Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs.meta delete mode 100644 Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs delete mode 100644 Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/PerformCSGJob.cs delete mode 100644 Core/2.Processing/Jobs/PerformCSGJob.cs.meta delete mode 100644 Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs delete mode 100644 Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs.meta delete mode 100644 Core/Tests/AlignedFaceDetectionTests.cs delete mode 100644 Core/Tests/AlignedFaceDetectionTests.cs.meta delete mode 100644 Core/Tests/AlignedFaceLoopDeliveryTests.cs delete mode 100644 Core/Tests/AlignedFaceLoopDeliveryTests.cs.meta delete mode 100644 Core/Tests/Contents/SharedCornerSurveyTests.cs delete mode 100644 Core/Tests/Contents/SharedCornerSurveyTests.cs.meta delete mode 100644 Core/Tests/InternedPlanesTests.cs delete mode 100644 Core/Tests/InternedPlanesTests.cs.meta delete mode 100644 Core/Tests/LoopSplitCapacityTests.cs delete mode 100644 Core/Tests/LoopSplitCapacityTests.cs.meta delete mode 160000 Dependencies/BurstTriangulator delete mode 100644 Dependencies/BurstTriangulator.meta diff --git a/.gitmodules b/.gitmodules index d298235a..5dbc4fb1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "Dependencies/CustomAssetMetadata"] path = Dependencies/CustomAssetMetadata url = https://github.com/LogicalError/CustomAssetMetadata.git -[submodule "Dependencies/BurstTriangulator"] - path = Dependencies/BurstTriangulator - url = https://github.com/RadicalCSG/BurstTriangulator.git diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be287c07..698043ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Chisel is a Unity package. To work on it, put it in a Unity project as an embedd `git submodule update --init` in it. 3. Open the project in Unity. The first time takes a while, because Burst compiles a lot of code. -The package's dependencies, BurstTriangulator and CustomAssetMetadata, are git submodules in `Dependencies`. +The package's dependency, CustomAssetMetadata, is a git submodule in `Dependencies`. ## Where things are diff --git a/Components/Managers/ChiselModelManager.cs b/Components/Managers/ChiselModelManager.cs index 56dc4198..77ea8b26 100644 --- a/Components/Managers/ChiselModelManager.cs +++ b/Components/Managers/ChiselModelManager.cs @@ -848,9 +848,8 @@ public static Hash128 GetComponentInputHash(ChiselModelComponent model) /// /// What both hashes have to cover and neither can see in the model: the version of the code that builds the - /// meshes, which of its two CSG algorithms builds them, and the helper materials FinishMeshUpdates hands - /// out, which no surface names. Without the code version a saved scene would keep meshes built by code that has - /// since changed, and without the algorithm meshes built by the other one. + /// meshes, and the helper materials FinishMeshUpdates hands out, which no surface names. Without the code + /// version a saved scene would keep meshes built by code that has since changed. /// static void AppendEnvironment(ref Hash128 hash) { @@ -861,9 +860,6 @@ static void AppendEnvironment(ref Hash128 hash) } hash.Append(ref s_CodeVersion); - int algorithm = CompactHierarchyManager.TreeUpdate.kExactCSG ? 1 : 0; - hash.Append(ref algorithm); - AppendMaterialIdentity(ref hash, ChiselProjectSettings.ForceShadowOnlySurfacesMaterial); AppendMaterialIdentity(ref hash, ChiselProjectSettings.CollisionSurfacesMaterial); var debugVisualizationMaterials = ChiselProjectSettings.DebugVisualizationMaterials; diff --git a/Core/2.Processing/CSGMath.cs b/Core/2.Processing/CSGMath.cs index af9ffc79..2c53fff2 100644 --- a/Core/2.Processing/CSGMath.cs +++ b/Core/2.Processing/CSGMath.cs @@ -42,8 +42,8 @@ public static float3 EdgePlaneCrossing(float3 v0, float3 v1, float d0, float d1) public static double3 PlaneIntersection(double4 plane0, double4 plane1, double4 plane2) => PlaneExtensions.Intersection(plane0, plane1, plane2); - // One vertex-pair term of Newell's polygon-normal accumulation, in double. Matches - // CalculatePlaneNormal in PerformCSGJob. Accumulate over consecutive (prev,curr) verts, normalize. + // One vertex-pair term of Newell's polygon-normal accumulation, in double. Accumulate over consecutive + // (prev,curr) verts, normalize. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float3 NewellTerm(float3 prev, float3 curr) { diff --git a/Core/2.Processing/Cache/ChiselLookup.cs b/Core/2.Processing/Cache/ChiselLookup.cs index 8c2e95a6..49435be1 100644 --- a/Core/2.Processing/Cache/ChiselLookup.cs +++ b/Core/2.Processing/Cache/ChiselLookup.cs @@ -26,9 +26,8 @@ public class Data public NativeList brushTreeSpaceBoundCache; public NativeList> brushTreeSpacePlaneCache; public NativeList> brushesTouchedByBrushCache; - public NativeList> loopVerticesCache; // Each brush as the exact CSG sees it (ExactBrush: exact planes, corners, bounds), rebuilt when the brush - // changes (ExactInputJob). Only kept up to date while the tree is built exactly (builtExact). + // changes (ExactInputJob). public NativeList> exactBrushCache; public NativeList decalInstances; @@ -44,9 +43,6 @@ public class Data public NativeParallelHashMap> brushRenderBufferLookup; - // Whether the tree's output was last built by the exact CSG (TreeUpdate.kExactCSG). An update by the other - // algorithm rebuilds every brush of the tree, so the two never mix in one tree's output. - public bool builtExact; // What each hotspot layout was at the last update (ChiselHotspotStore.LayoutHashes, by id - 1) public NativeList hotspotLayoutHashes; // Where the weld's last step moved the corners it moved at the last update (from, to; OutputModelWeld) @@ -79,7 +75,6 @@ internal void Initialize() brushTreeSpacePlaneCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed brushesTouchedByBrushCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed brushRenderBufferCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed - loopVerticesCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed exactBrushCache = new NativeList>(1000, Allocator.Persistent); decalInstances = new NativeList(16, Allocator.Persistent); @@ -132,9 +127,6 @@ internal void EnsureCapacity(int brushCount) if (brushRenderBufferCache.Capacity < brushCount) brushRenderBufferCache.Capacity = brushCount; - if (loopVerticesCache.Capacity < brushCount) - loopVerticesCache.Capacity = brushCount; - if (exactBrushCache.Capacity < brushCount) exactBrushCache.Capacity = brushCount; } @@ -235,18 +227,6 @@ internal void Dispose() brushRenderBufferCache.Dispose(); } brushRenderBufferCache = default; - if (loopVerticesCache.IsCreated) - { - for (int i = 0; i < loopVerticesCache.Length; i++) - { - if (loopVerticesCache[i].IsCreated) - loopVerticesCache[i].Dispose(); - loopVerticesCache[i] = default; - } - loopVerticesCache.Clear(); - loopVerticesCache.Dispose(); - } - loopVerticesCache = default; if (exactBrushCache.IsCreated) { for (int i = 0; i < exactBrushCache.Length; i++) @@ -307,7 +287,6 @@ internal void Dispose() if (brushRenderBufferLookup.IsCreated) brushRenderBufferLookup.Dispose(); brushRenderBufferLookup = default; - builtExact = false; } } diff --git a/Core/2.Processing/Containers/InternedPlanes.cs b/Core/2.Processing/Containers/InternedPlanes.cs deleted file mode 100644 index eb579ab4..00000000 --- a/Core/2.Processing/Containers/InternedPlanes.cs +++ /dev/null @@ -1,242 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Jobs; -using Unity.Mathematics; - -namespace Chisel.Core -{ - public unsafe struct InternedPlanes : INativeDisposable - { - internal const int kMinHashTableSize = 1024; - - internal const double kMaxNormalDeviation = 1e-7; - - // A matching normal differs by at most sqrt(2 * kMaxNormalDeviation) in a single component, - // so it lands in this cell or an adjacent one - which is what makes the 3^4 probe exhaustive. - internal const float kNormalCellSize = 4.4721e-4f * 2.5f; - - internal const float kOffsetCellSize = 1.0f; - - const long kHashMagicValue = (long)1099511628211ul; - - [NativeDisableUnsafePtrRestriction] UnsafeList* m_Planes; - [NativeDisableUnsafePtrRestriction] UnsafeList* m_ChainedIndices; - [NativeDisableUnsafePtrRestriction] void* m_HashTable; - - readonly int m_HashMask; - - readonly Allocator m_AllocatorLabel; - - public readonly bool IsCreated => m_Planes != null && m_ChainedIndices != null && m_HashTable != null; - public readonly int Count => m_Planes == null ? 0 : m_Planes->Length; - - public readonly float4 this[int id] => ((float4*)m_Planes->Ptr)[id]; - - public InternedPlanes(int minCapacity, Allocator allocator) - { - m_AllocatorLabel = allocator; - var tableSize = math.max(kMinHashTableSize, math.ceilpow2(math.max(1, minCapacity))); - m_HashMask = tableSize - 1; - var hashTableMemSize = tableSize * UnsafeUtility.SizeOf(); - m_HashTable = UnsafeUtility.Malloc(hashTableMemSize, UnsafeUtility.AlignOf(), allocator); - UnsafeUtility.MemClear(m_HashTable, hashTableMemSize); - m_Planes = UnsafeList.Create(math.max(1, minCapacity), allocator); - m_ChainedIndices = UnsafeList .Create(math.max(1, minCapacity), allocator); - } - - public void Dispose() - { - if (m_Planes != null) { UnsafeList.Destroy(m_Planes); m_Planes = null; } - if (m_ChainedIndices != null) { UnsafeList .Destroy(m_ChainedIndices); m_ChainedIndices = null; } - if (m_HashTable != null) { UnsafeUtility.Free(m_HashTable, m_AllocatorLabel); m_HashTable = null; } - } - - [BurstCompile] - struct DisposeJob : IJob - { - [NativeDisableUnsafePtrRestriction] public UnsafeList* planes; - [NativeDisableUnsafePtrRestriction] public UnsafeList* chainedIndices; - [NativeDisableUnsafePtrRestriction] public void* hashTable; - public Allocator allocator; - public void Execute() - { - if (planes != null) UnsafeList.Destroy(planes); - if (chainedIndices != null) UnsafeList .Destroy(chainedIndices); - if (hashTable != null) UnsafeUtility.Free(hashTable, allocator); - } - } - - // Lets the table be torn down on a worker once the jobs reading it have finished, matching - // how the rest of the pipeline's temporaries are released. - public JobHandle Dispose(JobHandle inputDeps) - { - var jobHandle = new DisposeJob - { - planes = m_Planes, - chainedIndices = m_ChainedIndices, - hashTable = m_HashTable, - allocator = m_AllocatorLabel - }.Schedule(inputDeps); - m_Planes = null; - m_ChainedIndices = null; - m_HashTable = null; - return jobHandle; - } - - public void Clear() - { - m_Planes->Clear(); - m_ChainedIndices->Clear(); - UnsafeUtility.MemClear(m_HashTable, (m_HashMask + 1) * UnsafeUtility.SizeOf()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float4 Normalize(float4 plane) - { - var length = math.length(plane.xyz); - return (length > (float)CSGConstants.kDivideMinimumEpsilon) ? (plane / length) : plane; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double Distance(float4 plane, float3 point) - { - return (double)plane.x * point.x + (double)plane.y * point.y + (double)plane.z * point.z + plane.w; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int4 CellOf(float4 plane) - { - // floor, not truncation: truncating toward zero makes a double-width cell straddling 0, - // and a plane through the origin is the common case here, not a rare one. - return new int4((int)math.floor(plane.x / kNormalCellSize), - (int)math.floor(plane.y / kNormalCellSize), - (int)math.floor(plane.z / kNormalCellSize), - (int)math.floor(plane.w / kOffsetCellSize)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly int GetHash(int4 index) - { - var hashCode = (uint)((index.w ^ ((index.y ^ ((index.x ^ index.z) * kHashMagicValue)) * kHashMagicValue)) * kHashMagicValue); - return (int)(hashCode & (uint)m_HashMask); - } - - readonly bool Matches(float4 stored, float4 query, float3* points, int pointCount, out bool flipped) - { - var dot = math.dot(stored.xyz, query.xyz); - flipped = dot < 0; - if (1.0 - math.abs((double)dot) > kMaxNormalDeviation) - return false; - for (int i = 0; i < pointCount; i++) - { - if (math.abs(Distance(stored, points[i])) > CSGConstants.kPlaneDAlignEpsilon) - return false; - } - return true; - } - - readonly bool ProbeCell(int4 cell, float4 query, float3* points, int pointCount, - out int id, out bool flipped) - { - var planes = (float4*)m_Planes->Ptr; - var chained = (int*)m_ChainedIndices->Ptr; - var chainIndex = ((int*)m_HashTable)[GetHash(cell)] - 1; - while (chainIndex != -1) - { - if (Matches(planes[chainIndex], query, points, pointCount, out flipped)) - { - id = chainIndex; - return true; - } - chainIndex = chained[chainIndex] - 1; - } - id = -1; - flipped = false; - return false; - } - - readonly bool ProbeNeighbourhood(float4 cellSource, float4 query, float3* points, int pointCount, - out int id, out bool flipped) - { - var center = CellOf(cellSource); - if (ProbeCell(center, query, points, pointCount, out id, out flipped)) - return true; - for (int dx = -1; dx <= 1; dx++) - for (int dy = -1; dy <= 1; dy++) - for (int dz = -1; dz <= 1; dz++) - for (int dw = -1; dw <= 1; dw++) - { - if ((dx | dy | dz | dw) == 0) - continue; // already done - if (ProbeCell(center + new int4(dx, dy, dz, dw), query, points, pointCount, out id, out flipped)) - return true; - } - id = -1; - flipped = false; - return false; - } - - public readonly bool TryFind(float4 plane, float3* points, int pointCount, out int id, out bool flipped) - { - plane = Normalize(plane); - var cell = CellOf(plane); - var flippedCell = CellOf(-plane); - if (ProbeCell(cell, plane, points, pointCount, out id, out flipped)) return true; - if (ProbeCell(flippedCell, plane, points, pointCount, out id, out flipped)) return true; - if (ProbeNeighbourhood(plane, plane, points, pointCount, out id, out flipped)) return true; - if (ProbeNeighbourhood(-plane, plane, points, pointCount, out id, out flipped)) return true; - return false; - } - - public readonly bool TryFind(float4 plane, float3 pointOnPlane, out int id, out bool flipped) - { - return TryFind(plane, &pointOnPlane, 1, out id, out flipped); - } - - public readonly bool TryFind(float4 plane, NativeArray pointsOnPlane, out int id, out bool flipped) - { - return TryFind(plane, (float3*)pointsOnPlane.GetUnsafeReadOnlyPtr(), pointsOnPlane.Length, out id, out flipped); - } - - // Returns the shared id for this plane, inserting it if it is new. - public int Add(float4 plane, float3* points, int pointCount, out bool flipped) - { - plane = Normalize(plane); - if (TryFind(plane, points, pointCount, out int existing, out flipped)) - return existing; - - flipped = false; - var hash = GetHash(CellOf(plane)); - var hashTable = (int*)m_HashTable; - var previousChain = hashTable[hash]; - m_Planes->Add(plane); - m_ChainedIndices->Add(previousChain); - hashTable[hash] = m_Planes->Length; // stored as id + 1, so 0 means "empty" - return m_Planes->Length - 1; - } - - public int Add(float4 plane, float3 pointOnPlane, out bool flipped) - { - return Add(plane, &pointOnPlane, 1, out flipped); - } - - public int Add(float4 plane, NativeArray pointsOnPlane, out bool flipped) - { - return Add(plane, (float3*)pointsOnPlane.GetUnsafeReadOnlyPtr(), pointsOnPlane.Length, out flipped); - } - - public int Add(float4 plane, float3 pointOnPlane) - { - return Add(plane, &pointOnPlane, 1, out _); - } - - public NativeArray AsArray() - { - return NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray( - m_Planes->Ptr, m_Planes->Length, Allocator.None); - } - } -} diff --git a/Core/2.Processing/Containers/InternedPlanes.cs.meta b/Core/2.Processing/Containers/InternedPlanes.cs.meta deleted file mode 100644 index c7310008..00000000 --- a/Core/2.Processing/Containers/InternedPlanes.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: c8393009ff3e5494eb51d132e37b149f \ No newline at end of file diff --git a/Core/2.Processing/Exact/ExactFace.cs b/Core/2.Processing/Exact/ExactFace.cs index 43536a27..95aaeba5 100644 --- a/Core/2.Processing/Exact/ExactFace.cs +++ b/Core/2.Processing/Exact/ExactFace.cs @@ -723,7 +723,7 @@ int Route(ref ExactList inside, ref ExactList routingRows, ref Exa int row = lookupStart[k] + input; if (row < lookupStart[k] || row >= lookupEnd[k]) { - // the same as PerformCSGJob: a lookup without a row for this input leaves the input as it is + // a lookup without a row for this input leaves the input as it is routingErrors++; continue; } diff --git a/Core/2.Processing/Jobs/CacheRemappingJob.cs b/Core/2.Processing/Jobs/CacheRemappingJob.cs index 2f424a96..b9c3f216 100644 --- a/Core/2.Processing/Jobs/CacheRemappingJob.cs +++ b/Core/2.Processing/Jobs/CacheRemappingJob.cs @@ -47,7 +47,6 @@ public int Compare(int2 a, int2 b) [NoAlias] public NativeList> brushTreeSpacePlaneCache; [NoAlias] public NativeList brushTreeSpaceBoundCache; [NoAlias] public NativeList> brushesTouchedByBrushCache; - [NoAlias] public NativeList> loopVerticesCache; [NoAlias] public NativeList> exactBrushCache; // Write @@ -188,8 +187,6 @@ public void Execute() brushTreeSpacePlaneCache.Resize(maxCount, NativeArrayOptions.ClearMemory); if (brushesTouchedByBrushCache.Length < maxCount) brushesTouchedByBrushCache.Resize(maxCount, NativeArrayOptions.ClearMemory); - if (loopVerticesCache.Length < maxCount) - loopVerticesCache.Resize(maxCount, NativeArrayOptions.ClearMemory); if (exactBrushCache.Length < maxCount) exactBrushCache.Resize(maxCount, NativeArrayOptions.ClearMemory); @@ -213,7 +210,6 @@ public void Execute() { var tmp = brushTreeSpaceBoundCache[source]; brushTreeSpaceBoundCache[source] = default; } { var tmp = brushTreeSpacePlaneCache[source]; brushTreeSpacePlaneCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } { var tmp = brushesTouchedByBrushCache[source]; brushesTouchedByBrushCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } - { var tmp = loopVerticesCache[source]; loopVerticesCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } { var tmp = exactBrushCache[source]; exactBrushCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } continue; } @@ -226,7 +222,6 @@ public void Execute() (brushTreeSpaceBoundCache[source], brushTreeSpaceBoundCache[destination]) = (brushTreeSpaceBoundCache[destination], brushTreeSpaceBoundCache[source]); (brushTreeSpacePlaneCache[source], brushTreeSpacePlaneCache[destination]) = (brushTreeSpacePlaneCache[destination], brushTreeSpacePlaneCache[source]); (brushesTouchedByBrushCache[source], brushesTouchedByBrushCache[destination]) = (brushesTouchedByBrushCache[destination], brushesTouchedByBrushCache[source]); - (loopVerticesCache[source], loopVerticesCache[destination]) = (loopVerticesCache[destination], loopVerticesCache[source]); (exactBrushCache[source], exactBrushCache[destination]) = (exactBrushCache[destination], exactBrushCache[source]); } } diff --git a/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs b/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs deleted file mode 100644 index a1f05a67..00000000 --- a/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Collections.LowLevel.Unsafe; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct CountIntersectionLoopsJob : IJob - { - // Read - [NoAlias, ReadOnly] public NativeList uniqueBrushPairs; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; - - // Write - [NoAlias, WriteOnly] public NativeReference intersectionLoopCountRef; - - readonly int FaceCountOf(int nodeOrder) - { - if (nodeOrder < 0 || nodeOrder >= brushMeshLookup.Length) - return 0; - var brushMesh = brushMeshLookup[nodeOrder]; - // A brush without a mesh has no faces, so it makes no loops. - if (!brushMesh.IsCreated) - return 0; - return brushMesh.Value.localPlaneCount; - } - - public void Execute() - { - var total = 0; - for (int i = 0; i < uniqueBrushPairs.Length; i++) - { - var pair = uniqueBrushPairs[i]; - if (pair.type == IntersectionType.InvalidValue) - continue; - total += FaceCountOf(pair.brushIndexOrder0.nodeOrder) + - FaceCountOf(pair.brushIndexOrder1.nodeOrder); - } - intersectionLoopCountRef.Value = total; - } - } -} diff --git a/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs.meta b/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs.meta deleted file mode 100644 index 1179d5fd..00000000 --- a/Core/2.Processing/Jobs/CountIntersectionLoopsJob.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 5473274e6fbcfa5dd8e040da6ef7473b \ No newline at end of file diff --git a/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs b/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs deleted file mode 100644 index 75b61abf..00000000 --- a/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs +++ /dev/null @@ -1,1172 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using Unity.Collections.LowLevel.Unsafe; -using System.Runtime.CompilerServices; -using System.Collections.Generic; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct CreateIntersectionLoopsJob : IJobParallelForDefer - { - const float kFatPlaneWidthEpsilon = CSGConstants.kFatPlaneWidthEpsilon; - - // Needed for count (forced & unused) - [NoAlias, ReadOnly] public NativeList uniqueBrushPairs; - - // Read - // Gate the vertex weld on plane incidence (WeldIncidenceFilter); set from CSGManager's kUseIncidenceWeld. - [NoAlias, ReadOnly] public bool useIncidenceWeld; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; - [NoAlias, ReadOnly] public NativeList> treeSpaceVerticesCache; - [NoAlias, ReadOnly] public NativeStream.Reader intersectingBrushesStream; - - // Canonical vertices (see CanonicalVertices) - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - - // Write - [NoAlias, WriteOnly] public NativeListExtensions.ParallelWriterExt outputSurfaceVertices; - [NoAlias, WriteOnly] public NativeList.ParallelWriter outputSurfaces; - - // Per thread scratch memory - //[NativeDisableContainerSafetyRestriction] NativeArray localVertices; - //[NativeDisableContainerSafetyRestriction] NativeArray usedVertexIndices; - //[NativeDisableContainerSafetyRestriction] NativeArray planeIndexOffsets; - //[NativeDisableContainerSafetyRestriction] NativeList uniqueIndices; - //[NativeDisableContainerSafetyRestriction] NativeArray sortedStack; - //[NativeDisableContainerSafetyRestriction] NativeArray foundIndices0; - //[NativeDisableContainerSafetyRestriction] NativeArray foundIndices1; - //[NativeDisableContainerSafetyRestriction] NativeArray foundVertices; - //[NativeDisableContainerSafetyRestriction] NativeArray foundEdges; - //[NativeDisableContainerSafetyRestriction] NativeArray foundIntersections; - //[NativeDisableContainerSafetyRestriction] NativeArray temporaryVertices; - //[NativeDisableContainerSafetyRestriction] HashedVertices hashedTreeSpaceVertices; - //[NativeDisableContainerSafetyRestriction] HashedVertices snapHashedVertices; - - internal struct PlaneVertexIndexPair - { - public ushort planeIndex; - public ushort vertexIndex; - } - - struct PlaneIndexOffsetLength - { - public ushort length; - public ushort offset; - public ushort planeIndex; - } - - static bool IsOutsidePlanes([NoAlias, ReadOnly] NativeArray planes, int planesLength, float4 localVertex) - { - int n = 0; - for (; n + 4 < planesLength; n+=4) - { - var distance = new float4(math.dot(planes[n+0], localVertex), - math.dot(planes[n+1], localVertex), - math.dot(planes[n+2], localVertex), - math.dot(planes[n+3], localVertex)); - - // will be 'false' when distance is NaN or Infinity - if (!math.all(distance <= kFatPlaneWidthEpsilon)) - return true; - } - for (; n < planesLength; n ++) - { - var distance = math.dot(planes[n], localVertex); - - // will be 'false' when distance is NaN or Infinity - if (!(distance <= kFatPlaneWidthEpsilon)) - return true; - } - return false; - } - - - #region Sort - static float3 FindPolygonCentroid([NoAlias, ReadOnly] HashedVertices vertices, [NoAlias, ReadOnly] NativeArray indices, int offset, int indicesCount) - { - var centroid = float3.zero; - for (int i = 0; i < indicesCount; i++, offset++) - centroid += vertices[indices[offset]]; - return centroid / indicesCount; - } - - // TODO: sort by using plane information instead of unreliable floating point math .. - static void SortIndices([NoAlias, ReadOnly] HashedVertices vertices, [NoAlias, ReadOnly] NativeArray sortedStack, [NoAlias, ReadOnly] NativeArray indices, int offset, int indicesCount, float3 normal) - { - // There's no point in trying to sort a point or a line - if (indicesCount < 3) - return; - - float3 tangentX, tangentY; - if (normal.x > normal.y) - { - if (normal.x > normal.z) - { - tangentX = math.cross(normal, new float3(0, 1, 0)); - tangentY = math.cross(normal, tangentX); - } else - { - tangentX = math.cross(normal, new float3(0, 0, 1)); - tangentY = math.cross(normal, tangentX); - } - } else - { - if (normal.y > normal.z) - { - tangentX = math.cross(normal, new float3(1, 0, 0)); - tangentY = math.cross(normal, tangentX); - } else - { - tangentX = math.cross(normal, new float3(0, 1, 0)); - tangentY = math.cross(normal, tangentX); - } - } - - var centroid = FindPolygonCentroid(vertices, indices, offset, indicesCount); - var center = new float2(math.dot(tangentX, centroid), // distance in direction of tangentX - math.dot(tangentY, centroid)); // distance in direction of tangentY - - - var sortedStackLength = 1; - sortedStack[0] = new int2(0, indicesCount - 1); - while (sortedStackLength > 0) - { - var top = sortedStack[sortedStackLength - 1]; - sortedStackLength--; - var l = top.x; - var r = top.y; - var left = l; - var right = r; - var va = vertices[indices[offset + (left + right) / 2]]; - while (true) - { - var a_angle = math.atan2(math.dot(tangentX, va) - center.x, math.dot(tangentY, va) - center.y); - - { - var vb = vertices[indices[offset + left]]; - var b_angle = math.atan2(math.dot(tangentX, vb) - center.x, math.dot(tangentY, vb) - center.y); - while (b_angle > a_angle) - { - left++; - vb = vertices[indices[offset + left]]; - b_angle = math.atan2(math.dot(tangentX, vb) - center.x, math.dot(tangentY, vb) - center.y); - } - } - - { - var vb = vertices[indices[offset + right]]; - var b_angle = math.atan2(math.dot(tangentX, vb) - center.x, math.dot(tangentY, vb) - center.y); - while (a_angle > b_angle) - { - right--; - vb = vertices[indices[offset + right]]; - b_angle = math.atan2(math.dot(tangentX, vb) - center.x, math.dot(tangentY, vb) - center.y); - } - } - - if (left <= right) - { - if (left != right) - { - var t = indices[offset + left]; - indices[offset + left] = indices[offset + right]; - indices[offset + right] = t; - } - - left++; - right--; - } - if (left > right) - break; - } - if (l < right) - { - sortedStack[sortedStackLength] = new int2(l, right); - sortedStackLength++; - } - if (left < r) - { - sortedStack[sortedStackLength] = new int2(left, r); - sortedStackLength++; - } - } - } - #endregion - - //[MethodImpl(MethodImplOptions.NoInlining)] - void FindInsideVertices([NoAlias, ReadOnly] NativeArray usedVertices0, - int usedVertices0Length, - [NoAlias, ReadOnly] NativeArray vertexIntersectionPlanes, - int vertexIntersectionPlanesLength, - [NoAlias, ReadOnly] NativeArray vertexIntersectionSegments, - int vertexIntersectionSegmentsLength, - [NoAlias, ReadOnly] NativeArray intersectingPlanes1, - int intersectingPlanes1Length, - int intersectingPlanesAndEdges1Length, - float4x4 nodeToTreeSpaceMatrix1, - float4x4 vertexToLocal0, - [NoAlias] ref HashedVertices hashedTreeSpaceVertices, - [NoAlias] ref HashedVertices snapHashedVertices, - [NoAlias] NativeArray foundIndices0, - ref int foundIndices0Length, - in WeldIncidenceFilter weldFilter = default, - in CanonicalVertices canonical = default, - int canonicalNodeOrder = -1) - { - NativeArray localVertices; - using var _localVertices = localVertices = new NativeArray(usedVertices0Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref localVertices, usedVertices0Length); - - NativeArray usedVertexIndices; - using var _usedVertexIndices = usedVertexIndices = new NativeArray(usedVertices0Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref usedVertexIndices, usedVertices0Length); - - for (int j = 0; j < usedVertices0Length; j++) - { - var brushVertex1 = new float4(usedVertices0[j], 1); - localVertices[j] = math.mul(vertexToLocal0, brushVertex1); - usedVertexIndices[j] = (ushort)j; - } - - var foundVertexCount = usedVertices0Length; - for (int j = foundVertexCount - 1; j >= 0; j--) - { - if (IsOutsidePlanes(intersectingPlanes1, intersectingPlanesAndEdges1Length, localVertices[j])) - { - if (j < foundVertexCount - 1) - { - localVertices[j] = localVertices[foundVertexCount - 1]; - usedVertexIndices[j] = usedVertexIndices[foundVertexCount - 1]; - } - foundVertexCount--; - } - } - - for (int j = 0; j < foundVertexCount; j++) - { - var usedVertexIndex = usedVertexIndices[j]; - var segment = vertexIntersectionSegments[usedVertexIndex]; - if (segment.y == 0) - continue; - - var treeSpaceVertex = math.mul(nodeToTreeSpaceMatrix1, localVertices[j]).xyz; - treeSpaceVertex = canonical.Canonicalize(treeSpaceVertex, canonicalNodeOrder, CanonicalVertexSite.InsideVertex); - treeSpaceVertex = snapHashedVertices[snapHashedVertices.AddNoResize(treeSpaceVertex, in weldFilter)]; - var treeSpaceVertexIndex = hashedTreeSpaceVertices.AddNoResize(treeSpaceVertex, in weldFilter); - for (int i = segment.x; i < segment.x + segment.y; i++) - { - var planeIndex = vertexIntersectionPlanes[i]; - - // TODO: optimize - for (int k = 0; k < foundIndices0Length; k++) - { - if (foundIndices0[k].planeIndex == planeIndex && - foundIndices0[k].vertexIndex == treeSpaceVertexIndex) - { - goto skipMe; - } - } - - foundIndices0[foundIndices0Length] = new PlaneVertexIndexPair { planeIndex = (ushort)planeIndex, vertexIndex = (ushort)treeSpaceVertexIndex }; - foundIndices0Length++; - skipMe: - ; - } - } - } - - struct IntersectionPlanes - { - //public float4 plane0; - //public float4 plane1; - public float4 plane2; - public int planeIndex0; - public int planeIndex1; - public int planeIndex2; - } - - struct IntersectionEdge - { - public float4 edgeVertex0; - public float4 edgeVertex1; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void FindIntersectionVertices(ref NativeArray intersectingPlanes0, - int intersectingPlanes0Length, - int intersectingPlanesAndEdges0Length, - ref NativeArray intersectingPlanes1, - int intersectingPlanes1Length, - int intersectingPlanesAndEdges1Length, - ref NativeArray usedPlanePairs1, - int usedPlanePairs1Length, - ref NativeArray intersectingPlaneIndices0, - int intersectingPlaneIndices0Length, - float4x4 nodeToTreeSpaceMatrix0, - ref HashedVertices hashedTreeSpaceVertices, - ref HashedVertices snapHashedVertices, - NativeArray foundIndices0, - ref int foundIndices0Length, - NativeArray foundIndices1, - ref int foundIndices1Length, - in WeldIncidenceFilter weldFilter = default, - in CanonicalVertices canonical = default, - int canonicalNodeOrder = -1) - { - int foundVerticesCount = usedPlanePairs1Length * intersectingPlanes0Length; - NativeArray foundVertices; - using var _foundVertices = foundVertices = new NativeArray (foundVerticesCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref foundVertices, foundVerticesCount); - - NativeArray foundEdges; - using var _foundEdges = foundEdges = new NativeArray (foundVerticesCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref foundEdges, foundVerticesCount); - - NativeArray foundIntersections; - using var _foundIntersections = foundIntersections = new NativeArray (foundVerticesCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref foundIntersections, foundVerticesCount); - - var n = 0; - for (int i = 0; i < usedPlanePairs1Length; i++) - { - for (int j = 0; j < intersectingPlanes0Length; j++) - { - var plane0 = usedPlanePairs1[i].plane0; - var plane1 = usedPlanePairs1[i].plane1; - var plane2 = intersectingPlanes0[j]; - - foundIntersections[n] = new IntersectionPlanes - { - //plane0 = plane0, - //plane1 = plane1, - plane2 = plane2, - planeIndex0 = usedPlanePairs1[i].planeIndex0, - planeIndex1 = usedPlanePairs1[i].planeIndex1, - planeIndex2 = intersectingPlaneIndices0[j] - }; - - foundEdges[n] = new IntersectionEdge - { - edgeVertex0 = usedPlanePairs1[i].edgeVertex0, - edgeVertex1 = usedPlanePairs1[i].edgeVertex1 - }; - - if (math.abs(math.dot(plane2.xyz, plane0.xyz)) >= CSGConstants.kNormalDotAlignEpsilon || - math.abs(math.dot(plane2.xyz, plane1.xyz)) >= CSGConstants.kNormalDotAlignEpsilon || - math.abs(math.dot(plane0.xyz, plane1.xyz)) >= CSGConstants.kNormalDotAlignEpsilon) - continue; - - var localVertex = PlaneExtensions.Intersection(plane2, plane0, plane1); - if (double.IsNaN(localVertex.x)) - continue; - - foundVertices[n] = new float4((float3)localVertex,1); - n++; - } - } - - for (int k = n - 1; k >= 0; k--) - { - var edgeVertex0 = foundEdges[k].edgeVertex0; - var edgeVertex1 = foundEdges[k].edgeVertex1; - var plane2 = foundIntersections[k].plane2; - - if (math.abs(math.dot(plane2, edgeVertex0)) <= kFatPlaneWidthEpsilon && - math.abs(math.dot(plane2, edgeVertex1)) <= kFatPlaneWidthEpsilon) - { - if (k < n - 1) - { - foundIntersections[k] = foundIntersections[n - 1]; - foundVertices[k] = foundVertices[n - 1]; - } - n--; - } - } - - // TODO: since we're using a pair in the outer loop, we could also determine which - // 2 planes it intersects at both ends and just check those two planes .. - - // NOTE: for brush2, the intersection will always be only on two planes - // UNLESS it's a corner vertex along that edge (we can compare to the two vertices) - // in which case we could use a pre-calculated list of planes .. - // OR when the intersection is outside of the edge .. - - for (int k = n - 1; k >= 0; k--) - { - if (IsOutsidePlanes(intersectingPlanes0, intersectingPlanesAndEdges0Length, foundVertices[k]) || - IsOutsidePlanes(intersectingPlanes1, intersectingPlanesAndEdges1Length, foundVertices[k])) - { - if (k < n - 1) - { - foundIntersections[k] = foundIntersections[n - 1]; - foundVertices[k] = foundVertices[n - 1]; - } - n--; - } - } - - for (int k = 0; k < n; k++) - { - var planeIndex0 = (ushort)foundIntersections[k].planeIndex0; - var planeIndex1 = (ushort)foundIntersections[k].planeIndex1; - var planeIndex2 = (ushort)foundIntersections[k].planeIndex2; - - var localVertex = foundVertices[k]; - - // TODO: should be having a Loop for each plane that intersects this vertex, and add that vertex - // to ensure they are identical - var treeSpaceVertex = math.mul(nodeToTreeSpaceMatrix0, localVertex).xyz; - treeSpaceVertex = canonical.Canonicalize(treeSpaceVertex, canonicalNodeOrder, CanonicalVertexSite.PairIntersection); - treeSpaceVertex = snapHashedVertices[snapHashedVertices.AddNoResize(treeSpaceVertex, in weldFilter)]; - var treeSpaceVertexIndex = hashedTreeSpaceVertices.AddNoResize(treeSpaceVertex, in weldFilter); - - { - // TODO: optimize - for (int f = 0; f < foundIndices0Length; f++) - { - if (foundIndices0[f].vertexIndex == treeSpaceVertexIndex && - foundIndices0[f].planeIndex == planeIndex2) - { - goto skip0; - } - } - - foundIndices0[foundIndices0Length] = new PlaneVertexIndexPair { planeIndex = planeIndex2, vertexIndex = treeSpaceVertexIndex }; - foundIndices0Length++; - skip0: ; - } - - { - // TODO: optimize - for (int f = 0; f < foundIndices1Length; f++) - { - if (foundIndices1[f].vertexIndex == treeSpaceVertexIndex && - foundIndices1[f].planeIndex == planeIndex0) - { - goto skip1; - } - } - - foundIndices1[foundIndices1Length] = new PlaneVertexIndexPair { planeIndex = planeIndex0, vertexIndex = treeSpaceVertexIndex }; - foundIndices1Length++; - skip1: ; - } - - { - // TODO: optimize - for (int f = 0; f < foundIndices1Length; f++) - { - if (foundIndices1[f].vertexIndex == treeSpaceVertexIndex && - foundIndices1[f].planeIndex == planeIndex1) - { - goto skip2; - } - } - - foundIndices1[foundIndices1Length] = new PlaneVertexIndexPair { planeIndex = planeIndex1, vertexIndex = treeSpaceVertexIndex }; - foundIndices1Length++; - skip2: ; - } - } - } - /* - struct Comparer : IComparer - { - public int Compare(PlaneVertexIndexPair x, PlaneVertexIndexPair y) - { - if (x.planeIndex > y.planeIndex) - continue; - if (x.planeIndex == y.planeIndex) - { - if (x.vertexIndex <= y.vertexIndex) - continue; - } - - var t = x; - foundIndices0[i] = foundIndices0[j]; - foundIndices0[j] = t; - } - } - readonly static readonly Comparer kComparer = new Comparer(); - */ - internal void AddFullSurfaceLoopsForAlignedFaces( - [NoAlias, ReadOnly] ref BlobArray treeSpaceVertices0, - [NoAlias, ReadOnly] ref BrushTreeSpacePlanes brushTreeSpacePlanes0, - [NoAlias, ReadOnly] ref BrushTreeSpacePlanes brushTreeSpacePlanes1, - [NoAlias, ReadOnly] NativeArray surfaceInfos0, int surfaceInfosLength0, - [NoAlias, ReadOnly] NativeArray intersectingPlaneIndices0, int intersectingPlanesLength0, - int facePlaneCount1, - [NoAlias] NativeArray foundIndices0, ref int foundIndices0Length, - [NoAlias] ref HashedVertices hashedTreeSpaceVertices, - [NoAlias] ref HashedVertices snapHashedVertices, - in WeldIncidenceFilter weldFilter = default, - in CanonicalVertices canonical = default, - int canonicalNodeOrder = -1) - { - ref var planes0 = ref brushTreeSpacePlanes0.treeSpacePlanes; - ref var planes1 = ref brushTreeSpacePlanes1.treeSpacePlanes; - facePlaneCount1 = math.min(facePlaneCount1, planes1.Length); - if (facePlaneCount1 < 4) // fewer than a tetrahedron's faces cannot bound a volume - return; - - for (int i = 0; i < intersectingPlanesLength0; i++) - { - var planeIndex = intersectingPlaneIndices0[i]; - if (planeIndex < 0 || planeIndex >= surfaceInfosLength0 || planeIndex >= planes0.Length) - continue; - - var category = (CategoryIndex)surfaceInfos0[planeIndex].interiorCategory; - - if (category != CategoryIndex.Aligned && - category != CategoryIndex.ReverseAligned) - continue; - - // leave any face that already got a real crossing loop completely alone - int alreadyFound = 0; - for (int f = 0; f < foundIndices0Length; f++) - { - if (foundIndices0[f].planeIndex == planeIndex) - alreadyFound++; - } - if (alreadyFound >= 3) - continue; - - var facePlane = planes0[planeIndex]; - - float4 counterpartProbe = default; - bool haveCounterpartProbe = false; - for (int v = 0; v < treeSpaceVertices0.Length; v++) - { - var candidate = new float4(treeSpaceVertices0[v], 1); - if (math.abs(math.dot(facePlane, candidate)) > kFatPlaneWidthEpsilon) - continue; - counterpartProbe = candidate; - haveCounterpartProbe = true; - break; - } - if (!haveCounterpartProbe) - continue; - - int coplanarPlane1 = -1; - float coplanarDistance = float.MaxValue; - for (int q = 0; q < facePlaneCount1; q++) - { - if (math.abs(math.dot(facePlane.xyz, planes1[q].xyz)) < CSGConstants.kNormalDotAlignEpsilon) - continue; - var distance = math.abs(math.dot(planes1[q], counterpartProbe)); - if (distance < coplanarDistance) - { - coplanarDistance = distance; - coplanarPlane1 = q; - } - } - - // every vertex of this face must lie inside the other brush - int onFace = 0; - bool covered = true; - for (int v = 0; v < treeSpaceVertices0.Length && covered; v++) - { - var vertex = new float4(treeSpaceVertices0[v], 1); - if (math.abs(math.dot(facePlane, vertex)) > kFatPlaneWidthEpsilon) - continue; - onFace++; - for (int q = 0; q < facePlaneCount1; q++) - { - if (q == coplanarPlane1) - continue; - if (math.dot(planes1[q], vertex) > kFatPlaneWidthEpsilon) - { covered = false; break; } - } - } - if (!covered || onFace < 3) - continue; - - // the face's own vertices become the loop; GenerateLoop sorts them around the normal - for (int v = 0; v < treeSpaceVertices0.Length; v++) - { - if (foundIndices0Length >= foundIndices0.Length) - return; - var treeSpaceVertex = treeSpaceVertices0[v]; - if (math.abs(math.dot(facePlane, new float4(treeSpaceVertex, 1))) > kFatPlaneWidthEpsilon) - continue; - treeSpaceVertex = canonical.Canonicalize(treeSpaceVertex, canonicalNodeOrder, CanonicalVertexSite.AlignedFace); - treeSpaceVertex = snapHashedVertices[snapHashedVertices.AddNoResize(treeSpaceVertex, in weldFilter)]; - var vertexIndex = hashedTreeSpaceVertices.AddNoResize(treeSpaceVertex, in weldFilter); - bool duplicate = false; - for (int f = 0; f < foundIndices0Length; f++) - { - if (foundIndices0[f].vertexIndex == vertexIndex && - foundIndices0[f].planeIndex == planeIndex) - { duplicate = true; break; } - } - if (duplicate) - continue; - foundIndices0[foundIndices0Length] = new PlaneVertexIndexPair - { planeIndex = (ushort)planeIndex, vertexIndex = vertexIndex }; - foundIndices0Length++; - } - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void GenerateLoop(IndexOrder brushIndexOrder0, - IndexOrder brushIndexOrder1, - bool invertedTransform, - [NoAlias, ReadOnly] NativeArray surfaceInfos, - [NoAlias] int surfaceInfosLength, - [NoAlias, ReadOnly] ref BrushTreeSpacePlanes brushTreeSpacePlanes0, - [NoAlias, ReadOnly] NativeArray foundIndices0, - [NoAlias] ref int foundIndices0Length, - [NoAlias] ref HashedVertices hashedTreeSpaceVertices, - [NoAlias] NativeList.ParallelWriter outputSurfaces) - { - // Why is the unity NativeSort slower than bubble sort? - // TODO: revisit this assumption - //* - for (int i = 0; i < foundIndices0Length - 1; i++) - { - for (int j = i + 1; j < foundIndices0Length; j++) - { - var x = foundIndices0[i]; - var y = foundIndices0[j]; - if (x.planeIndex > y.planeIndex) - continue; - if (x.planeIndex == y.planeIndex) - { - if (x.vertexIndex <= y.vertexIndex) - continue; - } - - var t = x; - foundIndices0[i] = foundIndices0[j]; - foundIndices0[j] = t; - } - } - /*/ - foundIndices0.Sort(kComparer); - //*/ - - NativeArray planeIndexOffsets; - using var _planeIndexOffsets = planeIndexOffsets = new NativeArray(foundIndices0Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref planeIndexOffsets, foundIndices0Length); - - NativeList uniqueIndices; - using var _uniqueIndices = uniqueIndices = new NativeList(foundIndices0Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref uniqueIndices, foundIndices0Length); - - var planeIndexOffsetsLength = 0; - //var planeIndexOffsets = stackalloc PlaneIndexOffsetLength[foundIndices0Length]; - //var uniqueIndices = stackalloc ushort[foundIndices0Length]; - - // Now that our indices are sorted by planeIndex, we can segment them by start/end offset - var previousPlaneIndex = foundIndices0[0].planeIndex; - var previousVertexIndex = foundIndices0[0].vertexIndex; - uniqueIndices.Add(previousVertexIndex); - var loopStart = 0; - for (int i = 1; i < foundIndices0Length; i++) - { - var indices = foundIndices0[i]; - - var planeIndex = indices.planeIndex; - var vertexIndex = indices.vertexIndex; - - // TODO: why do we have soooo many duplicates sometimes? - if (planeIndex == previousPlaneIndex && - vertexIndex == previousVertexIndex) - continue; - - if (planeIndex != previousPlaneIndex) - { - var currLength = RemoveDuplicateEdges(ref uniqueIndices, loopStart, uniqueIndices.Length); - //var currLength = (uniqueIndices.Length - loopStart); - if (currLength > 2) - { - planeIndexOffsets[planeIndexOffsetsLength] = new PlaneIndexOffsetLength - { - length = (ushort)currLength, - offset = (ushort)loopStart, - planeIndex = previousPlaneIndex - }; - planeIndexOffsetsLength++; - } - loopStart = uniqueIndices.Length; - } - - uniqueIndices.Add(vertexIndex); - previousVertexIndex = vertexIndex; - previousPlaneIndex = planeIndex; - } - { - var currLength = RemoveDuplicateEdges(ref uniqueIndices, loopStart, uniqueIndices.Length); - //var currLength = (uniqueIndices.Length - loopStart); - if (currLength > 2) - { - planeIndexOffsets[planeIndexOffsetsLength] = new PlaneIndexOffsetLength - { - length = (ushort)currLength, - offset = (ushort)loopStart, - planeIndex = previousPlaneIndex - }; - planeIndexOffsetsLength++; - } - } - - var maxLength = 0; - for (int i = 0; i < planeIndexOffsetsLength; i++) - maxLength = math.max(maxLength, planeIndexOffsets[i].length); - - var uniqueIndicesArray = uniqueIndices.AsArray(); - - using var sortedStack = new NativeArray(maxLength * 2, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref sortedStack, maxLength * 2); - - // For each segment, we now sort our vertices within each segment, - // making the assumption that they are convex - //var sortedStack = stackalloc int2[maxLength * 2]; - ref var vertices = ref hashedTreeSpaceVertices;//.GetUnsafeReadOnlyPtr(); - for (int n = planeIndexOffsetsLength - 1; n >= 0; n--) - { - var planeIndexOffset = planeIndexOffsets[n]; - var length = planeIndexOffset.length; - var offset = planeIndexOffset.offset; - var planeIndex = planeIndexOffset.planeIndex; - - float3 normal = brushTreeSpacePlanes0.treeSpacePlanes[planeIndex].xyz * (invertedTransform ? 1 : -1); - - // TODO: use plane information instead - SortIndices(vertices, sortedStack, uniqueIndicesArray, offset, length, normal); - } - - - var totalLoopsSize = 16 + (planeIndexOffsetsLength * UnsafeUtility.SizeOf()); - var totalSize = totalLoopsSize; - for (int j = 0; j < planeIndexOffsetsLength; j++) - { - var planeIndexLength = planeIndexOffsets[j]; - var loopLength = planeIndexLength.length; - totalSize += (loopLength * UnsafeUtility.SizeOf()); - } - - ref var srcVertices = ref hashedTreeSpaceVertices; - for (int j = 0; j < planeIndexOffsetsLength; j++) - { - var planeIndexLength = planeIndexOffsets[j]; - var offset = planeIndexLength.offset; - var loopLength = planeIndexLength.length; - var basePlaneIndex = planeIndexLength.planeIndex; - var surfaceInfo = surfaceInfos[basePlaneIndex]; - - NativeArray temporaryVertices; - using var _temporaryVertices = temporaryVertices = new NativeArray(loopLength, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref temporaryVertices, loopLength); - - for (int d = 0; d < loopLength; d++) - temporaryVertices[d] = srcVertices[uniqueIndicesArray[offset + d]]; - - var loopVertexIndex = outputSurfaceVertices.AddRangeNoResize(temporaryVertices, loopLength); - - outputSurfaces.AddNoResize(new BrushIntersectionLoop - { - indexOrder0 = brushIndexOrder0, - indexOrder1 = brushIndexOrder1, - surfaceInfo = surfaceInfo, - loopVertexIndex = loopVertexIndex, - loopVertexCount = loopLength - }); - } - } - - // Handles situation where a vertex leads back to itself, could potentially happen when planes graze edges - private readonly int RemoveDuplicateEdges(ref NativeList uniqueIndices, int start, int end) - { - int length = end - start; - if (length <= 2) - return 0; - - // UNTESTED - for (int indexA = end - 1, indexB = start, indexC = start + 1; indexA >= start; ) - { - var vertexIndexA = uniqueIndices[indexA]; - //var vertexIndexB = uniqueIndices[indexB]; - var vertexIndexC = uniqueIndices[indexC]; - - // B - // * - // | - // A * C - - if (vertexIndexA != vertexIndexC) - { - indexC = indexB; - indexB = indexA; - indexA --; - continue; - } - - // TODO: test this - Debug.Log("Untested condition occured, please share scene"); - length -=2; - if (length <= 2) - return 0; - - if (indexC > end - 2) - { - // 0 1 2 3 5 6 1 - // B C A - uniqueIndices.MemMove(start, indexC + 1, end - (indexC + 1)); - indexA -= 2; - end -= 2; - } else - if (indexC < end - 2) - { - // 0 1 2 3 4 5 6 - // A B C - uniqueIndices.MemMove(indexA, indexC + 1, end - (indexC + 1)); - end -= 2; - } else - if (indexC == end - 2) - { - // 0 1 2 3 4 5 6 - // A B C - end -= 2; - } - - indexB = indexA + 1; if (indexB >= end) indexB = start; - indexC = indexB + 1; if (indexC >= end) indexC = start; - } - return length; - } - - // Note: Temporary BlobAssetReference that only exists during a single frame - public struct BrushPairIntersection - { - public IntersectionType type; - // Note: that the localSpacePlanes0/localSpacePlaneIndices0 parameters for both brush0 and brush1 are in localspace of >brush0< - public BrushIntersectionInfo brush0; - public BrushIntersectionInfo brush1; - } - - public struct BrushIntersectionInfo - { - public IndexOrder brushIndexOrder; - - [NativeDisableContainerSafetyRestriction] - public NativeArray usedVertices; - public int usedVerticesLength; - - [NativeDisableContainerSafetyRestriction] - public NativeArray usedPlanePairs; - public int usedPlanePairsLength; - - [NativeDisableContainerSafetyRestriction] - public NativeArray localSpacePlanes0; // planes in local space of >brush0< - public int localSpacePlanes0Length; - public int localSpacePlanesAndEdges0Length; - - [NativeDisableContainerSafetyRestriction] - public NativeArray localSpacePlaneIndices0; // planes indices of >brush0< - public int localSpacePlaneIndices0Length; - - public float4x4 nodeToTreeSpace; - public float4x4 toOtherBrushSpace; - - [NativeDisableContainerSafetyRestriction] - public NativeArray vertexIntersectionPlanes; - public int vertexIntersectionPlanesLength; - - [NativeDisableContainerSafetyRestriction] - public NativeArray vertexIntersectionSegments; - public int vertexIntersectionSegmentsLength; - - [NativeDisableContainerSafetyRestriction] - public NativeArray surfaceInfos; - public int surfaceInfosLength; - } - - [NativeDisableContainerSafetyRestriction] - BrushPairIntersection intersection; - - public void Execute(int index) - { - intersectingBrushesStream.BeginForEachIndex(index); - - intersectingBrushesStream.Read(out this.intersection.type); - if (this.intersection.type == IntersectionType.InvalidValue) - return; - - intersectingBrushesStream.Read(out this.intersection.brush0.brushIndexOrder); - intersectingBrushesStream.Read(out this.intersection.brush0.nodeToTreeSpace); - intersectingBrushesStream.Read(out this.intersection.brush0.toOtherBrushSpace); - - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.usedVertices, out this.intersection.brush0.usedVerticesLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.usedPlanePairs, out this.intersection.brush0.usedPlanePairsLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.localSpacePlanes0, out this.intersection.brush0.localSpacePlanesAndEdges0Length); - - intersectingBrushesStream.Read(out this.intersection.brush0.localSpacePlanes0Length); - - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.localSpacePlaneIndices0, out this.intersection.brush0.localSpacePlaneIndices0Length); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.vertexIntersectionPlanes, out this.intersection.brush0.vertexIntersectionPlanesLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.vertexIntersectionSegments, out this.intersection.brush0.vertexIntersectionSegmentsLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush0.surfaceInfos, out this.intersection.brush0.surfaceInfosLength); - - intersectingBrushesStream.Read(out this.intersection.brush1.brushIndexOrder); - intersectingBrushesStream.Read(out this.intersection.brush1.nodeToTreeSpace); - intersectingBrushesStream.Read(out this.intersection.brush1.toOtherBrushSpace); - - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.usedVertices, out this.intersection.brush1.usedVerticesLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.usedPlanePairs, out this.intersection.brush1.usedPlanePairsLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.localSpacePlanes0, out this.intersection.brush1.localSpacePlanesAndEdges0Length); - - intersectingBrushesStream.Read(out this.intersection.brush1.localSpacePlanes0Length); - - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.localSpacePlaneIndices0, out this.intersection.brush1.localSpacePlaneIndices0Length); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.vertexIntersectionPlanes, out this.intersection.brush1.vertexIntersectionPlanesLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.vertexIntersectionSegments, out this.intersection.brush1.vertexIntersectionSegmentsLength); - intersectingBrushesStream.ReadArrayAndEnsureSize(ref this.intersection.brush1.surfaceInfos, out this.intersection.brush1.surfaceInfosLength); - intersectingBrushesStream.EndForEachIndex(); - - - ref var brushPairIntersection0 = ref intersection.brush0; - ref var brushPairIntersection1 = ref intersection.brush1; - var brushIndexOrder0 = brushPairIntersection0.brushIndexOrder; - var brushIndexOrder1 = brushPairIntersection1.brushIndexOrder; - - UnityEngine.Debug.Assert(brushPairIntersection0.brushIndexOrder.compactNodeID == brushIndexOrder0.compactNodeID); - UnityEngine.Debug.Assert(brushPairIntersection1.brushIndexOrder.compactNodeID == brushIndexOrder1.compactNodeID); - - var weldFilter = WeldIncidenceFilter.Disabled; - if (useIncidenceWeld && - brushTreeSpacePlaneCache[brushIndexOrder0.nodeOrder].IsCreated && - brushTreeSpacePlaneCache[brushIndexOrder1.nodeOrder].IsCreated) - weldFilter = WeldIncidenceFilter.Create(ref brushTreeSpacePlaneCache[brushIndexOrder0.nodeOrder].Value.treeSpacePlanes, brushPairIntersection0.surfaceInfosLength, - ref brushTreeSpacePlaneCache[brushIndexOrder1.nodeOrder].Value.treeSpacePlanes, brushPairIntersection1.surfaceInfosLength); - - // Canonical vertices: every vertex this pair creates lies on its first brush, and that brush's touching - // brushes include the second, so the first brush is where each vertex's key is gathered. - var canonical = CanonicalVertices.Create(canonicalVertexStage, brushTreeSpacePlaneCache, brushesTouchedByBrushCache); - var canonicalNodeOrder = brushIndexOrder0.nodeOrder; - if (canonical.DecidesLoopIdentity) - weldFilter = WeldIncidenceFilter.SameVertexOnly; - - int insideVerticesStream0Capacity = math.max(1, brushPairIntersection0.usedVerticesLength); - int insideVerticesStream1Capacity = math.max(1, brushPairIntersection1.usedVerticesLength); - int intersectionStream0Capacity = math.max(1, brushPairIntersection1.usedPlanePairsLength) * brushPairIntersection0.localSpacePlanes0Length; - int intersectionStream1Capacity = math.max(1, brushPairIntersection0.usedPlanePairsLength) * brushPairIntersection1.localSpacePlanes0Length; - int foundIndices0Capacity = intersectionStream0Capacity + (2 * intersectionStream1Capacity) + (brushPairIntersection0.localSpacePlanes0Length * insideVerticesStream0Capacity); - int foundIndices1Capacity = intersectionStream1Capacity + (2 * intersectionStream0Capacity) + (brushPairIntersection1.localSpacePlanes0Length * insideVerticesStream1Capacity); - - var foundIndices0Length = 0; - var foundIndices1Length = 0; - - var desiredVertexCapacity = math.max(foundIndices0Capacity, foundIndices1Capacity); - - NativeArray foundIndices0; - using var _foundIndices0 = foundIndices0 = new NativeArray(foundIndices0Capacity, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref foundIndices0, foundIndices0Capacity); - - NativeArray foundIndices1; - using var _foundIndices1 = foundIndices1 = new NativeArray(foundIndices1Capacity, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref foundIndices1, foundIndices1Capacity); - - HashedVertices hashedTreeSpaceVertices; - using var _hashedTreeSpaceVertices = hashedTreeSpaceVertices = new HashedVertices(desiredVertexCapacity, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref hashedTreeSpaceVertices, desiredVertexCapacity); - - HashedVertices snapHashedVertices; - using var _snapHashedVertices = snapHashedVertices = new HashedVertices(desiredVertexCapacity, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref snapHashedVertices, desiredVertexCapacity); - - - // TODO: fill them with original brush vertices so that they're always snapped to these - - // With canonical positions the brush corners here would be raw ones, and snapping a canonical vertex onto - // them would undo it; a canonical corner that is the same vertex has the same bits anyway. - if (canonical.MovesVertices) - { - } else - if (brushIndexOrder0.nodeOrder < brushIndexOrder1.nodeOrder) - { - snapHashedVertices.AddUniqueVertices(ref treeSpaceVerticesCache[brushIndexOrder0.nodeOrder].Value.treeSpaceVertices); - snapHashedVertices.ReplaceIfExists(ref treeSpaceVerticesCache[brushIndexOrder1.nodeOrder].Value.treeSpaceVertices, in weldFilter); - } else - { - snapHashedVertices.AddUniqueVertices(ref treeSpaceVerticesCache[brushIndexOrder1.nodeOrder].Value.treeSpaceVertices); - snapHashedVertices.ReplaceIfExists(ref treeSpaceVerticesCache[brushIndexOrder0.nodeOrder].Value.treeSpaceVertices, in weldFilter); - } - - - - // First find vertices from other brush that are inside the other brush, so that any vertex we - // find during the intersection part will be snapped to those vertices and not the other way around - - // TODO: when all vertices of a polygon are inside the other brush, don't bother intersecting it. - // same when two planes overlap each other ... - - // Now find all the intersection vertices - if (intersection.type == IntersectionType.Intersection) - { - if (brushPairIntersection1.usedPlanePairsLength > 0) - { - FindIntersectionVertices(ref brushPairIntersection0.localSpacePlanes0, - brushPairIntersection0.localSpacePlanes0Length, - brushPairIntersection0.localSpacePlanesAndEdges0Length, - ref brushPairIntersection1.localSpacePlanes0, - brushPairIntersection1.localSpacePlanes0Length, - brushPairIntersection1.localSpacePlanesAndEdges0Length, - ref brushPairIntersection1.usedPlanePairs, - brushPairIntersection1.usedPlanePairsLength, - ref brushPairIntersection0.localSpacePlaneIndices0, - brushPairIntersection0.localSpacePlaneIndices0Length, - brushPairIntersection0.nodeToTreeSpace, - ref hashedTreeSpaceVertices, - ref snapHashedVertices, - foundIndices0, ref foundIndices0Length, - foundIndices1, ref foundIndices1Length, in weldFilter, in canonical, canonicalNodeOrder); - } - - if (brushPairIntersection0.usedPlanePairsLength > 0) - { - FindIntersectionVertices(ref brushPairIntersection1.localSpacePlanes0, - brushPairIntersection1.localSpacePlanes0Length, - brushPairIntersection1.localSpacePlanesAndEdges0Length, - ref brushPairIntersection0.localSpacePlanes0, - brushPairIntersection0.localSpacePlanes0Length, - brushPairIntersection0.localSpacePlanesAndEdges0Length, - ref brushPairIntersection0.usedPlanePairs, - brushPairIntersection0.usedPlanePairsLength, - ref brushPairIntersection1.localSpacePlaneIndices0, - brushPairIntersection1.localSpacePlaneIndices0Length, - brushPairIntersection0.nodeToTreeSpace, - ref hashedTreeSpaceVertices, - ref snapHashedVertices, - foundIndices1, ref foundIndices1Length, - foundIndices0, ref foundIndices0Length, in weldFilter, in canonical, canonicalNodeOrder); - } - } - - // Find all vertices of brush0 that are inside brush1, and put their intersections into the appropriate loops - if (foundIndices0Length > 0 && - brushPairIntersection0.usedVerticesLength > 0) - { - FindInsideVertices(brushPairIntersection0.usedVertices, - brushPairIntersection0.usedVerticesLength, - brushPairIntersection0.vertexIntersectionPlanes, - brushPairIntersection0.vertexIntersectionPlanesLength, - brushPairIntersection0.vertexIntersectionSegments, - brushPairIntersection0.vertexIntersectionSegmentsLength, - brushPairIntersection1.localSpacePlanes0, - brushPairIntersection1.localSpacePlanes0Length, - brushPairIntersection1.localSpacePlanesAndEdges0Length, - brushPairIntersection0.nodeToTreeSpace, - float4x4.identity, - ref hashedTreeSpaceVertices, - ref snapHashedVertices, - foundIndices0, ref foundIndices0Length, in weldFilter, in canonical, canonicalNodeOrder); - } - - // Find all vertices of brush1 that are inside brush0, and put their intersections into the appropriate loops - if (foundIndices1Length > 0 && - brushPairIntersection1.usedVerticesLength > 0) - { - FindInsideVertices(brushPairIntersection1.usedVertices, - brushPairIntersection1.usedVerticesLength, - brushPairIntersection1.vertexIntersectionPlanes, - brushPairIntersection1.vertexIntersectionPlanesLength, - brushPairIntersection1.vertexIntersectionSegments, - brushPairIntersection1.vertexIntersectionSegmentsLength, - brushPairIntersection0.localSpacePlanes0, - brushPairIntersection0.localSpacePlanes0Length, - brushPairIntersection0.localSpacePlanesAndEdges0Length, - brushPairIntersection0.nodeToTreeSpace, - brushPairIntersection1.toOtherBrushSpace, - ref hashedTreeSpaceVertices, - ref snapHashedVertices, - foundIndices1, ref foundIndices1Length, in weldFilter, in canonical, canonicalNodeOrder); - } - - - ref var brushTreeSpacePlanes0 = ref brushTreeSpacePlaneCache[brushIndexOrder0.nodeOrder].Value; - ref var brushTreeSpacePlanes1 = ref brushTreeSpacePlaneCache[brushIndexOrder1.nodeOrder].Value; - - // Give a coplanar aligned face a loop to carry its category on; see the method comment. - if (intersection.type == IntersectionType.Intersection && - treeSpaceVerticesCache[brushIndexOrder0.nodeOrder].IsCreated && - treeSpaceVerticesCache[brushIndexOrder1.nodeOrder].IsCreated && - brushTreeSpacePlaneCache[brushIndexOrder0.nodeOrder].IsCreated && - brushTreeSpacePlaneCache[brushIndexOrder1.nodeOrder].IsCreated) - { - AddFullSurfaceLoopsForAlignedFaces( - ref treeSpaceVerticesCache[brushIndexOrder0.nodeOrder].Value.treeSpaceVertices, - ref brushTreeSpacePlanes0, ref brushTreeSpacePlanes1, - brushPairIntersection0.surfaceInfos, brushPairIntersection0.surfaceInfosLength, - brushPairIntersection0.localSpacePlaneIndices0, brushPairIntersection0.localSpacePlaneIndices0Length, - brushPairIntersection1.surfaceInfosLength, - foundIndices0, ref foundIndices0Length, - ref hashedTreeSpaceVertices, ref snapHashedVertices, in weldFilter, in canonical, canonicalNodeOrder); - - AddFullSurfaceLoopsForAlignedFaces( - ref treeSpaceVerticesCache[brushIndexOrder1.nodeOrder].Value.treeSpaceVertices, - ref brushTreeSpacePlanes1, ref brushTreeSpacePlanes0, - brushPairIntersection1.surfaceInfos, brushPairIntersection1.surfaceInfosLength, - brushPairIntersection1.localSpacePlaneIndices0, brushPairIntersection1.localSpacePlaneIndices0Length, - brushPairIntersection0.surfaceInfosLength, - foundIndices1, ref foundIndices1Length, - ref hashedTreeSpaceVertices, ref snapHashedVertices, in weldFilter, in canonical, canonicalNodeOrder); - } - - if (foundIndices0Length >= 3) - { - if (brushTreeSpacePlaneCache[brushIndexOrder0.nodeOrder].IsCreated) - { - var brushTransformations0 = brushPairIntersection0.nodeToTreeSpace; - var invertedTransform = math.determinant(brushTransformations0) < 0; - - GenerateLoop(brushIndexOrder0, - brushIndexOrder1, - invertedTransform, - brushPairIntersection0.surfaceInfos, - brushPairIntersection0.surfaceInfosLength, - ref brushTreeSpacePlanes0, - foundIndices0, ref foundIndices0Length, - ref hashedTreeSpaceVertices, - outputSurfaces); - } else - { - UnityEngine.Debug.LogError($"brushTreeSpacePlaneCache not initialized for brush with index {brushIndexOrder0.compactNodeID}"); - } - } - - if (foundIndices1Length >= 3) - { - if (brushTreeSpacePlaneCache[brushIndexOrder1.nodeOrder].IsCreated) - { - var brushTransformations1 = brushPairIntersection1.nodeToTreeSpace; - var invertedTransform = math.determinant(brushTransformations1) < 0; - - GenerateLoop(brushIndexOrder1, - brushIndexOrder0, - invertedTransform, - brushPairIntersection1.surfaceInfos, - brushPairIntersection1.surfaceInfosLength, - ref brushTreeSpacePlanes1, - foundIndices1, - ref foundIndices1Length, - ref hashedTreeSpaceVertices, - outputSurfaces); - } else - { - UnityEngine.Debug.LogError($"brushTreeSpacePlaneCache not initialized for brush with index {brushIndexOrder1.compactNodeID}"); - } - } - } - } -} diff --git a/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs.meta b/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs.meta deleted file mode 100644 index 1fa3f039..00000000 --- a/Core/2.Processing/Jobs/CreateIntersectionLoopsJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 82d79b9244f8ea14581b8ea24149a243 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs b/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs index 0288597a..8d36a356 100644 --- a/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs +++ b/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs @@ -17,14 +17,11 @@ struct FindAllBrushIntersectionPairsJob : IJobParallelForDefer { // Read [NoAlias, ReadOnly] public NativeList allTreeBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList transformationCache; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; [NoAlias, ReadOnly] public NativeList brushTreeSpaceBounds; [NoAlias, ReadOnly] public NativeList rebuildTreeBrushIndexOrders; // Broad-phase over every brush in the tree (BuildBrushBoundsSweepJob): only brushes whose // bounds can overlap this brush's are tested, instead of all of them. [NoAlias, ReadOnly] public NativeList brushBoundsSweep; - [NoAlias, ReadOnly] public bool exactCSG; [NoAlias, ReadOnly] public NativeList> exactBrushCache; // Read (Re-alloc) / Write @@ -37,13 +34,11 @@ struct FindAllBrushIntersectionPairsJob : IJobParallelForDefer IntersectionType Intersect(int brush0NodeOrder, int brush1NodeOrder) { - if (exactCSG) - return IntersectionUtility.FindIntersectionExact(exactBrushCache[brush0NodeOrder], exactBrushCache[brush1NodeOrder]); - return IntersectionUtility.FindIntersection(brush0NodeOrder, brush1NodeOrder, ref brushMeshLookup, ref brushTreeSpaceBounds, ref transformationCache); + return IntersectionUtility.FindIntersectionExact(exactBrushCache[brush0NodeOrder], exactBrushCache[brush1NodeOrder]); } - // How far apart bounds may be and still be tested: none for the exact CSG, whose bounds contain its exact corners - double BoundsMargin => exactCSG ? 0 : IntersectionUtility.kBoundsDistanceEpsilon; + // Bounds contain the exact corners, so they are tested without a margin + const double BoundsMargin = 0; // Per thread scratch memory //[NativeDisableContainerSafetyRestriction] NativeArray transformedPlanes0; @@ -196,12 +191,9 @@ struct FindAllIndirectBrushIntersectionPairsJob : IJobParallelForDefer { // Read [NoAlias, ReadOnly] public NativeList allTreeBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList transformationCache; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; [NoAlias, ReadOnly] public NativeList brushTreeSpaceBounds; [NoAlias, ReadOnly] public NativeList brushesThatNeedIndirectUpdate; [NoAlias, ReadOnly] public NativeList brushBoundsSweep; - [NoAlias, ReadOnly] public bool exactCSG; [NoAlias, ReadOnly] public NativeList> exactBrushCache; // Read (Re-alloc) / Write @@ -211,13 +203,11 @@ struct FindAllIndirectBrushIntersectionPairsJob : IJobParallelForDefer IntersectionType Intersect(int brush0NodeOrder, int brush1NodeOrder) { - if (exactCSG) - return IntersectionUtility.FindIntersectionExact(exactBrushCache[brush0NodeOrder], exactBrushCache[brush1NodeOrder]); - return IntersectionUtility.FindIntersection(brush0NodeOrder, brush1NodeOrder, ref brushMeshLookup, ref brushTreeSpaceBounds, ref transformationCache); + return IntersectionUtility.FindIntersectionExact(exactBrushCache[brush0NodeOrder], exactBrushCache[brush1NodeOrder]); } - // How far apart bounds may be and still be tested: none for the exact CSG, whose bounds contain its exact corners - double BoundsMargin => exactCSG ? 0 : IntersectionUtility.kBoundsDistanceEpsilon; + // Bounds contain the exact corners, so they are tested without a margin + const double BoundsMargin = 0; // Per thread scratch memory //[NativeDisableContainerSafetyRestriction] NativeArray transformedPlanes0; @@ -535,9 +525,6 @@ public void Execute(int index) sealed class IntersectionUtility { - public const double kBoundsDistanceEpsilon = CSGConstants.kBoundsDistanceEpsilon; - - public struct IndexOrderComparer : System.Collections.Generic.IComparer { public int Compare(IndexOrder x, IndexOrder y) @@ -546,151 +533,6 @@ public int Compare(IndexOrder x, IndexOrder y) } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void TransformOtherIntoBrushSpace([NoAlias, ReadOnly] ref float4x4 treeToBrushSpaceMatrix, [NoAlias, ReadOnly] ref float4x4 brushToTreeSpaceMatrix, [NoAlias, ReadOnly] ref BlobArray srcPlanes, [NoAlias, WriteOnly] NativeArray dstPlanes) - { - var brush1ToBrush0LocalLocalSpace = math.transpose(math.mul(treeToBrushSpaceMatrix, brushToTreeSpaceMatrix)); - for (int plane_index = 0; plane_index < srcPlanes.Length; plane_index++) - { - ref var srcPlane = ref srcPlanes[plane_index]; - dstPlanes[plane_index] = math.mul(brush1ToBrush0LocalLocalSpace, srcPlane); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntersectionType ConvexPolytopeTouching([NoAlias, ReadOnly] ref BrushMeshBlob brushMesh0, - [NoAlias, ReadOnly] ref float4x4 treeToNode0SpaceMatrix, - [NoAlias, ReadOnly] ref float4x4 nodeToTree0SpaceMatrix, - [NoAlias, ReadOnly] ref BrushMeshBlob brushMesh1, - [NoAlias, ReadOnly] ref float4x4 treeToNode1SpaceMatrix, - [NoAlias, ReadOnly] ref float4x4 nodeToTree1SpaceMatrix//, - //[NoAlias, ReadOnly] ref NativeArray transformedPlanes0, - //[NoAlias, ReadOnly] ref NativeArray transformedPlanes1 - ) - { - ref var brushPlanes0 = ref brushMesh0.localPlanes; - - NativeArray transformedPlanes0; - using var _transformedPlanes0 = transformedPlanes0 = new NativeArray(brushPlanes0.Length, Allocator.Temp); - - //NativeCollectionHelpers.EnsureMinimumSize(ref transformedPlanes0, brushPlanes0.Length); - TransformOtherIntoBrushSpace(ref treeToNode0SpaceMatrix, ref nodeToTree1SpaceMatrix, ref brushPlanes0, transformedPlanes0); - - ref var brushVertices1 = ref brushMesh1.localVertices; - int negativeSides1 = 0; - for (var i = 0; i < brushPlanes0.Length; i++) - { - var plane0 = transformedPlanes0[i]; - int side = WhichSide(ref brushVertices1, plane0, kBoundsDistanceEpsilon); - if (side < 0) negativeSides1++; - if (side > 0) return IntersectionType.NoIntersection; - } - - //if (intersectingSides1 != transformedPlanes0.Length) return IntersectionType.Intersection; - //if (intersectingSides > 0) return IntersectionType.Intersection; - //if (positiveSides1 > 0) return IntersectionType.NoIntersection; - //if (negativeSides > 0 && positiveSides > 0) return IntersectionType.Intersection; - if (negativeSides1 == brushPlanes0.Length) - return IntersectionType.BInsideA; - - ref var brushPlanes1 = ref brushMesh1.localPlanes; - //* - - NativeArray transformedPlanes1; - using var _transformedPlanes1 = transformedPlanes1 = new NativeArray(brushPlanes1.Length, Allocator.Temp); - - //NativeCollectionHelpers.EnsureMinimumSize(ref transformedPlanes1, brushPlanes1.Length); - TransformOtherIntoBrushSpace(ref treeToNode1SpaceMatrix, ref nodeToTree0SpaceMatrix, ref brushPlanes1, transformedPlanes1); - - - ref var brushVertices0 = ref brushMesh0.localVertices; - int negativeSides2 = 0; - int intersectingSides2 = 0; - for (var i = 0; i < brushPlanes1.Length; i++) - { - var plane1 = transformedPlanes1[i]; - int side = WhichSide(ref brushVertices0, plane1, kBoundsDistanceEpsilon); - if (side < 0) negativeSides2++; - if (side > 0) return IntersectionType.NoIntersection; - if (side == 0) intersectingSides2++; - } - - if (intersectingSides2 > 0) return IntersectionType.Intersection; - //if (negativeSides > 0 && positiveSides > 0) return IntersectionType.Intersection; - if (negativeSides2 == brushPlanes1.Length) - return IntersectionType.AInsideB; - - return IntersectionType.Intersection;//*/ - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int WhichSide([NoAlias,ReadOnly] ref BlobArray vertices, float4 plane, double epsilon) - { - { - var t = math.dot(plane, new float4(vertices[0], 1)); - if (t >= epsilon) goto HavePositive; - if (t <= -epsilon) goto HaveNegative; - return 0; - } - HaveNegative: - for (var i = 1; i < vertices.Length; i++) - { - var t = math.dot(plane, new float4(vertices[i], 1)); - if (t > -epsilon) - return 0; - } - return -1; - HavePositive: - for (var i = 1; i < vertices.Length; i++) - { - var t = math.dot(plane, new float4(vertices[i], 1)); - if (t < epsilon) - return 0; - } - return 1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IntersectionType FindIntersection(int brush0NodeOrder, int brush1NodeOrder, - [NoAlias, ReadOnly] ref NativeArray> brushMeshLookup, - [NoAlias, ReadOnly] ref NativeList brushTreeSpaceBounds, - [NoAlias, ReadOnly] ref NativeList transformations//, - //[NoAlias, ReadOnly] ref NativeArray transformedPlanes0, - //[NoAlias, ReadOnly] ref NativeArray transformedPlanes1 - ) - { - var brushMesh0 = brushMeshLookup[brush0NodeOrder]; - var brushMesh1 = brushMeshLookup[brush1NodeOrder]; - if (!brushMesh0.IsCreated || !brushMesh1.IsCreated) - return IntersectionType.NoIntersection; - - var bounds0 = brushTreeSpaceBounds[brush0NodeOrder]; - var bounds1 = brushTreeSpaceBounds[brush1NodeOrder]; - - if (!bounds0.Intersects(bounds1, IntersectionUtility.kBoundsDistanceEpsilon)) - return IntersectionType.NoIntersection; - - var transformation0 = transformations[brush0NodeOrder]; - var transformation1 = transformations[brush1NodeOrder]; - - var treeToNode0SpaceMatrix = transformation0.treeToNode; - var nodeToTree0SpaceMatrix = transformation0.nodeToTree; - var treeToNode1SpaceMatrix = transformation1.treeToNode; - var nodeToTree1SpaceMatrix = transformation1.nodeToTree; - - var result = IntersectionUtility.ConvexPolytopeTouching(ref brushMesh0.Value, - ref treeToNode0SpaceMatrix, - ref nodeToTree0SpaceMatrix, - ref brushMesh1.Value, - ref treeToNode1SpaceMatrix, - ref nodeToTree1SpaceMatrix//, - //ref transformedPlanes0, - //ref transformedPlanes1 - ); - - return result; - } - public static IntersectionType FindIntersectionExact(BlobAssetReference brush0, BlobAssetReference brush1) { if (!brush0.IsCreated || !brush1.IsCreated) diff --git a/Core/2.Processing/Jobs/FindBrushPairsJob.cs b/Core/2.Processing/Jobs/FindBrushPairsJob.cs deleted file mode 100644 index 5433e238..00000000 --- a/Core/2.Processing/Jobs/FindBrushPairsJob.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Collections.LowLevel.Unsafe; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct FindBrushPairsJob : IJob - { - // Read - [NoAlias, ReadOnly] public int maxOrder; - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushes; - - // Read (Re-allocate) / Write - [NoAlias] public NativeList uniqueBrushPairs; - - // Per thread scratch memory - //[NativeDisableContainerSafetyRestriction] NativeBitArray usedLookup; - - public void Execute() - { - var maxPairs = (maxOrder * maxOrder); - - NativeBitArray usedLookup; - using var _usedLookup = usedLookup = new NativeBitArray(maxPairs, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref usedLookup, maxPairs); - uniqueBrushPairs.Clear(); - - int requiredCapacity = 0; - for (int b0 = 0; b0 < allUpdateBrushIndexOrders.Length; b0++) - { - var brushIndexOrder0 = allUpdateBrushIndexOrders[b0]; - int brushNodeOrder0 = brushIndexOrder0.nodeOrder; - - var brushesTouchedByBrush = brushesTouchedByBrushes[brushNodeOrder0]; - if (brushesTouchedByBrush == BlobAssetReference.Null) - continue; - - ref var intersections = ref brushesTouchedByBrush.Value.brushIntersections; - if (intersections.Length == 0) - continue; - - requiredCapacity += intersections.Length + 1; - } - - if (uniqueBrushPairs.Capacity < requiredCapacity + 1) - uniqueBrushPairs.Capacity = requiredCapacity + 1; - // Workaround for the incredibly dumb "can't create a stream that is zero sized" when the value is determined at runtime. Yeah, thanks - uniqueBrushPairs.AddNoResize(new BrushPair2 { type = IntersectionType.InvalidValue }); - - for (int b0 = 0; b0 < allUpdateBrushIndexOrders.Length; b0++) - { - var brushIndexOrder0 = allUpdateBrushIndexOrders[b0]; - int brushNodeOrder0 = brushIndexOrder0.nodeOrder; - - var brushesTouchedByBrush = brushesTouchedByBrushes[brushNodeOrder0]; - if (brushesTouchedByBrush == BlobAssetReference.Null) - continue; - - ref var intersections = ref brushesTouchedByBrush.Value.brushIntersections; - if (intersections.Length == 0) - continue; - - // Find all intersections between brushes - for (int i = 0; i < intersections.Length; i++) - { - var intersection = intersections[i]; - var brushIndexOrder1 = intersection.nodeIndexOrder; - int brushNodeOrder1 = brushIndexOrder1.nodeOrder; - - var brushPair = new BrushPair2 - { - type = intersection.type, - brushIndexOrder0 = brushIndexOrder0, - brushIndexOrder1 = brushIndexOrder1 - }; - - if (brushNodeOrder0 > brushNodeOrder1) // ensures we do calculations exactly the same for each brush pair - brushPair.Flip(); - - int testIndex = (brushPair.brushIndexOrder0.nodeOrder * maxOrder) + brushPair.brushIndexOrder1.nodeOrder; - - if (!usedLookup.IsSet(testIndex)) - { - usedLookup.Set(testIndex, true); - uniqueBrushPairs.AddNoResize(brushPair); - } - } - } - } - } -} diff --git a/Core/2.Processing/Jobs/FindBrushPairsJob.cs.meta b/Core/2.Processing/Jobs/FindBrushPairsJob.cs.meta deleted file mode 100644 index 8ac69baf..00000000 --- a/Core/2.Processing/Jobs/FindBrushPairsJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 38ac1a09e7e8d9f44970a7abd9739d73 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs b/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs deleted file mode 100644 index f683c667..00000000 --- a/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs +++ /dev/null @@ -1,1035 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using Unity.Collections.LowLevel.Unsafe; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - - [BurstCompile(CompileSynchronously = true)] - struct FindLoopOverlapIntersectionsJob : IJobParallelForDefer - { - public const int kMaxVertexCount = short.MaxValue; - const float kSqrVertexEqualEpsilon = CSGConstants.kSqrVertexEqualEpsilon; - const float kFatPlaneWidthEpsilon = CSGConstants.kFatPlaneWidthEpsilon; - - // Set false to skip the base-polygon T-junction split. - const bool kSplitBasePolygonsAtIntersectionVertices = true; - - // Gate the vertex weld on plane incidence (WeldIncidenceFilter); set from CSGManager's kUseIncidenceWeld. - [NoAlias, ReadOnly] public bool useIncidenceWeld; - - // Canonical vertices (see CanonicalVertices) - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - - // Read - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList outputSurfaceVertices; - [NoAlias, ReadOnly] public NativeList outputSurfaces; - [NoAlias, ReadOnly] public NativeArray outputSurfacesRange; - [NoAlias, ReadOnly] public int maxNodeOrder; - [NoAlias, ReadOnly] public NativeList> basePolygonCache; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; - - // Read Write - public Allocator allocator; - [NativeDisableParallelForRestriction] - [NoAlias] public NativeArray> loopVerticesLookup; - - // Write - [NativeDisableParallelForRestriction] - [NoAlias, WriteOnly] public NativeStream.Writer output; - - // Per thread scratch memory - //[NativeDisableContainerSafetyRestriction] NativeArray newSelfEdges; - //[NativeDisableContainerSafetyRestriction] NativeArray srcIndices; - //[NativeDisableContainerSafetyRestriction] NativeArray basePolygonSurfaceInfos; - //[NativeDisableContainerSafetyRestriction] NativeList tempList; - //[NativeDisableContainerSafetyRestriction] NativeList brushIntersections; - //[NativeDisableContainerSafetyRestriction] NativeList intersectionSurfaceInfos; - //[NativeDisableContainerSafetyRestriction] NativeList> basePolygonEdges; - //[NativeDisableContainerSafetyRestriction] NativeList> intersectionEdges; - //[NativeDisableContainerSafetyRestriction] NativeBitArray usedNodeOrders; - //[NativeDisableContainerSafetyRestriction] HashedVertices hashedTreeSpaceVertices; - //[NativeDisableContainerSafetyRestriction] NativeArray intersectionSurfaceSegments; - //[NativeDisableContainerSafetyRestriction] NativeArray otherVertices; - - struct CompareSortByBasePlaneIndex : System.Collections.Generic.IComparer - { - public readonly int Compare(BrushIntersectionLoop x, BrushIntersectionLoop y) - { - var diff = x.surfaceInfo.basePlaneIndex - y.surfaceInfo.basePlaneIndex; - if (diff != 0) - return diff; - - return x.indexOrder1.nodeOrder - y.indexOrder1.nodeOrder; - } - } - - readonly static CompareSortByBasePlaneIndex kCompareSortByBasePlaneIndex = new CompareSortByBasePlaneIndex(); - - // The faces of this brush and of the brush it is being cut by are what identify the vertices here. - WeldIncidenceFilter CreateWeldFilter(int selfNodeOrder, int otherNodeOrder) - { - // Canonical vertices: a vertex computed twice has the same bits, so only the same position is the same vertex. - if (canonicalVertexStage >= CanonicalVertexStage.LoopIdentity && - CanonicalVertices.IsAvailable(canonicalVertexStage, brushTreeSpacePlaneCache, brushesTouchedByBrushCache)) - return WeldIncidenceFilter.SameVertexOnly; - if (!useIncidenceWeld || - !brushTreeSpacePlaneCache[selfNodeOrder].IsCreated || !basePolygonCache[selfNodeOrder].IsCreated || - !brushTreeSpacePlaneCache[otherNodeOrder].IsCreated || !basePolygonCache[otherNodeOrder].IsCreated) - return WeldIncidenceFilter.Disabled; - return WeldIncidenceFilter.Create(ref brushTreeSpacePlaneCache[selfNodeOrder].Value.treeSpacePlanes, basePolygonCache[selfNodeOrder].Value.polygons.Length, - ref brushTreeSpacePlaneCache[otherNodeOrder].Value.treeSpacePlanes, basePolygonCache[otherNodeOrder].Value.polygons.Length); - } - - void CopyFrom(NativeList> dst, int index, ref BrushIntersectionLoop brushIntersectionLoop, HashedVertices hashedTreeSpaceVertices, int extraCapacity, in WeldIncidenceFilter weldFilter) - { - Debug.Assert(extraCapacity >= 0); - ref var vertexIndex = ref brushIntersectionLoop.loopVertexIndex; - ref var loopVertexCount = ref brushIntersectionLoop.loopVertexCount; - - NativeArray srcIndices; - using var _srcIndices = srcIndices = new NativeArray(loopVertexCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref srcIndices, loopVertexCount); - - hashedTreeSpaceVertices.ReserveAdditionalVertices(loopVertexCount); - for (int j = 0; j < loopVertexCount; j++) - srcIndices[j] = hashedTreeSpaceVertices.AddNoResize(outputSurfaceVertices[vertexIndex + j], in weldFilter); - - var dstEdges = new UnsafeList(loopVertexCount + extraCapacity, Allocator.Temp); - for (int j = 1; j < loopVertexCount; j++) - { - dstEdges.AddNoResize(new Edge { index1 = srcIndices[j - 1], index2 = srcIndices[j] }); - } - dstEdges.AddNoResize(new Edge { index1 = srcIndices[loopVertexCount - 1], index2 = srcIndices[0] }); - - dst[index] = dstEdges; - } - - - public void Execute(int index) - { - var brushIndexOrder = allUpdateBrushIndexOrders[index]; - int brushNodeOrder = brushIndexOrder.nodeOrder; - - // Can happen when BrushMeshes are not initialized correctly - if (basePolygonCache[brushNodeOrder] == BlobAssetReference.Null) - { - loopVerticesLookup[brushIndexOrder.nodeOrder] = new UnsafeList(16, allocator); - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(0); - output.Write(0); - output.Write(0); - output.EndForEachIndex(); - return; - } - - ref var basePolygonBlob = ref basePolygonCache[brushNodeOrder].Value; - var surfaceCount = basePolygonBlob.polygons.Length; - if (surfaceCount == 0) - { - loopVerticesLookup[brushIndexOrder.nodeOrder] = new UnsafeList(16, allocator); - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(0); - output.Write(0); - output.Write(0); - output.EndForEachIndex(); - return; - } - - HashedVertices hashedTreeSpaceVertices; - using var _hashedTreeSpaceVertices = hashedTreeSpaceVertices = new HashedVertices(HashedVertices.kMaxVertexCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref hashedTreeSpaceVertices, HashedVertices.kMaxVertexCount); - - NativeArray basePolygonSurfaceInfos; - using var _basePolygonSurfaceInfos = basePolygonSurfaceInfos = new NativeArray(surfaceCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref basePolygonSurfaceInfos, surfaceCount); - NativeList> basePolygonEdges; - using var _basePolygonEdges = basePolygonEdges = new NativeList>(surfaceCount, Allocator.Temp); - basePolygonEdges.Resize(surfaceCount, NativeArrayOptions.ClearMemory); - //NativeCollectionHelpers.EnsureSizeAndClear(ref basePolygonEdges, surfaceCount); - try - { - - - var intersectionOffset = outputSurfacesRange[brushNodeOrder].x; - var intersectionCount = outputSurfacesRange[brushNodeOrder].y; - - hashedTreeSpaceVertices.AddUniqueVertices(ref basePolygonBlob.vertices); /*OUTPUT*/ - - if (intersectionCount == 0) - { - // If we don't have any intersection loops, just convert basePolygonBlob to loops and be done - // TODO: should do this per surface! - - for (int s = 0; s < basePolygonBlob.polygons.Length; s++) - { - ref var input = ref basePolygonBlob.polygons[s]; - - ref var nodeIndexOrder = ref basePolygonBlob.polygons[s].nodeIndexOrder; - ref var surfaceInfo = ref basePolygonBlob.polygons[s].surfaceInfo; - basePolygonSurfaceInfos[s] = new IndexSurfaceInfo - { - brushIndexOrder = nodeIndexOrder, - interiorCategory = surfaceInfo.interiorCategory, - basePlaneIndex = surfaceInfo.basePlaneIndex - }; - - if (input.endEdgeIndex == input.startEdgeIndex) - continue; - - var edges = new UnsafeList(input.endEdgeIndex - input.startEdgeIndex, Allocator.Temp); - for (int e = input.startEdgeIndex; e < input.endEdgeIndex; e++) - edges.AddNoResize(basePolygonBlob.edges[e]); - basePolygonEdges[s] = edges; - } - - //if (intersectionEdges.IsCreated) - // intersectionEdges.Clear(); - - //if (intersectionSurfaceInfos.IsCreated) - // intersectionSurfaceInfos.Clear(); - - var writeVertices = new UnsafeList(hashedTreeSpaceVertices.Length, allocator); - writeVertices.Resize(hashedTreeSpaceVertices.Length, - NativeArrayOptions.ClearMemory); - //NativeArrayOptions.UninitializedMemory); - for (int l = 0; l < hashedTreeSpaceVertices.Length; l++) - writeVertices[l] = hashedTreeSpaceVertices[l]; - loopVerticesLookup[brushIndexOrder.nodeOrder] = writeVertices; - - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(surfaceCount); - - output.Write(basePolygonEdges.Length); - for (int l = 0; l < basePolygonEdges.Length; l++) - { - output.Write(basePolygonSurfaceInfos[l]); - if (!basePolygonEdges[l].IsCreated) - { - output.Write(0); - continue; - } - var edges = basePolygonEdges[l]; - output.Write(edges.Length); - for (int e = 0; e < edges.Length; e++) - output.Write(edges[e]); - } - - output.Write(0); - output.EndForEachIndex(); - } - else - { - // Canonical vertices: every split point lies on this brush, so its key is gathered here. - var canonical = CanonicalVertices.Create(canonicalVertexStage, brushTreeSpacePlaneCache, brushesTouchedByBrushCache); - - NativeList brushIntersections; - using var _brushIntersections = brushIntersections = new NativeList(intersectionCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref brushIntersections, intersectionCount); - - NativeBitArray usedNodeOrders; - using var _usedNodeOrders = usedNodeOrders = new NativeBitArray(maxNodeOrder, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref usedNodeOrders, maxNodeOrder); - - var lastIntersectionIndex = intersectionCount + intersectionOffset; - for (int i = intersectionOffset; i < lastIntersectionIndex; i++) - { - var item = outputSurfaces[i]; - var otherNodeOrder1 = item.indexOrder1.nodeOrder; - - usedNodeOrders.Set(otherNodeOrder1, true); - - //Debug.Assert(outputSurface.surfaceInfo.brushIndex == pair.brushNodeIndex1); - //Debug.Assert(outputSurface.surfaceInfo.basePlaneIndex == pair.basePlaneIndex); - - brushIntersections.AddNoResize(item); - } - brushIntersections.Sort(kCompareSortByBasePlaneIndex); - - NativeList> intersectionEdges; - using var _intersectionEdges = intersectionEdges = new NativeList>(brushIntersections.Length, Allocator.Temp); - intersectionEdges.Resize(brushIntersections.Length, NativeArrayOptions.ClearMemory); - //NativeCollectionHelpers.EnsureSizeAndClear(ref intersectionEdges, brushIntersections.Length); - - NativeArray intersectionSurfaceSegments; - using var _intersectionSurfaceSegments = intersectionSurfaceSegments = new NativeArray(surfaceCount + 1, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref intersectionSurfaceSegments, surfaceCount + 1); - - NativeList intersectionSurfaceInfos; - using var _intersectionSurfaceInfos = intersectionSurfaceInfos = new NativeList(brushIntersections.Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref intersectionSurfaceInfos, brushIntersections.Length); - try - { - { - for (int s = 0; s < basePolygonBlob.polygons.Length; s++) - { - ref var input = ref basePolygonBlob.polygons[s]; - - ref var surfaceInfo = ref basePolygonBlob.polygons[s].surfaceInfo; - ref var nodeIndexOrder = ref basePolygonBlob.polygons[s].nodeIndexOrder; - basePolygonSurfaceInfos[s] = new IndexSurfaceInfo - { - brushIndexOrder = nodeIndexOrder, - interiorCategory = surfaceInfo.interiorCategory, - basePlaneIndex = surfaceInfo.basePlaneIndex - }; - - if (input.endEdgeIndex == input.startEdgeIndex) - continue; - - var edges = new UnsafeList((input.endEdgeIndex - input.startEdgeIndex) + (brushIntersections.Length * 4), Allocator.Temp); - for (int e = input.startEdgeIndex; e < input.endEdgeIndex; e++) - edges.AddNoResize(basePolygonBlob.edges[e]); - basePolygonEdges[s] = edges; - } - - - int prevBasePlaneIndex = 0; - int startIndex = 0; - for (int l = 0; l < brushIntersections.Length; l++) - { - var brushIntersectionLoop = brushIntersections[l]; - ref var surfaceInfo = ref brushIntersectionLoop.surfaceInfo; - //UnityEngine.Debug.Assert(brushIntersectionLoop.indexOrder0.compactNodeID == brushIndexOrder.compactNodeID); - UnityEngine.Debug.Assert(brushIntersectionLoop.indexOrder0.nodeOrder == brushIndexOrder.nodeOrder); - - var basePlaneIndex = surfaceInfo.basePlaneIndex; - if (prevBasePlaneIndex != basePlaneIndex) - { - intersectionSurfaceSegments[prevBasePlaneIndex] = new int2(startIndex, l - startIndex); - startIndex = l; - for (int s = prevBasePlaneIndex + 1; s < basePlaneIndex; s++) - intersectionSurfaceSegments[s] = new int2(startIndex, 0); - prevBasePlaneIndex = basePlaneIndex; - } - CopyFrom(intersectionEdges, l, ref brushIntersectionLoop, hashedTreeSpaceVertices, brushIntersections.Length * 4, - CreateWeldFilter(brushNodeOrder, brushIntersectionLoop.indexOrder1.nodeOrder)); - } - - intersectionSurfaceSegments[prevBasePlaneIndex] = new int2(startIndex, brushIntersections.Length - startIndex); - startIndex = brushIntersections.Length; - for (int s = prevBasePlaneIndex + 1; s < surfaceCount; s++) - intersectionSurfaceSegments[s] = new int2(startIndex, 0); - - - for (int s = 0; s < surfaceCount; s++) - { - var intersectionSurfaceCount = intersectionSurfaceSegments[s].y; - var intersectionSurfaceOffset = intersectionSurfaceSegments[s].x; - for (int l0 = intersectionSurfaceCount - 1; l0 >= 0; l0--) - { - //int intersectionBrushOrder0 = brushIntersections[intersectionSurfaceOffset + l0].indexOrder1.nodeOrder; - var edges = intersectionEdges[intersectionSurfaceOffset + l0]; - for (int l1 = 0; l1 < intersectionSurfaceCount; l1++) - { - if (l0 == l1) - continue; - - int intersectionBrushOrder1 = brushIntersections[intersectionSurfaceOffset + l1].indexOrder1.nodeOrder;// intersectionIndex1.w; - - FindLoopPlaneIntersections(brushTreeSpacePlaneCache.AsArray(), - CreateWeldFilter(brushNodeOrder, intersectionBrushOrder1), - intersectionBrushOrder1, - //intersectionBrushOrder0, - hashedTreeSpaceVertices, ref edges, - in canonical, brushNodeOrder); - - // TODO: merge these so that intersections will be identical on both loops (without using math, use logic) - // TODO: make sure that intersections between loops will be identical on OTHER brushes (without using math, use logic) - } - - intersectionEdges[intersectionSurfaceOffset + l0] = edges; - } - } - - ref var selfPlanes = ref brushTreeSpacePlaneCache[brushIndexOrder.nodeOrder].Value.treeSpacePlanes; - - // TODO: should only intersect with all brushes that each particular basepolygon intersects with - // but also need adjency information between basePolygons to ensure that intersections exist on - // both sides of each edge on a brush. - for (int otherBrushNodeOrder = 0; otherBrushNodeOrder < maxNodeOrder; otherBrushNodeOrder++) - { - if (!usedNodeOrders.IsSet(otherBrushNodeOrder) || - otherBrushNodeOrder == brushNodeOrder) - continue; - - ref var otherPlanes = ref brushTreeSpacePlaneCache[otherBrushNodeOrder].Value.treeSpacePlanes; - for (int b = 0; b < basePolygonEdges.Length; b++) - { - if (!basePolygonEdges[b].IsCreated) - continue; - var selfEdges = basePolygonEdges[b]; - //var before = selfEdges.Length; - - FindBasePolygonPlaneIntersections(ref otherPlanes, //ref selfPlanes, - CreateWeldFilter(brushNodeOrder, otherBrushNodeOrder), - ref selfEdges, hashedTreeSpaceVertices, - in canonical, brushNodeOrder); - basePolygonEdges[b] = selfEdges; - } - } - - for (int s = 0; s < surfaceCount; s++) - { - var intersectionSurfaceCount = intersectionSurfaceSegments[s].y; - var intersectionSurfaceOffset = intersectionSurfaceSegments[s].x; - if (intersectionSurfaceCount == 0) - continue; - - if (!basePolygonEdges[s].IsCreated) - continue; - - var bp_edges = basePolygonEdges[s]; - for (int l0 = 0; l0 < intersectionSurfaceCount; l0++) - { - int intersectionBrushOrder = brushIntersections[intersectionSurfaceOffset + l0].indexOrder1.nodeOrder;// intersectionIndex.w; - var in_edges = intersectionEdges[intersectionSurfaceOffset + l0]; - - ref var otherPlanes = ref brushTreeSpacePlaneCache[intersectionBrushOrder].Value.treeSpacePlanes; - - FindLoopVertexOverlaps(ref selfPlanes, ref in_edges, bp_edges, hashedTreeSpaceVertices); - intersectionEdges[intersectionSurfaceOffset + l0] = in_edges; - - // TODO: The following call, strictly speaking, is unncessary, but it'll hide bugs in other parts of the pipeline. - // it'll add missing vertices on overlapping loops, but they will be missing on touching surfaces, so there will be gaps. - // the alternative is that this surface could possible fail to triangulate and be completely missing .. (larger gap) - // Somehow it can also cause artifacts sometimes??? - //FindLoopVertexOverlaps(ref otherPlanes, ref bp_edges, in_edges, hashedTreeSpaceVertices); - } - } - - if (kSplitBasePolygonsAtIntersectionVertices) - { - var splitVertexCount = hashedTreeSpaceVertices.Length; - - // Mark which vertices belong to any intersection loop. - var isIntersectionVertex = new NativeArray(splitVertexCount, Allocator.Temp); - for (int i = 0; i < intersectionEdges.Length; i++) - { - if (!intersectionEdges[i].IsCreated) - continue; - var ie = intersectionEdges[i]; - for (int e = 0; e < ie.Length; e++) - { - isIntersectionVertex[ie[e].index1] = true; - isIntersectionVertex[ie[e].index2] = true; - } - } - - var candidateCount = 0; - for (int v = 0; v < splitVertexCount; v++) - if (isIntersectionVertex[v]) candidateCount++; - - if (candidateCount > 0) - { - var splitPositions = new NativeArray(splitVertexCount, Allocator.Temp); - for (int v = 0; v < splitVertexCount; v++) - splitPositions[v] = hashedTreeSpaceVertices[v]; - - var splitCandidates = new NativeArray(candidateCount, Allocator.Temp); - var k = 0; - for (int v = 0; v < splitVertexCount; v++) - if (isIntersectionVertex[v]) splitCandidates[k++] = (ushort)v; - - for (int b = 0; b < basePolygonEdges.Length; b++) - { - if (!basePolygonEdges[b].IsCreated) - continue; - var splitEdges = basePolygonEdges[b]; - LoopEdgeSplitter.SplitEdgesAtVertices(ref splitEdges, in splitPositions, in splitCandidates, candidateCount, CSGConstants.kSqrEdgeDistanceEpsilon); - basePolygonEdges[b] = splitEdges; - } - - splitPositions.Dispose(); - splitCandidates.Dispose(); - } - isIntersectionVertex.Dispose(); - } - - for (int i = 0; i < intersectionEdges.Length; i++) - { - // TODO: might not be necessary - var edges = intersectionEdges[i]; - RemoveDuplicates(ref edges); - intersectionEdges[i] = edges; - } - - for (int i = 0; i < basePolygonEdges.Length; i++) - { - if (!basePolygonEdges[i].IsCreated) - continue; - // TODO: might not be necessary - var edges = basePolygonEdges[i]; - RemoveDuplicates(ref edges); - basePolygonEdges[i] = edges; - } - - - // TODO: merge indices across multiple loops when vertices are identical - } - - - - for (int k = 0; k < brushIntersections.Length; k++) - { - var intersection = brushIntersections[k]; - ref var surfaceInfo = ref intersection.surfaceInfo; - intersectionSurfaceInfos.AddNoResize( - new IndexSurfaceInfo - { - brushIndexOrder = intersection.indexOrder1, - interiorCategory = surfaceInfo.interiorCategory, - basePlaneIndex = surfaceInfo.basePlaneIndex - }); //OUTPUT - } - - var writeVertices = new UnsafeList(hashedTreeSpaceVertices.Length, allocator); - writeVertices.Resize(hashedTreeSpaceVertices.Length, - NativeArrayOptions.ClearMemory); - //NativeArrayOptions.UninitializedMemory); - for (int l = 0; l < hashedTreeSpaceVertices.Length; l++) - writeVertices[l] = hashedTreeSpaceVertices[l]; - loopVerticesLookup[brushIndexOrder.nodeOrder] = writeVertices; - - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(surfaceCount); - - output.Write(basePolygonEdges.Length); - for (int l = 0; l < basePolygonEdges.Length; l++) - { - output.Write(basePolygonSurfaceInfos[l]); - if (!basePolygonEdges[l].IsCreated) - { - output.Write(0); - continue; - } - var edges = basePolygonEdges[l]; - output.Write(edges.Length); - for (int e = 0; e < edges.Length; e++) - output.Write(edges[e]); - } - - output.Write(intersectionEdges.Length); - for (int l = 0; l < intersectionEdges.Length; l++) - { - output.Write(intersectionSurfaceInfos[l]); - var edges = intersectionEdges[l]; - output.Write(edges.Length); - for (int e = 0; e < edges.Length; e++) - output.Write(edges[e]); - } - output.EndForEachIndex(); - } - finally - { - if (intersectionEdges.IsCreated) - { - for (int i = 0; i < intersectionEdges.Length; i++) - { - if (intersectionEdges[i].IsCreated) - intersectionEdges[i].Dispose(); - intersectionEdges[i] = default; - } - } - } - } - } - finally - { - if (basePolygonEdges.IsCreated) - { - for (int i = 0; i < basePolygonEdges.Length; i++) - { - if (basePolygonEdges[i].IsCreated) - basePolygonEdges[i].Dispose(); - basePolygonEdges[i] = default; - } - } - } - } - - public void FindLoopPlaneIntersections(NativeArray> brushTreeSpacePlanes, - in WeldIncidenceFilter weldFilter, - int intersectionBrushOrder1, //int intersectionBrushOrder0, - HashedVertices hashedTreeSpaceVertices, - ref UnsafeList edges, - in CanonicalVertices canonical = default, - int canonicalNodeOrder = -1) - { - if (edges.Length < 3) - return; - - var inputEdgesLength = edges.Length; - - NativeArray newSelfEdges; - using var _newSelfEdges = newSelfEdges = new (inputEdgesLength, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref newSelfEdges, inputEdgesLength); - - newSelfEdges.CopyFrom(edges, 0, edges.Length); - edges.Clear(); - - int4 tempVertices = int4.zero; - - ref var otherPlanes = ref brushTreeSpacePlanes[intersectionBrushOrder1].Value.treeSpacePlanes; - //ref var selfPlanes = ref brushTreeSpacePlanes[intersectionBrushOrder0].Value.treeSpacePlanes; - - var otherPlaneCount = otherPlanes.Length; - //var selfPlaneCount = selfPlanes.Length; - - hashedTreeSpaceVertices.ReserveAdditionalVertices(inputEdgesLength * 2); - - // TODO: Optimize the hell out of this - for (int e = 0; e < inputEdgesLength; e++) - { - var vertexIndex0 = newSelfEdges[e].index1; - var vertexIndex1 = newSelfEdges[e].index2; - - var vertex0 = hashedTreeSpaceVertices[vertexIndex0]; - var vertex1 = hashedTreeSpaceVertices[vertexIndex1]; - - var vertex0w = new float4(vertex0, 1); - var vertex1w = new float4(vertex1, 1); - - var foundVertices = 0; - - for (int p = 0; p < otherPlaneCount; p++) - { - var otherPlane = otherPlanes[p]; - - var distance0 = math.dot(otherPlane, vertex0w); - var distance1 = math.dot(otherPlane, vertex1w); - - if (distance0 < 0) - { - if (distance1 <= kFatPlaneWidthEpsilon || - distance0 >= -kFatPlaneWidthEpsilon) continue; - } else - { - if (distance1 >= -kFatPlaneWidthEpsilon || - distance0 <= kFatPlaneWidthEpsilon) continue; - } - - float3 newVertex; - - // Ensure we always do the intersection calculations in the exact same - // direction across a plane to increase floating point consistency - if (distance0 > 0) - { - var length = distance0 - distance1; - var delta = distance0 / length; - if (delta <= 0 || delta >= 1) - continue; - var vector = vertex0 - vertex1; - newVertex = vertex0 - (vector * delta); - } else - { - var length = distance1 - distance0; - var delta = distance1 / length; - if (delta <= 0 || delta >= 1) - continue; - var vector = vertex1 - vertex0; - newVertex = vertex1 - (vector * delta); - } - - // Whether the crossing exists at all is decided with the computed point, before canonicalization, - // so that moving it cannot carry it across the other brush's planes (and most candidates end here). - var newVertex4 = new float4(newVertex, 1); - for (int p2 = 0; p2 < otherPlaneCount; p2++) - { - otherPlane = otherPlanes[p2]; - var distance = math.dot(otherPlane, newVertex4); - if (distance > kFatPlaneWidthEpsilon) - goto SkipEdge; - } - - // TODO: store two end planes for each edge instead (the planes whose intersections with the infinite edge create the vertices) - /* - for (int p1 = 0; p1 < selfPlaneCount; p1++) - { - otherPlane = selfPlanes[p1]; - var distance = math.dot(otherPlane, newVertexw); - if (distance > kFatPlaneWidthEpsilon) - goto SkipEdge; - } - //*/ - - // The position this crossing is identified by. - var identityVertex = canonical.Canonicalize(newVertex, canonicalNodeOrder, CanonicalVertexSite.LoopSplit); - - // Check if the new vertex is identical to one of the edge vertices - if ((math.lengthsq(vertex0 - identityVertex) <= kSqrVertexEqualEpsilon && weldFilter.Allows(identityVertex, vertex0)) || - (math.lengthsq(vertex1 - identityVertex) <= kSqrVertexEqualEpsilon && weldFilter.Allows(identityVertex, vertex1))) - continue; - - var tempVertexIndex = hashedTreeSpaceVertices.AddNoResize(identityVertex, in weldFilter); - if ((foundVertices == 0 || tempVertexIndex != tempVertices[1]) && - vertexIndex0 != tempVertexIndex && - vertexIndex1 != tempVertexIndex) - { - tempVertices[foundVertices + 1] = tempVertexIndex; - foundVertices++; - - // It's impossible to have more than 2 intersections on a single edge when intersecting with a convex shape - if (foundVertices == 2) - break; - } - SkipEdge: - ; - } - - if (foundVertices > 0) - { - if (foundVertices == 2) - { - var tempVertexIndex0 = tempVertices[1]; - var tempVertexIndex1 = tempVertices[2]; - var tempVertex0 = hashedTreeSpaceVertices[tempVertexIndex0]; - var tempVertex1 = hashedTreeSpaceVertices[tempVertexIndex1]; - var dot0 = math.lengthsq(tempVertex0 - vertex1); - var dot1 = math.lengthsq(tempVertex1 - vertex1); - if (dot0 < dot1) - { - tempVertices[1] = tempVertexIndex1; - tempVertices[2] = tempVertexIndex0; - } - } - tempVertices[0] = vertexIndex0; - tempVertices[1 + foundVertices] = vertexIndex1; - for (int i = 1; i < 2 + foundVertices; i++) - { - if (tempVertices[i - 1] != tempVertices[i]) - edges.Add(new Edge() { index1 = (ushort)tempVertices[i - 1], index2 = (ushort)tempVertices[i] }); - } - } else - { - edges.Add(newSelfEdges[e]); - } - } - } - - public void FindBasePolygonPlaneIntersections([NoAlias, ReadOnly] ref BlobArray otherPlanes, - in WeldIncidenceFilter weldFilter, - //[NoAlias] ref BlobArray selfPlanes, - [NoAlias] ref UnsafeList selfEdges, - [NoAlias, ReadOnly] HashedVertices combinedVertices, - in CanonicalVertices canonical = default, - int canonicalNodeOrder = -1) - { - if (selfEdges.Length < 3) - return; - - var newSelfEdgesLength = selfEdges.Length; - - NativeArray newSelfEdges; - using var _newSelfEdges = newSelfEdges = new NativeArray(newSelfEdgesLength, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref newSelfEdges, newSelfEdgesLength); - - newSelfEdges.CopyFrom(selfEdges, 0, selfEdges.Length); - // Re-added with Add for the reason given in FindLoopPlaneIntersections: this runs once per other brush. - selfEdges.Clear(); - - int4 tempVertices = int4.zero; - - var otherPlaneCount = otherPlanes.Length; - //var selfPlaneCount = selfPlanes.Length; - - // At most two new vertices per edge; see FindLoopPlaneIntersections for why one per plane was not enough. - combinedVertices.ReserveAdditionalVertices(newSelfEdgesLength * 2); - - // TODO: Optimize the hell out of this - for (int e = 0; e < newSelfEdgesLength; e++) - { - var vertexIndex0 = newSelfEdges[e].index1; - var vertexIndex1 = newSelfEdges[e].index2; - - var vertex0 = combinedVertices[vertexIndex0]; - var vertex1 = combinedVertices[vertexIndex1]; - - var vertex0w = new float4(vertex0, 1); - var vertex1w = new float4(vertex1, 1); - - var foundVertices = 0; - - for (int p = 0; p < otherPlaneCount; p++) - { - var otherPlane = otherPlanes[p]; - - var distance0 = math.dot(otherPlane, vertex0w); - var distance1 = math.dot(otherPlane, vertex1w); - - if (distance0 < 0) - { - if (distance1 <= kFatPlaneWidthEpsilon || - distance0 >= -kFatPlaneWidthEpsilon) continue; - } else - { - if (distance1 >= -kFatPlaneWidthEpsilon || - distance0 <= kFatPlaneWidthEpsilon) continue; - } - - float3 newVertex; - - // Ensure we always do the intersection calculations in the exact same - // direction across a plane to increase floating point consistency - if (distance0 > 0) - { - var length = distance0 - distance1; - var delta = distance0 / length; - if (delta <= 0 || delta >= 1) - continue; - var vector = vertex0 - vertex1; - newVertex = vertex0 - (vector * delta); - } else - { - var length = distance1 - distance0; - var delta = distance1 / length; - if (delta <= 0 || delta >= 1) - continue; - var vector = vertex1 - vertex0; - newVertex = vertex1 - (vector * delta); - } - - // Existence first, with the computed point, as in FindLoopPlaneIntersections. - var newVertexw = new float4(newVertex, 1); - for (int p2 = 0; p2 < otherPlaneCount; p2++) - { - otherPlane = otherPlanes[p2]; - var distance = math.dot(otherPlane, newVertexw); - if (distance > kFatPlaneWidthEpsilon) - goto SkipEdge; - } - // TODO: store two end planes for each edge instead (the planes whose intersections with the infinite edge create the vertices) - /* - for (int p1 = 0; p1 < selfPlaneCount; p1++) - { - otherPlane = selfPlanes[p1]; - var distance = math.dot(otherPlane, newVertexw); - if (distance > kFatPlaneWidthEpsilon) - goto SkipEdge; - } - //*/ - - // Identified by its canonical position. - var identityVertex = canonical.Canonicalize(newVertex, canonicalNodeOrder, CanonicalVertexSite.BaseSplit); - - // Check if the new vertex is identical to one of our existing vertices - if ((math.lengthsq(vertex0 - identityVertex) <= kSqrVertexEqualEpsilon && weldFilter.Allows(identityVertex, vertex0)) || - (math.lengthsq(vertex1 - identityVertex) <= kSqrVertexEqualEpsilon && weldFilter.Allows(identityVertex, vertex1))) - continue; - - var tempVertexIndex = combinedVertices.AddNoResize(identityVertex, in weldFilter); - if ((foundVertices == 0 || tempVertexIndex != tempVertices[1]) && - vertexIndex0 != tempVertexIndex && - vertexIndex1 != tempVertexIndex) - { - tempVertices[foundVertices + 1] = tempVertexIndex; - foundVertices++; - - // It's impossible to have more than 2 intersections on a single edge when intersecting with a convex shape - if (foundVertices == 2) - break; - } - SkipEdge: - ; - } - - if (foundVertices > 0) - { - if (foundVertices == 2) - { - var tempVertexIndex0 = tempVertices[1]; - var tempVertexIndex1 = tempVertices[2]; - var tempVertex0 = combinedVertices[tempVertexIndex0]; - var tempVertex1 = combinedVertices[tempVertexIndex1]; - var dot0 = math.lengthsq(tempVertex0 - vertex1); - var dot1 = math.lengthsq(tempVertex1 - vertex1); - if (dot0 < dot1) - { - tempVertices[1] = tempVertexIndex1; - tempVertices[2] = tempVertexIndex0; - } - } - tempVertices[0] = vertexIndex0; - tempVertices[1 + foundVertices] = vertexIndex1; - for (int i = 1; i < 2 + foundVertices; i++) - { - if (tempVertices[i - 1] != tempVertices[i]) - selfEdges.Add(new Edge() { index1 = (ushort)tempVertices[i - 1], index2 = (ushort)tempVertices[i] }); - } - } else - { - selfEdges.Add(newSelfEdges[e]); - } - } - } - - - public void FindLoopVertexOverlaps([NoAlias, ReadOnly] ref BlobArray selfPlanes, - [NoAlias] ref UnsafeList selfEdges, - [NoAlias, ReadOnly] UnsafeList otherEdges, - [NoAlias, ReadOnly] HashedVertices combinedVertices) - { - if (selfEdges.Length < 3 || - otherEdges.Length < 3) - return; - - var otherVerticesLength = 0; - - NativeArray otherVertices; - using var _otherVertices = otherVertices = new (otherEdges.Length, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref otherVertices, otherEdges.Length); - - // TODO: use edges instead + 2 planes intersecting each edge - for (int v = 0; v < otherEdges.Length; v++) - { - var vertexIndex = otherEdges[v].index1; // <- assumes no gaps - for (int e = 0; e < selfEdges.Length; e++) - { - // If the exact edge already exists in other polygon, skip it - if (selfEdges[e].index1 == vertexIndex || - selfEdges[e].index2 == vertexIndex) - goto NextVertex; - } - - // TODO: store two end planes for each edge instead (the planes whose intersections with the infinite edge create the vertices) - //* - var vertex = combinedVertices[vertexIndex]; - for (int p = 0; p < selfPlanes.Length; p++) - { - var distance = math.dot(selfPlanes[p], new float4(vertex, 1)); - if (distance > kFatPlaneWidthEpsilon) - goto NextVertex; - } - //*/ - - // TODO: Check if vertex intersects at least 2 selfPlanes - otherVertices[otherVerticesLength] = vertexIndex; - otherVerticesLength++; - NextVertex: - ; - } - - if (otherVerticesLength == 0) - return; - - //NativeCollectionHelpers.EnsureCreatedAndClear(ref tempList); - - var tempListCapacity = (selfEdges.Length * 2) + otherVerticesLength; - //if (tempList.Capacity < tempListCapacity) - // tempList.Capacity = tempListCapacity; - - var inputEdgesLength = selfEdges.Length; - - NativeList tempList; - using var _tempList = tempList = new NativeList (tempListCapacity, Allocator.Temp); - - NativeArray newSelfEdges; - using var _newSelfEdges = newSelfEdges = new NativeArray(inputEdgesLength, Allocator.Temp); - //NativeCollectionHelpers.EnsureMinimumSize(ref newSelfEdges, inputEdgesLength); - - newSelfEdges.CopyFrom(selfEdges, 0, selfEdges.Length); - // Re-added with Add: every vertex of the other loop that lies on an edge adds one, which no capacity the - // caller reserved accounts for (see FindLoopPlaneIntersections). - selfEdges.Clear(); - - // TODO: Optimize the hell out of this - for (int e = 0; e < inputEdgesLength && otherVerticesLength > 0; e++) - { - var vertexIndex0 = newSelfEdges[e].index1; - var vertexIndex1 = newSelfEdges[e].index2; - - var vertex0 = combinedVertices[vertexIndex0]; - var vertex1 = combinedVertices[vertexIndex1]; - - tempList.Clear(); - - var delta = math.normalize(vertex1 - vertex0); - var max = math.dot(vertex1 - vertex0, delta); - for (int v1 = otherVerticesLength - 1; v1 >= 0; v1--) - { - var otherVertexIndex = otherVertices[v1]; - if (otherVertexIndex == vertexIndex0 || - otherVertexIndex == vertexIndex1) - continue; - var otherVertex = combinedVertices[otherVertexIndex]; - var dot = math.dot(otherVertex - vertex0, delta); - if (dot <= 0 || dot >= max) - continue; - // Both epsilons are compared against SQUARED distances (see IsPointOnLineSegment). - if (!MathExtensions.IsPointOnLineSegment(otherVertex, vertex0, vertex1, CSGConstants.kSqrVertexEqualEpsilon, CSGConstants.kSqrEdgeDistanceEpsilon)) - continue; - - // Note: the otherVertices array cannot contain any indices that are part in - // the input indices, since we checked for that when we created it. - tempList.AddNoResize(otherVertexIndex); - - // TODO: figure out why removing vertices fails? - //if (v1 != otherVerticesLength - 1 && otherVerticesLength > 0) - // otherVertices[v1] = otherVertices[otherVerticesLength - 1]; - //otherVerticesLength--; - } - - if (tempList.Length > 0) - { - float dot1, dot2; - for (int v1 = 0; v1 < tempList.Length - 1; v1++) - { - for (int v2 = v1 + 1; v2 < tempList.Length; v2++) - { - var otherVertexIndex1 = tempList[v1]; - var otherVertexIndex2 = tempList[v2]; - dot1 = math.dot(combinedVertices[otherVertexIndex1] - vertex0, delta); - dot2 = math.dot(combinedVertices[otherVertexIndex2] - vertex0, delta); - if (dot1 >= dot2) - { - tempList[v1] = otherVertexIndex2; - tempList[v2] = otherVertexIndex1; - } - } - } - if (vertexIndex0 != tempList[0]) - selfEdges.Add(new Edge { index1 = vertexIndex0, index2 = tempList[0] }); - for (int i = 1; i < tempList.Length; i++) - { - if (tempList[i - 1] != tempList[i]) - selfEdges.Add(new Edge { index1 = tempList[i - 1], index2 = tempList[i] }); - } - if (tempList[tempList.Length - 1] != vertexIndex1) - selfEdges.Add(new Edge { index1 = tempList[tempList.Length - 1], index2 = vertexIndex1 }); - } else - { - selfEdges.Add(newSelfEdges[e]); - } - } - } - - public static void RemoveDuplicates(ref UnsafeList edges) - { - if (edges.Length < 3) - { - edges.Clear(); - return; - } - - for (int e = edges.Length - 1; e >= 0; e--) - { - if (edges[e].index1 != edges[e].index2) - continue; - edges.RemoveAtSwapBack(e); - } - } - } -} diff --git a/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs.meta b/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs.meta deleted file mode 100644 index e5fb659f..00000000 --- a/Core/2.Processing/Jobs/FindLoopOverlapIntersectionJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 71dc194ba8ed8fc409d03c027d48e129 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/FindModifiedBrushesJob.cs b/Core/2.Processing/Jobs/FindModifiedBrushesJob.cs index 3e047306..9df8dcb3 100644 --- a/Core/2.Processing/Jobs/FindModifiedBrushesJob.cs +++ b/Core/2.Processing/Jobs/FindModifiedBrushesJob.cs @@ -18,7 +18,6 @@ unsafe struct FindModifiedBrushesJob : IJob [NoAlias, ReadOnly] public NativeList brushes; [NoAlias, ReadOnly] public int brushCount; [NoAlias, ReadOnly] public NativeList allTreeBrushIndexOrders; - [NoAlias, ReadOnly] public bool rebuildAll; // every brush, flagged or not // Read/Write [NoAlias] public NativeList rebuildTreeBrushIndexOrders; @@ -36,7 +35,7 @@ public void Execute() for (int nodeOrder = 0; nodeOrder < brushes.Length; nodeOrder++) { var brushCompactNodeID = brushes[nodeOrder]; - if (rebuildAll || compactHierarchy.IsAnyStatusFlagSet(brushCompactNodeID)) + if (compactHierarchy.IsAnyStatusFlagSet(brushCompactNodeID)) { var indexOrder = allTreeBrushIndexOrders[nodeOrder]; Debug.Assert(indexOrder.nodeOrder == nodeOrder); diff --git a/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs b/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs deleted file mode 100644 index 0bf60a8d..00000000 --- a/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct GatherOutputSurfacesJob : IJob - { - // Read / Write (Sort) - [NoAlias] public NativeList outputSurfaces; - - // Write - //[NoAlias, WriteOnly] public NativeReference> outputSurfaceLoops; - [NoAlias, WriteOnly] public NativeArray outputSurfacesRange; - - struct ListComparer : System.Collections.Generic.IComparer - { - public int Compare(BrushIntersectionLoop x, BrushIntersectionLoop y) - { - var orderX = x.indexOrder0.nodeOrder; - var orderY = y.indexOrder0.nodeOrder; - var diff = orderX.CompareTo(orderY); - if (diff != 0) - return diff; - orderX = x.indexOrder1.nodeOrder; - orderY = y.indexOrder1.nodeOrder; - return orderX.CompareTo(orderY); - } - } - - readonly static ListComparer listComparer = new ListComparer(); - - - public void Execute() - { - if (outputSurfaces.Length == 0) - return; - - outputSurfaces.Sort(listComparer); - - int previousOrder = outputSurfaces[0].indexOrder0.nodeOrder; - int2 range = new int2(0, 1); - for (int i = 1; i < outputSurfaces.Length; i++) - { - int currentOrder = outputSurfaces[i].indexOrder0.nodeOrder; - if (currentOrder != previousOrder) - { - //Debug.Log($"{previousOrder} {range}"); - outputSurfacesRange[previousOrder] = range; - previousOrder = currentOrder; - range.x = i; - range.y = 1; - } else - range.y++; - } - - //throw new Exception($"{previousOrder} {range.x} {range.y} / {brushBrushIntersections.Length}"); - outputSurfacesRange[previousOrder] = range; - } - } -} diff --git a/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs.meta b/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs.meta deleted file mode 100644 index 2a0d2feb..00000000 --- a/Core/2.Processing/Jobs/GatherOutputSurfacesJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a1c295f7a84fad34bb2fc69f493e92cc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs b/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs index 81644494..44988c0f 100644 --- a/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs +++ b/Core/2.Processing/Jobs/GenerateSurfaceTrianglesJob.cs @@ -6,47 +6,24 @@ using Debug = UnityEngine.Debug; using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; using Unity.Entities; -using andywiecko.BurstTriangulator.LowLevel.Unsafe; -using andywiecko.BurstTriangulator; namespace Chisel.Core { - // GenerateSurfaceTrianglesJob for the exact CSG's input (triangles already made), Burst compiled: the FIXME below is - // about the loops' triangulation, which this does not run [BurstCompile(FloatPrecision.High, FloatMode.Strict, CompileSynchronously = true)] - struct GenerateExactSurfaceTrianglesJob : IJobParallelForDefer - { - public GenerateSurfaceTrianglesJob job; - - public void Execute(int index) => job.ExecuteExact(index); - } - - //[BurstCompile(CompileSynchronously = true)] // FIXME: If enabled, it causes more missing triangles in the mesh struct GenerateSurfaceTrianglesJob : IJobParallelForDefer { // Read // 'Required' for scheduling with index count [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - // Gate the vertex re-add and the T-junction insert on plane incidence; set from CSGManager's kUseIncidenceWeld. - [NoAlias, ReadOnly] public bool useIncidenceWeld; - // Canonical vertices (see CanonicalVertices): from Everywhere on, only the same vertex is merged here. - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; [NoAlias, ReadOnly] public NativeList> basePolygonCache; [NoAlias, ReadOnly] public NativeList transformationCache; - [NativeDisableParallelForRestriction] - [NoAlias, ReadOnly] public NativeArray> loopVerticesLookup; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - [NoAlias, ReadOnly] public NativeStream.Reader input; [NoAlias, ReadOnly] public NativeArray meshQueries; [NoAlias, ReadOnly] public CompactHierarchyManagerInstance.ReadOnlyEntityIDLookup entityIDLookup; [NoAlias, ReadOnly] public bool subtractiveWorkflow; [NoAlias, ReadOnly] public float normalSmoothingAngle; - // Set when the input comes from ExactCSGJob (triangles already made) instead of PerformCSGJob (loops). - [NoAlias, ReadOnly] public bool exactInput; // Decals, lowest first. Empty when the tree has none. [NoAlias, ReadOnly] public NativeArray decalVolumes; // The surfaces decals are limited to (DecalVolume.targetStart/targetCount) @@ -61,164 +38,6 @@ struct GenerateSurfaceTrianglesJob : IJobParallelForDefer [NativeDisableParallelForRestriction] [NoAlias] public NativeList> brushRenderBufferCache; - [BurstDiscard] - public static void InvalidFinalCategory(CategoryIndex _interiorCategory) - { - Debug.Assert(false, $"Invalid final category {_interiorCategory}"); - } - - -#if UNITY_EDITOR - static readonly bool kLogTriangulationIssues = false; - -#endif - - static readonly bool kCleanupNonSimpleLoops = true; - - static readonly bool kCloseOpenChains2D = true; - - static readonly bool kInsertTJunctions = true; - - const float kTJunctionPlaneBand = 0.05f; - - static readonly bool kLogTJunctionInsertions = false; - - // Logs this brush's touch-graph neighbours (entityID:IntersectionType), so a brush whose vertex - // lands on this brush's edge can be checked against the touch graph for presence. - [BurstDiscard] - static void LogTouchedNeighbours(ulong entityID, ref BlobArray touched, - CompactHierarchyManagerInstance.ReadOnlyEntityIDLookup lookup) - { -#if UNITY_EDITOR - if (!kLogTJunctionInsertions) - return; - var sb = new System.Text.StringBuilder(160); - sb.Append($"TJTOUCH b{entityID}:"); - for (int i = 0; i < touched.Length; i++) - { - var id = lookup.SafeGetNodeEntityID(touched[i].nodeIndexOrder.compactNodeID); - sb.Append($" {id}:{touched[i].type}"); - } - Debug.LogWarning(sb.ToString()); -#endif - } - - static void LogClosestApproach(in UnsafeList edges, in NativeList candidates, - HashedVertices vertices, ref double minPerp) - { - for (int c = 0; c < candidates.Length; c++) - { - double3 w = candidates[c]; - for (int e = 0; e < edges.Length; e++) - { - double3 a = vertices[edges[e].index1]; - double3 ab = (double3)vertices[edges[e].index2] - a, aw = w - a; - double L2 = math.dot(ab, ab); - if (L2 <= 1e-12) - continue; - double t = math.dot(aw, ab) / L2; - if (t <= 0.0 || t >= 1.0) - continue; - double pp = math.length(math.cross(aw, ab)) / math.sqrt(L2); - if (pp < minPerp) minPerp = pp; - } - } - } - - [BurstDiscard] - static void LogTJunctionInsertions(ulong entityID, int touched, int withVerts, int planeCand, int inserted, double selfPlaneMax, double minPerp) - { -#if UNITY_EDITOR - if (!kLogTJunctionInsertions) - return; - var ci = System.Globalization.CultureInfo.InvariantCulture; - Debug.LogWarning($"TJINS b{entityID}: touched={touched} withVerts={withVerts} planeCand={planeCand} inserted={inserted} selfPlaneMax={selfPlaneMax.ToString("0.######", ci)} minPerp={minPerp.ToString("0.######", ci)}"); -#endif - } - - static readonly bool kLogBoundaryEdges = false; - - [BurstDiscard] - static unsafe void LogBoundaryEdges(ulong entityID, int surfaceIndex, int loopIndex, in UnsafeList edges, float3* verts) - { -#if UNITY_EDITOR - if (!kLogBoundaryEdges) - return; - var ci = System.Globalization.CultureInfo.InvariantCulture; - var sb = new System.Text.StringBuilder(256); - sb.Append($"BEDGE b{entityID} s{surfaceIndex} l{loopIndex}:"); - for (int e = 0; e < edges.Length; e++) - { - var a = verts[edges[e].index1]; - var b = verts[edges[e].index2]; - sb.Append($" {a.x.ToString("0.####", ci)},{a.y.ToString("0.####", ci)},{a.z.ToString("0.####", ci)}|{b.x.ToString("0.####", ci)},{b.y.ToString("0.####", ci)},{b.z.ToString("0.####", ci)}"); - } - sb.Append(" | I:"); - for (int e = 0; e < edges.Length; e++) - sb.Append($" {edges[e].index1}-{edges[e].index2}"); - Debug.LogWarning(sb.ToString()); -#endif - } - - enum SurfaceDropReason : byte - { - SurfaceHasNoLoops, // all loops on this surface were filtered out before triangulation - TooFewPointsAfterDedup, // < 3 unique points / edges left once duplicates were removed - DegenerateInput, // the 2D input collapsed to a line / point - TriangulatorError, // BurstTriangulator returned a non-OK Status (see decoded flags) - ZeroTrianglesReturned, // Status was OK but the triangulator produced no triangles - } - - [BurstDiscard] - static void LogSurfaceDrop(ulong entityID, int surfaceIndex, int loopIndex, - int pointCount, int edgeCount, SurfaceDropReason reason, Status status) - { -#if UNITY_EDITOR && DEBUG - if (!kLogTriangulationIssues) - return; - var detail = reason == SurfaceDropReason.TriangulatorError ? $", status={status} (0x{(int)status:X})" : ""; - // NOTE: edgeCount is roVerts.edgeIndices.Length, which is FLATTENED (2 ints per edge), so the - // real edge count is edgeCount/2. - Debug.LogWarning($"[Chisel.Triangulation] No geometry for brush {entityID}, surface {surfaceIndex}, " + - $"loop {loopIndex}: {reason}{detail} (points={pointCount}, edges={edgeCount})."); -#endif - } - -#if UNITY_EDITOR - static readonly bool kLogDropDetail = false; -#endif - - [BurstDiscard] - static void LogDropDetail(ulong entityID, int surfaceIndex, int loopIndex, SurfaceDropReason reason, - NativeArray positions2D, NativeArray edgeIndices) - { -#if UNITY_EDITOR - if (!kLogDropDetail) - return; - // InvariantCulture so decimals use '.' (not the OS locale's ',') and stay parseable - the - // only separators inside the line are then the ',' between x and y and spaces between points. - var ci = System.Globalization.CultureInfo.InvariantCulture; - var sb = new System.Text.StringBuilder(256); - sb.Append($"DROPDET b{entityID} s{surfaceIndex} l{loopIndex} {reason} pts={positions2D.Length} edges={edgeIndices.Length / 2} | P:"); - for (int i = 0; i < positions2D.Length; i++) - sb.Append($" {i}=({positions2D[i].x.ToString("0.####", ci)},{positions2D[i].y.ToString("0.####", ci)})"); - sb.Append(" | E:"); - for (int i = 0; i + 1 < edgeIndices.Length; i += 2) - sb.Append($" {edgeIndices[i]}-{edgeIndices[i + 1]}"); - Debug.LogWarning(sb.ToString()); -#endif - } - - [BurstDiscard] - static void SelfIntersectionDetected(int surf, int loopIdx) - { -#if UNITY_EDITOR && DEBUG - if (!kLogTriangulationIssues) - return; - Debug.LogWarning($"Self-intersection detected in surface {surf}, loop index {loopIdx}."); -#endif - } - struct CompareSortByBasePlaneIndex : System.Collections.Generic.IComparer { public readonly int Compare(ChiselQuerySurface x, ChiselQuerySurface y) @@ -231,560 +50,8 @@ public readonly int Compare(ChiselQuerySurface x, ChiselQuerySurface y) } readonly static CompareSortByBasePlaneIndex kCompareSortByBasePlaneIndex = new(); - - public unsafe void Execute(int index) - { - if (exactInput) - { - ExecuteExact(index); - return; - } - - var count = input.BeginForEachIndex(index); - if (count == 0) - return; - - // Read brush data - var brushIndexOrder = input.Read(); - var brushNodeOrder = brushIndexOrder.nodeOrder; - var vertexCount = input.Read(); - - if (vertexCount < 3) - return; - - // PerformCSGJob already welded this list; re-adding it may only merge what this brush's own faces allow. - var weldFilter = WeldIncidenceFilter.Disabled; - if (canonicalVertexStage >= CanonicalVertexStage.Everywhere) - weldFilter = WeldIncidenceFilter.SameVertexOnly; // canonical vertices: only the same vertex is merged - else - if (useIncidenceWeld && brushNodeOrder < brushTreeSpacePlaneCache.Length && - brushTreeSpacePlaneCache[brushNodeOrder].IsCreated && basePolygonCache[brushNodeOrder].IsCreated) - weldFilter = WeldIncidenceFilter.Create(ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes, - basePolygonCache[brushNodeOrder].Value.polygons.Length); - - HashedVertices brushVertices; - using var _brushVertices = brushVertices = new HashedVertices(vertexCount, Allocator.Temp); - for (int v = 0; v < vertexCount; v++) - { - var vertex = input.Read(); - brushVertices.AddNoResize(vertex, in weldFilter); - } - - // Read surface loops - var surfaceOuterCount = input.Read(); - NativeList> surfaceLoopIndices; - using var _surfaceLoopIndices = surfaceLoopIndices = new NativeList>(surfaceOuterCount, Allocator.Temp); - surfaceLoopIndices.Resize(surfaceOuterCount, NativeArrayOptions.ClearMemory); - try - { - for (int o = 0; o < surfaceOuterCount; o++) - { - var countInner = input.Read(); - if (countInner > 0) - { - var inner = new UnsafeList(countInner, Allocator.Temp); - for (int i = 0; i < countInner; i++) - { - inner.AddNoResize(input.Read()); - } - surfaceLoopIndices[o] = inner; - } - else - surfaceLoopIndices[o] = default; - } - - // Read loop infos and edges - var loopCount = input.Read(); - NativeArray surfaceLoopAllInfos; - using var _surfaceLoopAllInfos = surfaceLoopAllInfos = new NativeArray(loopCount, Allocator.Temp); - - NativeList> surfaceLoopAllEdges; - using var _surfaceLoopAllEdges = surfaceLoopAllEdges = new NativeList>(loopCount, Allocator.Temp); - surfaceLoopAllEdges.Resize(loopCount, NativeArrayOptions.ClearMemory); - - // Declared before the try so the finally can dispose it; populated by the cross-brush - // T-junction pre-pass below (default/uncreated when that pass is disabled or no-op). - NativeArray protectedVertices = default; - // Created only for a brush that decals may reach. Disposed explicitly, never through a copy: its lists - // reallocate as they grow. - DecalSurfaceBuilder decalBuilder = default; - try - { - for (int l = 0; l < loopCount; l++) - { - surfaceLoopAllInfos[l] = input.Read(); - var edgeCount = input.Read(); - if (edgeCount > 0) - { - var edges = new UnsafeList(edgeCount, Allocator.Temp); - for (int e = 0; e < edgeCount; e++) - { - edges.AddNoResize(input.Read()); - } - surfaceLoopAllEdges[l] = edges; - } - } - input.EndForEachIndex(); - - if (!basePolygonCache[brushNodeOrder].IsCreated) - return; - - ulong entityID = entityIDLookup.SafeGetNodeEntityID(brushIndexOrder.compactNodeID); - - // Compute maximum sizes - int maxLoops = 0, maxIndices = 0; - for (int s = 0; s < surfaceLoopIndices.Length; s++) - { - if (!surfaceLoopIndices[s].IsCreated) - continue; - var length = surfaceLoopIndices[s].Length; - maxIndices += length; - maxLoops = math.max(maxLoops, length); - } - - ref var baseSurfaces = ref basePolygonCache[brushNodeOrder].Value.surfaces; - var transform = transformationCache[brushNodeOrder]; - var treeToNode = transform.treeToNode; - var nodeToTreeInvTrans = math.transpose(treeToNode); - - protectedVertices = default; - if (kInsertTJunctions && - brushNodeOrder < brushesTouchedByBrushCache.Length && - brushesTouchedByBrushCache[brushNodeOrder].IsCreated) - { - var tjProtected = new NativeList(16, Allocator.Temp); - var planeCandidates = new NativeList(64, Allocator.Temp); - var candIdx = new NativeList(64, Allocator.Temp); - int dbgWithVerts = 0, dbgPlaneCand = 0, dbgInserted = 0; - double dbgSelfPlaneMax = 0, dbgMinPerp = double.PositiveInfinity; - - ref var touched = ref brushesTouchedByBrushCache[brushNodeOrder].Value.brushIntersections; - for (int i = 0; i < touched.Length; i++) - { - var oo = touched[i].nodeIndexOrder.nodeOrder; - if (oo != brushNodeOrder && oo >= 0 && oo < loopVerticesLookup.Length && - loopVerticesLookup[oo].IsCreated && loopVerticesLookup[oo].Length > 0) - dbgWithVerts++; - } - if (kLogTJunctionInsertions) - LogTouchedNeighbours(entityID, ref touched, entityIDLookup); - - var neighbourCandidates = new NativeList(64, Allocator.Temp); - { - float3 selfMin = brushVertices[0], selfMax = brushVertices[0]; - for (int v = 1; v < brushVertices.Length; v++) - { - var p = brushVertices[v]; - selfMin = math.min(selfMin, p); - selfMax = math.max(selfMax, p); - } - selfMin -= CSGConstants.kEdgeIntersectionEpsilon; - selfMax += CSGConstants.kEdgeIntersectionEpsilon; - - for (int i = 0; i < touched.Length; i++) - { - var otherOrder = touched[i].nodeIndexOrder.nodeOrder; - if (otherOrder == brushNodeOrder || - otherOrder < 0 || otherOrder >= loopVerticesLookup.Length) - continue; - var ov = loopVerticesLookup[otherOrder]; - if (!ov.IsCreated) continue; - for (int v = 0; v < ov.Length; v++) - { - var w = ov[v]; - if (math.any(w < selfMin) || math.any(w > selfMax)) - continue; - neighbourCandidates.Add(w); - } - } - } - - for (int surf = 0; surf < surfaceLoopIndices.Length && neighbourCandidates.Length > 0; surf++) - { - if (!surfaceLoopIndices[surf].IsCreated) continue; - var loopIndices = surfaceLoopIndices[surf]; - - var planeTree = math.mul(nodeToTreeInvTrans, baseSurfaces[surf].localPlane); - var planeLen = math.length(planeTree.xyz); - if (planeLen > 1e-9f) planeTree /= planeLen; - - planeCandidates.Clear(); - for (int c = 0; c < neighbourCandidates.Length; c++) - { - var w = neighbourCandidates[c]; - var d = CSGMath.SignedDistance(planeTree, w); - if (d < -kTJunctionPlaneBand || d > kTJunctionPlaneBand) - continue; - planeCandidates.Add(w); - } - dbgPlaneCand += planeCandidates.Length; - if (planeCandidates.Length == 0) continue; - - for (int l = 0; l < loopIndices.Length; l++) - { - var loopIdx = loopIndices[l]; - var edges = surfaceLoopAllEdges[loopIdx]; - if (!edges.IsCreated || edges.Length < 3) continue; - - if (kLogTJunctionInsertions) - { - var sa = brushVertices[edges[0].index1]; - var sd = math.abs(CSGMath.SignedDistance(planeTree, sa)); - if (sd > dbgSelfPlaneMax) dbgSelfPlaneMax = sd; - LogClosestApproach(in edges, in planeCandidates, brushVertices, ref dbgMinPerp); - } - - float3 loopMin = brushVertices[edges[0].index1], loopMax = loopMin; - for (int e = 0; e < edges.Length; e++) - { - var a = brushVertices[edges[e].index1]; - var b = brushVertices[edges[e].index2]; - loopMin = math.min(loopMin, math.min(a, b)); - loopMax = math.max(loopMax, math.max(a, b)); - } - loopMin -= CSGConstants.kEdgeIntersectionEpsilon; - loopMax += CSGConstants.kEdgeIntersectionEpsilon; - - bool reserved = false; - candIdx.Clear(); - for (int c = 0; c < planeCandidates.Length; c++) - { - var w = planeCandidates[c]; - if (math.any(w < loopMin) || math.any(w > loopMax)) - continue; - bool onAnEdge = false; - for (int e = 0; e < edges.Length; e++) - { - var a = brushVertices[edges[e].index1]; - var b = brushVertices[edges[e].index2]; - if (MathExtensions.IsPointOnLineSegmentButNotOnVertex(w, a, b, CSGConstants.kSqrEdgeDistanceEpsilon)) - { onAnEdge = true; break; } - } - if (!onAnEdge) continue; - if (!reserved) - { - // Only reserve once we know this loop actually inserts something - - // most loops insert nothing and would otherwise pay for the growth. - brushVertices.ReserveAdditionalVertices(planeCandidates.Length - c); - reserved = true; - } - var idx = brushVertices.AddNoResize(w, in weldFilter); - candIdx.Add(idx); - tjProtected.Add(idx); - } - if (candIdx.Length == 0) continue; - dbgInserted += candIdx.Length; - - var posArr = brushVertices.AsArray(); - var candArr = candIdx.AsArray(); - LoopEdgeSplitter.SplitEdgesAtVertices(ref edges, in posArr, in candArr, candArr.Length, CSGConstants.kSqrEdgeDistanceEpsilon); - surfaceLoopAllEdges[loopIdx] = edges; - } - } - - if (tjProtected.Length > 0) - { - protectedVertices = new NativeArray(brushVertices.Length, Allocator.Temp); - for (int i = 0; i < tjProtected.Length; i++) - { - var pi = tjProtected[i]; - if (pi >= 0 && pi < protectedVertices.Length) - protectedVertices[pi] = true; - } - } - - if (kLogTJunctionInsertions) - LogTJunctionInsertions(entityID, touched.Length, dbgWithVerts, dbgPlaneCand, dbgInserted, - dbgSelfPlaneMax, double.IsInfinity(dbgMinPerp) ? -1.0 : dbgMinPerp); - - tjProtected.Dispose(); - planeCandidates.Dispose(); - neighbourCandidates.Dispose(); - candIdx.Dispose(); - } - - // Decals: the ones that may reach each surface. Each gets a slot after the brush's own surfaces, - // which stays empty when the decal draws nothing there. - var baseSurfaceCount = surfaceLoopIndices.Length; - NativeList decalCandidates; - using var _decalCandidates = decalCandidates = new NativeList(16, Allocator.Temp); - NativeList decalTargeted; - using var _decalTargeted = decalTargeted = new NativeList(16, Allocator.Temp); - NativeArray decalCandidateStart; - using var _decalCandidateStart = decalCandidateStart = new NativeArray(baseSurfaceCount + 1, Allocator.Temp); - BoundsOf(brushVertices.AsArray(), out var brushMin, out var brushMax); - FindDecalCandidates(ref baseSurfaces, nodeToTreeInvTrans, brushVertices.Length > 0, brushMin, brushMax, entityID, decalCandidates, decalTargeted, decalCandidateStart); - if (decalCandidates.Length > 0) - decalBuilder = DecalSurfaceBuilder.Create(Allocator.Temp); - - // Scratch allocators - Vertex2DRemapper vertex2DRemapper; - using var _vertex2DRemapper = vertex2DRemapper = new() - { - lookup = new NativeList(64, Allocator.Temp), - positions2D = new NativeList(64, Allocator.Temp), - edgeIndices = new NativeList(128, Allocator.Temp), - collapseDegree = Vertex2DRemapper.AllocateCollapseScratch(brushVertices.Length), - collapseNeighbourA = Vertex2DRemapper.AllocateCollapseScratch(brushVertices.Length), - collapseNeighbourB = Vertex2DRemapper.AllocateCollapseScratch(brushVertices.Length) - }; - - UniqueVertexMapper uniqueVertexMapper; - using var _uniqueVertexMapper = uniqueVertexMapper = new() - { - indexRemap = new NativeArray(brushVertices.Length, Allocator.Temp), - surfaceColliderVertices = new NativeList(brushVertices.Length, Allocator.Temp), - surfaceSelectVertices = new NativeList(brushVertices.Length, Allocator.Temp), - surfaceRenderVertices = new NativeList(brushVertices.Length, Allocator.Temp) - }; - - Args settings = Args.Default( - autoHolesAndBoundary: true, - concentricShellsParameter: 0.001f, - preprocessor: Preprocessor.None, - refineMesh: false, - restoreBoundary: true, - sloanMaxIters: 1_000_000, - validateInput: true, - verbose: false, // we report failures ourselves via LogSurfaceDrop; verbose:true double-logs every failure as a red error - refinementThresholdAngle: math.radians(5), - refinementThresholdArea: 1f - ); - - NativeList surfaceIndexList; - using var _surfaceIndexList = surfaceIndexList = new NativeList(maxIndices, Allocator.Temp); - - NativeList loops; - using var _loops = loops = new NativeList(maxLoops, Allocator.Temp); - - NativeList triangles; - using var _triangles = triangles = new NativeList(64, Allocator.Temp); - - NativeReference status; - using var _status = status = new NativeReference(Allocator.Temp); - - var output = new andywiecko.BurstTriangulator.LowLevel.Unsafe.NativeOutputData() - { - Triangles = triangles, - Status = status - }; - - using var builder = new BlobBuilder(Allocator.Temp, 4096); - - ref var root = ref builder.ConstructRoot(); - var layout = new SlotLayout { baseSurfaceCount = baseSurfaceCount, candidateCount = decalCandidates.Length, - backFaces = AnyBackFaces(ref baseSurfaces, baseSurfaceCount) }; - var surfaceBuffers = builder.Allocate(ref root.surfaces, layout.Count); - - var triangulator = new UnsafeTriangulator(); - for (int surf = 0; surf < surfaceLoopIndices.Length; surf++) - { - if (!surfaceLoopIndices[surf].IsCreated) continue; - loops.Clear(); uniqueVertexMapper.Reset(); - - // Collect valid loops - var loopIndices = surfaceLoopIndices[surf]; - for (int l = 0; l < loopIndices.Length; l++) - { - var loopIdx = loopIndices[l]; - var edges = surfaceLoopAllEdges[loopIdx]; - if (edges.Length < 3) continue; - loops.AddNoResize(loopIdx); - } - if (loops.Length == 0) - { - LogSurfaceDrop(entityID, surf, -1, 0, 0, SurfaceDropReason.SurfaceHasNoLoops, Status.OK); - continue; - } - - // We need to convert our UV matrix from tree-space, to brush local-space, to plane-space - // since the vertices of the polygons, at this point, are in tree-space. - var plane = baseSurfaces[surf].localPlane; - var localToPlane = MathExtensions.GenerateLocalToPlaneSpaceMatrix(plane); - var treeToPlane = math.mul(localToPlane, treeToNode); - var planeNormalMap = math.mul(nodeToTreeInvTrans, plane); - // uvs relative to the world instead of the object (as SurfaceFrame) - if (baseSurfaces[surf].textureInWorld) - treeToPlane = MathExtensions.GenerateLocalToPlaneSpaceMatrix(planeNormalMap / math.length(planeNormalMap.xyz)); - var map3DTo2D = new Map3DTo2D(planeNormalMap.xyz); - - // Normal flip logic preparation - float3 finalFaceNormal = map3DTo2D.normal; - if (subtractiveWorkflow) - { - // Flip the normal direction so lighting is correct for the "inside" - finalFaceNormal = -finalFaceNormal; - } - - surfaceIndexList.Clear(); - for (int li = 0; li < loops.Length; li++) - { - var loopIdx = loops[li]; - var edges = surfaceLoopAllEdges[loopIdx]; - var info = surfaceLoopAllInfos[loopIdx]; - Debug.Assert(surf == info.basePlaneIndex, "surfaceIndex != loopInfo.basePlaneIndex"); - - if (kLogBoundaryEdges) - LogBoundaryEdges(entityID, surf, loopIdx, in edges, brushVertices.m_Vertices->Ptr); - - vertex2DRemapper.ConvertToPlaneSpace(*brushVertices.m_Vertices, edges, map3DTo2D, protectedVertices); - - if (kCloseOpenChains2D) - vertex2DRemapper.RepairBoundary(); - - if (vertex2DRemapper.CheckForSelfIntersections()) - { -#if UNITY_EDITOR && DEBUG - if (kLogTriangulationIssues) - { - SelfIntersectionDetected(surf, loopIdx); - } -#endif - vertex2DRemapper.RemoveSelfIntersectingEdges(); - } - - var roVerts = vertex2DRemapper.AsReadOnly(); - - // Pre-check: need enough points and edges - if (roVerts.positions2D.Length < 3 || roVerts.edgeIndices.Length < 3) - { - LogSurfaceDrop(entityID, surf, loopIdx, roVerts.positions2D.Length, roVerts.edgeIndices.Length, SurfaceDropReason.TooFewPointsAfterDedup, Status.OK); - continue; - } - - // Check for degenerate edges - if (IsDegenerate(roVerts.positions2D)) - { - LogSurfaceDrop(entityID, surf, loopIdx, roVerts.positions2D.Length, roVerts.edgeIndices.Length, SurfaceDropReason.DegenerateInput, Status.OK); - continue; - } - - try - { - output.Triangles.Clear(); - triangulator.Triangulate( - new andywiecko.BurstTriangulator.LowLevel.Unsafe.NativeInputData - { - Positions = roVerts.positions2D, - ConstraintEdges = roVerts.edgeIndices - }, - output, - settings, - Allocator.Temp); - - // Report (editor, while Burst is off) exactly why a surface produced no geometry. - if (output.Status.Value != Status.OK) - { - LogSurfaceDrop(entityID, surf, loopIdx, roVerts.positions2D.Length, roVerts.edgeIndices.Length, SurfaceDropReason.TriangulatorError, output.Status.Value); - LogDropDetail(entityID, surf, loopIdx, SurfaceDropReason.TriangulatorError, roVerts.positions2D, roVerts.edgeIndices); - continue; - } - if (output.Triangles.Length == 0) - { - bool recovered = false; - if (kCleanupNonSimpleLoops) - { - var cleaned = new UnsafeList(edges.Length, Allocator.Temp); - cleaned.AddRange(edges); - LoopEdgeSplitter.RemoveAntiparallelEdgePairs(ref cleaned); - if (cleaned.Length != edges.Length && cleaned.Length >= 3) - { - vertex2DRemapper.ConvertToPlaneSpace(*brushVertices.m_Vertices, cleaned, map3DTo2D, protectedVertices); - if (kCloseOpenChains2D) - vertex2DRemapper.RepairBoundary(); - if (vertex2DRemapper.CheckForSelfIntersections()) - vertex2DRemapper.RemoveSelfIntersectingEdges(); - roVerts = vertex2DRemapper.AsReadOnly(); - if (roVerts.positions2D.Length >= 3 && roVerts.edgeIndices.Length >= 3 && !IsDegenerate(roVerts.positions2D)) - { - output.Triangles.Clear(); - triangulator.Triangulate( - new andywiecko.BurstTriangulator.LowLevel.Unsafe.NativeInputData - { - Positions = roVerts.positions2D, - ConstraintEdges = roVerts.edgeIndices - }, - output, settings, Allocator.Temp); - recovered = output.Status.Value == Status.OK && output.Triangles.Length > 0; - } - } - cleaned.Dispose(); - } - if (!recovered) - { - LogSurfaceDrop(entityID, surf, loopIdx, roVerts.positions2D.Length, roVerts.edgeIndices.Length, SurfaceDropReason.ZeroTrianglesReturned, Status.OK); - LogDropDetail(entityID, surf, loopIdx, SurfaceDropReason.ZeroTrianglesReturned, roVerts.positions2D, roVerts.edgeIndices); - continue; - } - } - - // Winding order flip for subtractive - if (subtractiveWorkflow) - { - // Flip winding order (0,1,2 -> 0,2,1) to face inwards - for (int ti = 0; ti < output.Triangles.Length; ti += 3) - { - (output.Triangles[ti + 1], output.Triangles[ti + 2]) = - (output.Triangles[ti + 2], output.Triangles[ti + 1]); - } - } - - // Map triangles back - var prevCount = surfaceIndexList.Length; - var interiorCat = (CategoryIndex)info.interiorCategory; - roVerts.RemapTriangles(interiorCat, output.Triangles, surfaceIndexList); - - // Register vertices (Pass calculated/flipped normal) - uniqueVertexMapper.RegisterVertices( - surfaceIndexList, - prevCount, - *brushVertices.m_Vertices, - finalFaceNormal, - entityID, - interiorCat); - } - catch (System.Exception ex) { Debug.LogException(ex); } - } - - if (surfaceIndexList.Length == 0) continue; - FinishSurface(surf, brushNodeOrder, entityID, ref baseSurfaces, treeToPlane, transform.nodeToTree, finalFaceNormal, surfaceIndexList, ref uniqueVertexMapper, - builder, surfaceBuffers, ref decalBuilder, decalCandidates, decalTargeted, decalCandidateStart, layout); - } - - FinishBrush(brushIndexOrder, brushNodeOrder, builder, ref root, surfaceBuffers, decalCandidateStart, layout); - } - finally - { - if (protectedVertices.IsCreated) - protectedVertices.Dispose(); - decalBuilder.Dispose(); - - if (surfaceLoopAllEdges.IsCreated) - { - for (int i = 0; i < surfaceLoopAllEdges.Length; i++) - { - if (surfaceLoopAllEdges[i].IsCreated) - surfaceLoopAllEdges[i].Dispose(); - surfaceLoopAllEdges[i] = default; - } - } - } - } - finally - { - if (surfaceLoopIndices.IsCreated) - { - for (int i = 0; i < surfaceLoopIndices.Length; i++) - { - if (surfaceLoopIndices[i].IsCreated) - surfaceLoopIndices[i].Dispose(); - surfaceLoopIndices[i] = default; - } - } - } - } // After a surface's triangles have been registered: normal smoothing, UVs, tangents, lightmap coordinates, the decals on - // it and its render buffer. Shared by both inputs. + // it and its render buffer. unsafe void FinishSurface(int surf, int brushNodeOrder, ulong entityID, ref BlobArray baseSurfaces, float4x4 treeToPlane, float4x4 nodeToTree, float3 finalFaceNormal, NativeList surfaceIndexList, ref UniqueVertexMapper uniqueVertexMapper, BlobBuilder builder, BlobBuilderArray surfaceBuffers, @@ -1244,7 +511,7 @@ void SurfaceFrame(ref BlobArray baseSurfaces, int surf, float4x4 tr finalFaceNormal = -finalFaceNormal; // lighting for the "inside" } - internal unsafe void ExecuteExact(int index) + public unsafe void Execute(int index) { var count = input.BeginForEachIndex(index); if (count == 0) @@ -1441,25 +708,5 @@ unsafe void FindDecalCandidates(ref BlobArray baseSurfaces, float4x } start[surfaceCount] = candidates.Length; } - - static bool IsDegenerate(NativeArray verts) - { - if (verts.Length < 3) - return true; - - double2 min = verts[0]; - double2 max = verts[0]; - - for (int i = 1; i < verts.Length; i++) - { - var v = verts[i]; - min = math.min(min, v); - max = math.max(max, v); - } - - // A zero-area axis-aligned box means every point sits on a line - return math.abs(max.x - min.x) <= double.Epsilon || - math.abs(max.y - min.y) <= double.Epsilon; - } } } diff --git a/Core/2.Processing/Jobs/InternBrushPlanesJob.cs b/Core/2.Processing/Jobs/InternBrushPlanesJob.cs deleted file mode 100644 index baf7ef45..00000000 --- a/Core/2.Processing/Jobs/InternBrushPlanesJob.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System.Runtime.CompilerServices; -using Unity.Burst; -using Unity.Collections; -using Unity.Entities; -using Unity.Jobs; -using Unity.Mathematics; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct InternBrushPlanesJob : IJob - { - // Read - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; - [NoAlias, ReadOnly] public NativeList transformationCache; - - // Write - [NoAlias] public InternedPlanes internedPlanes; - [NoAlias] public NativeList brushPlaneIds; - [NoAlias] public NativeArray brushPlaneIdRange; // by nodeOrder: (offset, count) - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int Encode(int id, bool flipped) { return flipped ? -(id + 1) : (id + 1); } - - public void Execute() - { - internedPlanes.Clear(); - brushPlaneIds.Clear(); - - var facePoints = new NativeList(64, Allocator.Temp); - try - { - for (int index = 0; index < allUpdateBrushIndexOrders.Length; index++) - { - var brushIndexOrder = allUpdateBrushIndexOrders[index]; - int brushNodeOrder = brushIndexOrder.nodeOrder; - - var brushMeshBlob = brushMeshLookup[brushNodeOrder]; - if (!brushMeshBlob.IsCreated) - { - brushPlaneIdRange[brushNodeOrder] = new int2(brushPlaneIds.Length, 0); - continue; - } - - ref var mesh = ref brushMeshBlob.Value; - ref var polygons = ref mesh.polygons; - ref var halfEdges = ref mesh.halfEdges; - ref var localVertices = ref mesh.localVertices; - ref var localPlanes = ref mesh.localPlanes; - var nodeToTree = transformationCache[brushNodeOrder].nodeToTree; - var nodeToTreeInverseTransposed = math.transpose(math.inverse(nodeToTree)); - - var offset = brushPlaneIds.Length; - for (int p = 0; p < polygons.Length; p++) - { - if (p >= localPlanes.Length) - { - brushPlaneIds.Add(0); - continue; - } - - ref var polygon = ref polygons[p]; - facePoints.Clear(); - for (int e = 0; e < polygon.edgeCount; e++) - { - var vertexIndex = halfEdges[polygon.firstEdge + e].vertexIndex; - if (vertexIndex < 0 || vertexIndex >= localVertices.Length) - continue; - facePoints.Add(math.mul(nodeToTree, new float4(localVertices[vertexIndex], 1)).xyz); - } - if (facePoints.Length == 0) - { - brushPlaneIds.Add(0); - continue; - } - - var treePlane = math.mul(nodeToTreeInverseTransposed, localPlanes[p]); - var length = math.length(treePlane.xyz); - if (length <= (float)CSGConstants.kDivideMinimumEpsilon) - { - brushPlaneIds.Add(0); - continue; - } - treePlane /= length; - - var id = internedPlanes.Add(treePlane, facePoints.AsArray(), out bool flipped); - brushPlaneIds.Add(Encode(id, flipped)); - } - brushPlaneIdRange[brushNodeOrder] = new int2(offset, brushPlaneIds.Length - offset); - } - } - finally { facePoints.Dispose(); } - } - } -} diff --git a/Core/2.Processing/Jobs/InternBrushPlanesJob.cs.meta b/Core/2.Processing/Jobs/InternBrushPlanesJob.cs.meta deleted file mode 100644 index 311004a8..00000000 --- a/Core/2.Processing/Jobs/InternBrushPlanesJob.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b2234588c1fa93b468fbee2cfb23c569 \ No newline at end of file diff --git a/Core/2.Processing/Jobs/JobData/BrushesTouchedByBrush.cs b/Core/2.Processing/Jobs/JobData/BrushesTouchedByBrush.cs index 04be835e..e458e876 100644 --- a/Core/2.Processing/Jobs/JobData/BrushesTouchedByBrush.cs +++ b/Core/2.Processing/Jobs/JobData/BrushesTouchedByBrush.cs @@ -81,75 +81,6 @@ public int GetHashCode(BrushPair obj) #endregion } - struct BrushPair2 : System.IEquatable, - System.IComparable, - System.Collections.Generic.IEqualityComparer, - System.Collections.Generic.IComparer - { - public IndexOrder brushIndexOrder0; - public IndexOrder brushIndexOrder1; - public IntersectionType type; - - public void Flip() - { - if (type == IntersectionType.AInsideB) type = IntersectionType.BInsideA; - else if (type == IntersectionType.BInsideA) type = IntersectionType.AInsideB; - { var t = brushIndexOrder0; brushIndexOrder0 = brushIndexOrder1; brushIndexOrder1 = t; } - } - - #region Equals - public override bool Equals(object obj) - { - if (obj == null || !(obj is BrushPair2)) - return false; - - var other = (BrushPair2)obj; - return Equals(other); - } - - public bool Equals(BrushPair2 x, BrushPair2 y) { return x.Equals(y); } - - public bool Equals(BrushPair2 other) - { - return ((brushIndexOrder0.nodeOrder == other.brushIndexOrder0.nodeOrder) && - (brushIndexOrder1.nodeOrder == other.brushIndexOrder1.nodeOrder)); - } - #endregion - - #region Compare - public int Compare(BrushPair2 x, BrushPair2 y) { return x.CompareTo(y); } - - public int CompareTo(BrushPair2 other) - { - if (brushIndexOrder0.nodeOrder < other.brushIndexOrder0.nodeOrder) - return -1; - if (brushIndexOrder0.nodeOrder > other.brushIndexOrder0.nodeOrder) - return 1; - if (brushIndexOrder1.nodeOrder < other.brushIndexOrder1.nodeOrder) - return -1; - if (brushIndexOrder1.nodeOrder > other.brushIndexOrder1.nodeOrder) - return 1; - if (type < other.type) - return -1; - if (type > other.type) - return 1; - return 0; - } - #endregion - - #region GetHashCode - public override int GetHashCode() - { - return GetHashCode(this); - } - - public int GetHashCode(BrushPair2 obj) - { - return ((ulong)obj.brushIndexOrder0.nodeOrder + ((ulong)obj.brushIndexOrder1.nodeOrder << 32)).GetHashCode(); - } - #endregion - } - enum IntersectionType : byte { NoIntersection, @@ -174,15 +105,6 @@ struct IndexSurfaceInfo public ushort interiorCategory; } - struct BrushIntersectionLoop - { - public IndexOrder indexOrder0; - public IndexOrder indexOrder1; - public SurfaceInfo surfaceInfo; - public int loopVertexIndex; - public int loopVertexCount; - } - struct BrushesTouchedByBrush { public BlobArray brushIntersections; diff --git a/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs b/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs deleted file mode 100644 index 5a01b052..00000000 --- a/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using Unity.Jobs; -using Unity.Mathematics; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; - -namespace Chisel.Core -{ - - [BurstCompile(CompileSynchronously = true)] - struct SeedLoopVerticesFromCacheJob : IJob - { - // Read - [NoAlias, ReadOnly] public NativeList allTreeBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList> loopVerticesCache; - public Allocator allocator; - - // Write (only the entries of brushes outside the update; the update's own entries are created - // by FindLoopOverlapIntersectionsJob) - [NoAlias] public NativeArray> loopVerticesLookup; - - public void Execute() - { - var brushCount = loopVerticesLookup.Length; - if (brushCount == 0) - return; - - using var updated = new NativeBitArray(brushCount, Allocator.Temp, NativeArrayOptions.ClearMemory); - for (int i = 0; i < allUpdateBrushIndexOrders.Length; i++) - { - var nodeOrder = allUpdateBrushIndexOrders[i].nodeOrder; - if (nodeOrder >= 0 && nodeOrder < brushCount) - updated.Set(nodeOrder, true); - } - - for (int i = 0; i < allTreeBrushIndexOrders.Length; i++) - { - var nodeOrder = allTreeBrushIndexOrders[i].nodeOrder; - if (nodeOrder < 0 || nodeOrder >= brushCount || nodeOrder >= loopVerticesCache.Length || - updated.IsSet(nodeOrder)) - continue; - - var cached = loopVerticesCache[nodeOrder]; - if (!cached.IsCreated || cached.Length == 0) - continue; - - var copy = new UnsafeList(cached.Length, allocator); - copy.AddRange(cached); - loopVerticesLookup[nodeOrder] = copy; - } - } - } - - [BurstCompile(CompileSynchronously = true)] - struct StoreLoopVerticesJob : IJob - { - // Read - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList allTreeBrushIndexOrders; - [NoAlias, ReadOnly] public NativeArray> loopVerticesLookup; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - [NoAlias, ReadOnly] public NativeList brushTreeSpaceBounds; - [NoAlias, ReadOnly] public NativeArray mergeBrushState; - [NoAlias, ReadOnly] public int lastMergeIteration; - - // Read / Write - [NoAlias] public NativeList> loopVerticesCache; - - // Write - [NoAlias] public NativeList staleBrushes; - [NoAlias] public NativeArray stats; - public static int kStatsCount(int mergeIterations) { return mergeIterations + 4; } - - static bool SameVertices(in UnsafeList a, in UnsafeList b) - { - var lengthA = a.IsCreated ? a.Length : 0; - var lengthB = b.IsCreated ? b.Length : 0; - if (lengthA != lengthB) - return false; - for (int i = 0; i < lengthA; i++) - { - if (!math.all(a[i] == b[i])) - return false; - } - return true; - } - - // Vertices in `from` that are not in `to` (exact positions; order and duplicates are irrelevant - // to the neighbours, which consume these lists as sets). - static void AddMissing(in UnsafeList from, in UnsafeList to, ref NativeList result) - { - var fromLength = from.IsCreated ? from.Length : 0; - if (fromLength == 0) - return; - var toLength = to.IsCreated ? to.Length : 0; - using var lookup = new NativeHashSet(math.max(1, toLength), Allocator.Temp); - for (int i = 0; i < toLength; i++) - lookup.Add(to[i]); - for (int i = 0; i < fromLength; i++) - { - var vertex = from[i]; - if (!lookup.Contains(vertex)) - result.Add(vertex); - } - } - - public void Execute() - { - staleBrushes.Clear(); - for (int i = 0; i < stats.Length; i++) - stats[i] = 0; - var brushCount = loopVerticesLookup.Length; - if (brushCount == 0) - return; - for (int p = 0; p <= lastMergeIteration; p++) - { - var changedInPass = 0; - for (int i = 0; i < allUpdateBrushIndexOrders.Length; i++) - { - var nodeOrder = allUpdateBrushIndexOrders[i].nodeOrder; - if (nodeOrder >= 0 && nodeOrder < brushCount && - (mergeBrushState[(p * brushCount) + nodeOrder] & MergeTouchingBrushVerticesIndirectJob.kChanged) != 0) - changedInPass++; - } - stats[p] = changedInPass; - } - var statChangedBrushes = lastMergeIteration + 1; - var statChangedVertices = lastMergeIteration + 2; - var statMarksByChange = lastMergeIteration + 3; - var statMarksByMoving = lastMergeIteration + 4; - - var influence = 2.0f * math.max(HashedVertices.kCellSize, CSGConstants.kEdgeIntersectionEpsilon); - - using var updated = new NativeBitArray(brushCount, Allocator.Temp, NativeArrayOptions.ClearMemory); - using var marked = new NativeBitArray(brushCount, Allocator.Temp, NativeArrayOptions.ClearMemory); - for (int i = 0; i < allUpdateBrushIndexOrders.Length; i++) - { - var nodeOrder = allUpdateBrushIndexOrders[i].nodeOrder; - if (nodeOrder >= 0 && nodeOrder < brushCount) - updated.Set(nodeOrder, true); - } - - var changedVertices = new NativeList(64, Allocator.Temp); - for (int i = 0; i < allUpdateBrushIndexOrders.Length; i++) - { - var nodeOrder = allUpdateBrushIndexOrders[i].nodeOrder; - if (nodeOrder < 0 || nodeOrder >= brushCount || nodeOrder >= loopVerticesCache.Length) - continue; - - var current = loopVerticesLookup[nodeOrder]; - var cached = loopVerticesCache[nodeOrder]; - var stillMoving = (mergeBrushState[(lastMergeIteration * brushCount) + nodeOrder] & - MergeTouchingBrushVerticesIndirectJob.kChanged) != 0; - - changedVertices.Clear(); - if (!SameVertices(current, cached)) - { - AddMissing(current, cached, ref changedVertices); - AddMissing(cached, current, ref changedVertices); - stats[statChangedBrushes]++; - stats[statChangedVertices] += changedVertices.Length; - - if (cached.IsCreated) - cached.Dispose(); - var stored = default(UnsafeList); - if (current.IsCreated && current.Length > 0) - { - stored = new UnsafeList(current.Length, Allocator.Persistent); - stored.AddRange(current); - } - loopVerticesCache[nodeOrder] = stored; - } - if (changedVertices.Length == 0 && !stillMoving) - continue; - - var touched = brushesTouchedByBrushCache[nodeOrder]; - if (!touched.IsCreated) - continue; - - ref var brushIntersections = ref touched.Value.brushIntersections; - for (int t = 0; t < brushIntersections.Length; t++) - { - var otherOrder = brushIntersections[t].nodeIndexOrder.nodeOrder; - if (otherOrder < 0 || otherOrder >= brushCount || otherOrder == nodeOrder) - continue; - // A neighbour inside this update already welded against / split at this brush's - // vertices this round - unless those vertices were still moving in the last pass. - if (updated.IsSet(otherOrder) && !stillMoving) - continue; - if (marked.IsSet(otherOrder)) - continue; - - if (!stillMoving) - { - // Only a neighbour with a changed vertex within reach of its own vertices can be - // affected; its loop vertices lie inside its tree-space bounds. - var bounds = brushTreeSpaceBounds[otherOrder]; - var min = bounds.Min - influence; - var max = bounds.Max + influence; - var affected = false; - for (int c = 0; c < changedVertices.Length && !affected; c++) - { - var vertex = changedVertices[c]; - affected = !math.any(vertex < min) && !math.any(vertex > max); - } - if (!affected) - continue; - } - - marked.Set(otherOrder, true); - staleBrushes.Add(allTreeBrushIndexOrders[otherOrder].compactNodeID); - stats[stillMoving ? statMarksByMoving : statMarksByChange]++; - } - } - changedVertices.Dispose(); - } - } -} diff --git a/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs.meta b/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs.meta deleted file mode 100644 index 016d7ea7..00000000 --- a/Core/2.Processing/Jobs/LoopVerticesCacheJobs.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 94d8d04afffa8ad4fb156b7e641b8220 \ No newline at end of file diff --git a/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs b/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs deleted file mode 100644 index 706101c9..00000000 --- a/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using Unity.Jobs; -using Unity.Mathematics; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; - -namespace Chisel.Core -{/* - [BurstCompile(CompileSynchronously = true)] - struct MergeTouchingBrushVerticesJob : IJobParallelForDefer - { - // Read - [NoAlias, ReadOnly] public NativeArray treeBrushIndexOrders; - [NoAlias, ReadOnly] public NativeArray> brushesTouchedByBrushes; - - // Read/Write - [NativeDisableParallelForRestriction] - [NoAlias] public NativeArray> treeSpaceVerticesArray; - - // Per thread scratch memory - //[NativeDisableContainerSafetyRestriction] HashedVertices mergeVertices; - - public void Execute(int b) - { - var brushIndexOrder = treeBrushIndexOrders[b]; - int brushNodeOrder = brushIndexOrder.nodeOrder; - - var brushIntersectionsBlob = brushesTouchedByBrushes[brushNodeOrder]; - if (brushIntersectionsBlob == BlobAssetReference.Null) - return; - var treeSpaceVerticesBlob = treeSpaceVerticesArray[brushIndexOrder.nodeOrder]; - if (treeSpaceVerticesBlob == BlobAssetReference.Null) - return; - ref var vertices = ref treeSpaceVerticesBlob.Value.treeSpaceVertices; - - var mergeVertices = new HashedVertices(math.max(vertices.Length, 1000), Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref mergeVertices, math.max(vertices.Length, 1000)); - try - { - mergeVertices.AddUniqueVertices(ref vertices); - - // NOTE: assumes brushIntersections is in the same order as the brushes are in the tree - ref var brushIntersections = ref brushIntersectionsBlob.Value.brushIntersections; - for (int i = 0; i < brushIntersections.Length; i++) - { - var intersectingNodeOrder = brushIntersections[i].nodeIndexOrder.nodeOrder; - if (intersectingNodeOrder > brushNodeOrder) - continue; - - // In order, goes through the previous brushes in the tree, - // and snaps any vertex that is almost the same in the next brush, with that vertex - ref var intersectingVertices = ref treeSpaceVerticesArray[intersectingNodeOrder].Value.treeSpaceVertices; - mergeVertices.ReplaceIfExists(ref intersectingVertices); - } - - - for (int i = 0; i < vertices.Length; i++) - { - vertices[i] = mergeVertices.GetUniqueVertex(vertices[i]); - } - - //treeSpaceVerticesLookup.TryAdd(brushNodeIndex, treeSpaceVerticesBlob); - } - finally - { - mergeVertices.Dispose(); - } - } - } - */ - [BurstCompile(CompileSynchronously = true)] - struct MergeTouchingBrushVerticesIndirectJob : IJobParallelForDefer - { - // Read - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - // Gate the cross-brush snap on plane incidence (WeldIncidenceFilter); set from CSGManager's kUseIncidenceWeld. - [NoAlias, ReadOnly] public bool useIncidenceWeld; - // Canonical vertices (see CanonicalVertices): from Everywhere on, only the same vertex is merged here. - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; - [NoAlias, ReadOnly] public NativeList> basePolygonCache; - [NoAlias, ReadOnly] public NativeList> treeSpaceVerticesArray; - - [NativeDisableParallelForRestriction] - [NoAlias, ReadOnly] public NativeArray> loopVerticesLookup; - - // Write (own brush only): the snapped vertices for this pass. - [NativeDisableParallelForRestriction] - [NoAlias] public NativeArray> loopVerticesLookupOut; - - internal const int kRan = 1; - internal const int kChanged = 2; - - [NativeDisableParallelForRestriction] - [NoAlias] public NativeArray brushState; - [NoAlias, ReadOnly] public int iterationIndex; - - // Per thread scratch memory - [NativeDisableContainerSafetyRestriction] HashedVertices mergeVertices; - - public void Execute(int b) - { - var brushIndexOrder = allUpdateBrushIndexOrders[b]; - int brushNodeOrder = brushIndexOrder.nodeOrder; - - var brushIntersectionsBlob = brushesTouchedByBrushCache[brushNodeOrder]; - if (brushIntersectionsBlob == BlobAssetReference.Null) - return; - - // NOTE: assumes brushIntersections is in the same order as the brushes are in the tree - ref var brushIntersections = ref brushIntersectionsBlob.Value.brushIntersections; - - var brushCount = loopVerticesLookup.Length; - if (iterationIndex > 0) - { - var prevRow = (iterationIndex - 1) * brushCount; - var dirty = (brushState[prevRow + brushNodeOrder] & kChanged) != 0; - for (int i = 0; !dirty && i < brushIntersections.Length; i++) - { - var neighbourOrder = brushIntersections[i].nodeIndexOrder.nodeOrder; - if (neighbourOrder >= 0 && neighbourOrder < brushNodeOrder && - (brushState[prevRow + neighbourOrder] & kChanged) != 0) - dirty = true; - } - if (!dirty) - return; - } - - // A snap may pull a vertex onto a neighbour's, but not off the faces of its own brush. - var weldFilter = WeldIncidenceFilter.Disabled; - if (canonicalVertexStage >= CanonicalVertexStage.Everywhere) - weldFilter = WeldIncidenceFilter.SameVertexOnly; // canonical vertices: only the same vertex is merged - else - if (useIncidenceWeld && - brushTreeSpacePlaneCache[brushNodeOrder].IsCreated && basePolygonCache[brushNodeOrder].IsCreated) - weldFilter = WeldIncidenceFilter.Create(ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes, - basePolygonCache[brushNodeOrder].Value.polygons.Length); - - var vertices = loopVerticesLookup[brushIndexOrder.nodeOrder]; - - mergeVertices = new HashedVertices(math.max(vertices.Length, 1000), Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref mergeVertices, math.max(vertices.Length, 1000)); - try - { - mergeVertices.AddUniqueVertices(in vertices); - - var mergeMin = new float3(float.PositiveInfinity); - var mergeMax = new float3(float.NegativeInfinity); - for (int i = 0; i < vertices.Length; i++) - { - mergeMin = math.min(mergeMin, vertices[i]); - mergeMax = math.max(mergeMax, vertices[i]); - } - mergeMin -= HashedVertices.kCellSize; - mergeMax += HashedVertices.kCellSize; - - for (int i = 0; i < brushIntersections.Length; i++) - { - var intersectingNodeOrder = brushIntersections[i].nodeIndexOrder.nodeOrder; - if (intersectingNodeOrder > brushNodeOrder || - !loopVerticesLookup[intersectingNodeOrder].IsCreated) - continue; - - // In order, goes through the previous brushes in the tree, - // and snaps any vertex that is almost the same in the next brush, with that vertex - ref var intersectingVertices = ref treeSpaceVerticesArray[intersectingNodeOrder].Value.treeSpaceVertices; - mergeVertices.ReplaceIfExists(ref intersectingVertices, mergeMin, mergeMax); - } - - // NOTE: assumes brushIntersections is in the same order as the brushes are in the tree - for (int i = 0; i < brushIntersections.Length; i++) - { - var intersectingNodeOrder = brushIntersections[i].nodeIndexOrder.nodeOrder; - if (intersectingNodeOrder > brushNodeOrder || - !loopVerticesLookup[intersectingNodeOrder].IsCreated) - continue; - - // In order, goes through the previous brushes in the tree, - // and snaps any vertex that is almost the same in the next brush, with that vertex - var intersectingVertices = loopVerticesLookup[intersectingNodeOrder]; - mergeVertices.ReplaceIfExists(in intersectingVertices, mergeMin, mergeMax); - } - - - var outList = loopVerticesLookupOut[brushNodeOrder]; - if (!outList.IsCreated) - outList = new UnsafeList(vertices.Length, Allocator.Persistent); - else - outList.Clear(); - - var snapped = new NativeArray(vertices.Length, Allocator.Temp); - for (int i = 0; i < vertices.Length; i++) - snapped[i] = mergeVertices.GetUniqueVertex(vertices[i]); - - var claimHash = new HashedVertices(math.max(vertices.Length, 1), Allocator.Temp); - var claimFirst = new UnsafeList(vertices.Length, Allocator.Temp); // first original to claim each merged slot - var claimCollide = new UnsafeList (vertices.Length, Allocator.Temp); // slot claimed by >=2 distinct originals - var claimIdx = new NativeArray(vertices.Length, Allocator.Temp); - for (int i = 0; i < vertices.Length; i++) - { - int ci = claimHash.AddNoResize(snapped[i]); - claimIdx[i] = ci; - if (ci >= claimFirst.Length) - { - claimFirst.Add(vertices[i]); - claimCollide.Add(false); - } - else if (math.distancesq(claimFirst[ci], vertices[i]) > CSGConstants.kSqrVertexEqualEpsilon) - { - claimCollide[ci] = true; // a DISTINCT original wants the same merged slot -> collapse - } - } - var changed = false; - for (int i = 0; i < vertices.Length; i++) - { - var merged = claimCollide[claimIdx[i]] ? vertices[i] : snapped[i]; - if (weldFilter.IsEnabled && !weldFilter.Allows(vertices[i], merged)) merged = vertices[i]; - outList.Add(merged); - if (!changed && !merged.Equals(vertices[i])) - changed = true; - } - brushState[(iterationIndex * brushCount) + brushNodeOrder] = changed ? (kRan | kChanged) : kRan; - - snapped.Dispose(); - claimHash.Dispose(); - claimFirst.Dispose(); - claimCollide.Dispose(); - claimIdx.Dispose(); - loopVerticesLookupOut[brushNodeOrder] = outList; - } - finally - { - mergeVertices.Dispose(); - } - } - } - - [BurstCompile(CompileSynchronously = true)] - struct CopyBackLoopVerticesJob : IJobParallelForDefer - { - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NativeDisableParallelForRestriction] - [NoAlias, ReadOnly] public NativeArray> loopVerticesLookupOut; - [NativeDisableParallelForRestriction] - [NoAlias] public NativeArray> loopVerticesLookup; - - // Written by the merge pass this follows: a brush that pass skipped left its out-buffer holding - // what loopVerticesLookup already contains, so copying it back would be a no-op. - [NoAlias, ReadOnly] public NativeArray brushState; - [NoAlias, ReadOnly] public int iterationIndex; - - public void Execute(int b) - { - var brushNodeOrder = allUpdateBrushIndexOrders[b].nodeOrder; - - var brushCount = loopVerticesLookup.Length; - if ((brushState[(iterationIndex * brushCount) + brushNodeOrder] & - MergeTouchingBrushVerticesIndirectJob.kRan) == 0) - return; - - var src = loopVerticesLookupOut[brushNodeOrder]; - if (!src.IsCreated) - return; - var dst = loopVerticesLookup[brushNodeOrder]; - if (!dst.IsCreated) - return; - dst.Clear(); - dst.AddRange(src); - loopVerticesLookup[brushNodeOrder] = dst; - } - } -} diff --git a/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs.meta b/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs.meta deleted file mode 100644 index 188a6e2a..00000000 --- a/Core/2.Processing/Jobs/MergeTouchingBrushVerticesJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6205c239df12a564aad61ca6bbc95039 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/PerformCSGJob.cs b/Core/2.Processing/Jobs/PerformCSGJob.cs deleted file mode 100644 index 1e7f28b1..00000000 --- a/Core/2.Processing/Jobs/PerformCSGJob.cs +++ /dev/null @@ -1,1678 +0,0 @@ -using System; -using Unity.Mathematics; -using Unity.Jobs; -using Unity.Collections; -using Unity.Burst; -using Unity.Collections.LowLevel.Unsafe; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - [BurstCompile(CompileSynchronously = true)] - struct PerformCSGJob : IJobParallelForDefer - { - // Read - // 'Required' for scheduling with index count - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - - // Gate the vertex re-weld on plane incidence (WeldIncidenceFilter); set from CSGManager's kUseIncidenceWeld. - [NoAlias, ReadOnly] public bool useIncidenceWeld; - // Canonical vertices (see CanonicalVertices): from Everywhere on, only the same vertex is merged here. - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> routingTableCache; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; - [NoAlias, ReadOnly] public NativeList> brushesTouchedByBrushCache; - [NoAlias, ReadOnly] public NativeArray> loopVerticesLookup; - // Each brush's contents and whether it carves, for the contents rules - [NoAlias, ReadOnly] public NativeReference> compactTreeRef; - - [NoAlias, ReadOnly] public NativeStream.Reader input; - - // Write - [NoAlias, WriteOnly] public NativeStream.Writer output; - - // Per thread scratch memory - /* - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray categories1; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray categories2; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray outEdges; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray intersectedHoleIndices; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray intersectionSurfaceInfo; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeBitArray destroyedEdges; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList allInfos; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList intersectionSurfaceInfos; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList basePolygonSurfaceInfos; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList alltreeSpacePlanes; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList allSegments; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList allCombinedEdges; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> holeIndices; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> surfaceLoopIndices; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> allEdges; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> intersectionLoops; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> basePolygonEdges; - [NativeDisableContainerSafetyRestriction, NoAlias] HashedVertices hashedTreeSpaceVertices; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeArray indexRemap; - [NativeDisableContainerSafetyRestriction, NoAlias] NativeList> intersectionEdges; - */ - - [BurstDiscard] - private static void NotUniqueEdgeException() - { - throw new Exception("Edge is not unique"); - } - - static bool AddEdgesNoResize(ref UnsafeList dstEdges, in UnsafeList srcEdges) - { - if (srcEdges.Length == 0) - return false; - - for (int ae = srcEdges.Length - 1; ae >= 0; ae--) - { - var addEdge = srcEdges[ae]; - for (int e = dstEdges.Length - 1; e >= 0; ) - { - if (addEdge.Equals(dstEdges[e])) - { - NotUniqueEdgeException(); - dstEdges.RemoveAtSwapBack(e); - } else - e--; - } - } - - bool duplicates = false; - - for (int v = 0; v < srcEdges.Length;v++) - { - var addEdge = srcEdges[v]; - var index = IndexOf(dstEdges, addEdge, out bool _); - if (index != -1) - { - //Debug.Log($"Duplicate edge {inverted} {values[v].index1}/{values[v].index2} {edges[index].index1}/{edges[index].index2}"); - duplicates = true; - continue; - } - dstEdges.AddNoResize(addEdge); - } - - return duplicates; - } - - static int IndexOf(UnsafeList edges, Edge edge, out bool inverted) - { - //var builder = new System.Text.StringBuilder(); - for (int e = 0; e < edges.Length; e++) - { - //builder.AppendLine($"{e}/{edges.Count}: {edges[e]} {edge}"); - if (edges[e].index1 == edge.index1 && edges[e].index2 == edge.index2) { inverted = false; return e; } - if (edges[e].index1 == edge.index2 && edges[e].index2 == edge.index1) { inverted = true; return e; } - } - //Debug.Log(builder.ToString()); - inverted = false; - return -1; - } - - // Both endpoints of `edge` appear somewhere on `loop`. - static bool BoundedByLoop(Edge edge, [NoAlias] in UnsafeList loop) - { - bool first = false, second = false; - for (int e = 0; e < loop.Length; e++) - { - var index1 = loop[e].index1; - var index2 = loop[e].index2; - if (index1 == edge.index1 || index2 == edge.index1) first = true; - if (index1 == edge.index2 || index2 == edge.index2) second = true; - if (first && second) - return true; - } - return false; - } - - static UnsafeList BuildCutLoop([NoAlias] in NativeArray outEdges, int outEdgesLength) - { - var edges = new UnsafeList(outEdgesLength, Allocator.Temp); - edges.AddRangeNoResize(outEdges, outEdgesLength); - - if (edges.Length >= 3) - LoopEdgeSplitter.RemoveAntiparallelEdgePairs(ref edges); - if (kRemoveSimpleChords && edges.Length >= 3) - LoopEdgeSplitter.RemoveSimpleChords(ref edges); - if (kCloseOpenChains && edges.Length >= 2) - LoopEdgeSplitter.CloseSingleOpenChain(ref edges); - - if (edges.Length >= 3) - LoopEdgeSplitter.RewindClosedLoop(ref edges); - return edges; - } - - const float kSameRegionEpsilon = CSGConstants.kVertexEqualEpsilon * 2; - - const float kCanonicalSameRegionEpsilon = CSGConstants.kFatPlaneWidthEpsilon; - - static bool LoopsOutlineSameRegion([NoAlias] in UnsafeList loopA, [NoAlias] in UnsafeList loopB, - [NoAlias] in HashedVertices vertices, float epsilon) - { - if (loopA.Length < 3 || loopB.Length < 3) - return false; - return AllCornersOnBoundary(in loopA, in loopB, in vertices, epsilon) - && AllCornersOnBoundary(in loopB, in loopA, in vertices, epsilon); - } - - static bool AllCornersOnBoundary([NoAlias] in UnsafeList loopA, [NoAlias] in UnsafeList loopB, - [NoAlias] in HashedVertices vertices, float epsilon) - { - for (int a = 0; a < loopA.Length; a++) - { - if (!PointOnLoopBoundary(vertices[loopA[a].index1], in loopB, in vertices, epsilon) || - !PointOnLoopBoundary(vertices[loopA[a].index2], in loopB, in vertices, epsilon)) - return false; - } - return true; - } - - static bool PointOnLoopBoundary(float3 position, [NoAlias] in UnsafeList loop, - [NoAlias] in HashedVertices vertices, float epsilon) - { - var sqrEpsilon = epsilon * epsilon; - for (int e = 0; e < loop.Length; e++) - { - var from = vertices[loop[e].index1]; - if (math.distancesq(from, position) <= sqrEpsilon) - return true; - var to = vertices[loop[e].index2]; - if (math.distancesq(to, position) <= sqrEpsilon) - return true; - - // strictly between the endpoints: the other loop samples this stretch of boundary - // with a vertex that this one does not have - var delta = to - from; - var lengthSq = math.lengthsq(delta); - if (lengthSq <= 1e-12f) - continue; - var t = math.dot(position - from, delta) / lengthSq; - if (t <= 0 || t >= 1) - continue; - if (math.distancesq(from + delta * t, position) <= sqrEpsilon) - return true; - } - return false; - } - - void IntersectLoops([NoAlias] in HashedVertices hashedTreeSpaceVertices, - - [NoAlias] ref UnsafeList loopIndices, - int surfaceLoopIndex, - - [NoAlias] ref NativeList> holeIndices, - [NoAlias] ref NativeList allInfos, - [NoAlias] ref NativeList> allEdges, - - [NoAlias] ref NativeArray outEdges, - [NoAlias] ref NativeArray intersectedHoleIndices, - [NoAlias] ref NativeArray categories1, - [NoAlias] ref NativeArray categories2, - - - [NoAlias] in UnsafeList intersectionLoop, - ushort intersectionCategory, - IndexSurfaceInfo intersectionInfo) - { - if (intersectionLoop.Length == 0) - return; - - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - - var currentLoopEdges = allEdges[surfaceLoopIndex]; - var currentInfo = allInfos[surfaceLoopIndex]; - var currentHoleIndices = holeIndices[surfaceLoopIndex]; - - // It might look like we could just set the interiorCategory of brush_intersection here, and let all other cut loops copy from it below, - // but the same brush_intersection might be used by another categorized_loop and then we'd try to reroute it again, which wouldn't work - //brush_intersection.interiorCategory = newHoleCategory; - - if (currentLoopEdges.Length == 0) - return; - - var maxLength = math.max(16, intersectionLoop.Length + currentLoopEdges.Length); - if (maxLength < 3) - return; - - NativeCollectionHelpers.EnsureMinimumSize(ref categories1, intersectionLoop.Length); - NativeCollectionHelpers.EnsureMinimumSize(ref categories2, currentLoopEdges.Length); - - int inside1 = 0, outside1 = 0; - int currentBrushOrder = currentInfo.brushIndexOrder.nodeOrder; - ref var treeSpacePlanes2 = ref brushTreeSpacePlaneCache[currentBrushOrder].Value.treeSpacePlanes; - for (int e = 0; e < intersectionLoop.Length; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(intersectionLoop[e], ref treeSpacePlanes2, in currentLoopEdges, in hashedTreeSpaceVertices); - categories1[e] = category; - if (category == EdgeCategory.Inside) inside1++; - else if (category == EdgeCategory.Outside) outside1++; - } - var aligned1 = intersectionLoop.Length - (inside1 + outside1); - - int intersectionBrushOrder = intersectionInfo.brushIndexOrder.nodeOrder; - ref var treeSpacePlanes1 = ref brushTreeSpacePlaneCache[intersectionBrushOrder].Value.treeSpacePlanes; - int inside2 = 0, outside2 = 0; - for (int e = 0; e < currentLoopEdges.Length; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(currentLoopEdges[e], ref treeSpacePlanes1, in intersectionLoop, in hashedTreeSpaceVertices); - categories2[e] = category; - if (category == EdgeCategory.Inside) inside2++; - else if (category == EdgeCategory.Outside) outside2++; - } - var aligned2 = currentLoopEdges.Length - (inside2 + outside2); - - // Completely outside - if ((inside1 + aligned1) == 0 && (aligned2 + inside2) == 0) - return; - - if ((inside1 + (inside2 + aligned2)) < 3) - return; - - // Completely aligned - if (((outside1 + inside1) == 0 && (outside2 + inside2) == 0) || - // polygon1 edges Completely inside polygon2 - (inside1 == 0 && outside2 == 0)) - { - // New polygon overrides the existing polygon - currentInfo.interiorCategory = intersectionCategory; - allInfos[surfaceLoopIndex] = currentInfo; - //Debug.Assert(holeIndices.IsAllocated(surfaceLoopIndex)); - return; - } - - var sameRegionEpsilon = canonicalVertexStage >= CanonicalVertexStage.Everywhere ? kCanonicalSameRegionEpsilon - : kSameRegionEpsilon; - if (LoopsOutlineSameRegion(in intersectionLoop, in currentLoopEdges, in hashedTreeSpaceVertices, sameRegionEpsilon)) - { - currentInfo.interiorCategory = intersectionCategory; - allInfos[surfaceLoopIndex] = currentInfo; - return; - } - - NativeCollectionHelpers.EnsureMinimumSize(ref outEdges, maxLength); - - //var outEdges = stackalloc Edge[maxLength]; - var outEdgesLength = 0; - - // polygon2 edges Completely inside polygon1 - if (outside1 == 0 && inside2 == 0) - { - // polygon1 Completely inside polygon2 - for (int n = 0; n < intersectionLoop.Length; n++) - { - outEdges[outEdgesLength] = intersectionLoop[n]; - outEdgesLength++; - } - //OperationResult.Polygon1InsidePolygon2; - } else - { - //int outEdgesLength = 0; // Can't read from outEdges.Length since it's marked as WriteOnly - for (int e = 0; e < intersectionLoop.Length; e++) - { - var category = categories1[e]; - if (category == EdgeCategory.Inside) - { - outEdges[outEdgesLength] = intersectionLoop[e]; - outEdgesLength++; - } - } - - for (int e = 0; e < currentLoopEdges.Length; e++) - { - var category = categories2[e]; - if (category == EdgeCategory.Outside) - continue; - - if (category == EdgeCategory.Inside && - !BoundedByLoop(currentLoopEdges[e], in intersectionLoop)) - continue; - - outEdges[outEdgesLength] = currentLoopEdges[e]; - outEdgesLength++; - } - //OperationResult.Cut; - } - - if (outEdgesLength < 3) - return; - - if (EnclosesNoArea(in outEdges, outEdgesLength, in hashedTreeSpaceVertices)) - return; - - // the output of cutting operations are both holes for the original polygon (categorized_loop) - // and new polygons on the surface of the brush that need to be categorized - intersectionInfo.interiorCategory = intersectionCategory; - - - var brushesTouchedByBrush = brushesTouchedByBrushCache[currentBrushOrder]; - if (currentHoleIndices.Length > 0 && - // TODO: fix touching not being updated properly - brushesTouchedByBrush != BlobAssetReference.Null) - { - NativeCollectionHelpers.EnsureMinimumSize(ref intersectedHoleIndices, currentHoleIndices.Length); - var intersectedHoleIndicesLength = 0; - - // the output of cutting operations are both holes for the original polygon (categorized_loop) - // and new polygons on the surface of the brush that need to be categorized - - ref var brushesTouchedByBrushRef = ref brushesTouchedByBrush.Value; - ref var brushIntersections = ref brushesTouchedByBrushRef.brushIntersections; - for (int h = 0; h < currentHoleIndices.Length; h++) - { - // Need to make a copy so we can edit it without causing side effects - var holeIndex = currentHoleIndices[h]; - var holeEdges = allEdges[holeIndex]; - if (holeEdges.Length < 3) - continue; - - var holeInfo = allInfos[holeIndex]; - var holeBrushNodeID = holeInfo.brushIndexOrder.compactNodeID; - - bool touches = brushesTouchedByBrushRef.Get(holeBrushNodeID) != IntersectionType.NoIntersection; - - // Only add if they touch - if (touches) - { - intersectedHoleIndices[intersectedHoleIndicesLength] = allEdges.Length; - intersectedHoleIndicesLength++; - holeIndices.Add(new UnsafeList(1, Allocator.Temp)); - //if (allInfos.Capacity < allInfos.Length + 1) - // allInfos.Capacity = allInfos.Length + 16; - //allInfos.AddNoResize(holeInfo); - allInfos.Add(holeInfo); - var newHoleEdges = new UnsafeList(holeEdges.Length, Allocator.Temp); - newHoleEdges.AddRangeNoResize(holeEdges); - allEdges.Add(newHoleEdges); - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - //Debug.Assert(holeIndices.IsAllocated(allInfos.Length - 1)); - } - } - - // This loop is a hole - //if (currentHoleIndices.Capacity < currentHoleIndices.Length + 1) // TODO: figure out why capacity is sometimes not enough - // currentHoleIndices.Capacity = currentHoleIndices.Length + 16; - //currentHoleIndices.AddNoResize(allEdges.Length); - currentHoleIndices.Add(allEdges.Length); - holeIndices[surfaceLoopIndex] = currentHoleIndices; - holeIndices.Add(new UnsafeList(1, Allocator.Temp)); - //if (allInfos.Capacity < allInfos.Length + 1) - // allInfos.Capacity = allInfos.Length + 16; - //allInfos.AddNoResize(intersectionInfo); - allInfos.Add(intersectionInfo); - var newOutEdges = BuildCutLoop(in outEdges, outEdgesLength); - allEdges.Add(newOutEdges); - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - //Debug.Assert(holeIndices.IsAllocated(allInfos.Length - 1)); - - // But also a polygon on its own - //if (loopIndices.Capacity < loopIndices.Length + 1) // TODO: figure out why capacity is sometimes not enough - // loopIndices.Capacity = loopIndices.Length + 16; - //loopIndices.AddNoResize(allEdges.Length); - loopIndices.Add(allEdges.Length); - var newHoleIndices = new UnsafeList(intersectedHoleIndicesLength, Allocator.Temp); - newHoleIndices.AddRangeNoResize(intersectedHoleIndices, intersectedHoleIndicesLength); - holeIndices.Add(newHoleIndices); - //if (allInfos.Capacity < allInfos.Length + 1) - // allInfos.Capacity = allInfos.Length + 16; - //allInfos.AddNoResize(intersectionInfo); - allInfos.Add(intersectionInfo); - newOutEdges = BuildCutLoop(in outEdges, outEdgesLength); - allEdges.Add(newOutEdges); - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - //Debug.Assert(holeIndices.IsAllocated(allEdges.Length - 1)); - } else - { - // This loop is a hole - //currentHoleIndices.AddNoResize(allEdges.Length); - currentHoleIndices.Add(allEdges.Length); - holeIndices[surfaceLoopIndex] = currentHoleIndices; - holeIndices.Add(new UnsafeList(1, Allocator.Temp)); - //if (allInfos.Capacity < allInfos.Length + 1) - // allInfos.Capacity = allInfos.Length + 16; - //allInfos.AddNoResize(intersectionInfo); - allInfos.Add(intersectionInfo); - var newOutEdges = BuildCutLoop(in outEdges, outEdgesLength); - allEdges.Add(newOutEdges); - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - //Debug.Assert(holeIndices.IsAllocated(allInfos.Length - 1)); - - // But also a polygon on its own - //if (loopIndices.Capacity < loopIndices.Length + 1) // TODO: figure out why capacity is sometimes not enough - // loopIndices.Capacity = loopIndices.Length + 16; - //loopIndices.AddNoResize(allEdges.Length); - loopIndices.Add(allEdges.Length); - holeIndices.Add(new UnsafeList(1, Allocator.Temp)); - if (allInfos.Capacity < allInfos.Length + 1) - allInfos.Capacity = allInfos.Length + 16; - allInfos.AddNoResize(intersectionInfo); - newOutEdges = BuildCutLoop(in outEdges, outEdgesLength); - allEdges.Add(newOutEdges); - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - //Debug.Assert(holeIndices.IsAllocated(allInfos.Length - 1)); - } - } - - static bool EnclosesNoArea([NoAlias] in NativeArray edges, int edgesLength, [NoAlias] in HashedVertices vertices) - { - var from = (double3)vertices[edges[0].index1]; - for (int sweep = 0; sweep < 2; sweep++) - { - var furthest = from; - var best = -1.0; - for (int e = 0; e < edgesLength * 2; e++) - { - var vertex = (double3)vertices[(e & 1) == 0 ? edges[e >> 1].index1 : edges[e >> 1].index2]; - var distance = math.distancesq(from, vertex); - if (distance > best) { best = distance; furthest = vertex; } - } - if (sweep == 0) { from = furthest; continue; } - - var direction = furthest - from; - var lengthSq = math.lengthsq(direction); - var band = (double)CSGConstants.kFatPlaneWidthEpsilon; - if (lengthSq <= band * band) - return true; - for (int e = 0; e < edgesLength * 2; e++) - { - var offset = (double3)vertices[(e & 1) == 0 ? edges[e >> 1].index1 : edges[e >> 1].index2] - from; - // |offset x direction| / |direction| is the distance from the line - if (math.lengthsq(math.cross(offset, direction)) > band * band * lengthSq) - return false; - } - } - return true; - } - - internal static float3 CalculatePlaneNormal(in UnsafeList edges, in HashedVertices hashedVertices) - { - // Newell's algorithm to create a plane for concave polygons. - // NOTE: doesn't work well for self-intersecting polygons - var normal = float3.zero; - var vertices = hashedVertices; - for (int n = 0; n < edges.Length; n++) - { - var edge = edges[n]; - var prevVertex = vertices[(int)edge.index1]; - var currVertex = vertices[(int)edge.index2]; - normal += CSGMath.NewellTerm(prevVertex, currVertex); - } - normal = math.normalizesafe(normal); - - return normal; - } - - static readonly bool kDebugDumpNonSimpleLoops = false; - - // Investigation trace (CSGTrace): discarded under Burst, so it only runs when a test turns Burst off. - [BurstDiscard] - static void TraceInputs(int brushNodeOrder, [NoAlias] in HashedVertices vertices, - [NoAlias] in NativeList> basePolygonEdges, - [NoAlias] in NativeList intersectionSurfaceInfos, - [NoAlias] in NativeList> intersectionEdges) - { - if (CSGTrace.Sink == null) - return; - CSGTrace.Vertices(brushNodeOrder, in vertices); - for (int l = 0; l < basePolygonEdges.Length; l++) - CSGTrace.Loop('B', brushNodeOrder, l, -1, 0, brushNodeOrder, basePolygonEdges[l]); - for (int i = 0; i < intersectionSurfaceInfos.Length; i++) - { - var info = intersectionSurfaceInfos[i]; - CSGTrace.Loop('I', brushNodeOrder, info.basePlaneIndex, -1, info.interiorCategory, info.brushIndexOrder.nodeOrder, intersectionEdges[i]); - } - } - - [BurstDiscard] - static void TraceRoute(int brushNodeOrder, int surfaceIndex, int step, int loopIndex, int inCategory, int intersectionCategory, - int otherBrush, int intersectionLength, bool overlap, int outCategory, int cutCategory) - { - CSGTrace.Route(brushNodeOrder, surfaceIndex, step, loopIndex, inCategory, intersectionCategory, otherBrush, - intersectionLength, overlap, outCategory, cutCategory); - } - - [BurstDiscard] - static void TraceLoops(char stage, int brushNodeOrder, int surfaceIndex, [NoAlias] in UnsafeList loopIndices, - [NoAlias] in NativeList> holeIndices, [NoAlias] in NativeList allInfos, - [NoAlias] in NativeList> allEdges) - { - CSGTrace.Loops(stage, brushNodeOrder, surfaceIndex, in loopIndices, in holeIndices, in allInfos, in allEdges); - } - - // Set false to disable the conservative single-chord removal on the merged loop. - const bool kRemoveSimpleChords = true; - - // Re-close a merged loop that is a single open path missing exactly one edge. See - // LoopEdgeSplitter.CloseSingleOpenChain; only fires on one clean connected open chain. - const bool kCloseOpenChains = true; - - static readonly bool kLogDestroyedEdges = false; - - static readonly bool kLogStrictCrossing = false; - - [BurstDiscard] - static void LogStrictCrossing(int brushNodeOrder, int surfaceIndex, int loopIndex, int i1, int i2, EdgeCategory midpointVerdict, char kind) - { -#if UNITY_EDITOR - var msg = new FixedString128Bytes(); - msg.Append('X'); msg.Append('I'); msg.Append('N'); msg.Append('G'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); msg.Append('k'); msg.Append(kind); - msg.Append(' '); msg.Append('m'); msg.Append(midpointVerdict == EdgeCategory.Inside ? 'I' : 'O'); - msg.Append(':'); msg.Append(' '); msg.Append(i1); msg.Append('-'); msg.Append(i2); - Debug.Log(msg); -#endif - } - - static readonly bool kKeepReverseAlignedBaseEdges = false; - - [BurstDiscard] - static void LogDestroyedEdge(int brushNodeOrder, int surfaceIndex, int loopIndex, int i1, int i2, EdgeCategory category, char kind) - { -#if UNITY_EDITOR - var msg = new FixedString128Bytes(); - msg.Append('D'); msg.Append('E'); msg.Append('D'); msg.Append('G'); msg.Append('E'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); msg.Append('k'); msg.Append(kind); - msg.Append(' '); msg.Append('c'); - switch (category) - { - case EdgeCategory.Inside: msg.Append('I'); break; - case EdgeCategory.Aligned: msg.Append('A'); break; - case EdgeCategory.ReverseAligned: msg.Append('R'); break; - case EdgeCategory.Outside: msg.Append('O'); break; - default: msg.Append('?'); break; - } - msg.Append(':'); msg.Append(' '); msg.Append(i1); msg.Append('-'); msg.Append(i2); - Debug.Log(msg); -#endif - } - - const bool kKeepEdgesRestingOnTheOtherLoopsBoundary = true; - - // Diagnostic: a loop that EXITS CleanUp still containing antiparallel (A->B & B->A) pairs - i.e. - // CleanUp's RemoveAntiparallelEdgePairs did NOT clean it. - static readonly bool kLogCleanupAnti = false; - - [BurstDiscard] - static void LogCleanupAnti(in NativeList allInfos, int baseloopIndex, int pairCount) - { -#if UNITY_EDITOR - var brushNodeOrder = allInfos[baseloopIndex].brushIndexOrder.nodeOrder; - var surfaceIndex = allInfos[baseloopIndex].basePlaneIndex; - var loopIndex = baseloopIndex; - var msg = new FixedString128Bytes(); - msg.Append('C'); msg.Append('A'); msg.Append('N'); msg.Append('T'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); msg.Append('p'); msg.Append(pairCount); - Debug.Log(msg); -#endif - } - - [BurstDiscard] - static void LogCleanupAntiOut(int brushNodeOrder, int surfaceIndex, int loopIndex, int pairCount) - { -#if UNITY_EDITOR - var msg = new FixedString128Bytes(); - msg.Append('C'); msg.Append('A'); msg.Append('N'); msg.Append('T'); msg.Append('O'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); msg.Append('p'); msg.Append(pairCount); - Debug.Log(msg); -#endif - } - - static readonly bool kLogHoleFlip = false; - - [BurstDiscard] - static void LogHoleFlip(in NativeList allInfos, int baseloopIndex, - float3 holeNormal, float3 baseLoopNormal, - in UnsafeList holeEdges, in UnsafeList baseLoopEdges) - { -#if UNITY_EDITOR - int coincidentWithBase = 0; - for (int hn = 0; hn < holeEdges.Length; hn++) - for (int bn = 0; bn < baseLoopEdges.Length; bn++) - if ((holeEdges[hn].index1 == baseLoopEdges[bn].index1 && holeEdges[hn].index2 == baseLoopEdges[bn].index2) || - (holeEdges[hn].index1 == baseLoopEdges[bn].index2 && holeEdges[hn].index2 == baseLoopEdges[bn].index1)) - { coincidentWithBase++; break; } - - var brushNodeOrder = allInfos[baseloopIndex].brushIndexOrder.nodeOrder; - var surfaceIndex = allInfos[baseloopIndex].basePlaneIndex; - var dot = (float)math.dot(holeNormal, baseLoopNormal); - var holeNormalLen = math.length(holeNormal); - var holeLen = holeEdges.Length; - - var msg = new FixedString128Bytes(); - msg.Append('H'); msg.Append('F'); msg.Append('L'); msg.Append('P'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); - // dot and normal-length scaled x1000 and logged as ints (FixedString float formatting is fiddly) - msg.Append(' '); msg.Append('d'); msg.Append((int)(dot * 1000)); - msg.Append(' '); msg.Append('n'); msg.Append((int)(holeNormalLen * 1000)); - msg.Append(' '); msg.Append('h'); msg.Append(holeLen); - msg.Append(' '); msg.Append('c'); msg.Append(coincidentWithBase); - Debug.Log(msg); -#endif - } - - [BurstDiscard] - static void LogCleanupDup(int brushNodeOrder, int surfaceIndex, int loopIndex, int dupCount) - { -#if UNITY_EDITOR - var msg = new FixedString128Bytes(); - msg.Append('C'); msg.Append('D'); msg.Append('U'); msg.Append('P'); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); msg.Append('d'); msg.Append(dupCount); - Debug.Log(msg); -#endif - } - - [BurstDiscard] - static void LogLoopDefect(char stage, int brushNodeOrder, int surfaceIndex, int loopIndex, in UnsafeList edges, in HashedVertices vertices) - { -#if UNITY_EDITOR - var defect = LoopValidation.Classify(in edges, vertices.Length, out int badVertex); - if (defect == LoopDefect.None || defect == LoopDefect.Empty) - return; - var msg = new FixedString512Bytes(); - msg.Append('N'); msg.Append('S'); msg.Append('L'); msg.Append(stage); msg.Append(' '); - msg.Append(brushNodeOrder); msg.Append('/'); msg.Append(surfaceIndex); msg.Append('/'); msg.Append(loopIndex); - msg.Append(' '); - switch (defect) - { - case LoopDefect.DegenerateEdge: msg.Append('D'); msg.Append('E'); msg.Append('G'); break; - case LoopDefect.OpenChain: msg.Append('O'); msg.Append('P'); msg.Append('N'); break; - case LoopDefect.Pinch: msg.Append('P'); msg.Append('I'); msg.Append('N'); break; - } - msg.Append('@'); msg.Append(badVertex); msg.Append(':'); - for (int e = 0; e < edges.Length && msg.Length < 470; e++) - { - msg.Append(' '); - msg.Append(edges[e].index1); - msg.Append('-'); - msg.Append(edges[e].index2); - } - Debug.Log(msg); -#endif - } - - static int AddBoundingPlanes([NoAlias] ref NativeList planes, [NoAlias] ref BlobArray brushPlanes, - float3 loopNormal, [NoAlias] in UnsafeList loop, [NoAlias] in HashedVertices vertices) - { - int added = 0; - for (int p = 0; p < brushPlanes.Length; p++) - { - var plane = brushPlanes[p]; - if (ContainsLoop(plane, loopNormal, in loop, in vertices)) - continue; - planes.AddNoResize(plane); - added++; - } - return added; - } - - static bool ContainsLoop(float4 plane, float3 loopNormal, [NoAlias] in UnsafeList loop, [NoAlias] in HashedVertices vertices) - { - if (math.abs(math.dot(plane.xyz, loopNormal)) < CSGConstants.kNormalDotAlignEpsilon) - return false; - for (int e = 0; e < loop.Length; e++) - { - if (math.abs(CSGMath.SignedDistance(plane, vertices[loop[e].index1])) > CSGConstants.kFatPlaneWidthEpsilon) - return false; - } - return true; - } - - void CleanUp(in NativeList allInfos, ref NativeList> allEdges, in HashedVertices brushVertices, ref UnsafeList loopIndices, ref NativeList> holeIndices, - [NoAlias] ref NativeList alltreeSpacePlanes, [NoAlias] ref NativeList allSegments, [NoAlias] ref NativeList allCombinedEdges, [NoAlias] ref NativeBitArray destroyedEdges) - { - for (int l = loopIndices.Length - 1; l >= 0; l--) - { - var baseloopIndex = loopIndices[l]; - var baseLoopEdges = allEdges[baseloopIndex]; - - - // Remove degenerate edges that loop back on itself (zero-area slits/spikes). - if (baseLoopEdges.Length >= 3) - LoopEdgeSplitter.RemoveAntiparallelEdgePairs(ref baseLoopEdges); - - if (baseLoopEdges.Length < 3) - { - baseLoopEdges.Clear(); - allEdges[baseloopIndex] = baseLoopEdges; - continue; - } - - var surfaceLoopInfo = allInfos[baseloopIndex]; - var interiorCategory = (CategoryIndex)surfaceLoopInfo.interiorCategory; - if (interiorCategory != CategoryIndex.ValidAligned && - interiorCategory != CategoryIndex.ValidReverseAligned) - { - baseLoopEdges.Clear(); - allEdges[baseloopIndex] = baseLoopEdges; - continue; - } - - var baseLoopNormal = CalculatePlaneNormal(in baseLoopEdges, in brushVertices); - if (math.all(baseLoopNormal == float3.zero)) - { - baseLoopEdges.Clear(); - allEdges[baseloopIndex] = baseLoopEdges; - continue; - } - - allEdges[baseloopIndex] = baseLoopEdges; - var holeIndicesList = holeIndices[baseloopIndex]; - if (holeIndicesList.Length == 0) - continue; - - - for (int h = holeIndicesList.Length - 1; h >= 0; h--) - { - var holeIndex = holeIndicesList[h]; - var holeEdges = allEdges[holeIndex]; - - // Remove degenerate edges that loop back on itself - TryNextHole: - if (holeEdges.Length >= 3) - { - for (int a = 0; a < holeEdges.Length; a++) - { - for (int b = a + 1; b < holeEdges.Length; b++) - { - if (holeEdges[a].index1 == holeEdges[b].index2 && - holeEdges[a].index2 == holeEdges[b].index1) - { - //Debug.Log($"hole [{a},{b}] ({holeEdges[a].index1}, {holeEdges[a].index2}) ({holeEdges[b].index1}, {holeEdges[b].index2})"); - holeEdges.RemoveAtSwapBack(b); - holeEdges.RemoveAtSwapBack(a); - goto TryNextHole; - } - } - } - } - - allEdges[holeIndex] = holeEdges; - if (holeEdges.Length < 3) - { - holeIndicesList.RemoveAtSwapBack(h); - holeIndices[baseloopIndex] = holeIndicesList; - continue; - } - - var holeNormal = CalculatePlaneNormal(in holeEdges, in brushVertices); - if (math.all(holeNormal == float3.zero)) - { - holeIndicesList.RemoveAtSwapBack(h); - holeIndices[baseloopIndex] = holeIndicesList; - continue; - } - } - - if (holeIndicesList.Length == 0) - continue; - - int totalPlaneCount = 0; - int totalEdgeCount = baseLoopEdges.Length; - { - int brushNodeOrder = allInfos[baseloopIndex].brushIndexOrder.nodeOrder; - ref var treeSpacePlanes = ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes; - totalPlaneCount += treeSpacePlanes.Length; - } - for (int h = 0; h < holeIndicesList.Length; h++) - { - var holeIndex = holeIndicesList[h]; - var holeEdges = allEdges[holeIndex]; - totalEdgeCount += holeEdges.Length; - - int brushNodeOrder = allInfos[holeIndex].brushIndexOrder.nodeOrder; - ref var treeSpacePlanes = ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes; - - totalPlaneCount += treeSpacePlanes.Length; - } - - - NativeCollectionHelpers.EnsureCapacityAndClear(ref alltreeSpacePlanes, totalPlaneCount); - NativeCollectionHelpers.EnsureCapacityAndClear(ref allSegments, holeIndicesList.Length + 1); - NativeCollectionHelpers.EnsureCapacityAndClear(ref allCombinedEdges, totalEdgeCount); - - { - int edgeOffset = 0; - int planeOffset = 0; - for (int h = 0; h < holeIndicesList.Length; h++) - { - var holeIndex = holeIndicesList[h]; - var holeEdges = allEdges[holeIndex]; - - // TODO: figure out why sometimes polygons are flipped around, and try to fix this at the source - var holeNormal = CalculatePlaneNormal(in holeEdges, in brushVertices); - if (kLogHoleFlip) - { - LogHoleFlip(in allInfos, baseloopIndex, holeNormal, baseLoopNormal, in holeEdges, in baseLoopEdges); - } - if (math.dot(holeNormal, baseLoopNormal) > 0) - { - for (int n = 0; n < holeEdges.Length; n++) - { - var holeEdge = holeEdges[n]; - var i1 = holeEdge.index1; - var i2 = holeEdge.index2; - holeEdge.index1 = i2; - holeEdge.index2 = i1; - holeEdges[n] = holeEdge; - } - allEdges[holeIndex] = holeEdges; - } - - int brushNodeOrder = allInfos[holeIndex].brushIndexOrder.nodeOrder; - ref var treeSpacePlanes = ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes; - - // TODO: ideally we'd only use the planes that intersect our edges - var planesLength = AddBoundingPlanes(ref alltreeSpacePlanes, ref treeSpacePlanes, baseLoopNormal, in baseLoopEdges, in brushVertices); - var edgesLength = holeEdges.Length; - - allSegments.AddNoResize(new LoopSegment - { - edgeOffset = edgeOffset, - edgeLength = edgesLength, - planesOffset = planeOffset, - planesLength = planesLength - }); - - allCombinedEdges.AddRangeNoResize(holeEdges); - - edgeOffset += edgesLength; - planeOffset += planesLength; - } - if (baseLoopEdges.Length > 0) - { - int brushNodeOrder = allInfos[baseloopIndex].brushIndexOrder.nodeOrder; - ref var treeSpacePlanes = ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes; - - // TODO: ideally we'd only use the planes that intersect our edges - var planesLength = AddBoundingPlanes(ref alltreeSpacePlanes, ref treeSpacePlanes, baseLoopNormal, in baseLoopEdges, in brushVertices); - var edgesLength = baseLoopEdges.Length; - - allSegments.AddNoResize(new LoopSegment - { - edgeOffset = edgeOffset, - edgeLength = edgesLength, - planesOffset = planeOffset, - planesLength = planesLength - }); - - allCombinedEdges.AddRangeNoResize(baseLoopEdges); - - edgeOffset += edgesLength; - planeOffset += planesLength; - } - - NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref destroyedEdges, edgeOffset); - - var allCombinedEdgesArray = allCombinedEdges.AsArray(); - - // For the destroyedEdges instrumentation (DEDGE logs): brush/surface of this loop, - // so each deletion can be correlated with the NSLE dump of the same brush/surface/loop. - var dbgInfo = allInfos[baseloopIndex]; - var dbgBrush = dbgInfo.brushIndexOrder.nodeOrder; - int dbgSurf = dbgInfo.basePlaneIndex; - - { - { - var segment1 = allSegments[holeIndicesList.Length]; - for (int j = 0; j < holeIndicesList.Length; j++) - { - var segment2 = allSegments[j]; - - if (segment1.edgeLength == 0 || - segment2.edgeLength == 0) - continue; - - for (int e = 0; e < segment1.edgeLength; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(allCombinedEdgesArray[segment1.edgeOffset + e], in alltreeSpacePlanes, in allCombinedEdgesArray, segment2, in brushVertices); - if (kLogStrictCrossing && (category == EdgeCategory.Inside || category == EdgeCategory.Outside) && - BooleanEdgesUtility.EdgeStrictlyCrossesSegmentPlanes(allCombinedEdgesArray[segment1.edgeOffset + e], in alltreeSpacePlanes, segment2, in brushVertices)) - { - var se = allCombinedEdgesArray[segment1.edgeOffset + e]; - LogStrictCrossing(dbgBrush, dbgSurf, baseloopIndex, se.index1, se.index2, category, 'B'); - } - if (category == EdgeCategory.Outside || category == EdgeCategory.Aligned || - (kKeepReverseAlignedBaseEdges && category == EdgeCategory.ReverseAligned)) - continue; - destroyedEdges.Set(segment1.edgeOffset + e, true); - if (kLogDestroyedEdges) - { - var de = allCombinedEdgesArray[segment1.edgeOffset + e]; - LogDestroyedEdge(dbgBrush, dbgSurf, baseloopIndex, de.index1, de.index2, category, 'B'); - } - } - - for (int e = 0; e < segment2.edgeLength; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(allCombinedEdgesArray[segment2.edgeOffset + e], in alltreeSpacePlanes, in allCombinedEdgesArray, segment1, in brushVertices); - if (kLogStrictCrossing && (category == EdgeCategory.Inside || category == EdgeCategory.Outside) && - BooleanEdgesUtility.EdgeStrictlyCrossesSegmentPlanes(allCombinedEdgesArray[segment2.edgeOffset + e], in alltreeSpacePlanes, segment1, in brushVertices)) - { - var se = allCombinedEdgesArray[segment2.edgeOffset + e]; - LogStrictCrossing(dbgBrush, dbgSurf, baseloopIndex, se.index1, se.index2, category, 'h'); - } - if (category == EdgeCategory.Inside) - continue; - destroyedEdges.Set(segment2.edgeOffset + e, true); - if (kLogDestroyedEdges) - { - var de = allCombinedEdgesArray[segment2.edgeOffset + e]; - LogDestroyedEdge(dbgBrush, dbgSurf, baseloopIndex, de.index1, de.index2, category, 'h'); - } - } - } - } - - // TODO: optimize, keep track which holes (potentially) intersect - // TODO: create our own bounds data structure that doesn't use slow properties for everything - { - for (int j = 0, length = MathExtensions.GetTriangleArraySize(holeIndicesList.Length); j < length; j++) - { - var arrayIndex = MathExtensions.GetTriangleArrayIndex(j, holeIndicesList.Length); - var segmentIndex1 = arrayIndex.x; - var segmentIndex2 = arrayIndex.y; - var segment1 = allSegments[segmentIndex1]; - var segment2 = allSegments[segmentIndex2]; - if (segment1.edgeLength > 0 && segment2.edgeLength > 0) - { - for (int e = 0; e < segment1.edgeLength; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(allCombinedEdgesArray[segment1.edgeOffset + e], in alltreeSpacePlanes, in allCombinedEdgesArray, segment2, in brushVertices); - if (category == EdgeCategory.Outside || - category == EdgeCategory.Aligned) - continue; - // Inside only by a fat-band touch: this hole's edge rests on the - // other hole's boundary, which its loop need not run along - if (kKeepEdgesRestingOnTheOtherLoopsBoundary && category == EdgeCategory.Inside && - BooleanEdgesUtility.EdgeRestsOnSegmentPlanes(allCombinedEdgesArray[segment1.edgeOffset + e], in alltreeSpacePlanes, segment2, in brushVertices)) - continue; - destroyedEdges.Set(segment1.edgeOffset + e, true); - if (kLogDestroyedEdges) - { - var de = allCombinedEdgesArray[segment1.edgeOffset + e]; - LogDestroyedEdge(dbgBrush, dbgSurf, baseloopIndex, de.index1, de.index2, category, 'p'); - } - } - - for (int e = 0; e < segment2.edgeLength; e++) - { - var category = BooleanEdgesUtility.CategorizeEdge(allCombinedEdgesArray[segment2.edgeOffset + e], in alltreeSpacePlanes, in allCombinedEdgesArray, segment1, in brushVertices); - if (category == EdgeCategory.Outside) - continue; - // Inside only by a fat-band touch: this hole's edge rests on the - // other hole's boundary, which its loop need not run along - if (kKeepEdgesRestingOnTheOtherLoopsBoundary && category == EdgeCategory.Inside && - BooleanEdgesUtility.EdgeRestsOnSegmentPlanes(allCombinedEdgesArray[segment2.edgeOffset + e], in alltreeSpacePlanes, segment1, in brushVertices)) - continue; - destroyedEdges.Set(segment2.edgeOffset + e, true); - if (kLogDestroyedEdges) - { - var de = allCombinedEdgesArray[segment2.edgeOffset + e]; - LogDestroyedEdge(dbgBrush, dbgSurf, baseloopIndex, de.index1, de.index2, category, 'q'); - } - } - } - } - } - - { - var segment = allSegments[holeIndicesList.Length]; - for (int e = baseLoopEdges.Length - 1; e >= 0; e--) - { - if (!destroyedEdges.IsSet(segment.edgeOffset + e)) - continue; - baseLoopEdges.RemoveAtSwapBack(e); - } - allEdges[baseloopIndex] = baseLoopEdges; - } - - for (int h1 = holeIndicesList.Length - 1; h1 >= 0; h1--) - { - var holeIndex1 = holeIndicesList[h1]; - var holeEdges1 = allEdges[holeIndex1]; - var segment = allSegments[h1]; - for (int e = holeEdges1.Length - 1; e >= 0; e--) - { - if (!destroyedEdges.IsSet(segment.edgeOffset + e)) - continue; - holeEdges1.RemoveAtSwapBack(e); - } - allEdges[holeIndex1] = holeEdges1; - } - } - } - - for (int h = holeIndicesList.Length - 1; h >= 0; h--) - { - var holeIndex = holeIndicesList[h]; - var holeEdges = allEdges[holeIndex]; - - - // TODO: why is baseLoopEdges sometimes not properly allocated? - if (baseLoopEdges.Capacity < baseLoopEdges.Length + holeEdges.Length) - baseLoopEdges.Capacity = baseLoopEdges.Capacity + (holeEdges.Length * 2); - - // Note: can have duplicate edges when multiple holes share an edge - // (only edges between holes and base-loop are guaranteed to not be duplciate) - AddEdgesNoResize(ref baseLoopEdges, in holeEdges); - } - - if (baseLoopEdges.Length >= 3) - LoopEdgeSplitter.RemoveAntiparallelEdgePairs(ref baseLoopEdges); - - if (kRemoveSimpleChords && baseLoopEdges.Length >= 3) - LoopEdgeSplitter.RemoveSimpleChords(ref baseLoopEdges); - - // Re-close a loop that ended up a single open path missing exactly one edge. - // Exact and unambiguous; refuses anything that isn't one clean open chain. - if (kCloseOpenChains && baseLoopEdges.Length >= 2) - LoopEdgeSplitter.CloseSingleOpenChain(ref baseLoopEdges); - - allEdges[baseloopIndex] = baseLoopEdges; - holeIndicesList.Clear(); - holeIndices[baseloopIndex] = holeIndicesList; - } - - // TODO: remove the need for this - for (int l = loopIndices.Length - 1; l >= 0; l--) - { - var baseloopIndex = loopIndices[l]; - var baseLoopEdges = allEdges[baseloopIndex]; - if (baseLoopEdges.Length < 3) - { - loopIndices.RemoveAtSwapBack(l); - continue; - } - if (kLogCleanupAnti) - { - int anti = 0; - for (int a = 0; a < baseLoopEdges.Length; a++) - for (int b = a + 1; b < baseLoopEdges.Length; b++) - if (baseLoopEdges[a].index1 == baseLoopEdges[b].index2 && - baseLoopEdges[a].index2 == baseLoopEdges[b].index1) - anti++; - if (anti > 0) - LogCleanupAnti(in allInfos, baseloopIndex, anti); - } - } - } - - public void Execute(int index) - { - NativeArray categories1 = default; - NativeArray categories2 = default; - NativeArray outEdges = default; - NativeArray intersectedHoleIndices = default; - NativeArray intersectionSurfaceInfo = default; - NativeBitArray destroyedEdges = default; - NativeList allInfos = default; - NativeList intersectionSurfaceInfos = default; - NativeList basePolygonSurfaceInfos = default; - NativeList alltreeSpacePlanes = default; - NativeList allSegments = default; - NativeList allCombinedEdges = default; - NativeList> holeIndices = default; - NativeList> surfaceLoopIndices = default; - NativeList> allEdges = default; - NativeList> intersectionLoops = default; - NativeList> basePolygonEdges = default; - HashedVertices hashedTreeSpaceVertices = default; - NativeArray indexRemap = default; - NativeList> intersectionEdges = default; - try - { - var count = input.BeginForEachIndex(index); - if (count == 0) - { - input.EndForEachIndex(); - - output.BeginForEachIndex(index); - output.Write(new IndexOrder()); - output.Write(0); - output.Write(0); - output.Write(0); - output.EndForEachIndex(); - return; - } - - var brushIndexOrder = input.Read(); - var brushNodeOrder = brushIndexOrder.nodeOrder; - var surfaceCount = input.Read(); - - // This list is already welded upstream; re-welding it may only merge what the brush's own faces allow. - var weldFilter = WeldIncidenceFilter.Disabled; - if (canonicalVertexStage >= CanonicalVertexStage.Everywhere) - weldFilter = WeldIncidenceFilter.SameVertexOnly; // canonical vertices: only the same vertex is merged - else - if (useIncidenceWeld && brushTreeSpacePlaneCache[brushNodeOrder].IsCreated) - weldFilter = WeldIncidenceFilter.Create(ref brushTreeSpacePlaneCache[brushNodeOrder].Value.treeSpacePlanes, surfaceCount); - - var inputVertices = loopVerticesLookup[brushNodeOrder]; - var vertexCount = inputVertices.Length; - - hashedTreeSpaceVertices = new HashedVertices(vertexCount, Allocator.Temp); - //NativeCollectionHelpers.EnsureCapacityAndClear(ref hashedTreeSpaceVertices, vertexCount); - try - { - NativeCollectionHelpers.EnsureMinimumSize(ref indexRemap, vertexCount); - - - hashedTreeSpaceVertices.Clear(); - for (int v = 0; v < inputVertices.Length; v++) - { - var vertex = inputVertices[v]; - indexRemap[v] = hashedTreeSpaceVertices.AddNoResize(vertex, in weldFilter); - } - //Debug.Assert(hashedTreeSpaceVertices.Length == inputVertices.Length); - - var basePolygonEdgesLength = input.Read(); - NativeCollectionHelpers.EnsureSizeAndClear(ref basePolygonSurfaceInfos, basePolygonEdgesLength); - NativeCollectionHelpers.EnsureSizeAndClear(ref basePolygonEdges, basePolygonEdgesLength); - int polygonIndex = 0; - for (int l = 0; l < basePolygonEdgesLength; l++) - { - var indexSurfaceInfo = input.Read(); - //if (l >= basePolygonSurfaceInfos.Length) - // Debug.Log("F"); - basePolygonSurfaceInfos[l] = indexSurfaceInfo; - - var edgesLength = input.Read(); - if (edgesLength == 0) - { - //if (l >= basePolygonEdges.Length) - // Debug.Log("C"); - if (basePolygonEdges[l].IsCreated) - basePolygonEdges[l].Clear(); - continue; - } - //if (l >= basePolygonEdges.Length) - // Debug.Log($"D {l} {basePolygonEdges.Length} {basePolygonSurfaceInfos.Length} {basePolygonEdgesLength} {basePolygonEdges.Length}"); - var edgesInner = new UnsafeList(edgesLength, Allocator.Temp); - //Debug.Log("E"); - //edgesInner.ResizeUninitialized(edgesLength); - for (int e = 0; e < edgesLength; e++) - { - var edge = input.Read(); - edge.index1 = indexRemap[edge.index1]; - edge.index2 = indexRemap[edge.index2]; - if (edge.index1 == edge.index2) - continue; - //Debug.Assert(edge.index1 >= 0 && edge.index1 < hashedTreeSpaceVertices.Length); - //Debug.Assert(edge.index2 >= 0 && edge.index2 < hashedTreeSpaceVertices.Length); - //if (edgesInner.Length + 1 >= edgesInner.Capacity) - // Debug.Log("E"); - edgesInner.AddNoResize(edge); - } - if (edgesInner.Length < 3) - edgesInner.Clear(); - basePolygonEdges[l] = edgesInner; - if (kDebugDumpNonSimpleLoops) - LogLoopDefect('I', brushNodeOrder, l, -1, in edgesInner, in hashedTreeSpaceVertices); - } - //basePolygonSurfaceInfos.ResizeUninitialized(polygonIndex); - //basePolygonEdges.ResizeExact(polygonIndex); - - var intersectionEdgesLength = input.Read(); - NativeCollectionHelpers.EnsureSizeAndClear(ref intersectionSurfaceInfos, intersectionEdgesLength); - NativeCollectionHelpers.EnsureSizeAndClear(ref intersectionEdges, intersectionEdgesLength); - polygonIndex = 0; - for (int l = 0; l < intersectionEdgesLength; l++) - { - var indexSurfaceInfo = input.Read(); - var edgesLength = input.Read(); - var edgesInner = new UnsafeList(edgesLength, Allocator.Temp); - //edgesInner.ResizeUninitialized(edgesLength); - for (int e = 0; e < edgesLength; e++) - { - var edge = input.Read(); - edge.index1 = indexRemap[edge.index1]; - edge.index2 = indexRemap[edge.index2]; - if (edge.index1 == edge.index2) - continue; - //Debug.Assert(edge.index1 >= 0 && edge.index1 < hashedTreeSpaceVertices.Length); - //Debug.Assert(edge.index2 >= 0 && edge.index2 < hashedTreeSpaceVertices.Length); - edgesInner.AddNoResize(edge); - } - if (edgesInner.Length >= 3) - { - intersectionSurfaceInfos[polygonIndex] = indexSurfaceInfo; - intersectionEdges[polygonIndex] = edgesInner; - polygonIndex++; - if (kDebugDumpNonSimpleLoops) - LogLoopDefect('I', brushNodeOrder, indexSurfaceInfo.basePlaneIndex, -1, in edgesInner, in hashedTreeSpaceVertices); - } else - { - edgesInner.Clear(); - intersectionEdges[polygonIndex] = edgesInner; - } - } - intersectionSurfaceInfos.ResizeUninitialized(polygonIndex); - intersectionEdges.Resize(polygonIndex, NativeArrayOptions.ClearMemory); - input.EndForEachIndex(); - TraceInputs(brushNodeOrder, in hashedTreeSpaceVertices, in basePolygonEdges, in intersectionSurfaceInfos, in intersectionEdges); - - //int brushNodeIndex = treeBrushNodeIndices[index]; - - if (surfaceCount == 0) - { - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(0); - output.Write(0); - output.Write(0); - output.EndForEachIndex(); - return; - } - - - BlobAssetReference routingTableRef = routingTableCache[brushNodeOrder]; - if (routingTableRef == BlobAssetReference.Null) - { - //Debug.LogError("No routing table found"); - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(0); - output.Write(0); - output.Write(0); - output.EndForEachIndex(); - return; - } - - - - ref var nodeIDToTableIndex = ref routingTableRef.Value.nodeIDToTableIndex; - ref var nodeIDOffset = ref routingTableRef.Value.nodeIDOffset; - ref var routingLookups = ref routingTableRef.Value.routingLookups; - var routingLookupsLength = routingLookups.Length; - - - int maxIndex = intersectionSurfaceInfos.Length + (surfaceCount * routingLookupsLength); - for (int i = 0; i < intersectionSurfaceInfos.Length; i++) - { - var surfaceInfo = intersectionSurfaceInfos[i]; - var brushNodeID1 = surfaceInfo.brushIndexOrder.compactNodeID; - - // check if brush does not exist in routing table (will not have any effect) - var idWithOffset = brushNodeID1.slotIndex.index - nodeIDOffset; - if (idWithOffset < 0 || idWithOffset >= nodeIDToTableIndex.Length) - continue; - - var routingTableIndex = nodeIDToTableIndex[idWithOffset]; - if (routingTableIndex == -1) - continue; - - var surfaceIndex = surfaceInfo.basePlaneIndex; - maxIndex = math.max(maxIndex, routingTableIndex + (surfaceIndex * routingLookupsLength)); - } - - - // Brush contents: a piece of this brush's surface inside, or against, a brush of another type - // carries the category the contents rules give it into routing - ref var compactTree = ref compactTreeRef.Value.Value; - var processedContents = compactTree.GetBrushContents(brushIndexOrder.compactNodeID); - - int intersectionLoopCount = maxIndex + 1; - NativeCollectionHelpers.EnsureSizeAndClear(ref intersectionLoops, intersectionLoopCount); - NativeCollectionHelpers.EnsureMinimumSize(ref intersectionSurfaceInfo, intersectionLoopCount); - - { - // TODO: Sort the brushSurfaceInfos/intersectionEdges based on nodeIndexToTableIndex[surfaceInfo.brushNodeID], - // have a sequential list of all data. - // Have segment list to determine which part of array belong to which brushNodeID - // Don't need bottom part, can determine this in Job - - for (int i = 0; i < intersectionSurfaceInfos.Length; i++) - { - var surfaceInfo = intersectionSurfaceInfos[i]; - var brushNodeID1 = surfaceInfo.brushIndexOrder.compactNodeID; - - // check if brush does not exist in routing table (will not have any effect) - var idWithOffset = brushNodeID1.slotIndex.index - nodeIDOffset; - if (idWithOffset < 0 || idWithOffset >= nodeIDToTableIndex.Length) - continue; - - var routingTableIndex = nodeIDToTableIndex[idWithOffset]; - if (routingTableIndex == -1) - continue; - - var surfaceIndex = surfaceInfo.basePlaneIndex; - int offset = routingTableIndex + (surfaceIndex * routingLookupsLength); - - var srcEdges = intersectionEdges[i]; - var loops = new UnsafeList(srcEdges.Length, Allocator.Temp); - loops.AddRangeNoResize(srcEdges); - var otherContents = compactTree.GetBrushContents(brushNodeID1); - surfaceInfo.interiorCategory = (byte)ContentsRules.Rewrite((CategoryIndex)surfaceInfo.interiorCategory, - processedContents.Contents, processedContents.Carving, otherContents.Contents, otherContents.Carving, otherContents.Intersecting, - // Brushes are numbered depth first, the order their operations apply in - bIsLater: surfaceInfo.brushIndexOrder.nodeOrder > brushIndexOrder.nodeOrder); - intersectionSurfaceInfo[offset] = surfaceInfo; - intersectionLoops[offset] = loops; - } - } - - - var maxLoops = (routingLookupsLength + routingLookupsLength) * (surfaceCount + surfaceCount); // TODO: find a more reliable "max" - - NativeCollectionHelpers.EnsureCapacityAndClear(ref holeIndices, maxLoops); - NativeCollectionHelpers.EnsureSizeAndClear(ref surfaceLoopIndices, surfaceCount); - NativeCollectionHelpers.EnsureCapacityAndClear(ref allInfos, maxLoops); - NativeCollectionHelpers.EnsureCapacityAndClear(ref allEdges, maxLoops); - - - ref var routingTable = ref routingTableRef.Value; - for (int surfaceIndex = 0; surfaceIndex < surfaceCount; surfaceIndex++) - { - if (!basePolygonEdges[surfaceIndex].IsCreated) - continue; - var basePolygonSrc = basePolygonEdges[surfaceIndex]; - if (basePolygonSrc.Length < 3) - continue; - - var info = basePolygonSurfaceInfos[surfaceIndex]; - info.interiorCategory = 0; // TODO: make sure that it's always set to "0" so we don't need to do this - - var maxAllocation = 1 + (2 * (routingLookupsLength + allEdges.Length)); // TODO: find a more reliable "max" - var maxEdgeAllocation = 1 + (hashedTreeSpaceVertices.Length * 2); - - var loopIndices = new UnsafeList(maxAllocation, Allocator.Temp); - loopIndices.AddNoResize(allEdges.Length); - holeIndices.Add(new UnsafeList(maxAllocation, Allocator.Temp)); - allInfos .AddNoResize(info); - //Debug.Assert(holeIndices.IsAllocated(allInfos.Length - 1)); - - var basePolygonDst = new UnsafeList(basePolygonSrc.Length + maxEdgeAllocation, Allocator.Temp);// TODO: find a more reliable "max" - basePolygonDst.AddRangeNoResize(basePolygonSrc); - allEdges.Add(basePolygonDst); - - //Debug.Assert(allEdges.Length == allInfos.Length); - //Debug.Assert(allInfos.Length == holeIndices.Length); - - for (int routingTableIndex = 0; routingTableIndex < routingLookupsLength; routingTableIndex++) - { - int offset = routingTableIndex + (surfaceIndex * routingLookupsLength); - ref var routingLookup = ref routingLookups[routingTableIndex]; - var intersectionLoop = intersectionLoops[offset]; - var intersectionInfo = intersectionSurfaceInfo[offset]; - for (int l = loopIndices.Length - 1; l >= 0; l--) - { - var surfaceLoopIndex = loopIndices[l]; - var surfaceLoopEdges = allEdges[surfaceLoopIndex]; - if (surfaceLoopEdges.Length < 3) - continue; - - var surfaceLoopInfo = allInfos[surfaceLoopIndex]; - //Debug.Assert(holeIndices.IsAllocated(surfaceLoopIndex)); - - // Lookup categorization between original surface & other surface ... - if (!routingLookup.TryGetRoute(ref routingTable, surfaceLoopInfo.interiorCategory, out CategoryRoutingRow routingRow)) - { - Debug.Assert(false, "Could not find route"); - continue; - } - - var intersectionLength = intersectionLoop.IsCreated ? intersectionLoop.Length : 0; - bool overlap = intersectionLength != 0 && - BooleanEdgesUtility.AreLoopsOverlapping(in surfaceLoopEdges, in intersectionLoop); - - var inCategory = surfaceLoopInfo.interiorCategory; - if (overlap) - { - // If we overlap don't bother with creating a new polygon & hole and just reuse existing polygon + replace category - var overlapCategory = routingRow[(int)intersectionInfo.interiorCategory]; - TraceRoute(brushNodeOrder, surfaceIndex, routingTableIndex, surfaceLoopIndex, inCategory, - intersectionInfo.interiorCategory, intersectionInfo.brushIndexOrder.nodeOrder, intersectionLength, - true, overlapCategory, -1); - surfaceLoopInfo.interiorCategory = overlapCategory; - allInfos[surfaceLoopIndex] = surfaceLoopInfo; - continue; - } else - { - surfaceLoopInfo.interiorCategory = routingRow.outside; - allInfos[surfaceLoopIndex] = surfaceLoopInfo; - } - - // Add all holes that share the same plane to the polygon - if (intersectionLength != 0) - { - // Categorize between original surface & intersection - var intersectionCategory = routingRow[intersectionInfo.interiorCategory]; - TraceRoute(brushNodeOrder, surfaceIndex, routingTableIndex, surfaceLoopIndex, inCategory, - intersectionInfo.interiorCategory, intersectionInfo.brushIndexOrder.nodeOrder, intersectionLength, - false, surfaceLoopInfo.interiorCategory, intersectionCategory); - - // If the intersection polygon would get the same category, we don't need to do a pointless intersection - if (intersectionCategory == surfaceLoopInfo.interiorCategory) - continue; - - IntersectLoops(in hashedTreeSpaceVertices, ref loopIndices, surfaceLoopIndex, - ref holeIndices, ref allInfos, ref allEdges, - - ref outEdges, ref intersectedHoleIndices, - ref categories1, ref categories2, - - in intersectionLoop, - intersectionCategory, - intersectionInfo); - surfaceLoopIndices[surfaceIndex] = loopIndices; - } - } - } - if (kDebugDumpNonSimpleLoops) - { - for (int li = 0; li < loopIndices.Length; li++) - { - var ci = loopIndices[li]; - var ce = allEdges[ci]; - LogLoopDefect('C', brushIndexOrder.nodeOrder, surfaceIndex, ci, in ce, in hashedTreeSpaceVertices); - var chs = holeIndices[ci]; - for (int hi = 0; hi < chs.Length; hi++) - { - var hidx = chs[hi]; - var he = allEdges[hidx]; - LogLoopDefect('C', brushIndexOrder.nodeOrder, surfaceIndex, hidx, in he, in hashedTreeSpaceVertices); - } - } - } - TraceLoops('C', brushNodeOrder, surfaceIndex, in loopIndices, in holeIndices, in allInfos, in allEdges); - CleanUp(in allInfos, ref allEdges, in hashedTreeSpaceVertices, ref loopIndices, ref holeIndices, - ref alltreeSpacePlanes, ref allSegments, ref allCombinedEdges, ref destroyedEdges); - TraceLoops('E', brushNodeOrder, surfaceIndex, in loopIndices, in holeIndices, in allInfos, in allEdges); - surfaceLoopIndices[surfaceIndex] = loopIndices; - } - - output.BeginForEachIndex(index); - output.Write(brushIndexOrder); - output.Write(hashedTreeSpaceVertices.Length); - for (int l = 0; l < hashedTreeSpaceVertices.Length; l++) - output.Write(hashedTreeSpaceVertices[l]); - - output.Write(surfaceLoopIndices.Length); - for (int o = 0; o < surfaceLoopIndices.Length; o++) - { - var inner = surfaceLoopIndices[o]; - if (!inner.IsCreated) - { - output.Write(0); - continue; - } - output.Write(inner.Length); - for (int i = 0; i < inner.Length; i++) - output.Write(inner[i]); - } - - var dbgReferenced = new NativeBitArray(math.max(1, allEdges.Length), Allocator.Temp); - if (kDebugDumpNonSimpleLoops) - { - for (int o = 0; o < surfaceLoopIndices.Length; o++) - { - var inner = surfaceLoopIndices[o]; - if (!inner.IsCreated) - continue; - for (int i = 0; i < inner.Length; i++) - { - var idx = inner[i]; - if (idx >= 0 && idx < allEdges.Length) - dbgReferenced.Set(idx, true); - } - } - } - - output.Write(allEdges.Length); - for (int l = 0; l < allEdges.Length; l++) - { - var surfaceInfo = allInfos[l]; - output.Write(new SurfaceInfo { basePlaneIndex = surfaceInfo.basePlaneIndex, interiorCategory = surfaceInfo.interiorCategory//, nodeIndex = surfaceInfo.brushIndexOrder.nodeIndex - }); - var edges = allEdges[l]; - if (kDebugDumpNonSimpleLoops && dbgReferenced.IsSet(l)) - LogLoopDefect('E', brushIndexOrder.nodeOrder, surfaceInfo.basePlaneIndex, l, in edges, in hashedTreeSpaceVertices); - if (kLogCleanupAnti && edges.Length >= 3) - { - int oanti = 0; - for (int a = 0; a < edges.Length; a++) - for (int b = a + 1; b < edges.Length; b++) - if (edges[a].index1 == edges[b].index2 && edges[a].index2 == edges[b].index1) - oanti++; - if (oanti > 0) - LogCleanupAntiOut(brushIndexOrder.nodeOrder, surfaceInfo.basePlaneIndex, l, oanti); - int dup = 0; - for (int a = 0; a < edges.Length; a++) - { - int ia = edges[a].index1; - for (int b = a + 1; b < edges.Length; b++) - { - int ib = edges[b].index1; - if (ia != ib && math.distancesq(hashedTreeSpaceVertices[ia], hashedTreeSpaceVertices[ib]) < CSGConstants.kSqrVertexEqualEpsilon) - { dup++; break; } - } - } - if (dup > 0) - LogCleanupDup(brushIndexOrder.nodeOrder, surfaceInfo.basePlaneIndex, l, dup); - } - output.Write(edges.Length); - for (int e = 0; e < edges.Length; e++) - output.Write(edges[e]); - } - dbgReferenced.Dispose(); - output.EndForEachIndex(); - } - finally - { - hashedTreeSpaceVertices.Dispose(); - } - } - finally - { - if (categories1.IsCreated) categories1.Dispose(); - if (categories2.IsCreated) categories2.Dispose(); - if (outEdges.IsCreated) outEdges.Dispose(); - if (intersectedHoleIndices.IsCreated) intersectedHoleIndices.Dispose(); - if (intersectionSurfaceInfo.IsCreated) intersectionSurfaceInfo.Dispose(); - if (destroyedEdges.IsCreated) destroyedEdges.Dispose(); - if (allInfos.IsCreated) allInfos.Dispose(); - if (intersectionSurfaceInfos.IsCreated) intersectionSurfaceInfos.Dispose(); - if (basePolygonSurfaceInfos.IsCreated) basePolygonSurfaceInfos.Dispose(); - if (alltreeSpacePlanes.IsCreated) alltreeSpacePlanes.Dispose(); - if (allSegments.IsCreated) allSegments.Dispose(); - if (allCombinedEdges.IsCreated) allCombinedEdges.Dispose(); - if (hashedTreeSpaceVertices.IsCreated) hashedTreeSpaceVertices.Dispose(); - if (indexRemap.IsCreated) indexRemap.Dispose(); - if (holeIndices.IsCreated) - { - if (holeIndices.IsCreated) - { - for (int i = 0; i < holeIndices.Length; i++) - { - if (holeIndices[i].IsCreated) - holeIndices[i].Dispose(); - holeIndices[i] = default; - } - } - holeIndices.Dispose(); - } - if (surfaceLoopIndices.IsCreated) - { - if (surfaceLoopIndices.IsCreated) - { - for (int i = 0; i < surfaceLoopIndices.Length; i++) - { - if (surfaceLoopIndices[i].IsCreated) - surfaceLoopIndices[i].Dispose(); - surfaceLoopIndices[i] = default; - } - } - surfaceLoopIndices.Dispose(); - } - if (allEdges.IsCreated) - { - if (allEdges.IsCreated) - { - for (int i = 0; i < allEdges.Length; i++) - { - if (allEdges[i].IsCreated) - allEdges[i].Dispose(); - allEdges[i] = default; - } - } - allEdges.Dispose(); - } - if (intersectionLoops.IsCreated) - { - if (intersectionLoops.IsCreated) - { - for (int i = 0; i < intersectionLoops.Length; i++) - { - if (intersectionLoops[i].IsCreated) - intersectionLoops[i].Dispose(); - intersectionLoops[i] = default; - } - } - intersectionLoops.Dispose(); - } - if (basePolygonEdges.IsCreated) - { - if (basePolygonEdges.IsCreated) - { - for (int i = 0; i < basePolygonEdges.Length; i++) - { - if (basePolygonEdges[i].IsCreated) - basePolygonEdges[i].Dispose(); - basePolygonEdges[i] = default; - } - } - basePolygonEdges.Dispose(); - } - if (intersectionEdges.IsCreated) - { - if (intersectionEdges.IsCreated) - { - for (int i = 0; i < intersectionEdges.Length; i++) - { - if (intersectionEdges[i].IsCreated) - intersectionEdges[i].Dispose(); - intersectionEdges[i] = default; - } - } - intersectionEdges.Dispose(); - } - } - } - } -} \ No newline at end of file diff --git a/Core/2.Processing/Jobs/PerformCSGJob.cs.meta b/Core/2.Processing/Jobs/PerformCSGJob.cs.meta deleted file mode 100644 index fc5913d2..00000000 --- a/Core/2.Processing/Jobs/PerformCSGJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 97582d934267231418b555e4056f33e5 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs b/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs deleted file mode 100644 index f4af1e3e..00000000 --- a/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs +++ /dev/null @@ -1,623 +0,0 @@ -using System; -using Unity.Burst; -using Unity.Collections; -using Unity.Jobs; -using Unity.Mathematics; -using Unity.Collections.LowLevel.Unsafe; -using Debug = UnityEngine.Debug; -using ReadOnlyAttribute = Unity.Collections.ReadOnlyAttribute; -using WriteOnlyAttribute = Unity.Collections.WriteOnlyAttribute; -using Unity.Entities; - -namespace Chisel.Core -{ - struct PlanePair - { - public float4 plane0; - public float4 plane1; - public float4 edgeVertex0; - public float4 edgeVertex1; - public int planeIndex0; - public int planeIndex1; - } - - [BurstCompile(CompileSynchronously = true)] - struct PrepareBrushPairIntersectionsJob : IJobParallelForDefer - { - const float kFatPlaneWidthEpsilon = CSGConstants.kFatPlaneWidthEpsilon; - const float kPlaneWAlignEpsilon = CSGConstants.kPlaneDAlignEpsilon; - const float kNormalDotAlignEpsilon = CSGConstants.kNormalDotAlignEpsilon; - - // Read - [NoAlias, ReadOnly] public NativeList uniqueBrushPairs; - [NoAlias, ReadOnly] public NativeList transformationCache; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; - - // Shared plane identity (InternBrushPlanesJob). When usePlaneIds is false these are empty - // and alignment falls back to comparing plane equations. - [NoAlias, ReadOnly] public bool usePlaneIds; - [NoAlias, ReadOnly] public NativeList brushPlaneIds; - [NoAlias, ReadOnly] public NativeArray brushPlaneIdRange; - - [NoAlias, ReadOnly] public bool canonicalAlignment; - - // Write - [NoAlias, WriteOnly] public NativeStream.Writer intersectingBrushesStream; - /* - // Per thread scratch memory - [NativeDisableContainerSafetyRestriction] NativeArray intersectingPlaneIndices0; - [NativeDisableContainerSafetyRestriction] NativeArray intersectingPlaneIndices1; - [NativeDisableContainerSafetyRestriction] NativeArray surfaceInfos0; - [NativeDisableContainerSafetyRestriction] NativeArray surfaceInfos1; - [NativeDisableContainerSafetyRestriction] NativeArray localSpacePlanes0; - [NativeDisableContainerSafetyRestriction] NativeArray localSpacePlanes1; - [NativeDisableContainerSafetyRestriction] NativeArray intersectingLocalSpacePlanes0; - [NativeDisableContainerSafetyRestriction] NativeArray intersectingLocalSpacePlanes1; - [NativeDisableContainerSafetyRestriction] NativeArray usedVertices0; - [NativeDisableContainerSafetyRestriction] NativeArray usedVertices1; - [NativeDisableContainerSafetyRestriction] NativeArray vertexUsed; - [NativeDisableContainerSafetyRestriction] NativeArray usedPlanePairs0; - [NativeDisableContainerSafetyRestriction] NativeArray usedPlanePairs1; - [NativeDisableContainerSafetyRestriction] NativeArray vertexIntersectionPlanes0; - [NativeDisableContainerSafetyRestriction] NativeArray vertexIntersectionPlanes1; - [NativeDisableContainerSafetyRestriction] NativeArray vertexIntersectionSegments0; - [NativeDisableContainerSafetyRestriction] NativeArray vertexIntersectionSegments1; - [NativeDisableContainerSafetyRestriction] NativeBitArray planeAvailable; - */ - - // TODO: turn into job - static void GetIntersectingPlanes(IntersectionType type, - [NoAlias] ref BlobArray localPlanes, - int localPlaneCount, - [NoAlias] ref BlobArray vertices, - MinMaxAABB selfBounds, - float4x4 treeToNodeSpaceInverseTransposed, - [NoAlias] ref NativeArray intersectingPlaneIndices, - [NoAlias] out int intersectingPlaneLength, - [NoAlias] out int intersectingPlanesAndEdgesLength) - { - NativeCollectionHelpers.EnsureMinimumSize(ref intersectingPlaneIndices, localPlanes.Length); - if (type != IntersectionType.Intersection) - { - intersectingPlaneLength = localPlaneCount; - intersectingPlanesAndEdgesLength = localPlanes.Length; - for (int i = 0; i < intersectingPlaneLength; i++) intersectingPlaneIndices[i] = i; - return; - } - - var min = selfBounds.Min; - var max = selfBounds.Max; - - //Debug.Log($"{localPlanes.Length}"); - - intersectingPlaneLength = 0; - intersectingPlanesAndEdgesLength = 0; - var verticesLength = vertices.Length; - for (int i = 0; i < localPlanes.Length; i++) - { - // bring plane into local space of mesh, the same space as the bounds of the mesh - - var localPlane = localPlanes[i]; - - // note: a transpose is part of this transformation - var transformedPlane = math.mul(treeToNodeSpaceInverseTransposed, localPlane); - //var normal = transformedPlane.xyz; // only need the signs, so don't care about normalization - //transformedPlane /= math.length(normal); // we don't have to normalize the plane - - var corner = new float4((transformedPlane.x < 0) ? max.x : min.x, - (transformedPlane.y < 0) ? max.y : min.y, - (transformedPlane.z < 0) ? max.z : min.z, - 1.0f); - float forward = math.dot(transformedPlane, corner); - if (forward > kFatPlaneWidthEpsilon) // closest point is outside - { - intersectingPlaneLength = 0; - intersectingPlanesAndEdgesLength = 0; - return; - } - - // do a bounds check - corner = new float4((transformedPlane.x >= 0) ? max.x : min.x, - (transformedPlane.y >= 0) ? max.y : min.y, - (transformedPlane.z >= 0) ? max.z : min.z, - 1.0f); - float backward = math.dot(transformedPlane, corner); - if (backward < -kFatPlaneWidthEpsilon) // closest point is inside - continue; - - float minDistance = float.PositiveInfinity; - float maxDistance = float.NegativeInfinity; - int onCount = 0; - for (int v = 0; v < verticesLength; v++) - { - float distance = math.dot(transformedPlane, new float4(vertices[v], 1)); - minDistance = math.min(distance, minDistance); - maxDistance = math.max(distance, maxDistance); - onCount += (distance >= -kFatPlaneWidthEpsilon && distance <= kFatPlaneWidthEpsilon) ? 1 : 0; - } - - // if all vertices are 'inside' this plane, then we're not truly intersecting with it - if ((minDistance > kFatPlaneWidthEpsilon || maxDistance < -kFatPlaneWidthEpsilon)) - continue; - - if (i < localPlaneCount) - { - intersectingPlaneIndices[intersectingPlaneLength] = i; - intersectingPlaneLength++; - intersectingPlanesAndEdgesLength++; - } else - { - intersectingPlaneIndices[intersectingPlanesAndEdgesLength] = i; - intersectingPlanesAndEdgesLength++; - } - } - } - - // TODO: turn into job - void FindPlanePairs(IntersectionType type, - [NoAlias] ref BrushMeshBlob mesh, - [NoAlias] NativeArray intersectingPlanes, - int intersectingPlanesLength, - [NoAlias] NativeArray localSpacePlanesPtr, - [NoAlias] ref NativeArray vertexUsed, - float4x4 vertexTransform, - bool needTransform, - [NoAlias] ref NativeArray usedPlanePairs, - [NoAlias] ref NativeArray usedVertices, - [NoAlias] ref NativeBitArray planeAvailable, - out int usedPlanePairsLength, - out int usedVerticesLength) - { - NativeCollectionHelpers.EnsureMinimumSize(ref usedVertices, mesh.localVertices.Length); - NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref usedPlanePairs, mesh.halfEdges.Length); - - if (type != IntersectionType.Intersection) - { - usedPlanePairsLength = 0; - usedVerticesLength = mesh.localVertices.Length; - for (int i = 0; i < mesh.localVertices.Length; i++) - usedVertices[i] = mesh.localVertices[i]; - return; - } - NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref vertexUsed, mesh.localVertices.Length); - NativeCollectionHelpers.EnsureMinimumSizeAndClear(ref planeAvailable, mesh.localPlanes.Length); - - // TODO: this can be partially stored in brushmesh - // TODO: optimize - - ref var halfEdgePolygonIndices = ref mesh.halfEdgePolygonIndices; - ref var halfEdges = ref mesh.halfEdges; - - - usedVerticesLength = 0; - usedPlanePairsLength = 0; - - for (int p = 0; p < intersectingPlanesLength; p++) - planeAvailable.Set(intersectingPlanes[p], true); - - for (int e = 0; e < halfEdges.Length; e++) - { - var twinIndex = halfEdges[e].twinIndex; - if (twinIndex < e) - continue; - - var planeIndex0 = halfEdgePolygonIndices[e]; - var planeIndex1 = halfEdgePolygonIndices[twinIndex]; - - //Debug.Assert(planeIndex0 != planeIndex1); - - if (!planeAvailable.IsSet(planeIndex0) || - !planeAvailable.IsSet(planeIndex1)) - continue; - - var plane0 = localSpacePlanesPtr[planeIndex0]; - var plane1 = localSpacePlanesPtr[planeIndex1]; - - var vertexIndex0 = halfEdges[e].vertexIndex; - var vertexIndex1 = halfEdges[twinIndex].vertexIndex; - - var vertex0 = new float4(mesh.localVertices[vertexIndex0], 1); - var vertex1 = new float4(mesh.localVertices[vertexIndex1], 1); - - if (needTransform) - { - vertex0 = math.mul(vertexTransform, vertex0); - vertex1 = math.mul(vertexTransform, vertex1); - } - - if (vertexUsed[vertexIndex0] == 0) { vertexUsed[vertexIndex0] = vertexIndex0 + 1; usedVerticesLength++; } - if (vertexUsed[vertexIndex1] == 0) { vertexUsed[vertexIndex1] = vertexIndex1 + 1; usedVerticesLength++; } - usedPlanePairs[usedPlanePairsLength] = new PlanePair - { - plane0 = plane0, - plane1 = plane1, - edgeVertex0 = vertex0, - edgeVertex1 = vertex1, - planeIndex0 = planeIndex0, - planeIndex1 = planeIndex1 - }; - usedPlanePairsLength++; - } - if (usedVerticesLength > 0) - { - if (usedVerticesLength < mesh.localVertices.Length) - { - for (int i = 0, n = 0; i < mesh.localVertices.Length; i++) - if (vertexUsed[i] != 0) { usedVertices[n] = mesh.localVertices[vertexUsed[i] - 1]; n++; } - } else - { - for (int i = 0; i < mesh.localVertices.Length; i++) - usedVertices[i] = mesh.localVertices[i]; - } - } - } - - - // The encoded id of one brush face, or 0 if it has none. See InternBrushPlanesJob for the - // encoding: (id + 1), negated when the shared plane faces the other way from this face. - readonly int EncodedPlaneId(int nodeOrder, int planeIndex) - { - if (nodeOrder < 0 || nodeOrder >= brushPlaneIdRange.Length) - return 0; - var range = brushPlaneIdRange[nodeOrder]; - if (planeIndex < 0 || planeIndex >= range.y) - return 0; - return brushPlaneIds[range.x + planeIndex]; - } - - void FindAlignedPlanesByID(IntersectionType type, - int nodeOrder0, ref NativeArray intersectingPlaneIndices0, int intersectingPlanesLength0, - int localPlaneCount0, ref NativeArray surfaceInfos0, - int nodeOrder1, ref NativeArray intersectingPlaneIndices1, int intersectingPlanesLength1, - int localPlaneCount1, ref NativeArray surfaceInfos1) - { - NativeCollectionHelpers.EnsureMinimumSize(ref surfaceInfos0, localPlaneCount0); - for (int i = 0; i < localPlaneCount0; i++) - surfaceInfos0[i] = new SurfaceInfo { basePlaneIndex = (ushort)i, interiorCategory = (byte)CategoryIndex.Inside }; - - NativeCollectionHelpers.EnsureMinimumSize(ref surfaceInfos1, localPlaneCount1); - for (int i = 0; i < localPlaneCount1; i++) - surfaceInfos1[i] = new SurfaceInfo { basePlaneIndex = (ushort)i, interiorCategory = (byte)CategoryIndex.Inside }; - - if (type != IntersectionType.Intersection) - return; - - for (int i1 = 0; i1 < intersectingPlanesLength0; i1++) - { - var p1 = intersectingPlaneIndices0[i1]; - var id1 = EncodedPlaneId(nodeOrder0, p1); - if (id1 == 0) - continue; - for (int i2 = 0; i2 < intersectingPlanesLength1; i2++) - { - var p2 = intersectingPlaneIndices1[i2]; - var id2 = EncodedPlaneId(nodeOrder1, p2); - if (id2 == 0) - continue; - - CategoryIndex category; - if (id1 == id2) category = CategoryIndex.Aligned; - else if (id1 == -id2) category = CategoryIndex.ReverseAligned; - else continue; - - var surfaceInfo0 = surfaceInfos0[p1]; - surfaceInfo0.interiorCategory = (byte)category; - surfaceInfos0[p1] = surfaceInfo0; - var surfaceInfo1 = surfaceInfos1[p2]; - surfaceInfo1.interiorCategory = (byte)category; - surfaceInfos1[p2] = surfaceInfo1; - } - } - } - - // work around this, because this can fail hard - const float kPlaneVertexAlignEpsilon = 0.01f; // < kVertexEqualEpsilon (0.0125), below which - // the pipeline already refuses to tell two - // vertices apart - - // Does the face of brush0 lying on `facePlane` also lie on `otherPlane`? Needs at least a - // triangle's worth of vertices on the face, and every one of them within `epsilon` of it. - internal static bool FaceLiesOnPlane(ref BlobArray localVertices0, float4 facePlane, float4 otherPlane, - float epsilon = kPlaneVertexAlignEpsilon) - { - int onFace = 0; - for (int v = 0; v < localVertices0.Length; v++) - { - var vertex = new float4(localVertices0[v], 1); - if (math.abs(math.dot(facePlane, vertex)) > kPlaneWAlignEpsilon) - continue; - onFace++; - if (math.abs(math.dot(otherPlane, vertex)) >= epsilon) - return false; - } - return onFace >= 3; - } - - static void FindAlignedPlanes(IntersectionType type, - ref NativeArray intersectingPlaneIndices0, int intersectingPlanesLength0, - ref NativeArray localSpacePlanes0, int localSpacePlanes0Length, - ref NativeArray surfaceInfos0, - ref NativeArray intersectingPlaneIndices1, int intersectingPlanesLength1, - ref NativeArray localSpacePlanes1, int localSpacePlanes1Length, - ref NativeArray surfaceInfos1, - ref BlobArray localVertices0, - bool canonicalAlignment) - { - NativeCollectionHelpers.EnsureMinimumSize(ref surfaceInfos0, localSpacePlanes0Length); - for (int i = 0; i < localSpacePlanes0Length; i++) - surfaceInfos0[i] = new SurfaceInfo { basePlaneIndex = (ushort)i, interiorCategory = (byte)CategoryIndex.Inside }; - - NativeCollectionHelpers.EnsureMinimumSize(ref surfaceInfos1, localSpacePlanes1Length); - for (int i = 0; i < localSpacePlanes1Length; i++) - surfaceInfos1[i] = new SurfaceInfo { basePlaneIndex = (ushort)i, interiorCategory = (byte)CategoryIndex.Inside }; - - if (type == IntersectionType.Intersection) - { - // decide which planes of brush1 align with brush2 - // TODO: optimize - // TODO: should do this as a separate pass - for (int i1 = 0; i1 < intersectingPlanesLength0; i1++) - { - var p1 = intersectingPlaneIndices0[i1]; - var localPlane1 = localSpacePlanes0[p1]; - for (int i2 = 0; i2 < intersectingPlanesLength1; i2++) - { - var p2 = intersectingPlaneIndices1[i2]; - var localPlane2 = localSpacePlanes1[p2]; - - // Parallel either way round is required; which way decides the category. - var normalDot = math.dot(localPlane1.xyz, localPlane2.xyz); - bool sameFacing = normalDot >= kNormalDotAlignEpsilon; - if (!sameFacing && normalDot > -kNormalDotAlignEpsilon) - continue; - if (!sameFacing) - localPlane2 = -localPlane2; - - bool coincident = math.abs(localPlane1.w - localPlane2.w) < kPlaneWAlignEpsilon - || FaceLiesOnPlane(ref localVertices0, localPlane1, localPlane2, - canonicalAlignment ? kFatPlaneWidthEpsilon : kPlaneVertexAlignEpsilon); - if (!coincident) - continue; - - var category = sameFacing ? CategoryIndex.Aligned : CategoryIndex.ReverseAligned; - var surfaceInfo0 = surfaceInfos0[p1]; - surfaceInfo0.interiorCategory = (byte)category; - surfaceInfos0[p1] = surfaceInfo0; - var surfaceInfo1 = surfaceInfos1[p2]; - surfaceInfo1.interiorCategory = (byte)category; - surfaceInfos1[p2] = surfaceInfo1; - } - } - } - } - - - static void GetLocalAndIndirectPlanes(ref BlobArray inLocalSpacePlanes0, - ref NativeArray outLocalSpacePlanes0, int localSpacePlanes0Length, - ref BlobArray inLocalSpacePlanes1, float4x4 inversedNode1ToNode0, - ref NativeArray outLocalSpacePlanes1, int localSpacePlanes1Length, - ref NativeArray intersectingPlaneIndices0, int intersectingPlanesLength0, - ref NativeArray intersectingLocalSpacePlanes0, - ref NativeArray intersectingPlaneIndices1, int intersectingPlanesLength1, - ref NativeArray intersectingLocalSpacePlanes1) - { - - NativeCollectionHelpers.EnsureMinimumSize(ref outLocalSpacePlanes0, localSpacePlanes0Length); - for (int p = 0; p < localSpacePlanes0Length; p++) - outLocalSpacePlanes0[p] = inLocalSpacePlanes0[p]; - - NativeCollectionHelpers.EnsureMinimumSize(ref outLocalSpacePlanes1, localSpacePlanes1Length); - for (int p = 0; p < localSpacePlanes1Length; p++) - { - var transformedPlane = math.mul(inversedNode1ToNode0, inLocalSpacePlanes1[p]); - outLocalSpacePlanes1[p] = transformedPlane / math.length(transformedPlane.xyz); - } - - NativeCollectionHelpers.EnsureMinimumSize(ref intersectingLocalSpacePlanes0, localSpacePlanes0Length); - if (intersectingPlanesLength0 == outLocalSpacePlanes0.Length) - { - for (int i = 0; i < intersectingPlanesLength0; i++) - intersectingLocalSpacePlanes0[i] = outLocalSpacePlanes0[i]; - } else - { - for (int i = 0; i < intersectingPlanesLength0; i++) - intersectingLocalSpacePlanes0[i] = outLocalSpacePlanes0[intersectingPlaneIndices0[i]]; - } - - NativeCollectionHelpers.EnsureMinimumSize(ref intersectingLocalSpacePlanes1, localSpacePlanes1Length); - if (intersectingPlanesLength1 == outLocalSpacePlanes1.Length) - { - for (int i = 0; i < intersectingPlanesLength1; i++) - intersectingLocalSpacePlanes1[i] = outLocalSpacePlanes1[i]; - } else - { - for (int i = 0; i < intersectingPlanesLength1; i++) - intersectingLocalSpacePlanes1[i] = outLocalSpacePlanes1[intersectingPlaneIndices1[i]]; - } - } - - static void FindPlanesIntersectingVertices(ref NativeArray usedVertices0, int usedVerticesLength0, - ref NativeArray intersectingPlaneIndices0, int intersectingPlanesLength0, - ref BlobArray localPlanes, - ref NativeArray vertexIntersectionPlanes0, ref NativeArray vertexIntersectionSegments0, out int vertexIntersectionPlaneCount0) - { - vertexIntersectionPlaneCount0 = 0; - var vertexIntersectionPlaneMax0 = math.max(1, usedVerticesLength0 * intersectingPlanesLength0); - NativeCollectionHelpers.EnsureMinimumSize(ref vertexIntersectionPlanes0, vertexIntersectionPlaneMax0); - NativeCollectionHelpers.EnsureMinimumSize(ref vertexIntersectionSegments0, usedVerticesLength0); - for (int i = 0; i < usedVerticesLength0; i++) - { - var segment = new int2(vertexIntersectionPlaneCount0, 0); - for (int j = 0; j < intersectingPlanesLength0; j++) - { - var planeIndex = intersectingPlaneIndices0[j]; - var distance = math.dot(localPlanes[planeIndex], new float4(usedVertices0[i], 1)); - if (distance >= -kPlaneWAlignEpsilon && distance <= kPlaneWAlignEpsilon) // Note: this is false on NaN/Infinity, so don't invert - { - vertexIntersectionPlanes0[vertexIntersectionPlaneCount0] = (ushort)planeIndex; - vertexIntersectionPlaneCount0++; - } - } - segment.y = vertexIntersectionPlaneCount0 - segment.x; - vertexIntersectionSegments0[i] = segment; - } - if (vertexIntersectionPlaneCount0 <= 0) - { - vertexIntersectionPlaneCount0 = 1; - vertexIntersectionPlanes0[0] = 0; - } - } - - public void Execute(int index) - { - NativeArray intersectingPlaneIndices0 = default; - NativeArray intersectingPlaneIndices1 = default; - NativeArray surfaceInfos0 = default; - NativeArray surfaceInfos1 = default; - NativeArray localSpacePlanes0 = default; - NativeArray localSpacePlanes1 = default; - NativeArray intersectingLocalSpacePlanes0 = default; - NativeArray intersectingLocalSpacePlanes1 = default; - NativeArray usedVertices0 = default; - NativeArray usedVertices1 = default; - NativeArray vertexUsed = default; - NativeArray usedPlanePairs0 = default; - NativeArray usedPlanePairs1 = default; - NativeArray vertexIntersectionPlanes0 = default; - NativeArray vertexIntersectionPlanes1 = default; - NativeArray vertexIntersectionSegments0 = default; - NativeArray vertexIntersectionSegments1 = default; - NativeBitArray planeAvailable = default; - - try - { - if (index >= uniqueBrushPairs.Length) - goto Fail; - - var brushPair = uniqueBrushPairs[index]; - - if (brushPair.type == IntersectionType.InvalidValue) - goto Fail; - - var brushIndexOrder0 = brushPair.brushIndexOrder0; - var brushIndexOrder1 = brushPair.brushIndexOrder1; - int brushNodeOrder0 = brushIndexOrder0.nodeOrder; - int brushNodeOrder1 = brushIndexOrder1.nodeOrder; - - var blobMesh0 = brushMeshLookup[brushNodeOrder0]; - var blobMesh1 = brushMeshLookup[brushNodeOrder1]; - - - var type = brushPair.type; - if (type != IntersectionType.Intersection && - type != IntersectionType.AInsideB && - type != IntersectionType.BInsideA) - goto Fail; - - - ref var mesh0 = ref blobMesh0.Value; - ref var mesh1 = ref blobMesh1.Value; - - var transformations0 = transformationCache[brushNodeOrder0]; - var transformations1 = transformationCache[brushNodeOrder1]; - - - var node1ToNode0 = math.mul(transformations0.treeToNode, transformations1.nodeToTree); - var inversedNode0ToNode1 = math.transpose(node1ToNode0); - GetIntersectingPlanes(type, ref mesh0.localPlanes, mesh0.localPlaneCount, ref mesh1.localVertices, mesh1.localBounds, inversedNode0ToNode1, ref intersectingPlaneIndices0, out int intersectingPlanesLength0, out int intersectingPlanesAndEdgesLength0); - if (intersectingPlanesLength0 == 0) goto Fail; - - var node0ToNode1 = math.mul(transformations1.treeToNode, transformations0.nodeToTree); - var inversedNode1ToNode0 = math.transpose(node0ToNode1); - GetIntersectingPlanes(type, ref mesh1.localPlanes, mesh1.localPlaneCount, ref mesh0.localVertices, mesh0.localBounds, inversedNode1ToNode0, ref intersectingPlaneIndices1, out int intersectingPlanesLength1, out int intersectingPlanesAndEdgesLength1); - if (intersectingPlanesLength1 == 0) goto Fail; - - // TODO: for each edge of each polygon that is considered an intersecting plane, find the plane that's in between both planes on each side of the edge, and add these planes. - // this is to avoid very sharp plane intersections accepting vertices that are obviously outside of the convex polytope - - GetLocalAndIndirectPlanes(ref mesh0.localPlanes, ref localSpacePlanes0, mesh0.localPlanes.Length, - ref mesh1.localPlanes, inversedNode1ToNode0, ref localSpacePlanes1, mesh1.localPlanes.Length, - ref intersectingPlaneIndices0, intersectingPlanesAndEdgesLength0, ref intersectingLocalSpacePlanes0, - ref intersectingPlaneIndices1, intersectingPlanesAndEdgesLength1, ref intersectingLocalSpacePlanes1); - - FindPlanePairs(type, ref mesh0, intersectingPlaneIndices0, intersectingPlanesLength0, localSpacePlanes0, ref vertexUsed, float4x4.identity, false, ref usedPlanePairs0, ref usedVertices0, ref planeAvailable, out int usedPlanePairsLength0, out int usedVerticesLength0); - FindPlanePairs(type, ref mesh1, intersectingPlaneIndices1, intersectingPlanesLength1, localSpacePlanes1, ref vertexUsed, node1ToNode0, true, ref usedPlanePairs1, ref usedVertices1, ref planeAvailable, out int usedPlanePairsLength1, out int usedVerticesLength1); - - if (usePlaneIds) - FindAlignedPlanesByID(type, brushIndexOrder0.nodeOrder, ref intersectingPlaneIndices0, intersectingPlanesLength0, mesh0.localPlaneCount, ref surfaceInfos0, - brushIndexOrder1.nodeOrder, ref intersectingPlaneIndices1, intersectingPlanesLength1, mesh1.localPlaneCount, ref surfaceInfos1); - else - FindAlignedPlanes(type, ref intersectingPlaneIndices0, intersectingPlanesLength0, ref localSpacePlanes0, mesh0.localPlaneCount, ref surfaceInfos0, - ref intersectingPlaneIndices1, intersectingPlanesLength1, ref localSpacePlanes1, mesh1.localPlaneCount, ref surfaceInfos1, - ref mesh0.localVertices, canonicalAlignment); - - FindPlanesIntersectingVertices(ref usedVertices0, usedVerticesLength0, - ref intersectingPlaneIndices0, intersectingPlanesLength0, - ref mesh0.localPlanes, - ref vertexIntersectionPlanes0, ref vertexIntersectionSegments0, out int vertexIntersectionPlaneCount0); - - FindPlanesIntersectingVertices(ref usedVertices1, usedVerticesLength1, - ref intersectingPlaneIndices1, intersectingPlanesLength1, - ref mesh1.localPlanes, - ref vertexIntersectionPlanes1, ref vertexIntersectionSegments1, out int vertexIntersectionPlaneCount1); - - // TODO: mix writing to stream with filling the lists, this would remove allocation + copy for those lists - intersectingBrushesStream.BeginForEachIndex(index); - intersectingBrushesStream.Write(type); - - intersectingBrushesStream.Write(brushIndexOrder0); - intersectingBrushesStream.Write(transformations0.nodeToTree); - intersectingBrushesStream.Write(node0ToNode1); - intersectingBrushesStream.Write(usedVertices0, usedVerticesLength0); - intersectingBrushesStream.Write(usedPlanePairs0, usedPlanePairsLength0); - intersectingBrushesStream.Write(intersectingLocalSpacePlanes0, intersectingPlanesAndEdgesLength0); - intersectingBrushesStream.Write(intersectingPlanesLength0); - intersectingBrushesStream.Write(intersectingPlaneIndices0, intersectingPlanesLength0); - intersectingBrushesStream.Write(vertexIntersectionPlanes0, vertexIntersectionPlaneCount0); - intersectingBrushesStream.Write(vertexIntersectionSegments0, usedVerticesLength0); - intersectingBrushesStream.Write(surfaceInfos0, mesh0.localPlaneCount); - - intersectingBrushesStream.Write(brushIndexOrder1); - intersectingBrushesStream.Write(transformations1.nodeToTree); - intersectingBrushesStream.Write(node1ToNode0); - intersectingBrushesStream.Write(usedVertices1, usedVerticesLength1); - intersectingBrushesStream.Write(usedPlanePairs1, usedPlanePairsLength1); - intersectingBrushesStream.Write(intersectingLocalSpacePlanes1, intersectingPlanesAndEdgesLength1); - intersectingBrushesStream.Write(intersectingPlanesLength1); - intersectingBrushesStream.Write(intersectingPlaneIndices1, intersectingPlanesLength1); - intersectingBrushesStream.Write(vertexIntersectionPlanes1, vertexIntersectionPlaneCount1); - intersectingBrushesStream.Write(vertexIntersectionSegments1, usedVerticesLength1); - intersectingBrushesStream.Write(surfaceInfos1, mesh1.localPlaneCount); - - intersectingBrushesStream.EndForEachIndex(); - return; - Fail: - intersectingBrushesStream.BeginForEachIndex(index); - intersectingBrushesStream.Write(IntersectionType.InvalidValue); - intersectingBrushesStream.EndForEachIndex(); - return; - } - finally - { - if (intersectingPlaneIndices0.IsCreated) intersectingPlaneIndices0.Dispose(); - if (intersectingPlaneIndices1.IsCreated) intersectingPlaneIndices1.Dispose(); - if (surfaceInfos0.IsCreated) surfaceInfos0.Dispose(); - if (surfaceInfos1.IsCreated) surfaceInfos1.Dispose(); - if (localSpacePlanes0.IsCreated) localSpacePlanes0.Dispose(); - if (localSpacePlanes1.IsCreated) localSpacePlanes1.Dispose(); - if (intersectingLocalSpacePlanes0.IsCreated) intersectingLocalSpacePlanes0.Dispose(); - if (intersectingLocalSpacePlanes1.IsCreated) intersectingLocalSpacePlanes1.Dispose(); - if (usedVertices0.IsCreated) usedVertices0.Dispose(); - if (usedVertices1.IsCreated) usedVertices1.Dispose(); - if (vertexUsed.IsCreated) vertexUsed.Dispose(); - if (usedPlanePairs0.IsCreated) usedPlanePairs0.Dispose(); - if (usedPlanePairs1.IsCreated) usedPlanePairs1.Dispose(); - if (vertexIntersectionPlanes0.IsCreated) vertexIntersectionPlanes0.Dispose(); - if (vertexIntersectionPlanes1.IsCreated) vertexIntersectionPlanes1.Dispose(); - if (vertexIntersectionSegments0.IsCreated) vertexIntersectionSegments0.Dispose(); - if (vertexIntersectionSegments1.IsCreated) vertexIntersectionSegments1.Dispose(); - if (planeAvailable.IsCreated) planeAvailable.Dispose(); - } - } - } -} diff --git a/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs.meta b/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs.meta deleted file mode 100644 index a6ca01bd..00000000 --- a/Core/2.Processing/Jobs/PrepareBrushPairIntersectionsJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3786d4c03b9f93544a55f96342d545da -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs index 31e8e80c..ba55c0c7 100644 --- a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs +++ b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs @@ -57,16 +57,8 @@ static partial class CompactHierarchyManager { const bool runInParallelDefault = true; - const int kMergeIterations = 30; - const int kMaxPropagationRounds = 8; - - public static int LastUpdateRounds { get; private set; } public static int LastUpdateModifiedBrushCount { get; private set; } - public static int LastUpdateStaleBrushCount { get; private set; } - // One line per round and tree: what was modified, how many brushes were updated in total - // (modified + touching) and how many the propagation dirtied for the next round. - public static string LastUpdateLog { get; private set; } = ""; // The debug visualization meshes an update makes (bit i: AssignMeshesJob.kGeneratedDebugRendererFlags[i]); one // added makes every tree's meshes again at the next update, without building any brush again @@ -141,41 +133,12 @@ internal static bool UpdateAllTreeMeshes(FinishMeshUpdate finishMeshUpdates, Can using (kUpdateTreeMeshesProfilerMarker.Auto()) { - TreeUpdate.s_PropagationRound = 0; - TreeUpdate.s_PropagationRequested = false; TreeUpdate.s_ModifiedBrushCount = 0; - TreeUpdate.s_StaleBrushCount = 0; - TreeUpdate.s_Log.Clear(); System.Array.Clear(TreeUpdate.s_LastExactCSGStats, 0, TreeUpdate.s_LastExactCSGStats.Length); allTrees = TreeUpdate.ScheduleTreeMeshJobs(finishMeshUpdates, instance.updatedTrees, canSkipTreeUpdate); TreeUpdate.s_RemakeAll = false; - // Further rounds for the brushes the welding/T-junction propagation dirtied (see - // kMaxPropagationRounds), until nothing moves any more. - while (TreeUpdate.s_PropagationRequested && - TreeUpdate.s_PropagationRound + 1 < kMaxPropagationRounds) - { - TreeUpdate.s_PropagationRound++; - TreeUpdate.s_PropagationRequested = false; - instance.updatedTrees.Clear(); - for (int t = 0; t < instance.allTrees.Length; t++) - { - var tree = instance.allTrees[t]; - if (tree.Valid && - tree.IsStatusFlagSet(NodeStatusFlags.TreeNeedsUpdate)) - instance.updatedTrees.Add(tree); - } - TreeUpdate.s_Log.Append("round ").Append(TreeUpdate.s_PropagationRound) - .Append(": trees=").Append(instance.allTrees.Length) - .Append(" dirty=").Append(instance.updatedTrees.Length).Append('\n'); - if (instance.updatedTrees.Length == 0) - break; - allTrees = JobHandle.CombineDependencies(allTrees, TreeUpdate.ScheduleTreeMeshJobs(finishMeshUpdates, instance.updatedTrees)); - } - LastUpdateRounds = TreeUpdate.s_PropagationRound + 1; LastUpdateModifiedBrushCount = TreeUpdate.s_ModifiedBrushCount; - LastUpdateStaleBrushCount = TreeUpdate.s_StaleBrushCount; - LastUpdateLog = TreeUpdate.s_Log.ToString(); ReportExactCSGFailures(); return true; } @@ -208,39 +171,21 @@ static void ReportExactCSGFailures() internal struct TreeUpdate { - // Propagation-round bookkeeping shared between UpdateAllTreeMeshes (which loops) and - // ScheduleTreeMeshJobs (which dirties the stale brushes); see kMaxPropagationRounds. - internal static int s_PropagationRound; // this round makes every tree's meshes again (DebugVisualizations) internal static bool s_RemakeAll; - internal static bool s_PropagationRequested; internal static int s_ModifiedBrushCount; - internal static int s_StaleBrushCount; - internal static readonly System.Text.StringBuilder s_Log = new(); - - internal static readonly bool kInternBrushPlanes = false; - - internal static readonly bool kUsePlaneIdsForAlignment = false; - - internal static bool kUseIncidenceWeld = false; internal static int kCanonicalVertexStage = 0; internal static bool kCanonicalAlignment = true; - internal static bool kExactCSG = true; - // What the exact CSG counted in the last UpdateAllTreeMeshes call (indexed by ExactCSGStat), summed over trees. internal static readonly int[] s_LastExactCSGStats = new int[(int)ExactCSGStat.Count]; public CanonicalVertexStage canonicalVertexStage; - public bool exactCSG; // ExactCSGCapture is on for this update: ExactCSGJob writes its exact output into Temporaries.exactCapture, and // PreMeshUpdateDispose stores it with every brush's exact planes. public bool captureExact; - // The tree's output was built by the other algorithm (ChiselTreeLookup.Data.builtExact): every brush is rebuilt, - // so an incremental update never leaves one algorithm's output next to the other's. - public bool rebuildAllBrushes; public CSGTree tree; public CompactNodeID treeCompactNodeID; @@ -248,11 +193,6 @@ internal struct TreeUpdate public int maxNodeOrder; public int updateCount; public bool brushListChanged; - // Set when this update dirtied brushes of this tree for another propagation round, so the - // tree's "needs update" flag survives the clean-up that otherwise clears it. - public bool propagationRequested; - // Set when RunMeshUpdateJobs left the meshes for the next propagation round to build. - public bool skipMeshGeneration; // RunMeshUpdateJobs ran for this update (RunMeshCopyJobs follows) public bool ranMeshUpdate; public bool subtractiveWorkflow; @@ -284,20 +224,10 @@ internal struct TemporariesStruct public NativeArray> brushBrushIntersections; public NativeList brushBoundsSweep; - // Shared plane identity (see InternedPlanes / InternBrushPlanesJob). Only built when - // kInternBrushPlanes is on; nothing consumes it yet. - public InternedPlanes internedPlanes; - public NativeList brushPlaneIds; // flat, (id+1), negated when flipped - public NativeArray brushPlaneIdRange; // by nodeOrder: (offset, count) - // Brushes this update left with stale welding/T-junction inputs (StoreLoopVerticesJob); - // read back on the main thread to dirty them for another round. - public NativeList staleLoopBrushes; - // Diagnostics written by StoreLoopVerticesJob (see its kStats* indices); logged per round. - public NativeArray propagationStats; // Counters written by ExactCSGJob (ExactCSGStat); always allocated, a job cannot be scheduled without it. public NativeArray exactCSGStats; // Every brush's bounds as its exact planes make it (ExactBrush.bounds), by node order: what the exact CSG's - // broad phase sweeps instead of the brush meshes' bounds. Minimal when the exact CSG is off. + // broad phase sweeps instead of the brush meshes' bounds. public NativeList exactBounds; // The ExactBrush entries this update replaced (ExactInputJob), disposed once the update is done public NativeList> exactBrushDisposeList; @@ -309,21 +239,9 @@ internal struct TemporariesStruct public NativeList brushesThatNeedIndirectUpdate; public NativeParallelHashSet brushesThatNeedIndirectUpdateHashMap; - public NativeList uniqueBrushPairs; - - public NativeList outputSurfaceVertices; - public NativeList outputSurfaces; - public NativeArray outputSurfacesRange; - public NativeArray> brushMeshLookup; - public NativeArray> loopVerticesLookup; - public NativeArray> loopVerticesLookupOut; - public NativeArray mergeBrushState; public NativeReference surfaceCountRef; - // How many intersection loops CreateIntersectionLoopsJob will write, counted by - // CountIntersectionLoopsJob so its output list can be reserved up front. - public NativeReference intersectionLoopCountRef; public NativeReference> compactTreeRef; public NativeReference needRemappingRef; @@ -394,9 +312,6 @@ internal enum JobHandleType basePolygonCacheJobHandle, brushBrushIntersectionsJobHandle, brushBoundsSweepJobHandle, - internedPlanesJobHandle, - loopVerticesCacheJobHandle, - staleLoopBrushesJobHandle, brushesTouchedByBrushCacheJobHandle, brushRenderBufferCacheJobHandle, brushRenderDataJobHandle, @@ -410,22 +325,13 @@ internal enum JobHandleType brushesThatNeedIndirectUpdateHashMapJobHandle, brushesThatNeedIndirectUpdateJobHandle, brushTreeSpaceBoundCacheJobHandle, - dataStream1JobHandle, dataStream2JobHandle, - intersectingBrushesStreamJobHandle, - loopVerticesLookupJobHandle, - loopVerticesLookupOutJobHandle, - mergeBrushStateJobHandle, meshQueriesJobHandle, nodeIDValueToNodeOrderArrayJobHandle, - outputSurfaceVerticesJobHandle, - outputSurfacesJobHandle, - outputSurfacesRangeJobHandle, routingTableCacheJobHandle, rebuildTreeBrushIndexOrdersJobHandle, sectionsJobHandle, surfaceCountRefJobHandle, - intersectionLoopCountRefJobHandle, compactTreeRefJobHandle, compactHierarchyJobHandle, needRemappingRefJobHandle, @@ -434,7 +340,6 @@ internal enum JobHandleType subMeshDescriptionsJobHandle, treeSpaceVerticesCacheJobHandle, transformationCacheJobHandle, - uniqueBrushPairsJobHandle, vertexBufferContents_renderDescriptorsJobHandle, vertexBufferContents_colliderDescriptorsJobHandle, vertexBufferContents_subMeshSectionsJobHandle, @@ -615,20 +520,13 @@ public void Initialize() Temporaries.brushesThatNeedIndirectUpdateHashMap = new NativeParallelHashSet(brushCount, defaultAllocator); Temporaries.brushesThatNeedIndirectUpdate = new NativeList(brushCount, defaultAllocator); - // TODO: find actual vertex count - Temporaries.outputSurfaceVertices = new NativeList(65535 * 10, defaultAllocator); - - Temporaries.outputSurfaces = new NativeList(brushCount * 32, defaultAllocator); Temporaries.brushIntersectionsWith = new NativeList(brushCount, defaultAllocator); Temporaries.nodeIDValueToNodeOrderOffsetRef = new NativeReference(defaultAllocator); Temporaries.surfaceCountRef = new NativeReference(defaultAllocator); - Temporaries.intersectionLoopCountRef = new NativeReference(defaultAllocator); Temporaries.compactTreeRef = new NativeReference>(defaultAllocator); Temporaries.needRemappingRef = new NativeReference(defaultAllocator); - Temporaries.uniqueBrushPairs = new NativeList(brushCount * 256, defaultAllocator); - Temporaries.rebuildTreeBrushIndexOrders = new NativeList(brushCount, defaultAllocator); Temporaries.allUpdateBrushIndexOrders = new NativeList(brushCount, defaultAllocator); Temporaries.allBrushMeshIDs = new NativeArray(brushCount, defaultAllocator); @@ -637,25 +535,15 @@ public void Initialize() Temporaries.allTreeBrushIndexOrders.Clear(); Temporaries.allTreeBrushIndexOrders.Resize(brushCount, NativeArrayOptions.ClearMemory); - Temporaries.outputSurfacesRange = new NativeArray(brushCount, defaultAllocator); Temporaries.brushIntersectionsWithRange = new NativeArray(brushCount, defaultAllocator); Temporaries.nodeIDValueToNodeOrder = new NativeList(brushCount, defaultAllocator); Temporaries.brushMeshLookup = new NativeArray>(brushCount, defaultAllocator); Temporaries.brushBrushIntersections = new NativeArray>(brushCount, defaultAllocator); Temporaries.brushBoundsSweep = new NativeList(brushCount, defaultAllocator); - Temporaries.brushPlaneIds = new NativeList(kInternBrushPlanes ? brushCount * 6 : 1, defaultAllocator); - Temporaries.brushPlaneIdRange = new NativeArray(kInternBrushPlanes ? brushCount : 1, defaultAllocator); - if (kInternBrushPlanes) - Temporaries.internedPlanes = new InternedPlanes(math.max(1024, brushCount), defaultAllocator); - Temporaries.staleLoopBrushes = new NativeList(brushCount, defaultAllocator); - Temporaries.propagationStats = new NativeArray(StoreLoopVerticesJob.kStatsCount(kMergeIterations), defaultAllocator); Temporaries.exactCSGStats = new NativeArray((int)ExactCSGStat.Count, defaultAllocator); - this.exactCSG = kExactCSG; - this.captureExact = kExactCSG && ExactCSGCapture.Enabled; - this.rebuildAllBrushes = chiselLookupValues.builtExact != exactCSG; - chiselLookupValues.builtExact = exactCSG; - Temporaries.exactBounds = new NativeList(exactCSG ? math.max(1, brushCount) : 1, defaultAllocator); + this.captureExact = ExactCSGCapture.Enabled; + Temporaries.exactBounds = new NativeList(math.max(1, brushCount), defaultAllocator); // The decals, and where they changed since the last update. The next update starts from here. Temporaries.decalVolumes = new NativeArray(chiselLookupValues.decalVolumes.AsArray(), defaultAllocator); @@ -697,13 +585,6 @@ public void Initialize() Temporaries.renderCopyRanges = new NativeList(defaultAllocator); Temporaries.renderCopyChunks = new NativeList(defaultAllocator); - - Temporaries.loopVerticesLookup = new NativeArray>(this.brushCount, defaultAllocator); - Temporaries.loopVerticesLookupOut = new NativeArray>(this.brushCount, defaultAllocator); - // Merge fixpoint worklist: one row of brushCount ran/changed flags per merge pass. - // Pass 0 merges everything regardless, so this starts out cleared. - Temporaries.mergeBrushState = new NativeArray(kMergeIterations * this.brushCount, defaultAllocator); - Temporaries.vertexBufferContents.EnsureInitialized(); #region MeshQueries @@ -745,8 +626,6 @@ public void Initialize() chiselLookupValues.brushTreeSpaceBoundCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); if (chiselLookupValues.brushesTouchedByBrushCache.Length < newBrushCount) chiselLookupValues.brushesTouchedByBrushCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); - if (chiselLookupValues.loopVerticesCache.Length < newBrushCount) - chiselLookupValues.loopVerticesCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); if (chiselLookupValues.exactBrushCache.Length < newBrushCount) chiselLookupValues.exactBrushCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); @@ -756,7 +635,7 @@ public void Initialize() Temporaries.routingTableDisposeList = new NativeList>(chiselLookupValues.routingTableCache.Length, defaultAllocator); Temporaries.brushTreeSpacePlaneDisposeList = new NativeList>(chiselLookupValues.brushTreeSpacePlaneCache.Length, defaultAllocator); Temporaries.brushRenderBufferDisposeList = new NativeList>(chiselLookupValues.brushRenderBufferCache.Length, defaultAllocator); - Temporaries.exactBrushDisposeList = new NativeList>(exactCSG ? math.max(1, chiselLookupValues.exactBrushCache.Length) : 1, defaultAllocator); + Temporaries.exactBrushDisposeList = new NativeList>(math.max(1, chiselLookupValues.exactBrushCache.Length), defaultAllocator); #endregion } @@ -786,8 +665,6 @@ public void Initialize() readonly static ProfilerMarker kJob_FixupBrushCacheIndicesJobProfilerMarker = new("Job_FixupBrushCacheIndicesJob"); readonly static ProfilerMarker kJob_CreateTreeSpaceVerticesAndBoundsJobProfilerMarker = new("Job_CreateTreeSpaceVerticesAndBoundsJob"); readonly static ProfilerMarker kJob_BuildBrushBoundsSweepProfilerMarker = new("Job_BuildBrushBoundsSweep"); - readonly static ProfilerMarker kJob_SeedLoopVerticesProfilerMarker = new("Job_SeedLoopVerticesFromCache"); - readonly static ProfilerMarker kJob_StoreLoopVerticesProfilerMarker = new("Job_StoreLoopVertices"); readonly static ProfilerMarker kJob_FindAllBrushIntersectionPairsProfilerMarker = new("Job_FindAllBrushIntersectionPairs"); readonly static ProfilerMarker kJob_FindUniqueIndirectBrushIntersectionsProfilerMarker = new("Job_FindUniqueIndirectBrushIntersections"); readonly static ProfilerMarker kJob_InvalidateBrushCache_IndirectProfilerMarker = new("Job_InvalidateBrushCache_Indirect"); @@ -795,13 +672,8 @@ public void Initialize() readonly static ProfilerMarker kJob_FindAllBrushIntersectionPairs_IndirectProfilerMarker = new("Job_FindAllBrushIntersectionPairs_Indirect"); readonly static ProfilerMarker kJob_AddIndirectUpdatedBrushesToListAndSortProfilerMarker = new("Job_AddIndirectUpdatedBrushesToListAndSort"); readonly static ProfilerMarker kJob_GatherAndStoreBrushIntersectionsProfilerMarker = new("Job_GatherAndStoreBrushIntersections"); - readonly static ProfilerMarker kJob_PrepareBrushPairIntersectionsProfilerMarker = new("Job_PrepareBrushPairIntersections"); readonly static ProfilerMarker kJob_GenerateBasePolygonLoopsProfilerMarker = new("Job_GenerateBasePolygonLoops"); readonly static ProfilerMarker kJob_UpdateBrushTreeSpacePlanesProfilerMarker = new("Job_UpdateBrushTreeSpacePlanes"); - readonly static ProfilerMarker kJob_CreateIntersectionLoopsProfilerMarker = new("Job_CreateIntersectionLoops"); - readonly static ProfilerMarker kJob_GatherOutputSurfacesProfilerMarker = new("Job_GatherOutputSurfaces"); - readonly static ProfilerMarker kJob_FindLoopOverlapIntersectionsProfilerMarker = new("Job_FindLoopOverlapIntersections"); - readonly static ProfilerMarker kJob_MergeTouchingBrushVerticesIndirectProfilerMarker = new("Job_MergeTouchingBrushVerticesIndirect"); readonly static ProfilerMarker kJob_UpdateBrushCategorizationTablesProfilerMarker = new("Job_UpdateBrushCategorizationTables"); readonly static ProfilerMarker kJob_PerformCSGProfilerMarker = new("Job_PerformCSG"); readonly static ProfilerMarker kJob_GenerateSurfaceTrianglesProfilerMarker = new("Job_GenerateSurfaceTriangles"); @@ -872,8 +744,6 @@ public static JobHandle ScheduleTreeMeshJobs(FinishMeshUpdate finishMeshUpdates, ref var treeUpdate = ref treeUpdates[treeUpdateLength]; treeUpdate.tree = tree; treeUpdate.treeCompactNodeID = treeCompactNodeID; - treeUpdate.propagationRequested = false; - treeUpdate.skipMeshGeneration = false; treeUpdate.ranMeshUpdate = false; treeUpdate.brushListChanged = false; treeUpdateLength++; @@ -881,7 +751,6 @@ public static JobHandle ScheduleTreeMeshJobs(FinishMeshUpdate finishMeshUpdates, if (treeUpdateLength == 0) { - s_Log.Append("round ").Append(s_PropagationRound).Append(": no valid dirty trees (of ").Append(trees.Length).Append(")\n"); return finalJobHandle; } } @@ -1027,7 +896,7 @@ public static JobHandle ScheduleTreeMeshJobs(FinishMeshUpdate finishMeshUpdates, // But it could eventually, optionally, output entities instead at some point // #region Finish Mesh Updates - if (finishMeshUpdates != null && !treeUpdate.skipMeshGeneration) + if (finishMeshUpdates != null) { using (kFinishMeshUpdatesProfilerMarker.Auto()) { @@ -1059,44 +928,7 @@ public static JobHandle ScheduleTreeMeshJobs(FinishMeshUpdate finishMeshUpdates, // Error or not, our jobs need to be completed at this point treeUpdate.dependencies.Complete(); - treeUpdate.JobHandles[JobHandleType.staleLoopBrushesJobHandle].readWriteBarrier.Complete(); s_ModifiedBrushCount += treeUpdate.updateCount; - var staleCount = 0; - var dirtiedCount = 0; - if (s_PropagationRound + 1 < kMaxPropagationRounds && - treeUpdate.Temporaries.staleLoopBrushes.IsCreated) - { - var staleLoopBrushes = treeUpdate.Temporaries.staleLoopBrushes; - staleCount = staleLoopBrushes.Length; - for (int i = 0; i < staleLoopBrushes.Length; i++) - { - if (SetDirty(staleLoopBrushes[i])) - { - s_PropagationRequested = true; - treeUpdate.propagationRequested = true; - s_StaleBrushCount++; - dirtiedCount++; - } - } - } - if (treeUpdate.Temporaries.propagationStats.IsCreated) - { - var stats = treeUpdate.Temporaries.propagationStats; - s_Log.Append(" merge changed/pass:"); - for (int p = 0; p < kMergeIterations; p++) s_Log.Append(' ').Append(stats[p]); - s_Log.Append(" brushesWithChangedLoops=").Append(stats[kMergeIterations]) - .Append(" changedVertices=").Append(stats[kMergeIterations + 1]) - .Append(" marksByChange=").Append(stats[kMergeIterations + 2]) - .Append(" marksByStillMoving=").Append(stats[kMergeIterations + 3]) - .Append('\n'); - } - s_Log.Append("round ").Append(s_PropagationRound) - .Append(": tree ").Append(t) - .Append(" brushes=").Append(treeUpdate.brushCount) - .Append(" modified=").Append(treeUpdate.updateCount) - .Append(" stale=").Append(staleCount) - .Append(" dirtied=").Append(dirtiedCount) - .Append('\n'); // Ensure our meshDataArray ends up being disposed, even if we had errors if (treeUpdate.Temporaries.meshDataArray.Length > 0) @@ -1143,10 +975,6 @@ public static JobHandle ScheduleTreeMeshJobs(FinishMeshUpdate finishMeshUpdates, var treeCompactNodeID = treeUpdates[t].treeCompactNodeID; ref var compactHierarchy = ref CompactHierarchyManager.GetHierarchy(treeCompactNodeID); compactHierarchy.ClearAllStatusFlags(treeCompactNodeID); - // ... except when this update dirtied brushes of this tree for another propagation round - // (see kMaxPropagationRounds): keep it flagged so UpdateAllTreeMeshes picks it up again. - if (treeUpdates[t].propagationRequested) - compactHierarchy.SetStatusFlag(treeCompactNodeID, NodeStatusFlags.TreeNeedsUpdate); } catch { } // Preserve the original exception. } @@ -1216,7 +1044,6 @@ public void RunMeshInitJobs(JobHandle dependsOn) brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, brushTreeSpaceBoundCache = chiselLookupValues.brushTreeSpaceBoundCache, brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - loopVerticesCache = chiselLookupValues.loopVerticesCache, exactBrushCache = chiselLookupValues.exactBrushCache, // Write @@ -1239,7 +1066,6 @@ public void RunMeshInitJobs(JobHandle dependsOn) JobHandleType.brushTreeSpacePlaneCacheJobHandle, JobHandleType.brushTreeSpaceBoundCacheJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, - JobHandleType.loopVerticesCacheJobHandle, JobHandleType.exactBrushCacheJobHandle, JobHandleType.brushesThatNeedIndirectUpdateHashMapJobHandle, JobHandleType.needRemappingRefJobHandle)); @@ -1281,7 +1107,6 @@ public void RunMeshInitJobs(JobHandle dependsOn) brushCount = this.brushCount, allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, compactHierarchy = CompactHierarchyManager.GetReadOnlyHierarchy(treeCompactNodeID), - rebuildAll = this.rebuildAllBrushes, // Read/Write rebuildTreeBrushIndexOrders = Temporaries.rebuildTreeBrushIndexOrders, @@ -1498,7 +1323,6 @@ public void RunMeshUpdateJobs() #endregion #region Exact input - if (exactCSG) { const bool runInParallel = runInParallelDefault; var exactInputJob = new ExactInputJob @@ -1660,7 +1484,7 @@ public void RunMeshUpdateJobs() { // Read (the exact CSG sweeps the bounds of the brushes as their exact planes make them) allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, - brushTreeSpaceBounds = exactCSG ? Temporaries.exactBounds : chiselLookupValues.brushTreeSpaceBoundCache, + brushTreeSpaceBounds = Temporaries.exactBounds, // Write sweepEntries = Temporaries.brushBoundsSweep @@ -1685,12 +1509,9 @@ public void RunMeshUpdateJobs() { // Read allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, - transformationCache = chiselLookupValues.transformationCache, - brushMeshLookup = Temporaries.brushMeshLookup, - brushTreeSpaceBounds = exactCSG ? Temporaries.exactBounds : chiselLookupValues.brushTreeSpaceBoundCache, + brushTreeSpaceBounds = Temporaries.exactBounds, rebuildTreeBrushIndexOrders = Temporaries.rebuildTreeBrushIndexOrders, brushBoundsSweep = Temporaries.brushBoundsSweep, - exactCSG = exactCSG, exactBrushCache = chiselLookupValues.exactBrushCache, // Read / Write @@ -1812,12 +1633,9 @@ public void RunMeshUpdateJobs() { // Read allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, - transformationCache = chiselLookupValues.transformationCache, - brushMeshLookup = Temporaries.brushMeshLookup, - brushTreeSpaceBounds = exactCSG ? Temporaries.exactBounds : chiselLookupValues.brushTreeSpaceBoundCache, + brushTreeSpaceBounds = Temporaries.exactBounds, brushesThatNeedIndirectUpdate = Temporaries.brushesThatNeedIndirectUpdate, brushBoundsSweep = Temporaries.brushBoundsSweep, - exactCSG = exactCSG, exactBrushCache = chiselLookupValues.exactBrushCache, // Read / Write @@ -1919,102 +1737,8 @@ public void RunMeshUpdateJobs() // Determine all surfaces and intersections // - NativeStream intersectingBrushesStream = default; #region Determine Intersection Surfaces // Find all pairs of brush intersections for each brush - if (!exactCSG) - using (kJob_PrepareBrushPairIntersectionsProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - var findBrushPairsJob = new FindBrushPairsJob - { - // Read - maxOrder = brushCount, - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - brushesTouchedByBrushes = chiselLookupValues.brushesTouchedByBrushCache, - - // Read (Re-allocate) / Write - uniqueBrushPairs = Temporaries.uniqueBrushPairs - }; - findBrushPairsJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle), - JobHandles.Write(JobHandleType.uniqueBrushPairsJobHandle)); - - // The pairs are known now, and the brush meshes have been looked up since well - // before this, so what CreateIntersectionLoopsJob will write can be counted here. - var countIntersectionLoopsJob = new CountIntersectionLoopsJob - { - // Read - uniqueBrushPairs = Temporaries.uniqueBrushPairs, - brushMeshLookup = Temporaries.brushMeshLookup, - - // Write - intersectionLoopCountRef = Temporaries.intersectionLoopCountRef - }; - countIntersectionLoopsJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.uniqueBrushPairsJobHandle, - JobHandleType.brushMeshLookupJobHandle), - JobHandles.Write(JobHandleType.intersectionLoopCountRefJobHandle)); - - NativeCollection.ScheduleConstruct(runInParallel, out intersectingBrushesStream, Temporaries.uniqueBrushPairs, - JobHandles.Read( - JobHandleType.uniqueBrushPairsJobHandle - ), - JobHandles.Write( - JobHandleType.intersectingBrushesStreamJobHandle - ), - defaultAllocator); - - if (kInternBrushPlanes) - { - var internBrushPlanesJob = new InternBrushPlanesJob - { - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - brushMeshLookup = Temporaries.brushMeshLookup, - transformationCache = chiselLookupValues.transformationCache, - - // Write - internedPlanes = Temporaries.internedPlanes, - brushPlaneIds = Temporaries.brushPlaneIds, - brushPlaneIdRange = Temporaries.brushPlaneIdRange - }; - internBrushPlanesJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.brushMeshLookupJobHandle, - JobHandleType.transformationCacheJobHandle), - JobHandles.Write( - JobHandleType.internedPlanesJobHandle)); - } - - var prepareBrushPairIntersectionsJob = new PrepareBrushPairIntersectionsJob - { - // Read - uniqueBrushPairs = Temporaries.uniqueBrushPairs, - transformationCache = chiselLookupValues.transformationCache, - brushMeshLookup = Temporaries.brushMeshLookup, - // Shared plane identity; empty when kInternBrushPlanes is off, in which case - // the job falls back to comparing plane equations. - usePlaneIds = kInternBrushPlanes && kUsePlaneIdsForAlignment, - brushPlaneIds = Temporaries.brushPlaneIds, - brushPlaneIdRange = Temporaries.brushPlaneIdRange, - canonicalAlignment = kCanonicalAlignment && canonicalVertexStage >= CanonicalVertexStage.LoopIdentity, - - // Write - intersectingBrushesStream = intersectingBrushesStream.AsWriter() - }; - prepareBrushPairIntersectionsJob.Schedule(runInParallel, Temporaries.uniqueBrushPairs, 1, - JobHandles.Read( - JobHandleType.uniqueBrushPairsJobHandle, - JobHandleType.transformationCacheJobHandle, - JobHandleType.brushMeshLookupJobHandle, - JobHandleType.internedPlanesJobHandle), - JobHandles.Write(JobHandleType.intersectingBrushesStreamJobHandle)); - } using (kJob_UpdateBrushTreeSpacePlanesProfilerMarker.Auto()) { @@ -2069,236 +1793,11 @@ public void RunMeshUpdateJobs() JobHandleType.basePolygonCacheJobHandle)); } - if (!exactCSG) - using (kJob_CreateIntersectionLoopsProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - NativeCollection.ScheduleEnsureCapacity(runInParallel, ref Temporaries.outputSurfaces, Temporaries.intersectionLoopCountRef, - JobHandles.Read( - JobHandleType.intersectionLoopCountRefJobHandle), - JobHandles.Write( - JobHandleType.outputSurfacesJobHandle), - defaultAllocator); - - var createIntersectionLoopsJob = new CreateIntersectionLoopsJob - { - useIncidenceWeld = kUseIncidenceWeld, - // Needed for count (forced & unused) - uniqueBrushPairs = Temporaries.uniqueBrushPairs, - - // Read - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, - treeSpaceVerticesCache = chiselLookupValues.treeSpaceVerticesCache, - intersectingBrushesStream = intersectingBrushesStream.AsReader(), - canonicalVertexStage = canonicalVertexStage, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - - // Write - outputSurfaceVertices = Temporaries.outputSurfaceVertices.AsParallelWriterExt(), - outputSurfaces = Temporaries.outputSurfaces.AsParallelWriter() - }; - var currentJobHandle = createIntersectionLoopsJob.Schedule(runInParallel, Temporaries.uniqueBrushPairs, 8, - JobHandles.Read( - JobHandleType.uniqueBrushPairsJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, - JobHandleType.treeSpaceVerticesCacheJobHandle, - JobHandleType.intersectingBrushesStreamJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle), - JobHandles.Write( - JobHandleType.outputSurfaceVerticesJobHandle, - JobHandleType.outputSurfacesJobHandle)); - - NativeCollection.ScheduleDispose(runInParallel, ref intersectingBrushesStream, currentJobHandle); - } - - if (!exactCSG) - using (kJob_GatherOutputSurfacesProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - var gatherOutputSurfacesJob = new GatherOutputSurfacesJob - { - // Read / Write (Sort) - outputSurfaces = Temporaries.outputSurfaces, - // Write - outputSurfacesRange = Temporaries.outputSurfacesRange - }; - gatherOutputSurfacesJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.outputSurfacesJobHandle), // TODO: support not having any read-handles - JobHandles.Write( - JobHandleType.outputSurfacesJobHandle, - JobHandleType.outputSurfacesRangeJobHandle)); - } - NativeStream dataStream1 = default; - if (!exactCSG) - using (kJob_FindLoopOverlapIntersectionsProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - NativeCollection.ScheduleConstruct(runInParallel, out dataStream1, Temporaries.allUpdateBrushIndexOrders, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle - ), - JobHandles.Write( - JobHandleType.dataStream1JobHandle - ), - defaultAllocator); - - using (kJob_SeedLoopVerticesProfilerMarker.Auto()) - { - var seedLoopVerticesFromCacheJob = new SeedLoopVerticesFromCacheJob - { - // Read - allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - loopVerticesCache = chiselLookupValues.loopVerticesCache, - allocator = defaultAllocator, - - // Write - loopVerticesLookup = Temporaries.loopVerticesLookup - }; - seedLoopVerticesFromCacheJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.allTreeBrushIndexOrdersJobHandle, - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.loopVerticesCacheJobHandle), - JobHandles.Write( - JobHandleType.loopVerticesLookupJobHandle)); - } - - var findLoopOverlapIntersectionsJob = new FindLoopOverlapIntersectionsJob - { - useIncidenceWeld = kUseIncidenceWeld, - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - outputSurfaceVertices = Temporaries.outputSurfaceVertices, - outputSurfaces = Temporaries.outputSurfaces, - outputSurfacesRange = Temporaries.outputSurfacesRange, - maxNodeOrder = maxNodeOrder, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, - basePolygonCache = chiselLookupValues.basePolygonCache, - canonicalVertexStage = canonicalVertexStage, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - - // Read Write - allocator = defaultAllocator, - loopVerticesLookup = Temporaries.loopVerticesLookup, - - // Write - output = dataStream1.AsWriter() - }; - findLoopOverlapIntersectionsJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.outputSurfaceVerticesJobHandle, - JobHandleType.outputSurfacesJobHandle, - JobHandleType.outputSurfacesRangeJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, - JobHandleType.basePolygonCacheJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle), - JobHandles.Write( - JobHandleType.loopVerticesLookupJobHandle, - JobHandleType.dataStream1JobHandle)); - } #endregion - // - // Ensure vertices that should be identical on different brushes, ARE actually identical - // - - #region Merge vertices - if (!exactCSG) - using (kJob_MergeTouchingBrushVerticesIndirectProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - for (int mergeIteration = 0; mergeIteration < kMergeIterations; mergeIteration++) - { - var mergeTouchingBrushVerticesIndirectJob = new MergeTouchingBrushVerticesIndirectJob - { - useIncidenceWeld = kUseIncidenceWeld, - canonicalVertexStage = canonicalVertexStage, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, - basePolygonCache = chiselLookupValues.basePolygonCache, - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - treeSpaceVerticesArray = chiselLookupValues.treeSpaceVerticesCache, - loopVerticesLookup = Temporaries.loopVerticesLookup, - iterationIndex = mergeIteration, - - // Read Write - brushState = Temporaries.mergeBrushState, - // Write - loopVerticesLookupOut = Temporaries.loopVerticesLookupOut, - }; - mergeTouchingBrushVerticesIndirectJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, - JobHandles.Read( - JobHandleType.brushTreeSpacePlaneCacheJobHandle, - JobHandleType.basePolygonCacheJobHandle, - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.treeSpaceVerticesCacheJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle, - JobHandleType.loopVerticesLookupJobHandle), - JobHandles.Write( - JobHandleType.loopVerticesLookupOutJobHandle, - JobHandleType.mergeBrushStateJobHandle)); - - var copyBackLoopVerticesJob = new CopyBackLoopVerticesJob - { - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - loopVerticesLookupOut = Temporaries.loopVerticesLookupOut, - loopVerticesLookup = Temporaries.loopVerticesLookup, - brushState = Temporaries.mergeBrushState, - iterationIndex = mergeIteration, - }; - copyBackLoopVerticesJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.loopVerticesLookupOutJobHandle, - JobHandleType.mergeBrushStateJobHandle), - JobHandles.Write(JobHandleType.loopVerticesLookupJobHandle)); - } - } - #endregion - - #region Persist merged loop vertices, find the brushes this update left stale - if (!exactCSG) - using (kJob_StoreLoopVerticesProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - var storeLoopVerticesJob = new StoreLoopVerticesJob - { - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - allTreeBrushIndexOrders = Temporaries.allTreeBrushIndexOrders, - loopVerticesLookup = Temporaries.loopVerticesLookup, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - brushTreeSpaceBounds = chiselLookupValues.brushTreeSpaceBoundCache, - mergeBrushState = Temporaries.mergeBrushState, - lastMergeIteration = kMergeIterations - 1, - - // Read / Write - loopVerticesCache = chiselLookupValues.loopVerticesCache, - - // Write - staleBrushes = Temporaries.staleLoopBrushes, - stats = Temporaries.propagationStats - }; - storeLoopVerticesJob.Schedule(runInParallel, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.allTreeBrushIndexOrdersJobHandle, - JobHandleType.loopVerticesLookupJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle, - JobHandleType.brushTreeSpaceBoundCacheJobHandle, - JobHandleType.mergeBrushStateJobHandle), - JobHandles.Write( - JobHandleType.loopVerticesCacheJobHandle, - JobHandleType.staleLoopBrushesJobHandle)); - } - #endregion // // Perform CSG on prepared surfaces, giving each surface a categorization @@ -2342,82 +1841,47 @@ public void RunMeshUpdateJobs() ), defaultAllocator); - if (exactCSG) - { - // What a test judges exactly (ExactCSGCapture), written beside the output - if (captureExact) - NativeCollection.ScheduleConstruct(runInParallel, out Temporaries.exactCapture, Temporaries.allUpdateBrushIndexOrders, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle - ), - JobHandles.Write( - JobHandleType.dataStream2JobHandle - ), - defaultAllocator); - else - Temporaries.exactCapture = new NativeStream(1, defaultAllocator); - - // Every face decided exactly, from the brushes' planes and the routing table, and triangulated - var exactCSGJob = new ExactCSGJob - { - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - brushMeshLookup = Temporaries.brushMeshLookup, - exactBrushCache = chiselLookupValues.exactBrushCache, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - routingTableCache = chiselLookupValues.routingTableCache, - compactTreeRef = Temporaries.compactTreeRef, - captureExact = captureExact, - collectDiscarded = CollectDiscarded, + // What a test judges exactly (ExactCSGCapture), written beside the output + if (captureExact) + NativeCollection.ScheduleConstruct(runInParallel, out Temporaries.exactCapture, Temporaries.allUpdateBrushIndexOrders, + JobHandles.Read( + JobHandleType.allUpdateBrushIndexOrdersJobHandle + ), + JobHandles.Write( + JobHandleType.dataStream2JobHandle + ), + defaultAllocator); + else + Temporaries.exactCapture = new NativeStream(1, defaultAllocator); - // Write - stats = Temporaries.exactCSGStats, - output = dataStream2.AsWriter(), - capture = Temporaries.exactCapture.AsWriter(), - }; - exactCSGJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, - JobHandles.Read( - JobHandleType.compactTreeRefJobHandle, - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.brushMeshLookupJobHandle, - JobHandleType.exactBrushCacheJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle, - JobHandleType.routingTableCacheJobHandle), - JobHandles.Write( - JobHandleType.dataStream2JobHandle)); - } else - { - // Perform CSG - var performCSGJob = new PerformCSGJob + // Every face decided exactly, from the brushes' planes and the routing table, and triangulated + var exactCSGJob = new ExactCSGJob { - useIncidenceWeld = kUseIncidenceWeld, - canonicalVertexStage = canonicalVertexStage, // Read allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - routingTableCache = chiselLookupValues.routingTableCache, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, + brushMeshLookup = Temporaries.brushMeshLookup, + exactBrushCache = chiselLookupValues.exactBrushCache, brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, - loopVerticesLookup = Temporaries.loopVerticesLookup, + routingTableCache = chiselLookupValues.routingTableCache, compactTreeRef = Temporaries.compactTreeRef, - input = dataStream1.AsReader(), + captureExact = captureExact, + collectDiscarded = CollectDiscarded, // Write + stats = Temporaries.exactCSGStats, output = dataStream2.AsWriter(), + capture = Temporaries.exactCapture.AsWriter(), }; - var currentJobHandle = performCSGJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, + exactCSGJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, JobHandles.Read( JobHandleType.compactTreeRefJobHandle, JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.routingTableCacheJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, + JobHandleType.brushMeshLookupJobHandle, + JobHandleType.exactBrushCacheJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, - JobHandleType.dataStream1JobHandle, - JobHandleType.loopVerticesLookupJobHandle), + JobHandleType.routingTableCacheJobHandle), JobHandles.Write( JobHandleType.dataStream2JobHandle)); - - NativeCollection.ScheduleDispose(runInParallel, ref dataStream1, currentJobHandle); - } } #endregion @@ -2431,16 +1895,10 @@ public void RunMeshUpdateJobs() const bool runInParallel = runInParallelDefault; var generateSurfaceTrianglesJob = new GenerateSurfaceTrianglesJob { - exactInput = exactCSG, - useIncidenceWeld = kUseIncidenceWeld, - canonicalVertexStage = canonicalVertexStage, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, // Read allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, basePolygonCache = chiselLookupValues.basePolygonCache, transformationCache = chiselLookupValues.transformationCache, - loopVerticesLookup = Temporaries.loopVerticesLookup, - brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, input = dataStream2.AsReader(), meshQueries = Temporaries.meshQueries, entityIDLookup = GetReadOnlyEntityIDLookup(), @@ -2456,20 +1914,15 @@ public void RunMeshUpdateJobs() brushRenderBufferCache = chiselLookupValues.brushRenderBufferCache }; var readHandles = JobHandles.Read( - JobHandleType.brushTreeSpacePlaneCacheJobHandle, JobHandleType.allUpdateBrushIndexOrdersJobHandle, JobHandleType.basePolygonCacheJobHandle, JobHandleType.transformationCacheJobHandle, - JobHandleType.loopVerticesLookupJobHandle, - JobHandleType.brushesTouchedByBrushCacheJobHandle, JobHandleType.dataStream2JobHandle, JobHandleType.meshQueriesJobHandle, JobHandleType.decalVolumesJobHandle, JobHandleType.compactHierarchyJobHandle); // entityIDLookup reads every hierarchy var writeHandles = JobHandles.Write(JobHandleType.brushRenderBufferCacheJobHandle); - var currentJobHandle = exactCSG - ? new GenerateExactSurfaceTrianglesJob { job = generateSurfaceTrianglesJob }.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, readHandles, writeHandles) - : generateSurfaceTrianglesJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, readHandles, writeHandles); + var currentJobHandle = generateSurfaceTrianglesJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 1, readHandles, writeHandles); NativeCollection.ScheduleDispose(runInParallel, ref dataStream2, currentJobHandle); } @@ -2478,13 +1931,6 @@ public void RunMeshUpdateJobs() #endregion - JobHandles[JobHandleType.staleLoopBrushesJobHandle].readWriteBarrier.Complete(); - skipMeshGeneration = Temporaries.staleLoopBrushes.IsCreated && - Temporaries.staleLoopBrushes.Length > 0 && - s_PropagationRound + 1 < kMaxPropagationRounds; - if (skipMeshGeneration) - return; - #region Store Results // TODO: move this out of this method, make it ON DEMAND @@ -2927,8 +2373,6 @@ int FindKeptSections(NativeParallelHashMap hashes) // The meshes of this update, once its sections are known: one per section, so as many are allocated as are used public void RunMeshCopyJobs() { - if (skipMeshGeneration) - return; var chiselLookupValues = ChiselTreeLookup.Value[this.tree]; ref var brushMeshBlobs = ref ChiselMeshLookup.Value.brushMeshBlobCache; @@ -3244,15 +2688,9 @@ public JobHandle PreMeshUpdateDispose() JobHandles[JobHandleType.brushesThatNeedIndirectUpdateJobHandle].writeBarrier, JobHandles[JobHandleType.brushTreeSpaceBoundCacheJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( - JobHandles[JobHandleType.dataStream1JobHandle].writeBarrier, JobHandles[JobHandleType.dataStream2JobHandle].writeBarrier, - JobHandles[JobHandleType.intersectingBrushesStreamJobHandle].writeBarrier, - JobHandles[JobHandleType.loopVerticesLookupJobHandle].writeBarrier, JobHandles[JobHandleType.meshQueriesJobHandle].writeBarrier, - JobHandles[JobHandleType.nodeIDValueToNodeOrderArrayJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfaceVerticesJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfacesJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfacesRangeJobHandle].writeBarrier), + JobHandles[JobHandleType.nodeIDValueToNodeOrderArrayJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.routingTableCacheJobHandle].writeBarrier, JobHandles[JobHandleType.rebuildTreeBrushIndexOrdersJobHandle].writeBarrier, @@ -3260,8 +2698,7 @@ public JobHandle PreMeshUpdateDispose() JobHandles[JobHandleType.subMeshSurfacesJobHandle].writeBarrier, JobHandles[JobHandleType.subMeshDescriptionsJobHandle].writeBarrier, JobHandles[JobHandleType.treeSpaceVerticesCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.transformationCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.uniqueBrushPairsJobHandle].writeBarrier), + JobHandles[JobHandleType.transformationCacheJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.brushesJobHandle].writeBarrier, JobHandles[JobHandleType.nodesJobHandle].writeBarrier, @@ -3290,16 +2727,12 @@ public JobHandle PreMeshUpdateDispose() lastJobHandle.AddDependency(Temporaries.brushIntersectionsWithRange .SafeDispose(JobHandles[JobHandleType.brushIntersectionsWithRangeJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.brushIntersectionsWith .SafeDispose(JobHandles[JobHandleType.brushIntersectionsWithJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.outputSurfaceVertices .SafeDispose(JobHandles[JobHandleType.outputSurfaceVerticesJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.outputSurfacesRange .SafeDispose(JobHandles[JobHandleType.outputSurfacesRangeJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.brushMeshLookup .SafeDispose(JobHandles[JobHandleType.brushMeshLookupJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.outputSurfaces .SafeDispose(JobHandles[JobHandleType.outputSurfacesJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.nodes .SafeDispose(JobHandles[JobHandleType.nodesJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.brushes .SafeDispose(JobHandles[JobHandleType.brushesJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.allBrushMeshIDs .SafeDispose(JobHandles[JobHandleType.allBrushMeshIDsJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.brushRenderData .SafeDispose(JobHandles[JobHandleType.brushRenderDataJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.uniqueBrushPairs .SafeDispose(JobHandles[JobHandleType.uniqueBrushPairsJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.nodeIDValueToNodeOrder .SafeDispose(JobHandles[JobHandleType.nodeIDValueToNodeOrderArrayJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.allUpdateBrushIndexOrders .SafeDispose(JobHandles[JobHandleType.allUpdateBrushIndexOrdersJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.rebuildTreeBrushIndexOrders .SafeDispose(JobHandles[JobHandleType.rebuildTreeBrushIndexOrdersJobHandle].readWriteBarrier)); @@ -3322,35 +2755,23 @@ public JobHandle PreMeshUpdateDispose() lastJobHandle.AddDependency(Temporaries.meshQueries .SafeDispose(JobHandles[JobHandleType.meshQueriesJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.loopVerticesLookupOut .DisposeDeep(JobHandles[JobHandleType.loopVerticesLookupOutJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.mergeBrushState .SafeDispose(JobHandles[JobHandleType.mergeBrushStateJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.brushBoundsSweep .SafeDispose(JobHandles[JobHandleType.brushBoundsSweepJobHandle].readWriteBarrier)); - { - var internedPlanesBarrier = JobHandles[JobHandleType.internedPlanesJobHandle].readWriteBarrier; - if (Temporaries.internedPlanes.IsCreated) - lastJobHandle.AddDependency(Temporaries.internedPlanes.Dispose(internedPlanesBarrier)); - lastJobHandle.AddDependency(Temporaries.brushPlaneIds .SafeDispose(internedPlanesBarrier)); - lastJobHandle.AddDependency(Temporaries.brushPlaneIdRange.SafeDispose(internedPlanesBarrier)); - } { var exactBrushCacheBarrier = JobHandles[JobHandleType.exactBrushCacheJobHandle].readWriteBarrier; lastJobHandle.AddDependency(Temporaries.exactBounds .SafeDispose(exactBrushCacheBarrier)); lastJobHandle.AddDependency(Temporaries.exactBrushDisposeList.DisposeDeep(exactBrushCacheBarrier)); } - lastJobHandle.AddDependency(Temporaries.loopVerticesLookup .DisposeDeep(JobHandles[JobHandleType.loopVerticesLookupJobHandle].readWriteBarrier), - Temporaries.brushBrushIntersections .DisposeDeep(JobHandles[JobHandleType.brushBrushIntersectionsJobHandle].readWriteBarrier), - - Temporaries.basePolygonDisposeList .DisposeDeep(JobHandles[JobHandleType.basePolygonCacheJobHandle].readWriteBarrier), - Temporaries.routingTableDisposeList .DisposeDeep(JobHandles[JobHandleType.routingTableCacheJobHandle].readWriteBarrier), - Temporaries.brushRenderBufferDisposeList .DisposeDeep(JobHandles[JobHandleType.brushRenderBufferCacheJobHandle].readWriteBarrier), - Temporaries.treeSpaceVerticesDisposeList .DisposeDeep(JobHandles[JobHandleType.treeSpaceVerticesCacheJobHandle].readWriteBarrier), - Temporaries.brushTreeSpacePlaneDisposeList .DisposeDeep(JobHandles[JobHandleType.brushTreeSpacePlaneCacheJobHandle].readWriteBarrier), - Temporaries.brushesTouchedByBrushDisposeList.DisposeDeep(JobHandles[JobHandleType.brushesTouchedByBrushCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.brushBrushIntersections .DisposeDeep(JobHandles[JobHandleType.brushBrushIntersectionsJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.basePolygonDisposeList .DisposeDeep(JobHandles[JobHandleType.basePolygonCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.routingTableDisposeList .DisposeDeep(JobHandles[JobHandleType.routingTableCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.brushRenderBufferDisposeList .DisposeDeep(JobHandles[JobHandleType.brushRenderBufferCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.treeSpaceVerticesDisposeList .DisposeDeep(JobHandles[JobHandleType.treeSpaceVerticesCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.brushTreeSpacePlaneDisposeList .DisposeDeep(JobHandles[JobHandleType.brushTreeSpacePlaneCacheJobHandle].readWriteBarrier)); + lastJobHandle.AddDependency(Temporaries.brushesTouchedByBrushDisposeList.DisposeDeep(JobHandles[JobHandleType.brushesTouchedByBrushCacheJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.compactTreeRef .DisposeBlobDeep(JobHandles[JobHandleType.compactTreeRefJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.surfaceCountRef .Dispose(JobHandles[JobHandleType.surfaceCountRefJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.intersectionLoopCountRef .Dispose(JobHandles[JobHandleType.intersectionLoopCountRefJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.needRemappingRef .Dispose(JobHandles[JobHandleType.needRemappingRefJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.nodeIDValueToNodeOrderOffsetRef .Dispose(JobHandles[JobHandleType.nodeIDValueToNodeOrderOffsetRefJobHandle].readWriteBarrier)); @@ -3396,15 +2817,9 @@ public JobHandle FreeTemporaries(ref JobHandle finalJobHandle) JobHandles[JobHandleType.brushesThatNeedIndirectUpdateJobHandle].writeBarrier, JobHandles[JobHandleType.brushTreeSpaceBoundCacheJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( - JobHandles[JobHandleType.dataStream1JobHandle].writeBarrier, JobHandles[JobHandleType.dataStream2JobHandle].writeBarrier, - JobHandles[JobHandleType.intersectingBrushesStreamJobHandle].writeBarrier, - JobHandles[JobHandleType.loopVerticesLookupJobHandle].writeBarrier, JobHandles[JobHandleType.meshQueriesJobHandle].writeBarrier, - JobHandles[JobHandleType.nodeIDValueToNodeOrderArrayJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfaceVerticesJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfacesJobHandle].writeBarrier, - JobHandles[JobHandleType.outputSurfacesRangeJobHandle].writeBarrier), + JobHandles[JobHandleType.nodeIDValueToNodeOrderArrayJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.routingTableCacheJobHandle].writeBarrier, JobHandles[JobHandleType.rebuildTreeBrushIndexOrdersJobHandle].writeBarrier, @@ -3412,8 +2827,7 @@ public JobHandle FreeTemporaries(ref JobHandle finalJobHandle) JobHandles[JobHandleType.subMeshSurfacesJobHandle].writeBarrier, JobHandles[JobHandleType.subMeshDescriptionsJobHandle].writeBarrier, JobHandles[JobHandleType.treeSpaceVerticesCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.transformationCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.uniqueBrushPairsJobHandle].writeBarrier), + JobHandles[JobHandleType.transformationCacheJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.transformTreeBrushIndicesListJobHandle].writeBarrier, JobHandles[JobHandleType.nodesJobHandle].writeBarrier), @@ -3465,8 +2879,6 @@ public JobHandle FreeTemporaries(ref JobHandle finalJobHandle) lastJobHandle.AddDependency(Temporaries.hotspotFind.Dispose(JobHandles[JobHandleType.brushRenderDataJobHandle].readWriteBarrier)); Temporaries.hotspotFind = default; lastJobHandle.AddDependency(Temporaries.allTreeBrushIndexOrders .SafeDispose(JobHandles[JobHandleType.allTreeBrushIndexOrdersJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.staleLoopBrushes .SafeDispose(JobHandles[JobHandleType.staleLoopBrushesJobHandle].readWriteBarrier)); - lastJobHandle.AddDependency(Temporaries.propagationStats .SafeDispose(JobHandles[JobHandleType.staleLoopBrushesJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.exactCSGStats .SafeDispose(JobHandles[JobHandleType.dataStream2JobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.meshUpdatesColliders .SafeDispose(JobHandles[JobHandleType.colliderMeshUpdatesJobHandle].readWriteBarrier)); lastJobHandle.AddDependency(Temporaries.meshUpdatesDebugVisualizations.SafeDispose(JobHandles[JobHandleType.debugHelperMeshesJobHandle].readWriteBarrier)); diff --git a/Core/Tests/AlignedFaceDetectionTests.cs b/Core/Tests/AlignedFaceDetectionTests.cs deleted file mode 100644 index 421bad65..00000000 --- a/Core/Tests/AlignedFaceDetectionTests.cs +++ /dev/null @@ -1,131 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class AlignedFaceDetectionTests - { - // FaceLiesOnPlane takes a BlobArray, so the vertices have to live in a blob - static BlobAssetReference Blob(params float3[] vertices) - { - using var builder = new BlobBuilder(Allocator.Temp); - ref var root = ref builder.ConstructRoot(); - var array = builder.Allocate(ref root.vertices, vertices.Length); - for (int i = 0; i < vertices.Length; i++) - array[i] = vertices[i]; - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - - struct VertexBlob { public BlobArray vertices; } - - static bool FaceLiesOnPlane(float4 facePlane, float4 otherPlane, params float3[] vertices) - { - var blob = Blob(vertices); - var result = PrepareBrushPairIntersectionsJob.FaceLiesOnPlane(ref blob.Value.vertices, facePlane, otherPlane); - blob.Dispose(); - return result; - } - - static bool FaceLiesOnPlaneWithin(float epsilon, float4 facePlane, float4 otherPlane, params float3[] vertices) - { - var blob = Blob(vertices); - var result = PrepareBrushPairIntersectionsJob.FaceLiesOnPlane(ref blob.Value.vertices, facePlane, otherPlane, epsilon); - blob.Dispose(); - return result; - } - - // a unit square on z = 0, far enough from the origin that tilt matters - static readonly float3[] kFaceOnZ0 = - { - new float3(100, 100, 0), - new float3(101, 100, 0), - new float3(101, 101, 0), - new float3(100, 101, 0), - }; - - static readonly float4 kPlaneZ0 = new float4(0, 0, 1, 0); - - [Test] - public void AFaceLyingExactlyOnTheOtherPlane_Qualifies() - { - Assert.That(FaceLiesOnPlane(kPlaneZ0, kPlaneZ0, kFaceOnZ0), Is.True); - } - - [Test] - public void AnOppositeFacingCoincidentPlane_Qualifies() - { - // the caller negates the plane first, so the normals agree: a flipped and negated plane still measures zero distance - Assert.That(FaceLiesOnPlane(kPlaneZ0, -(-kPlaneZ0), kFaceOnZ0), Is.True); - } - - [Test] - public void ATiltedButCoincidentPlane_Qualifies_EvenThoughItsOffsetAtTheOriginDisagrees() - { - var tilt = math.normalize(new float3(1e-4f, 0, 1)); - var tiltedPlane = new float4(tilt, -math.dot(tilt, kFaceOnZ0[0])); - - // the offset comparison the original test used would reject this outright - var offsetDisagreement = math.abs(kPlaneZ0.w - tiltedPlane.w); - Assert.That(offsetDisagreement, Is.GreaterThan(CSGConstants.kPlaneDAlignEpsilon), - "the fixture must actually exercise the tilt case"); - // while the normals are nowhere near far enough apart to be rejected for that - Assert.That(math.dot(kPlaneZ0.xyz, tiltedPlane.xyz), - Is.GreaterThan(CSGConstants.kNormalDotAlignEpsilon)); - - Assert.That(FaceLiesOnPlane(kPlaneZ0, tiltedPlane, kFaceOnZ0), Is.True); - } - - [Test] - public void AGenuinelyOffsetPlane_DoesNotQualify() - { - var farPlane = new float4(0, 0, 1, -1.0f); // a whole unit away - Assert.That(FaceLiesOnPlane(kPlaneZ0, farPlane, kFaceOnZ0), Is.False); - } - - // Missing a match is safer than a wrong one: a wrong match moves geometry - [Test] - public void APlaneJustBeyondTheTolerance_DoesNotQualify() - { - var justTooFar = new float4(0, 0, 1, -0.02f); - Assert.That(FaceLiesOnPlane(kPlaneZ0, justTooFar, kFaceOnZ0), Is.False); - } - - // A face 1 mm from another is a real step that both brushes draw: only faces less than τ apart are aligned - [Test] - public void WithCanonicalAlignment_OnlyFacesLessThanTauApartQualify() - { - var oneMillimetre = new float4(0, 0, 1, -0.001f); - var tenthOfAMillimetre = new float4(0, 0, 1, -0.0001f); - Assert.That(FaceLiesOnPlane(kPlaneZ0, oneMillimetre, kFaceOnZ0), Is.True, "10 mm: aligned"); - Assert.That(FaceLiesOnPlaneWithin(CSGConstants.kFatPlaneWidthEpsilon, kPlaneZ0, oneMillimetre, kFaceOnZ0), Is.False, - "τ: a 1 mm step"); - Assert.That(FaceLiesOnPlaneWithin(CSGConstants.kFatPlaneWidthEpsilon, kPlaneZ0, tenthOfAMillimetre, kFaceOnZ0), Is.True, - "τ: 0.1 mm is the same surface"); - } - - [Test] - public void FewerThanThreeVerticesOnTheFace_DoesNotQualify() - { - // two vertices define a line, not a face - Assert.That(FaceLiesOnPlane(kPlaneZ0, kPlaneZ0, - new float3(100, 100, 0), - new float3(101, 100, 0), - new float3(100, 100, 5)), Is.False); - } - - [Test] - public void VerticesNotOnTheFaceAreIgnored_NotCountedAgainstIt() - { - // only the vertices lying on facePlane are the face; the rest of the brush is irrelevant - Assert.That(FaceLiesOnPlane(kPlaneZ0, kPlaneZ0, - new float3(100, 100, 0), - new float3(101, 100, 0), - new float3(101, 101, 0), - new float3(100, 100, 7), // elsewhere on the brush - new float3(101, 101, 9)), Is.True); - } - } -} diff --git a/Core/Tests/AlignedFaceDetectionTests.cs.meta b/Core/Tests/AlignedFaceDetectionTests.cs.meta deleted file mode 100644 index 007151fd..00000000 --- a/Core/Tests/AlignedFaceDetectionTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: fc52899dd45551b4b804a32c0244f3ed \ No newline at end of file diff --git a/Core/Tests/AlignedFaceLoopDeliveryTests.cs b/Core/Tests/AlignedFaceLoopDeliveryTests.cs deleted file mode 100644 index 7b133173..00000000 --- a/Core/Tests/AlignedFaceLoopDeliveryTests.cs +++ /dev/null @@ -1,241 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class AlignedFaceLoopDeliveryTests - { - struct VertexBlob { public BlobArray vertices; } - - static BlobAssetReference Planes(params float4[] planes) - { - using var builder = new BlobBuilder(Allocator.Temp); - ref var root = ref builder.ConstructRoot(); - var array = builder.Allocate(ref root.treeSpacePlanes, planes.Length); - for (int i = 0; i < planes.Length; i++) - array[i] = planes[i]; - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - - static BlobAssetReference Vertices(params float3[] vertices) - { - using var builder = new BlobBuilder(Allocator.Temp); - ref var root = ref builder.ConstructRoot(); - var array = builder.Allocate(ref root.vertices, vertices.Length); - for (int i = 0; i < vertices.Length; i++) - array[i] = vertices[i]; - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - - // An axis-aligned box as 6 outward tree-space planes: dot(plane, p) <= 0 means inside. - static float4[] BoxPlanes(float3 min, float3 max) - { - return new[] - { - new float4(-1, 0, 0, min.x), - new float4( 1, 0, 0, -max.x), - new float4( 0, -1, 0, min.y), - new float4( 0, 1, 0, -max.y), - new float4( 0, 0, -1, min.z), - new float4( 0, 0, 1, -max.z), - }; - } - - // Brush0 is a unit quad on z = 0 (plane 0 is the face under test), Brush1 a box. Returns how many indices were added for plane 0. - static int AddedIndicesForFace(float3[] faceVertices, float4[] brush1Planes, - CategoryIndex category, int preExistingOnPlane0 = 0, - int facePlaneCount1 = -1) - { - if (facePlaneCount1 < 0) facePlaneCount1 = brush1Planes.Length; - var facePlane = new float4(0, 0, 1, 0); - var brush0 = Planes(facePlane, new float4(0, 0, -1, 0)); - var brush1 = Planes(brush1Planes); - var vertexBlob = Vertices(faceVertices); - - var surfaceInfos = new NativeArray(2, Allocator.Temp); - surfaceInfos[0] = new SurfaceInfo { basePlaneIndex = 0, interiorCategory = (byte)category }; - surfaceInfos[1] = new SurfaceInfo { basePlaneIndex = 1, interiorCategory = (byte)CategoryIndex.Inside }; - - var intersectingPlaneIndices = new NativeArray(new[] { 0, 1 }, Allocator.Temp); - var foundIndices = new NativeArray(64, Allocator.Temp); - - var hashedVertices = new HashedVertices(64, Allocator.Temp); - var snapVertices = new HashedVertices(64, Allocator.Temp); - - int foundIndicesLength = 0; - // pre-seed the plane with vertices, to prove a face that already has a real loop is skipped - for (int i = 0; i < preExistingOnPlane0; i++) - { - var index = hashedVertices.AddNoResize(new float3(-50 - i, -50, 0)); - foundIndices[foundIndicesLength++] = new CreateIntersectionLoopsJob.PlaneVertexIndexPair - { planeIndex = 0, vertexIndex = index }; - } - var before = foundIndicesLength; - - var job = new CreateIntersectionLoopsJob(); - job.AddFullSurfaceLoopsForAlignedFaces( - ref vertexBlob.Value.vertices, - ref brush0.Value, ref brush1.Value, - surfaceInfos, 2, - intersectingPlaneIndices, 2, - facePlaneCount1, - foundIndices, ref foundIndicesLength, - ref hashedVertices, ref snapVertices); - - int addedOnPlane0 = 0; - for (int i = before; i < foundIndicesLength; i++) - if (foundIndices[i].planeIndex == 0) - addedOnPlane0++; - - snapVertices.Dispose(); - hashedVertices.Dispose(); - foundIndices.Dispose(); - intersectingPlaneIndices.Dispose(); - surfaceInfos.Dispose(); - vertexBlob.Dispose(); - brush1.Dispose(); - brush0.Dispose(); - return addedOnPlane0; - } - - // a unit quad on z = 0 - static readonly float3[] kQuadOnZ0 = - { - new float3(0, 0, 0), new float3(1, 0, 0), - new float3(1, 1, 0), new float3(0, 1, 0), - }; - - // A coplanar face wholly inside the other brush gets a loop, so its ReverseAligned category reaches PerformCSGJob - [Test] - public void AReverseAlignedFaceInsideTheOtherBrush_GetsItsOwnVerticesAsALoop() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.ReverseAligned), - Is.EqualTo(4), "all four corners of the face should become the loop"); - } - - [Test] - public void AnAlignedFaceInsideTheOtherBrush_AlsoGetsALoop() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.Aligned), - Is.EqualTo(4)); - } - - [Test] - public void AGapToTheCoplanarCounterpart_DoesNotVetoCoverage() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - covering[5] = new float4(0, 0, 1, -0.0005f); // the coplanar face, a hair outside - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.ReverseAligned), - Is.EqualTo(4)); - } - - // A face only partly covered is left alone: dropping it whole would open a hole - [Test] - public void APartlyCoveredFace_IsLeftAlone() - { - var partial = BoxPlanes(new float3(-1, -1, -1), new float3(0.5f, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, partial, CategoryIndex.ReverseAligned), - Is.EqualTo(0)); - } - - [Test] - public void AFaceCompletelyOutsideTheOtherBrush_IsLeftAlone() - { - var elsewhere = BoxPlanes(new float3(10, 10, 10), new float3(12, 12, 12)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, elsewhere, CategoryIndex.ReverseAligned), - Is.EqualTo(0)); - } - - // Only a coplanar pair is short of vertices; anything else already has a proper loop and must - // not be touched. - [Test] - public void AFaceThatIsNotAligned_IsLeftAlone() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.Inside), Is.EqualTo(0)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.Outside), Is.EqualTo(0)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.SelfAligned), Is.EqualTo(0)); - } - - // A face that already collected the 3 vertices GenerateLoop needs has a real crossing loop; - // adding the whole face on top of it would corrupt that loop. - [Test] - public void AFaceThatAlreadyHasALoop_IsLeftAlone() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.ReverseAligned, - preExistingOnPlane0: 3), - Is.EqualTo(0)); - } - - [Test] - public void EdgePlanesBeyondTheFaceCount_DoNotVetoContainment() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - // six face planes, then edge planes slicing through the middle of the box; every one of - // them would report the face as outside if it were treated as a bound - var withEdgePlanes = new float4[] - { - covering[0], covering[1], covering[2], covering[3], covering[4], covering[5], - new float4(0, 0, 1, -0.5f), - new float4(1, 0, 0, -0.5f), - new float4(math.normalize(new float3(1, 1, 0)), -0.25f), - }; - - // sanity: those extra planes really would reject it - Assert.That(AddedIndicesForFace(kQuadOnZ0, withEdgePlanes, CategoryIndex.ReverseAligned, - facePlaneCount1: withEdgePlanes.Length), - Is.EqualTo(0), "the fixture must actually exercise the edge-plane case"); - - // with the face count respected, the face is contained as it should be - Assert.That(AddedIndicesForFace(kQuadOnZ0, withEdgePlanes, CategoryIndex.ReverseAligned, - facePlaneCount1: 6), - Is.EqualTo(4)); - } - - [Test] - public void TheCounterpartIsTheNearestParallelFace_NotTheFirstOneFound() - { - const float gap = 0.0008f; - Assert.That(gap, Is.GreaterThan(CSGConstants.kFatPlaneWidthEpsilon), - "the fixture must exceed the containment tolerance, or excluding the wrong face would not matter"); - - // box from z = -1 up to z = -gap: the face on z = 0 sits `gap` above its top - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, -gap)); - Assert.That(math.abs(covering[4].z), Is.EqualTo(1f), "the far face must come before the near one"); - Assert.That(covering[5].z, Is.EqualTo(1f)); - - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.ReverseAligned), - Is.EqualTo(4), "excluding the near coincident face leaves only real bounds in the test"); - } - - // Fewer planes than a tetrahedron cannot bound a volume, so containment is meaningless. - [Test] - public void TooFewFacePlanesToBoundAVolume_IsLeftAlone() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - Assert.That(AddedIndicesForFace(kQuadOnZ0, covering, CategoryIndex.ReverseAligned, - facePlaneCount1: 3), - Is.EqualTo(0)); - } - - // Two vertices are an edge, not a face - [Test] - public void AFaceWithFewerThanThreeVerticesOnIt_IsLeftAlone() - { - var covering = BoxPlanes(new float3(-1, -1, -1), new float3(2, 2, 1)); - var twoOnPlane = new[] - { - new float3(0, 0, 0), new float3(1, 0, 0), - new float3(0, 0, 0.5f), // off the face - }; - Assert.That(AddedIndicesForFace(twoOnPlane, covering, CategoryIndex.ReverseAligned), - Is.EqualTo(0)); - } - } -} diff --git a/Core/Tests/AlignedFaceLoopDeliveryTests.cs.meta b/Core/Tests/AlignedFaceLoopDeliveryTests.cs.meta deleted file mode 100644 index 2388a93d..00000000 --- a/Core/Tests/AlignedFaceLoopDeliveryTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 4597d72e3bbb70049a8ac4a7786c00d5 \ No newline at end of file diff --git a/Core/Tests/Contents/CanonicalVertexEditTests.cs b/Core/Tests/Contents/CanonicalVertexEditTests.cs index 4845bd2e..fd257c8e 100644 --- a/Core/Tests/Contents/CanonicalVertexEditTests.cs +++ b/Core/Tests/Contents/CanonicalVertexEditTests.cs @@ -76,10 +76,7 @@ public void AddingABrush_KeepsTheSeamWithABrushThatWasNotRebuilt([Values(0, 2, 3 "the left box changed, so it was rebuilt: " + harness.LastUpdateReport); AssertSeamMatches(harness, left, middle, 1, "after the edit"); - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - AssertStepIsClosed(harness, middle, right, 2); - else if (stage >= CanonicalVertexStage.Positions) - AssertSeamMatches(harness, middle, right, 2, "after the edit"); + AssertStepIsClosed(harness, middle, right, 2); } static void AssertStepIsClosed(ContentsTreeHarness harness, ContentsSceneNode lower, ContentsSceneNode taller, float seamX) diff --git a/Core/Tests/Contents/ConcretePadTests.cs b/Core/Tests/Contents/ConcretePadTests.cs index 306aca39..75bc9d1b 100644 --- a/Core/Tests/Contents/ConcretePadTests.cs +++ b/Core/Tests/Contents/ConcretePadTests.cs @@ -739,16 +739,13 @@ static double MeasureTopFace(ContentsScene scene, string what) using (var harness = ContentsTreeHarness.Build(scene)) { Assert.That(harness.Update(), Is.True, what + ": the CSG update did not run"); - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - ContentsComparison.AssertMatchesOracle(harness, what); + ContentsComparison.AssertMatchesOracle(harness, what); return TopFaceArea(harness); } } static void AssertArea(double area, double expected, double tolerance, string message) { - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - return; Assert.That(area, Is.EqualTo(expected).Within(tolerance), message); } diff --git a/Core/Tests/Contents/ContentsComparison.cs b/Core/Tests/Contents/ContentsComparison.cs index 2f3518b8..b7531a40 100644 --- a/Core/Tests/Contents/ContentsComparison.cs +++ b/Core/Tests/Contents/ContentsComparison.cs @@ -133,85 +133,9 @@ public static List FindMismatchRecords(ContentsTreeHarness harness, out List facesWithoutSamples, float extent, int maxReported, bool budgeted = false) { - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - { - skipped = 0; - return ExactJudge.FindMismatches(harness, maxReported, out checkedSamples, out facesWithoutSamples, - budgeted ? ExactJudge.SweepBudget : ExactJudge.WorkBudget.Unlimited); - } - - var scene = harness.Scene; - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - - var failures = new List(); - facesWithoutSamples = new List(); - checkedSamples = 0; skipped = 0; - - var allBounds = new List<(float3 min, float3 max)>(brushes.Count); - foreach (var other in brushes) - allBounds.Add(ContentsFaceSampler.BrushBounds(oracle.TreePlanesOf(other), extent)); - - for (int b = 0; b < brushes.Count && failures.Count < maxReported; b++) - { - var brush = brushes[b]; - var planes = oracle.TreePlanesOf(brush); - - var otherPlanes = new List(); - var otherBounds = new List<(float3 min, float3 max)>(); - for (int o = 0; o < brushes.Count; o++) - { - if (o == b) - continue; - otherPlanes.Add(oracle.TreePlanesOf(brushes[o])); - otherBounds.Add(allBounds[o]); - } - - for (int p = 0; p < planes.Length && failures.Count < maxReported; p++) - { - // the grid, plus a point in every region the brushes reaching this face cut it into - var samples = ContentsFaceSampler.FaceSamples(planes, p, otherPlanes, otherBounds, extent, kInset, kSpacing, - kCellClearance); - - // A face nothing sampled has not passed - it has not been examined. Recorded so a sweep - // can tell the two apart instead of counting silence as agreement. - if (samples.Count == 0) - facesWithoutSamples.Add($"{brush.name ?? ("brush" + b)} face {p}"); - - foreach (var point in samples) - { - if (ContentsFaceSampler.NearACrossingPlane(otherPlanes, planes[p], point, kCrossingBand)) - { - skipped++; - continue; - } - checkedSamples++; - - var expected = ExpectedFaces(oracle, brushes, planes[p], point); - var found = FoundFaces(harness, planes[p], point); - - var problem = Compare(expected, found); - if (problem != null) - { - var nearest = NearestOtherPlaneDistance(otherPlanes, point); - failures.Add(new Mismatch - { - brush = brush, - brushIndex = b, - face = p, - point = point, - problem = problem, - nearest = nearest - }); - if (failures.Count >= maxReported) - break; - } - } - } - } - - return failures; + return ExactJudge.FindMismatches(harness, maxReported, out checkedSamples, out facesWithoutSamples, + budgeted ? ExactJudge.SweepBudget : ExactJudge.WorkBudget.Unlimited); } public static string MismatchAt(ContentsTreeHarness harness, ContentsOracle oracle, float4 plane, float3 point) diff --git a/Core/Tests/Contents/ContentsHarvest.cs b/Core/Tests/Contents/ContentsHarvest.cs index 6ec8c221..8b2a5f4c 100644 --- a/Core/Tests/Contents/ContentsHarvest.cs +++ b/Core/Tests/Contents/ContentsHarvest.cs @@ -330,17 +330,7 @@ static bool FailsAt(ContentsScene scene, Site site, Stats stats) harness.JudgeEachUpdate = false; if (!harness.Update()) return false; - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - return FailsExactlyAt(harness, site); - var oracle = new ContentsOracle(harness.Scene); - var plane = oracle.TreePlanesOf(brush)[site.face]; - for (int i = 0; i < site.points.Count; i++) - { - var problem = ContentsComparison.MismatchAt(harness, oracle, plane, site.points[i]); - if (problem != null && ContentsComparison.KindOf(problem) == site.kinds[i]) - return true; - } - return false; + return FailsExactlyAt(harness, site); } } catch (Exception e) when (!IsHarnessRefusal(e)) diff --git a/Core/Tests/Contents/ContentsTreeHarness.cs b/Core/Tests/Contents/ContentsTreeHarness.cs index 72d9e9aa..cf6b6a02 100644 --- a/Core/Tests/Contents/ContentsTreeHarness.cs +++ b/Core/Tests/Contents/ContentsTreeHarness.cs @@ -345,7 +345,7 @@ public bool Update() CollapseMoves.Add(move); } } - LastUpdateReport = $"updated={updated}, rounds={CompactHierarchyManager.LastUpdateRounds}, " + + LastUpdateReport = $"updated={updated}, " + $"brushes flagged={CompactHierarchyManager.LastUpdateModifiedBrushCount}; " + $"callbacks: mine={callbacksForThisTree}, other trees={callbacksForOtherTrees}; " + $"renderable sections={renderableSections}, of which with a lookup={sectionsWithLookup}" + @@ -353,26 +353,24 @@ public bool Update() // The exact CSG reports what it could not build instead of repairing it; under the harness any such report is // an error of the CSG, never something to average away in a comparison. - if (CompactHierarchyManager.TreeUpdate.kExactCSG) - { - var failures = ExactCSGFailures(); - LastUpdateReport += "; exact CSG: " + (failures ?? "no failures"); - if (failures != null) - throw new InvalidOperationException("The exact CSG reported failures: " + failures + " (" + LastUpdateReport + ")"); + var failures = ExactCSGFailures(); + LastUpdateReport += "; exact CSG: " + (failures ?? "no failures"); + if (failures != null) + throw new InvalidOperationException("The exact CSG reported failures: " + failures + " (" + LastUpdateReport + ")"); - if (updated && JudgeEachUpdate) + if (updated && JudgeEachUpdate) + { + var mismatches = ExactJudge.FindMismatches(this, kMaxJudgeReports, out _, checkMesh: false); + if (mismatches.Count > 0) { - var mismatches = ExactJudge.FindMismatches(this, kMaxJudgeReports, out _, checkMesh: false); - if (mismatches.Count > 0) - { - var report = new System.Text.StringBuilder("The exact judge disagrees with this update's output:"); - foreach (var mismatch in mismatches) - report.Append(Environment.NewLine).Append(" ").Append(mismatch.Format("exact")); - report.Append(Environment.NewLine).Append(" (").Append(LastUpdateReport).Append(')'); - throw new InvalidOperationException(report.ToString()); - } + var report = new System.Text.StringBuilder("The exact judge disagrees with this update's output:"); + foreach (var mismatch in mismatches) + report.Append(Environment.NewLine).Append(" ").Append(mismatch.Format("exact")); + report.Append(Environment.NewLine).Append(" (").Append(LastUpdateReport).Append(')'); + throw new InvalidOperationException(report.ToString()); } } + // And every delivered update is checked for what the weld at the very end leaves (OutputWeld, ExactCSG.md step 6), // whatever else the test checks, in both pipelines diff --git a/Core/Tests/Contents/ExactCSGProbeTests.cs b/Core/Tests/Contents/ExactCSGProbeTests.cs index b7fb49db..f96504b0 100644 --- a/Core/Tests/Contents/ExactCSGProbeTests.cs +++ b/Core/Tests/Contents/ExactCSGProbeTests.cs @@ -26,51 +26,42 @@ static readonly (string what, Func scene)[] kScenes = [Test] public void TraceFailingFixtures() { - var saved = CompactHierarchyManager.TreeUpdate.kExactCSG; var report = new StringBuilder(); - CompactHierarchyManager.TreeUpdate.kExactCSG = true; - try + foreach (var (fixture, method) in kFixtures) { - foreach (var (fixture, method) in kFixtures) + ContentsHarvest.TraceTo = "exact"; + try { - ContentsHarvest.TraceTo = "exact"; - try - { - var instance = Activator.CreateInstance(fixture); - fixture.GetMethod(method, BindingFlags.Public | BindingFlags.Instance).Invoke(instance, null); - report.AppendLine($"{method}: PASS"); - } - catch (TargetInvocationException e) - { - var message = e.InnerException?.Message ?? e.Message; - report.AppendLine($"{method}: FAIL {message.Split('\n')[0]}"); - } - finally - { - ContentsHarvest.TraceTo = null; - } + var instance = Activator.CreateInstance(fixture); + fixture.GetMethod(method, BindingFlags.Public | BindingFlags.Instance).Invoke(instance, null); + report.AppendLine($"{method}: PASS"); } - foreach (var (what, scene) in kScenes) + catch (TargetInvocationException e) { - ContentsHarvest.TraceTo = "exact"; - try - { - ContentsHarvest.AssertMatchesOracle(scene(), what); - report.AppendLine($"{what}: PASS"); - } - catch (Exception e) - { - report.AppendLine($"{what}: FAIL {e.Message.Split('\n')[0]}"); - } - finally - { - ContentsHarvest.TraceTo = null; - } + var message = e.InnerException?.Message ?? e.Message; + report.AppendLine($"{method}: FAIL {message.Split('\n')[0]}"); + } + finally + { + ContentsHarvest.TraceTo = null; } } - finally + foreach (var (what, scene) in kScenes) { - CompactHierarchyManager.TreeUpdate.kExactCSG = saved; + ContentsHarvest.TraceTo = "exact"; + try + { + ContentsHarvest.AssertMatchesOracle(scene(), what); + report.AppendLine($"{what}: PASS"); + } + catch (Exception e) + { + report.AppendLine($"{what}: FAIL {e.Message.Split('\n')[0]}"); + } + finally + { + ContentsHarvest.TraceTo = null; + } } Assert.Pass(report.ToString()); } @@ -78,53 +69,41 @@ public void TraceFailingFixtures() [Test] public void NestedSubtractionDepths() { - var saved = CompactHierarchyManager.TreeUpdate.kExactCSG; var report = new StringBuilder(); - try + foreach (int depth in new[] { 4, 8, 10, 12, 13, 14, 15, 16 }) { - foreach (var exact in new[] { true, false }) + using (var harness = ContentsTreeHarness.Build(NestedSubtractions(depth))) { - CompactHierarchyManager.TreeUpdate.kExactCSG = exact; - foreach (int depth in new[] { 4, 8, 10, 12, 13, 14, 15, 16 }) + harness.JudgeEachUpdate = false; + harness.Update(); + var mismatches = ContentsComparison.FindMismatchRecords(harness, out int judged, out int skipped, out _, 64f, 3); + var data = ChiselTreeLookup.Value[harness.Tree]; + int maxRows = 0, maxLookupRows = 0, maxDestination = 0, tables = 0; + for (int i = 0; i < data.routingTableCache.Length; i++) { - using (var harness = ContentsTreeHarness.Build(NestedSubtractions(depth))) + var table = data.routingTableCache[i]; + if (!table.IsCreated) continue; + tables++; + ref var rows = ref table.Value.routingRows; + maxRows = Math.Max(maxRows, rows.Length); + ref var lookups = ref table.Value.routingLookups; + for (int k = 0; k < lookups.Length; k++) + maxLookupRows = Math.Max(maxLookupRows, lookups[k].endIndex - lookups[k].startIndex); + for (int r = 0; r < rows.Length; r++) { - harness.JudgeEachUpdate = false; - harness.Update(); - var mismatches = ContentsComparison.FindMismatchRecords(harness, out int judged, out int skipped, out _, 64f, 3); - var data = ChiselTreeLookup.Value[harness.Tree]; - int maxRows = 0, maxLookupRows = 0, maxDestination = 0, tables = 0; - for (int i = 0; i < data.routingTableCache.Length; i++) - { - var table = data.routingTableCache[i]; - if (!table.IsCreated) continue; - tables++; - ref var rows = ref table.Value.routingRows; - maxRows = Math.Max(maxRows, rows.Length); - ref var lookups = ref table.Value.routingLookups; - for (int k = 0; k < lookups.Length; k++) - maxLookupRows = Math.Max(maxLookupRows, lookups[k].endIndex - lookups[k].startIndex); - for (int r = 0; r < rows.Length; r++) - { - var row = rows[r]; - foreach (var destination in new[] { (int)row.inside, (int)row.aligned, (int)row.selfAligned, - (int)row.selfReverseAligned, (int)row.reverseAligned, (int)row.outside }) - maxDestination = Math.Max(maxDestination, destination); - } - } - report.Append($"{(exact ? "exact" : "loops")} depth {depth}: {mismatches.Count} mismatch(es) over {judged} judged ({skipped} skipped); ") - .Append($"{tables} tables, rows max {maxRows}, rows per lookup max {maxLookupRows}, destination max {maxDestination}"); - if (mismatches.Count > 0) - report.Append("; first: ").Append(mismatches[0].Format("n")); - report.AppendLine(); + var row = rows[r]; + foreach (var destination in new[] { (int)row.inside, (int)row.aligned, (int)row.selfAligned, + (int)row.selfReverseAligned, (int)row.reverseAligned, (int)row.outside }) + maxDestination = Math.Max(maxDestination, destination); } } + report.Append($"depth {depth}: {mismatches.Count} mismatch(es) over {judged} judged ({skipped} skipped); ") + .Append($"{tables} tables, rows max {maxRows}, rows per lookup max {maxLookupRows}, destination max {maxDestination}"); + if (mismatches.Count > 0) + report.Append("; first: ").Append(mismatches[0].Format("n")); + report.AppendLine(); } } - finally - { - CompactHierarchyManager.TreeUpdate.kExactCSG = saved; - } // Also to the log: a script that runs this reads the numbers there, where a passed result carries no message UnityEngine.Debug.Log("ExactCSGProbeTests.NestedSubtractionDepths\n" + report); Assert.Pass(report.ToString()); @@ -146,126 +125,52 @@ static ContentsScene NestedSubtractions(int depth) } // Does every output triangle lie on a face plane of the brush it is attributed to? Written to - // Library/ChiselHarvest/traces/attribution_*.txt for the exact CSG and for the loop pipeline. + // Library/ChiselHarvest/traces/attribution_model2f04.txt. [Test] public void CheckTriangleAttribution() { - var saved = CompactHierarchyManager.TreeUpdate.kExactCSG; var report = new StringBuilder(); - try - { - foreach (var exact in new[] { true, false }) - { - CompactHierarchyManager.TreeUpdate.kExactCSG = exact; - var text = new StringBuilder(); - var scene = Model2F04Scene(); - using (var harness = ContentsTreeHarness.Build(scene)) - { - harness.Update(); - var brushes = new System.Collections.Generic.List(); - foreach (var root in scene.roots) AddBrushes(root, brushes); - int wrong = 0, total = 0; - for (int t = 0; t < harness.Triangles.Count; t++) - { - var triangle = harness.Triangles[t]; - var node = harness.NodeOf(triangle.brushID); - total++; - bool onOwn = node != null && OnAPlaneOf(node, triangle); - if (onOwn) - continue; - wrong++; - var owners = new StringBuilder(); - foreach (var other in brushes) - if (OnAPlaneOf(other, triangle)) owners.Append(other.name).Append(' '); - text.AppendLine($"triangle {t} section {triangle.section} material {triangle.material} attributed to {node?.name ?? "nothing"} " + - $"({triangle.a}, {triangle.b}, {triangle.c}) lies on a plane of: {owners}"); - } - // The failing sample point on Extruded Shape #172's bottom, and every triangle of Box (15) - var point = new Unity.Mathematics.float3(-11.988f, -1f, -13.033f); - var bottom = new Unity.Mathematics.float4(0, -1, 0, -1); - for (int t = 0; t < harness.Triangles.Count; t++) - { - var triangle = harness.Triangles[t]; - var node = harness.NodeOf(triangle.brushID); - bool covers = ContentsComparison.Covers(triangle.a, triangle.b, triangle.c, bottom, point, out int facing); - if (covers || (node != null && node.name.StartsWith("Box (15)"))) - text.AppendLine($" {(covers ? "COVERS P " : "")}triangle {t} of {node?.name ?? "nothing"} facing {(covers ? facing : 0)}: {triangle.a} {triangle.b} {triangle.c}"); - } - text.Insert(0, $"exact={exact}: {wrong} of {total} triangles are not on a plane of the brush they are attributed to\n"); - report.AppendLine($"exact={exact}: {wrong} of {total} misattributed"); - } - var directory = System.IO.Path.Combine("Library", "ChiselHarvest", "traces"); - System.IO.Directory.CreateDirectory(directory); - System.IO.File.WriteAllText(System.IO.Path.Combine(directory, $"attribution_model2f04_{(exact ? "exact" : "loops")}.txt"), text.ToString()); - } - } - finally - { - CompactHierarchyManager.TreeUpdate.kExactCSG = saved; - } - Assert.Pass(report.ToString()); - } - - [Test] - public void CheckLoopHoleCoverage() - { - var saved = CompactHierarchyManager.TreeUpdate.kExactCSG; var text = new StringBuilder(); - CompactHierarchyManager.TreeUpdate.kExactCSG = false; - try + var scene = Model2F04Scene(); + using (var harness = ContentsTreeHarness.Build(scene)) { - var build = typeof(IntersectionLoopCapacityTests).GetMethod("MutuallyOverlappingBrushes", BindingFlags.NonPublic | BindingFlags.Static); - var scene = (ContentsScene)build.Invoke(null, new object[] { 10 }); + harness.Update(); var brushes = new System.Collections.Generic.List(); foreach (var root in scene.roots) AddBrushes(root, brushes); - var b7 = brushes[7]; - var toTree = Unity.Mathematics.math.transpose(Unity.Mathematics.math.inverse(b7.localToTree)); - var plane = Unity.Mathematics.math.mul(toTree, b7.localPlanes[1]); - plane /= Unity.Mathematics.math.length(plane.xyz); - var point = new Unity.Mathematics.float3(0.273f, -0.764f, -0.35f); - text.AppendLine($"b7 face 1 plane {plane}, point {point} is {Unity.Mathematics.math.dot(plane.xyz, point) + plane.w} off it"); - using (var harness = ContentsTreeHarness.Build(scene)) + int wrong = 0, total = 0; + for (int t = 0; t < harness.Triangles.Count; t++) { - harness.Update(); - for (int t = 0; t < harness.Triangles.Count; t++) - { - var triangle = harness.Triangles[t]; - bool covers = ContentsComparison.Covers(triangle.a, triangle.b, triangle.c, plane, point, out _); - bool oldCovers = OldCoversPoint(triangle.a, triangle.b, triangle.c, plane, point, 0.02f, 0.001f); - if (!covers && !oldCovers) - continue; - var node = harness.NodeOf(triangle.brushID); - float longest = Unity.Mathematics.math.max(Unity.Mathematics.math.length(triangle.b - triangle.a), - Unity.Mathematics.math.max(Unity.Mathematics.math.length(triangle.c - triangle.b), Unity.Mathematics.math.length(triangle.a - triangle.c))); - var center = (triangle.a + triangle.b + triangle.c) / 3; - text.AppendLine($"triangle {t} of {node?.name ?? "nothing"}: covers now {covers}, under the old test {oldCovers}; longest edge {longest}, " + - $"centre {Unity.Mathematics.math.distance(center, point)} from the point: {triangle.a} {triangle.b} {triangle.c}"); - } + var triangle = harness.Triangles[t]; + var node = harness.NodeOf(triangle.brushID); + total++; + bool onOwn = node != null && OnAPlaneOf(node, triangle); + if (onOwn) + continue; + wrong++; + var owners = new StringBuilder(); + foreach (var other in brushes) + if (OnAPlaneOf(other, triangle)) owners.Append(other.name).Append(' '); + text.AppendLine($"triangle {t} section {triangle.section} material {triangle.material} attributed to {node?.name ?? "nothing"} " + + $"({triangle.a}, {triangle.b}, {triangle.c}) lies on a plane of: {owners}"); } - } - finally - { - CompactHierarchyManager.TreeUpdate.kExactCSG = saved; + // The failing sample point on Extruded Shape #172's bottom, and every triangle of Box (15) + var point = new Unity.Mathematics.float3(-11.988f, -1f, -13.033f); + var bottom = new Unity.Mathematics.float4(0, -1, 0, -1); + for (int t = 0; t < harness.Triangles.Count; t++) + { + var triangle = harness.Triangles[t]; + var node = harness.NodeOf(triangle.brushID); + bool covers = ContentsComparison.Covers(triangle.a, triangle.b, triangle.c, bottom, point, out int facing); + if (covers || (node != null && node.name.StartsWith("Box (15)"))) + text.AppendLine($" {(covers ? "COVERS P " : "")}triangle {t} of {node?.name ?? "nothing"} facing {(covers ? facing : 0)}: {triangle.a} {triangle.b} {triangle.c}"); + } + text.Insert(0, $"{wrong} of {total} triangles are not on a plane of the brush they are attributed to\n"); + report.AppendLine($"{wrong} of {total} misattributed"); } var directory = System.IO.Path.Combine("Library", "ChiselHarvest", "traces"); System.IO.Directory.CreateDirectory(directory); - System.IO.File.WriteAllText(System.IO.Path.Combine(directory, "coverage_ten_overlapping_brushes.txt"), text.ToString()); - Assert.Pass(text.ToString()); - } - - // ContentsFaceSampler.CoversPoint with exact edge tests instead of distances - static bool OldCoversPoint(Unity.Mathematics.float3 a, Unity.Mathematics.float3 b, Unity.Mathematics.float3 c, - Unity.Mathematics.float4 plane, Unity.Mathematics.float3 point, float onPlaneEpsilon, float insideEpsilon) - { - if (Unity.Mathematics.math.abs(Unity.Mathematics.math.dot(plane.xyz, a) + plane.w) > onPlaneEpsilon) return false; - if (Unity.Mathematics.math.abs(Unity.Mathematics.math.dot(plane.xyz, b) + plane.w) > onPlaneEpsilon) return false; - if (Unity.Mathematics.math.abs(Unity.Mathematics.math.dot(plane.xyz, c) + plane.w) > onPlaneEpsilon) return false; - var normal = plane.xyz; - var d0 = Unity.Mathematics.math.dot(Unity.Mathematics.math.cross(b - a, point - a), normal); - var d1 = Unity.Mathematics.math.dot(Unity.Mathematics.math.cross(c - b, point - b), normal); - var d2 = Unity.Mathematics.math.dot(Unity.Mathematics.math.cross(a - c, point - c), normal); - return (d0 >= -insideEpsilon && d1 >= -insideEpsilon && d2 >= -insideEpsilon) || - (d0 <= insideEpsilon && d1 <= insideEpsilon && d2 <= insideEpsilon); + System.IO.File.WriteAllText(System.IO.Path.Combine(directory, "attribution_model2f04.txt"), text.ToString()); + Assert.Pass(report.ToString()); } static void AddBrushes(ContentsSceneNode node, System.Collections.Generic.List brushes) diff --git a/Core/Tests/Contents/ExactDelaunayTests.cs b/Core/Tests/Contents/ExactDelaunayTests.cs index b2d896c3..0a8c6331 100644 --- a/Core/Tests/Contents/ExactDelaunayTests.cs +++ b/Core/Tests/Contents/ExactDelaunayTests.cs @@ -22,8 +22,6 @@ static ContentsScene StripScene() static ContentsTreeHarness BuildAndUpdate(ContentsScene scene) { - if (!CompactHierarchyManager.TreeUpdate.kExactCSG) - Assert.Ignore("judges the exact CSG's triangles, and the exact CSG is off"); var harness = ContentsTreeHarness.Build(scene); Assert.That(harness.Update(), Is.True, "the CSG update did not run"); return harness; diff --git a/Core/Tests/Contents/ExactInputTests.cs b/Core/Tests/Contents/ExactInputTests.cs index 8839976f..1f4e8ad0 100644 --- a/Core/Tests/Contents/ExactInputTests.cs +++ b/Core/Tests/Contents/ExactInputTests.cs @@ -28,8 +28,6 @@ static ContentsScene Row(int count) static ContentsTreeHarness BuildAndUpdate(ContentsScene scene) { - if (!CompactHierarchyManager.TreeUpdate.kExactCSG) - Assert.Ignore("tests the exact CSG's input, which is off"); var harness = ContentsTreeHarness.Build(scene); Assert.That(harness.Update(), Is.True, "the first update did not run"); return harness; diff --git a/Core/Tests/Contents/ExactJudgeCaseTests.cs b/Core/Tests/Contents/ExactJudgeCaseTests.cs index c0519b80..1058517e 100644 --- a/Core/Tests/Contents/ExactJudgeCaseTests.cs +++ b/Core/Tests/Contents/ExactJudgeCaseTests.cs @@ -67,8 +67,6 @@ public void TheSlabsUnderside_TheMeshIsWhatTheExactCSGMade() static void AssertTheMeshIsWhatTheExactCSGMade(ContentsScene scene) { - if (!CompactHierarchyManager.TreeUpdate.kExactCSG) - Assert.Ignore("judges the exact CSG, which is off"); using var harness = ContentsTreeHarness.Build(scene); Assert.That(harness.Update(), Is.True, "the CSG update did not run"); var mismatches = ExactJudge.FindMismatches(harness, 100, out var judged); diff --git a/Core/Tests/Contents/ExactJudgeTests.cs b/Core/Tests/Contents/ExactJudgeTests.cs index 042b039b..702c0e5e 100644 --- a/Core/Tests/Contents/ExactJudgeTests.cs +++ b/Core/Tests/Contents/ExactJudgeTests.cs @@ -21,8 +21,6 @@ static ContentsScene GapScene() static ContentsTreeHarness BuildAndUpdate(ContentsScene scene) { - if (!CompactHierarchyManager.TreeUpdate.kExactCSG) - Assert.Ignore("judges the exact CSG, which is off"); var harness = ContentsTreeHarness.Build(scene); Assert.That(harness.Update(), Is.True, "the CSG update did not run"); return harness; diff --git a/Core/Tests/Contents/SharedCornerSurveyTests.cs b/Core/Tests/Contents/SharedCornerSurveyTests.cs deleted file mode 100644 index 77069363..00000000 --- a/Core/Tests/Contents/SharedCornerSurveyTests.cs +++ /dev/null @@ -1,609 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using NUnit.Framework; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - [Category("Contents")] - public class SharedCornerSurveyTests - { - static readonly float3 kCorner = new float3(1, 2, 3); - static readonly float3 kTwoMillimetres = new float3(0.002f, 0, 0); - - #region Classifier - [Test] - public void Classify_BothAtTheCorner_IsSame() - { - var result = SharedCornerSurvey.Classify(kCorner, new[] { kCorner }, new[] { kCorner + new float3(0.000001f) }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Same)); - } - - [Test] - public void Classify_OneEachTwoMillimetresApart_IsApart() - { - var result = SharedCornerSurvey.Classify(kCorner, new[] { kCorner }, new[] { kCorner + kTwoMillimetres }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Apart)); - Assert.That(result.distance, Is.EqualTo(0.002f).Within(0.000001f)); - } - - [Test] - public void Classify_OneAgainstTwo_IsOneVsTwo() - { - // One side welded its two nearby vertices into one that sits between them - var result = SharedCornerSurvey.Classify(kCorner, new[] { kCorner + (kTwoMillimetres * 0.5f) }, - new[] { kCorner, kCorner + kTwoMillimetres }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.OneVsTwo)); - Assert.That(result.countA, Is.EqualTo(1)); - Assert.That(result.countB, Is.EqualTo(2)); - } - - [Test] - public void Classify_OneSideDrawsNothingNearIt_IsMissing() - { - var result = SharedCornerSurvey.Classify(kCorner, new[] { kCorner }, new[] { kCorner + new float3(1, 0, 0) }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Missing)); - Assert.That(result.isDisagreement, Is.True); - } - - [Test] - public void Classify_NeitherSideDrawsNearIt_IsHidden() - { - var result = SharedCornerSurvey.Classify(kCorner, new float3[0], new[] { kCorner + new float3(1, 0, 0) }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Hidden)); - Assert.That(result.isDisagreement, Is.False); - } - - // A brush keeping a vertex of its own next to the shared corner is not a disagreement about - // the corner. - [Test] - public void Classify_SameCornerWithAnOwnVertexNearby_IsSame() - { - var result = SharedCornerSurvey.Classify(kCorner, new[] { kCorner, kCorner + kTwoMillimetres }, new[] { kCorner }); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Same)); - } - - [Test] - public void ClassifyCorner_OnlyComparesFacesThroughTheCorner() - { - var corner = new SharedCornerSurvey.Corner { position = kCorner, planesA = new[] { 0 }, planesB = new[] { 0 } }; - var verticesA = new List> { new List { kCorner } }; - var verticesB = new List> - { - new List { kCorner + new float3(0.0019f, 0, 0) }, - new List { kCorner } - }; - var result = SharedCornerSurvey.ClassifyCorner(corner, verticesA, verticesB); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Apart)); - Assert.That(result.distance, Is.EqualTo(0.0019f).Within(0.000001f)); - } - - [Test] - public void ClassifyCorner_TakesTheWorstFacePair() - { - var corner = new SharedCornerSurvey.Corner { position = kCorner, planesA = new[] { 0, 1 }, planesB = new[] { 0 } }; - var verticesA = new List> - { - new List { kCorner }, - new List { kCorner + kTwoMillimetres } - }; - var verticesB = new List> { new List { kCorner } }; - var result = SharedCornerSurvey.ClassifyCorner(corner, verticesA, verticesB); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Apart)); - Assert.That(result.distance, Is.EqualTo(0.002f).Within(0.000001f)); - } - - [Test] - public void ClassifyCorner_OneSideWithNoFaceNearIt_IsMissing() - { - var corner = new SharedCornerSurvey.Corner { position = kCorner, planesA = new[] { 0 }, planesB = new[] { 0 } }; - var verticesA = new List> { new List { kCorner } }; - var verticesB = new List> { new List() }; - var result = SharedCornerSurvey.ClassifyCorner(corner, verticesA, verticesB); - Assert.That(result.verdict, Is.EqualTo(SharedCornerSurvey.Verdict.Missing)); - } - - [Test] - public void PlaneOf_FindsTheFaceATriangleLiesOn_InEitherOrientation() - { - var planes = ContentsScene.BoxPlanes(new float3(0), new float3(2)); - Assert.That(SharedCornerSurvey.PlaneOf(new float3(1, 0, 0), new float3(2.01f, 1, 1), planes), Is.EqualTo(1)); - Assert.That(SharedCornerSurvey.PlaneOf(new float3(-1, 0, 0), new float3(2, 1, 1), planes), Is.EqualTo(1), "a carved wall faces inwards"); - Assert.That(SharedCornerSurvey.PlaneOf(new float3(1, 0, 0), new float3(1, 1, 1), planes), Is.EqualTo(-1), "not on any face"); - } - #endregion - - #region Corners - // Two unit-offset cubes: their surfaces cross on a skew hexagon, whose six corners are the - // corners of the overlap that lie on both surfaces - all of them except (1,1,1) and (2,2,2). - [Test] - public void SharedCorners_OfTwoOverlappingBoxes_AreTheCornersOfTheirCrossing() - { - var planes = new List - { - ContentsScene.BoxPlanes(new float3(0), new float3(2)), - ContentsScene.BoxPlanes(new float3(1), new float3(3)) - }; - var corners = SharedCornerSurvey.SharedCorners(planes); - - var expected = new[] - { - new float3(2, 1, 1), new float3(1, 2, 1), new float3(1, 1, 2), - new float3(1, 2, 2), new float3(2, 1, 2), new float3(2, 2, 1) - }; - Assert.That(corners.Count, Is.EqualTo(expected.Length), string.Join(", ", corners.Select(c => c.position))); - foreach (var position in expected) - Assert.That(corners.Any(c => math.distance(c.position, position) < 0.00001f), Is.True, "missing " + position); - Assert.That(corners.All(c => c.isolated), Is.True, "nothing else comes near these corners"); - - // (2, 1, 1) lies on A's +x face, and on B's -y and -z faces - var corner = corners.First(c => math.distance(c.position, new float3(2, 1, 1)) < 0.00001f); - Assert.That(corner.planesA, Is.EqualTo(new[] { 1 })); - Assert.That(corner.planesB, Is.EqualTo(new[] { 2, 4 })); - } - - [Test] - public void SharedCorners_WithAnotherFaceWithinTheWeldRadius_AreNotIsolated() - { - var planes = new List - { - ContentsScene.BoxPlanes(new float3(0), new float3(2)), - ContentsScene.BoxPlanes(new float3(1), new float3(3)), - // Its -x face lies 2 mm beyond the corner (2, 1, 1) - ContentsScene.BoxPlanes(new float3(2.002f, 0.5f, 0.5f), new float3(3, 1.5f, 1.5f)) - }; - var corners = SharedCornerSurvey.SharedCorners(planes); - var corner = corners.First(c => c.brushA == 0 && c.brushB == 1 && math.distance(c.position, new float3(2, 1, 1)) < 0.00001f); - Assert.That(corner.isolated, Is.False); - } - - [Test] - public void NearMissPopulations_HaveCornersThatAreNotIsolated([Values] bool slivers) - { - var notIsolated = 0; - var pairsCrossingTheirBox = 0; - var pairs = 0; - for (int seed = 1; seed <= 20; seed++) - { - var scene = slivers ? ContentsSceneGenerators.NearMissSlivers(seed, rotated: false) - : ContentsSceneGenerators.NearMissLedges(seed, rotated: false); - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - var planes = brushes.Select(oracle.TreePlanesOf).ToList(); - var corners = SharedCornerSurvey.SharedCorners(planes); - notIsolated += corners.Count(c => !c.isolated); - for (int w = 0; w + 1 < brushes.Count; w += 2) - { - pairs++; - if (corners.Count(c => c.brushA == w && c.brushB == w + 1 && !c.isolated) >= 2) - pairsCrossingTheirBox++; - } - } - Assert.That(notIsolated, Is.GreaterThan(0)); - if (slivers) - Assert.That(pairsCrossingTheirBox, Is.EqualTo(pairs), "every sliver pair has its two near crossings"); - } - #endregion - - #region Survey - const int kSeeds = 50; - - static readonly (string name, Func make)[] kPopulations = - { - ("near-miss slivers", seed => ContentsSceneGenerators.NearMissSlivers(seed, rotated: false)), - ("near-miss slivers, turned",seed => ContentsSceneGenerators.NearMissSlivers(seed, rotated: true)), - ("near-miss ledges", seed => ContentsSceneGenerators.NearMissLedges(seed, rotated: false)), - ("near-miss boxes", seed => ContentsSceneGenerators.NearMissBoxes(seed, rotated: false)), - ("near-miss boxes, turned", seed => ContentsSceneGenerators.NearMissBoxes(seed, rotated: true)), - ("grid boxes", seed => ContentsSceneGenerators.AllSolid(ContentsSceneGenerators.GridBoxes(seed))), - ("cut brushes", seed => ContentsSceneGenerators.AllSolid(ContentsSceneGenerators.CutBrushes(seed))), - }; - - static readonly SharedCornerSurvey.Verdict[] kReported = - { - SharedCornerSurvey.Verdict.Same, SharedCornerSurvey.Verdict.Apart, SharedCornerSurvey.Verdict.OneVsTwo, - SharedCornerSurvey.Verdict.Other, SharedCornerSurvey.Verdict.Missing - }; - - sealed class Tally - { - public int scenes, corners, hidden; - // Every isolated corner, hidden or not, and how many of them the output gets wrong by the oracle - public int isolatedCorners, isolatedWrong; - public readonly int[] isolated = new int[Enum.GetValues(typeof(SharedCornerSurvey.Verdict)).Length]; - public readonly int[] notIsolated = new int[Enum.GetValues(typeof(SharedCornerSurvey.Verdict)).Length]; - public readonly List distances = new List(); - } - - sealed class Case - { - public int setting; - public string population; - public int seed; - public string pair; - public float3 position; - public SharedCornerSurvey.Result result; - public bool isolated; - // Isolated corners only: which one, and what the oracle says is wrong with the output there - public int isolatedIndex; - public string wrong; - } - - // An isolated corner as one setting drew it - readonly struct IsolatedOutcome - { - public readonly SharedCornerSurvey.Verdict verdict; - // Null where the output agrees with the oracle - public readonly string wrong; - public IsolatedOutcome(SharedCornerSurvey.Verdict verdict, string wrong) { this.verdict = verdict; this.wrong = wrong; } - public string Describe() => $"{verdict} ({wrong ?? "right"})"; - } - - readonly struct Setting - { - public readonly string title; - public readonly Action apply; - public Setting(string title, Action apply) { this.title = title; this.apply = apply; } - } - - [Test, Explicit] - public void Survey_GateOffThenOn() - { - var original = CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld; - RunSurvey("SharedCornerSurvey", "the gate", - new[] - { - new Setting("GATE OFF (shipped; the control)", () => CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld = false), - new Setting("GATE ON (kUseIncidenceWeld = true)", () => CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld = true), - }, - () => CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld = original); - } - - // The shipped state against itself. Every control above assumes the pipeline gives the same output for the same - // scene twice; any difference reported here is nondeterminism, not a setting. - [Test, Explicit] - public void Survey_ControlAgainstItself() - { - var originalGate = CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld; - var originalStage = CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage; - Action shipped = () => - { - CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld = false; - CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = (int)CanonicalVertexStage.Off; - }; - RunSurvey("SharedCornerSurveyDeterminism", "a second run of the same build", - new[] { new Setting("SHIPPED (first run)", shipped), new Setting("SHIPPED (second run)", shipped) }, - () => - { - CompactHierarchyManager.TreeUpdate.kUseIncidenceWeld = originalGate; - CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = originalStage; - }); - } - - // Canonical vertices (Documentation~/Design/CanonicalVertices.md): every stage against the shipped state. The bar - // is two-sided splits at the control's level and one-sided misses down by at least 90%. - [Test, Explicit] - public void Survey_CanonicalVertexStages() - { - var original = CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage; - Action Stage(CanonicalVertexStage value) => () => CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = (int)value; - RunSurvey("CanonicalVertexSurvey", "canonical vertices", - new[] - { - new Setting("CANONICAL OFF (shipped; the control)", Stage(CanonicalVertexStage.Off)), - new Setting("CANONICAL POSITIONS (stage 2)", Stage(CanonicalVertexStage.Positions)), - new Setting("CANONICAL LOOP IDENTITY (stage 3)", Stage(CanonicalVertexStage.LoopIdentity)), - new Setting("CANONICAL EVERYWHERE (stage 4)", Stage(CanonicalVertexStage.Everywhere)), - }, - () => CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = original); - } - - static void RunSurvey(string reportName, string changedBy, Setting[] settings, Action restore) - { - var cases = new List(); - // Isolated corners where the output contradicts the oracle, and where the two sides put the corner in - // different places - var wrongCases = new List(); - var positionLeaks = new List(); - var outputs = new Dictionary<(int, string, int), List>(); - var tallies = new Dictionary<(int, string), Tally>(); - var stats = new string[settings.Length]; - // Every isolated corner's outcome, per setting, in corner order - var isolatedOutcomes = new Dictionary<(int, string, int), List>(); - // What the oracle expects of each side of every isolated corner, in corner order; the same for every setting - var expectations = new Dictionary<(string, int), List<(SharedCornerSurvey.Expectation a, SharedCornerSurvey.Expectation b)>>(); - (SharedCornerSurvey.Corner corner, List> a, List> b)? isolatedSample = null; - - var wasExactCSG = CompactHierarchyManager.TreeUpdate.kExactCSG; - try - { - CompactHierarchyManager.TreeUpdate.kExactCSG = false; - for (int setting = 0; setting < settings.Length; setting++) - { - settings[setting].apply(); - CanonicalVertexStats.Reset(); - foreach (var (name, make) in kPopulations) - { - var tally = new Tally(); - tallies[(setting, name)] = tally; - for (int seed = 1; seed <= kSeeds; seed++) - { - var scene = make(seed); - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - var planes = brushes.Select(oracle.TreePlanesOf).ToList(); - - // Per brush, per plane: the distinct vertices it drew on that face - var byPlane = planes.Select(p => p.Select(_ => new List()).ToList()).ToList(); - var all = new List(); - using (var harness = ContentsTreeHarness.Build(scene)) - { - Assert.That(harness.Update(), Is.True, $"{name} seed {seed}: the CSG update did not run"); - var index = new Dictionary(); - for (int i = 0; i < brushes.Count; i++) - index[brushes[i]] = i; - foreach (var triangle in harness.Triangles) - { - var node = harness.NodeOf(triangle.brushID); - if (node == null) - continue; - var brush = index[node]; - all.Add(triangle.a); all.Add(triangle.b); all.Add(triangle.c); - var plane = SharedCornerSurvey.PlaneOf(triangle.surfaceNormal, triangle.Center, planes[brush]); - if (plane < 0) - continue; - byPlane[brush][plane].Add(triangle.a); - byPlane[brush][plane].Add(triangle.b); - byPlane[brush][plane].Add(triangle.c); - } - } - byPlane = byPlane.Select(faces => faces.Select(SharedCornerSurvey.Distinct).ToList()).ToList(); - outputs[(setting, name, seed)] = SharedCornerSurvey.Distinct(all); - tally.scenes++; - - var corners = SharedCornerSurvey.SharedCorners(planes); - if (!expectations.TryGetValue((name, seed), out var expected)) - { - expected = corners.Where(c => c.isolated) - .Select(c => (SharedCornerSurvey.Expect(oracle, brushes[c.brushA], c.planesA, c.position), - SharedCornerSurvey.Expect(oracle, brushes[c.brushB], c.planesB, c.position))) - .ToList(); - expectations[(name, seed)] = expected; - } - - var outcomes = new List(); - isolatedOutcomes[(setting, name, seed)] = outcomes; - foreach (var corner in corners) - { - var result = SharedCornerSurvey.ClassifyCorner(corner, byPlane[corner.brushA], byPlane[corner.brushB]); - var pair = brushes[corner.brushA].name + "/" + brushes[corner.brushB].name; - if (corner.isolated) - { - var isolatedIndex = outcomes.Count; - var wrong = Judge(expected[isolatedIndex], result); - outcomes.Add(new IsolatedOutcome(result.verdict, wrong)); - tally.isolatedCorners++; - if (wrong != null) - { - tally.isolatedWrong++; - wrongCases.Add(new Case - { - setting = setting, population = name, seed = seed, position = corner.position, pair = pair, - result = result, isolated = true, isolatedIndex = isolatedIndex, wrong = wrong - }); - } - if (result.isPositionDisagreement) - { - positionLeaks.Add($"[{setting}] {name} seed {seed}, isolated corner {isolatedIndex}, {pair} at {Format(corner.position)}: " + - $"{result.verdict}, {result.distance * 1000:0.###} mm"); - } - } - if (result.verdict == SharedCornerSurvey.Verdict.Hidden) - { - tally.hidden++; - continue; - } - tally.corners++; - (corner.isolated ? tally.isolated : tally.notIsolated)[(int)result.verdict]++; - if (result.isDisagreement) - { - if (!float.IsNaN(result.distance)) - tally.distances.Add(result.distance); - cases.Add(new Case - { - setting = setting, population = name, seed = seed, position = corner.position, - pair = pair, result = result, isolated = corner.isolated - }); - } - else if (setting == 0 && corner.isolated && isolatedSample == null && - result.verdict == SharedCornerSurvey.Verdict.Same) - { - isolatedSample = (corner, byPlane[corner.brushA], byPlane[corner.brushB]); - } - } - } - } - stats[setting] = CanonicalVertexStats.Describe(); - } - } - finally - { - restore(); - CompactHierarchyManager.TreeUpdate.kExactCSG = wasExactCSG; - } - - var controlPassed = false; - var controlText = "no isolated corner where both sides agree was found"; - if (isolatedSample.HasValue) - { - var (corner, a, b) = isolatedSample.Value; - var moved = a.Select(face => face.Select(v => math.distance(v, corner.position) <= SharedCornerSurvey.kWeldRadius ? v + kTwoMillimetres : v).ToList()).ToList(); - var control = SharedCornerSurvey.ClassifyCorner(corner, moved, b); - controlPassed = control.isDisagreement && math.abs(control.distance - 0.002f) < 0.00005f; - controlText = $"moving one side's vertices 2 mm at the isolated corner {Format(corner.position)} gives {control.verdict} " + - $"at {control.distance * 1000:0.###} mm: {(controlPassed ? "PASS" : "FAIL")}"; - } - - var changes = new List(); - var changedVerdicts = new int[settings.Length]; - var fixes = new int[settings.Length]; - var breaks = new int[settings.Length]; - for (int setting = 1; setting < settings.Length; setting++) - { - foreach (var (name, _) in kPopulations) - { - for (int seed = 1; seed <= kSeeds; seed++) - { - var control = isolatedOutcomes[(0, name, seed)]; - var other = isolatedOutcomes[(setting, name, seed)]; - for (int i = 0; i < control.Count && i < other.Count; i++) - { - if (control[i].verdict == other[i].verdict) - continue; - changedVerdicts[setting]++; - if (control[i].wrong != null && other[i].wrong == null) fixes[setting]++; - if (control[i].wrong == null && other[i].wrong != null) breaks[setting]++; - changes.Add($"[{setting}] {name} seed {seed}, isolated corner {i}: {control[i].Describe()} in the control, {other[i].Describe()} here"); - } - } - } - } - - var report = new StringBuilder(); - report.AppendLine($"Shared-corner survey of {changedBy}, {DateTime.Now:yyyy-MM-dd HH:mm}, seeds 1..{kSeeds} per population"); - report.AppendLine("A corner is a point on the surfaces of two brushes. Per pair of faces through it (one of each brush), the"); - report.AppendLine($"vertices each face drew within {SharedCornerSurvey.kWeldRadius * 1000:0.#} mm are compared by their nearest one: 'same' within {SharedCornerSurvey.kSamePosition * 1000:0.##} mm."); - report.AppendLine("A corner takes its worst face pair. 'missing' = one brush drew nothing near the corner on its faces through it."); - report.AppendLine("Isolated corners have no other face within the weld radius, so the two sides can't put them in different"); - report.AppendLine("places (negative control). Whether each side draws there is judged against the oracle instead: a face lost or"); - report.AppendLine("kept anywhere along its outline shows up at its isolated corners too, whatever the weld did there."); - report.AppendLine(); - for (int setting = 0; setting < settings.Length; setting++) - { - report.AppendLine($"[{setting}] {settings[setting].title}"); - foreach (var (name, _) in kPopulations) - { - var tally = tallies[(setting, name)]; - report.AppendLine($" {name}: {tally.scenes} scenes, {tally.corners} corners drawn, {tally.hidden} hidden"); - report.AppendLine($" isolated: {Counts(tally.isolated)}; the oracle contradicts {tally.isolatedWrong} of {tally.isolatedCorners}"); - report.AppendLine($" not isolated: {Counts(tally.notIsolated)}"); - report.AppendLine($" disagreement distances: {Distances(tally.distances)}"); - } - report.AppendLine($" canonical vertices: {stats[setting]}"); - report.AppendLine(); - } - - report.AppendLine("Scenes whose output a setting changed from the control's (any vertex added, removed or moved):"); - for (int setting = 1; setting < settings.Length; setting++) - { - report.AppendLine($" [{setting}] {settings[setting].title}"); - foreach (var (name, _) in kPopulations) - { - var changed = 0; - for (int seed = 1; seed <= kSeeds; seed++) - { - if (!SamePositions(outputs[(0, name, seed)], outputs[(setting, name, seed)])) - changed++; - } - report.AppendLine($" {name}: {changed} of {kSeeds}"); - } - } - report.AppendLine(); - report.AppendLine("Positive control: " + controlText); - report.AppendLine($"Negative control, isolated corners where the two sides put the corner in different places: {positionLeaks.Count}"); - foreach (var leak in positionLeaks.Take(10)) - report.AppendLine(" " + leak); - report.AppendLine(); - report.AppendLine("Isolated corners where the output contradicts the oracle, first 10 per setting:"); - for (int setting = 0; setting < settings.Length; setting++) - { - foreach (var item in wrongCases.Where(c => c.setting == setting).Take(10)) - { - report.AppendLine($" [{setting}] {item.population} seed {item.seed}, isolated corner {item.isolatedIndex}, {item.pair} (A/B) " + - $"at {Format(item.position)}: {item.wrong} ({item.result.verdict})"); - } - } - report.AppendLine(); - report.AppendLine("Isolated corners whose verdict a setting changed from the control's (information, judged by the oracle):"); - for (int setting = 1; setting < settings.Length; setting++) - report.AppendLine($" [{setting}] {changedVerdicts[setting]} changed: {fixes[setting]} were wrong and are now right, {breaks[setting]} were right and are now wrong"); - foreach (var change in changes.Take(25)) - report.AppendLine(" " + change); - report.AppendLine(); - report.AppendLine("Disagreements, first 25 per setting:"); - for (int setting = 0; setting < settings.Length; setting++) - { - foreach (var item in cases.Where(c => c.setting == setting).Take(25)) - { - report.AppendLine($" [{setting}] {item.population} seed {item.seed}, {item.pair} at {Format(item.position)}" + - $"{(item.isolated ? " (isolated)" : "")}: {item.result.verdict}, " + - $"{(float.IsNaN(item.result.distance) ? "nothing to measure" : $"{item.result.distance * 1000:0.###} mm")}" + - $" ({item.result.countA} vs {item.result.countB} vertices)"); - } - } - - var text = report.ToString(); - var path = Path.GetFullPath(Path.Combine(UnityEngine.Application.dataPath, "..", "Logs", reportName + ".txt")); - File.WriteAllText(path, text); - UnityEngine.Debug.Log($"Shared-corner survey written to {path}\n{text}"); - - var examined = tallies.Values.Sum(t => t.corners); - Assert.That(examined, Is.GreaterThan(0), "the survey found no drawn corners at all"); - Assert.That(controlPassed, Is.True, controlText); - Assert.That(positionLeaks, Is.Empty, "the two sides put an isolated corner in different places, where nothing near it could move it"); - Assert.Pass($"{examined} corners examined; report at {path}"); - } - - // What is wrong with the output at an isolated corner by the oracle, or null when nothing is - static string Judge((SharedCornerSurvey.Expectation a, SharedCornerSurvey.Expectation b) expected, SharedCornerSurvey.Result result) - { - var wrongA = SharedCornerSurvey.JudgeSide(expected.a, result.drewA); - var wrongB = SharedCornerSurvey.JudgeSide(expected.b, result.drewB); - if (wrongA == null && wrongB == null) - return null; - if (wrongA != null && wrongB != null) - return $"A {wrongA}, B {wrongB}"; - return wrongA != null ? "A " + wrongA : "B " + wrongB; - } - - static string Counts(int[] counts) - { - return string.Join(", ", kReported.Select(v => $"{v.ToString().ToLowerInvariant()} {counts[(int)v]}")); - } - - // The bands of the welds the gate refused, so the two can be compared - static string Distances(List distances) - { - if (distances.Count == 0) - return "none"; - var sorted = distances.Select(d => d * 1000).OrderBy(d => d).ToList(); - var median = sorted[sorted.Count / 2]; - int Bin(float lo, float hi) => sorted.Count(d => d >= lo && d < hi); - return $"n={sorted.Count}, min {sorted[0]:0.###} mm, median {median:0.###} mm, max {sorted[sorted.Count - 1]:0.###} mm; " + - $"0.05-0.6: {Bin(0.05f, 0.6f)}, 0.6-1.2: {Bin(0.6f, 1.2f)}, 1.2-3: {Bin(1.2f, 3)}, 3-6: {Bin(3, 6)}, " + - $"6-12.5: {Bin(6, 12.5f)}, 12.5+: {Bin(12.5f, float.MaxValue)}"; - } - - static bool SamePositions(List a, List b) - { - if (a.Count != b.Count) - return false; - var remaining = new List(b); - foreach (var position in a) - { - var index = remaining.FindIndex(p => math.distance(p, position) <= 0.0000001f); - if (index < 0) - return false; - remaining.RemoveAt(index); - } - return true; - } - - static string Format(float3 position) => $"({position.x:0.####}, {position.y:0.####}, {position.z:0.####})"; - #endregion - } -} diff --git a/Core/Tests/Contents/SharedCornerSurveyTests.cs.meta b/Core/Tests/Contents/SharedCornerSurveyTests.cs.meta deleted file mode 100644 index 4f5fa4b0..00000000 --- a/Core/Tests/Contents/SharedCornerSurveyTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 87a6a7647a18b9346a648a43c20ddcee \ No newline at end of file diff --git a/Core/Tests/InternedPlanesTests.cs b/Core/Tests/InternedPlanesTests.cs deleted file mode 100644 index 1f71b6db..00000000 --- a/Core/Tests/InternedPlanesTests.cs +++ /dev/null @@ -1,274 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class InternedPlanesTests - { - static float4 Plane(float3 normal, float3 pointOn) - { - var n = math.normalize(normal); - return new float4(n, -math.dot(n, pointOn)); - } - - [Test] - public void IdenticalPlanes_ShareOneId() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 5, 0); - var p = Plane(new float3(0, 1, 0), on); - - var a = table.Add(p, on, out bool flippedA); - var b = table.Add(p, on, out bool flippedB); - - Assert.That(b, Is.EqualTo(a)); - Assert.That(flippedA, Is.False); - Assert.That(flippedB, Is.False); - Assert.That(table.Count, Is.EqualTo(1), "an identical plane must not create a second entry"); - } - - [Test] - public void OppositeFacingPlane_SharesIdAndReportsFlipped() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 5, 0); - var up = Plane(new float3(0, 1, 0), on); - var down = Plane(new float3(0, -1, 0), on); - - var a = table.Add(up, on, out bool flippedA); - var b = table.Add(down, on, out bool flippedB); - - Assert.That(b, Is.EqualTo(a), "a face and the face it abuts lie on ONE plane"); - Assert.That(flippedA, Is.False); - Assert.That(flippedB, Is.True, "the caller must be told, or its inside/outside test inverts"); - Assert.That(table.Count, Is.EqualTo(1)); - } - - [Test] - public void PlanesWithinTolerance_Share_OutsideTolerance_Differ() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var onBase = new float3(10, 0, 0); - var baseline = Plane(new float3(1, 0, 0), onBase); - - // shifted by less than kPlaneDAlignEpsilon (0.0006), and by clearly more (0.0184) - var onWithin = new float3(10 + 0.0004f, 0, 0); - var onWithout = new float3(10 + 0.0184f, 0, 0); - - var idBase = table.Add(baseline, onBase, out _); - var idWithin = table.Add(Plane(new float3(1, 0, 0), onWithin), onWithin, out _); - var idWithout = table.Add(Plane(new float3(1, 0, 0), onWithout), onWithout, out _); - - Assert.That(idWithin, Is.EqualTo(idBase)); - Assert.That(idWithout, Is.Not.EqualTo(idBase), - "0.0184 apart is two genuinely different walls; merging them would delete geometry"); - Assert.That(table.Count, Is.EqualTo(2)); - } - - [Test] - public void UnnormalizedInput_MatchesNormalized() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 0, 3); - var p = Plane(new float3(0, 0, 1), on); - - var a = table.Add(p, on, out _); - var b = table.Add(p * 7.5f, on, out bool flipped); // same plane, arbitrary scale - - Assert.That(b, Is.EqualTo(a)); - Assert.That(flipped, Is.False); - Assert.That(table.Count, Is.EqualTo(1)); - } - - [Test] - public void NegativelyScaledInput_MatchesAndReportsFlipped() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 0, 3); - var p = Plane(new float3(0, 0, 1), on); - - var a = table.Add(p, on, out _); - var b = table.Add(p * -2.0f, on, out bool flipped); - - Assert.That(b, Is.EqualTo(a)); - Assert.That(flipped, Is.True); - } - - // Two near-identical normals either side of a hemisphere rule's boundary still intern together - [Test] - public void NormalsStraddlingAHemisphereBoundary_StillShareOneId() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 3.0053f, 3.0053f); - var na = math.normalize(new float3(+1e-8f, 0.70710678f, 0.70710678f)); - var nb = math.normalize(new float3(-1e-8f, 0.70710678f, 0.70710678f)); - - var idA = table.Add(Plane(na, on), on, out _); - var idB = table.Add(Plane(nb, on), on, out _); - - Assert.That(idB, Is.EqualTo(idA)); - Assert.That(table.Count, Is.EqualTo(1)); - } - - [Test] - public void ParallelButDistinctPlanes_StayDistinct() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on0 = new float3(0, 0, 0); - var on1 = new float3(1, 0, 0); - - var id0 = table.Add(Plane(new float3(1, 0, 0), on0), on0, out _); - var id1 = table.Add(Plane(new float3(1, 0, 0), on1), on1, out _); - var id0b = table.Add(Plane(new float3(-1, 0, 0), on0), on0, out bool flipped); - - Assert.That(id1, Is.Not.EqualTo(id0), "two walls a metre apart are two planes"); - Assert.That(id0b, Is.EqualTo(id0)); - Assert.That(flipped, Is.True); - Assert.That(table.Count, Is.EqualTo(2)); - } - - const float kTilt = 4e-4f; - static float4 TiltedThroughOrigin() { return Plane(new float3(0, 1, kTilt), float3.zero); } - static float3 OnTiltedAt(float z) { return new float3(0, -kTilt * z, z); } - - [Test] - public void TiltIsInsideTheAngularTolerance_SoOnlyThePointTestCanRejectIt() - { - var flat = Plane(new float3(0, 1, 0), float3.zero); - var deviation = 1.0 - math.abs(math.dot(flat.xyz, TiltedThroughOrigin().xyz)); - Assert.That(deviation, Is.LessThan(InternedPlanes.kMaxNormalDeviation), - "if this ever fails the two tests below stop testing what they claim"); - // and the far point really is on the tilted plane, not the flat one - Assert.That(math.abs(InternedPlanes.Distance(TiltedThroughOrigin(), OnTiltedAt(100))), - Is.LessThan(1e-5)); - Assert.That(math.abs(InternedPlanes.Distance(flat, OnTiltedAt(100))), - Is.GreaterThan(CSGConstants.kPlaneDAlignEpsilon)); - } - - [Test] - public void NearlyParallelPlanes_ThatDivergeFarFromOrigin_AreNotMerged() - { - using var table = new InternedPlanes(8, Allocator.Temp); - - var idFlat = table.Add(Plane(new float3(0, 1, 0), float3.zero), float3.zero, out _); - var idTilted = table.Add(TiltedThroughOrigin(), OnTiltedAt(100), out _); - - Assert.That(idTilted, Is.Not.EqualTo(idFlat), - "they agree exactly at the origin but are 0.04 apart where the geometry is"); - Assert.That(table.Count, Is.EqualTo(2)); - } - - [Test] - public void AllSuppliedPoints_MustLieOnTheMatchedPlane() - { - var flat = Plane(new float3(0, 1, 0), float3.zero); - - // vouching only for the shared point at the origin accepts the match ... - using (var nearOnly = new InternedPlanes(8, Allocator.Temp)) - { - var idFlat = nearOnly.Add(flat, float3.zero, out _); - var idTilt = nearOnly.Add(TiltedThroughOrigin(), float3.zero, out _); - Assert.That(idTilt, Is.EqualTo(idFlat), - "at the origin the two planes really are indistinguishable"); - } - - // ... but vouching for the whole face must not, because part of it is 0.04 away - using (var wholeFace = new InternedPlanes(8, Allocator.Temp)) - { - var idFlat = wholeFace.Add(flat, float3.zero, out _); - using var pts = new NativeArray(new[] { float3.zero, OnTiltedAt(100) }, Allocator.Temp); - var idBoth = wholeFace.Add(TiltedThroughOrigin(), pts, out _); - Assert.That(idBoth, Is.Not.EqualTo(idFlat), - "a face is only on a plane if ALL of it is on that plane"); - } - } - - [Test] - public void MatchedPlane_StaysWithinToleranceOfTheCallersPoints() - { - using var table = new InternedPlanes(16, Allocator.Temp); - var on0 = new float3(3, 7, 11); - var id = table.Add(Plane(new float3(0.3f, 0.9f, 0.31f), on0), on0, out _); - - // a slightly different plane through a nearby point that still matches - var on1 = new float3(3.0001f, 7.0001f, 11.0001f); - var id2 = table.Add(Plane(new float3(0.3f, 0.9f, 0.31f), on1), on1, out _); - - Assert.That(id2, Is.EqualTo(id), "these are the same wall"); - var stored = table[id]; - Assert.That(math.abs(InternedPlanes.Distance(stored, on1)), - Is.LessThanOrEqualTo(CSGConstants.kPlaneDAlignEpsilon), - "the invariant the whole design rests on: the shared plane still passes through the face"); - } - - [Test] - public void TryFind_DoesNotInsert() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 2, 0); - var p = Plane(new float3(0, 1, 0), on); - - Assert.That(table.TryFind(p, on, out _, out _), Is.False); - Assert.That(table.Count, Is.EqualTo(0)); - - var id = table.Add(p, on, out _); - Assert.That(table.TryFind(p, on, out int found, out _), Is.True); - Assert.That(found, Is.EqualTo(id)); - Assert.That(table.Count, Is.EqualTo(1)); - } - - [Test] - public void StoredPlane_IsNormalizedAndRetrievable() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var on = new float3(0, 3, 0); - var id = table.Add(new float4(0, 4, 0, -12), on, out _); // normal length 4 - - var stored = table[id]; - Assert.That(math.length(stored.xyz), Is.EqualTo(1f).Within(1e-5f)); - Assert.That(stored.w, Is.EqualTo(-3f).Within(1e-5f)); - } - - [Test] - public void ManyDistinctPlanes_AllGetDistinctIds_AndReAddingFindsThem() - { - using var table = new InternedPlanes(64, Allocator.Temp); - const int kCount = 200; - var ids = new int[kCount]; - for (int i = 0; i < kCount; i++) - { - var on = new float3(i * 0.5f, 0, 0); - ids[i] = table.Add(Plane(new float3(1, 0, 0), on), on, out _); - } - - Assert.That(table.Count, Is.EqualTo(kCount), "hash chaining must not merge distinct planes"); - for (int i = 1; i < kCount; i++) - Assert.That(ids[i], Is.Not.EqualTo(ids[i - 1])); - - for (int i = 0; i < kCount; i++) - { - var on = new float3(i * 0.5f, 0, 0); - Assert.That(table.Add(Plane(new float3(1, 0, 0), on), on, out _), Is.EqualTo(ids[i])); - } - Assert.That(table.Count, Is.EqualTo(kCount), "re-adding must find, not grow"); - } - - [Test] - public void Clear_EmptiesTheTableAndItsHashChains() - { - using var table = new InternedPlanes(8, Allocator.Temp); - var onA = new float3(0, 1, 0); - var onB = new float3(1, 0, 0); - table.Add(Plane(new float3(0, 1, 0), onA), onA, out _); - table.Add(Plane(new float3(1, 0, 0), onB), onB, out _); - Assert.That(table.Count, Is.EqualTo(2)); - - table.Clear(); - Assert.That(table.Count, Is.EqualTo(0)); - Assert.That(table.TryFind(Plane(new float3(0, 1, 0), onA), onA, out _, out _), Is.False, - "a stale hash chain would hand back an id into an empty list"); - } - } -} diff --git a/Core/Tests/InternedPlanesTests.cs.meta b/Core/Tests/InternedPlanesTests.cs.meta deleted file mode 100644 index e24e0890..00000000 --- a/Core/Tests/InternedPlanesTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 3f959a05e78e75e4ea9640e0990891a8 \ No newline at end of file diff --git a/Core/Tests/LoopSplitCapacityTests.cs b/Core/Tests/LoopSplitCapacityTests.cs deleted file mode 100644 index a6468fa3..00000000 --- a/Core/Tests/LoopSplitCapacityTests.cs +++ /dev/null @@ -1,204 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class LoopSplitCapacityTests - { - const float kHalf = 5f; - - static BlobAssetReference Planes(float4[] planes) - { - using var builder = new BlobBuilder(Allocator.Temp); - ref var root = ref builder.ConstructRoot(); - var array = builder.Allocate(ref root.treeSpacePlanes, planes.Length); - for (int i = 0; i < planes.Length; i++) - array[i] = planes[i]; - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - - // Six planes, with outward normals (n·p + w <= 0 inside). - static float4[] Diamond(float r) - { - var s = math.sqrt(0.5f); - return new[] - { - new float4( s, 0, s, -r * s), - new float4( s, 0, -s, -r * s), - new float4(-s, 0, s, -r * s), - new float4(-s, 0, -s, -r * s), - new float4( 0, -1, 0, -1), - new float4( 0, 1, 0, -1), - }; - } - - // The brush the square is a face of. - static float4[] SquareBrush() - { - return new[] - { - new float4(-1, 0, 0, -kHalf), new float4(1, 0, 0, -kHalf), - new float4( 0, -1, 0, -1), new float4(0, 1, 0, -1), - new float4( 0, 0, -1, -kHalf), new float4(0, 0, 1, -kHalf), - }; - } - - static float3[] Square() - { - return new[] - { - new float3(-kHalf, 0, -kHalf), new float3(kHalf, 0, -kHalf), - new float3( kHalf, 0, kHalf), new float3(-kHalf, 0, kHalf), - }; - } - - // The square's outline with extra points on every side at +-offset from the side's middle, in loop order. - static float3[] SplitSquare(params float[] offsets) - { - var sorted = new float[offsets.Length * 2]; - for (int i = 0; i < offsets.Length; i++) - { - sorted[i] = -offsets[i]; - sorted[offsets.Length + i] = offsets[i]; - } - System.Array.Sort(sorted); - - var ring = new System.Collections.Generic.List(); - ring.Add(new float3(-kHalf, 0, -kHalf)); - foreach (var t in sorted) ring.Add(new float3(t, 0, -kHalf)); // along z = -5, x rising - ring.Add(new float3(kHalf, 0, -kHalf)); - foreach (var t in sorted) ring.Add(new float3(kHalf, 0, t)); // along x = +5, z rising - ring.Add(new float3(kHalf, 0, kHalf)); - for (int i = sorted.Length - 1; i >= 0; i--) - ring.Add(new float3(sorted[i], 0, kHalf)); // along z = +5, x falling - ring.Add(new float3(-kHalf, 0, kHalf)); - for (int i = sorted.Length - 1; i >= 0; i--) - ring.Add(new float3(-kHalf, 0, sorted[i])); // along x = -5, z falling - return ring.ToArray(); - } - - // A closed loop through the ring's points, in a list allocated with exactly the given capacity request. - static UnsafeList Loop(HashedVertices vertices, float3[] ring, int capacity) - { - Assert.That(capacity, Is.GreaterThanOrEqualTo(ring.Length)); - var edges = new UnsafeList(capacity, Allocator.Temp); - var first = vertices.AddNoResize(ring[0]); - var previous = first; - for (int i = 1; i < ring.Length; i++) - { - var current = vertices.AddNoResize(ring[i]); - edges.AddNoResize(new Edge { index1 = previous, index2 = current }); - previous = current; - } - edges.AddNoResize(new Edge { index1 = previous, index2 = first }); - return edges; - } - - [Test] - public void AFaceCutByManyBrushes_OutgrowsTheEdgeListTheJobAllocates() - { - const int kBrushes = 6; - using var vertices = new HashedVertices(64, Allocator.Temp); - // Sized as the job sizes a base polygon's list: its own edges plus four per intersecting brush. - var edges = Loop(vertices, Square(), 4 + kBrushes * 4); - var capacity = edges.Capacity; - - for (int b = 0; b < kBrushes; b++) - { - var diamond = Planes(Diamond(5.5f + 0.3f * b)); - default(FindLoopOverlapIntersectionsJob).FindBasePolygonPlaneIntersections( - ref diamond.Value.treeSpacePlanes, WeldIncidenceFilter.Disabled, ref edges, vertices); - diamond.Dispose(); - } - - Assert.That(edges.Length, Is.EqualTo(4 + kBrushes * 8), "every brush crosses each side of the square twice"); - Assert.That(edges.Length, Is.GreaterThan(capacity), "the fixture must outgrow the list, or it proves nothing"); - Assert.That(LoopValidation.IsValid(in edges, vertices.Length), Is.True, "the face is still one closed loop"); - edges.Dispose(); - } - - [Test] - public void AnIntersectionLoopCutByManyLoops_OutgrowsTheEdgeListCopyFromAllocates() - { - const int kBrushes = 6; - var planes = new NativeArray>(kBrushes, Allocator.Temp); - for (int b = 0; b < kBrushes; b++) - planes[b] = Planes(Diamond(5.5f + 0.3f * b)); - - using var vertices = new HashedVertices(64, Allocator.Temp); - // Sized as CopyFrom sizes an intersection loop: its own edges plus four per loop of the brush. - var edges = Loop(vertices, Square(), 4 + kBrushes * 4); - var capacity = edges.Capacity; - - for (int b = 0; b < kBrushes; b++) - { - default(FindLoopOverlapIntersectionsJob).FindLoopPlaneIntersections( - planes, WeldIncidenceFilter.Disabled, b, vertices, ref edges); - } - - Assert.That(edges.Length, Is.EqualTo(4 + kBrushes * 8), "every brush crosses each side of the square twice"); - Assert.That(edges.Length, Is.GreaterThan(capacity), "the fixture must outgrow the list, or it proves nothing"); - Assert.That(LoopValidation.IsValid(in edges, vertices.Length), Is.True, "the loop is still one closed loop"); - - edges.Dispose(); - for (int b = 0; b < kBrushes; b++) - planes[b].Dispose(); - planes.Dispose(); - } - - [Test] - public void ABrushCrossingTwicePerPlane_DoesNotOverflowTheVertexList() - { - const int kPlanes = 6, kCrossings = 8; - var diamond = Planes(Diamond(6f)); - using var vertices = new HashedVertices(64, Allocator.Temp); - var edges = Loop(vertices, Square(), 16); - - // Room for one new vertex per plane, not one per crossing: the eight crossings still fit - var y = 100f; - while (vertices.Capacity - vertices.Length >= kCrossings) - { - vertices.AddNoResize(new float3(0, y, 0)); // unrelated vertices, far apart and far from the face - y += 1f; - } - Assert.That(vertices.Capacity - vertices.Length, Is.GreaterThanOrEqualTo(kPlanes), - "the fixture must leave room for one vertex per plane, or the old reservation would have grown it"); - var before = vertices.Length; - - default(FindLoopOverlapIntersectionsJob).FindBasePolygonPlaneIntersections( - ref diamond.Value.treeSpacePlanes, WeldIncidenceFilter.Disabled, ref edges, vertices); - - Assert.That(vertices.Length - before, Is.EqualTo(kCrossings), "one new vertex per crossing"); - Assert.That(edges.Length, Is.EqualTo(4 + kCrossings)); - Assert.That(LoopValidation.IsValid(in edges, vertices.Length), Is.True); - - edges.Dispose(); - diamond.Dispose(); - } - - [Test] - public void ManyVerticesLyingOnALoop_OutgrowItsEdgeList() - { - var brush = Planes(SquareBrush()); - using var vertices = new HashedVertices(64, Allocator.Temp); - var self = Loop(vertices, Square(), 4); - var capacity = self.Capacity; - // A neighbouring loop that shares the corners and has six more points on every side. - var other = Loop(vertices, SplitSquare(0.5f, 0.8f, 1.1f), 64); - - default(FindLoopOverlapIntersectionsJob).FindLoopVertexOverlaps(ref brush.Value.treeSpacePlanes, ref self, other, vertices); - - Assert.That(self.Length, Is.EqualTo(4 + 4 * 6), "each side is split at the other loop's six points on it"); - Assert.That(self.Length, Is.GreaterThan(capacity), "the fixture must outgrow the list, or it proves nothing"); - Assert.That(LoopValidation.IsValid(in self, vertices.Length), Is.True); - - other.Dispose(); - self.Dispose(); - brush.Dispose(); - } - } -} diff --git a/Core/Tests/LoopSplitCapacityTests.cs.meta b/Core/Tests/LoopSplitCapacityTests.cs.meta deleted file mode 100644 index 96cb768d..00000000 --- a/Core/Tests/LoopSplitCapacityTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 35b0b5ea3fd1dc84994a8a48fea6ecb5 \ No newline at end of file diff --git a/Core/assembly.cs b/Core/assembly.cs index af0ff781..159cfba6 100644 --- a/Core/assembly.cs +++ b/Core/assembly.cs @@ -37,7 +37,6 @@ [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.BranchAllocateBrushesJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.BranchCreateBrushesJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.GeneratorBranchJobPool.InitializeArraysJob))] -[assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.EnsureCapacityListReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.EnsureCapacityListForEachCountFromListJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeReferenceChildBlobAssetReferenceJob))] diff --git a/Core/com.chisel.core.asmdef b/Core/com.chisel.core.asmdef index 37a4b7a8..04f4eb3a 100644 --- a/Core/com.chisel.core.asmdef +++ b/Core/com.chisel.core.asmdef @@ -9,7 +9,6 @@ "Unity.Mathematics.Extensions", "Unity.Mathematics.Extensions.Hybrid", "AssetMetadataRuntime", - "andywiecko.BurstTriangulator", "com.chisel.dots.extensions" ], "includePlatforms": [], diff --git a/Dependencies/BurstTriangulator b/Dependencies/BurstTriangulator deleted file mode 160000 index 273cdc4e..00000000 --- a/Dependencies/BurstTriangulator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 273cdc4e6ef29f7cd4312fde275890ad1de526bd diff --git a/Dependencies/BurstTriangulator.meta b/Dependencies/BurstTriangulator.meta deleted file mode 100644 index aa35bcb2..00000000 --- a/Dependencies/BurstTriangulator.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 76b602ce9af3b204ebd43d9dae8a6a2b -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/INSTALLATION.md b/INSTALLATION.md index 938a3f07..eb65ade9 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -18,7 +18,7 @@ And press enter. This installs Chisel and the packages it depends on. ![URL Textbox](Documentation~/Images/package_manager_url.png) --- -Chisel's own dependencies (BurstTriangulator and CustomAssetMetadata) are git submodules of its repository, so git +Chisel's own dependency (CustomAssetMetadata) is a git submodule of its repository, so git needs to be installed and on your path. The first time you start up it might take some time, because Unity Burst compiles a lot of code.