From 605ccbcf8db4c3e33e6818375d9176621188c903 Mon Sep 17 00:00:00 2001 From: CortexCore <2630229280@qq.com> Date: Mon, 11 Mar 2024 00:28:51 +0800 Subject: [PATCH] 1 --- .../I18N/{I18N.asmdef => Plugins.I18N.asmdef} | 0 .../Unity/Scripts/NativeQuadTree/AABB2D.cs | 59 +++++ .../BITKit.NativeQuadTree.Runtime.asmdef | 18 ++ .../BITKit.NativeQuadTree.Editor.asmdef | 22 ++ .../NativeQuadTree/Editor/QuadTreeDrawer.cs | 61 +++++ .../NativeQuadTree/Editor/QuadTreeTests.cs | 134 ++++++++++ .../Scripts/NativeQuadTree/LookupTables.cs | 70 ++++++ .../Scripts/NativeQuadTree/NativeQuadTree.cs | 238 ++++++++++++++++++ .../NativeQuadTree/NativeQuadTreeDrawing.cs | 77 ++++++ .../NativeQuadTreeRangeQuery.cs | 110 ++++++++ .../Scripts/NativeQuadTree/QuadTreeJobs.cs | 54 ++++ .../Scenes/Editor/MaterialPaletteWindow.cs | 61 +++++ .../WorldChunk/BITKit.WorkdChunk.asmdef | 4 +- .../Scripts/WorldChunk/WorldChunkManager.cs | 10 + ProjectSettings/ProjectSettings.asset | 3 +- 15 files changed, 917 insertions(+), 4 deletions(-) rename Assets/BITKit/Unity/Scripts/I18N/{I18N.asmdef => Plugins.I18N.asmdef} (100%) create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/AABB2D.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/BITKit.NativeQuadTree.Runtime.asmdef create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/BITKit.NativeQuadTree.Editor.asmdef create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeDrawer.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeTests.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/LookupTables.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTree.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeDrawing.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeRangeQuery.cs create mode 100644 Assets/BITKit/Unity/Scripts/NativeQuadTree/QuadTreeJobs.cs create mode 100644 Assets/BITKit/Unity/Scripts/Scenes/Editor/MaterialPaletteWindow.cs diff --git a/Assets/BITKit/Unity/Scripts/I18N/I18N.asmdef b/Assets/BITKit/Unity/Scripts/I18N/Plugins.I18N.asmdef similarity index 100% rename from Assets/BITKit/Unity/Scripts/I18N/I18N.asmdef rename to Assets/BITKit/Unity/Scripts/I18N/Plugins.I18N.asmdef diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/AABB2D.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/AABB2D.cs new file mode 100644 index 00000000..83735b91 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/AABB2D.cs @@ -0,0 +1,59 @@ +using System; +using Unity.Mathematics; + +namespace NativeQuadTree +{ + [Serializable] + public struct AABB2D { + public float2 Center; + public float2 Extents; + + public float2 Size => Extents * 2; + public float2 Min => Center - Extents; + public float2 Max => Center + Extents; + + public AABB2D(float2 center, float2 extents) + { + Center = center; + Extents = extents; + } + + public bool Contains(float2 point) { + if (point[0] < Center[0] - Extents[0]) { + return false; + } + if (point[0] > Center[0] + Extents[0]) { + return false; + } + + if (point[1] < Center[1] - Extents[1]) { + return false; + } + if (point[1] > Center[1] + Extents[1]) { + return false; + } + + return true; + } + + public bool Contains(AABB2D b) { + return Contains(b.Center + new float2(-b.Extents.x, -b.Extents.y)) && + Contains(b.Center + new float2(-b.Extents.x, b.Extents.y)) && + Contains(b.Center + new float2(b.Extents.x, -b.Extents.y)) && + Contains(b.Center + new float2(b.Extents.x, b.Extents.y)); + } + + public bool Intersects(AABB2D b) + { + //bool noOverlap = Min[0] > b.Max[0] || + // b.Min[0] > Max[0]|| + // Min[1] > b.Max[1] || + // b.Min[1] > Max[1]; +// + //return !noOverlap; + + return (math.abs(Center[0] - b.Center[0]) < (Extents[0] + b.Extents[0])) && + (math.abs(Center[1] - b.Center[1]) < (Extents[1] + b.Extents[1])); + } + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/BITKit.NativeQuadTree.Runtime.asmdef b/Assets/BITKit/Unity/Scripts/NativeQuadTree/BITKit.NativeQuadTree.Runtime.asmdef new file mode 100644 index 00000000..907fbe82 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/BITKit.NativeQuadTree.Runtime.asmdef @@ -0,0 +1,18 @@ +{ + "name": "BITKit.NativeQuadTree.Runtime", + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:2665a8d13d1b3f18800f46e256720795" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/BITKit.NativeQuadTree.Editor.asmdef b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/BITKit.NativeQuadTree.Editor.asmdef new file mode 100644 index 00000000..cadc2c0a --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/BITKit.NativeQuadTree.Editor.asmdef @@ -0,0 +1,22 @@ +{ + "name": "BITKit.NativeQuadTree.Editor", + "rootNamespace": "", + "references": [ + "GUID:d8b63aba1907145bea998dd612889d6b", + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:a9eec99827e569e45bfe3e5ea7494591", + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:0acc523941302664db1f4e527237feb3" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeDrawer.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeDrawer.cs new file mode 100644 index 00000000..9b154a17 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeDrawer.cs @@ -0,0 +1,61 @@ +using NativeQuadTree; +using Unity.Collections; +using UnityEditor; +using UnityEngine; + +public class QuadTreeDrawer : EditorWindow +{ + [MenuItem("Window/QuadTreeDrawer")] + static void Init() + { + GetWindow(typeof(QuadTreeDrawer)).Show(); + } + + public static void Draw(NativeQuadTree quadTree) where T : unmanaged + { + QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer)); + window.DoDraw(quadTree, default, default); + } + + public static void DrawWithResults(QuadTreeJobs.RangeQueryJob queryJob) where T : unmanaged + { + QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer)); + window.DoDraw(queryJob); + } + + [SerializeField] + Color[][] pixels; + + void DoDraw(NativeQuadTree quadTree, NativeList> results, AABB2D bounds) where T : unmanaged + { + pixels = new Color[256][]; + for (var i = 0; i < pixels.Length; i++) + { + pixels[i] = new Color[256]; + } + NativeQuadTree.Draw(quadTree, results, bounds, pixels); + } + + void DoDraw(QuadTreeJobs.RangeQueryJob queryJob) where T : unmanaged + { + DoDraw(queryJob.QuadTree, queryJob.Results, queryJob.Bounds); + } + + void OnGUI() + { + if(pixels != null) + { + var texture = new Texture2D(256, 256); + for (var x = 0; x < pixels.Length; x++) + { + for (int y = 0; y < pixels[x].Length; y++) + { + texture.SetPixel(x, y, pixels[x][y]); + } + } + texture.Apply(); + + GUI.DrawTexture(new Rect(0, 0, position.width, position.height), texture); + } + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeTests.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeTests.cs new file mode 100644 index 00000000..ccc8818f --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/Editor/QuadTreeTests.cs @@ -0,0 +1,134 @@ +using System.Diagnostics; +using NUnit.Framework; +using NativeQuadTree; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using Debug = UnityEngine.Debug; +using Random = UnityEngine.Random; + +public class QuadTreeTests +{ + AABB2D Bounds => new AABB2D(0, 1000); + + float2[] GetValues() + { + Random.InitState(0); + var values = new float2[20000]; + + for (int x = 0; x < values.Length; x++) + { + var val = new int2((int) Random.Range(-900, 900), (int) Random.Range(-900, 900)); + values[x] = val; + } + + return values; + } + + [Test] + public void InsertTriggerDivideBulk() + { + var values = GetValues(); + + var elements = new NativeArray>(values.Length, Allocator.TempJob); + + for (int i = 0; i < values.Length; i++) + { + elements[i] = new QuadElement + { + pos = values[i], + element = i + }; + } + + using var quadtree = new NativeQuadTree(Bounds, Allocator.TempJob); + var job = new QuadTreeJobs.AddBulkJob + { + Elements = elements, + QuadTree = quadtree, + }; + + var s = Stopwatch.StartNew(); + + job.Run(); + + s.Stop(); + Debug.Log(s.Elapsed.TotalMilliseconds); + + QuadTreeDrawer.Draw(quadtree); + elements.Dispose(); + } + + [Test] + public void RangeQueryAfterBulk() + { + var values = GetValues(); + + NativeArray> elements = new NativeArray>(values.Length, Allocator.TempJob); + + for (int i = 0; i < values.Length; i++) + { + elements[i] = new QuadElement + { + pos = values[i], + element = i + }; + } + + var quadTree = new NativeQuadTree(Bounds); + quadTree.ClearAndBulkInsert(elements); + + var queryJob = new QuadTreeJobs.RangeQueryJob + { + QuadTree = quadTree, + Bounds = new AABB2D(100, 140), + Results = new NativeList>(1000, Allocator.TempJob) + }; + + var s = Stopwatch.StartNew(); + queryJob.Run(); + s.Stop(); + Debug.Log(s.Elapsed.TotalMilliseconds + " result: " + queryJob.Results.Length); + + QuadTreeDrawer.DrawWithResults(queryJob); + quadTree.Dispose(); + elements.Dispose(); + queryJob.Results.Dispose(); + } + + [Test] + public void InsertTriggerDivideNonBurstBulk() + { + var values = GetValues(); + + var positions = new NativeArray(values.Length, Allocator.TempJob); + var quadTree = new NativeQuadTree(Bounds); + + positions.CopyFrom(values); + + + NativeArray> elements = new NativeArray>(positions.Length, Allocator.Temp); + + for (int i = 0; i < positions.Length; i++) + { + elements[i] = new QuadElement + { + pos = positions[i], + element = i + }; + } + + var s = Stopwatch.StartNew(); + + quadTree.ClearAndBulkInsert(elements); + + s.Stop(); + Debug.Log(s.Elapsed.TotalMilliseconds); + + QuadTreeDrawer.Draw(quadTree); + quadTree.Dispose(); + positions.Dispose(); + } +} diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/LookupTables.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/LookupTables.cs new file mode 100644 index 00000000..6e681fc7 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/LookupTables.cs @@ -0,0 +1,70 @@ +namespace NativeQuadTree +{ + public static class LookupTables + { + public static readonly ushort[] MortonLookup = { + // 0 1 100 101 10000 10001 10100 10101 + 0x0000, 0x0001, 0x0004, 0x0005, 0x0010, 0x0011, 0x0014, 0x0015, + // 1000000 1000001 1000100 1000101 1010000 1010001 1010100 1010101 + 0x0040, 0x0041, 0x0044, 0x0045, 0x0050, 0x0051, 0x0054, 0x0055, + // etc... + 0x0100, 0x0101, 0x0104, 0x0105, 0x0110, 0x0111, 0x0114, 0x0115, + 0x0140, 0x0141, 0x0144, 0x0145, 0x0150, 0x0151, 0x0154, 0x0155, + 0x0400, 0x0401, 0x0404, 0x0405, 0x0410, 0x0411, 0x0414, 0x0415, + 0x0440, 0x0441, 0x0444, 0x0445, 0x0450, 0x0451, 0x0454, 0x0455, + 0x0500, 0x0501, 0x0504, 0x0505, 0x0510, 0x0511, 0x0514, 0x0515, + 0x0540, 0x0541, 0x0544, 0x0545, 0x0550, 0x0551, 0x0554, 0x0555, + 0x1000, 0x1001, 0x1004, 0x1005, 0x1010, 0x1011, 0x1014, 0x1015, + 0x1040, 0x1041, 0x1044, 0x1045, 0x1050, 0x1051, 0x1054, 0x1055, + 0x1100, 0x1101, 0x1104, 0x1105, 0x1110, 0x1111, 0x1114, 0x1115, + 0x1140, 0x1141, 0x1144, 0x1145, 0x1150, 0x1151, 0x1154, 0x1155, + 0x1400, 0x1401, 0x1404, 0x1405, 0x1410, 0x1411, 0x1414, 0x1415, + 0x1440, 0x1441, 0x1444, 0x1445, 0x1450, 0x1451, 0x1454, 0x1455, + 0x1500, 0x1501, 0x1504, 0x1505, 0x1510, 0x1511, 0x1514, 0x1515, + 0x1540, 0x1541, 0x1544, 0x1545, 0x1550, 0x1551, 0x1554, 0x1555, + 0x4000, 0x4001, 0x4004, 0x4005, 0x4010, 0x4011, 0x4014, 0x4015, + 0x4040, 0x4041, 0x4044, 0x4045, 0x4050, 0x4051, 0x4054, 0x4055, + 0x4100, 0x4101, 0x4104, 0x4105, 0x4110, 0x4111, 0x4114, 0x4115, + 0x4140, 0x4141, 0x4144, 0x4145, 0x4150, 0x4151, 0x4154, 0x4155, + 0x4400, 0x4401, 0x4404, 0x4405, 0x4410, 0x4411, 0x4414, 0x4415, + 0x4440, 0x4441, 0x4444, 0x4445, 0x4450, 0x4451, 0x4454, 0x4455, + 0x4500, 0x4501, 0x4504, 0x4505, 0x4510, 0x4511, 0x4514, 0x4515, + 0x4540, 0x4541, 0x4544, 0x4545, 0x4550, 0x4551, 0x4554, 0x4555, + 0x5000, 0x5001, 0x5004, 0x5005, 0x5010, 0x5011, 0x5014, 0x5015, + 0x5040, 0x5041, 0x5044, 0x5045, 0x5050, 0x5051, 0x5054, 0x5055, + 0x5100, 0x5101, 0x5104, 0x5105, 0x5110, 0x5111, 0x5114, 0x5115, + 0x5140, 0x5141, 0x5144, 0x5145, 0x5150, 0x5151, 0x5154, 0x5155, + 0x5400, 0x5401, 0x5404, 0x5405, 0x5410, 0x5411, 0x5414, 0x5415, + 0x5440, 0x5441, 0x5444, 0x5445, 0x5450, 0x5451, 0x5454, 0x5455, + 0x5500, 0x5501, 0x5504, 0x5505, 0x5510, 0x5511, 0x5514, 0x5515, + 0x5540, 0x5541, 0x5544, 0x5545, 0x5550, 0x5551, 0x5554, 0x5555 + }; + + public static readonly int[] DepthSizeLookup = + { + 0, + 1, + 1+2*2, + 1+2*2+4*4, + 1+2*2+4*4+8*8, + 1+2*2+4*4+8*8+16*16, + 1+2*2+4*4+8*8+16*16+32*32, + 1+2*2+4*4+8*8+16*16+32*32+64*64, + 1+2*2+4*4+8*8+16*16+32*32+64*64+128*128, + 1+2*2+4*4+8*8+16*16+32*32+64*64+128*128+256*256, + }; + + public static readonly int[] DepthLookup = + { + 0, + 2, + 4, + 8, + 16, + 32, + 64, + 128, + 256, + }; + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTree.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTree.cs new file mode 100644 index 00000000..3a1280f3 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTree.cs @@ -0,0 +1,238 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using UnityEngine; + +namespace NativeQuadTree +{ + // Represents an element node in the quadtree. + public struct QuadElement where T : unmanaged + { + public float2 pos; + public T element; + } + + struct QuadNode + { + // Points to this node's first child index in elements + public int firstChildIndex; + + // Number of elements in the leaf + public short count; + public bool isLeaf; + } + + /// + /// A QuadTree aimed to be used with Burst, supports fast bulk insertion and querying. + /// + /// TODO: + /// - Better test coverage + /// - Automated depth / bounds / max leaf elements calculation + /// + public unsafe partial struct NativeQuadTree : IDisposable where T : unmanaged + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Safety + AtomicSafetyHandle safetyHandle; + [NativeSetClassTypeToNullOnSchedule] + DisposeSentinel disposeSentinel; +#endif + // Data + [NativeDisableUnsafePtrRestriction] + UnsafeList>* elements; + + [NativeDisableUnsafePtrRestriction] + UnsafeList* lookup; + + [NativeDisableUnsafePtrRestriction] + UnsafeList* nodes; + + int elementsCount; + + int maxDepth; + short maxLeafElements; + + AABB2D bounds; // NOTE: Currently assuming uniform + + /// + /// Create a new QuadTree. + /// - Ensure the bounds are not way bigger than needed, otherwise the buckets are very off. Probably best to calculate bounds + /// - The higher the depth, the larger the overhead, it especially goes up at a depth of 7/8 + /// + public NativeQuadTree(AABB2D bounds, Allocator allocator = Allocator.Temp, int maxDepth = 6, short maxLeafElements = 16, + int initialElementsCapacity = 256 + ) : this() + { + this.bounds = bounds; + this.maxDepth = maxDepth; + this.maxLeafElements = maxLeafElements; + elementsCount = 0; + + if(maxDepth > 8) + { + // Currently no support for higher depths, the morton code lookup tables would have to support it + throw new InvalidOperationException(); + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // TODO: Find out what the equivalent of this is in latest entities + // CollectionHelper.CheckIsUnmanaged(); + DisposeSentinel.Create(out safetyHandle, out disposeSentinel, 1, allocator); +#endif + + // Allocate memory for every depth, the nodes on all depths are stored in a single continuous array + var totalSize = LookupTables.DepthSizeLookup[maxDepth+1]; + + lookup = UnsafeList.Create( + totalSize, + allocator, + NativeArrayOptions.ClearMemory); + + nodes = UnsafeList.Create( + totalSize, + allocator, + NativeArrayOptions.ClearMemory); + + elements = UnsafeList>.Create( + initialElementsCapacity, + allocator); + } + + public void ClearAndBulkInsert(NativeArray> incomingElements) + { + // Always have to clear before bulk insert as otherwise the lookup and node allocations need to account + // for existing data. + Clear(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(safetyHandle); +#endif + + // Resize if needed + if(elements->Capacity < elementsCount + incomingElements.Length) + { + elements->Resize(math.max(incomingElements.Length, elements->Capacity*2)); + } + + // Prepare morton codes + var mortonCodes = new NativeArray(incomingElements.Length, Allocator.Temp); + var depthExtentsScaling = LookupTables.DepthLookup[maxDepth] / bounds.Extents; + for (var i = 0; i < incomingElements.Length; i++) + { + var incPos = incomingElements[i].pos; + incPos -= bounds.Center; // Offset by center + incPos.y = -incPos.y; // World -> array + var pos = (incPos + bounds.Extents) * .5f; // Make positive + // Now scale into available space that belongs to the depth + pos *= depthExtentsScaling; + // And interleave the bits for the morton code + mortonCodes[i] = (LookupTables.MortonLookup[(int) pos.x] | (LookupTables.MortonLookup[(int) pos.y] << 1)); + } + + // Index total child element count per node (total, so parent's counts include those of child nodes) + for (var i = 0; i < mortonCodes.Length; i++) + { + int atIndex = 0; + for (int depth = 0; depth <= maxDepth; depth++) + { + // Increment the node on this depth that this element is contained in + (*(int*) ((IntPtr) lookup->Ptr + atIndex * sizeof (int)))++; + atIndex = IncrementIndex(depth, mortonCodes, i, atIndex); + } + } + + // Prepare the tree leaf nodes + RecursivePrepareLeaves(1, 1); + + // Add elements to leaf nodes + for (var i = 0; i < incomingElements.Length; i++) + { + int atIndex = 0; + + for (int depth = 0; depth <= maxDepth; depth++) + { + var node = UnsafeUtility.ReadArrayElement(nodes->Ptr, atIndex); + if(node.isLeaf) + { + // We found a leaf, add this element to it and move to the next element + UnsafeUtility.WriteArrayElement(elements->Ptr, node.firstChildIndex + node.count, incomingElements[i]); + node.count++; + UnsafeUtility.WriteArrayElement(nodes->Ptr, atIndex, node); + break; + } + // No leaf found, we keep going deeper until we find one + atIndex = IncrementIndex(depth, mortonCodes, i, atIndex); + } + } + + mortonCodes.Dispose(); + } + + int IncrementIndex(int depth, NativeArray mortonCodes, int i, int atIndex) + { + var atDepth = math.max(0, maxDepth - depth); + // Shift to the right and only get the first two bits + int shiftedMortonCode = (mortonCodes[i] >> ((atDepth - 1) * 2)) & 0b11; + // so the index becomes that... (0,1,2,3) + atIndex += LookupTables.DepthSizeLookup[atDepth] * shiftedMortonCode; + atIndex++; // offset for self + return atIndex; + } + + void RecursivePrepareLeaves(int prevOffset, int depth) + { + for (int l = 0; l < 4; l++) + { + var at = prevOffset + l * LookupTables.DepthSizeLookup[maxDepth - depth+1]; + + var elementCount = UnsafeUtility.ReadArrayElement(lookup->Ptr, at); + + if(elementCount > maxLeafElements && depth < maxDepth) + { + // There's more elements than allowed on this node so keep going deeper + RecursivePrepareLeaves(at+1, depth+1); + } + else if(elementCount != 0) + { + // We either hit max depth or there's less than the max elements on this node, make it a leaf + var node = new QuadNode {firstChildIndex = elementsCount, count = 0, isLeaf = true }; + UnsafeUtility.WriteArrayElement(nodes->Ptr, at, node); + elementsCount += elementCount; + } + } + } + + public void RangeQuery(AABB2D bounds, NativeList> results) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(safetyHandle); +#endif + new QuadTreeRangeQuery().Query(this, bounds, results); + } + + public void Clear() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(safetyHandle); +#endif + UnsafeUtility.MemClear(lookup->Ptr, lookup->Capacity * UnsafeUtility.SizeOf()); + UnsafeUtility.MemClear(nodes->Ptr, nodes->Capacity * UnsafeUtility.SizeOf()); + UnsafeUtility.MemClear(elements->Ptr, elements->Capacity * UnsafeUtility.SizeOf>()); + elementsCount = 0; + } + + public void Dispose() + { + UnsafeList>.Destroy(elements); + elements = null; + UnsafeList.Destroy(lookup); + lookup = null; + UnsafeList.Destroy(nodes); + nodes = null; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + DisposeSentinel.Dispose(ref safetyHandle, ref disposeSentinel); +#endif + } + } +} diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeDrawing.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeDrawing.cs new file mode 100644 index 00000000..b580dd0d --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeDrawing.cs @@ -0,0 +1,77 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; +using UnityEngine; + +namespace NativeQuadTree +{ + /// + /// Editor drawing of the NativeQuadTree + /// + public unsafe partial struct NativeQuadTree where T : unmanaged + { + public static void Draw(NativeQuadTree tree, NativeList> results, AABB2D range, + Color[][] texture) + { + var widthMult = texture.Length / tree.bounds.Extents.x * 2 / 2 / 2; + var heightMult = texture[0].Length / tree.bounds.Extents.y * 2 / 2 / 2; + + var widthAdd = tree.bounds.Center.x + tree.bounds.Extents.x; + var heightAdd = tree.bounds.Center.y + tree.bounds.Extents.y; + + for (int i = 0; i < tree.nodes->Capacity; i++) + { + var node = UnsafeUtility.ReadArrayElement(tree.nodes->Ptr, i); + + if(node.count > 0) + { + for (int k = 0; k < node.count; k++) + { + var element = + UnsafeUtility.ReadArrayElement>(tree.elements->Ptr, node.firstChildIndex + k); + + texture[(int) ((element.pos.x + widthAdd) * widthMult)] + [(int) ((element.pos.y + heightAdd) * heightMult)] = Color.red; + } + } + } + + if (results.IsCreated) + { + foreach (var element in results) + { + texture[(int)((element.pos.x + widthAdd) * widthMult)] + [(int)((element.pos.y + heightAdd) * heightMult)] = Color.green; + } + } + + DrawBounds(texture, range, tree); + } + + static void DrawBounds(Color[][] texture, AABB2D bounds, NativeQuadTree tree) + { + var widthMult = texture.Length / tree.bounds.Extents.x * 2 / 2 / 2; + var heightMult = texture[0].Length / tree.bounds.Extents.y * 2 / 2 / 2; + + var widthAdd = tree.bounds.Center.x + tree.bounds.Extents.x; + var heightAdd = tree.bounds.Center.y + tree.bounds.Extents.y; + + var top = new float2(bounds.Center.x, bounds.Center.y - bounds.Extents.y); + var left = new float2(bounds.Center.x - bounds.Extents.x, bounds.Center.y); + + for (int leftToRight = 0; leftToRight < bounds.Extents.x * 2; leftToRight++) + { + var poxX = left.x + leftToRight; + texture[(int) ((poxX + widthAdd) * widthMult)][(int) ((bounds.Center.y + heightAdd + bounds.Extents.y) * heightMult)] = Color.blue; + texture[(int) ((poxX + widthAdd) * widthMult)][(int) ((bounds.Center.y + heightAdd - bounds.Extents.y) * heightMult)] = Color.blue; + } + + for (int topToBottom = 0; topToBottom < bounds.Extents.y * 2; topToBottom++) + { + var posY = top.y + topToBottom; + texture[(int) ((bounds.Center.x + widthAdd + bounds.Extents.x) * widthMult)][(int) ((posY + heightAdd) * heightMult)] = Color.blue; + texture[(int) ((bounds.Center.x + widthAdd - bounds.Extents.x) * widthMult)][(int) ((posY + heightAdd) * heightMult)] = Color.blue; + } + } + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeRangeQuery.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeRangeQuery.cs new file mode 100644 index 00000000..db70a37a --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/NativeQuadTreeRangeQuery.cs @@ -0,0 +1,110 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace NativeQuadTree +{ + public unsafe partial struct NativeQuadTree where T : unmanaged + { + struct QuadTreeRangeQuery + { + NativeQuadTree tree; + + UnsafeList* fastResults; + int count; + + AABB2D bounds; + + public void Query(NativeQuadTree tree, AABB2D bounds, NativeList> results) + { + this.tree = tree; + this.bounds = bounds; + count = 0; + + // Get pointer to inner list data for faster writing + fastResults = (UnsafeList*) NativeListUnsafeUtility.GetInternalListDataPtrUnchecked(ref results); + + RecursiveRangeQuery(tree.bounds, false, 1, 1); + + fastResults->Length = count; + } + + public void RecursiveRangeQuery(AABB2D parentBounds, bool parentContained, int prevOffset, int depth) + { + if(count + 4 * tree.maxLeafElements > fastResults->Capacity) + { + fastResults->Resize(math.max(fastResults->Capacity * 2, count + 4 * tree.maxLeafElements)); + } + + var depthSize = LookupTables.DepthSizeLookup[tree.maxDepth - depth+1]; + for (int l = 0; l < 4; l++) + { + var childBounds = GetChildBounds(parentBounds, l); + + var contained = parentContained; + if(!contained) + { + if(bounds.Contains(childBounds)) + { + contained = true; + } + else if(!bounds.Intersects(childBounds)) + { + continue; + } + } + + + var at = prevOffset + l * depthSize; + + var elementCount = UnsafeUtility.ReadArrayElement(tree.lookup->Ptr, at); + + if(elementCount > tree.maxLeafElements && depth < tree.maxDepth) + { + RecursiveRangeQuery(childBounds, contained, at+1, depth+1); + } + else if(elementCount != 0) + { + var node = UnsafeUtility.ReadArrayElement(tree.nodes->Ptr, at); + + if(contained) + { + var index = (void*) ((IntPtr) tree.elements->Ptr + node.firstChildIndex * UnsafeUtility.SizeOf>()); + + UnsafeUtility.MemCpy((void*) ((IntPtr) fastResults->Ptr + count * UnsafeUtility.SizeOf>()), + index, node.count * UnsafeUtility.SizeOf>()); + count += node.count; + } + else + { + for (int k = 0; k < node.count; k++) + { + var element = UnsafeUtility.ReadArrayElement>(tree.elements->Ptr, node.firstChildIndex + k); + if(bounds.Contains(element.pos)) + { + UnsafeUtility.WriteArrayElement(fastResults->Ptr, count++, element); + } + } + } + } + } + } + + static AABB2D GetChildBounds(AABB2D parentBounds, int childZIndex) + { + var half = parentBounds.Extents.x * .5f; + + switch (childZIndex) + { + case 0: return new AABB2D(new float2(parentBounds.Center.x - half, parentBounds.Center.y + half), half); + case 1: return new AABB2D(new float2(parentBounds.Center.x + half, parentBounds.Center.y + half), half); + case 2: return new AABB2D(new float2(parentBounds.Center.x - half, parentBounds.Center.y - half), half); + case 3: return new AABB2D(new float2(parentBounds.Center.x + half, parentBounds.Center.y - half), half); + default: throw new Exception(); + } + } + } + + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/NativeQuadTree/QuadTreeJobs.cs b/Assets/BITKit/Unity/Scripts/NativeQuadTree/QuadTreeJobs.cs new file mode 100644 index 00000000..0c303e21 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/NativeQuadTree/QuadTreeJobs.cs @@ -0,0 +1,54 @@ +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace NativeQuadTree +{ + /// + /// Examples on jobs for the NativeQuadTree + /// + public static class QuadTreeJobs + { + /// + /// Bulk insert many items into the tree + /// + [BurstCompile] + public struct AddBulkJob : IJob where T : unmanaged + { + [ReadOnly] + public NativeArray> Elements; + + public NativeQuadTree QuadTree; + + public void Execute() + { + QuadTree.ClearAndBulkInsert(Elements); + } + } + + /// + /// Example on how to do a range query, it's better to write your own and do many queries in a batch + /// + [BurstCompile] + public struct RangeQueryJob : IJob where T : unmanaged + { + [ReadOnly] + public AABB2D Bounds; + + [ReadOnly] + public NativeQuadTree QuadTree; + + public NativeList> Results; + + public void Execute() + { + for (int i = 0; i < 1000; i++) + { + QuadTree.RangeQuery(Bounds, Results); + Results.Clear(); + } + QuadTree.RangeQuery(Bounds, Results); + } + } + } +} \ No newline at end of file diff --git a/Assets/BITKit/Unity/Scripts/Scenes/Editor/MaterialPaletteWindow.cs b/Assets/BITKit/Unity/Scripts/Scenes/Editor/MaterialPaletteWindow.cs new file mode 100644 index 00000000..60cf0c94 --- /dev/null +++ b/Assets/BITKit/Unity/Scripts/Scenes/Editor/MaterialPaletteWindow.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; + +namespace BITKit +{ + public class MaterialPaletteWindow : EditorWindow + { + [MenuItem("Tools/Scenes/Material Palette")] + public static void Open() + { + GetWindow().Show(); + } + + private Button buildButton; + private Label _titleLabel; + private VisualElement _container; + + private void OnEnable() + { + UnityEditor.Selection.selectionChanged += OnSelectionChanged; + + rootVisualElement.Clear(); + _titleLabel = rootVisualElement.Create