unsafe void SplitSubtreesIntoChildrenBinned(ref BinnedResources resources,
                                                    int start, int count,
                                                    int stagingNodeIndex, ref int stagingNodesCount, out float childrenTreeletsCost)
        {
            Debug.Assert(count > 2);
            FindPartitionBinned(ref resources, start, count, out int splitIndex, out BoundingBox aBounds, out BoundingBox bBounds, out int leafCountA, out int leafCountB);

            //Recursion bottomed out.
            var stagingNode = resources.StagingNodes + stagingNodeIndex;

            var     stagingChildren = &stagingNode->A;
            ref var a = ref stagingNode->A;
        public static unsafe void CreateBinnedResources(BufferPool bufferPool, int maximumSubtreeCount, out RawBuffer buffer, out BinnedResources resources)
        {
            //TODO: This is a holdover from the pre-BufferPool tree design. It's pretty ugly. While some preallocation is useful (there's no reason to suffer the overhead of
            //pulling things out of the BufferPool over and over and over again), the degree to which this preallocates has a negative impact on L1 cache for subtree refines.
            int nodeCount     = maximumSubtreeCount - 1;
            int bytesRequired =
                16 * (3 + 3 + 1) + sizeof(BoundingBox) * (maximumSubtreeCount + 3 * nodeCount + 3 * MaximumBinCount) +
                16 * (6 + 3 + 8) + sizeof(int) * (maximumSubtreeCount * 6 + nodeCount * 3 + MaximumBinCount * 8) +
                16 * (1) + sizeof(Vector3) * maximumSubtreeCount +
                16 * (1) + sizeof(SubtreeHeapEntry) * maximumSubtreeCount +
                16 * (1) + sizeof(Node) * nodeCount +
                16 * (1) + sizeof(int) * nodeCount;

            bufferPool.TakeAtLeast(bytesRequired, out buffer);
            var memory          = buffer.Memory;
            int memoryAllocated = 0;

            resources.BoundingBoxes      = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * maximumSubtreeCount);
            resources.LeafCounts         = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);
            resources.IndexMap           = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);
            resources.Centroids          = (Vector3 *)Suballocate(memory, ref memoryAllocated, sizeof(Vector3) * maximumSubtreeCount);
            resources.SubtreeHeapEntries = (SubtreeHeapEntry *)Suballocate(memory, ref memoryAllocated, sizeof(SubtreeHeapEntry) * maximumSubtreeCount);
            resources.StagingNodes       = (Node *)Suballocate(memory, ref memoryAllocated, sizeof(Node) * nodeCount);
            resources.RefineFlags        = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount);

            resources.SubtreeBinIndicesX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);
            resources.SubtreeBinIndicesY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);
            resources.SubtreeBinIndicesZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);
            resources.TempIndexMap       = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * maximumSubtreeCount);

            resources.ALeafCountsX = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount);
            resources.ALeafCountsY = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount);
            resources.ALeafCountsZ = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * nodeCount);
            resources.AMergedX     = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount);
            resources.AMergedY     = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount);
            resources.AMergedZ     = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * nodeCount);

            resources.BinBoundingBoxesX          = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount);
            resources.BinBoundingBoxesY          = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount);
            resources.BinBoundingBoxesZ          = (BoundingBox *)Suballocate(memory, ref memoryAllocated, sizeof(BoundingBox) * MaximumBinCount);
            resources.BinLeafCountsX             = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinLeafCountsY             = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinLeafCountsZ             = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinSubtreeCountsX          = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinSubtreeCountsY          = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinSubtreeCountsZ          = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinStartIndices            = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);
            resources.BinSubtreeCountsSecondPass = (int *)Suballocate(memory, ref memoryAllocated, sizeof(int) * MaximumBinCount);

            Debug.Assert(memoryAllocated <= buffer.Length, "The allocated buffer should be large enough for all the suballocations.");
        }
        unsafe void FindPartitionBinned(ref BinnedResources resources, int start, int count,
                                        out int splitIndex, out BoundingBox a, out BoundingBox b, out int leafCountA, out int leafCountB)
        {
            //Initialize the per-axis candidate maps.
            var         localIndexMap = resources.IndexMap + start;
            BoundingBox centroidBoundingBox;

            centroidBoundingBox.Min = resources.Centroids[localIndexMap[0]];
            centroidBoundingBox.Max = centroidBoundingBox.Min;

            for (int i = 1; i < count; ++i)
            {
                var centroid = resources.Centroids + localIndexMap[i];
                centroidBoundingBox.Min = Vector3.Min(*centroid, centroidBoundingBox.Min);
                centroidBoundingBox.Max = Vector3.Max(*centroid, centroidBoundingBox.Max);
            }

            //Bin along all three axes simultaneously.
            var nullBoundingBox = new BoundingBox {
                Min = new Vector3(float.MaxValue), Max = new Vector3(float.MinValue)
            };
            var         span    = centroidBoundingBox.Max - centroidBoundingBox.Min;
            const float epsilon = 1e-12f;

            if (span.X < epsilon && span.Y < epsilon && span.Z < epsilon)
            {
                //All axes are degenerate.
                //This is the one situation in which we can end up with all objects in the same bin.
                //To stop this, just short circuit.
                splitIndex = count / 2;
                a          = nullBoundingBox;
                b          = nullBoundingBox;
                leafCountA = 0;
                leafCountB = 0;
                for (int i = 0; i < splitIndex; ++i)
                {
                    BoundingBox.CreateMerged(a, resources.BoundingBoxes[localIndexMap[i]], out a);
                    leafCountA += resources.LeafCounts[localIndexMap[i]];
                }
                for (int i = splitIndex; i < count; ++i)
                {
                    BoundingBox.CreateMerged(b, resources.BoundingBoxes[localIndexMap[i]], out b);
                    leafCountB += resources.LeafCounts[localIndexMap[i]];
                }
                splitIndex += start;
                return;
            }


            //There is no real value in having tons of bins when there are very few children.
            //At low counts, many of them even end up empty.
            //You can get huge speed boosts by simply dropping the bin count adaptively.
            var binCount = (int)Math.Min(MaximumBinCount, Math.Max(count * .25f, 2));

            //Take into account zero-width cases.
            //This will result in degenerate axes all being dumped into the first bin.
            var inverseBinSize = new Vector3(
                span.X > epsilon ? binCount / span.X : 0,
                span.Y > epsilon ? binCount / span.Y : 0,
                span.Z > epsilon ? binCount / span.Z : 0);
            //inverseBinSize = new Vector3(inverseBinSize.X, inverseBinSize.Y, inverseBinSize.Z);

            //If the span along an axis is too small, just ignore it.
            var maximumBinIndex = new Vector3(binCount - 1);

            //Initialize bin information.
            for (int i = 0; i < binCount; ++i)
            {
                resources.BinBoundingBoxesX[i] = nullBoundingBox;
                resources.BinBoundingBoxesY[i] = nullBoundingBox;
                resources.BinBoundingBoxesZ[i] = nullBoundingBox;

                resources.BinSubtreeCountsX[i] = 0;
                resources.BinSubtreeCountsY[i] = 0;
                resources.BinSubtreeCountsZ[i] = 0;

                resources.BinLeafCountsX[i] = 0;
                resources.BinLeafCountsY[i] = 0;
                resources.BinLeafCountsZ[i] = 0;
            }

            //var startAllocateToBins = Stopwatch.GetTimestamp();
            //Allocate subtrees to bins for all axes simultaneously.
            for (int i = 0; i < count; ++i)
            {
                var subtreeIndex = localIndexMap[i];
                var binIndices   = Vector3.Min((resources.Centroids[subtreeIndex] - centroidBoundingBox.Min) * inverseBinSize, maximumBinIndex);
                var x            = (int)binIndices.X;
                var y            = (int)binIndices.Y;
                var z            = (int)binIndices.Z;

                resources.SubtreeBinIndicesX[i] = x;
                resources.SubtreeBinIndicesY[i] = y;
                resources.SubtreeBinIndicesZ[i] = z;

                var leafCount          = resources.LeafCounts + subtreeIndex;
                var subtreeBoundingBox = resources.BoundingBoxes + subtreeIndex;

                resources.BinLeafCountsX[x] += *leafCount;
                resources.BinLeafCountsY[y] += *leafCount;
                resources.BinLeafCountsZ[z] += *leafCount;

                ++resources.BinSubtreeCountsX[x];
                ++resources.BinSubtreeCountsY[y];
                ++resources.BinSubtreeCountsZ[z];

                BoundingBox.CreateMerged(resources.BinBoundingBoxesX[x], *subtreeBoundingBox, out resources.BinBoundingBoxesX[x]);
                BoundingBox.CreateMerged(resources.BinBoundingBoxesY[y], *subtreeBoundingBox, out resources.BinBoundingBoxesY[y]);
                BoundingBox.CreateMerged(resources.BinBoundingBoxesZ[z], *subtreeBoundingBox, out resources.BinBoundingBoxesZ[z]);
            }

            //Determine split axes for all axes simultaneously.
            //Sweep from low to high.
            var lastIndex = binCount - 1;

            resources.ALeafCountsX[0] = resources.BinLeafCountsX[0];
            resources.ALeafCountsY[0] = resources.BinLeafCountsY[0];
            resources.ALeafCountsZ[0] = resources.BinLeafCountsZ[0];
            resources.AMergedX[0]     = resources.BinBoundingBoxesX[0];
            resources.AMergedY[0]     = resources.BinBoundingBoxesY[0];
            resources.AMergedZ[0]     = resources.BinBoundingBoxesZ[0];
            for (int i = 1; i < lastIndex; ++i)
            {
                var previousIndex = i - 1;
                resources.ALeafCountsX[i] = resources.BinLeafCountsX[i] + resources.ALeafCountsX[previousIndex];
                resources.ALeafCountsY[i] = resources.BinLeafCountsY[i] + resources.ALeafCountsY[previousIndex];
                resources.ALeafCountsZ[i] = resources.BinLeafCountsZ[i] + resources.ALeafCountsZ[previousIndex];
                BoundingBox.CreateMerged(resources.AMergedX[previousIndex], resources.BinBoundingBoxesX[i], out resources.AMergedX[i]);
                BoundingBox.CreateMerged(resources.AMergedY[previousIndex], resources.BinBoundingBoxesY[i], out resources.AMergedY[i]);
                BoundingBox.CreateMerged(resources.AMergedZ[previousIndex], resources.BinBoundingBoxesZ[i], out resources.AMergedZ[i]);
            }

            //Sweep from high to low.
            BoundingBox bMergedX    = nullBoundingBox;
            BoundingBox bMergedY    = nullBoundingBox;
            BoundingBox bMergedZ    = nullBoundingBox;
            int         bLeafCountX = 0;
            int         bLeafCountY = 0;
            int         bLeafCountZ = 0;

            int   bestAxis      = 0;
            float cost          = float.MaxValue;
            var   binSplitIndex = 0;

            a          = nullBoundingBox;
            b          = nullBoundingBox;
            leafCountA = 0;
            leafCountB = 0;


            for (int i = lastIndex; i >= 1; --i)
            {
                int aIndex = i - 1;
                BoundingBox.CreateMerged(bMergedX, resources.BinBoundingBoxesX[i], out bMergedX);
                BoundingBox.CreateMerged(bMergedY, resources.BinBoundingBoxesY[i], out bMergedY);
                BoundingBox.CreateMerged(bMergedZ, resources.BinBoundingBoxesZ[i], out bMergedZ);
                bLeafCountX += resources.BinLeafCountsX[i];
                bLeafCountY += resources.BinLeafCountsY[i];
                bLeafCountZ += resources.BinLeafCountsZ[i];


                //It's possible for a lot of bins in a row to be unpopulated. In that event, the metric isn't defined; don't bother calculating it.
                float costCandidateX, costCandidateY, costCandidateZ;
                if (bLeafCountX > 0 && resources.ALeafCountsX[aIndex] > 0)
                {
                    var metricAX = ComputeBoundsMetric(ref resources.AMergedX[aIndex]);
                    var metricBX = ComputeBoundsMetric(ref bMergedX);
                    costCandidateX = resources.ALeafCountsX[aIndex] * metricAX + bLeafCountX * metricBX;
                }
                else
                {
                    costCandidateX = float.MaxValue;
                }
                if (bLeafCountY > 0 && resources.ALeafCountsY[aIndex] > 0)
                {
                    var metricAY = ComputeBoundsMetric(ref resources.AMergedY[aIndex]);
                    var metricBY = ComputeBoundsMetric(ref bMergedY);
                    costCandidateY = resources.ALeafCountsY[aIndex] * metricAY + bLeafCountY * metricBY;
                }
                else
                {
                    costCandidateY = float.MaxValue;
                }
                if (bLeafCountZ > 0 && resources.ALeafCountsZ[aIndex] > 0)
                {
                    var metricAZ = ComputeBoundsMetric(ref resources.AMergedZ[aIndex]);
                    var metricBZ = ComputeBoundsMetric(ref bMergedZ);
                    costCandidateZ = resources.ALeafCountsZ[aIndex] * metricAZ + bLeafCountZ * metricBZ;
                }
                else
                {
                    costCandidateZ = float.MaxValue;
                }
                if (costCandidateX < costCandidateY && costCandidateX < costCandidateZ)
                {
                    if (costCandidateX < cost)
                    {
                        bestAxis      = 0;
                        cost          = costCandidateX;
                        binSplitIndex = i;
                        a             = resources.AMergedX[aIndex];
                        b             = bMergedX;
                        leafCountA    = resources.ALeafCountsX[aIndex];
                        leafCountB    = bLeafCountX;
                    }
                }
                else if (costCandidateY < costCandidateZ)
                {
                    if (costCandidateY < cost)
                    {
                        bestAxis      = 1;
                        cost          = costCandidateY;
                        binSplitIndex = i;
                        a             = resources.AMergedY[aIndex];
                        b             = bMergedY;
                        leafCountA    = resources.ALeafCountsY[aIndex];
                        leafCountB    = bLeafCountY;
                    }
                }
                else
                {
                    if (costCandidateZ < cost)
                    {
                        bestAxis      = 2;
                        cost          = costCandidateZ;
                        binSplitIndex = i;
                        a             = resources.AMergedZ[aIndex];
                        b             = bMergedZ;
                        leafCountA    = resources.ALeafCountsZ[aIndex];
                        leafCountB    = bLeafCountZ;
                    }
                }
            }


            int *bestBinSubtreeCounts;
            int *bestSubtreeBinIndices;

            switch (bestAxis)
            {
            case 0:
                bestBinSubtreeCounts  = resources.BinSubtreeCountsX;
                bestSubtreeBinIndices = resources.SubtreeBinIndicesX;
                break;

            case 1:
                bestBinSubtreeCounts  = resources.BinSubtreeCountsY;
                bestSubtreeBinIndices = resources.SubtreeBinIndicesY;
                break;

            default:
                bestBinSubtreeCounts  = resources.BinSubtreeCountsZ;
                bestSubtreeBinIndices = resources.SubtreeBinIndicesZ;
                break;
            }
            //Rebuild the index map.

            resources.BinStartIndices[0]            = 0;
            resources.BinSubtreeCountsSecondPass[0] = 0;

            for (int i = 1; i < binCount; ++i)
            {
                resources.BinStartIndices[i]            = resources.BinStartIndices[i - 1] + bestBinSubtreeCounts[i - 1];
                resources.BinSubtreeCountsSecondPass[i] = 0;
            }

            //var startIndexMapTime = Stopwatch.GetTimestamp();

            for (int i = 0; i < count; ++i)
            {
                var index = bestSubtreeBinIndices[i];
                resources.TempIndexMap[resources.BinStartIndices[index] + resources.BinSubtreeCountsSecondPass[index]++] = localIndexMap[i];
            }

            //Update the real index map.
            for (int i = 0; i < count; ++i)
            {
                localIndexMap[i] = resources.TempIndexMap[i];
            }
            //Transform the split index into object indices.
            splitIndex = resources.BinStartIndices[binSplitIndex] + start;
        }