/// <summary> /// Sets the node's parent. This call is ignored if the specified parent /// is the node itself or if it's the same as the current parent. /// </summary> public void SetParent(SphereTreeNode <T> newParent) { // Ignore parent? if (newParent == this || newParent == _parent) { return; } // If we already have a parent, detach the node from it if (_parent != null) { _parent._children.Remove(this); _parent = null; } // If the new node is not null, attach the node to this new parent if (newParent != null) { _parent = newParent; _parent._children.Add(this); } else { _parent = null; } }
/// <summary> /// This method will recalculate the node's center and radius /// so that it encapsulates all children. This is a recursive /// call which propagates up the hierarchy towards the root. /// </summary> public void EncapsulateChildrenBottomUp() { // Nothing to do if the node doesn't have any children if (NumChildren != 0) { SphereTreeNode <T> parent = this; while (parent != null) { // First, we will calculate the new sphere center as the average // of all child node centers. Vector3 centerSum = Vector3.zero; foreach (SphereTreeNode <T> child in parent._children) { centerSum += child.Center; } parent.Center = centerSum * (1.0f / parent.NumChildren); // Now we will calculate the radius which the node must have so that // it can encapsulate all its children. float maxRadius = float.MinValue; foreach (SphereTreeNode <T> child in parent._children) { float distToExitPt = (child.Center - parent._sphere.Center).magnitude + child.Radius; if (distToExitPt > maxRadius) { maxRadius = distToExitPt; } } parent.Radius = maxRadius; parent = parent.Parent; } } }
/// <summary> /// Removes the specified node from the tree. The client code should only /// ever call this method on nodes returned from 'AddNode'. /// </summary> public void RemoveNode(SphereTreeNode <T> node) { // The root node can never be removed if (node.IsFlagBitSet(BVHNodeFlags.Root)) { return; } // Keep moving up the hierarchy and remove all nodes which don't // have any child nodes any more. There's no point in keeping these // around as they're nothing but noise inside the tree. SphereTreeNode <T> parent = node.Parent; node.SetParent(null); while (parent != null && parent.NumChildren == 0 && !parent.IsFlagBitSet(BVHNodeFlags.Root)) { SphereTreeNode <T> newParent = parent.Parent; parent.SetParent(null); parent = newParent; } // We have been removing nodes, so we need to make sure that the parent // at which we stopped the removal process has its volume recalculated. parent.EncapsulateChildrenBottomUp(); }
/// <summary> /// Finds the child which is closest to 'node'. /// </summary> /// <returns> /// The child closest to 'node' or nul if the no children /// are available. /// </returns> public SphereTreeNode <T> ClosestChild(SphereTreeNode <T> node) { // No children? if (NumChildren == 0) { return(null); } // Loop through each child int numChildren = _children.Count; float minDistSq = float.MaxValue; SphereTreeNode <T> closestChild = null; for (int childIndex = 0; childIndex < numChildren; ++childIndex) { SphereTreeNode <T> child = _children[childIndex]; // Closer than what we have so far? float distSq = (node.Center - child.Center).sqrMagnitude; if (distSq < minDistSq) { minDistSq = distSq; closestChild = child; } } // Return the closest child return(closestChild); }
/// <summary> /// Recursive method which moves down the hierarchy performing overlap tests /// against 'box'. At the end of the recursive chain, all terminal nodes which /// are overlapped by 'box' will be stored in 'overlappedNodes'. /// </summary> private void OverlapBoxRecurse(OBB box, SphereTreeNode <T> node, List <SphereTreeNode <T> > overlappedNodes) { // If the parent node is not overlapped, its children can not possibly // be overlapped so thre is no need to go any further. if (!box.IntersectsSphere(node.Sphere)) { return; } else { // If this is a terminal node, add it to the output list and return if (node.IsFlagBitSet(BVHNodeFlags.Terminal)) { overlappedNodes.Add(node); return; } else { // Recurse for each child node List <SphereTreeNode <T> > childNodes = node.Children; foreach (SphereTreeNode <T> childNode in childNodes) { OverlapBoxRecurse(box, childNode, overlappedNodes); } } } }
/// <summary> /// Recursive method which casts a ray against 'node' and moves down the /// hierarchy towards the terminal nodes and stores any hits between the /// ray and these nodes inside 'hitList'. /// </summary> private void RaycastAllRecurse(Ray ray, SphereTreeNode <T> node, List <SphereTreeNodeRayHit <T> > hitList) { // Is this a terminal node? if (!node.IsFlagBitSet(BVHNodeFlags.Terminal)) { // This is not a terminal node. We will check if the ray intersects the node's // sphere and if it does, we will go further down the hierarchy. If it doesn't // then there is no need to go on because if the ray does not intersect the node, // it can't possibly intersect any of its children. if (SphereMath.Raycast(ray, node.Center, node.Radius)) { List <SphereTreeNode <T> > children = node.Children; foreach (var child in children) { RaycastAllRecurse(ray, child, hitList); } } } else { // This is a terminal node and if the ray intersects this node, we will add the // node hit information inside the hit list. float t; if (SphereMath.Raycast(ray, out t, node.Center, node.Radius)) { var nodeHit = new SphereTreeNodeRayHit <T>(ray, node, t); hitList.Add(nodeHit); } } }
/// <summary> /// Must be called whenever a node's bounding sphere has changed. /// </summary> public void OnNodeSphereUpdated(SphereTreeNode <T> node) { // Just make sure this is a terminal node. Otherwise, ignore. if (!node.IsFlagBitSet(BVHNodeFlags.Terminal)) { return; } // Check if the node is now outside of its parent if (node.IsOutsideParent()) { // The node is outside of its parent. In this case, the first step // is to detach it from its parent. SphereTreeNode <T> parent = node.Parent; node.SetParent(null); // Now if the parent no longer has any children, we remove it from the // tree. Otherwise, we make sure it properly encapsulates its children. if (parent.NumChildren == 0) { RemoveNode(parent); } else { parent.EncapsulateChildrenBottomUp(); } // The node needs to be reintegrated inside the tree IntegrateNodeRecurse(node, _root); } }
/// <summary> /// Finds the child which is closest to 'node'. /// </summary> /// <returns> /// The child closest to 'node' or nul if the no children /// are available. /// </returns> public SphereTreeNode <T> ClosestChild(SphereTreeNode <T> node) { // No children? if (NumChildren == 0) { return(null); } // Loop through each child float minDistSq = float.MaxValue; SphereTreeNode <T> closestChild = null; foreach (var child in _children) { // Closer than what we have so far? float distSq = (node.Center - child.Center).sqrMagnitude; if (distSq < minDistSq) { minDistSq = distSq; closestChild = child; } } // Return the closest child return(closestChild); }
/// <summary> /// Adds a new node to the tree and returns it to the caller. /// </summary> /// <param name="nodeData"> /// The data associated with the node. /// </param> /// <param name="sphere"> /// The node's bounding sphere. /// </param> public SphereTreeNode <T> AddNode(T nodeData, Sphere sphere) { // Create the node instance and feed it to the 'AddNodeRecurse' method // to integrate it inside the tree properly. var newNode = new SphereTreeNode <T>(nodeData, sphere); IntegrateNodeRecurse(newNode, _root); return(newNode); }
/// <summary> /// Constructor. /// </summary> public SphereTree(int numChildrenPerNode) { // Store the number of children per node and clamp _numChildrenPerNode = numChildrenPerNode; if (_numChildrenPerNode < 2) { _numChildrenPerNode = 2; } // Create the root node _root = new SphereTreeNode <T>(default(T), new Sphere(Vector3.zero, 1.0f)); _root.SetFlagsBits(BVHNodeFlags.Root); }
public void OnObjectTransformChanged(Transform objectTransform) { ObjectBounds.QueryConfig boundsQConfig = new ObjectBounds.QueryConfig(); boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined; boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(RTScene.Get.Settings.NonMeshObjectSize); AABB worldAABB = ObjectBounds.CalcWorldAABB(objectTransform.gameObject, boundsQConfig); Sphere worldSphere = new Sphere(worldAABB); SphereTreeNode <GameObject> objectNode = _objectToNode[objectTransform.gameObject]; objectNode.Sphere = worldSphere; _objectTree.OnNodeSphereUpdated(objectNode); RTFocusCamera.Get.SetObjectVisibilityDirty(); }
public void RegisterObject(GameObject gameObject) { if (!CanRegisterObject(gameObject)) { return; } ObjectBounds.QueryConfig boundsQConfig = new ObjectBounds.QueryConfig(); boundsQConfig.ObjectTypes = GameObjectTypeHelper.AllCombined; boundsQConfig.NoVolumeSize = Vector3Ex.FromValue(RTScene.Get.Settings.NonMeshObjectSize); AABB worldAABB = ObjectBounds.CalcWorldAABB(gameObject, boundsQConfig); Sphere worldSphere = new Sphere(worldAABB); SphereTreeNode <GameObject> objectNode = _objectTree.AddNode(gameObject, worldSphere); _objectToNode.Add(gameObject, objectNode); RTFocusCamera.Get.SetObjectVisibilityDirty(); }
/// <summary> /// Integrates 'node' inside the tree. This is a recursive method which will /// search for the best place where to place this node inside the tree. /// </summary> /// <param name="parent"> /// Keeps track of the current parent node we are processing., Allow us to keep /// going down the tree hierarchy. /// </param> private void IntegrateNodeRecurse(SphereTreeNode <T> node, SphereTreeNode <T> parent) { // Are we dealing with a terminal node? if (!parent.IsFlagBitSet(BVHNodeFlags.Terminal)) { // This is not a terminal node. First thing to do is check if this node has // room for one more child. If it does, we add the node here. Otherwise, we // keep searching. if (parent.NumChildren < _numChildrenPerNode) { node.SetFlagsBits(BVHNodeFlags.Terminal); node.SetParent(parent); parent.EncapsulateChildrenBottomUp(); } else { // Find the child closest to 'node' and recurse down that path SphereTreeNode <T> closestChild = parent.ClosestChild(node); if (closestChild != null) { IntegrateNodeRecurse(node, closestChild); } } } else { // We have reached a terminal node. We have no choice but to create a new non-terminal // node to take the place of the terminal one and then attach the integration node and // the old terminal node as children of this new node. SphereTreeNode <T> newParentNode = new SphereTreeNode <T>(default(T), parent.Sphere); newParentNode.SetParent(parent.Parent); parent.SetParent(newParentNode); node.SetParent(newParentNode); node.SetFlagsBits(BVHNodeFlags.Terminal); newParentNode.EncapsulateChildrenBottomUp(); } }
public SphereTreeNodeRayHit(Ray ray, SphereTreeNode <T> hitNode, float hitEnter) { _hitNode = hitNode; _hitEnter = hitEnter; _hitPoint = ray.GetPoint(_hitEnter); }