/// <summary>
        /// Schedule a set of jobs to build the dynamic tree of the broadphase based on the given world.
        /// </summary>
        public JobHandle ScheduleDynamicTreeBuildJobs(
            ref PhysicsWorld world, float timeStep, float3 gravity, int numThreadsHint, JobHandle inputDeps)
        {
            Assert.AreEqual(world.NumDynamicBodies, m_DynamicTree.NumBodies);
            if (world.NumDynamicBodies == 0)
            {
                return(inputDeps);
            }

            var aabbs  = new NativeArray <Aabb>(world.NumDynamicBodies, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
            var points = new NativeArray <PointAndIndex>(world.NumDynamicBodies, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);

            JobHandle handle = new PrepareDynamicBodyDataJob
            {
                RigidBodies      = world.DynamicBodies,
                MotionVelocities = world.MotionVelocities,
                MotionDatas      = world.MotionDatas,
                Aabbs            = aabbs,
                Points           = points,
                FiltersOut       = m_DynamicTree.BodyFilters,
                AabbMargin       = world.CollisionWorld.CollisionTolerance * 0.5f, // each body contributes half
                TimeStep         = timeStep,
                Gravity          = gravity
            }.Schedule(world.NumDynamicBodies, 32, inputDeps);

            var shouldDoWork = new NativeArray <int>(1, Allocator.TempJob);

            shouldDoWork[0] = 1;

            handle = m_DynamicTree.BoundingVolumeHierarchy.ScheduleBuildJobs(
                points, aabbs, m_DynamicTree.BodyFilters, shouldDoWork, numThreadsHint, handle,
                m_DynamicTree.Nodes.Length, m_DynamicTree.Ranges, m_DynamicTree.BranchCount);

            return(shouldDoWork.Dispose(handle));
        }
        /// <summary>
        /// Build the dynamic tree of the broadphase based on the given array of rigid bodies and motions.
        /// </summary>
        public void BuildDynamicTree(NativeSlice <RigidBody> dynamicBodies, NativeSlice <MotionData> motionDatas,
                                     NativeSlice <MotionVelocity> motionVelocities, float3 gravity, float timeStep, float aabbMargin)
        {
            Assert.AreEqual(dynamicBodies.Length, m_DynamicTree.NumBodies);

            if (dynamicBodies.Length == 0)
            {
                return;
            }

            // Read bodies
            var aabbs  = new NativeArray <Aabb>(dynamicBodies.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
            var points = new NativeArray <PointAndIndex>(dynamicBodies.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);

            for (int i = 0; i < dynamicBodies.Length; i++)
            {
                PrepareDynamicBodyDataJob.ExecuteImpl(i, aabbMargin, gravity, timeStep, dynamicBodies, motionDatas, motionVelocities, aabbs, points, m_DynamicTree.BodyFilters);
            }

            // Build tree
            m_DynamicTree.BoundingVolumeHierarchy.Build(points, aabbs, out int nodeCount);

            // Build node filters
            m_DynamicTree.BoundingVolumeHierarchy.BuildCombinedCollisionFilter(m_DynamicTree.BodyFilters, 1, nodeCount - 1);
        }