/// <summary> /// Removes the specified node from the tree. /// </summary> public void RemoveNode(SphereTreeNode <T> node) { // If the node is not the root node and if it has a parent... if (!node.IsRoot && node.Parent != null) { // Remove the node from its parent SphereTreeNode <T> parentNode = node.Parent; parentNode.RemoveChild(node); // Move up the hierarhcy and remove all parents which don't have any children any more. // Note: We will always stop at the root node. The root node is allowed to exist even // if it has no children. while (parentNode.Parent != null && parentNode.HasNoChildren && !parentNode.IsRoot) { SphereTreeNode <T> newParent = parentNode.Parent; newParent.RemoveChild(parentNode); parentNode = newParent; } // At this point 'parentNode' references the deepest parent which has at least one child. // Because we have removed children from it, its volume must be recalculated, so we add // it to the recomputation queue. // Note: Even if this function was called from 'PerformPendingUpdates' we still get correct // results because the node will be added to the recomputation queue and it will be // processed inside the recomputation 'while' loop from where this method is called. AddNodeToRecomputationQueue(parentNode); } }
/// <summary> /// This is a recursive method which is responsible for integration the specified /// node inside the tree. /// </summary> private void IntegrateTerminalNodeRecurse(SphereTreeNode <T> nodeToIntegrate, SphereTreeNode <T> parentNode) { // If this node still has room for children, we will add the integration node here. This 'if' statement // will also handle the special case in which only the root node currently exists inside the tree. if (parentNode.NumberOfChildren < _numberOfChildNodesPerNode) { // Add the node as a child of the parent node and ensure that the root node encapsulates it parentNode.AddChild(nodeToIntegrate); parentNode.EncapsulateChildNode(nodeToIntegrate); } else { // If there is no more room, we will proceed by choosing one of the parent's children which // is closest to the node that we want to integrate. We choose the closest node because when // the node will be added as a child of it, we want the parent to grow as little as possible. List <SphereTreeNode <T> > children = parentNode.Children; SphereTreeNode <T> closestChild = FindClosestNode(children, nodeToIntegrate); if (closestChild == null) { return; } // If the closest child is not a terminal node, recurse. if (!closestChild.IsTerminal) { IntegrateTerminalNodeRecurse(nodeToIntegrate, closestChild); } else { // If we reached a terminal node, we create a new node which encapsulates 'closestChild' and 'nodeToIntegrate' // and then we replace 'closestChild' with this new node. The first step is to create the sphere of this // new node. It doesn't matter how big this sphere is initially because we will call 'EncapsulateChildNode' // later. Sphere newNodeSphere = closestChild.Sphere; // Create the node using the sphere we just calculated SphereTreeNode <T> newNode = new SphereTreeNode <T>(newNodeSphere, this, default(T)); newNode.SetFlag(SphereTreeNodeFlags.SuperSphere); // Replace 'closestChild' with the new node and add both 'closestChild' and 'nodeToIntegrate' as children of it parentNode.RemoveChild(closestChild); parentNode.AddChild(newNode); newNode.AddChild(nodeToIntegrate); newNode.AddChild(closestChild); // Encapsulate the children inside the new node newNode.EncapsulateChildNode(closestChild); newNode.EncapsulateChildNode(nodeToIntegrate); // Ensure that the new node is fully contained inside the parent node parentNode.EncapsulateChildNode(newNode); } } }
/// <summary> /// This method is called when the sphere of a terminal node has been updated. /// </summary> private void OnTerminalNodeSphereUpdated(SphereTreeNode <T> terminalNode) { // If the node is already marked for reintegration, there is nothing left for us to do if (terminalNode.MustIntegrate) { return; } // If the node is now outside of its parent, it may now be closer to another parent and associating // it with that new parent may provide better space/volume savings. So we remove the node from its // parent and add it to the integration queue so that it can be reintegrated. During the integration // process, the algorithm may find a more optimal parent or the same one if a more optimal one doesn't // exist. // Note: We are only removing the child from its parent if it went outside of its parent volume. It may // probably be a better idea to always check the surrounding parents and see if a more optimal one // exists even if the node doesn't pierce its parent's skin. For the moment however, we will only // remove the child from its parent if it pierced its skin. SphereTreeNode <T> parentNode = terminalNode.Parent; float distanceToParentNodeExitPoint = parentNode.GetDistanceToNodeExitPoint(terminalNode); if (distanceToParentNodeExitPoint > parentNode.Radius) { // Note: It may be a good idea to check if the node contains only one child after removal. In that // case the node itself can be removed and its child moved up the hierarchy. However, for the // moment we'll keep things simple. // Remove the child from its parent and add it to the integration queue. Adding it to // the integration queue is necessary in order to ensure that it gets reassigned to // the correct parrent based on its current position. parentNode.RemoveChild(terminalNode); AddNodeToIntegrationQueue(terminalNode); } // Whenever a terminal node is updated, it's parent must gave its volume recomputed. We always do this // regardless of whether or not the node was removed from the parent or not. AddNodeToRecomputationQueue(parentNode); }