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/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/MeshQueryRoutingTests.cs b/Core/Tests/MeshQueryRoutingTests.cs index 1b6a7c7f..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() { 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))]