예제 #1
0
        /// <summary>
        /// Finds and returns the node inside 'nodes' which is closest to 'node'.
        /// </summary>
        private SphereTreeNode <T> FindClosestNode(List <SphereTreeNode <T> > nodes, SphereTreeNode <T> node)
        {
            float minDistanceSq            = float.MaxValue;
            SphereTreeNode <T> closestNode = null;

            // We will choose the node whose center is closest to 'node'
            foreach (SphereTreeNode <T> potentialNode in nodes)
            {
                // Calculate the squared distance between the node centers
                float distanceBetweenNodesSq = potentialNode.GetDistanceBetweenCentersSq(node);
                if (distanceBetweenNodesSq < minDistanceSq)
                {
                    // Smaller than what we have so far?
                    minDistanceSq = distanceBetweenNodesSq;
                    closestNode   = potentialNode;

                    // If we somehow managed to find a node which has the same position as 'node', we can exit
                    if (minDistanceSq == 0.0f)
                    {
                        return(closestNode);
                    }
                }
            }

            return(closestNode);
        }
예제 #2
0
        /// <summary>
        /// Recursive method which is used to detect which terminal nodes in the tree are intersected
        /// by 'ray'. Information about the intersected nodes is stored inside 'terminalNodeHitInfo'.
        /// </summary>
        private void RaycastAllRecurse(Ray ray, SphereTreeNode <T> parentNode, List <SphereTreeNodeRayHit <T> > terminalNodeHitInfo)
        {
            // If the parent node is not hit by the ray, there is no need to go further
            float t;

            if (!parentNode.Sphere.Raycast(ray, out t))
            {
                return;
            }
            else
            {
                // If the parent node was hit, we have 2 choices:
                //  a) if the node is a terminal node, we add it to the 'hitNodes' list and exit;
                //  b) if the node is a super-sphere, we will recurse for eacoh if its children.
                if (parentNode.IsTerminal)
                {
                    terminalNodeHitInfo.Add(new SphereTreeNodeRayHit <T>(ray, t, parentNode));
                    return;
                }
                else
                {
                    // Recurse for each child node
                    List <SphereTreeNode <T> > childNodes = parentNode.Children;
                    foreach (SphereTreeNode <T> childNode in childNodes)
                    {
                        RaycastAllRecurse(ray, childNode, terminalNodeHitInfo);
                    }
                }
            }
        }
예제 #3
0
 /// <summary>
 /// Recursive method which is used to step down the tree hierarchy collecting all terminal nodes
 /// which are overlapped by the specified box.
 /// </summary>
 private void OverlapBoxRecurse(OrientedBox box, SphereTreeNode <T> parentNode, List <SphereTreeNode <T> > overlappedTerminalNodes)
 {
     // If the parent is not overlapped there is no need to go any further
     if (!parentNode.Sphere.OverlapsFullyOrPartially(box))
     {
         return;
     }
     else
     {
         // If this is a terminal node, add it to the output list and return
         if (parentNode.IsTerminal)
         {
             overlappedTerminalNodes.Add(parentNode);
             return;
         }
         else
         {
             // Recurs for each child node
             List <SphereTreeNode <T> > childNodes = parentNode.Children;
             foreach (SphereTreeNode <T> childNode in childNodes)
             {
                 OverlapBoxRecurse(box, childNode, overlappedTerminalNodes);
             }
         }
     }
 }
예제 #4
0
        /// <summary>
        /// The client code must call this function during each frame update in order
        /// to ensure that any pending node updates are performed.
        /// </summary>
        public void PerformPendingUpdates()
        {
            // First, ensure that all nodes are recomputed accordingly
            while (_nodesPendingRecomputation.Count != 0)
            {
                SphereTreeNode <T> node = _nodesPendingRecomputation.Dequeue();

                // Note: If the node has any children left, we will recompute its volume. Otherwise,
                //       we will remove the node from the tree.
                if (node.HasChildren)
                {
                    node.RecomputeCenterAndRadius();
                }
                else
                {
                    RemoveNode(node);
                }
            }

            // At this point, all super sphere nodes have had their volume updated accordingly.
            // In the next step, we will integrate any necessary terminal nodes.
            while (_terminalNodesPendingIntegration.Count != 0)
            {
                SphereTreeNode <T> node = _terminalNodesPendingIntegration.Dequeue();
                IntegrateTerminalNode(node);
            }
        }
예제 #5
0
        /// <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>
        /// Registers the specified object with the tree.
        /// </summary>
        private bool RegisterGameObject(GameObject gameObject)
        {
            if (!CanGameObjectBeRegisteredWithTree(gameObject))
            {
                return(false);
            }

            // Build the object's sphere
            Box      objectWorldBox = gameObject.GetWorldBox();
            Sphere3D objectSphere   = objectWorldBox.GetEncapsulatingSphere();

            // Add the object as a terminal node. Also store the node in the dictionary so that we can
            // use it when it's needed.
            SphereTreeNode <GameObject> objectNode = _sphereTree.AddTerminalNode(objectSphere, gameObject);

            _gameObjectToNode.Add(gameObject, objectNode);

            #if !USE_TRANSFORM_HAS_CHANGED
            _gameObjectToTransformData.Add(gameObject, GetGameObjectTransformData(gameObject));
            #endif

            // If it is a mesh object, start silent building the mesh
            if (gameObject.HasMesh())
            {
                EditorMesh editorMesh = EditorMeshDatabase.Instance.CreateEditorMesh(gameObject.GetMesh());
                if (editorMesh != null)
                {
                    EditorMeshDatabase.Instance.AddMeshToSilentBuild(editorMesh);
                }
            }

            EditorCamera.Instance.AdjustObjectVisibility(gameObject);
            return(true);
        }
예제 #7
0
 /// <summary>
 /// Creates the root node of the tree.
 /// </summary>
 private void CreateRootNode()
 {
     // Create the root node with some sensible default values. It doesn't really matter what
     // initial size the root node has. It will grow as needed when new nodes are added to the
     // tree.
     _rootNode = new SphereTreeNode <T>(Vector3.zero, 10.0f, this);
     _rootNode.SetFlag(SphereTreeNodeFlags.Root | SphereTreeNodeFlags.SuperSphere);
 }
예제 #8
0
 /// <summary>
 /// Integrates the specified node into the tree hierarchy. Integrating a nod means placing it
 /// inside the tree hierarchy in the correct spot. Because it is a terminal node, the method
 /// will have to search for the correct super-sphere node which can act as a parent of this node.
 /// </summary>
 private void IntegrateTerminalNode(SphereTreeNode <T> nodeToIntegrate)
 {
     // Start a recursive process from the root of the hierarchy. After the integration
     // is finished, we will clear the node's integration flag because it no loger needs
     // to be integrated.
     IntegrateTerminalNodeRecurse(nodeToIntegrate, _rootNode);
     nodeToIntegrate.ClearFlag(SphereTreeNodeFlags.MustIntegrate);
 }
예제 #9
0
 /// <summary>
 /// Recursive function which renders the specified node in the scene view and then
 /// steps down the hierarchy for each child of the node.
 /// </summary>
 private void RenderGizmosDebugRecurse(SphereTreeNode <T> node)
 {
     // Draw the sphere for the specified node and then recurse for each child node
     Gizmos.DrawSphere(node.Sphere.Center, node.Sphere.Radius);
     foreach (var child in node.Children)
     {
         RenderGizmosDebugRecurse(child);
     }
 }
예제 #10
0
        public void RemoveChild(SphereTreeNode <T> child)
        {
            int indexOfChild = _children.FindIndex(item => item == child);

            if (indexOfChild >= 0)
            {
                _children.RemoveAt(indexOfChild);
                child._parent = null;
            }
        }
예제 #11
0
        /// <summary>
        /// Adds a terminal node to the tree.
        /// </summary>
        /// <remarks>
        /// The function does not integrate the node inside the sphere hierarchy. It
        /// will only add it to the integration pending queue. The actual integration
        /// process will be performed inside 'PerformPendingUpdates'.
        /// </remarks>
        /// <param name="sphere">
        /// The node's sphere.
        /// </param>
        /// <param name="data">
        /// The node's data.
        /// </param>
        /// <returns>
        /// The node which was added to the tree.
        /// </returns>
        public SphereTreeNode <T> AddTerminalNode(Sphere3D sphere, T data)
        {
            // Create a new node and mark it as terminal
            var newTerminalNode = new SphereTreeNode <T>(sphere, this, data);

            newTerminalNode.SetFlag(SphereTreeNodeFlags.Terminal);

            // Add the node to the integration queue
            AddNodeToIntegrationQueue(newTerminalNode);

            return(newTerminalNode);
        }
예제 #12
0
        public void EncapsulateChildNode(SphereTreeNode <T> node)
        {
            SphereTreeNode <T> currentParent = this;
            SphereTreeNode <T> currentChild  = node;

            while (currentParent != null && !currentParent.FullyContains(currentChild))
            {
                currentParent.Encapsulate(currentChild);
                currentChild  = currentParent;
                currentParent = currentParent.Parent;
            }
        }
예제 #13
0
        /// <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);
                parentNode.RecomputeCenterAndRadius();
            }
            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
                {
                    SphereTreeNode <T> newParentNode = new SphereTreeNode <T>(closestChild.Sphere, this, default(T));
                    newParentNode.SetFlag(SphereTreeNodeFlags.SuperSphere);

                    // Replace 'closestChild' with the new parent node and add both 'closestChild' and 'nodeToIntegrate' as children of it
                    parentNode.RemoveChild(closestChild);
                    parentNode.AddChild(newParentNode);
                    newParentNode.AddChild(nodeToIntegrate);
                    newParentNode.AddChild(closestChild);

                    // Encapsulate the children inside the new node
                    //newParentNode.EncapsulateChildNode(closestChild);
                    //newParentNode.EncapsulateChildNode(nodeToIntegrate);

                    // Ensure that the new node is fully contained inside the parent node
                    //parentNode.EncapsulateChildNode(newParentNode);

                    newParentNode.RecomputeCenterAndRadius();
                    parentNode.RecomputeCenterAndRadius();
                }
            }
        }
예제 #14
0
 /// <summary>
 /// Adds the specified node to the integration queue.
 /// </summary>
 private void AddNodeToIntegrationQueue(SphereTreeNode <T> node)
 {
     // Only terminal, non-root nodes are allowed. We also have to ensure that
     // the node hasn't already been added to the integration queue.
     if (node.IsSuperSphere || node.IsRoot || node.MustIntegrate)
     {
         return;
     }
     if (node.IsTerminal)
     {
         node.SetFlag(SphereTreeNodeFlags.MustIntegrate);
         _terminalNodesPendingIntegration.Enqueue(node);
     }
 }
예제 #15
0
 /// <summary>
 /// Recursive method which steps down the tree hierarchy and adds pairs of node-data/node to 'dictionary'.
 /// When this method returns, all the terminal nodes and their data will be stored inside 'dictionary'.
 /// </summary>
 private void GetDataToTerminalNodeDictionaryRecurse(SphereTreeNode <T> parentNode, Dictionary <T, SphereTreeNode <T> > dictionary)
 {
     if (parentNode.IsTerminal)
     {
         dictionary.Add(parentNode.Data, parentNode);
     }
     else
     {
         List <SphereTreeNode <T> > children = parentNode.Children;
         foreach (var child in children)
         {
             GetDataToTerminalNodeDictionaryRecurse(child, dictionary);
         }
     }
 }
예제 #16
0
        public void AddChild(SphereTreeNode <T> child)
        {
            if (ContainsChild(child))
            {
                return;
            }

            if (child.HasParent)
            {
                child.Parent.RemoveChild(child);
            }

            _children.Add(child);
            child._parent = this;
        }
예제 #17
0
        /// <summary>
        /// Handles the transform change for the specified object transform.
        /// </summary>
        private void HandleObjectTransformChange(Transform gameObjectTransform)
        {
            // Just ensure that the object is registered with the tree
            GameObject gameObject = gameObjectTransform.gameObject;

            if (!IsGameObjectRegistered(gameObject))
            {
                return;
            }

            // Store the object's node for easy access. We will need to instruct the
            // tree to update this node as needed.
            SphereTreeNode <GameObject> objectNode = _gameObjectToNode[gameObject];

            // We will first have to detect what has changed. So we will compare the
            // object's sphere as it is now with what was before.
            bool     updateCenter   = false;
            bool     updateRadius   = false;
            Sphere3D previousSphere = objectNode.Sphere;
            Sphere3D currentSphere  = gameObject.GetWorldBox().GetEncapsulatingSphere();

            // Detect what changed
            if (previousSphere.Center != currentSphere.Center)
            {
                updateCenter = true;
            }
            if (previousSphere.Radius != currentSphere.Radius)
            {
                updateRadius = true;
            }

            // Call the appropriate node update method
            if (updateCenter && updateRadius)
            {
                _sphereTree.UpdateTerminalNodeCenterAndRadius(objectNode, currentSphere.Center, currentSphere.Radius);
            }
            else if (updateCenter)
            {
                _sphereTree.UpdateTerminalNodeCenter(objectNode, currentSphere.Center);
            }
            else if (updateRadius)
            {
                _sphereTree.UpdateTerminalNodeRadius(objectNode, currentSphere.Radius);
            }

            EditorCamera.Instance.AdjustObjectVisibility(gameObject);
        }
예제 #18
0
        /// <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 remove 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);
        }
예제 #19
0
 public void Encapsulate(SphereTreeNode <T> node)
 {
     _sphere = _sphere.Encapsulate(node.Sphere);
 }
예제 #20
0
 /// <summary>
 /// Updates the radius of the specified terminal node.
 /// </summary>
 public void UpdateTerminalNodeRadius(SphereTreeNode <T> terminalNode, float newRadius)
 {
     terminalNode.Radius = newRadius;
     OnTerminalNodeSphereUpdated(terminalNode);
 }
예제 #21
0
 /// <summary>
 /// Updates the center of the specified terminal node.
 /// </summary>
 public void UpdateTerminalNodeCenter(SphereTreeNode <T> terminalNode, Vector3 newCenter)
 {
     terminalNode.Center = newCenter;
     OnTerminalNodeSphereUpdated(terminalNode);
 }
예제 #22
0
 public float GetDistanceBetweenCentersSq(SphereTreeNode <T> node)
 {
     return(_sphere.GetDistanceBetweenCentersSq(node.Sphere));
 }
예제 #23
0
 public float GetDistanceToNodeExitPoint(SphereTreeNode <T> node)
 {
     return((Center - node.Center).magnitude + node.Radius);
 }
예제 #24
0
 /// <summary>
 /// Updates the center and radius of the specified terminal node.
 /// </summary>
 public void UpdateTerminalNodeCenterAndRadius(SphereTreeNode <T> terminalNode, Vector3 newCenter, float newRadius)
 {
     terminalNode.Center = newCenter;
     terminalNode.Radius = newRadius;
     OnTerminalNodeSphereUpdated(terminalNode);
 }
예제 #25
0
 public bool ContainsChild(SphereTreeNode <T> child)
 {
     return(_children.Contains(child));
 }
 public SphereTreeNodeRayHit(Ray ray, float t, SphereTreeNode <T> hitNode)
 {
     _ray      = ray;
     _hitNode  = hitNode;
     _hitPoint = _ray.GetPoint(t);
 }
예제 #27
0
 public bool FullyContains(SphereTreeNode <T> node)
 {
     return(_sphere.FullyOverlaps(node.Sphere));
 }