/// <summary> /// Creates a cache optimized copy of the tree that uses the minimum amount of memory possible. /// </summary> /// <returns>Cache and memory optimized copy of the tree.</returns> public unsafe Tree CreateOptimized() { var optimizedLeavesArray = new Leaf[leafCount]; var optimizedNodesArray = new Node[nodeCount]; CreateOptimized(optimizedLeavesArray, optimizedNodesArray); return new Tree(optimizedLeavesArray, optimizedNodesArray); }
/// <summary> /// Creates a cache optimized version of the tree in the given leaves and nodes arrays. /// </summary> /// <param name="optimizedLeavesArray">Array to fill with optimized leaves.</param> /// <param name="optimizedNodesArray">Array to fill with optimized nodes.</param> public unsafe void CreateOptimized(Leaf[] optimizedLeavesArray, Node[] optimizedNodesArray) { if (optimizedLeavesArray.Length < LeafCount) throw new ArgumentException("Leaves array must be able to contain all leaves in this tree."); if (optimizedLeavesArray.Length < nodeCount) throw new ArgumentException("Nodes array must be able to contain all nodes in this tree."); fixed (Leaf* optimizedLeaves = optimizedLeavesArray) fixed (Node* optimizedNodes = optimizedNodesArray) { int optimizedNodeCount = 0; int optimizedLeafCount = 0; OptimizeDFS(-1, 0, optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); //OptimizeGroupDFS(optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); //OptimizeBFS(optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); Debug.Assert(optimizedNodeCount == nodeCount); Debug.Assert(optimizedLeafCount == leafCount); } }
unsafe void OptimizeBFS(Node* optimizedNodes, Leaf* optimizedLeaves, ref int optimizedNodeCount, ref int optimizedLeafCount) { var nodesToVisit = new Queue<NodeToVisit>(); nodesToVisit.Enqueue(new NodeToVisit { NodeIndex = 0, OptimizedIndex = 0, OptimizedParentIndex = -1 }); optimizedNodeCount = 1; while (nodesToVisit.Count > 0) { var nodeToVisit = nodesToVisit.Dequeue(); var node = nodes + nodeToVisit.NodeIndex; var optimizedNodeIndex = nodeToVisit.OptimizedIndex; var optimizedNode = optimizedNodes + optimizedNodeIndex; *optimizedNode = *node; optimizedNode->Parent = nodeToVisit.OptimizedParentIndex; var optimizedChildren = &optimizedNode->ChildA; var nodeChildren = &node->ChildA; for (int i = 0; i < node->ChildCount; ++i) { if (nodeChildren[i] >= 0) { optimizedChildren[i] = optimizedNodeCount++; nodesToVisit.Enqueue(new NodeToVisit { NodeIndex = nodeChildren[i], OptimizedIndex = optimizedChildren[i], OptimizedParentIndex = optimizedNodeIndex }); } else { var leafIndex = Encode(nodeChildren[i]); var optimizedLeafIndex = optimizedLeafCount++; var optimizedLeaf = optimizedLeaves + optimizedLeafIndex; optimizedLeaf->Id = leaves[leafIndex].Id; optimizedLeaf->NodeIndex = optimizedNodeIndex; optimizedLeaf->ChildIndex = i; optimizedChildren[i] = Encode(optimizedLeafIndex); } } } }
public unsafe LeafMove RemoveAt(int leafIndex) { if (leafIndex < 0 || leafIndex >= leafCount) throw new ArgumentOutOfRangeException("Leaf index must be a valid index in the tree's leaf array."); //Cache the leaf before overwriting. var leaf = leaves[leafIndex]; //Delete the leaf from the leaves array. //This is done up front to make it easier for tests to catch bad behavior. var lastIndex = leafCount - 1; if (lastIndex != leafIndex) { //The removed leaf was in the middle of the leaves array. Take the last leaf and use it to fill the slot. //The node owner's index must be updated to point to the new location. var lastLeafOwner = nodes + leaves[lastIndex].NodeIndex; (&lastLeafOwner->ChildA)[leaves[lastIndex].ChildIndex] = Encode(leafIndex); leaves[leafIndex] = leaves[lastIndex]; } leaves[lastIndex] = new Leaf(); leafCount = lastIndex; var node = nodes + leaf.NodeIndex; var nodeChildren = &node->ChildA; var nodeBounds = &node->A; var nodeLeafCounts = &node->LeafCountA; //Remove the leaf from this node. //Note that the children must remain contiguous. Requires taking the last child of the node and moving into the slot //if the removed child was not the last child. //Further, if a child is moved and if it is a leaf, that leaf's ChildIndex must be updated. //If a child is moved and it is an internal node, all immediate children of that node must have their parent nodes updated. //Check to see if this node should collapse. if (node->ChildCount == 2 && node->Parent >= 0) //The root cannot 'collapse'. The root is the only node that can end up with 1 child. { Debug.Assert(node->ChildCount != 1); //If there are only two children in the node, then the node containing the removed leaf will collapse. var otherIndex = leaf.ChildIndex == 0 ? 1 : 0; var otherChildIndex = nodeChildren[otherIndex]; //Move the other node into the slot that used to point to the collapsing internal node. var parentNode = nodes + node->Parent; (&parentNode->A)[node->IndexInParent] = nodeBounds[otherIndex]; (&parentNode->ChildA)[node->IndexInParent] = otherChildIndex; (&parentNode->LeafCountA)[node->IndexInParent] = nodeLeafCounts[otherIndex]; if (otherChildIndex < 0) { //It's a leaf. Update the leaf's reference in the leaves array. var otherLeafIndex = Encode(otherChildIndex); leaves[otherLeafIndex].NodeIndex = node->Parent; leaves[otherLeafIndex].ChildIndex = node->IndexInParent; } else { //It's an internal node. Update its parent node. nodes[otherChildIndex].Parent = node->Parent; nodes[otherChildIndex].IndexInParent = node->IndexInParent; } //Remove the now dead node. RemoveNodeAt(leaf.NodeIndex); //Work up the chain of parent pointers, refitting bounding boxes and decrementing leaf counts. //Note that this starts at the parent; we've already done the refit for the current level via collapse. RefitForRemoval(parentNode); } else { //The node has enough children that it should not collapse or it's the root; just need to remove the leaf. var lastChildIndex = node->ChildCount - 1; if (leaf.ChildIndex < lastChildIndex) { //The removed leaf is not the last child of the node it belongs to. //Move the last node into the position of the removed node. nodeBounds[leaf.ChildIndex] = nodeBounds[lastChildIndex]; nodeChildren[leaf.ChildIndex] = nodeChildren[lastChildIndex]; nodeLeafCounts[leaf.ChildIndex] = nodeLeafCounts[lastChildIndex]; if (nodeChildren[lastChildIndex] >= 0) { //The moved child is an internal node. //Update the child's IndexInParent pointer. var movedInternalNode = nodes + nodeChildren[lastChildIndex]; movedInternalNode->IndexInParent = leaf.ChildIndex; } else { //The moved node is a leaf, so update the leaf array's reference. leaves[Encode(nodeChildren[lastChildIndex])].ChildIndex = leaf.ChildIndex; } } //Clear out the last slot. (&node->A)[lastChildIndex] = new BoundingBox { Min = new Vector3(float.MaxValue), Max = new Vector3(float.MinValue) }; (&node->ChildA)[lastChildIndex] = -1; (&node->LeafCountA)[lastChildIndex] = 0; --node->ChildCount; //Work up the chain of parent pointers, refitting bounding boxes and decrementing leaf counts. RefitForRemoval(node); } return new LeafMove { OriginalIndex = lastIndex, NewIndex = leafIndex }; }
unsafe int OptimizeDFS(int optimizedParentIndex, int nodeIndex, Node* optimizedNodes, Leaf* optimizedLeaves, ref int optimizedNodeCount, ref int optimizedLeafCount) { var node = nodes + nodeIndex; var optimizedNodeIndex = optimizedNodeCount++; var optimizedNode = optimizedNodes + optimizedNodeIndex; *optimizedNode = *node; optimizedNode->Parent = optimizedParentIndex; var children = &optimizedNode->ChildA; for (int i = 0; i < node->ChildCount; ++i) { if (children[i] >= 0) { children[i] = OptimizeDFS(optimizedNodeIndex, children[i], optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); } else { var leafIndex = Encode(children[i]); var optimizedLeafIndex = optimizedLeafCount++; var optimizedLeaf = optimizedLeaves + optimizedLeafIndex; optimizedLeaf->Id = leaves[leafIndex].Id; optimizedLeaf->NodeIndex = optimizedNodeIndex; optimizedLeaf->ChildIndex = i; children[i] = Encode(optimizedLeafIndex); } } return optimizedNodeIndex; }
unsafe void OptimizeGroupDFS(int optimizedNodeIndex, int optimizedParentNodeIndex, int nodeIndex, Node* optimizedNodes, Leaf* optimizedLeaves, ref int optimizedNodeCount, ref int optimizedLeafCount) { var node = nodes + nodeIndex; var optimizedNode = optimizedNodes + optimizedNodeIndex; *optimizedNode = *node; optimizedNode->Parent = optimizedParentNodeIndex; var optimizedChildren = &optimizedNode->ChildA; var nodeChildren = &node->ChildA; for (int i = 0; i < node->ChildCount; ++i) { if (nodeChildren[i] >= 0) { optimizedChildren[i] = optimizedNodeCount++; } else { var leafIndex = Encode(nodeChildren[i]); var optimizedLeafIndex = optimizedLeafCount++; var optimizedLeaf = optimizedLeaves + optimizedLeafIndex; optimizedLeaf->Id = leaves[leafIndex].Id; optimizedLeaf->NodeIndex = optimizedNodeIndex; optimizedLeaf->ChildIndex = i; optimizedChildren[i] = Encode(optimizedLeafIndex); } } for (int i = 0; i < node->ChildCount; ++i) { if (nodeChildren[i] >= 0) { OptimizeGroupDFS(optimizedChildren[i], optimizedNodeIndex, nodeChildren[i], optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); } } }
unsafe void OptimizeGroupDFS(Node* optimizedNodes, Leaf* optimizedLeaves, ref int optimizedNodeCount, ref int optimizedLeafCount) { optimizedNodeCount = 1; OptimizeGroupDFS(0, -1, 0, optimizedNodes, optimizedLeaves, ref optimizedNodeCount, ref optimizedLeafCount); }
public unsafe LeafMove RemoveAt(int leafIndex) { if (leafIndex < 0 || leafIndex >= leafCount) { throw new ArgumentOutOfRangeException("Leaf index must be a valid index in the tree's leaf array."); } //Cache the leaf before overwriting. var leaf = leaves[leafIndex]; //Delete the leaf from the leaves array. //This is done up front to make it easier for tests to catch bad behavior. var lastIndex = leafCount - 1; if (lastIndex != leafIndex) { //The removed leaf was in the middle of the leaves array. Take the last leaf and use it to fill the slot. //The node owner's index must be updated to point to the new location. var lastLeafOwner = nodes + leaves[lastIndex].NodeIndex; (&lastLeafOwner->ChildA)[leaves[lastIndex].ChildIndex] = Encode(leafIndex); leaves[leafIndex] = leaves[lastIndex]; } leaves[lastIndex] = new Leaf(); leafCount = lastIndex; var node = nodes + leaf.NodeIndex; var nodeChildren = &node->ChildA; var nodeBounds = &node->A; var nodeLeafCounts = &node->LeafCountA; //Remove the leaf from this node. //Note that the children must remain contiguous. Requires taking the last child of the node and moving into the slot //if the removed child was not the last child. //Further, if a child is moved and if it is a leaf, that leaf's ChildIndex must be updated. //If a child is moved and it is an internal node, all immediate children of that node must have their parent nodes updated. //Check to see if this node should collapse. if (node->ChildCount == 2 && node->Parent >= 0) //The root cannot 'collapse'. The root is the only node that can end up with 1 child. { Debug.Assert(node->ChildCount != 1); //If there are only two children in the node, then the node containing the removed leaf will collapse. var otherIndex = leaf.ChildIndex == 0 ? 1 : 0; var otherChildIndex = nodeChildren[otherIndex]; //Move the other node into the slot that used to point to the collapsing internal node. var parentNode = nodes + node->Parent; (&parentNode->A)[node->IndexInParent] = nodeBounds[otherIndex]; (&parentNode->ChildA)[node->IndexInParent] = otherChildIndex; (&parentNode->LeafCountA)[node->IndexInParent] = nodeLeafCounts[otherIndex]; if (otherChildIndex < 0) { //It's a leaf. Update the leaf's reference in the leaves array. var otherLeafIndex = Encode(otherChildIndex); leaves[otherLeafIndex].NodeIndex = node->Parent; leaves[otherLeafIndex].ChildIndex = node->IndexInParent; } else { //It's an internal node. Update its parent node. nodes[otherChildIndex].Parent = node->Parent; nodes[otherChildIndex].IndexInParent = node->IndexInParent; } //Remove the now dead node. RemoveNodeAt(leaf.NodeIndex); //Work up the chain of parent pointers, refitting bounding boxes and decrementing leaf counts. //Note that this starts at the parent; we've already done the refit for the current level via collapse. RefitForRemoval(parentNode); } else { //The node has enough children that it should not collapse or it's the root; just need to remove the leaf. var lastChildIndex = node->ChildCount - 1; if (leaf.ChildIndex < lastChildIndex) { //The removed leaf is not the last child of the node it belongs to. //Move the last node into the position of the removed node. nodeBounds[leaf.ChildIndex] = nodeBounds[lastChildIndex]; nodeChildren[leaf.ChildIndex] = nodeChildren[lastChildIndex]; nodeLeafCounts[leaf.ChildIndex] = nodeLeafCounts[lastChildIndex]; if (nodeChildren[lastChildIndex] >= 0) { //The moved child is an internal node. //Update the child's IndexInParent pointer. var movedInternalNode = nodes + nodeChildren[lastChildIndex]; movedInternalNode->IndexInParent = leaf.ChildIndex; } else { //The moved node is a leaf, so update the leaf array's reference. leaves[Encode(nodeChildren[lastChildIndex])].ChildIndex = leaf.ChildIndex; } } //Clear out the last slot. (&node->A)[lastChildIndex] = new BoundingBox { Min = new Vector3(float.MaxValue), Max = new Vector3(float.MinValue) }; (&node->ChildA)[lastChildIndex] = -1; (&node->LeafCountA)[lastChildIndex] = 0; --node->ChildCount; //Work up the chain of parent pointers, refitting bounding boxes and decrementing leaf counts. RefitForRemoval(node); } return(new LeafMove { OriginalIndex = lastIndex, NewIndex = leafIndex }); }