public unsafe void BuildTreeAndOverlapTasks([Values(2, 10, 33, 100)] int elementCount)
        {
            const int threadCount = 8;

            elementCount *= 2;
            int numNodes = elementCount + Constants.MaxNumTreeBranches;

            var points      = new NativeArray <PointAndIndex>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var aabbs       = new NativeArray <Aabb>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var filters     = new NativeArray <CollisionFilter>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var nodefilters = new NativeArray <CollisionFilter>(numNodes, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var branchCount = new NativeArray <int>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            InitInputWithCopyArrays(points, aabbs, filters);

            // Override filter data with default filters.
            for (int i = 0; i < filters.Length; i++)
            {
                filters[i] = CollisionFilter.Default;
            }

            for (int i = 0; i < nodefilters.Length; i++)
            {
                nodefilters[i] = CollisionFilter.Default;
            }

            var nodes = new NativeArray <Node>(numNodes, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            var ranges           = new NativeArray <Range>(Constants.MaxNumTreeBranches, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var branchNodeOffset = new NativeArray <int>(Constants.MaxNumTreeBranches, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            JobHandle handle = new BuildFirstNLevelsJob
            {
                Points            = points,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                Ranges            = ranges,
                BranchNodeOffsets = branchNodeOffset,
                BranchCount       = branchCount,
                ThreadCount       = threadCount,
            }.Schedule();

            handle = new BuildBranchesJob
            {
                Points            = points,
                Aabbs             = aabbs,
                BodyFilters       = filters,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                Ranges            = ranges,
                BranchNodeOffsets = branchNodeOffset,
                BranchCount       = branchCount
            }.Schedule(Constants.MaxNumTreeBranches, 1, handle);

            handle = new FinalizeTreeJob
            {
                Aabbs             = aabbs,
                LeafFilters       = filters,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                BranchNodeOffsets = branchNodeOffset,
                NumNodes          = numNodes,
                BranchCount       = branchCount
            }.Schedule(handle);

            handle.Complete();

            int numBranchOverlapPairs = branchCount[0] * (branchCount[0] + 1) / 2;
            var nodePairIndices       = new NativeArray <int2>(numBranchOverlapPairs, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var collisionPairs        = new BlockStream(numBranchOverlapPairs, 0xb08c3d78);

            handle = new Broadphase.DynamicVsDynamicBuildBranchNodePairsJob
            {
                Ranges          = ranges,
                NumBranches     = branchCount[0],
                NodePairIndices = nodePairIndices
            }.Schedule();

            handle = new Broadphase.DynamicVsDynamicFindOverlappingPairsJob
            {
                DynamicNodes       = nodes,
                PairWriter         = collisionPairs,
                BodyFilters        = filters,
                NodePairIndices    = nodePairIndices,
                DynamicNodeFilters = nodefilters,
            }.Schedule(numBranchOverlapPairs, numBranchOverlapPairs, handle);

            handle.Complete();

            int numPairs = collisionPairs.ComputeItemCount();

            Debug.Log($"Num colliding pairs: {numPairs}");

            var bvh = new BoundingVolumeHierarchy(nodes);

            bvh.CheckIntegrity();

            Assert.IsTrue(elementCount / 2 == numPairs);

            filters.Dispose();
            nodefilters.Dispose();
            nodes.Dispose();
            ranges.Dispose();
            collisionPairs.Dispose();
            branchCount.Dispose();
        }
        public unsafe void BuildTreeTasks([Values(2, 10, 33, 100, 1000)] int elementCount)
        {
            const int threadCount = 8;
            int       numNodes    = elementCount + Constants.MaxNumTreeBranches;

            var points  = new NativeArray <PointAndIndex>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var aabbs   = new NativeArray <Aabb>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var filters = new NativeArray <CollisionFilter>(elementCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            InitInputArrays(points, aabbs, filters);

            var nodes = new NativeArray <Node>(numNodes, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            var ranges           = new NativeArray <Range>(Constants.MaxNumTreeBranches, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var branchNodeOffset = new NativeArray <int>(Constants.MaxNumTreeBranches, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var branchCount      = new NativeArray <int>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            var handle = new BuildFirstNLevelsJob
            {
                Points            = points,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                Ranges            = ranges,
                BranchNodeOffsets = branchNodeOffset,
                BranchCount       = branchCount,
                ThreadCount       = threadCount
            }.Schedule();

            var task2 = new BuildBranchesJob
            {
                Points            = points,
                Aabbs             = aabbs,
                BodyFilters       = filters,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                NodeFilters       = null,
                Ranges            = ranges,
                BranchNodeOffsets = branchNodeOffset,
                BranchCount       = branchCount
            };

            var task3 = new FinalizeTreeJob
            {
                Aabbs             = aabbs,
                Nodes             = (Node *)nodes.GetUnsafePtr(),
                BranchNodeOffsets = branchNodeOffset,
                NumNodes          = nodes.Length,
                LeafFilters       = filters,
                BranchCount       = branchCount
            };

            handle = task2.Schedule(Constants.MaxNumTreeBranches, 1, handle);
            handle = task3.Schedule(handle);
            handle.Complete();

            var bvh = new BoundingVolumeHierarchy(nodes);

            bvh.CheckIntegrity();

            filters.Dispose();
            nodes.Dispose();
            ranges.Dispose();
            branchCount.Dispose();
        }