// Lock the node dictionary, and rebuild the static
        // geometry for objects of this kind
        protected void Rebuild(SceneManager mgr, Dictionary <long, WorldEntity> nodeDictionary)
        {
            log.DebugFormat("Entering StaticGeometryHelper.Rebuild for geometry '{0}'", name);
            long tickStart = TimeTool.CurrentTime;

            try {
                nodesAddedSinceLastRebuild   = 0;
                nodesRemovedSinceLastRebuild = 0;
                force = false;
                Monitor.Enter(mgr);
                if (objectGeometry != null)
                {
                    objectGeometry.Reset();
                }
                else
                {
                    objectGeometry = new StaticGeometry(mgr, name);
                }
                // Dictionary mapping Material into a list of
                // ObjectNodes in which some submesh uses the material
                Dictionary <Material, MaterialAndNodeCounts> materialsUsedMap = new Dictionary <Material, MaterialAndNodeCounts>();
                lock (nodeDictionary) {
                    foreach (WorldEntity entity in nodeDictionary.Values)
                    {
                        if (entity is ObjectNode)
                        {
                            ObjectNode node = (ObjectNode)entity;
                            // For now, we only consider "props" that have an associated SceneNode
                            // and are direct descendants of the root scene node, and are of the right
                            // kind, i.e., don't have a perception radius if this static geometry is for
                            // little nodes, and vice versa.
//                             log.DebugFormat("StaticGeometry.Rebuild: Examining node {0}, oid {1}, type {2}, sceneNode {3}, InStaticGeometry {4}, top-level {5}",
//                                 node.Name, node.Oid, node.ObjectType, node.SceneNode, node.InStaticGeometry, node.SceneNode.Parent == mgr.RootSceneNode);
                            if (node.ObjectType == ObjectNodeType.Prop &&
                                (node.InStaticGeometry || (node.SceneNode != null && node.SceneNode.Parent == mgr.RootSceneNode)) &&
                                RightKind(node))
                            {
                                foreach (Material m in node.Entity.SubEntityMaterials)
                                {
                                    MaterialAndNodeCounts nodesUsingMaterial;
                                    if (!materialsUsedMap.TryGetValue(m, out nodesUsingMaterial))
                                    {
                                        nodesUsingMaterial  = new MaterialAndNodeCounts();
                                        materialsUsedMap[m] = nodesUsingMaterial;
                                    }
                                    nodesUsingMaterial.materialUseCount++;
                                    int subMeshUseCount;
                                    Dictionary <ObjectNode, int> submeshUseCounts = nodesUsingMaterial.submeshUseCounts;
                                    if (!submeshUseCounts.TryGetValue(node, out subMeshUseCount))
                                    {
                                        submeshUseCounts[node] = 1;
                                    }
                                    else
                                    {
                                        submeshUseCounts[node] = subMeshUseCount + 1;
                                    }
                                }
                            }
                        }
                    }
                }

                // Now we have a count of uses of each material, and
                // for each node, the number of subentities that use the
                // material.  Now we need to calculate the number of
                // instance of sharings for each object node
                Dictionary <ObjectNode, bool> candidateNodes = new Dictionary <ObjectNode, bool>();
                foreach (MaterialAndNodeCounts counts in materialsUsedMap.Values)
                {
                    if (counts.materialUseCount > 1)
                    {
                        foreach (KeyValuePair <ObjectNode, int> pair in counts.submeshUseCounts)
                        {
                            candidateNodes[pair.Key] = true;
                        }
                    }
                }
                Dictionary <ObjectNode, int> staticNodes = new Dictionary <ObjectNode, int>();
                foreach (KeyValuePair <ObjectNode, bool> pair in candidateNodes)
                {
                    ObjectNode candidate = pair.Key;
                    bool       useIt     = pair.Value;
                    if (useIt)
                    {
                        staticNodes[candidate] = 0;
                    }
                }
                if (staticNodes.Count == 0)
                {
                    log.InfoFormat("StaticGeometryHelper.Rebuild: Didn't build static geometry {0} because object count was zero", name);
                }
                else
                {
                    log.InfoFormat("StaticGeometryHelper.Rebuild: {0} ObjectNodes", staticNodes.Count);
                    foreach (ObjectNode staticNode in staticNodes.Keys)
                    {
                        SceneNode sc = staticNode.SceneNode;
                        if (!staticNode.InStaticGeometry)
                        {
                            sc.RemoveFromParent();
                            staticNode.InStaticGeometry = true;
                        }
                        log.DebugFormat("StaticGeometryHelper.Rebuild: Add node {0} with name {1} to static geometry",
                                        staticNode.Oid, staticNode.Name);
                        objectGeometry.AddSceneNode(sc);
                    }
                }
                if (lastStaticNodes != null)
                {
                    foreach (ObjectNode node in lastStaticNodes.Keys)
                    {
                        if (!staticNodes.ContainsKey(node))
                        {
                            // Only 1 instance of the mesh, so make sure that if in a former build it was in
                            // static geometry, that we add it back to the scene graph.
                            if (node.InStaticGeometry)
                            {
                                SceneNode sn = node.SceneNode;
                                if (sn != null)
                                {
                                    mgr.RootSceneNode.AddChild(sn);
                                }
                                node.InStaticGeometry = false;
                            }
                        }
                    }
                }
                if (staticNodes.Count > 0)
                {
                    objectGeometry.Build();
                }
                lastStaticNodes   = staticNodes;
                timeOfLastRebuild = TimeTool.CurrentTime;
            }
            finally {
                Monitor.Exit(mgr);
            }
            log.InfoFormat("StaticGeometryHelper.Rebuild: Rebuild of geometry '{0}' took {1} ms",
                           name, TimeTool.CurrentTime - tickStart);
        }
        // Lock the node dictionary, and rebuild the static
        // geometry for objects of this kind
        protected void Rebuild(SceneManager mgr, Dictionary<long, WorldEntity> nodeDictionary)
        {
            log.DebugFormat("Entering StaticGeometryHelper.Rebuild for geometry '{0}'", name);
            long tickStart = TimeTool.CurrentTime;
            try {
                nodesAddedSinceLastRebuild = 0;
                nodesRemovedSinceLastRebuild = 0;
                force = false;
                Monitor.Enter(mgr);
                if (objectGeometry != null)
                    objectGeometry.Reset();
                else
                    objectGeometry = new StaticGeometry(mgr, name);
                // Dictionary mapping Material into a list of
                // ObjectNodes in which some submesh uses the material
                Dictionary<Material, MaterialAndNodeCounts> materialsUsedMap = new Dictionary<Material, MaterialAndNodeCounts>();
                lock(nodeDictionary) {
                    foreach (WorldEntity entity in nodeDictionary.Values) {
                        if (entity is ObjectNode) {
                            ObjectNode node = (ObjectNode)entity;
                            // For now, we only consider "props" that have an associated SceneNode
                            // and are direct descendants of the root scene node, and are of the right
                            // kind, i.e., don't have a perception radius if this static geometry is for
                            // little nodes, and vice versa.
            //                             log.DebugFormat("StaticGeometry.Rebuild: Examining node {0}, oid {1}, type {2}, sceneNode {3}, InStaticGeometry {4}, top-level {5}",
            //                                 node.Name, node.Oid, node.ObjectType, node.SceneNode, node.InStaticGeometry, node.SceneNode.Parent == mgr.RootSceneNode);
                            if (node.ObjectType == ObjectNodeType.Prop &&
                                (node.InStaticGeometry || (node.SceneNode != null && node.SceneNode.Parent == mgr.RootSceneNode)) &&
                                RightKind(node)) {
                                foreach (Material m in node.Entity.SubEntityMaterials) {
                                    MaterialAndNodeCounts nodesUsingMaterial;
                                    if (!materialsUsedMap.TryGetValue(m, out nodesUsingMaterial)) {
                                        nodesUsingMaterial = new MaterialAndNodeCounts();
                                        materialsUsedMap[m] = nodesUsingMaterial;
                                    }
                                    nodesUsingMaterial.materialUseCount++;
                                    int subMeshUseCount;
                                    Dictionary<ObjectNode, int> submeshUseCounts = nodesUsingMaterial.submeshUseCounts;
                                    if (!submeshUseCounts.TryGetValue(node, out subMeshUseCount))
                                        submeshUseCounts[node] = 1;
                                    else
                                        submeshUseCounts[node] = subMeshUseCount + 1;
                                }
                            }
                        }
                    }
                }

                // Now we have a count of uses of each material, and
                // for each node, the number of subentities that use the
                // material.  Now we need to calculate the number of
                // instance of sharings for each object node
                Dictionary<ObjectNode, bool> candidateNodes = new Dictionary<ObjectNode, bool>();
                foreach (MaterialAndNodeCounts counts in materialsUsedMap.Values) {
                    if (counts.materialUseCount > 1) {
                        foreach (KeyValuePair<ObjectNode, int> pair in counts.submeshUseCounts)
                            candidateNodes[pair.Key] = true;
                    }
                }
                Dictionary<ObjectNode, int> staticNodes = new Dictionary<ObjectNode, int>();
                foreach (KeyValuePair<ObjectNode, bool> pair in candidateNodes) {
                    ObjectNode candidate = pair.Key;
                    bool useIt = pair.Value;
                    if (useIt)
                        staticNodes[candidate] = 0;
                }
                if (staticNodes.Count == 0)
                    log.InfoFormat("StaticGeometryHelper.Rebuild: Didn't build static geometry {0} because object count was zero", name);
                else {
                    log.InfoFormat("StaticGeometryHelper.Rebuild: {0} ObjectNodes", staticNodes.Count);
                    foreach(ObjectNode staticNode in staticNodes.Keys) {
                        SceneNode sc = staticNode.SceneNode;
                        if (!staticNode.InStaticGeometry) {
                            sc.RemoveFromParent();
                            staticNode.InStaticGeometry = true;
                        }
                        log.DebugFormat("StaticGeometryHelper.Rebuild: Add node {0} with name {1} to static geometry",
                            staticNode.Oid, staticNode.Name);
                        objectGeometry.AddSceneNode(sc);
                    }
                }
                if (lastStaticNodes != null) {
                    foreach(ObjectNode node in lastStaticNodes.Keys) {
                        if (!staticNodes.ContainsKey(node)) {
                            // Only 1 instance of the mesh, so make sure that if in a former build it was in
                            // static geometry, that we add it back to the scene graph.
                            if (node.InStaticGeometry) {
                                SceneNode sn = node.SceneNode;
                                if (sn != null)
                                    mgr.RootSceneNode.AddChild(sn);
                                node.InStaticGeometry = false;
                            }
                        }
                    }
                }
                if (staticNodes.Count > 0)
                    objectGeometry.Build();
                lastStaticNodes = staticNodes;
                timeOfLastRebuild = TimeTool.CurrentTime;
            }
            finally {
                Monitor.Exit(mgr);
            }
            log.InfoFormat("StaticGeometryHelper.Rebuild: Rebuild of geometry '{0}' took {1} ms",
                name, TimeTool.CurrentTime - tickStart);
        }