예제 #1
0
 public override string ToString()
 {
     return(string.Format("OBB(handle {0}, center{1}, axis1{2}, axis2{3}, axis3{4}, extents{5})",
                          MO.HandleString(handle), MO.MeterFractionString(center),
                          MO.AxisString(axes[0]), MO.AxisString(axes[1]), MO.AxisString(axes[2]),
                          MO.MeterFractionString(extents)));
 }
예제 #2
0
 private void FlushAllCollisionCounts()
 {
     if (tiles == null)
     {
         return;
     }
     if (MO.DoLog)
     {
         MO.Log("Flushing all collision counts");
     }
     for (int i = 0; i < tileCount; i++)
     {
         for (int j = 0; j < tileCount; j++)
         {
             if (tiles[i, j] != 0)
             {
                 int  tX     = ArrayIndexToTile(i, tileXCenter);
                 int  tY     = ArrayIndexToTile(j, tileZCenter);
                 long handle = ComposeHandle(tX, tY);
                 if (MO.DoLog)
                 {
                     MO.Log(string.Format("Flushing tileX={0}, tileY={1}, tiles[{2},{3}], handle {4}, center {5} in FlushAllCollisioncounts",
                                          tX, tY, i, j, MO.HandleString(handle), MO.MeterString(tileWorldCenter)));
                 }
                 int count = collisionAPI.RemoveCollisionShapesWithHandle(handle);
                 objectsRemoved += count;
                 tiles[i, j]     = 0;
             }
         }
     }
 }
예제 #3
0
 private void ReplaceChild(SphereTreeNode currentChild, SphereTreeNode newChild)
 {
     if (MO.DoLog)
     {
         if (newChild == null)
         {
             MO.Log(" Replacing child {0} with null", currentChild);
         }
         else
         {
             MO.Log(" Replacing child {0} with child {1}", currentChild, newChild);
         }
     }
     for (int i = 0; i < childCount; i++)
     {
         SphereTreeNode child = children[i];
         if (child == currentChild)
         {
             children[i] = newChild;
             if (newChild != null)
             {
                 newChild.parent = this;
             }
             currentChild.parent = null;
             return;
         }
     }
     Debug.Assert(false, "Didn't find child");
 }
예제 #4
0
 public void RecreateCollisionTiles()
 {
     if (MO.DoLog)
     {
         MO.Log("Flushing all tiles due to call to RecreateTiles");
     }
     FlushAllCollisionCounts();
     InitializeBasedOnCollisionHorizon(collisionHorizon);
 }
예제 #5
0
 private void AddIntersectingShape(CollisionShape shape)
 {
     if (MO.DoLog)
     {
         MO.Log(" Adding shape {0} to intersecting shapes of {1}",
                shape, this);
     }
     intersectingShapes.Add(shape);
     sphereTree.intersectingShapeCount++;
 }
예제 #6
0
        public List <CollisionShape> GetCollisionShapesWithHandle(long handle)
        {
            List <CollisionShape> shapes = new List <CollisionShape>();

            if (MO.DoLog)
            {
                MO.Log("Starting to get shapes with handle {0}", MO.HandleString(handle));
            }
            root.GetCollisionShapesWithHandleInternal(handle, shapes);
            return(shapes);
        }
예제 #7
0
        public int RemoveCollisionShapesWithHandle(long handle)
        {
            if (MO.DoLog)
            {
                MO.Log("Starting removal of shapes with handle {0}", MO.HandleString(handle));
                MO.Log(" Before removal {0}", SphereTreeCounters());
            }
            int removeCount = root.RemoveCollisionShapesWithHandleInternal(handle);

            if (MO.DoLog)
            {
                MaybeVerifyOrDump();
                MO.Log(" After removal {0}", SphereTreeCounters());
            }
            return(removeCount);
        }
예제 #8
0
        public void AddCollisionShape(CollisionShape shape)
        {
            // Called with this equal to the root of the tree
            shapesAdded++;
            SphereTreeNode s = new SphereTreeNode(shape, this);

            if (MO.DoLog)
            {
                MO.Log("Adding shape {0} to leaf {1}", shape, s);
            }
            root.InsertSphereTreeNode(s);
            root.AddToIntersectingShapes(shape);
            Debug.Assert(shapesAdded - shapesRemoved <= nodeCount,
                         "shapesAdded - shapesRemoved > nodeCount!");
            MaybeVerifyOrDump();
        }
예제 #9
0
//////////////////////////////////////////////////////////////////////
//
// The external interface to the collision library
//
//////////////////////////////////////////////////////////////////////

        public CollisionAPI(bool logCollisions)
        {
            if (logCollisions)
#if NOT
            { MO.InitLog(true); }
#else
            { MO.DoLog = true; }
#endif
            sphereTree = new SphereTree();
            sphereTree.Initialize();
            topLevelCalls      = 0;
            partCalls          = 0;
            topLevelCollisions = 0;
            collisionTestCount = 0;
            ParameterRegistry.RegisterSubsystemHandlers("CollisionAPI", setParameterHandler, getParameterHandler);
        }
예제 #10
0
 private void AddChild(SphereTreeNode s)
 {
     for (int i = 0; i < childCount; i++)
     {
         if (children[i] == null)
         {
             if (MO.DoLog)
             {
                 MO.Log(" Adding child {0} to parent {1}", s, this);
             }
             children[i] = s;
             s.parent    = this;
             return;
         }
     }
     Debug.Assert(false, "No free slot to add child");
 }
예제 #11
0
 public override string ToString()
 {
     if (leafNode)
     {
         return(string.Format("(Leaf {0}:{1}, id {2}, shape {3}{4})",
                              MO.MeterString(center), MO.MeterString(radius), id,
                              (containedShape == null ? "null" : containedShape.ToString()),
                              StringIntersectingShapes()));
     }
     else
     {
         return(string.Format("(Non-leaf {0}:{1}, id {2}, kids {3}{4})",
                              MO.MeterString(center), MO.MeterString(radius), id,
                              CountChildren(),
                              StringIntersectingShapes()));
     }
 }
예제 #12
0
        public void InsertSphereTreeNode(SphereTreeNode s)
        {
            Debug.Assert(s.leafNode);
            if (leafNode)
            {
                CombineLeafNodes(s);
                parent.RecalculateCenterAndRadius();
                return;
            }

            SphereTreeNode child = null;

            for (int i = 0; i < childCount; i++)
            {
                child = children[i];
                if (child != null && child.Contains(s))
                {
                    child.InsertSphereTreeNode(s);
                    RecalculateCenterAndRadius();
                    return;
                }
            }

            // Not contained in any child, so create it here
            bool inserted = false;

            for (int i = 0; i < childCount; i++)
            {
                if (children[i] == null)
                {
                    s.parent    = this;
                    children[i] = s;
                    inserted    = true;
                    if (MO.DoLog)
                    {
                        MO.Log(" Inserted sphere {0} in parent {1}", s, this);
                    }
                    break;
                }
            }
            if (!inserted)
            {
                child = FindChildToDemote(s);
            }
            RecalculateCenterAndRadius();
        }
예제 #13
0
        private static RenderedNode UnscaledRenderedObject(string meshName, int id,
                                                           int index, Vector3 position)
        {
            string name = RenderedNodeName(id, index);

            if (MO.DoLog)
            {
                MO.Log(string.Format(" Creating rendering for node {0}", name));
            }
            Entity    entity = scene.CreateEntity(name, meshName);
            SceneNode node   = scene.RootSceneNode.CreateChildSceneNode(name);

            node.AttachObject(entity);
            node.Position    = position;
            node.ScaleFactor = Vector3.UnitScale;
            node.Orientation = Quaternion.Identity;
            RenderedNode n = new RenderedNode(entity, node);

            return(n);
        }
예제 #14
0
 public static void RemoveNodeRendering(int id, ref List <RenderedNode> renderedNodes)
 {
     if (renderedNodes != null)
     {
         for (int i = 0; i < renderedNodes.Count; i++)
         {
             string name = RenderedNodeName(id, i);
             if (MO.DoLog)
             {
                 MO.Log(string.Format(" Removing rendering for node {0}", name));
             }
             RenderedNode n = renderedNodes[i];
             n.node.Creator.DestroySceneNode(name);
             // remove the entity from the scene
             scene.RemoveEntity(n.entity);
             // clean up any unmanaged resources
             n.entity.Dispose();
         }
         renderedNodes.Clear();
     }
 }
예제 #15
0
        private SphereTreeNode CombineLeafNodes(SphereTreeNode s)
        {
            // Remember my own parent, and remove me from that parent
            if (MO.DoLog)
            {
                MO.Log(" Combined {0} with {1}", this, s);
            }
            SphereTreeNode myparent = parent;

            Debug.Assert(myparent != null, "parent was null!");
            parent.RemoveChild(this);
            // Make a new non-leaf node node to hold both
            SphereTreeNode n = new SphereTreeNode(center, radius, sphereTree);

            myparent.AddChild(n);
            n.AddChild(this);
            n.AddChild(s);
            n.RecalculateCenterAndRadius();
            if (MO.DoLog)
            {
                MO.Log(" Made combined node {0} child of {1}", n, myparent);
            }
            return(n);
        }
예제 #16
0
// The is the top-level collision detection function.
//
// The MovingObject doesn't intersect anything now; would it
// do so if the vector displacement is applied?
//
// If we do have a collision, we "creep up" on the object we collide
// with until we're within stepsize of the minimum dimension of the
// moving part
//
        public bool TestCollision(MovingObject mo, float stepSize, ref Vector3 displacement, CollisionParms parms)

        {
            topLevelCalls++;
            parms.Initialize();
            if (mo.parts.Count == 0)
            {
                return(false);
            }
            // Remember where the first part started
            Vector3 start = mo.parts[0].shape.center;
            Vector3 originalDisplacement = displacement;
            float   len = displacement.Length;
            //MaybeDumpSphereTree();
            int     nsteps = (int)Math.Ceiling(len / stepSize);
            Vector3 step   = (len <= stepSize ? displacement : displacement * (stepSize / len));

            // Try to step the whole way
            if (!StepTowardCollision(mo, displacement, nsteps, step, parms))
            {
                displacement = Vector3.Zero;
                return(false);
            }
            // Otherwise, we hit something.  Step toward it
            // The minimum distance
            float smallStepSize = Math.Min(MO.InchesToMeters(MinInchesToObstacle),
                                           stepSize * MinFractionToObstacle);

            len    = step.Length;
            nsteps = (int)Math.Ceiling(len / smallStepSize);
            Vector3 smallStep = step * (smallStepSize / len);
            bool    hit       = StepTowardCollision(mo, step, nsteps, smallStep, parms);

            displacement = originalDisplacement - (mo.parts[0].shape.center - start);
            return(hit);
        }
예제 #17
0
 public override string ToString()
 {
     return(string.Format("Sphere(handle {0}, center{1}, radius {2})",
                          MO.HandleString(handle), MO.MeterString(center), MO.MeterString(radius)));
 }
예제 #18
0
        // This is the principle interface to the upper levels of the
        // // client.  When the client realizes that the center of the
        // // collision visibility circle has changed, it calls
        // SetCollisionArea // to let us know.
        //
        // Nearly always the collisionHorizon is the same as the original
        // collisionHorizon.  If it's not, we flush all collision counts and
        // rebuild everything.
        public void SetCollisionArea(Vector3 newTileWorldCenterValue, float newCollisionHorizon)
        {
            newTileWorldCenter = newTileWorldCenterValue;
            WorldToTileCoords(newTileWorldCenter, out newTileXCenter, out newTileZCenter);
            if (tiles == null || Math.Abs(newCollisionHorizon - collisionHorizon) > .0001)
            {
                // We need to start from scratch again
                if (MO.DoLog)
                {
                    MO.Log("Horizon changed; flushing everything");
                }
                FlushAllCollisionCounts();
                InitializeBasedOnCollisionHorizon(newCollisionHorizon);
            }
            else
            {
                if (newTileXCenter == tileXCenter && newTileZCenter == tileZCenter)
                {
                    // the tile center didn't change, so there is nothing
                    // to do put set the world center
                    tileWorldCenter = newTileWorldCenter;
                    return;
                }
                else
                {
                    if (MO.DoLog)
                    {
                        MO.Log(string.Format("old center {0}[{1},{2}], new center {3}[{4},{5}], old horizon {6}, new horizon {7}",
                                             MO.MeterString(tileWorldCenter), tileXCenter, tileZCenter,
                                             MO.MeterString(newTileWorldCenter), newTileXCenter, newTileZCenter,
                                             MO.MeterString(collisionHorizon), MO.MeterString(newCollisionHorizon)));
                    }
                    ShiftTileArrayToNewCenter();
                }
            }
            // Now ask the WorldManager to iterate over boundary
            // objects, passing a box representing the world coords of
            // the new bounds of the entire tile array, and a callback
            // to actually add the objects

            // Choose a bound that is guaranteed to cover all the
            // trees in the tile array
            float   bound = 2 * tileSize + collisionHorizon;
            Vector3 d     = new Vector3(bound, 0.0f, bound);
            Vector3 min   = newTileWorldCenter - d;
            Vector3 max   = newTileWorldCenter + d;

            // Make the range of Y "infinitely" large
            min.y -= 10000.0f * MO.Meter;
            max.y += 10000.0f * MO.Meter;
            AxisAlignedBox b = new AxisAlignedBox(min, max);

            if (MO.DoLog)
            {
                MO.Log(string.Format("Searching for obstacles in box min = {0}, max = {1}",
                                     MO.MeterString(min), MO.MeterString(max)));
            }
            ObstacleFinder(b, AddTreeObstacles);

            // Finally, update the tileWorldCenter
            tileWorldCenter = newTileWorldCenter;
            tileXCenter     = newTileXCenter;
            tileZCenter     = newTileZCenter;
        }
예제 #19
0
 public override string ToString()
 {
     return(string.Format("AABB(handle {0}, min{1}, max{2})",
                          MO.HandleString(handle), MO.MeterFractionString(min), MO.MeterFractionString(max)));
 }
예제 #20
0
        private SphereTreeNode FindChildToDemote(SphereTreeNode s)
        {
            if (MO.DoLog)
            {
                MO.Log(" Demote this {0} s {1}", this, s);
            }
            Debug.Assert(s.leafNode, "s isn't a leaf node");
            // All children are occupied, and s is not wholly contained in
            // any of them.  Combine s with some child, creating a
            // subordinate.  Choose the child whose center is nearest
            // s.center.  If the child has a larger radius, insert s in
            // the child.  If the child has a smaller radius, s becomes
            // the child, and the child is inserted in s.
            float closest      = float.MaxValue;
            int   closestChild = -1;

            for (int i = 0; i < childCount; i++)
            {
                SphereTreeNode child = children[i];
                Debug.Assert(child != null, "Null child!");
                float d = Primitives.DistanceSquared(s.center, child.center);
                if (d < closest)
                {
                    closest      = d;
                    closestChild = i;
                }
            }
            Debug.Assert(closestChild >= 0, "closestChild not found!");
            SphereTreeNode cs = children[closestChild];

            if (cs.leafNode)
            {
                if (MO.DoLog)
                {
                    MO.Log(" Calling CombineLeafNodes to combine {0} with {1}", cs, this);
                }
                SphereTreeNode n = cs.CombineLeafNodes(s);
                children[closestChild] = n;
                return(n);
            }
            else if (cs.ChildSlotsFree() > 0)
            {
                cs.AddChild(s);
                cs.RecalculateCenterAndRadius();
                return(cs);
            }
            else
            {
                SphereTreeNode n = new SphereTreeNode(sphereTree);
                RemoveChild(cs);
                AddChild(n);
                n.AddChild(cs);
                n.AddChild(s);
                n.RecalculateCenterAndRadius();
                if (MO.DoLog)
                {
                    MO.Log(" In demote, made new node n {0}", n);
                }
                return(n);
            }
        }
예제 #21
0
        private void ShiftTileArrayToNewCenter()
        {
//          if (MO.DoLog) {
//              MO.Log("ShiftTileArrayToNewCenter");
//              MO.Log(string.Format(" Nonzero tiles {0}",
//                                   TileArrayString(tiles, tileXCenter, tileZCenter)));
//          }
            // Iterate over the tiles, flushing objects that are no
            // longer in range
            arrayShifts++;
            for (int i = 0; i < tileCount; i++)
            {
                for (int j = 0; j < tileCount; j++)
                {
                    if (tiles[i, j] != 0)
                    {
                        int tX = ArrayIndexToTile(i, tileXCenter);
                        int tZ = ArrayIndexToTile(j, tileZCenter);
                        if (!InRange(newTileXCenter, newTileZCenter, tX, tZ))
                        {
                            long handle = ComposeHandle(tX, tZ);
                            if (MO.DoLog)
                            {
                                MO.Log(string.Format(" Flushing tile coords[{0},{1}], tiles[{2},{3}] = {4}, handle {5}, center {6}",
                                                     tX, tZ, i, j, tiles[i, j], MO.HandleString(handle), MO.MeterString(newTileWorldCenter)));
                            }
                            int count = collisionAPI.RemoveCollisionShapesWithHandle(handle);
                            objectsRemoved += count;
//                          if (MO.DoLog)
//                              MO.Log(string.Format(" Flushed {0} shapes at tiles[{1},{2}]",
//                                                   count, i, j));
                            tiles[i, j] = 0;
                        }
                    }
                }
            }
//          if (MO.DoLog) {
//              MO.Log(string.Format(" After range flush, nonzero tiles {0}",
//                                   TileArrayString(tiles, tileXCenter, tileZCenter)));
//          }

            // Now shift the array of counts.
            // If either coord difference moves the tiles so that none
            // of the entries are in range, just zero the array
            if (Math.Abs(newTileXCenter - tileXCenter) >= tileCount ||
                Math.Abs(newTileZCenter - tileZCenter) >= tileCount)
            {
                if (MO.DoLog)
                {
                    MO.Log(" Moved more than tileCount, so zeroing");
                }
                ZeroTiles(tiles);
            }
            // Else we have to move the contents of the array.  Do
            // this in a temporary array, because of the overlap
            // constraints.
            else
            {
                int xDelta = newTileXCenter - tileXCenter;
                int zDelta = newTileZCenter - tileZCenter;
                if (MO.DoLog)
                {
                    MO.Log(string.Format(" xDelta {0}, zDelta {1}", xDelta, zDelta));
                }
                int[,] newTiles = new int[tileCount, tileCount];
                ZeroTiles(newTiles);
                for (int i = 0; i < tileCount; i++)
                {
                    for (int j = 0; j < tileCount; j++)
                    {
                        int xSource = i + xDelta;
                        int zSource = j + zDelta;
                        int source  = ((xSource >= 0) && (xSource < tileCount) &&
                                       (zSource >= 0) && (zSource < tileCount)
                                      ? tiles[xSource, zSource] : 0);
                        if (source != 0)
                        {
//                          if (MO.DoLog)
//                              MO.Log(string.Format(" Moving tiles[{0},{1}] to tiles[{2},{3}], count {4}, center {5}, newcenter {6}",
//                                                   xSource, zSource, i, j, source,
//                                                   MO.MeterString(tileWorldCenter), MO.MeterString(newTileWorldCenter)));
                            newTiles[i, j] = source;
                        }
                    }
                }
//              if (MO.DoLog) {
//                  MO.Log(string.Format(" newTiles nonzero tiles {0}",
//                                       TileArrayString(newTiles, tileXCenter, tileZCenter)));
//              }
                // Now copy back
                for (int i = 0; i < tileCount; i++)
                {
                    for (int j = 0; j < tileCount; j++)
                    {
                        tiles[i, j] = newTiles[i, j];
                    }
                }
            }
        }
예제 #22
0
 public override string ToString()
 {
     return(string.Format("Capsule(handle {0}, bottom{1}, top{2}, capRadius {3})",
                          MO.HandleString(handle), MO.MeterString(bottomcenter), MO.MeterString(topcenter),
                          MO.MeterFractionString(capRadius)));
 }
예제 #23
0
        // This method is the callback by which the forests controlled
        // by the scene manager tell us about SpeedTrees.
        private void AddTreeObstacles(SpeedTreeWrapper tree)
        {
            // If this tree didn't use to be in range but now is
            V3      vp = tree.TreePosition;
            Vector3 p  = new Vector3(vp.x, vp.y, vp.z);
            // Find the tile in question
            int tileX, tileZ;

            WorldToTileCoords(p, out tileX, out tileZ);
            bool oir = InRange(tileXCenter, tileZCenter, tileX, tileZ);
            bool nir = InRange(newTileXCenter, newTileZCenter, tileX, tileZ);

//             if (MO.DoLog)
//                 MO.Log(String.Format("For tree at {0}, testing !InRange({1},{2},{3},{4}) = {5} && InRange({6},{7},{8},{9}) = {10}",
//                                      MO.MeterString(p),tileXCenter, tileZCenter, tileX, tileZ, MO.Bool(!oir),
//                                      newTileXCenter, newTileZCenter, tileX, tileZ, MO.Bool(nir)));
            if (!oir && nir)
            {
                int            tX     = TileToArrayIndex(tileX, newTileXCenter);
                int            tZ     = TileToArrayIndex(tileZ, newTileZCenter);
                uint           count  = tree.CollisionObjectCount;
                long           handle = ComposeHandle(tileX, tileZ);
                CollisionShape shape  = null;
                if (MO.DoLog)
                {
                    MO.Log(string.Format("Adding tree at {0}, tiles[{1},{2}] = {3}, tile coords[{4},{5}], obj. new count {6}",
                                         MO.MeterString(p), tX, tZ, tiles[tX, tZ], tileX, tileZ, count));
                    MO.Log(string.Format("Handle {0}, oldcenter {1}, newcenter {2}",
                                         MO.HandleString(handle), MO.MeterString(tileWorldCenter), MO.MeterString(newTileWorldCenter)));
                }
                float size     = 0f;
                float variance = 0f;
                tree.GetTreeSize(ref size, ref variance);
                float scaleFactor = size / tree.OriginalSize;
                for (uint i = 0; i < count; i++)
                {
                    TreeCollisionObject tco = tree.CollisionObject(i);
                    Vector3             cp  = new Vector3(tco.position.x, tco.position.y, tco.position.z) * scaleFactor + p;
                    Vector3             cd  = new Vector3(tco.dimensions.x, tco.dimensions.y, tco.dimensions.z) * scaleFactor;
                    switch ((CollisionObjectType)tco.type)
                    {
                    case CollisionObjectType.ColSphere:
                        shape = new CollisionSphere(cp, cd.x);
                        break;

                    case CollisionObjectType.ColCylinder:
                        // We treat it as a capsule, but we raise the
                        // capsule up by the capRadius, and shorten
                        // the segment by the double the capRadius
                        Vector3 top = cp;
                        top.y += cd.y - cd.x * 2f;
                        cp.y  += cd.x;
                        shape  = new CollisionCapsule(cp, top, cd.x);
                        break;

                    case CollisionObjectType.ColBox:
                        Vector3 tp = cp;
                        tp.x -= cd.x * .5f;
                        tp.y -= cd.y * .5f;
                        shape = new CollisionAABB(tp, tp + cd);
                        break;
                    }
                    collisionAPI.AddCollisionShape(shape, handle);
                    tiles[tX, tZ]++;
                    objectsAdded++;

                    if (MO.DoLog)
                    {
                        MO.Log(string.Format(" tiles[{0},{1}] = {2}, tile at [{3},{4}] after adding shape {5}",
                                             tX, tZ, tiles[tX, tZ], tileX, tileZ, shape.ToString()));
                    }
                }
            }
        }
예제 #24
0
        public int RemoveCollisionShapesWithHandleInternal(long handle)
        {
            // The dumb version: iterate over all containment spheres
            // looking for obstacles with the given handle, returning true
            // if a child changed.  (The smarter version would maintain an
            // index mapping handles to SphereTreeNodes whose
            // containedShape or intersectingShapes have a shape with
            // that handle.)
            //
            // This runs bottom-up, to provide an opportunity to coalesce
            // the sphere tree.

            // intersectingShapes are easy - - just remove if they have a
            // matching handle, since their removal doesn't change the tree
            int removeCount = 0;

            for (int i = 0; i < intersectingShapes.Count; i++)
            {
                CollisionShape obstacle = intersectingShapes[i];
                if (obstacle.handle == handle)
                {
                    if (MO.DoLog)
                    {
                        MO.Log(" Removing intersecting shape {0} of {1}", obstacle, this);
                    }
                    intersectingShapes.RemoveAt(i);
                    i--;
                    sphereTree.intersectingShapeCount--;
                    Debug.Assert(sphereTree.intersectingShapeCount >= 0, "intersectingShapeCount < 0!");
                }
            }

            // Now handle the children
            for (int i = 0; i < childCount; i++)
            {
                SphereTreeNode child = children[i];
                if (child != null)
                {
                    if (child.leafNode && child.containedShape.handle == handle)
                    {
                        if (MO.DoLog)
                        {
                            MO.Log(" Removing child leaf {0} of {1}", child, this);
                        }
                        children[i]  = null;
                        child.parent = null;
                        RenderedNode.RemoveNodeRendering(child.id, ref child.renderedNodes);
                        sphereTree.shapesRemoved++;
                        sphereTree.nodeCount--;
                        removeCount++;
                    }
                    else if (!child.leafNode)
                    {
                        removeCount += child.RemoveCollisionShapesWithHandleInternal(handle);
                    }
                }
            }

            int count = CountChildren();

            if (count == 0 && containedShape == null)
            {
                // This is now a truly pointless node - - remove from its
                // parent.  Parent will only be null for the root node
                if (parent != null)
                {
                    if (MO.DoLog)
                    {
                        MO.Log(" Removing branch with no kids {0} of {1}", this, parent);
                    }
                    parent.RemoveChild(this);
                    sphereTree.nodeCount--;
                    Debug.Assert(sphereTree.nodeCount > 0, "nodeCount <= 0");
                    return(removeCount);
                }
            }
            else if (count == 1)
            {
                if (parent != null)
                {
                    // Make my child the child of my parent
                    if (MO.DoLog)
                    {
                        MO.Log(" Replacing 1-child node {0} in parent {1}", this, parent);
                    }
                    parent.ReplaceChild(this, FindFirstChild());
                    sphereTree.nodeCount--;
                    Debug.Assert(sphereTree.nodeCount > 0, "nodeCount <= 0");
                    return(removeCount);
                }
            }
            if (removeCount > 0)
            {
                RecalculateCenterAndRadius();
            }
            return(removeCount);
        }