Pārlūkot izejas kodu

Added local node & component ID's in preparation to networking.
Split rendering documentation on several pages.

Lasse Öörni 14 gadi atpakaļ
vecāks
revīzija
62c9aa3d9c

+ 4 - 2
Docs/GettingStarted.dox

@@ -149,11 +149,13 @@ Urho3D uses the following conventions and principles:
 
 - Raw pointers are used whenever possible in the classes' public API. This simplifies exposing functions & classes to script, and is relatively safe, because SharedPtr & WeakPtr use intrusive reference counting.
 
-- No C++ exceptions. Error return values (false / null pointer / dummy reference) are used instead. %Script exceptions are used when there is no other sensible way, such as with illegal array access.
+- No C++ exceptions. Error return values (false / null pointer / dummy reference) are used instead. %Script exceptions are used when there is no other sensible way, such as with out of bounds array access.
 
 - Feeding illegal data to public API functions, such out of range indices or null pointers, should not cause crashes or corruption. Instead errors are logged as appropriate.
 
-- Third party libraries are hidden from the public API as completely as possible.
+- For threading and multi-instance safety, no mutable static data (including singletons) or function-static data is allowed.
+
+- Third party libraries are included as source code into the main engine project. They are however hidden from the public API as completely as possible.
 
 For more details related to the C++ coding style, see also \ref CodingConventions "Coding conventions".
 

+ 55 - 14
Docs/Reference.dox

@@ -363,8 +363,6 @@ The rendering operations are divided into passes in the following order:
 - Post-opaque or "extra" rendering pass for materials that define that.
 - Transparent geometry rendering pass. Transparent, alpha-blended objects are sorted according to distance and rendered back-to-front to ensure correct blending.
 
-For details on how lighting opaque geometry differs between forward and deferred rendering, see \ref ForwardDeferred "Comparision of forward, light pre-pass and deferred rendering".
-
 \section Rendering_Drawable Rendering components
 
 The rendering-related components defined by the %Graphics library are:
@@ -381,7 +379,14 @@ The rendering-related components defined by the %Graphics library are:
 - Light: illuminates the scene. Can optionally cast shadows.
 - Zone: defines global properties like fogging and background color. The active zone is the one the Camera is currently in.
 
-\section Rendering_Materials Materials
+\section Rendering_Further Further details
+
+See also \ref Materials "Materials", \ref Lights "Lights and shadows", \ref Particles "Particle systems" and \ref AuxiliaryViews "Auxiliary views".
+
+For details on how lighting opaque geometry differs between forward and deferred rendering, see \ref ForwardDeferred "Comparision of forward, light pre-pass and deferred rendering".
+
+
+\page Materials Materials
 
 Material and Technique resources define how to render 3D scene geometry. On the disk, they are XML data. By default, materials exist in the CoreData/Materials & Data/Materials subdirectories, and techniques exist in the CoreData/Techniques subdirectory.
 
@@ -441,7 +446,8 @@ The passes are:
 
 Note that the technique does not need to enumerate shaders used for different geometry types (non-skinned, skinned, instanced, billboard) and light types (directional, point and spot, shadowed and non-shadowed.) Instead specific hardcoded shader variations are assumed to exist.
 
-\section Rendering_Lights Lights
+
+\page Lights Lights and shadows
 
 Lights in Urho3D can be directional, point, or spot lights. Shadow mapping is supported for all light types.
 
@@ -453,7 +459,17 @@ Spot lights have FOV & aspect ratio values like cameras to define the shape of t
 
 Both point and spot lights use an attenuation ramp texture to determine how the intensity varies with distance. In addition they have a shape texture, 2D for spot lights, and an optional cube texture for point lights. It is important that the spot light's shape texture has black at the borders, and has mipmapping disabled, otherwise there will be "bleeding" artifacts at the edges of the light cone.
 
-\section Rendering_ShadowedLights Shadowed lights
+\section Lights_LightCulling Light culling
+
+When occlusion is used, a light will automatically be culled if its bounding box is fully behind an occluder. However, directional lights have an infinite bounding box, and can not be culled this way.
+
+In forward rendering, it is possible to limit which objects are affected by each light, by calling \ref Drawable::SetLightMask "SetLightMask()" on both the light and the objects. The lightmasks of the light and objects are ANDed to check whether the light should have effect: the light will only illuminate an object if the result is nonzero. By default objects and lights have all bits set in their lightmask, thus passing this test always.
+
+\ref Zone "Zones" can also be used for light culling in a similar manner. The lightmask of the zone the camera is in will be ANDed with each light's lightmask to see whether the light should be included in rendering. This method of light culling works equally in deferred and forward rendering. By default a zone has all bits set in its lightmask.
+
+Care must be utilized when doing light culling with lightmasks, because they easily create situations where a light's influence is cut off unnaturally. However, they can be a great performance boost: for example if you imagine a multi-store building with lights, lights would normally need to have shadows enabled to not "bleed" into the lower floors, which would cost performance. The bleeding could also be prevented by using unique lightmask bits on the objects and lights of each floor.
+
+\section Lights_ShadowedLights Shadowed lights
 
 Shadow rendering is easily the most complex aspect of using lights, and therefore a wide range of parameters exists for controlling the shadows:
 
@@ -473,7 +489,7 @@ Additionally, there exist shadow fade distance, shadow intensity, shadow resolut
 
 - The shadow near/far ratio controls shadow camera near clip distance for point & spot lights. The default ratio is 0.002, which means a light with range 100 would have its shadow camera near plane set at the distance of 0.2. Set this as high as you can for better shadow depth resolution, but note that the bias parameters will likely have to be adjusted as well.
 
-\section Rendering_ShadowMapReuse Shadow map reuse
+\section Lights_ShadowMapReuse Shadow map reuse
 
 The Renderer can be configured to either reuse shadow maps, or not. To reuse is the default, use \ref Renderer::SetReuseShadowMaps "SetReuseShadowMaps()" to change.
 
@@ -481,17 +497,44 @@ When reuse is enabled, only one shadow texture of each shadow map size (full, ha
 
 When reuse is disabled, all shadow maps are rendered before the actual scene rendering. Now multiple shadow textures need to be reserved based on the desired number of simultaneous shadow casting lights. See the function \ref Renderer::SetNumShadowMaps "SetNumShadowMaps()". If there are not enough shadow textures, they will be assigned to the closest/brightest lights, and the rest will be rendered unshadowed. Now more texture memory is needed, but the advantage is that also transparent objects can receive shadows. The exception is shadowed point lights: they need stencil masking to split into the 6 shadow map sides, which conflicts with the need to render transparent objects back-to-front, instead of rendering per light.
 
-\section Rendering_LightCulling Light culling
 
-When occlusion is used, a light will automatically be culled if its bounding box is fully behind an occluder. However, directional lights have an infinite bounding box, and can not be culled this way.
+\page Particles %Particle systems
 
-In forward rendering, it is possible to limit which objects are affected by each light, by calling \ref Drawable::SetLightMask "SetLightMask()" on both the light and the objects. The lightmasks of the light and objects are ANDed to check whether the light should have effect: the light will only illuminate an object if the result is nonzero. By default objects and lights have all bits set in their lightmask, thus passing this test always.
+The ParticleEmitter class derives from BillboardSet to implement a particle system that updates automatically.
 
-\ref Zone "Zones" can also be used for light culling in a similar manner. The lightmask of the zone the camera is in will be ANDed with each light's lightmask to see whether the light should be included in rendering. This method of light culling works equally in deferred and forward rendering. By default a zone has all bits set in its lightmask.
+The particle system's properties can be set through a XML description file, see \ref ParticleEmitter::LoadParameters "LoadParameters()".
 
-Care must be utilized when doing light culling with lightmasks, because they easily create situations where a light's influence is cut off unnaturally. However, they can be a great performance boost: for example if you imagine a multi-store building with lights, lights would normally need to have shadows enabled to not "bleed" into the lower floors, which would cost performance. The bleeding could also be prevented by using unique lightmask bits on the objects and lights of each floor.
+Most of the parameters can take either a single value, or minimum and maximum values to allow for random variation. See below for all supported parameters:s
 
-\section Rendering_AuxiliaryViews Auxiliary views
+\code
+<particleemitter>
+    <material name="MaterialName" />
+    <sorting enable="true|false" />
+    <updateinvisible enable="true|false" />
+    <relative enable="true|false" />
+    <emittertype value="point|box|sphere" />
+    <emittersize value="x y z" />
+    <direction min="x1 y1 z1" max="x2 y2 z2" />
+    <constantforce value="x y z" />
+    <dampingforce value="x" />
+    <activetime value="t" />
+    <inactivetime value="t" />
+    <interval min="t1" max="t2" />
+    <particlesize min="x1 y1" max="x2 y2" />
+    <timetolive min="t1" max="t2" />
+    <velocity min="x1" max="x2" />
+    <rotation min="x1" max="x2" />
+    <rotationspeed min="x1" max="x2" />
+    <sizedelta add="x" mul="y" />
+    <color value="r g b a" />
+    <colorfade color="r g b a" time="t" />
+</particleemitter>
+\endcode
+
+Note: zero active or inactive time period means infinite. Instead of defining a single color element, several colorfade elements can be defined in time order to describe how the particles change color over time.
+
+
+\page AuxiliaryViews Auxiliary views
 
 Auxiliary views are viewports defined into a RenderSurface. These will be rendered whenever the texture containing the surface is visible, and can be typically used to implement for example reflections. The texture in question must have been created in rendertarget mode, see Texture's \ref Texture2D::SetSize "SetSize()" function.
 
@@ -1162,7 +1205,5 @@ Note: animations are stored using absolute bone transformations. Therefore only
 
 - Inline functions are defined inside the class definitions where possible, without using the inline keyword.
 
-- For threading and multi-instance safety, the only allowed form of static data is const static data outside function definitions.
-
 
 */

+ 7 - 7
Engine/Engine/APITemplates.h

@@ -329,14 +329,14 @@ template <class T> void RegisterComponent(asIScriptEngine* engine, const char* c
         engine->RegisterObjectMethod(className, "Node@+ get_node() const", asMETHODPR(T, GetNode, () const, Node*), asCALL_THISCALL);
 }
 
-static Component* NodeCreateComponent(const std::string& typeName, Node* ptr)
+static Component* NodeCreateComponent(const std::string& typeName, bool local, Node* ptr)
 {
-    return ptr->CreateComponent(ShortStringHash(typeName));
+    return ptr->CreateComponent(ShortStringHash(typeName), local);
 }
 
-static Component* NodeGetOrCreateComponent(const std::string& typeName, Node* ptr)
+static Component* NodeGetOrCreateComponent(const std::string& typeName, bool local, Node* ptr)
 {
-    return ptr->GetOrCreateComponent(ShortStringHash(typeName));
+    return ptr->GetOrCreateComponent(ShortStringHash(typeName), local);
 }
 
 static Component* NodeGetComponent(unsigned index, Node* ptr)
@@ -452,13 +452,13 @@ template <class T> void RegisterNode(asIScriptEngine* engine, const char* classN
     engine->RegisterObjectMethod(className, "void Roll(float, bool)", asMETHOD(T, Roll), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void Scale(float)", asMETHODPR(T, Scale, (float), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void Scale(const Vector3&in)", asMETHODPR(T, Scale, (const Vector3&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "Node@+ CreateChild(const String&in name = \"\")", asMETHODPR(T, CreateChild, (const std::string&), Node*), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "Node@+ CreateChild(const String&in name = \"\", bool local = false)", asMETHODPR(T, CreateChild, (const std::string&, bool), Node*), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void AddChild(Node@+)", asMETHOD(T, AddChild), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void RemoveChild(Node@+)", asMETHODPR(T, RemoveChild, (Node*), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void RemoveAllChildren()", asMETHOD(T, RemoveAllChildren), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void Remove()", asMETHOD(T, Remove), asCALL_THISCALL);
-    engine->RegisterObjectMethod(className, "Component@+ CreateComponent(const String&in)", asFUNCTION(NodeCreateComponent), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod(className, "Component@+ GetOrCreateComponent(const String&in)", asFUNCTION(NodeGetOrCreateComponent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "Component@+ CreateComponent(const String&in, bool local = false)", asFUNCTION(NodeCreateComponent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "Component@+ GetOrCreateComponent(const String&in, bool local = false)", asFUNCTION(NodeGetOrCreateComponent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "Array<Node@>@ GetChildren(bool recursive = false) const", asFUNCTION(NodeGetChildren), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "Array<Node@>@ GetChildrenWithComponent(const String&in, bool recursive = false) const", asFUNCTION(NodeGetChildrenWithComponent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "Array<Node@>@ GetScriptedChildren(bool recursive = false) const", asFUNCTION(NodeGetScriptedChildren), asCALL_CDECL_OBJLAST);

+ 12 - 0
Engine/Engine/SceneAPI.cpp

@@ -69,6 +69,16 @@ static bool SceneSaveXML(File* file, Scene* ptr)
 
 static void RegisterScene(asIScriptEngine* engine)
 {
+    engine->RegisterEnum("NetworkMode");
+    engine->RegisterEnumValue("NetworkMode", "NM_NONETWORK", NM_NONETWORK);
+    engine->RegisterEnumValue("NetworkMode", "NM_SERVER", NM_SERVER);
+    engine->RegisterEnumValue("NetworkMode", "NM_CLIENT", NM_CLIENT);
+    
+    engine->RegisterGlobalProperty("const uint FIRST_NONLOCAL_ID", (void*)&FIRST_NONLOCAL_ID);
+    engine->RegisterGlobalProperty("const uint LAST_NONLOCAL_ID", (void*)&LAST_NONLOCAL_ID);
+    engine->RegisterGlobalProperty("const uint FIRST_LOCAL_ID", (void*)&FIRST_LOCAL_ID);
+    engine->RegisterGlobalProperty("const uint LAST_LOCAL_ID", (void*)&LAST_LOCAL_ID);
+    
     RegisterNode<Scene>(engine, "Scene");
     RegisterObjectConstructor<Scene>(engine, "Scene");
     RegisterNamedObjectConstructor<Scene>(engine, "Scene");
@@ -79,6 +89,8 @@ static void RegisterScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "void Update(float)", asMETHOD(Scene, Update), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void set_active(bool)", asMETHOD(Scene, SetActive), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_active() const", asMETHOD(Scene, IsActive), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void set_networkMode(NetworkMode)", asMETHOD(Scene, SetNetworkMode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "NetworkMode get_networkMode() const", asMETHOD(Scene, GetNetworkMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Node", "Scene@+ get_scene() const", asMETHOD(Node, GetScene), asCALL_THISCALL);
     engine->RegisterGlobalFunction("Scene@+ get_scene()", asFUNCTION(GetScriptContextScene), asCALL_CDECL);
     

+ 26 - 18
Engine/Scene/Node.cpp

@@ -129,7 +129,7 @@ bool Node::Load(Deserializer& source)
     {
         VectorBuffer compBuffer(source, source.ReadVLE());
         ShortStringHash newType = compBuffer.ReadShortStringHash();
-        Component* newComponent = CreateComponent(newType, compBuffer.ReadUInt());
+        Component* newComponent = CreateComponent(newType, compBuffer.ReadUInt(), false);
         if (newComponent)
         {
             if (!newComponent->Load(compBuffer))
@@ -140,7 +140,7 @@ bool Node::Load(Deserializer& source)
     unsigned numChildren = source.ReadVLE();
     for (unsigned i = 0; i < numChildren; ++i)
     {
-        Node* newNode = CreateChild(source.ReadUInt());
+        Node* newNode = CreateChild(source.ReadUInt(), false);
         if (!newNode->Load(source))
             return false;
     }
@@ -192,7 +192,7 @@ bool Node::LoadXML(const XMLElement& source)
     while (compElem)
     {
         std::string typeName = compElem.GetString("type");
-        Component* newComponent = CreateComponent(ShortStringHash(compElem.GetString("type")), compElem.GetInt("id"));
+        Component* newComponent = CreateComponent(ShortStringHash(compElem.GetString("type")), compElem.GetInt("id"), false);
         if (newComponent)
         {
             if (!newComponent->LoadXML(compElem))
@@ -205,7 +205,7 @@ bool Node::LoadXML(const XMLElement& source)
     XMLElement childElem = source.GetChildElement("node");
     while (childElem)
     {
-        Node* newNode = CreateChild(compElem.GetInt("id"));
+        Node* newNode = CreateChild(compElem.GetInt("id"), false);
         if (!newNode->LoadXML(childElem))
             return false;
         
@@ -404,11 +404,10 @@ void Node::MarkDirty()
         (*i)->MarkDirty();
 }
 
-Node* Node::CreateChild(const std::string& name)
+Node* Node::CreateChild(const std::string& name, bool local)
 {
-    SharedPtr<Node> newNode(new Node(context_));
+    Node* newNode = CreateChild(0, local);
     newNode->SetName(name);
-    AddChild(newNode);
     return newNode;
 }
 
@@ -459,18 +458,18 @@ void Node::SetParent(Node* parent)
         parent->AddChild(this);
 }
 
-Component* Node::CreateComponent(ShortStringHash type)
+Component* Node::CreateComponent(ShortStringHash type, bool local)
 {
-    return CreateComponent(type, 0);
+    return CreateComponent(type, 0, local);
 }
 
-Component* Node::GetOrCreateComponent(ShortStringHash type)
+Component* Node::GetOrCreateComponent(ShortStringHash type, bool local)
 {
     Component* oldComponent = GetComponent(type);
     if (oldComponent)
         return oldComponent;
     else
-        return CreateComponent(type, 0);
+        return CreateComponent(type, 0, local);
 }
 
 void Node::RemoveComponent(Component* component)
@@ -661,7 +660,7 @@ Component* Node::GetComponent(ShortStringHash type, unsigned index) const
 }
 
 
-Component* Node::CreateComponent(ShortStringHash type, unsigned id)
+Component* Node::CreateComponent(ShortStringHash type, unsigned id, bool local)
 {
     // Make sure the object in question is a component
     SharedPtr<Component> newComponent = DynamicCast<Component>(context_->CreateObject(type));
@@ -671,23 +670,32 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id)
         return 0;
     }
     
-    // If zero ID specified, the scene will auto-assign
-    newComponent->SetID(id);
-    
     components_.push_back(newComponent);
+    
+    // If zero ID specified, let the scene assign
     if (scene_)
+    {
+        newComponent->SetID(id ? id : scene_->GetFreeComponentID(local));
         scene_->ComponentAdded(newComponent);
+    }
+    else
+        newComponent->SetID(id);
     
     newComponent->SetNode(this);
     newComponent->OnMarkedDirty(this);
-    
     return newComponent;
 }
 
-Node* Node::CreateChild(unsigned id)
+Node* Node::CreateChild(unsigned id, bool local)
 {
     SharedPtr<Node> newNode(new Node(context_));
-    newNode->SetID(id);
+    
+    // If zero ID specified, let the scene assign
+    if (scene_)
+        newNode->SetID(id ? id : scene_->GetFreeNodeID(local));
+    else
+        newNode->SetID(id);
+    
     AddChild(newNode);
     return newNode;
 }

+ 11 - 11
Engine/Scene/Node.h

@@ -98,7 +98,7 @@ public:
     /// Mark node and child nodes to need world transform recalculation. Notify listener components
     void MarkDirty();
     /// Create a child scene node
-    Node* CreateChild(const std::string& name = std::string());
+    Node* CreateChild(const std::string& name = std::string(), bool local = false);
     /// Add a child scene node
     void AddChild(Node* node);
     /// Remove a child scene node
@@ -110,9 +110,9 @@ public:
     /// Set parent scene node. Same as parent->AddChild(this)
     void SetParent(Node* parent);
     /// Create a component to this node
-    Component* CreateComponent(ShortStringHash type);
+    Component* CreateComponent(ShortStringHash type, bool local = false);
     /// Create a component to this node if it does not exist already
-    Component* GetOrCreateComponent(ShortStringHash type);
+    Component* GetOrCreateComponent(ShortStringHash type, bool local = false);
     /// Remove a component from this node
     void RemoveComponent(Component* component);
     /// Remove all components from this node
@@ -122,9 +122,9 @@ public:
     /// Remove listener component
     void RemoveListener(Component* component);
     /// Template version of creating a component
-    template <class T> T* CreateComponent();
+    template <class T> T* CreateComponent(bool local = false);
     /// Template version of getting or creating a component
-    template <class T> T* GetOrCreateComponent();
+    template <class T> T* GetOrCreateComponent(bool local = false);
     
     /// Return ID
     unsigned GetID() const { return id_; }
@@ -238,9 +238,9 @@ public:
     
 protected:
     /// Create a component with specific ID. Used internally
-    Component* CreateComponent(ShortStringHash type, unsigned id);
+    Component* CreateComponent(ShortStringHash type, unsigned id, bool local);
     /// Create a child node with specific ID. Used internally
-    Node* CreateChild(unsigned id);
+    Node* CreateChild(unsigned id, bool local);
     
 private:
     /// Recalculate the world transform
@@ -286,14 +286,14 @@ private:
     bool dirty_;
 };
 
-template <class T> T* Node::CreateComponent()
+template <class T> T* Node::CreateComponent(bool local)
 {
-    return static_cast<T*>(CreateComponent(T::GetTypeStatic()));
+    return static_cast<T*>(CreateComponent(T::GetTypeStatic(), local));
 }
 
-template <class T> T* Node::GetOrCreateComponent()
+template <class T> T* Node::GetOrCreateComponent(bool local)
 {
-    return static_cast<T*>(GetOrCreateComponent(T::GetTypeStatic()));
+    return static_cast<T*>(GetOrCreateComponent(T::GetTypeStatic(), local));
 }
 
 template <class T> void Node::GetChildrenWithComponent(std::vector<Node*>& dest, bool recursive) const

+ 76 - 20
Engine/Scene/Scene.cpp

@@ -36,11 +36,15 @@ OBJECTTYPESTATIC(Scene);
 
 Scene::Scene(Context* context) :
     Node(context),
-    nodeID_(1),
-    componentID_(1),
+    networkMode_(NM_NONETWORK),
+    nonLocalNodeID_(FIRST_NONLOCAL_ID),
+    nonLocalComponentID_(FIRST_NONLOCAL_ID),
+    localNodeID_(FIRST_LOCAL_ID),
+    localComponentID_(FIRST_LOCAL_ID),
     active_(true)
 {
     // Assign an ID to self so that nodes can refer to this node as a parent
+    SetID(GetFreeNodeID(false));
     NodeAdded(this);
     
     SubscribeToEvent(E_UPDATE, HANDLER(Scene, HandleUpdate));
@@ -57,6 +61,11 @@ void Scene::RegisterObject(Context* context)
 {
     context->RegisterFactory<Scene>();
     context->CopyBaseAttributes<Node, Scene>();
+    
+    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Node ID", nonLocalNodeID_, FIRST_NONLOCAL_ID);
+    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Component ID", nonLocalComponentID_, FIRST_NONLOCAL_ID);
+    ATTRIBUTE(Scene, VAR_INT, "Next Local Node ID", localNodeID_, FIRST_LOCAL_ID);
+    ATTRIBUTE(Scene, VAR_INT, "Next Local Component ID", localComponentID_, FIRST_LOCAL_ID);
 }
 
 bool Scene::Load(Deserializer& source)
@@ -148,22 +157,18 @@ void Scene::SetActive(bool enable)
     active_ = enable;
 }
 
+void Scene::SetNetworkMode(NetworkMode mode)
+{
+    networkMode_ = mode;
+}
+
 void Scene::NodeAdded(Node* node)
 {
     if ((!node) || (node->GetScene()))
         return;
     
-    // Assign ID if not already set
-    if (!node->GetID())
-    {
-        while (allNodes_.find(nodeID_) != allNodes_.end())
-            GetNextNodeID();
-        node->SetID(nodeID_);
-    }
-    
     node->setScene(this);
     allNodes_[node->GetID()] = node;
-    GetNextNodeID();
 }
 
 void Scene::NodeRemoved(Node* node)
@@ -181,16 +186,7 @@ void Scene::ComponentAdded(Component* component)
     if (!component)
         return;
     
-    // Assign ID if not already set
-    if (!component->GetID())
-    {
-        while (allComponents_.find(componentID_) != allComponents_.end())
-            GetNextComponentID();
-        component->SetID(componentID_);
-    }
-    
     allComponents_[component->GetID()] = component;
-    GetNextComponentID();
 }
 
 void Scene::ComponentRemoved(Component* component)
@@ -228,6 +224,66 @@ void Scene::HandleUpdate(StringHash eventType, VariantMap& eventData)
         Update(eventData[P_TIMESTEP].GetFloat());
 }
 
+unsigned Scene::GetFreeNodeID(bool local)
+{
+    if (!local)
+    {
+        for (;;)
+        {
+            if (allNodes_.find(nonLocalNodeID_) == allNodes_.end())
+                return nonLocalNodeID_;
+            
+            if (nonLocalNodeID_ != LAST_NONLOCAL_ID)
+                ++nonLocalNodeID_;
+            else
+                nonLocalNodeID_ = FIRST_NONLOCAL_ID;
+        }
+    }
+    else
+    {
+        for (;;)
+        {
+            if (allNodes_.find(localNodeID_) == allNodes_.end())
+                return localNodeID_;
+            
+            if (localNodeID_ != LAST_LOCAL_ID)
+                ++localNodeID_;
+            else
+                localNodeID_ = FIRST_LOCAL_ID;
+        }
+    }
+}
+
+unsigned Scene::GetFreeComponentID(bool local)
+{
+    if (!local)
+    {
+        for (;;)
+        {
+            if (allComponents_.find(nonLocalComponentID_) == allComponents_.end())
+                return nonLocalComponentID_;
+            
+            if (nonLocalComponentID_ != LAST_NONLOCAL_ID)
+                ++nonLocalComponentID_;
+            else
+                nonLocalComponentID_ = FIRST_NONLOCAL_ID;
+        }
+    }
+    else
+    {
+        for (;;)
+        {
+            if (allComponents_.find(localComponentID_) == allComponents_.end())
+                return localComponentID_;
+            
+            if (localComponentID_ != LAST_LOCAL_ID)
+                ++localComponentID_;
+            else
+                localComponentID_ = FIRST_LOCAL_ID;
+        }
+    }
+}
+
 void RegisterSceneLibrary(Context* context)
 {
     Node::RegisterObject(context);

+ 48 - 30
Engine/Scene/Scene.h

@@ -25,13 +25,30 @@
 
 #include "Node.h"
 
+/// First replicated node/component ID
+static const unsigned FIRST_NONLOCAL_ID = 0x1;
+/// Last replicated node/component ID
+static const unsigned LAST_NONLOCAL_ID = 0xffffff;
+/// First local node/component ID
+static const unsigned FIRST_LOCAL_ID = 0x01000000;
+/// Last local node/component ID
+static const unsigned LAST_LOCAL_ID = 0xffffffff;
+
+/// Scene's networking mode
+enum NetworkMode
+{
+    NM_NONETWORK,
+    NM_SERVER,
+    NM_CLIENT
+};
+
 /// Root scene node, represents the whole scene
 class Scene : public Node
 {
     OBJECT(Scene);
-
+    
     using Node::SaveXML;
-        
+    
 public:
     /// Construct
     Scene(Context* context);
@@ -53,50 +70,51 @@ public:
     bool SaveXML(Serializer& dest);
     /// Update scene
     void Update(float timeStep);
+    /// Set networking mode
+    void SetNetworkMode(NetworkMode mode);
     /// Set active flag. Only active scenes will be updated automatically
     void SetActive(bool enable);
-    /// Node added. Assign scene pointer & ID
-    void NodeAdded(Node* node);
-    /// Node removed. Remove ID assignment
-    void NodeRemoved(Node* node);
-    /// Component added. Assign ID
-    void ComponentAdded(Component* component);
-    /// Component removed. Remove ID assignment
-    void ComponentRemoved(Component* component);
     
-    /// Return active flag
-    bool IsActive() const { return active_; }
     /// Return node from the whole scene by ID, or null if not found
     Node* GetNodeByID(unsigned id) const;
     /// Return component from the whole scene by ID, or null if not found
     Component* GetComponentByID(unsigned id) const;
+    /// Return networking mode
+    NetworkMode GetNetworkMode() const { return networkMode_; }
+    /// Return active flag
+    bool IsActive() const { return active_; }
+    
+    /// Get free node ID, either non-local or local
+    unsigned GetFreeNodeID(bool local);
+    /// Get free component ID, either non-local or local
+    unsigned GetFreeComponentID(bool local);
+    /// Node added. Assign scene pointer and add to ID map
+    void NodeAdded(Node* node);
+    /// Node removed. Remove from ID map
+    void NodeRemoved(Node* node);
+    /// Component added. Add to ID map
+    void ComponentAdded(Component* component);
+    /// Component removed. Remove from ID map
+    void ComponentRemoved(Component* component);
     
 private:
     /// Handle the logic update event to update the scene, if active
     void HandleUpdate(StringHash eventType, VariantMap& eventData);
     
-    inline void GetNextNodeID()
-    {
-        ++nodeID_;
-        if (!nodeID_)
-            nodeID_ = 1;
-    }
-    
-    inline void GetNextComponentID()
-    {
-        ++componentID_;
-        if (!componentID_)
-            componentID_ = 1;
-    }
-    
-    /// Next free node ID
-    unsigned nodeID_;
-    /// Next free component ID
-    unsigned componentID_;
     /// Map of scene nodes by ID
     std::map<unsigned, Node*> allNodes_;
     /// Map of components by ID
     std::map<unsigned, Component*> allComponents_;
+    /// Networking mode
+    NetworkMode networkMode_;
+    /// Next free non-local node ID
+    unsigned nonLocalNodeID_;
+    /// Next free local node ID
+    unsigned localNodeID_;
+    /// Next free non-local component ID
+    unsigned nonLocalComponentID_;
+    /// Next free local component ID
+    unsigned localComponentID_;
     /// Active flag
     bool active_;
 };