This commit is contained in:
parent
2c8dfd3c86
commit
605ccbcf8d
|
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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<T>(NativeQuadTree<T> quadTree) where T : unmanaged
|
||||
{
|
||||
QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer));
|
||||
window.DoDraw(quadTree, default, default);
|
||||
}
|
||||
|
||||
public static void DrawWithResults<T>(QuadTreeJobs.RangeQueryJob<T> queryJob) where T : unmanaged
|
||||
{
|
||||
QuadTreeDrawer window = (QuadTreeDrawer)GetWindow(typeof(QuadTreeDrawer));
|
||||
window.DoDraw(queryJob);
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
Color[][] pixels;
|
||||
|
||||
void DoDraw<T>(NativeQuadTree<T> quadTree, NativeList<QuadElement<T>> results, AABB2D bounds) where T : unmanaged
|
||||
{
|
||||
pixels = new Color[256][];
|
||||
for (var i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
pixels[i] = new Color[256];
|
||||
}
|
||||
NativeQuadTree<T>.Draw(quadTree, results, bounds, pixels);
|
||||
}
|
||||
|
||||
void DoDraw<T>(QuadTreeJobs.RangeQueryJob<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<QuadElement<int>>(values.Length, Allocator.TempJob);
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
elements[i] = new QuadElement<int>
|
||||
{
|
||||
pos = values[i],
|
||||
element = i
|
||||
};
|
||||
}
|
||||
|
||||
using var quadtree = new NativeQuadTree<int>(Bounds, Allocator.TempJob);
|
||||
var job = new QuadTreeJobs.AddBulkJob<int>
|
||||
{
|
||||
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<QuadElement<int>> elements = new NativeArray<QuadElement<int>>(values.Length, Allocator.TempJob);
|
||||
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
elements[i] = new QuadElement<int>
|
||||
{
|
||||
pos = values[i],
|
||||
element = i
|
||||
};
|
||||
}
|
||||
|
||||
var quadTree = new NativeQuadTree<int>(Bounds);
|
||||
quadTree.ClearAndBulkInsert(elements);
|
||||
|
||||
var queryJob = new QuadTreeJobs.RangeQueryJob<int>
|
||||
{
|
||||
QuadTree = quadTree,
|
||||
Bounds = new AABB2D(100, 140),
|
||||
Results = new NativeList<QuadElement<int>>(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<float2>(values.Length, Allocator.TempJob);
|
||||
var quadTree = new NativeQuadTree<int>(Bounds);
|
||||
|
||||
positions.CopyFrom(values);
|
||||
|
||||
|
||||
NativeArray<QuadElement<int>> elements = new NativeArray<QuadElement<int>>(positions.Length, Allocator.Temp);
|
||||
|
||||
for (int i = 0; i < positions.Length; i++)
|
||||
{
|
||||
elements[i] = new QuadElement<int>
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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<T> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public unsafe partial struct NativeQuadTree<T> : IDisposable where T : unmanaged
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
// Safety
|
||||
AtomicSafetyHandle safetyHandle;
|
||||
[NativeSetClassTypeToNullOnSchedule]
|
||||
DisposeSentinel disposeSentinel;
|
||||
#endif
|
||||
// Data
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
UnsafeList<QuadElement<T>>* elements;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
UnsafeList<int>* lookup;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
UnsafeList<QuadNode>* nodes;
|
||||
|
||||
int elementsCount;
|
||||
|
||||
int maxDepth;
|
||||
short maxLeafElements;
|
||||
|
||||
AABB2D bounds; // NOTE: Currently assuming uniform
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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<T>();
|
||||
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<int>.Create(
|
||||
totalSize,
|
||||
allocator,
|
||||
NativeArrayOptions.ClearMemory);
|
||||
|
||||
nodes = UnsafeList<QuadNode>.Create(
|
||||
totalSize,
|
||||
allocator,
|
||||
NativeArrayOptions.ClearMemory);
|
||||
|
||||
elements = UnsafeList<QuadElement<T>>.Create(
|
||||
initialElementsCapacity,
|
||||
allocator);
|
||||
}
|
||||
|
||||
public void ClearAndBulkInsert(NativeArray<QuadElement<T>> 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<int>(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<QuadNode>(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<int> 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<int>(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<QuadElement<T>> 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<int>());
|
||||
UnsafeUtility.MemClear(nodes->Ptr, nodes->Capacity * UnsafeUtility.SizeOf<QuadNode>());
|
||||
UnsafeUtility.MemClear(elements->Ptr, elements->Capacity * UnsafeUtility.SizeOf<QuadElement<T>>());
|
||||
elementsCount = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnsafeList<QuadElement<T>>.Destroy(elements);
|
||||
elements = null;
|
||||
UnsafeList<int>.Destroy(lookup);
|
||||
lookup = null;
|
||||
UnsafeList<QuadNode>.Destroy(nodes);
|
||||
nodes = null;
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
DisposeSentinel.Dispose(ref safetyHandle, ref disposeSentinel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NativeQuadTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor drawing of the NativeQuadTree
|
||||
/// </summary>
|
||||
public unsafe partial struct NativeQuadTree<T> where T : unmanaged
|
||||
{
|
||||
public static void Draw(NativeQuadTree<T> tree, NativeList<QuadElement<T>> 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<QuadNode>(tree.nodes->Ptr, i);
|
||||
|
||||
if(node.count > 0)
|
||||
{
|
||||
for (int k = 0; k < node.count; k++)
|
||||
{
|
||||
var element =
|
||||
UnsafeUtility.ReadArrayElement<QuadElement<T>>(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<T> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace NativeQuadTree
|
||||
{
|
||||
public unsafe partial struct NativeQuadTree<T> where T : unmanaged
|
||||
{
|
||||
struct QuadTreeRangeQuery
|
||||
{
|
||||
NativeQuadTree<T> tree;
|
||||
|
||||
UnsafeList<T>* fastResults;
|
||||
int count;
|
||||
|
||||
AABB2D bounds;
|
||||
|
||||
public void Query(NativeQuadTree<T> tree, AABB2D bounds, NativeList<QuadElement<T>> results)
|
||||
{
|
||||
this.tree = tree;
|
||||
this.bounds = bounds;
|
||||
count = 0;
|
||||
|
||||
// Get pointer to inner list data for faster writing
|
||||
fastResults = (UnsafeList<T>*) 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<int>(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<QuadNode>(tree.nodes->Ptr, at);
|
||||
|
||||
if(contained)
|
||||
{
|
||||
var index = (void*) ((IntPtr) tree.elements->Ptr + node.firstChildIndex * UnsafeUtility.SizeOf<QuadElement<T>>());
|
||||
|
||||
UnsafeUtility.MemCpy((void*) ((IntPtr) fastResults->Ptr + count * UnsafeUtility.SizeOf<QuadElement<T>>()),
|
||||
index, node.count * UnsafeUtility.SizeOf<QuadElement<T>>());
|
||||
count += node.count;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int k = 0; k < node.count; k++)
|
||||
{
|
||||
var element = UnsafeUtility.ReadArrayElement<QuadElement<T>>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace NativeQuadTree
|
||||
{
|
||||
/// <summary>
|
||||
/// Examples on jobs for the NativeQuadTree
|
||||
/// </summary>
|
||||
public static class QuadTreeJobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Bulk insert many items into the tree
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
public struct AddBulkJob<T> : IJob where T : unmanaged
|
||||
{
|
||||
[ReadOnly]
|
||||
public NativeArray<QuadElement<T>> Elements;
|
||||
|
||||
public NativeQuadTree<T> QuadTree;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
QuadTree.ClearAndBulkInsert(Elements);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Example on how to do a range query, it's better to write your own and do many queries in a batch
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
public struct RangeQueryJob<T> : IJob where T : unmanaged
|
||||
{
|
||||
[ReadOnly]
|
||||
public AABB2D Bounds;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeQuadTree<T> QuadTree;
|
||||
|
||||
public NativeList<QuadElement<T>> Results;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
QuadTree.RangeQuery(Bounds, Results);
|
||||
Results.Clear();
|
||||
}
|
||||
QuadTree.RangeQuery(Bounds, Results);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MaterialPaletteWindow>().Show();
|
||||
}
|
||||
|
||||
private Button buildButton;
|
||||
private Label _titleLabel;
|
||||
private VisualElement _container;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
UnityEditor.Selection.selectionChanged += OnSelectionChanged;
|
||||
|
||||
rootVisualElement.Clear();
|
||||
_titleLabel = rootVisualElement.Create<Label>();
|
||||
_container = rootVisualElement.Create<VisualElement>();
|
||||
|
||||
_titleLabel.text = "选择一个物体";
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnityEditor.Selection.selectionChanged -= OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged()
|
||||
{
|
||||
var active = UnityEditor.Selection.activeGameObject;
|
||||
|
||||
_container.Clear();
|
||||
|
||||
_titleLabel.text = active ? active.name : "未选择物体";
|
||||
if (!active) return;
|
||||
foreach (var x in active.GetComponentsInChildren<MeshRenderer>(true))
|
||||
{
|
||||
foreach (var material in x.sharedMaterials)
|
||||
{
|
||||
var filed = rootVisualElement.Create<ObjectField>();
|
||||
filed.label = material.name;
|
||||
filed.objectType = typeof(Material);
|
||||
filed.value = material;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,9 +2,9 @@
|
|||
"name": "BITKit.WorkdChunk",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:a209c53514018594f9f482516f2a6781",
|
||||
"GUID:14fe60d984bf9f84eac55c6ea033a8f4",
|
||||
"GUID:9400d40641bab5b4a9702f65bf5c6eb5"
|
||||
"GUID:a9eec99827e569e45bfe3e5ea7494591",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using NativeQuadTree;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace BITKit.WorldChunk
|
||||
{
|
||||
public class WorldChunkManager : MonoBehaviour
|
||||
{
|
||||
public Vector2Int playerSize;
|
||||
private Rect playerRect;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
var quadTree = new NativeQuadTree<int>(new AABB2D(default, new float2(1024,1024)));
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
var cameraPos = Camera.main.transform.position;
|
||||
|
|
|
@ -140,8 +140,7 @@ PlayerSettings:
|
|||
visionOSBundleVersion: 1.0
|
||||
tvOSBundleVersion: 1.0
|
||||
bundleVersion: 1.5
|
||||
preloadedAssets:
|
||||
- {fileID: 11400000, guid: 6c3ca1ca26aa7d84eaa7fee9094d9164, type: 2}
|
||||
preloadedAssets: []
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
m_HolographicPauseOnTrackingLoss: 1
|
||||
|
|
Loading…
Reference in New Issue