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.