From 02ea87ba7ac01559bc315f34f391eb868954237f Mon Sep 17 00:00:00 2001 From: Sander van Rossen Date: Sun, 25 Oct 2026 09:56:17 +0100 Subject: [PATCH 1/3] Use only ASCII in comments --- Components/Tests/ChiselMaterialMeshTests.cs | 2 +- Core/1.Input/BrushMesh/BrushMesh.Utility.cs | 2 +- Core/1.Input/Nodes/CSGTree.cs | 2 +- Core/1.Input/Nodes/CSGTreeBranch.cs | 2 +- Core/2.Processing/Algorithms/MeshAlgorithms.cs | 2 +- Core/Tests/MeshQueryRoutingTests.cs | 12 ++++++------ 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Components/Tests/ChiselMaterialMeshTests.cs b/Components/Tests/ChiselMaterialMeshTests.cs index 85d4b78c..2c6d7c57 100644 --- a/Components/Tests/ChiselMaterialMeshTests.cs +++ b/Components/Tests/ChiselMaterialMeshTests.cs @@ -186,7 +186,7 @@ public void AChunk_IsNotSplit_InAModelLitByProbes() Assert.That(RenderersOf(model, first).Count, Is.EqualTo(1), "one mesh for the 32 m cell"); } - // Three boxes of about 6 m² of lightmap each: with room for 15, their 32 m cell (18) splits into its 16 m cells (12 and 6) + // Three boxes of about 6 m^2 of lightmap each: with room for 15, their 32 m cell (18) splits into its 16 m cells (12 and 6) [Test] public void ASplitStopsAtTheCellsThatFit() { diff --git a/Core/1.Input/BrushMesh/BrushMesh.Utility.cs b/Core/1.Input/BrushMesh/BrushMesh.Utility.cs index ecb38028..401a5b24 100644 --- a/Core/1.Input/BrushMesh/BrushMesh.Utility.cs +++ b/Core/1.Input/BrushMesh/BrushMesh.Utility.cs @@ -655,7 +655,7 @@ static bool SplitPolygon(int polygonIndex, List vertexDistances, Lis intersections[i0] = planeTraversal0; } - // NOTE: from this point on Traversal𝑛.EdgeIndex may no longer be valid! + // NOTE: from this point on TraversalN.EdgeIndex may no longer be valid! Debug.Assert((intersections.Count & 1) != 1, "Found an uneven number of edge-plane traversals??"); diff --git a/Core/1.Input/Nodes/CSGTree.cs b/Core/1.Input/Nodes/CSGTree.cs index 413f0e78..c457d2f2 100644 --- a/Core/1.Input/Nodes/CSGTree.cs +++ b/Core/1.Input/Nodes/CSGTree.cs @@ -199,7 +199,7 @@ public bool InsertRange(int index, NativeArray array) /// Determines the index of a specific child in the . /// The to locate in the . - /// The index of if found in the ; otherwise, –1. + /// The index of if found in the ; otherwise, -1. public int IndexOf (CSGTreeNode item) { return Hierarchy.SiblingIndexOf(CompactNodeID, item.CompactNodeID); } /// Determines whether the contains a specific value. diff --git a/Core/1.Input/Nodes/CSGTreeBranch.cs b/Core/1.Input/Nodes/CSGTreeBranch.cs index 45fdfd1d..421a49ec 100644 --- a/Core/1.Input/Nodes/CSGTreeBranch.cs +++ b/Core/1.Input/Nodes/CSGTreeBranch.cs @@ -217,7 +217,7 @@ public bool InsertRange(int index, NativeArray array) /// Determines the index of a specific child in the . /// The to locate in the . - /// The index of if found in the ; otherwise, –1. + /// The index of if found in the ; otherwise, -1. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf (CSGTreeNode item) { return Hierarchy.SiblingIndexOf(CompactNodeID, item.CompactNodeID); } diff --git a/Core/2.Processing/Algorithms/MeshAlgorithms.cs b/Core/2.Processing/Algorithms/MeshAlgorithms.cs index 15223552..81b2140c 100644 --- a/Core/2.Processing/Algorithms/MeshAlgorithms.cs +++ b/Core/2.Processing/Algorithms/MeshAlgorithms.cs @@ -410,7 +410,7 @@ public void RemoveSelfIntersectingEdges() for (int i = 0; i < edgeCount; i++) removeFlags[i] = false; - // Mark any pair of non‐adjacent edges that intersect + // Mark any pair of non-adjacent edges that intersect for (int i = 0; i < edgeCount; ++i) { int p1i = edgeIndices[i * 2 + 0]; diff --git a/Core/Tests/MeshQueryRoutingTests.cs b/Core/Tests/MeshQueryRoutingTests.cs index 1b6a7c7f..4d34804f 100644 --- a/Core/Tests/MeshQueryRoutingTests.cs +++ b/Core/Tests/MeshQueryRoutingTests.cs @@ -7,11 +7,11 @@ namespace Chisel.Core.Tests public class MeshQueryRoutingTests { readonly static int[] kValidRenderableIndices = { 2, 3, 7, 9, 10, 11, 13, 15 }; - const int kShadowCastingRendererIndex = 2; // ‹[generated-ShadowCasting]›, ie shadow-only - const int kDefaultRendererIndex = 7; // ‹[generated-Renderable|ShadowCasting|ShadowReceiving]› - const int kRenderableRendererIndex = 9; // ‹[generated-Renderable|ExcludedFromGlobalIllumination]› - const int kReceivingRendererIndex = 13; // ‹[generated-Renderable|ShadowReceiving|ExcludedFromGlobalIllumination]› - const int kExcludedRendererIndex = 15; // ‹[generated-Renderable|ShadowCasting|ShadowReceiving|ExcludedFromGlobalIllumination]› + const int kShadowCastingRendererIndex = 2; // <[generated-ShadowCasting]>, ie shadow-only + const int kDefaultRendererIndex = 7; // <[generated-Renderable|ShadowCasting|ShadowReceiving]> + const int kRenderableRendererIndex = 9; // <[generated-Renderable|ExcludedFromGlobalIllumination]> + const int kReceivingRendererIndex = 13; // <[generated-Renderable|ShadowReceiving|ExcludedFromGlobalIllumination]> + const int kExcludedRendererIndex = 15; // <[generated-Renderable|ShadowCasting|ShadowReceiving|ExcludedFromGlobalIllumination]> // Renderable, ShadowCasting, ShadowReceiving, Collidable and ExcludedFromGlobalIllumination; // Discarded is not a combination a real surface carries alongside the others, so it is left out. @@ -115,7 +115,7 @@ public void DefaultQueries_EveryRendererHasOneQuery() } } - // A surface that casts shadows but isn't rendered reaches ‹[generated-ShadowCasting]›, which casts it with the ForceShadowOnly material + // A surface that casts shadows but isn't rendered reaches <[generated-ShadowCasting]>, which casts it with the ForceShadowOnly material [Test] public void DefaultQueries_ShadowOnlySurfaceReachesTheShadowCastingRenderer() { From b52ae3b9e9a5a2844ef330c4ca224839564b9045 Mon Sep 17 00:00:00 2001 From: Sander van Rossen Date: Sun, 25 Oct 2026 10:00:35 +0100 Subject: [PATCH 2/3] Refer to generated object names by their searchable part in comments --- Core/Tests/MeshQueryRoutingTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/Tests/MeshQueryRoutingTests.cs b/Core/Tests/MeshQueryRoutingTests.cs index 4d34804f..49d52812 100644 --- a/Core/Tests/MeshQueryRoutingTests.cs +++ b/Core/Tests/MeshQueryRoutingTests.cs @@ -7,11 +7,11 @@ namespace Chisel.Core.Tests public class MeshQueryRoutingTests { readonly static int[] kValidRenderableIndices = { 2, 3, 7, 9, 10, 11, 13, 15 }; - const int kShadowCastingRendererIndex = 2; // <[generated-ShadowCasting]>, ie shadow-only - const int kDefaultRendererIndex = 7; // <[generated-Renderable|ShadowCasting|ShadowReceiving]> - const int kRenderableRendererIndex = 9; // <[generated-Renderable|ExcludedFromGlobalIllumination]> - const int kReceivingRendererIndex = 13; // <[generated-Renderable|ShadowReceiving|ExcludedFromGlobalIllumination]> - const int kExcludedRendererIndex = 15; // <[generated-Renderable|ShadowCasting|ShadowReceiving|ExcludedFromGlobalIllumination]> + const int kShadowCastingRendererIndex = 2; // [generated-ShadowCasting], ie shadow-only + const int kDefaultRendererIndex = 7; // [generated-Renderable|ShadowCasting|ShadowReceiving] + const int kRenderableRendererIndex = 9; // [generated-Renderable|ExcludedFromGlobalIllumination] + const int kReceivingRendererIndex = 13; // [generated-Renderable|ShadowReceiving|ExcludedFromGlobalIllumination] + const int kExcludedRendererIndex = 15; // [generated-Renderable|ShadowCasting|ShadowReceiving|ExcludedFromGlobalIllumination] // Renderable, ShadowCasting, ShadowReceiving, Collidable and ExcludedFromGlobalIllumination; // Discarded is not a combination a real surface carries alongside the others, so it is left out. @@ -115,7 +115,7 @@ public void DefaultQueries_EveryRendererHasOneQuery() } } - // A surface that casts shadows but isn't rendered reaches <[generated-ShadowCasting]>, which casts it with the ForceShadowOnly material + // A surface that casts shadows but isn't rendered reaches [generated-ShadowCasting], which casts it with the ForceShadowOnly material [Test] public void DefaultQueries_ShadowOnlySurfaceReachesTheShadowCastingRenderer() { From 72877b0f491d60263981b50ba4cbb888c6bbddad Mon Sep 17 00:00:00 2001 From: Sander van Rossen Date: Sun, 25 Oct 2026 10:12:24 +0100 Subject: [PATCH 3/3] Remove canonical vertices, the incidence weld filter and the tree space plane cache Canonical vertices were an experiment for the old CSG path and never shipped (stage 0, off). Removed with them: the incidence weld filter (also an old path experiment, off), the HashedVertices overloads that took it, and the tree space plane cache, which only those read. The base polygons are built as before: corners as they are, snapped onto the corners of the brushes touching them (the hotspot caps read those corners). Fixes #59 --- Core/2.Processing/Cache/ChiselLookup.cs | 17 - .../Containers/CanonicalVertices.cs | 466 ------------------ .../Containers/CanonicalVertices.cs.meta | 2 - .../2.Processing/Containers/HashedVertices.cs | 133 ----- Core/2.Processing/Containers/WeldIncidence.cs | 107 ---- .../Containers/WeldIncidence.cs.meta | 2 - Core/2.Processing/Jobs/CacheRemappingJob.cs | 5 - .../Jobs/CreateBlobPolygonsBlobsJob.cs | 21 +- .../Jobs/CreateBrushTreeSpacePlanesJob.cs | 69 --- .../CreateBrushTreeSpacePlanesJob.cs.meta | 11 - .../Jobs/FindAllBrushIntersectionPairsJob.cs | 16 - .../Jobs/JobData/BrushTreeMesh.cs | 7 - .../Managers/CSGManager.UpdateTreeMeshes.cs | 57 +-- Core/Tests/CanonicalVerticesTests.cs | 318 ------------ Core/Tests/CanonicalVerticesTests.cs.meta | 2 - .../Contents/CanonicalVertexEditTests.cs | 113 ----- .../Contents/CanonicalVertexEditTests.cs.meta | 2 - .../Contents/SharedCornerExpectationTests.cs | 133 ----- .../SharedCornerExpectationTests.cs.meta | 2 - Core/Tests/Contents/SharedCornerSurvey.cs | 456 ----------------- .../Tests/Contents/SharedCornerSurvey.cs.meta | 2 - Core/Tests/WeldIncidenceTests.cs | 208 -------- Core/Tests/WeldIncidenceTests.cs.meta | 2 - Core/assembly.cs | 1 - 24 files changed, 8 insertions(+), 2144 deletions(-) delete mode 100644 Core/2.Processing/Containers/CanonicalVertices.cs delete mode 100644 Core/2.Processing/Containers/CanonicalVertices.cs.meta delete mode 100644 Core/2.Processing/Containers/WeldIncidence.cs delete mode 100644 Core/2.Processing/Containers/WeldIncidence.cs.meta delete mode 100644 Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs delete mode 100644 Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs.meta delete mode 100644 Core/Tests/CanonicalVerticesTests.cs delete mode 100644 Core/Tests/CanonicalVerticesTests.cs.meta delete mode 100644 Core/Tests/Contents/CanonicalVertexEditTests.cs delete mode 100644 Core/Tests/Contents/CanonicalVertexEditTests.cs.meta delete mode 100644 Core/Tests/Contents/SharedCornerExpectationTests.cs delete mode 100644 Core/Tests/Contents/SharedCornerExpectationTests.cs.meta delete mode 100644 Core/Tests/Contents/SharedCornerSurvey.cs delete mode 100644 Core/Tests/Contents/SharedCornerSurvey.cs.meta delete mode 100644 Core/Tests/WeldIncidenceTests.cs delete mode 100644 Core/Tests/WeldIncidenceTests.cs.meta diff --git a/Core/2.Processing/Cache/ChiselLookup.cs b/Core/2.Processing/Cache/ChiselLookup.cs index 49435be1..9ebdfc42 100644 --- a/Core/2.Processing/Cache/ChiselLookup.cs +++ b/Core/2.Processing/Cache/ChiselLookup.cs @@ -24,7 +24,6 @@ public class Data public NativeList> brushRenderBufferCache; public NativeList brushTreeSpaceBoundCache; - public NativeList> brushTreeSpacePlaneCache; public NativeList> brushesTouchedByBrushCache; // Each brush as the exact CSG sees it (ExactBrush: exact planes, corners, bounds), rebuilt when the brush // changes (ExactInputJob). @@ -72,7 +71,6 @@ internal void Initialize() basePolygonCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed treeSpaceVerticesCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed routingTableCache = new NativeList>(1000, Allocator.Persistent); // Confirmed to get disposed - 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 exactBrushCache = new NativeList>(1000, Allocator.Persistent); @@ -115,8 +113,6 @@ internal void EnsureCapacity(int brushCount) if (routingTableCache.Capacity < brushCount) routingTableCache.Capacity = brushCount; - if (brushTreeSpacePlaneCache.Capacity < brushCount) - brushTreeSpacePlaneCache.Capacity = brushCount; if (brushesTouchedByBrushCache.Capacity < brushCount) brushesTouchedByBrushCache.Capacity = brushCount; @@ -182,19 +178,6 @@ internal void Dispose() routingTableCache.Dispose(); } routingTableCache = default; - if (brushTreeSpacePlaneCache.IsCreated) - { - // Confirmed to be called - for (int i = 0; i < brushTreeSpacePlaneCache.Length; i++) - { - if (brushTreeSpacePlaneCache[i].IsCreated) - brushTreeSpacePlaneCache[i].Dispose(); - brushTreeSpacePlaneCache[i] = default; - } - brushTreeSpacePlaneCache.Clear(); - brushTreeSpacePlaneCache.Dispose(); - } - brushTreeSpacePlaneCache = default; if (brushesTouchedByBrushCache.IsCreated) { // Confirmed to be called diff --git a/Core/2.Processing/Containers/CanonicalVertices.cs b/Core/2.Processing/Containers/CanonicalVertices.cs deleted file mode 100644 index 1dfa6af9..00000000 --- a/Core/2.Processing/Containers/CanonicalVertices.cs +++ /dev/null @@ -1,466 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Threading; -using Unity.Burst; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core -{ - /// - /// How far the pipeline goes with canonical vertices. Each stage includes the ones before it. - /// - enum CanonicalVertexStage - { - /// Shipped behaviour: vertices are welded by distance. - Off = 0, - /// Canonical positions are computed and counted, but nothing uses them. - Measure = 1, - /// Vertices are created at their canonical positions; welding is unchanged. - Positions = 2, - /// While building loops, two vertices are the same only when their canonical positions are. - LoopIdentity = 3, - /// The same in the merge, the CSG re-weld and triangulation. - Everywhere = 4, - } - - /// Where a vertex was created, for the counters. - enum CanonicalVertexSite : byte - { - Unknown = 0, - BrushCorner = 1, // CreateBlobPolygonsBlobsJob - PairIntersection = 2, // CreateIntersectionLoopsJob: an edge of one brush through a face of the other - InsideVertex = 3, // CreateIntersectionLoopsJob: a corner of one brush inside the other - AlignedFace = 4, // CreateIntersectionLoopsJob: a corner of a face lying against the other brush - LoopSplit = 5, // FindLoopOverlapIntersectionsJob: an intersection loop cut by another brush - BaseSplit = 6, // FindLoopOverlapIntersectionsJob: a face cut by another brush - } - - /// - /// The planes a vertex lies on: a small set of face planes, each turned to face along its largest normal component, - /// kept sorted by coefficients and without exact duplicates. Its contents depend only on which planes were added, - /// never on the order they were added in. - /// - unsafe struct PlaneKey - { - internal const int kCapacity = 32; - - fixed float m_Planes[kCapacity * 4]; - public int Length; - public bool Overflowed; - - public readonly float4 this[int index] - { - get - { - int offset = index * 4; - return new float4(m_Planes[offset], m_Planes[offset + 1], m_Planes[offset + 2], m_Planes[offset + 3]); - } - } - - void Set(int index, float4 plane) - { - int offset = index * 4; - m_Planes[offset] = plane.x; - m_Planes[offset + 1] = plane.y; - m_Planes[offset + 2] = plane.z; - m_Planes[offset + 3] = plane.w; - } - - /// Inserts in order, ignoring exact duplicates. - public void Add(float4 plane) - { - plane = Oriented(plane); - int position = 0; - while (position < Length && Compare(this[position], plane) < 0) - position++; - if (position < Length && Compare(this[position], plane) == 0) - return; - if (Length == kCapacity) - { - // Keep the smallest planes, so that a full key is still the same whatever order its planes came in. - Overflowed = true; - if (position == kCapacity) - return; - Length--; - } - for (int i = Length; i > position; i--) - Set(i, this[i - 1]); - Set(position, plane); - Length++; - } - - public readonly bool Equals(in PlaneKey other) - { - if (Length != other.Length) - return false; - for (int i = 0; i < Length; i++) - { - if (Compare(this[i], other[i]) != 0) - return false; - } - return true; - } - - /// - /// A plane and its twin (the same plane facing the other way) are one plane here, so every plane is turned to - /// face along its largest normal component. Negation is exact, and so is the intersection of negated planes, - /// so this changes no position. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static float4 Oriented(float4 plane) - { - var magnitude = math.abs(plane.xyz); - var major = (magnitude.x >= magnitude.y && magnitude.x >= magnitude.z) ? plane.x - : (magnitude.y >= magnitude.z) ? plane.y - : plane.z; - if (major < 0) - plane = -plane; - // -0 and +0 compare equal but have different bits; adding +0 turns every -0 into +0 - return plane + 0.0f; - } - - // Lexicographic on (x, y, z, w). Only ever used to order planes and break exact ties, so it has to be total, - // not meaningful. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static int Compare(float4 a, float4 b) - { - if (a.x != b.x) return a.x < b.x ? -1 : 1; - if (a.y != b.y) return a.y < b.y ? -1 : 1; - if (a.z != b.z) return a.z < b.z ? -1 : 1; - if (a.w != b.w) return a.w < b.w ? -1 : 1; - return 0; - } - } - - unsafe struct CanonicalVertices - { - /// τ: how close a plane must be to a point to count as passing through it. - internal const double kOnPlane = CSGConstants.kFatPlaneWidthEpsilon; - - /// - /// How close two canonical positions must be to be the same vertex. Canonical positions of one vertex are - /// bit-identical; this only absorbs the rounding of a position that was stored and transformed on the way. - /// - internal const float kSameVertex = 0.00001f; - internal const float kSqrSameVertex = kSameVertex * kSameVertex; - - /// - /// The furthest a canonical position may lie from the computed one. Every plane of the key passes within τ of - /// the computed point, so a well-conditioned triple lands within a few τ of it; anything further means the - /// triple is too close to degenerate to trust, and the computed position is kept (and counted). - /// - internal const double kMaxMove = kOnPlane * 4; - - /// Below this |det| of three unit normals the triple is too close to degenerate to intersect. - const double kMinConditioning = 1e-6; - - [NativeDisableUnsafePtrRestriction] BlobAssetReference* m_TreeSpacePlanes; - int m_TreeSpacePlaneCount; - [NativeDisableUnsafePtrRestriction] BlobAssetReference* m_Touched; - int m_TouchedCount; - - public CanonicalVertexStage Stage; - - public static CanonicalVertices Disabled => default; - - public readonly bool Computes => Stage >= CanonicalVertexStage.Measure; - public readonly bool MovesVertices => Stage >= CanonicalVertexStage.Positions; - public readonly bool DecidesLoopIdentity => Stage >= CanonicalVertexStage.LoopIdentity; - public readonly bool DecidesAllIdentity => Stage >= CanonicalVertexStage.Everywhere; - - /// Whether would enable with these inputs. - public static bool IsAvailable(CanonicalVertexStage stage, - NativeList> treeSpacePlanes, - NativeList> touched) - { - return stage != CanonicalVertexStage.Off && treeSpacePlanes.IsCreated && touched.IsCreated; - } - - public static CanonicalVertices Create(CanonicalVertexStage stage, - NativeList> treeSpacePlanes, - NativeList> touched) - { - if (!IsAvailable(stage, treeSpacePlanes, touched)) - return Disabled; - return new CanonicalVertices - { - Stage = stage, - m_TreeSpacePlanes = (BlobAssetReference*)treeSpacePlanes.GetUnsafeReadOnlyPtr(), - m_TreeSpacePlaneCount = treeSpacePlanes.Length, - m_Touched = (BlobAssetReference*)touched.GetUnsafeReadOnlyPtr(), - m_TouchedCount = touched.Length, - }; - } - - /// - /// The position of the vertex near that lies on brush - /// . Below this only counts, and - /// returns . - /// - public readonly float3 Canonicalize(float3 approximate, int nodeOrder, CanonicalVertexSite site = CanonicalVertexSite.Unknown) - { - if (!Computes) - return approximate; - - var key = new PlaneKey(); - int ambiguous = Gather(approximate, nodeOrder, ref key); - if (!TryPosition(in key, out var position)) - { - CanonicalVertexStats.Record(site, in key, approximate, approximate, CanonicalVertexStats.Outcome.Degenerate, refined: false, ambiguous); - return approximate; - } - - // One refinement: the key at the canonical point. Two computations that started a little apart but - // agree here end up with the same bits even if their first keys differed at the edge of τ. - var refinedKey = new PlaneKey(); - Gather(position, nodeOrder, ref refinedKey); - bool refined = !refinedKey.Equals(in key); - if (refined && TryPosition(in refinedKey, out var refinedPosition)) - position = refinedPosition; - - ref var usedKey = ref (refined ? ref refinedKey : ref key); - if (math.distance((double3)approximate, (double3)position) > kMaxMove) - { - CanonicalVertexStats.Record(site, in usedKey, approximate, position, CanonicalVertexStats.Outcome.Rejected, refined, ambiguous); - return approximate; - } - CanonicalVertexStats.Record(site, in usedKey, approximate, position, CanonicalVertexStats.Outcome.Placed, refined, ambiguous); - return MovesVertices ? position : approximate; - } - - /// - /// The key of the point: the face planes within τ of it, from and every brush it - /// touches that contains the point. Returns how many faces lay between τ and 2τ. - /// - public readonly int Gather(float3 point, int nodeOrder, ref PlaneKey key) - { - int ambiguous = AddBrush(point, nodeOrder, ref key); - if (nodeOrder < 0 || nodeOrder >= m_TouchedCount) - return ambiguous; - var touchedRef = m_Touched[nodeOrder]; - if (!touchedRef.IsCreated) - return ambiguous; - ref var intersections = ref touchedRef.Value.brushIntersections; - for (int i = 0; i < intersections.Length; i++) - { - var other = intersections[i].nodeIndexOrder.nodeOrder; - if (other != nodeOrder) - ambiguous += AddBrush(point, other, ref key); - } - return ambiguous; - } - - readonly int AddBrush(float3 point, int nodeOrder, ref PlaneKey key) - { - if (nodeOrder < 0 || nodeOrder >= m_TreeSpacePlaneCount) - return 0; - var planesRef = m_TreeSpacePlanes[nodeOrder]; - if (!planesRef.IsCreated) - return 0; - ref var brush = ref planesRef.Value; - ref var brushPlanes = ref brush.treeSpacePlanes; - // Only the faces: the planes stored after them are edge planes, which cut through the interior and must - // not decide containment. - int faceCount = math.min(brush.faceCount, brushPlanes.Length); - - // Only a brush that contains the point identifies it: a plane is infinite, and one of a brush far - // away can pass through the point by accident. - for (int f = 0; f < faceCount; f++) - { - if (Distance(brushPlanes[f], point) > kOnPlane) - return 0; - } - - int ambiguous = 0; - for (int f = 0; f < faceCount; f++) - { - var distance = math.abs(Distance(brushPlanes[f], point)); - if (distance <= kOnPlane) - key.Add(brushPlanes[f]); - else if (distance <= kOnPlane * 2) - ambiguous++; - } - return ambiguous; - } - - /// - /// The canonical position of : the intersection of its best-conditioned triple. - /// - public static bool TryPosition(in PlaneKey key, out float3 position) - { - position = default; - if (key.Length < 3) - return false; - - // The key is sorted, so triples are visited in lexicographic order, and keeping the first of equally - // conditioned triples picks the one with the smallest planes: the choice depends only on the planes. - double bestConditioning = 0; - int bestA = -1, bestB = -1, bestC = -1; - for (int a = 0; a < key.Length - 2; a++) - { - var normalA = (double3)key[a].xyz; - for (int b = a + 1; b < key.Length - 1; b++) - { - var crossAB = math.cross(normalA, (double3)key[b].xyz); - for (int c = b + 1; c < key.Length; c++) - { - var conditioning = math.abs(math.dot(crossAB, (double3)key[c].xyz)); - if (conditioning <= bestConditioning) - continue; - bestConditioning = conditioning; - bestA = a; bestB = b; bestC = c; - } - } - } - if (bestA < 0 || bestConditioning < kMinConditioning) - return false; - - // The same three planes in the same order give the same bits, wherever this runs. - var intersection = PlaneExtensions.Intersection((double4)key[bestA], (double4)key[bestB], (double4)key[bestC]); - if (math.any(math.isnan(intersection)) || math.any(math.isinf(intersection))) - return false; - position = (float3)intersection; - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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; - } - } - - /// - /// What canonicalization did during the last rebuilds, in total and per creation site: how often it placed, - /// rejected or could not place a vertex, how far it moved them, how large keys were, and how often a face sat at - /// the edge of τ. Burst-safe; read and reset from the editor. - /// - static unsafe class CanonicalVertexStats - { - internal enum Outcome { Placed, Degenerate, Rejected } - - internal const int kComputed = 0; - internal const int kDegenerate = 1; // fewer than three planes, or no usable triple - internal const int kRejected = 2; // the triple landed more than kMaxMove away - internal const int kMoved = 3; - internal const int kRefined = 4; - internal const int kOverflowed = 5; - internal const int kAmbiguous = 6; // vertices with at least one face between τ and 2τ - internal const int kMaxMovedNm = 7; // largest displacement of a placed vertex, in nanometres - internal const int kDisplacement = 8; // 8 bins: 0, <1e-6, <1e-5, <1e-4, <τ, <2τ, <4τ, >=4τ - internal const int kDisplacementBins = 8; - internal const int kKeySize = kDisplacement + kDisplacementBins; // 8 bins: <3, 3, 4, 5, 6, 7, 8, >8 - internal const int kKeySizeBins = 8; - internal const int kCount = kKeySize + kKeySizeBins; - - const int kSites = 7; // block 0 is every site together, then one block per CanonicalVertexSite - - struct Block { public fixed long values[kCount * kSites]; } - - static readonly SharedStatic s_Block = SharedStatic.GetOrCreate(); - - public static void Record(CanonicalVertexSite site, in PlaneKey key, float3 approximate, float3 canonical, - Outcome outcome, bool refined, int ambiguous) - { - var values = (long*)s_Block.UnsafeDataPointer; - RecordInto(values, in key, approximate, canonical, outcome, refined, ambiguous); - var index = (int)site; - if (index > 0 && index < kSites) - RecordInto(values + index * kCount, in key, approximate, canonical, outcome, refined, ambiguous); - } - - static void RecordInto(long* values, in PlaneKey key, float3 approximate, float3 canonical, - Outcome outcome, bool refined, int ambiguous) - { - Interlocked.Increment(ref values[kComputed]); - if (outcome == Outcome.Degenerate) - Interlocked.Increment(ref values[kDegenerate]); - if (outcome == Outcome.Rejected) - Interlocked.Increment(ref values[kRejected]); - if (refined) - Interlocked.Increment(ref values[kRefined]); - if (key.Overflowed) - Interlocked.Increment(ref values[kOverflowed]); - if (ambiguous > 0) - Interlocked.Increment(ref values[kAmbiguous]); - - int size = key.Length < 3 ? 0 : key.Length > 8 ? 7 : key.Length - 2; - Interlocked.Increment(ref values[kKeySize + size]); - - if (outcome == Outcome.Degenerate) - return; - - var tau = CanonicalVertices.kOnPlane; - var moved = math.distance((double3)approximate, (double3)canonical); - int bin = moved == 0 ? 0 : moved < 1e-6 ? 1 : moved < 1e-5 ? 2 : moved < 1e-4 ? 3 - : moved < tau ? 4 : moved < tau * 2 ? 5 : moved < tau * 4 ? 6 : 7; - Interlocked.Increment(ref values[kDisplacement + bin]); - if (outcome != Outcome.Placed) - return; - if (moved > 0) - Interlocked.Increment(ref values[kMoved]); - - var nanometres = (long)(moved * 1e9); - long current; - do - { - current = values[kMaxMovedNm]; - if (nanometres <= current) - break; - } while (Interlocked.CompareExchange(ref values[kMaxMovedNm], nanometres, current) != current); - } - - public static void Reset() - { - var values = (long*)s_Block.UnsafeDataPointer; - for (int i = 0; i < kCount * kSites; i++) - values[i] = 0; - } - - public static long Get(int index, CanonicalVertexSite site = CanonicalVertexSite.Unknown) - { - return ((long*)s_Block.UnsafeDataPointer)[(int)site * kCount + index]; - } - - // Managed only (read from the editor after a rebuild). ASCII, so any console can print it. - public static string Describe() - { - var text = new System.Text.StringBuilder(); - for (int site = 0; site < kSites; site++) - { - var s = (CanonicalVertexSite)site; - long V(int i) => Get(i, s); - if (V(kComputed) == 0 && site > 0) - continue; - text.Append(site == 0 ? "all sites" : s.ToString()) - .Append(": computed ").Append(V(kComputed)) - .Append(", no usable key ").Append(V(kDegenerate)) - .Append(", rejected (moved > 4 tau) ").Append(V(kRejected)) - .Append(", moved ").Append(V(kMoved)) - .Append(" (max ").Append((V(kMaxMovedNm) / 1e6).ToString("0.######", System.Globalization.CultureInfo.InvariantCulture)).Append(" mm)") - .Append(", refined ").Append(V(kRefined)) - .Append(", overflowed ").Append(V(kOverflowed)) - .Append(", face near tau ").Append(V(kAmbiguous)).Append('\n'); - text.Append(" displacement: 0 ").Append(V(kDisplacement)) - .Append(", <1e-6 ").Append(V(kDisplacement + 1)) - .Append(", <1e-5 ").Append(V(kDisplacement + 2)) - .Append(", <1e-4 ").Append(V(kDisplacement + 3)) - .Append(", =4tau ").Append(V(kDisplacement + 7)).Append('\n'); - text.Append(" planes per vertex: <3 ").Append(V(kKeySize)) - .Append(", 3 ").Append(V(kKeySize + 1)) - .Append(", 4 ").Append(V(kKeySize + 2)) - .Append(", 5 ").Append(V(kKeySize + 3)) - .Append(", 6 ").Append(V(kKeySize + 4)) - .Append(", 7 ").Append(V(kKeySize + 5)) - .Append(", 8 ").Append(V(kKeySize + 6)) - .Append(", >8 ").Append(V(kKeySize + 7)).Append('\n'); - } - return text.ToString(); - } - } -} diff --git a/Core/2.Processing/Containers/CanonicalVertices.cs.meta b/Core/2.Processing/Containers/CanonicalVertices.cs.meta deleted file mode 100644 index 8ed8e7fd..00000000 --- a/Core/2.Processing/Containers/CanonicalVertices.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: c1b903ad1718b534b92f89556e321798 \ No newline at end of file diff --git a/Core/2.Processing/Containers/HashedVertices.cs b/Core/2.Processing/Containers/HashedVertices.cs index 425d7cd5..4c7bcdd4 100644 --- a/Core/2.Processing/Containers/HashedVertices.cs +++ b/Core/2.Processing/Containers/HashedVertices.cs @@ -173,109 +173,6 @@ public unsafe static ushort AddNoResize(ushort* hashTable, UnsafeList* c } } - public unsafe static ushort AddNoResize(ushort* hashTable, UnsafeList* chainedIndices, UnsafeList* vertices, float3 vertex, in WeldIncidenceFilter filter) - { - var centerIndex = new int3((int)(vertex.x / HashedVertices.kCellSize), (int)(vertex.y / HashedVertices.kCellSize), (int)(vertex.z / HashedVertices.kCellSize)); - var offsets = stackalloc int3[] - { - new int3(-1, -1, -1), new int3(-1, -1, 0), new int3(-1, -1, +1), - new int3(-1, 0, -1), new int3(-1, 0, 0), new int3(-1, 0, +1), - new int3(-1, +1, -1), new int3(-1, +1, 0), new int3(-1, +1, +1), - - new int3( 0, -1, -1), new int3( 0, -1, 0), new int3( 0, -1, +1), - new int3( 0, 0, -1), new int3( 0, 0, 0), new int3( 0, 0, +1), - new int3( 0, +1, -1), new int3( 0, +1, 0), new int3( 0, +1, +1), - - new int3(+1, -1, -1), new int3(+1, -1, 0), new int3(+1, -1, +1), - new int3(+1, 0, -1), new int3(+1, 0, 0), new int3(+1, 0, +1), - new int3(+1, +1, -1), new int3(+1, +1, 0), new int3(+1, +1, +1) - }; - - float3* verticesPtr = (float3*)vertices->Ptr; - - ushort closestVertexIndex = ushort.MaxValue; - for (int i = 0; i < 3 * 3 * 3; i++) - { - var index = centerIndex + offsets[i]; - var chainIndex = ((int)hashTable[GetHash(index)]) - 1; - { - double closestDistance = CSGConstants.kSqrVertexEqualEpsilon; - while (chainIndex != -1) - { - var nextChainIndex = ((int)((ushort*)chainedIndices->Ptr)[chainIndex]) - 1; - var sqrDistance = math.lengthsq((double3)verticesPtr[chainIndex] - (double3)vertex); - if (sqrDistance < closestDistance && filter.Allows(vertex, verticesPtr[chainIndex])) - { - closestVertexIndex = (ushort)chainIndex; - closestDistance = sqrDistance; - } - chainIndex = nextChainIndex; - } - } - } - if (closestVertexIndex != ushort.MaxValue) - return closestVertexIndex; - - // Add Unique vertex - { - - var hashCode = GetHash(centerIndex); - var prevChainIndex = hashTable[hashCode]; - var newChainIndex = chainedIndices->Length; - vertices ->AddNoResize(vertex); - chainedIndices->AddNoResize((ushort)prevChainIndex); - hashTable[(int)hashCode] = (ushort)(newChainIndex + 1); - return (ushort)newChainIndex; - } - } - - // Same as ReplaceIfExists, but a stored vertex is only moved onto the given position when the filter allows it. - public unsafe static void ReplaceIfExists(ushort* hashTable, UnsafeList* chainedIndices, UnsafeList* vertices, float3 vertex, in WeldIncidenceFilter filter) - { - var centerIndex = new int3((int)(vertex.x / HashedVertices.kCellSize), (int)(vertex.y / HashedVertices.kCellSize), (int)(vertex.z / HashedVertices.kCellSize)); - var offsets = stackalloc int3[] - { - new int3(-1, -1, -1), new int3(-1, -1, 0), new int3(-1, -1, +1), - new int3(-1, 0, -1), new int3(-1, 0, 0), new int3(-1, 0, +1), - new int3(-1, +1, -1), new int3(-1, +1, 0), new int3(-1, +1, +1), - - new int3( 0, -1, -1), new int3( 0, -1, 0), new int3( 0, -1, +1), - new int3( 0, 0, -1), new int3( 0, 0, 0), new int3( 0, 0, +1), - new int3( 0, +1, -1), new int3( 0, +1, 0), new int3( 0, +1, +1), - - new int3(+1, -1, -1), new int3(+1, -1, 0), new int3(+1, -1, +1), - new int3(+1, 0, -1), new int3(+1, 0, 0), new int3(+1, 0, +1), - new int3(+1, +1, -1), new int3(+1, +1, 0), new int3(+1, +1, +1) - }; - - float3* verticesPtr = (float3*)vertices->Ptr; - - ushort closestVertexIndex = ushort.MaxValue; - for (int i = 0; i < 3 * 3 * 3; i++) - { - var index = centerIndex + offsets[i]; - var chainIndex = ((int)hashTable[GetHash(index)]) - 1; - { - double closestDistance = CSGConstants.kSqrVertexEqualEpsilon; - while (chainIndex != -1) - { - var nextChainIndex = ((int)((ushort*)chainedIndices->Ptr)[chainIndex]) - 1; - var sqrDistance = math.lengthsq((double3)verticesPtr[chainIndex] - (double3)vertex); - if (sqrDistance < closestDistance && filter.Allows(verticesPtr[chainIndex], vertex)) - { - closestVertexIndex = (ushort)chainIndex; - closestDistance = sqrDistance; - } - chainIndex = nextChainIndex; - } - } - } - if (closestVertexIndex == ushort.MaxValue) - return; - - verticesPtr[closestVertexIndex] = vertex; - } - // Add but make the assumption we're not growing any list public unsafe static ushort Add(ushort* hashTable, UnsafeList* chainedIndices, UnsafeList* vertices, float3 vertex) { @@ -709,36 +606,6 @@ public unsafe ushort AddNoResize(float3 vertex) return HashedVerticesUtility.AddNoResize((ushort*)m_HashTable, m_ChainedIndices, m_Vertices, vertex); } - - // Merges onto an existing vertex only when the filter allows it; see WeldIncidenceFilter. - internal unsafe ushort AddNoResize(float3 vertex, in WeldIncidenceFilter filter) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); -#endif - if (!filter.IsEnabled) - return HashedVerticesUtility.AddNoResize((ushort*)m_HashTable, m_ChainedIndices, m_Vertices, vertex); - return HashedVerticesUtility.AddNoResize((ushort*)m_HashTable, m_ChainedIndices, m_Vertices, vertex, in filter); - } - - // Moves stored vertices onto the given ones only where the filter allows it; see WeldIncidenceFilter. - internal unsafe void ReplaceIfExists([ReadOnly] ref BlobArray uniqueVertices, in WeldIncidenceFilter filter) - { -#if ENABLE_UNITY_COLLECTIONS_CHECKS - AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); -#endif - if (!filter.IsEnabled) - { - ReplaceIfExists(ref uniqueVertices); - return; - } - for (int i = 0; i < uniqueVertices.Length; i++) - { - var vertex = uniqueVertices[i]; - HashedVerticesUtility.ReplaceIfExists((ushort*)m_HashTable, m_ChainedIndices, m_Vertices, vertex, in filter); - } - } - public unsafe void AddUniqueVertices([ReadOnly] ref BlobArray uniqueVertices) { #if ENABLE_UNITY_COLLECTIONS_CHECKS diff --git a/Core/2.Processing/Containers/WeldIncidence.cs b/Core/2.Processing/Containers/WeldIncidence.cs deleted file mode 100644 index f4a15b7d..00000000 --- a/Core/2.Processing/Containers/WeldIncidence.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core -{ - unsafe struct WeldIncidenceFilter - { - /// How close a point must be to a plane to count as lying on it: the CSG's own plane tolerance. - const float kOnPlane = CSGConstants.kFatPlaneWidthEpsilon; - - /// - /// How far a merge may end up pushing a point off a plane it lies on. Two tolerances, because each of the two - /// points may sit a full tolerance off the plane on opposite sides, and merging them anywhere between is no - /// worse than the pipeline's own plane tolerance. - /// - const float kOffPlane = kOnPlane * 2; - - float4* m_Planes0; - int m_FaceCount0; - float4* m_Planes1; - int m_FaceCount1; - bool m_SameVertexOnly; - - /// Allows every merge, i.e. the distance-only behaviour. - public static WeldIncidenceFilter Disabled => default; - - /// - /// For canonical vertices (): a vertex computed twice lands on the same bits, - /// so a merge is allowed only between positions that are the same vertex. Everything else is a different - /// vertex, however close. - /// - public static WeldIncidenceFilter SameVertexOnly => new WeldIncidenceFilter { m_SameVertexOnly = true }; - - /// The filter a weld site uses for the given canonical-vertex decision and incidence setting. - public static WeldIncidenceFilter Choose(bool canonicalDecides, bool useIncidenceWeld, in WeldIncidenceFilter incidence) - { - if (canonicalDecides) - return SameVertexOnly; - return useIncidenceWeld ? incidence : Disabled; - } - - /// - /// Gate against one brush's faces. Only the first planes bound the brush; - /// the edge planes stored after them cut through its interior and would reject points that are inside it. - /// - public static WeldIncidenceFilter Create(ref BlobArray planes, int facePlaneCount) - { - return new WeldIncidenceFilter - { - m_Planes0 = (float4*)planes.GetUnsafePtr(), - m_FaceCount0 = math.min(facePlaneCount, planes.Length) - }; - } - - /// Gate against the faces of both brushes of a pair. - public static WeldIncidenceFilter Create(ref BlobArray planes0, int facePlaneCount0, - ref BlobArray planes1, int facePlaneCount1) - { - return new WeldIncidenceFilter - { - m_Planes0 = (float4*)planes0.GetUnsafePtr(), - m_FaceCount0 = math.min(facePlaneCount0, planes0.Length), - m_Planes1 = (float4*)planes1.GetUnsafePtr(), - m_FaceCount1 = math.min(facePlaneCount1, planes1.Length) - }; - } - - public readonly bool IsEnabled { get { return m_SameVertexOnly || m_Planes0 != null || m_Planes1 != null; } } - - /// True when moving onto keeps every incidence. - public readonly bool Allows(float3 from, float3 to) - { - if (m_SameVertexOnly) - return math.lengthsq(from - to) <= CanonicalVertices.kSqrSameVertex; - return KeepsIncidence(m_Planes0, m_FaceCount0, from, to) && - KeepsIncidence(m_Planes1, m_FaceCount1, from, to); - } - - static bool KeepsIncidence(float4* planes, int faceCount, float3 a, float3 b) - { - if (planes == null || faceCount <= 0) - return true; - - var a4 = new float4(a, 1); - var b4 = new float4(b, 1); - bool aInside = true, bInside = true; - for (int i = 0; i < faceCount; i++) - { - var plane = planes[i]; - if (math.dot(plane, a4) > kOnPlane) aInside = false; - if (math.dot(plane, b4) > kOnPlane) bInside = false; - if (!aInside && !bInside) - return true; - } - - for (int i = 0; i < faceCount; i++) - { - var plane = planes[i]; - var distanceA = math.abs(math.dot(plane, a4)); - var distanceB = math.abs(math.dot(plane, b4)); - if (aInside && distanceA <= kOnPlane && distanceB > kOffPlane) return false; - if (bInside && distanceB <= kOnPlane && distanceA > kOffPlane) return false; - } - return true; - } - } -} diff --git a/Core/2.Processing/Containers/WeldIncidence.cs.meta b/Core/2.Processing/Containers/WeldIncidence.cs.meta deleted file mode 100644 index 7b6f6e08..00000000 --- a/Core/2.Processing/Containers/WeldIncidence.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 5c5f47430587b5e4ebcc142dab53b6f5 \ No newline at end of file diff --git a/Core/2.Processing/Jobs/CacheRemappingJob.cs b/Core/2.Processing/Jobs/CacheRemappingJob.cs index b9c3f216..193624c7 100644 --- a/Core/2.Processing/Jobs/CacheRemappingJob.cs +++ b/Core/2.Processing/Jobs/CacheRemappingJob.cs @@ -44,7 +44,6 @@ public int Compare(int2 a, int2 b) [NoAlias] public NativeList transformationCache; [NoAlias] public NativeList> brushRenderBufferCache; [NoAlias] public NativeList> treeSpaceVerticesCache; - [NoAlias] public NativeList> brushTreeSpacePlaneCache; [NoAlias] public NativeList brushTreeSpaceBoundCache; [NoAlias] public NativeList> brushesTouchedByBrushCache; [NoAlias] public NativeList> exactBrushCache; @@ -183,8 +182,6 @@ public void Execute() brushRenderBufferCache.Resize(maxCount, NativeArrayOptions.ClearMemory); if (brushTreeSpaceBoundCache.Length < maxCount) brushTreeSpaceBoundCache.Resize(maxCount, NativeArrayOptions.ClearMemory); - if (brushTreeSpacePlaneCache.Length < maxCount) - brushTreeSpacePlaneCache.Resize(maxCount, NativeArrayOptions.ClearMemory); if (brushesTouchedByBrushCache.Length < maxCount) brushesTouchedByBrushCache.Resize(maxCount, NativeArrayOptions.ClearMemory); if (exactBrushCache.Length < maxCount) @@ -208,7 +205,6 @@ public void Execute() { var tmp = brushRenderBufferCache[source]; brushRenderBufferCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } { var tmp = treeSpaceVerticesCache[source]; treeSpaceVerticesCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } { 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 = exactBrushCache[source]; exactBrushCache[source] = default; if (tmp.IsCreated) tmp.Dispose(); } continue; @@ -220,7 +216,6 @@ public void Execute() (brushRenderBufferCache[source], brushRenderBufferCache[destination]) = (brushRenderBufferCache[destination], brushRenderBufferCache[source]); (treeSpaceVerticesCache[source], treeSpaceVerticesCache[destination]) = (treeSpaceVerticesCache[destination], treeSpaceVerticesCache[source]); (brushTreeSpaceBoundCache[source], brushTreeSpaceBoundCache[destination]) = (brushTreeSpaceBoundCache[destination], brushTreeSpaceBoundCache[source]); - (brushTreeSpacePlaneCache[source], brushTreeSpacePlaneCache[destination]) = (brushTreeSpacePlaneCache[destination], brushTreeSpacePlaneCache[source]); (brushesTouchedByBrushCache[source], brushesTouchedByBrushCache[destination]) = (brushesTouchedByBrushCache[destination], brushesTouchedByBrushCache[source]); (exactBrushCache[source], exactBrushCache[destination]) = (exactBrushCache[destination], exactBrushCache[source]); } diff --git a/Core/2.Processing/Jobs/CreateBlobPolygonsBlobsJob.cs b/Core/2.Processing/Jobs/CreateBlobPolygonsBlobsJob.cs index c218f1da..756af440 100644 --- a/Core/2.Processing/Jobs/CreateBlobPolygonsBlobsJob.cs +++ b/Core/2.Processing/Jobs/CreateBlobPolygonsBlobsJob.cs @@ -20,10 +20,6 @@ struct CreateBlobPolygonsBlobsJob : IJobParallelForDefer [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; [NoAlias, ReadOnly] public NativeList> treeSpaceVerticesCache; - // Canonical vertices (see CanonicalVertices) - [NoAlias, ReadOnly] public CanonicalVertexStage canonicalVertexStage; - [NoAlias, ReadOnly] public NativeList> brushTreeSpacePlaneCache; - // Write [NativeDisableParallelForRestriction] [NoAlias, WriteOnly] public NativeList> basePolygonCache; @@ -80,7 +76,7 @@ static void RemoveDuplicates(NativeArray edges, ref int edgeCount) } } - bool CopyPolygonToIndices([NoAlias, ReadOnly] BlobAssetReference mesh, [NoAlias, ReadOnly] NativeArray treeSpaceVertices, int polygonIndex, [NoAlias] HashedVertices hashedTreeSpaceVertices, [NoAlias] NativeArray edges, [NoAlias] ref int edgeCount, in WeldIncidenceFilter weldFilter) + bool CopyPolygonToIndices([NoAlias, ReadOnly] BlobAssetReference mesh, [NoAlias, ReadOnly] NativeArray treeSpaceVertices, int polygonIndex, [NoAlias] HashedVertices hashedTreeSpaceVertices, [NoAlias] NativeArray edges, [NoAlias] ref int edgeCount) { ref var halfEdges = ref mesh.Value.halfEdges; ref var polygon = ref mesh.Value.polygons[polygonIndex]; @@ -95,7 +91,7 @@ bool CopyPolygonToIndices([NoAlias, ReadOnly] BlobAssetReference var vertexIndex = halfEdges[e].vertexIndex; var treeSpaceVertex = treeSpaceVertices[vertexIndex]; - var newIndex = hashedTreeSpaceVertices.AddNoResize(treeSpaceVertex, in weldFilter); + var newIndex = hashedTreeSpaceVertices.AddNoResize(treeSpaceVertex); if (e > firstEdge) { var edge = edges[edgeCount - 1]; @@ -144,14 +140,10 @@ public void Execute(int b) ref var localPlanes = ref mesh.Value.localPlanes; ref var polygons = ref mesh.Value.polygons; - var canonical = CanonicalVertices.Create(canonicalVertexStage, brushTreeSpacePlaneCache, brushesTouchedByBrushCache); - var weldFilter = canonical.DecidesLoopIdentity ? WeldIncidenceFilter.SameVertexOnly : WeldIncidenceFilter.Disabled; - - // Each corner once, not once per face it belongs to. NativeArray treeSpaceVertices; using var _treeSpaceVertices = treeSpaceVertices = new NativeArray(treeSpaceVerticesBlob.Length, Allocator.Temp); for (int v = 0; v < treeSpaceVerticesBlob.Length; v++) - treeSpaceVertices[v] = canonical.Canonicalize(treeSpaceVerticesBlob[v], nodeOrder, CanonicalVertexSite.BrushCorner); + treeSpaceVertices[v] = treeSpaceVerticesBlob[v]; HashedVertices hashedTreeSpaceVertices; using var _hashedTreeSpaceVertices = hashedTreeSpaceVertices = new HashedVertices(math.max(treeSpaceVertices.Length, 1000), Allocator.Temp); @@ -198,7 +190,7 @@ public void Execute(int b) //var tempEdges = new NativeArray(polygon.edgeCount, Allocator.Temp); - CopyPolygonToIndices(mesh, treeSpaceVertices, polygonIndex, hashedTreeSpaceVertices, tempEdges, ref edgeCount, in weldFilter); + CopyPolygonToIndices(mesh, treeSpaceVertices, polygonIndex, hashedTreeSpaceVertices, tempEdges, ref edgeCount); if (edgeCount == 0) // Can happen when multiple vertices are collapsed on eachother / degenerate polygon { validPolygons[totalSurfaceCount] = new ValidPolygon @@ -232,10 +224,7 @@ public void Execute(int b) // NOTE: assumes brushIntersections is in the same order as the brushes are in the tree ref var brushIntersections = ref brushesTouchedByBrushCache[nodeOrder].Value.brushIntersections; - // Canonical corners that are the same vertex already have the same bits, and snapping onto a - // neighbour's raw corners would undo them. - var snapToNeighbours = !canonical.MovesVertices; - for (int i = 0; snapToNeighbours && i < brushIntersections.Length; i++) + for (int i = 0; i < brushIntersections.Length; i++) { var intersectingNodeOrder = brushIntersections[i].nodeIndexOrder.nodeOrder; if (intersectingNodeOrder < nodeOrder) diff --git a/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs b/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs deleted file mode 100644 index e28bab03..00000000 --- a/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -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 CreateBrushTreeSpacePlanesJob : IJobParallelForDefer - { - // Read - [NoAlias, ReadOnly] public NativeList allUpdateBrushIndexOrders; - [NoAlias, ReadOnly] public NativeArray> brushMeshLookup; - [NoAlias, ReadOnly] public NativeList transformationCache; - - // Write - [NativeDisableParallelForRestriction] - [NoAlias, WriteOnly] public NativeList> brushTreeSpacePlanes; - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - readonly BlobAssetReference Build(ref BrushMeshBlob brushMeshBlob, float4x4 nodeToTreeTransformation) - { - var nodeToTreeInversed = math.inverse(nodeToTreeTransformation); - var nodeToTreeInverseTransposed = math.transpose(nodeToTreeInversed); - - ref var localPlanes = ref brushMeshBlob.localPlanes; - - var totalSize = 16 + (localPlanes.Length * UnsafeUtility.SizeOf()); - - using var builder = new BlobBuilder(Allocator.Temp, totalSize); - ref var root = ref builder.ConstructRoot(); - var treeSpacePlaneArray = builder.Allocate(ref root.treeSpacePlanes, localPlanes.Length); - root.faceCount = brushMeshBlob.localPlaneCount; - for (int i = 0; i < localPlanes.Length; i++) - { - var localPlane = localPlanes[i]; - var treePlane = math.mul(nodeToTreeInverseTransposed, localPlane); - treePlane /= math.length(treePlane.xyz); - treeSpacePlaneArray[i] = treePlane; - } - var result = builder.CreateBlobAssetReference(Allocator.Persistent); // Confirmed to be disposed - return result; - } - - public void Execute(int index) - { - var brushIndexOrder = allUpdateBrushIndexOrders[index]; - int brushNodeOrder = brushIndexOrder.nodeOrder; - var brushMeshBlob = brushMeshLookup[brushNodeOrder]; - if (!brushMeshBlob.IsCreated) - { - // The brushMeshID is invalid: a Generator created/didn't update a TreeBrush correctly, or the input values didn't produce a valid mesh (for example; 0 height cube) - //Debug.LogError($"BrushMeshBlob invalid for brush with index {brushIndexOrder.compactNodeID}"); - brushTreeSpacePlanes[brushNodeOrder] = BlobAssetReference.Null; - return; - } - var worldPlanes = Build(ref brushMeshLookup[brushNodeOrder].Value, transformationCache[brushNodeOrder].nodeToTree); - brushTreeSpacePlanes[brushNodeOrder] = worldPlanes; - } - } -} diff --git a/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs.meta b/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs.meta deleted file mode 100644 index ee90a78d..00000000 --- a/Core/2.Processing/Jobs/CreateBrushTreeSpacePlanesJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cf448d9f40c39ba49a4ccdec9076f5a8 -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 8d36a356..0059c254 100644 --- a/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs +++ b/Core/2.Processing/Jobs/FindAllBrushIntersectionPairsJob.cs @@ -282,8 +282,6 @@ struct InvalidateBrushCacheJob : IJobParallelForDefer [NativeDisableParallelForRestriction] [NoAlias] public NativeList> routingTableCache; [NativeDisableParallelForRestriction] - [NoAlias] public NativeList> brushTreeSpacePlaneCache; - [NativeDisableParallelForRestriction] [NoAlias] public NativeList> brushRenderBufferCache; // Write @@ -291,7 +289,6 @@ struct InvalidateBrushCacheJob : IJobParallelForDefer [NoAlias, WriteOnly] public NativeList>.ParallelWriter treeSpaceVerticesDisposeList; [NoAlias, WriteOnly] public NativeList>.ParallelWriter brushesTouchedByBrushDisposeList; [NoAlias, WriteOnly] public NativeList>.ParallelWriter routingTableDisposeList; - [NoAlias, WriteOnly] public NativeList>.ParallelWriter brushTreeSpacePlaneDisposeList; [NoAlias, WriteOnly] public NativeList>.ParallelWriter brushRenderBufferDisposeList; public void Execute(int index) @@ -332,12 +329,6 @@ public void Execute(int index) routingTableDisposeList.AddNoResize(original); routingTableCache[nodeOrder] = default; } - { - var original = brushTreeSpacePlaneCache[nodeOrder]; - if (original != default && original.IsCreated) - brushTreeSpacePlaneDisposeList.AddNoResize(original); - brushTreeSpacePlaneCache[nodeOrder] = default; - } { var original = brushRenderBufferCache[nodeOrder]; if (original != default && original.IsCreated) @@ -367,8 +358,6 @@ struct InvalidateIndirectBrushCacheJob : IJobParallelForDefer [NativeDisableParallelForRestriction] [NoAlias] public NativeList> routingTableCache; [NativeDisableParallelForRestriction] - [NoAlias] public NativeList> brushTreeSpacePlaneCache; - [NativeDisableParallelForRestriction] [NoAlias] public NativeList> brushRenderBufferCache; public void Execute(int index) @@ -405,11 +394,6 @@ public void Execute(int index) if (original != default && original.IsCreated) original.Dispose(); routingTableCache[nodeOrder] = default; } - { - var original = brushTreeSpacePlaneCache[nodeOrder]; - if (original != default && original.IsCreated) original.Dispose(); - brushTreeSpacePlaneCache[nodeOrder] = default; - } { var original = brushRenderBufferCache[nodeOrder]; if (original != default && original.IsCreated) original.Dispose(); diff --git a/Core/2.Processing/Jobs/JobData/BrushTreeMesh.cs b/Core/2.Processing/Jobs/JobData/BrushTreeMesh.cs index 0834c044..6cf3aaa9 100644 --- a/Core/2.Processing/Jobs/JobData/BrushTreeMesh.cs +++ b/Core/2.Processing/Jobs/JobData/BrushTreeMesh.cs @@ -11,11 +11,4 @@ struct BrushTreeSpaceVerticesBlob { public BlobArray treeSpaceVertices; } - - struct BrushTreeSpacePlanes - { - public BlobArray treeSpacePlanes; - // How many of treeSpacePlanes are the brush's faces (BrushMeshBlob.localPlaneCount); the rest are edge planes. - public int faceCount; - } } diff --git a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs index ba55c0c7..699a5b6f 100644 --- a/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs +++ b/Core/2.Processing/Managers/CSGManager.UpdateTreeMeshes.cs @@ -175,14 +175,10 @@ internal struct TreeUpdate internal static bool s_RemakeAll; internal static int s_ModifiedBrushCount; - internal static int kCanonicalVertexStage = 0; - - internal static bool kCanonicalAlignment = 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; // 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; @@ -278,7 +274,6 @@ internal struct TemporariesStruct public NativeList> treeSpaceVerticesDisposeList; public NativeList> brushesTouchedByBrushDisposeList; public NativeList> routingTableDisposeList; - public NativeList> brushTreeSpacePlaneDisposeList; public NativeList> brushRenderBufferDisposeList; // Decals (ChiselDecalStore): this update's copy of the tree's decals, and where they changed @@ -315,7 +310,6 @@ internal enum JobHandleType brushesTouchedByBrushCacheJobHandle, brushRenderBufferCacheJobHandle, brushRenderDataJobHandle, - brushTreeSpacePlaneCacheJobHandle, brushMeshBlobsLookupJobHandle, hierarchyIDJobHandle, hierarchyListJobHandle, @@ -510,9 +504,6 @@ public void Initialize() chiselLookupValues.EnsureCapacity(newBrushCount); this.maxNodeOrder = this.brushCount; - this.canonicalVertexStage = (CanonicalVertexStage)math.clamp(kCanonicalVertexStage, - (int)CanonicalVertexStage.Off, - (int)CanonicalVertexStage.Everywhere); Temporaries.meshDataArray = default; Temporaries.meshDatas = new NativeList(defaultAllocator); @@ -620,8 +611,6 @@ public void Initialize() chiselLookupValues.brushRenderBufferCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); if (chiselLookupValues.treeSpaceVerticesCache.Length < newBrushCount) chiselLookupValues.treeSpaceVerticesCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); - if (chiselLookupValues.brushTreeSpacePlaneCache.Length < newBrushCount) - chiselLookupValues.brushTreeSpacePlaneCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); if (chiselLookupValues.brushTreeSpaceBoundCache.Length < newBrushCount) chiselLookupValues.brushTreeSpaceBoundCache.Resize(newBrushCount, NativeArrayOptions.ClearMemory); if (chiselLookupValues.brushesTouchedByBrushCache.Length < newBrushCount) @@ -633,7 +622,6 @@ public void Initialize() Temporaries.treeSpaceVerticesDisposeList = new NativeList>(chiselLookupValues.treeSpaceVerticesCache.Length, defaultAllocator); Temporaries.brushesTouchedByBrushDisposeList = new NativeList>(chiselLookupValues.brushesTouchedByBrushCache.Length, defaultAllocator); 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>(math.max(1, chiselLookupValues.exactBrushCache.Length), defaultAllocator); @@ -673,7 +661,6 @@ public void Initialize() readonly static ProfilerMarker kJob_AddIndirectUpdatedBrushesToListAndSortProfilerMarker = new("Job_AddIndirectUpdatedBrushesToListAndSort"); readonly static ProfilerMarker kJob_GatherAndStoreBrushIntersectionsProfilerMarker = new("Job_GatherAndStoreBrushIntersections"); readonly static ProfilerMarker kJob_GenerateBasePolygonLoopsProfilerMarker = new("Job_GenerateBasePolygonLoops"); - readonly static ProfilerMarker kJob_UpdateBrushTreeSpacePlanesProfilerMarker = new("Job_UpdateBrushTreeSpacePlanes"); readonly static ProfilerMarker kJob_UpdateBrushCategorizationTablesProfilerMarker = new("Job_UpdateBrushCategorizationTables"); readonly static ProfilerMarker kJob_PerformCSGProfilerMarker = new("Job_PerformCSG"); readonly static ProfilerMarker kJob_GenerateSurfaceTrianglesProfilerMarker = new("Job_GenerateSurfaceTriangles"); @@ -1041,7 +1028,6 @@ public void RunMeshInitJobs(JobHandle dependsOn) transformationCache = chiselLookupValues.transformationCache, brushRenderBufferCache = chiselLookupValues.brushRenderBufferCache, treeSpaceVerticesCache = chiselLookupValues.treeSpaceVerticesCache, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, brushTreeSpaceBoundCache = chiselLookupValues.brushTreeSpaceBoundCache, brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, exactBrushCache = chiselLookupValues.exactBrushCache, @@ -1063,7 +1049,6 @@ public void RunMeshInitJobs(JobHandle dependsOn) JobHandleType.transformationCacheJobHandle, JobHandleType.brushRenderBufferCacheJobHandle, JobHandleType.treeSpaceVerticesCacheJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, JobHandleType.brushTreeSpaceBoundCacheJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, JobHandleType.exactBrushCacheJobHandle, @@ -1386,7 +1371,6 @@ public void RunMeshUpdateJobs() treeSpaceVerticesCache = chiselLookupValues.treeSpaceVerticesCache, brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, routingTableCache = chiselLookupValues.routingTableCache, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, brushRenderBufferCache = chiselLookupValues.brushRenderBufferCache, // Write @@ -1394,7 +1378,6 @@ public void RunMeshUpdateJobs() routingTableDisposeList = Temporaries.routingTableDisposeList.AsParallelWriter(), brushRenderBufferDisposeList = Temporaries.brushRenderBufferDisposeList.AsParallelWriter(), treeSpaceVerticesDisposeList = Temporaries.treeSpaceVerticesDisposeList.AsParallelWriter(), - brushTreeSpacePlaneDisposeList = Temporaries.brushTreeSpacePlaneDisposeList.AsParallelWriter(), brushesTouchedByBrushDisposeList = Temporaries.brushesTouchedByBrushDisposeList.AsParallelWriter() }; invalidateBrushCacheJob.Schedule(runInParallel, Temporaries.rebuildTreeBrushIndexOrders, 16, @@ -1405,7 +1388,6 @@ public void RunMeshUpdateJobs() JobHandleType.treeSpaceVerticesCacheJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, JobHandleType.routingTableCacheJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, JobHandleType.brushRenderBufferCacheJobHandle)); } #endregion @@ -1574,7 +1556,6 @@ public void RunMeshUpdateJobs() treeSpaceVerticesCache = chiselLookupValues.treeSpaceVerticesCache, brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, routingTableCache = chiselLookupValues.routingTableCache, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, brushRenderBufferCache = chiselLookupValues.brushRenderBufferCache }; invalidateIndirectBrushCacheJob.Schedule(runInParallel, Temporaries.brushesThatNeedIndirectUpdate, 16, @@ -1585,7 +1566,6 @@ public void RunMeshUpdateJobs() JobHandleType.treeSpaceVerticesCacheJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, JobHandleType.routingTableCacheJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle, JobHandleType.brushRenderBufferCacheJobHandle)); } #endregion @@ -1740,31 +1720,6 @@ public void RunMeshUpdateJobs() #region Determine Intersection Surfaces // Find all pairs of brush intersections for each brush - using (kJob_UpdateBrushTreeSpacePlanesProfilerMarker.Auto()) - { - const bool runInParallel = runInParallelDefault; - // TODO: should only do this at creation time + when moved / store with brush component itself - var createBrushTreeSpacePlanesJob = new CreateBrushTreeSpacePlanesJob - { - // Read - allUpdateBrushIndexOrders = Temporaries.allUpdateBrushIndexOrders, - brushMeshLookup = Temporaries.brushMeshLookup, - transformationCache = chiselLookupValues.transformationCache, - - // Write - brushTreeSpacePlanes = chiselLookupValues.brushTreeSpacePlaneCache - }; - createBrushTreeSpacePlanesJob.Schedule(runInParallel, Temporaries.allUpdateBrushIndexOrders, 16, - JobHandles.Read( - JobHandleType.allUpdateBrushIndexOrdersJobHandle, - JobHandleType.brushMeshLookupJobHandle, - JobHandleType.transformationCacheJobHandle), - JobHandles.Write( - JobHandleType.brushTreeSpacePlaneCacheJobHandle)); - - } - - // After the tree-space planes: canonical vertices place the brush corners with them. using (kJob_GenerateBasePolygonLoopsProfilerMarker.Auto()) { const bool runInParallel = runInParallelDefault; @@ -1776,8 +1731,6 @@ public void RunMeshUpdateJobs() brushesTouchedByBrushCache = chiselLookupValues.brushesTouchedByBrushCache, brushMeshLookup = Temporaries.brushMeshLookup, treeSpaceVerticesCache = chiselLookupValues.treeSpaceVerticesCache, - canonicalVertexStage = canonicalVertexStage, - brushTreeSpacePlaneCache = chiselLookupValues.brushTreeSpacePlaneCache, // Write basePolygonCache = chiselLookupValues.basePolygonCache @@ -1787,8 +1740,7 @@ public void RunMeshUpdateJobs() JobHandleType.allUpdateBrushIndexOrdersJobHandle, JobHandleType.brushesTouchedByBrushCacheJobHandle, JobHandleType.brushMeshLookupJobHandle, - JobHandleType.treeSpaceVerticesCacheJobHandle, - JobHandleType.brushTreeSpacePlaneCacheJobHandle), + JobHandleType.treeSpaceVerticesCacheJobHandle), JobHandles.Write( JobHandleType.basePolygonCacheJobHandle)); } @@ -2675,8 +2627,7 @@ public JobHandle PreMeshUpdateDispose() JobHandles[JobHandleType.brushBrushIntersectionsJobHandle].writeBarrier, JobHandles[JobHandleType.brushesTouchedByBrushCacheJobHandle].writeBarrier, JobHandles[JobHandleType.brushRenderBufferCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.brushRenderDataJobHandle].writeBarrier, - JobHandles[JobHandleType.brushTreeSpacePlaneCacheJobHandle].writeBarrier), + JobHandles[JobHandleType.brushRenderDataJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.brushMeshBlobsLookupJobHandle].writeBarrier, JobHandles[JobHandleType.hierarchyIDJobHandle].writeBarrier, @@ -2766,7 +2717,6 @@ public JobHandle PreMeshUpdateDispose() 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)); @@ -2804,8 +2754,7 @@ public JobHandle FreeTemporaries(ref JobHandle finalJobHandle) JobHandles[JobHandleType.brushBrushIntersectionsJobHandle].writeBarrier, JobHandles[JobHandleType.brushesTouchedByBrushCacheJobHandle].writeBarrier, JobHandles[JobHandleType.brushRenderBufferCacheJobHandle].writeBarrier, - JobHandles[JobHandleType.brushRenderDataJobHandle].writeBarrier, - JobHandles[JobHandleType.brushTreeSpacePlaneCacheJobHandle].writeBarrier), + JobHandles[JobHandleType.brushRenderDataJobHandle].writeBarrier), JobHandleExtensions.CombineDependencies( JobHandles[JobHandleType.brushMeshBlobsLookupJobHandle].writeBarrier, JobHandles[JobHandleType.hierarchyIDJobHandle].writeBarrier, diff --git a/Core/Tests/CanonicalVerticesTests.cs b/Core/Tests/CanonicalVerticesTests.cs deleted file mode 100644 index 40674796..00000000 --- a/Core/Tests/CanonicalVerticesTests.cs +++ /dev/null @@ -1,318 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class CanonicalVerticesTests - { - // The pipeline inputs canonical vertices read, for brushes given as tree-space face planes with outward normals - // (n·x + w <= 0 inside). Every brush touches every other one. - sealed class Scene : IDisposable - { - public NativeList> treeSpacePlanes; - public NativeList> touched; - - public Scene(params float4[][] brushes) - { - treeSpacePlanes = new NativeList>(brushes.Length, Allocator.Persistent); - touched = new NativeList>(brushes.Length, Allocator.Persistent); - for (int b = 0; b < brushes.Length; b++) - { - // Normalized as CreateBrushTreeSpacePlanesJob does it - treeSpacePlanes.Add(Blob(brushes[b].Select(p => p / math.length(p.xyz)).ToArray())); - touched.Add(Touching(b, brushes.Length)); - } - } - - public CanonicalVertices Canonical(CanonicalVertexStage stage = CanonicalVertexStage.Positions) - { - return CanonicalVertices.Create(stage, treeSpacePlanes, touched); - } - - public void Dispose() - { - foreach (var blob in treeSpacePlanes) blob.Dispose(); - foreach (var blob in touched) blob.Dispose(); - treeSpacePlanes.Dispose(); - touched.Dispose(); - } - - static BlobAssetReference Blob(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]; - root.faceCount = planes.Length; - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - - static BlobAssetReference Touching(int self, int count) - { - using var builder = new BlobBuilder(Allocator.Temp); - ref var root = ref builder.ConstructRoot(); - var list = builder.Allocate(ref root.brushIntersections, count - 1); - int n = 0; - for (int i = 0; i < count; i++) - { - if (i != self) - list[n++] = new BrushIntersection { nodeIndexOrder = new IndexOrder { nodeOrder = i }, type = IntersectionType.Intersection }; - } - builder.Allocate(ref root.intersectionBits, 0); - return builder.CreateBlobAssetReference(Allocator.Persistent); - } - } - - // An axis-aligned box as six outward planes. - static float4[] Box(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), - }; - } - - // Rotates planes: a plane (n, w) through a rotation R becomes (R n, w). - static float4[] Rotate(float4[] planes, quaternion rotation) - { - return planes.Select(p => new float4(math.mul(rotation, p.xyz), p.w)).ToArray(); - } - - static readonly quaternion kTurn = quaternion.Euler(0.3f, 0.7f, -0.2f); - - static bool SameBits(float a, float b) => BitConverter.SingleToInt32Bits(a) == BitConverter.SingleToInt32Bits(b); - static bool SameBits(float3 a, float3 b) => SameBits(a.x, b.x) && SameBits(a.y, b.y) && SameBits(a.z, b.z); - static bool SameBits(float4 a, float4 b) => SameBits(a.xyz, b.xyz) && SameBits(a.w, b.w); - - // Two turned boxes side by side. The second's shared face is nudged 0.2 um, so the brushes' own planes - // differ, as they do after transforms. - static float4[][] TwoTurnedBoxes() - { - var a = Rotate(Box(new float3(0, 0, 0), new float3(1, 1, 1)), kTurn); - var b = Rotate(Box(new float3(1, 0, 0), new float3(2, 1, 1)), kTurn); - b[0].w += 2e-7f; - return new[] { a, b }; - } - - static float3 SharedCorner => math.mul(kTurn, new float3(1, 0, 0)); - - [Test] - public void ASharedCorner_HasTheSameBitsFromEitherBrush() - { - using var scene = new Scene(TwoTurnedBoxes()); - var canonical = scene.Canonical(); - - var fromA = canonical.Canonicalize(SharedCorner + new float3(1e-6f, -2e-6f, 0), 0); - var fromB = canonical.Canonicalize(SharedCorner + new float3(-2e-6f, 1e-6f, 1e-6f), 1); - - Assert.That(SameBits(fromA, fromB), Is.True, $"{fromA} from the first brush, {fromB} from the second"); - Assert.That(math.distance(fromA, SharedCorner), Is.LessThan(1e-5f)); - } - - [Test] - public void CanonicalizingTwice_ChangesNothing() - { - using var scene = new Scene(TwoTurnedBoxes()); - var canonical = scene.Canonical(); - - var once = canonical.Canonicalize(SharedCorner + new float3(3e-6f, 0, -1e-6f), 0); - var twice = canonical.Canonicalize(once, 1); - - Assert.That(SameBits(once, twice), Is.True); - } - - // The key is sorted by plane, so the order the brushes come in doesn't matter - [Test] - public void BrushOrder_DoesNotChangeTheResult() - { - var brushes = TwoTurnedBoxes(); - using var forward = new Scene(brushes[0], brushes[1]); - using var backward = new Scene(brushes[1], brushes[0]); - - var approximate = SharedCorner + new float3(1e-6f, 1e-6f, 0); - var fromForward = forward.Canonical().Canonicalize(approximate, 0); - var fromBackward = backward.Canonical().Canonicalize(approximate, 1); - - Assert.That(SameBits(fromForward, fromBackward), Is.True, $"{fromForward} against {fromBackward}"); - } - - // The trim case in miniature: a neighbour whose bottom face sits a little above this brush's, so their - // corners are a sliver apart. Thicker than τ it is a real feature and both corners stay. - [Test] - public void ASliverThickerThanTau_KeepsBothCorners() - { - const float kSliver = 0.002f; - using var scene = new Scene(Box(new float3(0, 0, 0), new float3(1, 1, 1)), - Box(new float3(1, kSliver, 0), new float3(2, 1, 1))); - var canonical = scene.Canonical(); - - var corner = canonical.Canonicalize(new float3(1, 0, 0), 0); - var raised = canonical.Canonicalize(new float3(1, kSliver, 0), 1); - - Assert.That(corner.y, Is.EqualTo(0).Within(1e-6f)); - Assert.That(raised.y, Is.EqualTo(kSliver).Within(1e-6f)); - Assert.That(math.distance(corner, raised), Is.GreaterThan(CanonicalVertices.kSameVertex), - "two different vertices, however close"); - } - - // Thinner than τ both bottom faces pass through the corner, and both brushes pick the same one of them. - [Test] - public void ASliverThinnerThanTau_BecomesOneCornerOnBothBrushes() - { - const float kSliver = 0.0003f; - using var scene = new Scene(Box(new float3(0, 0, 0), new float3(1, 1, 1)), - Box(new float3(1, kSliver, 0), new float3(2, 1, 1))); - var canonical = scene.Canonical(); - - var corner = canonical.Canonicalize(new float3(1, 0, 0), 0); - var raised = canonical.Canonicalize(new float3(1, kSliver, 0), 1); - - Assert.That(SameBits(corner, raised), Is.True, $"{corner} against {raised}"); - } - - [Test] - public void ABrushAddedOnTheSamePlane_DoesNotMoveCornersItDoesNotContain() - { - var left = Box(new float3(0, 0, 0), new float3(1, 1, 1)); - var middle = Box(new float3(1, 0, 0), new float3(2, 1, 1)); - // Added by the edit: it touches the middle box only, and its top is 0.3 mm higher, less than τ - var right = Box(new float3(2, 0, 0), new float3(3, 1.0003f, 1)); - using var before = new Scene(left, middle); - using var after = new Scene(left, middle, right); - - // The corner the left and middle boxes share, as the middle box finds it before and after the edit - var approximate = new float3(1, 1, 0) + new float3(1e-6f, -1e-6f, 0); - var unedited = before.Canonical().Canonicalize(approximate, 1); - var edited = after.Canonical().Canonicalize(approximate, 1); - Assert.That(SameBits(unedited, edited), Is.True, $"{unedited} before the edit, {edited} after it"); - Assert.That(unedited, Is.EqualTo(new float3(1, 1, 0))); - - // Where the new brush does meet the middle box, the two agree - var fromMiddle = after.Canonical().Canonicalize(new float3(2, 1, 0), 1); - var fromRight = after.Canonical().Canonicalize(new float3(2, 1.0003f, 0), 2); - Assert.That(SameBits(fromMiddle, fromRight), Is.True, $"{fromMiddle} from the middle box, {fromRight} from the new one"); - } - - // A plane that only touches the brush along an edge joins its corners' key, but each corner stays where its three faces put it - [Test] - public void APlaneThroughAnEdge_JoinsTheKeyButDoesNotMoveTheCorner() - { - var box = Box(new float3(0, 0, 0), new float3(1, 1, 1)); - // x + y <= 2: it touches the box only along the edge x = 1, y = 1 - var withDiagonal = box.Append(new float4(math.normalize(new float3(1, 1, 0)), -math.sqrt(2f))).ToArray(); - using var scene = new Scene(withDiagonal); - var canonical = scene.Canonical(); - - var key = new PlaneKey(); - canonical.Gather(new float3(1, 1, 0), 0, ref key); - Assert.That(key.Length, Is.EqualTo(4), "x = 1, y = 1, z = 0 and the diagonal"); - - var corner = canonical.Canonicalize(new float3(1, 1, 0) + new float3(2e-6f, -1e-6f, 1e-6f), 0); - Assert.That(corner.x, Is.EqualTo(1f)); - Assert.That(corner.y, Is.EqualTo(1f)); - Assert.That(corner.z, Is.EqualTo(0f)); - } - - // A plane is infinite. A brush far away with a face on the same plane does not contain the corner, so it - // does not take part. - [Test] - public void AFarBrushOnTheSamePlane_DoesNotChangeTheCorner() - { - var near = Box(new float3(0, 0, 0), new float3(1, 1, 1)); - var far = Box(new float3(1, 5, 0), new float3(1.5f, 6, 1)); // its -x face is the plane x = 1 - using var alone = new Scene(near); - using var both = new Scene(near, far); - - var approximate = new float3(1, 0, 0) + new float3(1e-6f, 0, 0); - var key = new PlaneKey(); - both.Canonical().Gather(approximate, 0, ref key); - Assert.That(key.Length, Is.EqualTo(3)); - Assert.That(SameBits(alone.Canonical().Canonicalize(approximate, 0), both.Canonical().Canonicalize(approximate, 0)), Is.True); - } - - // A key holds each plane once, counting a plane and its twin as one, and what it holds doesn't depend on the - // order the planes came in, even when there are more of them than it has room for. - [Test] - public void APlaneKey_DependsOnlyOnItsPlanes() - { - var random = new Unity.Mathematics.Random(12345); - var planes = new List(); - for (int i = 0; i < PlaneKey.kCapacity + 8; i++) - planes.Add(new float4(random.NextFloat3Direction(), random.NextFloat(-2, 2))); - - var forward = new PlaneKey(); - foreach (var plane in planes) - forward.Add(plane); - var backward = new PlaneKey(); - for (int i = planes.Count - 1; i >= 0; i--) - { - backward.Add(-planes[i]); - backward.Add(planes[i]); - } - - Assert.That(forward.Overflowed, Is.True); - Assert.That(forward.Length, Is.EqualTo(PlaneKey.kCapacity)); - Assert.That(backward.Length, Is.EqualTo(PlaneKey.kCapacity)); - for (int i = 0; i < forward.Length; i++) - Assert.That(SameBits(forward[i], backward[i]), Is.True, $"plane {i}: {forward[i]} against {backward[i]}"); - } - - // -0 and +0 are equal but have different bits, and a twin's negation is full of -0. - [Test] - public void APlaneKey_StoresOneKindOfZero() - { - var key = new PlaneKey(); - key.Add(new float4(0, -1, 0, 0)); // turned around, this is (-0, 1, -0, -0) - key.Add(new float4(0, 1, 0, 0)); - Assert.That(key.Length, Is.EqualTo(1)); - Assert.That(SameBits(key[0], new float4(0, 1, 0, 0)), Is.True, $"{key[0]}"); - } - - [Test] - public void Measure_CountsButDoesNotMove() - { - using var scene = new Scene(TwoTurnedBoxes()); - var approximate = SharedCorner + new float3(3e-6f, 0, 0); - - CanonicalVertexStats.Reset(); - var measured = scene.Canonical(CanonicalVertexStage.Measure).Canonicalize(approximate, 0); - - Assert.That(SameBits(measured, approximate), Is.True); - Assert.That(CanonicalVertexStats.Get(CanonicalVertexStats.kComputed), Is.EqualTo(1)); - Assert.That(CanonicalVertexStats.Get(CanonicalVertexStats.kMoved), Is.EqualTo(1)); - } - - [Test] - public void Off_IsInert() - { - var approximate = new float3(1.25f, -3.5f, 7); - Assert.That(SameBits(CanonicalVertices.Disabled.Canonicalize(approximate, 0), approximate), Is.True); - Assert.That(CanonicalVertices.Disabled.Computes, Is.False); - } - - // With canonical positions, the weld merges only the same vertex, however close another one is. - [Test] - public void TheSameVertexWeld_MergesOnlyTheSameVertex() - { - var filter = WeldIncidenceFilter.SameVertexOnly; - var corner = new float3(4, 5, 6); - Assert.That(filter.Allows(corner, corner), Is.True); - Assert.That(filter.Allows(corner, corner + new float3(0.000005f, 0, 0)), Is.True, "0.005 mm: rounding"); - Assert.That(filter.Allows(corner, corner + new float3(0.0001f, 0, 0)), Is.False, "0.1 mm: a different vertex"); - - using var hashed = new HashedVertices(16, Allocator.Temp); - var first = hashed.AddNoResize(corner, in filter); - var second = hashed.AddNoResize(corner + new float3(0, 0.002f, 0), in filter); - Assert.That(second, Is.Not.EqualTo(first), "2 mm apart: two vertices, where the distance weld made one"); - } - } -} diff --git a/Core/Tests/CanonicalVerticesTests.cs.meta b/Core/Tests/CanonicalVerticesTests.cs.meta deleted file mode 100644 index 6c827b43..00000000 --- a/Core/Tests/CanonicalVerticesTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 12089733e49c6334a83123f854947149 \ No newline at end of file diff --git a/Core/Tests/Contents/CanonicalVertexEditTests.cs b/Core/Tests/Contents/CanonicalVertexEditTests.cs deleted file mode 100644 index fd257c8e..00000000 --- a/Core/Tests/Contents/CanonicalVertexEditTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - [Category("Contents")] - public class CanonicalVertexEditTests - { - int savedStage; - - [SetUp] - public void SaveStage() => savedStage = CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage; - - [TearDown] - public void RestoreStage() => CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = savedStage; - - static ContentsSceneNode Box(string name, float3 min, float3 max) - { - return ContentsSceneNode.Brush(ContentsScene.BoxPlanes(min, max), name: name); - } - - static List VerticesOf(ContentsTreeHarness harness, ContentsSceneNode brush) - { - var vertices = new List(); - foreach (var triangle in harness.Triangles) - { - if (harness.NodeOf(triangle.brushID) != brush) - continue; - vertices.Add(triangle.a); - vertices.Add(triangle.b); - vertices.Add(triangle.c); - } - return vertices.Distinct().ToList(); - } - - // Every vertex either brush draws on the plane x = seamX is one the other brush draws too. - static void AssertSeamMatches(ContentsTreeHarness harness, ContentsSceneNode a, ContentsSceneNode b, float seamX, string when) - { - var verticesA = VerticesOf(harness, a); - var verticesB = VerticesOf(harness, b); - var seamA = verticesA.Where(v => math.abs(v.x - seamX) < 0.001f).ToList(); - var seamB = verticesB.Where(v => math.abs(v.x - seamX) < 0.001f).ToList(); - Assert.That(seamA, Is.Not.Empty, $"{when}: {a.name} draws nothing on the seam"); - Assert.That(seamB, Is.Not.Empty, $"{when}: {b.name} draws nothing on the seam"); - foreach (var vertex in seamA) - Assert.That(seamB.Any(v => math.distance(v, vertex) <= CanonicalVertices.kSameVertex), Is.True, - $"{when}: {a.name} has {vertex} on the seam and {b.name} doesn't; {b.name} has {string.Join(" ", seamB)}"); - foreach (var vertex in seamB) - Assert.That(seamA.Any(v => math.distance(v, vertex) <= CanonicalVertices.kSameVertex), Is.True, - $"{when}: {b.name} has {vertex} on the seam and {a.name} doesn't; {a.name} has {string.Join(" ", seamA)}"); - } - - [Test] - public void AddingABrush_KeepsTheSeamWithABrushThatWasNotRebuilt([Values(0, 2, 3, 4)] int stageValue) - { - var stage = (CanonicalVertexStage)stageValue; - CompactHierarchyManager.TreeUpdate.kCanonicalVertexStage = stageValue; - - var left = Box("left", new float3(0, 0, 0), new float3(1, 1, 1)); - var middle = Box("middle", new float3(1, 0, 0), new float3(2, 1, 1)); - using var harness = ContentsTreeHarness.Build(new ContentsScene().Add(left).Add(middle)); - Assert.That(harness.Update(), Is.True, "the first update did not run"); - AssertSeamMatches(harness, left, middle, 1, "before the edit"); - var leftBefore = VerticesOf(harness, left); - - var right = Box("right", new float3(2, 0, 0), new float3(3, 1.0003f, 1)); - harness.Insert(null, 2, right); - Assert.That(harness.Update(), Is.True, "the update after the edit did not run"); - Assert.That(harness.Delivered, Is.True, harness.LastUpdateReport); - - // Only a brush that wasn't rebuilt makes this a test of the rule - CollectionAssert.AreEquivalent(leftBefore, VerticesOf(harness, left), - "the left box changed, so it was rebuilt: " + harness.LastUpdateReport); - AssertSeamMatches(harness, left, middle, 1, "after the edit"); - - AssertStepIsClosed(harness, middle, right, 2); - } - - static void AssertStepIsClosed(ContentsTreeHarness harness, ContentsSceneNode lower, ContentsSceneNode taller, float seamX) - { - Assert.That(ExactCSGCapture.Brushes.TryGetValue(harness.NodeIDOf(taller), out var capture) && capture.planes != null, - Is.True, $"exact CSG: no planes were captured for {taller.name}"); - var tops = capture.planes.Where(p => p.a == 0 && p.c == 0 && p.b > 0).ToList(); - Assert.That(tops.Count, Is.EqualTo(1), $"exact CSG: {taller.name} does not have one plane facing straight up"); - // a*x + b*y + c*z + w = 0 with a = c = 0: every point of the top has y = -w / b - var stepTop = ExactPredicates.RoundToFloat(Int128.FromLong(-tops[0].w), Int128.FromLong(tops[0].b)); - - var seamLower = VerticesOf(harness, lower).Where(v => math.abs(v.x - seamX) < 0.001f).ToList(); - var seamTaller = VerticesOf(harness, taller).Where(v => math.abs(v.x - seamX) < 0.001f).ToList(); - Assert.That(seamLower, Is.Not.Empty, $"exact CSG: {lower.name} draws nothing on the seam"); - foreach (var vertex in seamLower) - Assert.That(seamTaller.Contains(vertex), Is.True, - $"exact CSG: {lower.name} has {vertex} on the seam and {taller.name} doesn't; {taller.name} has {string.Join(" ", seamTaller)}"); - var stepCorners = seamTaller.Where(v => !seamLower.Contains(v)).ToList(); - foreach (var vertex in stepCorners) - Assert.That(vertex.y, Is.EqualTo(stepTop), - $"exact CSG: {taller.name} has {vertex} on the seam, which is neither a corner of {lower.name} nor on the top of the step " + - $"(y = {stepTop:R}); {lower.name} has {string.Join(" ", seamLower)}"); - // a step lower than OutputModelWeld.kCollapseLength is merged away; a higher one is drawn as one strip - var lowerTop = seamLower.Max(v => v.y); - if (stepTop - lowerTop < OutputModelWeld.kCollapseLength) - { - Assert.That(stepCorners, Is.Empty, $"exact CSG: the step above {lower.name}, {stepTop - lowerTop} high, is not merged away; {taller.name} has {string.Join(" ", seamTaller)}"); - return; - } - Assert.That(stepCorners.Count, Is.EqualTo(2), $"exact CSG: the step above {lower.name} is not drawn as one strip; {taller.name} has {string.Join(" ", seamTaller)}"); - Assert.That(stepCorners[0].y, Is.EqualTo(stepCorners[1].y), "exact CSG: the top of the step is not one plane"); - } - } -} diff --git a/Core/Tests/Contents/CanonicalVertexEditTests.cs.meta b/Core/Tests/Contents/CanonicalVertexEditTests.cs.meta deleted file mode 100644 index d0d40c67..00000000 --- a/Core/Tests/Contents/CanonicalVertexEditTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: bbaaa31e5ab64a8d9c0d2253b59b2e75 diff --git a/Core/Tests/Contents/SharedCornerExpectationTests.cs b/Core/Tests/Contents/SharedCornerExpectationTests.cs deleted file mode 100644 index 1269a461..00000000 --- a/Core/Tests/Contents/SharedCornerExpectationTests.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Linq; -using NUnit.Framework; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - [Category("Contents")] - public class SharedCornerExpectationTests - { - const int kSectors = 720; - - static bool?[] Pattern(System.Func sector) => Enumerable.Range(0, kSectors).Select(sector).ToArray(); - - #region Sector patterns - [Test] - public void ExpectFace_NothingDrawn_IsNoFace() - { - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => false)), Is.EqualTo(SharedCornerSurvey.Expectation.NoFace)); - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 360 ? (bool?)null : false)), Is.EqualTo(SharedCornerSurvey.Expectation.NoFace)); - } - - [Test] - public void ExpectFace_DrawnAllAround_IsDrawnThrough() - { - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => true)), Is.EqualTo(SharedCornerSurvey.Expectation.DrawnThrough)); - // The edge brush's face only covers half the circle - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 360 ? (bool?)null : true)), Is.EqualTo(SharedCornerSurvey.Expectation.DrawnThrough)); - } - - [Test] - public void ExpectFace_CutByAStraightLine_IsAStraightBoundary() - { - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 360)), Is.EqualTo(SharedCornerSurvey.Expectation.StraightBoundary)); - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k >= 100 && k < 460)), Is.EqualTo(SharedCornerSurvey.Expectation.StraightBoundary)); - // One sector either way is sampling, not a bend - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 361)), Is.EqualTo(SharedCornerSurvey.Expectation.StraightBoundary)); - } - - [Test] - public void ExpectFace_BentOutline_IsABoundary() - { - // A quarter cut out, and a 9 degree bend - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k >= 180)), Is.EqualTo(SharedCornerSurvey.Expectation.Boundary)); - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 378)), Is.EqualTo(SharedCornerSurvey.Expectation.Boundary)); - // Two separate wedges - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => (k / 180) % 2 == 0)), Is.EqualTo(SharedCornerSurvey.Expectation.Boundary)); - } - - [Test] - public void ExpectFace_OutlineEndingOnTheEdge_IsABoundary() - { - // Half the circle is off the face; the drawn part ends on the edge at the corner, even when the - // line it ends along is straight - Assert.That(SharedCornerSurvey.ExpectFace(Pattern(k => k < 360 ? (bool?)null : k < 540)), Is.EqualTo(SharedCornerSurvey.Expectation.Boundary)); - } - #endregion - - #region Judging a side - [Test] - public void JudgeSide_OnlyFlagsAFaceThatShouldBeGone_OrACornerThatShouldBeThere() - { - Assert.That(SharedCornerSurvey.JudgeSide(SharedCornerSurvey.Expectation.NoFace, drewNearCorner: false), Is.Null); - Assert.That(SharedCornerSurvey.JudgeSide(SharedCornerSurvey.Expectation.NoFace, drewNearCorner: true), Is.Not.Null); - Assert.That(SharedCornerSurvey.JudgeSide(SharedCornerSurvey.Expectation.Boundary, drewNearCorner: true), Is.Null); - Assert.That(SharedCornerSurvey.JudgeSide(SharedCornerSurvey.Expectation.Boundary, drewNearCorner: false), Is.Not.Null); - // Either is fine where a vertex is allowed but not needed - foreach (var optional in new[] { SharedCornerSurvey.Expectation.DrawnThrough, SharedCornerSurvey.Expectation.StraightBoundary }) - { - Assert.That(SharedCornerSurvey.JudgeSide(optional, drewNearCorner: false), Is.Null, optional.ToString()); - Assert.That(SharedCornerSurvey.JudgeSide(optional, drewNearCorner: true), Is.Null, optional.ToString()); - } - } - #endregion - - #region From the oracle - // Two unit-offset cubes: at every corner where their surfaces cross, both outlines bend. - [Test] - public void Expect_OverlappingBoxes_HaveABoundaryOnBothSides() - { - var scene = new ContentsScene() - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(0), new float3(2)), name: "a")) - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(1), new float3(3)), name: "b")); - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - var corners = SharedCornerSurvey.SharedCorners(brushes.Select(oracle.TreePlanesOf).ToList()); - Assert.That(corners.Count, Is.EqualTo(6)); - foreach (var corner in corners) - { - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[corner.brushA], corner.planesA, corner.position), - Is.EqualTo(SharedCornerSurvey.Expectation.Boundary), "a at " + corner.position); - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[corner.brushB], corner.planesB, corner.position), - Is.EqualTo(SharedCornerSurvey.Expectation.Boundary), "b at " + corner.position); - } - } - - // The same crossing buried deep inside a third solid brush: nothing may be drawn there. - [Test] - public void Expect_CrossingInsideASolid_HasNoFaceOnEitherSide() - { - var scene = new ContentsScene() - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(0), new float3(2)), name: "a")) - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(1), new float3(3)), name: "b")) - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(1.5f, 0.5f, 0.5f), new float3(2.5f, 1.5f, 1.5f)), name: "cover")); - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - var corners = SharedCornerSurvey.SharedCorners(brushes.Select(oracle.TreePlanesOf).ToList()); - 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.True, "the cover's faces are half a unit away"); - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[0], corner.planesA, corner.position), Is.EqualTo(SharedCornerSurvey.Expectation.NoFace)); - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[1], corner.planesB, corner.position), Is.EqualTo(SharedCornerSurvey.Expectation.NoFace)); - } - - // A carve before the brush it crosses carves nothing of it: the later brush's face is drawn straight through - [Test] - public void Expect_CarveBeforeTheBrushItCrosses_LeavesTheLaterFaceDrawnThrough() - { - var scene = new ContentsScene() - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(-5), new float3(-4)), name: "base")) - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(0), new float3(2)), operation: CSGOperationType.Subtractive, name: "carve")) - .Add(ContentsSceneNode.Brush(ContentsScene.BoxPlanes(new float3(1), new float3(3)), name: "fill")); - var oracle = new ContentsOracle(scene); - var brushes = oracle.Brushes; - var corners = SharedCornerSurvey.SharedCorners(brushes.Select(oracle.TreePlanesOf).ToList()); - // (1, 1, 2): the carve's top face against the fill's -x and -y faces, where the fill's edge - // crosses the carve's face - var corner = corners.First(c => c.brushA == 1 && c.brushB == 2 && math.distance(c.position, new float3(1, 1, 2)) < 0.00001f); - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[1], corner.planesA, corner.position), Is.EqualTo(SharedCornerSurvey.Expectation.NoFace), "carve"); - Assert.That(SharedCornerSurvey.Expect(oracle, brushes[2], corner.planesB, corner.position), Is.EqualTo(SharedCornerSurvey.Expectation.DrawnThrough), "fill"); - } - #endregion - } -} diff --git a/Core/Tests/Contents/SharedCornerExpectationTests.cs.meta b/Core/Tests/Contents/SharedCornerExpectationTests.cs.meta deleted file mode 100644 index 6ba37a33..00000000 --- a/Core/Tests/Contents/SharedCornerExpectationTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2dff24e8efc358640864a99d02b12344 \ No newline at end of file diff --git a/Core/Tests/Contents/SharedCornerSurvey.cs b/Core/Tests/Contents/SharedCornerSurvey.cs deleted file mode 100644 index a3b2a574..00000000 --- a/Core/Tests/Contents/SharedCornerSurvey.cs +++ /dev/null @@ -1,456 +0,0 @@ -using System.Collections.Generic; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - public static class SharedCornerSurvey - { - // Output vertices are collected within the weld distance of a corner: that is as far as any - // decision about it can move it. - public const float kWeldRadius = CSGConstants.kVertexEqualEpsilon; - // Two decisions closer than this (0.05 mm) count as the same: far above float noise at these sizes - public const float kSamePosition = 0.00005f; - // A corner lies on a brush's boundary when it is within this of the brush's planes. - const double kOnBoundary = 0.0001; - // A plane passes through a corner when it is within this of it; any other plane closer than - // the weld radius means the weld has something to decide there. - const double kThroughCorner = 0.00001; - // Three planes that are this close to dependent don't define a usable point. - const double kMinDeterminant = 0.000001; - // Corners closer than this are one corner. - const float kSameCorner = 0.00001f; - // Output vertices closer than this are one vertex. Well below kSamePosition, so a pair of - // vertices a weld left apart is never folded into one here. - const float kSameVertex = 0.000001f; - - public struct Corner - { - public float3 position; - public int brushA, brushB; - // The planes of each brush that pass through the corner: its faces there. - public int[] planesA, planesB; - // No plane of any brush near this corner comes within the weld radius, other than the ones - // passing through it: the weld has nothing to decide here, so both sides must agree. - public bool isolated; - } - - // In rising order of how bad it is; a corner takes the worst verdict of its face pairs. - public enum Verdict - { - // Neither brush drew anything near the corner on its faces through it: something else covers it. - Hidden, - // Both put their nearest vertex at the same place. - Same, - // Each emitted one vertex, at measurably different places. - Apart, - // One side emitted one vertex, the other two, and their nearest ones differ. - OneVsTwo, - // Any other disagreement about where the corner is. - Other, - // One side drew faces through the corner and the other drew nothing near it: a hole. - Missing - } - - public struct Result - { - public Verdict verdict; - // Between the two sides' nearest vertices, the largest over the face pairs that disagree; - // NaN when there is nothing to measure. - public float distance; - public int countA, countB; - // Whether each side drew any vertex near the corner on its faces through it - public bool drewA, drewB; - public bool isDisagreement => verdict >= Verdict.Apart; - // The two sides put the corner in different places. Unlike Missing, which can come from a - // face lost anywhere along its outline, this is about the corner itself. - public bool isPositionDisagreement => verdict == Verdict.Apart || verdict == Verdict.OneVsTwo || verdict == Verdict.Other; - } - - public enum Expectation - { - // None of the side's faces through the corner is drawn around it: the side must draw nothing - // near the corner. - NoFace, - // A face is drawn all the way around the corner: a vertex there is allowed, not needed. - DrawnThrough, - // A face the corner lies inside of is cut by one straight line through it: a vertex there is - // allowed, not needed. - StraightBoundary, - // A face's drawn region bends or ends at the corner: the side must have a vertex there. - Boundary - } - - // Well inside the weld radius, so the circle only meets planes through the corner, and far enough - // out that float error at scene coordinates (a few micrometres) can't flip a sample. - public const float kExpectationRadius = 0.008f; - // The probes on either side of a face. Much smaller than the radius, so a probe beside a sharp edge - // doesn't step out through the neighbouring face. - const float kExpectationOffset = 0.00004f; - const int kExpectationSectors = 720; - - public static Expectation Expect(ContentsOracle oracle, ContentsSceneNode brush, int[] facePlanes, float3 corner) - { - var planes = oracle.TreePlanesOf(brush); - var result = Expectation.NoFace; - foreach (var plane in facePlanes) - { - var expectation = ExpectFace(SampleAround(oracle, brush, planes, plane, corner)); - if (expectation > result) - result = expectation; - } - return result; - } - - // Per direction around the corner on one face: true drawn, false not drawn, null off the face. - static bool?[] SampleAround(ContentsOracle oracle, ContentsSceneNode brush, float4[] planes, int plane, float3 corner) - { - var normal = planes[plane].xyz; - var u = math.normalize(math.cross(normal, math.abs(normal.x) < 0.9f ? new float3(1, 0, 0) : new float3(0, 1, 0))); - var v = math.cross(normal, u); - var sectors = new bool?[kExpectationSectors]; - for (int k = 0; k < sectors.Length; k++) - { - // Off the sector boundaries, so a plane through the corner at a round angle isn't sampled - var angle = (k + 0.37f) * 2 * math.PI / sectors.Length; - var point = corner + kExpectationRadius * ((math.cos(angle) * u) + (math.sin(angle) * v)); - point -= normal * (math.dot(normal, point) + planes[plane].w); - var onFace = true; - for (int q = 0; q < planes.Length && onFace; q++) - onFace = q == plane || math.dot(planes[q].xyz, point) + planes[q].w <= 0; - if (onFace) - sectors[k] = oracle.JudgeFace(brush, plane, point, kExpectationOffset) != ContentsFaceVerdict.None; - } - return sectors; - } - - // Classifies one face from its samples around the corner. - public static Expectation ExpectFace(bool?[] sectors) - { - var anyDrawn = false; - var anyUndrawn = false; - var offFace = false; - foreach (var sector in sectors) - { - if (sector == null) offFace = true; - else if (sector.Value) anyDrawn = true; - else anyUndrawn = true; - } - if (!anyDrawn) return Expectation.NoFace; - if (!anyUndrawn) return Expectation.DrawnThrough; - - // Where drawn turns into undrawn or back. On a face the corner only bounds (the edge brush's), any - // change means an outline ends at the edge there. - var transitions = new List(); - for (int k = 0; k < sectors.Length; k++) - { - var current = sectors[k]; - var next = sectors[(k + 1) % sectors.Length]; - if (current != null && next != null && current != next) - transitions.Add(k); - } - if (!offFace && transitions.Count == 2 && - math.abs(math.abs(transitions[1] - transitions[0]) - (sectors.Length / 2)) <= 1) - return Expectation.StraightBoundary; - return Expectation.Boundary; - } - - // Whether what one side drew near an isolated corner agrees with the oracle: null when it does, - // otherwise what is wrong. - public static string JudgeSide(Expectation expectation, bool drewNearCorner) - { - if (expectation == Expectation.NoFace && drewNearCorner) - return "draws where the oracle draws nothing"; - if (expectation == Expectation.Boundary && !drewNearCorner) - return "has no vertex where the oracle's outline has a corner"; - return null; - } - - public static List SharedCorners(IReadOnlyList brushPlanes) - { - var corners = new List(); - for (int a = 0; a < brushPlanes.Count; a++) - { - for (int b = a + 1; b < brushPlanes.Count; b++) - { - if (!BoundsNear(brushPlanes[a], brushPlanes[b])) - continue; - - var found = new List(); - AddCrossings(brushPlanes, a, b, edgeBrush: a, faceBrush: b, found, corners); - AddCrossings(brushPlanes, a, b, edgeBrush: b, faceBrush: a, found, corners); - } - } - return corners; - } - - static void AddCrossings(IReadOnlyList brushPlanes, int a, int b, int edgeBrush, int faceBrush, - List found, List corners) - { - var edgePlanes = brushPlanes[edgeBrush]; - var facePlanes = brushPlanes[faceBrush]; - for (int i = 0; i < edgePlanes.Length; i++) - { - for (int j = i + 1; j < edgePlanes.Length; j++) - { - for (int k = 0; k < facePlanes.Length; k++) - { - if (!TryIntersect(edgePlanes[i], edgePlanes[j], facePlanes[k], out var point)) - continue; - if (!OnBoundary(edgePlanes, point) || !OnBoundary(facePlanes, point)) - continue; - // Strictly inside the edge, and strictly inside the face - var throughEdgeBrush = PlanesThrough(edgePlanes, point); - var throughFaceBrush = PlanesThrough(facePlanes, point); - if (throughEdgeBrush.Length != 2 || throughFaceBrush.Length != 1) - continue; - var position = (float3)point; - if (Contains(found, position, kSameCorner)) - continue; - found.Add(position); - corners.Add(new Corner - { - position = position, - brushA = a, - brushB = b, - planesA = edgeBrush == a ? throughEdgeBrush : throughFaceBrush, - planesB = edgeBrush == a ? throughFaceBrush : throughEdgeBrush, - isolated = IsIsolated(brushPlanes, point) - }); - } - } - } - } - - // Compares one corner across its two brushes, face against face. verticesByPlane[p] holds the - // distinct output vertices a brush drew on its plane p. - public static Result ClassifyCorner(Corner corner, IReadOnlyList> verticesByPlaneA, - IReadOnlyList> verticesByPlaneB) - { - var facesA = NearFaces(corner.position, corner.planesA, verticesByPlaneA); - var facesB = NearFaces(corner.position, corner.planesB, verticesByPlaneB); - var result = new Result { distance = float.NaN, countA = Total(facesA), countB = Total(facesB), - drewA = facesA.Count > 0, drewB = facesB.Count > 0 }; - - if (facesA.Count == 0 && facesB.Count == 0) { result.verdict = Verdict.Hidden; return result; } - if (facesA.Count == 0 || facesB.Count == 0) { result.verdict = Verdict.Missing; return result; } - - result.verdict = Verdict.Same; - foreach (var faceA in facesA) - { - foreach (var faceB in facesB) - { - var pair = Classify(corner.position, faceA, faceB); - if (pair.isDisagreement) - result.distance = float.IsNaN(result.distance) ? pair.distance : math.max(result.distance, pair.distance); - if (pair.verdict > result.verdict) - { - result.verdict = pair.verdict; - result.countA = pair.countA; - result.countB = pair.countB; - } - } - } - return result; - } - - // Compares the vertices two faces emitted near one corner. - public static Result Classify(float3 corner, IReadOnlyList verticesA, IReadOnlyList verticesB) - { - var nearA = Near(corner, verticesA); - var nearB = Near(corner, verticesB); - var result = new Result { countA = nearA.Count, countB = nearB.Count, distance = float.NaN }; - - if (nearA.Count == 0 && nearB.Count == 0) { result.verdict = Verdict.Hidden; return result; } - if (nearA.Count == 0 || nearB.Count == 0) { result.verdict = Verdict.Missing; return result; } - - result.distance = math.distance(Nearest(corner, nearA), Nearest(corner, nearB)); - if (result.distance <= kSamePosition) - result.verdict = Verdict.Same; - else if (nearA.Count == 1 && nearB.Count == 1) - result.verdict = Verdict.Apart; - else if (nearA.Count + nearB.Count == 3 && math.min(nearA.Count, nearB.Count) == 1) - result.verdict = Verdict.OneVsTwo; - else - result.verdict = Verdict.Other; - return result; - } - - public static int PlaneOf(float3 normal, float3 center, float4[] planes) - { - var best = -1; - var bestDistance = float.MaxValue; - for (int p = 0; p < planes.Length; p++) - { - if (math.abs(math.abs(math.dot(normal, planes[p].xyz)) - 1) > 0.001f) - continue; - var distance = math.abs(math.dot(planes[p].xyz, center) + planes[p].w); - if (distance < bestDistance) - { - best = p; - bestDistance = distance; - } - } - return bestDistance <= 2 * kWeldRadius ? best : -1; - } - - // The distinct positions in a list of vertices; the render buffer repeats a position for every - // surface that uses it. - public static List Distinct(IEnumerable positions) - { - var result = new List(); - foreach (var position in positions) - { - if (!Contains(result, position, kSameVertex)) - result.Add(position); - } - return result; - } - - static List> NearFaces(float3 corner, int[] planes, IReadOnlyList> verticesByPlane) - { - var faces = new List>(); - foreach (var plane in planes) - { - var near = Near(corner, verticesByPlane[plane]); - if (near.Count > 0) - faces.Add(near); - } - return faces; - } - - static int Total(List> faces) - { - var all = new List(); - foreach (var face in faces) - all.AddRange(face); - return Distinct(all).Count; - } - - static List Near(float3 corner, IReadOnlyList vertices) - { - var result = new List(); - for (int i = 0; i < vertices.Count; i++) - { - if (math.distance(corner, vertices[i]) <= kWeldRadius) - result.Add(vertices[i]); - } - return result; - } - - static float3 Nearest(float3 corner, List vertices) - { - var best = vertices[0]; - for (int i = 1; i < vertices.Count; i++) - { - if (math.distancesq(corner, vertices[i]) < math.distancesq(corner, best)) - best = vertices[i]; - } - return best; - } - - static bool Contains(List positions, float3 position, float distance) - { - for (int i = 0; i < positions.Count; i++) - { - if (math.distance(positions[i], position) <= distance) - return true; - } - return false; - } - - static bool TryIntersect(float4 p0, float4 p1, float4 p2, out double3 point) - { - double3 n0 = p0.xyz, n1 = p1.xyz, n2 = p2.xyz; - var determinant = math.dot(n0, math.cross(n1, n2)); - if (math.abs(determinant) < kMinDeterminant) - { - point = default; - return false; - } - // Cramer's rule for dot(n, x) = -w on all three planes - point = (-(double)p0.w * math.cross(n1, n2) - -(double)p1.w * math.cross(n2, n0) - -(double)p2.w * math.cross(n0, n1)) / determinant; - return true; - } - - static double Distance(float4 plane, double3 point) => math.dot((double3)plane.xyz, point) + plane.w; - - // Inside or on every plane, and on at least one - static bool OnBoundary(float4[] planes, double3 point) - { - var onOne = false; - for (int i = 0; i < planes.Length; i++) - { - var distance = Distance(planes[i], point); - if (distance > kOnBoundary) - return false; - if (distance >= -kOnBoundary) - onOne = true; - } - return onOne; - } - - static int[] PlanesThrough(float4[] planes, double3 point) - { - var result = new List(); - for (int i = 0; i < planes.Length; i++) - { - if (math.abs(Distance(planes[i], point)) <= kThroughCorner) - result.Add(i); - } - return result.ToArray(); - } - - static bool IsIsolated(IReadOnlyList brushPlanes, double3 point) - { - foreach (var planes in brushPlanes) - { - // A brush whose planes all keep the corner further out than the weld radius can't - // reach it: the largest plane distance is a lower bound on the distance to the brush. - var furthest = double.MinValue; - for (int i = 0; i < planes.Length; i++) - furthest = math.max(furthest, Distance(planes[i], point)); - if (furthest > kWeldRadius) - continue; - - for (int i = 0; i < planes.Length; i++) - { - var distance = math.abs(Distance(planes[i], point)); - if (distance > kThroughCorner && distance <= kWeldRadius) - return false; - } - } - return true; - } - - static bool BoundsNear(float4[] planesA, float4[] planesB) - { - var boundsA = BoundsOf(planesA); - var boundsB = BoundsOf(planesB); - return math.all(boundsA.min - kWeldRadius <= boundsB.max) && - math.all(boundsB.min - kWeldRadius <= boundsA.max); - } - - // The bounds of a brush's vertices, found the same way as the corners - static (float3 min, float3 max) BoundsOf(float4[] planes) - { - var min = new float3(float.MaxValue); - var max = new float3(float.MinValue); - for (int i = 0; i < planes.Length; i++) - { - for (int j = i + 1; j < planes.Length; j++) - { - for (int k = j + 1; k < planes.Length; k++) - { - if (!TryIntersect(planes[i], planes[j], planes[k], out var point) || !OnBoundary(planes, point)) - continue; - min = math.min(min, (float3)point); - max = math.max(max, (float3)point); - } - } - } - return (min, max); - } - } -} diff --git a/Core/Tests/Contents/SharedCornerSurvey.cs.meta b/Core/Tests/Contents/SharedCornerSurvey.cs.meta deleted file mode 100644 index 2d821a2f..00000000 --- a/Core/Tests/Contents/SharedCornerSurvey.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7fde3e7955cb2d74893e567c5b353071 \ No newline at end of file diff --git a/Core/Tests/WeldIncidenceTests.cs b/Core/Tests/WeldIncidenceTests.cs deleted file mode 100644 index 26e77d81..00000000 --- a/Core/Tests/WeldIncidenceTests.cs +++ /dev/null @@ -1,208 +0,0 @@ -using NUnit.Framework; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; - -namespace Chisel.Core.Tests -{ - [TestFixture] - public class WeldIncidenceTests - { - 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); - } - - // Planes given as n·x = d; Chisel stores n·x + w = 0 with outward normals, so w = -d - static float4 Side(double nx, double ny, double nz, double d) - { - return new float4((float)nx, (float)ny, (float)nz, (float)-d); - } - - // The trim: a 6-sided prism along z, its face at x = 59.70375 19.06 mm tall - static float4[] TrimSides() - { - return new[] - { - Side( 0, 0, 1, 72.20875000000001), - Side( 0, 0, -1, -28.89875), - Side(-1, 0, 0, -59.70375000000001), - Side(-0.11684124756739721, 0.9931506043228763, 0, -11.63311624959858), - Side(-0.37863284572050415, -0.9255469562056768, 0, -18.247920766075882), - Side( 0.923076923076923, 0.3846153846153846, 0, 53.621346153846154), - }; - } - - // The wall whose sloped underside (side 2 here) cuts the trim's face - static float4[] WallSides() - { - return new[] - { - Side( 0, 0, -1, -28.28875), - Side( 0, 0, 1, 72.81875), - Side( 0.1501760908452158, -0.9886592647310041, 0, 13.613243628319662), - Side( 0, 1, 0, -2.44), - Side(-1, 0, 0, -58.521875), - Side( 0.9917428264343938, 0.12824260686651645, 0, 58.93566400823595), - }; - } - - const float kTrimFaceX = 59.70375f; - const float kTrimBottomY = -4.7084375f; // the face's bottom edge - const float kMidZ = 50f; // mid-way along the 43 m trim, away from its end caps - - // where the wall's underside crosses the trim's face - static float3 CutPoint() - { - var underside = WallSides()[2]; - float y = -(underside.x * kTrimFaceX + underside.w) / underside.y; - return new float3(kTrimFaceX, y, kMidZ); - } - - static float3 CornerPoint() { return new float3(kTrimFaceX, kTrimBottomY, kMidZ); } - - [Test] - public void TheFixtureMatchesTheMap() - { - var cut = CutPoint(); - var corner = CornerPoint(); - Assert.That(cut.y, Is.EqualTo(-4.70047f).Within(1e-4f), "the cut sits where the wall's underside crosses the face"); - Assert.That(math.distance(cut, corner), Is.EqualTo(0.00796f).Within(1e-4f), "7.96 mm of face below the cut"); - Assert.That(math.distance(cut, corner), Is.LessThan(CSGConstants.kVertexEqualEpsilon), - "the whole point of the case: the cut is inside the weld radius of the corner"); - } - - [Test] - public void TheCutAndTheCorner_AreNotMerged() - { - var trim = Planes(TrimSides()); - var wall = Planes(WallSides()); - var filter = WeldIncidenceFilter.Create(ref trim.Value.treeSpacePlanes, 6, ref wall.Value.treeSpacePlanes, 6); - - Assert.That(filter.Allows(CutPoint(), CornerPoint()), Is.False, - "the corner is 7.9 mm off the wall underside that the cut lies on"); - Assert.That(filter.Allows(CornerPoint(), CutPoint()), Is.False, "and the test is symmetric"); - - wall.Dispose(); - trim.Dispose(); - } - - [Test] - public void ThePlainWeldMergesThem_WhichIsTheDefect() - { - using var plain = new HashedVertices(64, Allocator.Temp); - var corner = plain.AddNoResize(CornerPoint()); - Assert.That(plain.AddNoResize(CutPoint()), Is.EqualTo(corner), - "distance alone merges the cut into the corner, which is how the cut is lost"); - } - - [Test] - public void TheGatedWeld_KeepsThemApart() - { - var trim = Planes(TrimSides()); - var wall = Planes(WallSides()); - var filter = WeldIncidenceFilter.Create(ref trim.Value.treeSpacePlanes, 6, ref wall.Value.treeSpacePlanes, 6); - - using var hashed = new HashedVertices(64, Allocator.Temp); - var corner = hashed.AddNoResize(CornerPoint(), in filter); - var cut = hashed.AddNoResize(CutPoint(), in filter); - Assert.That(cut, Is.Not.EqualTo(corner), "the cut must become its own vertex"); - Assert.That(hashed.Length, Is.EqualTo(2)); - - wall.Dispose(); - trim.Dispose(); - } - - // The gate must not break what the weld is for: one point computed twice, a fraction of a millimetre apart. - [Test] - public void AVertexComputedTwice_IsStillMerged() - { - var trim = Planes(TrimSides()); - var wall = Planes(WallSides()); - var filter = WeldIncidenceFilter.Create(ref trim.Value.treeSpacePlanes, 6, ref wall.Value.treeSpacePlanes, 6); - - var cut = CutPoint(); - var again = cut + new float3(0.0002f, -0.0001f, 0.0003f); // 0.37 mm of float and conditioning noise - Assert.That(filter.Allows(cut, again), Is.True); - - using var hashed = new HashedVertices(64, Allocator.Temp); - Assert.That(hashed.AddNoResize(again, in filter), Is.EqualTo(hashed.AddNoResize(cut, in filter))); - - wall.Dispose(); - trim.Dispose(); - } - - static float4[] UnitBoxSides() - { - return new[] - { - new float4(-1, 0, 0, 0), new float4(1, 0, 0, -1), - new float4( 0, -1, 0, 0), new float4(0, 1, 0, -1), - new float4( 0, 0, -1, 0), new float4(0, 0, 1, -1), - }; - } - - [Test] - public void AMergeOffAFaceItLiesOn_IsRefused() - { - var box = Planes(UnitBoxSides()); - var filter = WeldIncidenceFilter.Create(ref box.Value.treeSpacePlanes, 6); - - var onTop = new float3(0.5f, 1f, 0.5f); - Assert.That(filter.Allows(onTop, onTop - new float3(0, 0.005f, 0)), Is.False, "5 mm below the face it lies on"); - Assert.That(filter.Allows(onTop, onTop - new float3(0, 0.0005f, 0)), Is.True, "0.5 mm is within the plane tolerance"); - - box.Dispose(); - } - - // A plane is infinite, so a point far outside a brush can sit on the plane of one of its faces by accident. - // Such a point does not belong to the brush and must not be gated by it. - [Test] - public void PointsOutsideTheBrush_AreNotGatedByIt() - { - var box = Planes(UnitBoxSides()); - var filter = WeldIncidenceFilter.Create(ref box.Value.treeSpacePlanes, 6); - - var outside = new float3(0.5f, 5f, 0.5f); - Assert.That(filter.Allows(outside, outside + new float3(0, 0.005f, 0)), Is.True); - - box.Dispose(); - } - - [Test] - public void EdgePlanesBeyondTheFaceCount_AreIgnored() - { - var withEdgePlane = new[] - { - UnitBoxSides()[0], UnitBoxSides()[1], UnitBoxSides()[2], - UnitBoxSides()[3], UnitBoxSides()[4], UnitBoxSides()[5], - new float4(math.normalize(new float3(1, 1, 0)), -0.25f), // slices through the middle - }; - var box = Planes(withEdgePlane); - var onTop = new float3(0.5f, 1f, 0.5f); - var below = onTop - new float3(0, 0.005f, 0); - - var faces = WeldIncidenceFilter.Create(ref box.Value.treeSpacePlanes, 6); - Assert.That(faces.Allows(onTop, below), Is.False, "with only the face planes the top face still gates it"); - - var everything = WeldIncidenceFilter.Create(ref box.Value.treeSpacePlanes, 7); - Assert.That(everything.Allows(onTop, below), Is.True, - "the fixture must actually exercise the case: counting the edge plane puts the point outside"); - - box.Dispose(); - } - - [Test] - public void TheDisabledFilter_AllowsEverything() - { - var filter = WeldIncidenceFilter.Disabled; - Assert.That(filter.IsEnabled, Is.False); - Assert.That(filter.Allows(CutPoint(), CornerPoint()), Is.True); - } - } -} diff --git a/Core/Tests/WeldIncidenceTests.cs.meta b/Core/Tests/WeldIncidenceTests.cs.meta deleted file mode 100644 index 31095748..00000000 --- a/Core/Tests/WeldIncidenceTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 0b097c2c69a6bd6419c94cf0191366f8 \ No newline at end of file diff --git a/Core/assembly.cs b/Core/assembly.cs index 159cfba6..3dbc3787 100644 --- a/Core/assembly.cs +++ b/Core/assembly.cs @@ -49,7 +49,6 @@ [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] -[assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))] [assembly: Unity.Jobs.RegisterGenericJobType(typeof(Chisel.Core.DisposeListChildrenBlobAssetReferenceJob))]