Browse Source

Fixed UI related crashes in headless mode.
Fixed loading of materials in headless mode (just return success while doing nothing.)
Fixed EventType parameter of SendRemoteEvent() / BroadcastRemoteEvent() in the script API, should be a string.
Added smoothed motion for nodes in network client scene.
Added enum CreateMode to CreateChild() & CreateComponent() which replaces the "local" bool parameter.
Added possibility for client to spawn physics boxes in networked TestScene.

Lasse Öörni 14 years ago
parent
commit
ffad4ba005

+ 54 - 31
Bin/Data/Scripts/TestScene.as

@@ -9,15 +9,14 @@ bool clientMode = false;
 
 void Start()
 {
-    if (engine.headless)
+    if (!engine.headless)
     {
-        ErrorDialog("TestScene", "Headless mode is not supported. The program will now exit.");
-        engine.Exit();
-        return;
+        InitConsole();
+        InitUI();
     }
+    else
+        OpenConsoleWindow();
 
-    InitConsole();
-    InitUI();
     InitScene();
 
     SubscribeToEvent("Update", "HandleUpdate");
@@ -26,7 +25,8 @@ void Start()
     SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
     SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
-    
+    SubscribeToEvent("SpawnBox", "HandleSpawnBox");
+
     for (uint i = 0; i < arguments.length; ++i)
     {
         if (arguments[i] == "server")
@@ -190,7 +190,8 @@ void InitScene()
     camera = cameraNode.CreateComponent("Camera");
     cameraNode.position = Vector3(0, 2, 0);
 
-    renderer.viewports[0] = Viewport(testScene, camera);
+    if (!engine.headless)
+        renderer.viewports[0] = Viewport(testScene, camera);
 }
 
 void HandleUpdate(StringHash eventType, VariantMap& eventData)
@@ -346,33 +347,52 @@ void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
         ui.cursor.visible = false;
 
     // Test creating a new physics object
-    if (!clientMode && button == MOUSEB_LEFT && ui.GetElementAt(ui.cursorPosition, true) is null && ui.focusElement is null)
+    if (button == MOUSEB_LEFT && ui.GetElementAt(ui.cursorPosition, true) is null && ui.focusElement is null)
     {
-        Node@ newNode = testScene.CreateChild();
-        newNode.position = cameraNode.position;
-        newNode.rotation = cameraNode.rotation;
-        newNode.SetScale(0.1);
-
-        CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
-        shape.SetBox(Vector3(2, 2, 2), Vector3(), Quaternion());
-        shape.friction = 1.0;
-        shape.collisionGroup = 1;
-        shape.collisionMask = 3;
-
-        RigidBody@ body = newNode.CreateComponent("RigidBody");
-        body.angularMaxVelocity = 500.0;
-        body.linearVelocity = camera.upVector + camera.forwardVector * 10.0;
-        body.mass = 1;
-        
-        StaticModel@ object = newNode.CreateComponent("StaticModel");
-        object.model = cache.GetResource("Model", "Models/Box.mdl");
-        object.material = cache.GetResource("Material", "Materials/Test.xml");
-        object.castShadows = true;
-        object.shadowDistance = 150.0;
-        object.drawDistance = 200.0;
+        VariantMap eventData;
+        eventData["Pos"] = cameraNode.position;
+        eventData["Rot"] = cameraNode.rotation;
+
+        // If we are the client, send the spawn command as a remote event, else send locally
+        if (clientMode)
+        {
+            if (network.serverConnection !is null)
+                network.serverConnection.SendRemoteEvent("SpawnBox", true, eventData);
+        }
+        else
+            SendEvent("SpawnBox", eventData);
     }
 }
 
+void HandleSpawnBox(StringHash eventType, VariantMap& eventData)
+{
+    Vector3 position = eventData["Pos"].GetVector3();
+    Quaternion rotation = eventData["Rot"].GetQuaternion();
+
+    Node@ newNode = testScene.CreateChild();
+    newNode.position = position;
+    newNode.rotation = rotation;
+    newNode.SetScale(0.1);
+
+    CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
+    shape.SetBox(Vector3(2, 2, 2), Vector3(), Quaternion());
+    shape.friction = 1.0;
+    shape.collisionGroup = 1;
+    shape.collisionMask = 3;
+
+    RigidBody@ body = newNode.CreateComponent("RigidBody");
+    body.angularMaxVelocity = 500.0;
+    body.linearVelocity = rotation * Vector3(0.0, 1.0, 10.0);
+    body.mass = 1;
+
+    StaticModel@ object = newNode.CreateComponent("StaticModel");
+    object.model = cache.GetResource("Model", "Models/Box.mdl");
+    object.material = cache.GetResource("Material", "Materials/Test.xml");
+    object.castShadows = true;
+    object.shadowDistance = 150.0;
+    object.drawDistance = 200.0;
+}
+
 void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 {
     if (eventData["Button"].GetInt() == MOUSEB_RIGHT)
@@ -381,6 +401,9 @@ void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
 
 void HandlePostRenderUpdate()
 {
+    if (engine.headless)
+        return;
+
     // Draw rendering debug geometry without depth test to see the effect of occlusion
     if (drawDebug == 1)
         renderer.DrawDebugGeometry(false);

+ 26 - 17
Docs/ScriptAPI.dox

@@ -91,11 +91,13 @@
 - uint SCAN_FILES
 - uint SCAN_DIRS
 - uint SCAN_HIDDEN
-- uint AM_SERIALIZATION
-- uint AM_NETWORK
-- uint AM_BOTH
-- uint FIRST_NONLOCAL_ID
-- uint LAST_NONLOCAL_ID
+- uint AM_FILE
+- uint AM_NET
+- uint AM_DEFAULT
+- uint AM_LATESTDATA
+- uint AM_NOEDIT
+- uint FIRST_REPLICATED_ID
+- uint LAST_REPLICATED_ID
 - uint FIRST_LOCAL_ID
 - uint LAST_LOCAL_ID
 - uint DRAWABLE_GEOMETRY
@@ -1010,13 +1012,13 @@ Methods:<br>
 - void Roll(float, bool)
 - void Scale(float)
 - void Scale(const Vector3&)
-- Node@ CreateChild(const String& arg0 = "", bool arg1 = false)
+- Node@ CreateChild(const String& arg0 = "", CreateMode arg1 = REPLICATED)
 - void AddChild(Node@)
 - void RemoveChild(Node@)
 - void RemoveAllChildren()
 - void Remove()
-- Component@ CreateComponent(const String&, bool arg1 = false)
-- Component@ GetOrCreateComponent(const String&, bool arg1 = false)
+- Component@ CreateComponent(const String&, CreateMode arg1 = REPLICATED)
+- Component@ GetOrCreateComponent(const String&, CreateMode arg1 = REPLICATED)
 - Node@[]@ GetChildren(bool arg0 = false) const
 - Node@[]@ GetChildrenWithComponent(const String&, bool arg1 = false) const
 - Node@[]@ GetChildrenWithScript(bool arg0 = false) const
@@ -1040,10 +1042,13 @@ Properties:<br>
 - Quaternion& rotation
 - Vector3 direction
 - Vector3& scale
+- bool smoothed
 - Vector3 worldPosition (readonly)
 - Quaternion worldRotation (readonly)
 - Vector3 worldDirection (readonly)
 - Vector3 worldScale (readonly)
+- Vector3& targetPosition (readonly)
+- Vector3& targetRotation (readonly)
 - uint id (readonly)
 - uint numChildren (readonly)
 - uint numAllChildren (readonly)
@@ -1078,13 +1083,13 @@ Methods:<br>
 - void Roll(float, bool)
 - void Scale(float)
 - void Scale(const Vector3&)
-- Node@ CreateChild(const String& arg0 = "", bool arg1 = false)
+- Node@ CreateChild(const String& arg0 = "", CreateMode arg1 = REPLICATED)
 - void AddChild(Node@)
 - void RemoveChild(Node@)
 - void RemoveAllChildren()
 - void Remove()
-- Component@ CreateComponent(const String&, bool arg1 = false)
-- Component@ GetOrCreateComponent(const String&, bool arg1 = false)
+- Component@ CreateComponent(const String&, CreateMode arg1 = REPLICATED)
+- Component@ GetOrCreateComponent(const String&, CreateMode arg1 = REPLICATED)
 - Node@[]@ GetChildren(bool arg0 = false) const
 - Node@[]@ GetChildrenWithComponent(const String&, bool arg1 = false) const
 - Node@[]@ GetChildrenWithScript(bool arg0 = false) const
@@ -1100,7 +1105,6 @@ Methods:<br>
 - bool LoadAsyncXML(File@)
 - void StopAsyncLoading()
 - void Clear()
-- void ClearNonLocal()
 - void AddRequiredPackageFile(PackageFile@)
 - void ClearRequiredPackageFiles()
 - Component@ GetComponentByID(uint)
@@ -1116,10 +1120,13 @@ Properties:<br>
 - Quaternion& rotation
 - Vector3 direction
 - Vector3& scale
+- bool smoothed
 - Vector3 worldPosition (readonly)
 - Quaternion worldRotation (readonly)
 - Vector3 worldDirection (readonly)
 - Vector3 worldScale (readonly)
+- Vector3& targetPosition (readonly)
+- Vector3& targetRotation (readonly)
 - uint id (readonly)
 - uint numChildren (readonly)
 - uint numAllChildren (readonly)
@@ -1129,6 +1136,8 @@ Properties:<br>
 - String& name
 - Node@ parent
 - bool active
+- float smoothingConstant
+- float snapThreshold
 - bool asyncLoading (readonly)
 - float asyncProgress (readonly)
 - uint checksum (readonly)
@@ -3430,8 +3439,8 @@ Connection
 Methods:<br>
 - void SendMessage(int, bool, bool, const VectorBuffer&)
 - void SendMessage(int, uint, bool, bool, const VectorBuffer&)
-- void SendRemoteEvent(StringHash, bool, const VariantMap& arg2 = VariantMap ( ))
-- void SendRemoteEvent(Node@, StringHash, bool, const VariantMap& arg3 = VariantMap ( ))
+- void SendRemoteEvent(const String&, bool, const VariantMap& arg2 = VariantMap ( ))
+- void SendRemoteEvent(Node@, const String&, bool, const VariantMap& arg3 = VariantMap ( ))
 - void Disconnect(int arg0 = 0)
 - String ToString() const
 
@@ -3456,9 +3465,9 @@ Methods:<br>
 - void StopServer()
 - void BroadcastMessage(int, bool, bool, const VectorBuffer&)
 - void BroadcastMessage(int, uint, bool, bool, const VectorBuffer&)
-- void BroadcastRemoteEvent(StringHash, bool, const VariantMap& arg2 = VariantMap ( ))
-- void BroadcastRemoteEvent(Scene@, StringHash, bool, const VariantMap& arg3 = VariantMap ( ))
-- void BroadcastRemoteEvent(Node@, StringHash, bool, const VariantMap& arg3 = VariantMap ( ))
+- void BroadcastRemoteEvent(const String&, bool, const VariantMap& arg2 = VariantMap ( ))
+- void BroadcastRemoteEvent(Scene@, const String&, bool, const VariantMap& arg3 = VariantMap ( ))
+- void BroadcastRemoteEvent(Node@, const String&, bool, const VariantMap& arg3 = VariantMap ( ))
 
 Properties:<br>
 - ShortStringHash type (readonly)

+ 11 - 7
Engine/Engine/APITemplates.h

@@ -373,14 +373,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 String& typeName, bool local, Node* ptr)
+static Component* NodeCreateComponent(const String& typeName, CreateMode mode, Node* ptr)
 {
-    return ptr->CreateComponent(ShortStringHash(typeName), local);
+    return ptr->CreateComponent(ShortStringHash(typeName), mode);
 }
 
-static Component* NodeGetOrCreateComponent(const String& typeName, bool local, Node* ptr)
+static Component* NodeGetOrCreateComponent(const String& typeName, CreateMode mode, Node* ptr)
 {
-    return ptr->GetOrCreateComponent(ShortStringHash(typeName), local);
+    return ptr->GetOrCreateComponent(ShortStringHash(typeName), mode);
 }
 
 static Component* NodeGetComponent(unsigned index, Node* ptr)
@@ -496,13 +496,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 = \"\", bool local = false)", asMETHODPR(T, CreateChild, (const String&, bool), Node*), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "Node@+ CreateChild(const String&in name = \"\", CreateMode mode = REPLICATED)", asMETHODPR(T, CreateChild, (const String&, CreateMode), 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, 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, "Component@+ CreateComponent(const String&in, CreateMode mode = REPLICATED)", asFUNCTION(NodeCreateComponent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod(className, "Component@+ GetOrCreateComponent(const String&in, CreateMode mode = REPLICATED)", 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@>@ GetChildrenWithScript(bool recursive = false) const", asFUNCTION(NodeGetChildrenWithScript), asCALL_CDECL_OBJLAST);
@@ -519,10 +519,14 @@ template <class T> void RegisterNode(asIScriptEngine* engine, const char* classN
     engine->RegisterObjectMethod(className, "Vector3 get_direction() const", asMETHOD(T, GetDirection), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "void set_scale(const Vector3&in)", asMETHODPR(T, SetScale, (const Vector3&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "const Vector3& get_scale() const", asMETHOD(T, GetScale), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void set_smoothed(bool)", asMETHOD(T, SetSmoothed), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "bool get_smoothed() const", asMETHOD(T, IsSmoothed), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Vector3 get_worldPosition()", asMETHOD(T, GetWorldPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Quaternion get_worldRotation()", asMETHOD(T, GetWorldRotation), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Vector3 get_worldDirection()", asMETHOD(T, GetWorldDirection), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "Vector3 get_worldScale()", asMETHOD(T, GetWorldScale), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const Vector3& get_targetPosition() const", asMETHOD(T, GetTargetPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "const Vector3& get_targetRotation() const", asMETHOD(T, GetTargetRotation), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_id()", asMETHOD(T, GetID), asCALL_THISCALL);
     engine->RegisterObjectMethod(className, "uint get_numChildren() const", asFUNCTION(NodeGetNumChildrenNonRecursive), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod(className, "uint get_numAllChildren() const", asFUNCTION(NodeGetNumChildrenRecursive), asCALL_CDECL_OBJLAST);

+ 30 - 5
Engine/Engine/NetworkAPI.cpp

@@ -58,13 +58,23 @@ static void RegisterControls(asIScriptEngine* engine)
     engine->RegisterObjectProperty("Controls", "VariantMap extraData", offsetof(Controls, extraData_));
 }
 
+void SendRemoteEvent(const String& eventType, bool inOrder, const VariantMap& eventData, Connection* ptr)
+{
+    ptr->SendRemoteEvent(StringHash(eventType), inOrder, eventData);
+}
+
+void SendRemoteNodeEvent(Node* receiver, const String& eventType, bool inOrder, const VariantMap& eventData, Connection* ptr)
+{
+    ptr->SendRemoteEvent(receiver, StringHash(eventType), inOrder, eventData);
+}
+
 static void RegisterConnection(asIScriptEngine* engine)
 {
     RegisterObject<Connection>(engine, "Connection");
     engine->RegisterObjectMethod("Connection", "void SendMessage(int, bool, bool, const VectorBuffer&in)", asMETHODPR(Connection, SendMessage, (int, bool, bool, const VectorBuffer&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Connection", "void SendMessage(int, uint, bool, bool, const VectorBuffer&in)", asMETHODPR(Connection, SendMessage, (int, unsigned, bool, bool, const VectorBuffer&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Connection", "void SendRemoteEvent(StringHash, bool, const VariantMap&in eventData = VariantMap())", asMETHODPR(Connection, SendRemoteEvent, (StringHash, bool, const VariantMap&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Connection", "void SendRemoteEvent(Node@+, StringHash, bool, const VariantMap&in eventData = VariantMap())", asMETHODPR(Connection, SendRemoteEvent, (Node*, StringHash, bool, const VariantMap&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Connection", "void SendRemoteEvent(const String&in, bool, const VariantMap&in eventData = VariantMap())", asFUNCTION(SendRemoteEvent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Connection", "void SendRemoteEvent(Node@+, const String&in, bool, const VariantMap&in eventData = VariantMap())", asFUNCTION(SendRemoteNodeEvent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Connection", "void Disconnect(int waitMSec = 0)", asMETHOD(Connection, Disconnect), asCALL_THISCALL);
     engine->RegisterObjectMethod("Connection", "String ToString() const", asMETHOD(Connection, ToString), asCALL_THISCALL);
     engine->RegisterObjectMethod("Connection", "void set_scene(Scene@+)", asMETHOD(Connection, SetScene), asCALL_THISCALL);
@@ -113,6 +123,21 @@ static CScriptArray* NetworkGetClientConnections(Network* ptr)
         return 0;
 }
 
+void BroadcastRemoteEvent(const String& eventType, bool inOrder, const VariantMap& eventData, Network* ptr)
+{
+    ptr->BroadcastRemoteEvent(StringHash(eventType), inOrder, eventData);
+}
+
+void BroadcastRemoteSceneEvent(Scene* scene, const String& eventType, bool inOrder, const VariantMap& eventData, Network* ptr)
+{
+    ptr->BroadcastRemoteEvent(scene, StringHash(eventType), inOrder, eventData);
+}
+
+void BroadcastRemoteNodeEvent(Node* node, const String& eventType, bool inOrder, const VariantMap& eventData, Network* ptr)
+{
+    ptr->BroadcastRemoteEvent(node, StringHash(eventType), inOrder, eventData);
+}
+
 void RegisterNetwork(asIScriptEngine* engine)
 {
     RegisterObject<Network>(engine, "Network");
@@ -122,9 +147,9 @@ void RegisterNetwork(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Network", "void StopServer()", asMETHOD(Network, StopServer), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "void BroadcastMessage(int, bool, bool, const VectorBuffer&in)", asMETHODPR(Network, BroadcastMessage, (int, bool, bool, const VectorBuffer&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "void BroadcastMessage(int, uint, bool, bool, const VectorBuffer&in)", asMETHODPR(Network, BroadcastMessage, (int, unsigned, bool, bool, const VectorBuffer&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(StringHash, bool, const VariantMap&in eventData = VariantMap())", asMETHODPR(Network, BroadcastRemoteEvent, (StringHash, bool, const VariantMap&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(Scene@+, StringHash, bool, const VariantMap&in eventData = VariantMap())", asMETHODPR(Network, BroadcastRemoteEvent, (Scene*, StringHash, bool, const VariantMap&), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(Node@+, StringHash, bool, const VariantMap&in eventData = VariantMap())", asMETHODPR(Network, BroadcastRemoteEvent, (Node*, StringHash, bool, const VariantMap&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(const String&in, bool, const VariantMap&in eventData = VariantMap())", asFUNCTION(BroadcastRemoteEvent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(Scene@+, const String&in, bool, const VariantMap&in eventData = VariantMap())", asFUNCTION(BroadcastRemoteSceneEvent), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Network", "void BroadcastRemoteEvent(Node@+, const String&in, bool, const VariantMap&in eventData = VariantMap())", asFUNCTION(BroadcastRemoteNodeEvent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Network", "void set_updateFps(int)", asMETHOD(Network, SetUpdateFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "int get_updateFps() const", asMETHOD(Network, GetUpdateFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Network", "bool get_serverRunning() const", asMETHOD(Network, IsServerRunning), asCALL_THISCALL);

+ 10 - 2
Engine/Engine/SceneAPI.cpp

@@ -39,6 +39,10 @@ static void RegisterSerializable(asIScriptEngine* engine)
 
 static void RegisterNode(asIScriptEngine* engine)
 {
+    engine->RegisterEnum("CreateMode");
+    engine->RegisterEnumValue("CreateMode", "REPLICATED", REPLICATED);
+    engine->RegisterEnumValue("CreateMode", "LOCAL", LOCAL);
+    
     // Register Component first. At this point Node is not yet registered, so can not register GetNode for Component
     RegisterComponent<Component>(engine, "Component", false);
     RegisterNode<Node>(engine, "Node");
@@ -77,8 +81,8 @@ static CScriptArray* SceneGetRequiredPackageFiles(Scene* ptr)
 
 static void RegisterScene(asIScriptEngine* engine)
 {
-    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_REPLICATED_ID", (void*)&FIRST_REPLICATED_ID);
+    engine->RegisterGlobalProperty("const uint LAST_REPLICATED_ID", (void*)&LAST_REPLICATED_ID);
     engine->RegisterGlobalProperty("const uint FIRST_LOCAL_ID", (void*)&FIRST_LOCAL_ID);
     engine->RegisterGlobalProperty("const uint LAST_LOCAL_ID", (void*)&LAST_LOCAL_ID);
     
@@ -98,6 +102,10 @@ static void RegisterScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "Node@+ GetNodeByID(uint)", asMETHOD(Scene, GetNodeByID), 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_smoothingConstant(float)", asMETHOD(Scene, SetSmoothingConstant), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "float get_smoothingConstant() const", asMETHOD(Scene, GetSmoothingConstant), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void set_snapThreshold(float)", asMETHOD(Scene, SetSnapThreshold), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "float get_snapThreshold() const", asMETHOD(Scene, GetSnapThreshold), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "bool get_asyncLoading() const", asMETHOD(Scene, IsAsyncLoading), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "float get_asyncProgress() const", asMETHOD(Scene, GetAsyncProgress), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "uint get_checksum() const", asMETHOD(Scene, GetChecksum), asCALL_THISCALL);

+ 1 - 1
Engine/Graphics/AnimatedModel.cpp

@@ -561,7 +561,7 @@ void AnimatedModel::SetSkeleton(const Skeleton& skeleton, bool createBones)
             for (Vector<Bone>::Iterator i = bones.Begin(); i != bones.End(); ++i)
             {
                 // Create bones as local, as they are never to be directly synchronized over the network
-                Node* boneNode = node_->CreateChild(i->name_, true);
+                Node* boneNode = node_->CreateChild(i->name_, LOCAL);
                 boneNode->AddListener(this);
                 boneNode->SetTransform(i->initialPosition_, i->initialRotation_, i->initialScale_);
                 i->node_ = boneNode;

+ 6 - 6
Engine/Graphics/Material.cpp

@@ -109,9 +109,13 @@ bool Material::Load(Deserializer& source)
 {
     PROFILE(LoadMaterial);
     
-    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    // In headless mode, do not actually load the material, just return success
     Graphics* graphics = GetSubsystem<Graphics>();
-    if (!cache || !graphics)
+    if (!graphics)
+        return true;
+    
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    if (!cache)
         return false;
     
     SharedPtr<XMLFile> xml(new XMLFile(context_));
@@ -201,10 +205,6 @@ bool Material::Load(Deserializer& source)
 
 bool Material::Save(Serializer& dest)
 {
-    Graphics* graphics = GetSubsystem<Graphics>();
-    if (!graphics)
-        return false;
-    
     SharedPtr<XMLFile> xml(new XMLFile(context_));
     XMLElement materialElem = xml->CreateRoot("material");
     

+ 3 - 3
Engine/Graphics/Model.cpp

@@ -122,7 +122,7 @@ bool Model::Load(Deserializer& source)
                 memcpy(morphResetData.RawPtr(), &data[morphStart * vertexSize], morphCount * vertexSize);
                 buffer->SetMorphRangeResetData(morphResetData);
             }
-
+            
             // Copy the raw position data for CPU-side operations
             SharedArrayPtr<unsigned char> rawVertexData(new unsigned char[3 * sizeof(float) * vertexCount]);
             float* rawDest = (float*)rawVertexData.RawPtr();
@@ -157,12 +157,12 @@ bool Model::Load(Deserializer& source)
         if (data)
         {
             source.Read(data, indexCount * indexSize);
-
+            
             // Copy the raw index data for CPU-side operations
             SharedArrayPtr<unsigned char> rawIndexData(new unsigned char[indexSize * indexCount]);
             memcpy(rawIndexData.RawPtr(), data, indexSize * indexCount);
             rawIndexDatas.Push(rawIndexData);
-
+            
             buffer->Unlock();
         }
         else

+ 2 - 2
Engine/IO/File.cpp

@@ -161,7 +161,7 @@ unsigned File::Read(void* dest, unsigned size)
     {
         // Return to the position where the read began
         fseek((FILE*)handle_, position_ + offset_, SEEK_SET);
-        LOGERROR("Error while reading from file");
+        LOGERROR("Error while reading from file " + GetName());
         return 0;
     }
     
@@ -207,7 +207,7 @@ unsigned File::Write(const void* data, unsigned size)
     {
         // Return to the position where the write began
         fseek((FILE*)handle_, position_ + offset_, SEEK_SET);
-        LOGERROR("Error while writing to file");
+        LOGERROR("Error while writing to file " + GetName());
         return 0;
     }
     

+ 16 - 5
Engine/Network/Connection.cpp

@@ -129,9 +129,14 @@ void Connection::SetScene(Scene* newScene)
     if (scene_ == newScene)
         return;
     
-    // Reset the owner reference from the previous scene's nodes
     if (scene_)
+    {
+        // Disable smoothing in case scene is no longer used for networking
+        if (!isClient_)
+            scene_->SetSmoothed(false);
+        // Reset the owner reference from the previous scene's nodes
         scene_->ResetOwner(this);
+    }
     
     scene_ = newScene;
     sceneLoaded_ = false;
@@ -152,6 +157,8 @@ void Connection::SetScene(Scene* newScene)
     }
     else
     {
+        // Enable motion smoothing on the client network scene
+        scene_->SetSmoothed(true);
         // Make sure there is no existing async loading
         scene_->StopAsyncLoading();
         SubscribeToEvent(scene_, E_ASYNCLOADFINISHED, HANDLER(Connection, HandleAsyncLoadFinished));
@@ -353,11 +360,15 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
             if (!node)
             {
                 // Add initially to the root level. May be moved later
-                node = scene_->CreateChild(nodeID, false);
+                node = scene_->CreateChild(nodeID, REPLICATED);
             }
             
-            // Read initial attributes
+            // Enable motion smoothing on the node
+            node->SetSmoothed(true);
+            
+            // Read initial attributes, then snap the motion smoothing immediately to the end
             node->ReadDeltaUpdate(msg, deltaUpdateBits_);
+            node->UpdateSmoothing(1.0f, 0.0f);
             
             // Read initial user variables
             unsigned numVars = msg.ReadVLE();
@@ -385,7 +396,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
                 {
                     if (component)
                         component->Remove();
-                    component = node->CreateComponent(type, componentID, false);
+                    component = node->CreateComponent(type, componentID, REPLICATED);
                 }
                 
                 // If was unable to create the component, would desync the message and therefore have to abort
@@ -468,7 +479,7 @@ void Connection::ProcessSceneUpdate(int msgID, MemoryBuffer& msg)
                 {
                     if (component)
                         component->Remove();
-                    component = node->CreateComponent(type, componentID, false);
+                    component = node->CreateComponent(type, componentID, REPLICATED);
                 }
                 
                 // If was unable to create the component, would desync the message and therefore have to abort

+ 8 - 6
Engine/Physics/CollisionShape.cpp

@@ -560,12 +560,13 @@ void CollisionShape::UpdateTransform()
     else
     {
         // No rigid body. Must update the geometry transform manually
-        Vector3 nodePos = GetWorldPosition();
-        Quaternion nodeRot = GetWorldRotation();
-        Vector3 geoposition_ = nodePos + (nodeRot * (geometryScale_ * position_));
+        /// \todo Support parented nodes
+        Vector3 nodePos = node_->GetPosition();
+        Quaternion nodeRot = node_->GetRotation();
+        Vector3 geomPos = nodePos + (nodeRot * (geometryScale_ * position_));
         Quaternion geomRot = nodeRot * rotation_;
         
-        dGeomSetPosition(geometry_, geoposition_.x_, geoposition_.y_, geoposition_.z_);
+        dGeomSetPosition(geometry_, geomPos.x_, geomPos.y_, geomPos.z_);
         dGeomSetQuaternion(geometry_, geomRot.GetData());
     }
 }
@@ -780,7 +781,7 @@ void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 void CollisionShape::OnMarkedDirty(Node* node)
 {
     // If scale has changed, must recreate the geometry
-    if (GetWorldScale() != geometryScale_)
+    if (node->GetScale() != geometryScale_)
         CreateGeometry();
     else
         UpdateTransform();
@@ -826,7 +827,8 @@ void CollisionShape::CreateGeometry()
         geometry_ = 0;
     }
     
-    geometryScale_ = GetWorldScale();
+    /// \todo Support parented nodes
+    geometryScale_ = node_->GetScale();
     Vector3 size = size_ * geometryScale_;
     dSpaceID space = physicsWorld_->GetSpace();
     

+ 62 - 33
Engine/Physics/RigidBody.cpp

@@ -231,22 +231,26 @@ void RigidBody::ResetForces()
 
 Vector3 RigidBody::GetPosition() const
 {
+    /// \todo Support parented nodes
     if (body_)
     {
         const dReal* pos = dBodyGetPosition(body_);
         return Vector3(pos[0], pos[1], pos[2]);
     }
-    else return node_->GetWorldPosition();
+    // Use smoothing target position in case node is smoothed; if it is not, it is same as GetPosition()
+    else return node_->GetTargetPosition();
 }
 
 Quaternion RigidBody::GetRotation() const
 {
+    /// \todo Support parented nodes
     if (body_)
     {
         const dReal* quat = dBodyGetQuaternion(body_);
         return Quaternion(quat[0], quat[1], quat[2], quat[3]);
     }
-    else return node_->GetWorldRotation();
+    // Use smoothing target rotation in case node is smoothed; if it is not, it is same as GetRotation()
+    else return node_->GetTargetRotation();
 }
 
 Vector3 RigidBody::GetLinearVelocity() const
@@ -354,25 +358,31 @@ const PODVector<unsigned char>& RigidBody::GetNetAngularVelocityAttr() const
 
 void RigidBody::OnMarkedDirty(Node* node)
 {
+    // If the node is smoothed, do not use the dirty callback, but rather update manually during prestep
+    if (node_->IsSmoothed())
+        return;
+    
     // Clear the dirty flag by querying world position; this way we are sure to get the dirty notification immediately
     // also the next time the node transform changes
-    const Vector3& position = node_->GetWorldPosition();
-    Quaternion rotation = node_->GetWorldRotation();
+    node_->GetWorldPosition();
     
-    // Disregard node dirtying during the physics poststep, when rendering transform is synced from physics transform
-    if (inPostStep_)
+    // Disregard node dirtying during the physics poststep, when we set node transform ourselves
+    if (inPostStep_ || !body_)
         return;
     
-    if (body_)
+    /// \todo Support parented nodes
+    const Vector3& currentPosition = *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
+    const Quaternion& currentRotation = *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
+    const Vector3& newPosition = node_->GetPosition();
+    const Quaternion& newRotation = node_->GetRotation();
+    
+    if (newPosition != currentPosition || newRotation != currentRotation)
     {
-        if (GetPosition() != position || GetRotation() != rotation)
-        {
-            SetActive(true);
-            dBodySetPosition(body_, position.x_, position.y_, position.z_);
-            dBodySetQuaternion(body_, rotation.GetData());
-        }
-        previousPosition_ = position;
-        previousRotation_ = rotation;
+        SetActive(true);
+        dBodySetPosition(body_, newPosition.x_, newPosition.y_, newPosition.z_);
+        dBodySetQuaternion(body_, newRotation.GetData());
+        previousPosition_ = newPosition;
+        previousRotation_ = newRotation;
     }
 }
 
@@ -395,13 +405,31 @@ void RigidBody::OnNodeSet(Node* node)
 
 void RigidBody::PreStep()
 {
-    // Store the previous position for interpolation
-    if (body_)
+    if (!body_)
+        return;
+    
+    const Vector3& currentPosition = *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
+    const Quaternion& currentRotation = *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
+    
+    /// \todo Support parented nodes
+    if (!node_->IsSmoothed())
     {
-        const dReal* pos = dBodyGetPosition(body_);
-        const dReal* quat = dBodyGetQuaternion(body_);
-        previousPosition_ = Vector3(pos[0], pos[1], pos[2]);
-        previousRotation_ = Quaternion(quat[0], quat[1], quat[2], quat[3]);
+        // If no smoothing, store the current body position for interpolation
+        previousPosition_ = currentPosition;
+        previousRotation_ = currentRotation;
+    }
+    else
+    {
+        // If smoothing is active, get the node's target transform and check manually if it has changed
+        if (node_->GetTargetPosition() != currentPosition || node_->GetTargetRotation() != currentRotation)
+        {
+            const Vector3& newPosition = node_->GetTargetPosition();
+            const Quaternion& newRotation = node_->GetTargetRotation();
+            
+            SetActive(true);
+            dBodySetPosition(body_, newPosition.x_, newPosition.y_, newPosition.z_);
+            dBodySetQuaternion(body_, newRotation.GetData());
+        }
     }
 }
 
@@ -411,14 +439,15 @@ void RigidBody::PostStep(float t)
     {
         inPostStep_ = true;
         
-        const dReal* pos = dBodyGetPosition(body_);
-        const dReal* quat = dBodyGetQuaternion(body_);
-        Vector3 currentPosition(pos[0], pos[1], pos[2]);
-        Quaternion currentRotation(quat[0], quat[1], quat[2], quat[3]);
+        const Vector3& currentPosition = *reinterpret_cast<const Vector3*>(dBodyGetPosition(body_));
+        const Quaternion& currentRotation = *reinterpret_cast<const Quaternion*>(dBodyGetQuaternion(body_));
         
-        /// \todo If the node is parented, transform will not be set correctly
-        node_->SetPosition(previousPosition_.Lerp(currentPosition, t));
-        node_->SetRotation(previousRotation_.Slerp(currentRotation, t));
+        // If node already has motion smoothing enabled, do not do substep interpolation
+        /// \todo Support parented nodes
+        if (!node_->IsSmoothed())
+            node_->SetTransform(previousPosition_.Lerp(currentPosition, t), previousRotation_.Slerp(currentRotation, t));
+        else
+            node_->SetTransform(currentPosition, currentRotation);
         
         inPostStep_ = false;
     }
@@ -439,9 +468,9 @@ void RigidBody::CreateBody()
         // Set the user data pointer
         dBodySetData(body_, this);
         
-        // Set rendering transform as the initial transform
-        const Vector3& position = node_->GetWorldPosition();
-        Quaternion rotation = node_->GetWorldRotation();
+        // Set initial transform. Use target position in case the node is smoothed
+        const Vector3& position = node_->GetTargetPosition();
+        Quaternion rotation = node_->GetTargetRotation();
         dBodySetPosition(body_, position.x_, position.y_, position.z_);
         dBodySetQuaternion(body_, rotation.GetData());
         previousPosition_ = position;
@@ -500,8 +529,8 @@ void RigidBody::UpdateMass()
         CollisionShape* shape = shapes[i];
         
         dMass subMass;
-        Vector3 size = shape->GetSize() * GetWorldScale();
-        Vector3 offset = shape->GetPosition() * GetWorldScale();
+        Vector3 size = shape->GetSize() * node_->GetScale();
+        Vector3 offset = shape->GetPosition() * node_->GetScale();
         
         switch (shape->GetShapeType())
         {

+ 159 - 41
Engine/Scene/Node.cpp

@@ -46,8 +46,12 @@ Node::Node(Context* context) :
     rotation_(Quaternion::IDENTITY),
     scale_(Vector3::UNITY),
     worldTransform_(Matrix3x4::IDENTITY),
+    targetPosition_(Vector3::ZERO),
+    targetRotation_(Quaternion::IDENTITY),
     rotateCount_(0),
-    dirty_(false)
+    smoothingFlags_(SMOOTH_NONE),
+    dirty_(false),
+    smoothed_(false)
 {
 }
 
@@ -180,17 +184,33 @@ void Node::SetName(const String& name)
 
 void Node::SetPosition(const Vector3& position)
 {
-    position_ = position;
-    if (!dirty_)
-        MarkDirty();
+    if (!smoothed_)
+    {
+        position_ = position;
+        if (!dirty_)
+            MarkDirty();
+    }
+    else
+    {
+        targetPosition_ = position;
+        smoothingFlags_ |= SMOOTH_POSITION;
+    }
 }
 
 void Node::SetRotation(const Quaternion& rotation)
 {
-    rotation_ = rotation;
+    if (!smoothed_)
+    {
+        rotation_ = rotation;
+        if (!dirty_)
+            MarkDirty();
+    }
+    else
+    {
+        targetRotation_ = rotation;
+        smoothingFlags_ |= SMOOTH_ROTATION;
+    }
     rotateCount_ = 0;
-    if (!dirty_)
-        MarkDirty();
 }
 
 void Node::SetDirection(const Vector3& direction)
@@ -214,58 +234,115 @@ void Node::SetScale(const Vector3& scale)
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation)
 {
-    position_ = position;
-    rotation_ = rotation;
+    if (!smoothed_)
+    {
+        position_ = position;
+        rotation_ = rotation;
+        if (!dirty_)
+            MarkDirty();
+    }
+    else
+    {
+        targetPosition_ = position;
+        targetRotation_ = rotation;
+        smoothingFlags_ |= SMOOTH_POSITION | SMOOTH_ROTATION;
+    }
     rotateCount_ = 0;
-    if (!dirty_)
-        MarkDirty();
 }
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation, float scale)
 {
-    position_ = position;
-    rotation_ = rotation;
-    scale_ = Vector3(scale, scale, scale);
+    if (!smoothed_)
+    {
+        position_ = position;
+        rotation_ = rotation;
+    }
+    else
+    {
+        targetPosition_ = position;
+        targetRotation_ = rotation;
+        smoothingFlags_ |= SMOOTH_POSITION | SMOOTH_ROTATION;
+    }
     rotateCount_ = 0;
+    scale_ = Vector3(scale, scale, scale);
     if (!dirty_)
         MarkDirty();
 }
 
 void Node::SetTransform(const Vector3& position, const Quaternion& rotation, const Vector3& scale)
 {
-    position_ = position;
-    rotation_ = rotation;
-    scale_ = scale;
+    if (!smoothed_)
+    {
+        position_ = position;
+        rotation_ = rotation;
+    }
+    else
+    {
+        targetPosition_ = position;
+        targetRotation_ = rotation;
+        smoothingFlags_ |= SMOOTH_POSITION | SMOOTH_ROTATION;
+    }
     rotateCount_ = 0;
+    scale_ = scale;
     if (!dirty_)
         MarkDirty();
 }
 
 void Node::Translate(const Vector3& delta)
 {
-    position_ += delta;
-    if (!dirty_)
-        MarkDirty();
+    if (!smoothed_)
+    {
+        position_ += delta;
+        if (!dirty_)
+            MarkDirty();
+    }
+    else
+    {
+        targetPosition_ += delta;
+        smoothingFlags_ |= SMOOTH_POSITION;
+    }
 }
 
 void Node::TranslateRelative(const Vector3& delta)
 {
-    position_ += rotation_ * delta;
-    if (!dirty_)
-        MarkDirty();
+    if (!smoothed_)
+    {
+        position_ += rotation_ * delta;
+        if (!dirty_)
+            MarkDirty();
+    }
+    else
+    {
+        targetPosition_ += targetRotation_ * delta;
+        smoothingFlags_ |= SMOOTH_POSITION;
+    }
 }
 
 void Node::Rotate(const Quaternion& delta, bool fixedAxis)
 {
-    if (!fixedAxis)
-        rotation_ = rotation_ * delta;
+    if (!smoothed_)
+    {
+        if (!fixedAxis)
+            rotation_ = rotation_ * delta;
+        else
+            rotation_ = delta * rotation_;
+    }
     else
-        rotation_ = delta * rotation_;
+    {
+        if (!fixedAxis)
+            targetRotation_ = targetRotation_ * delta;
+        else
+            targetRotation_ = delta * targetRotation_;
+        smoothingFlags_ |= SMOOTH_ROTATION;
+    }
     
     ++rotateCount_;
     if (rotateCount_ >= NORMALIZE_ROTATION_EVERY)
     {
-        rotation_.Normalize();
+        if (!smoothed_)
+            rotation_.Normalize();
+        else
+            targetRotation_.Normalize();
         rotateCount_ = 0;
     }
     
@@ -302,6 +379,11 @@ void Node::Scale(const Vector3& scale)
         MarkDirty();
 }
 
+void Node::SetSmoothed(bool enable)
+{
+    smoothed_ = enable;
+}
+
 void Node::MarkDirty()
 {
     dirty_ = true;
@@ -323,9 +405,9 @@ void Node::MarkDirty()
         (*i)->MarkDirty();
 }
 
-Node* Node::CreateChild(const String& name, bool local)
+Node* Node::CreateChild(const String& name, CreateMode mode)
 {
-    Node* newNode = CreateChild(0, local);
+    Node* newNode = CreateChild(0, mode);
     newNode->SetName(name);
     return newNode;
 }
@@ -371,18 +453,18 @@ void Node::RemoveAllChildren()
         RemoveChild(children_.End() - 1);
 }
 
-Component* Node::CreateComponent(ShortStringHash type, bool local)
+Component* Node::CreateComponent(ShortStringHash type, CreateMode mode)
 {
-    return CreateComponent(type, 0, local);
+    return CreateComponent(type, 0, mode);
 }
 
-Component* Node::GetOrCreateComponent(ShortStringHash type, bool local)
+Component* Node::GetOrCreateComponent(ShortStringHash type, CreateMode mode)
 {
     Component* oldComponent = GetComponent(type);
     if (oldComponent)
         return oldComponent;
     else
-        return CreateComponent(type, 0, local);
+        return CreateComponent(type, 0, mode);
 }
 
 void Node::RemoveComponent(Component* component)
@@ -682,6 +764,42 @@ const PODVector<unsigned char>& Node::GetNetParentAttr() const
     return attrBuffer_.GetBuffer();
 }
 
+void Node::UpdateSmoothing(float constant, float squaredSnapThreshold)
+{
+    if (!smoothed_ || !smoothingFlags_)
+        return;
+    
+    if (smoothingFlags_ & SMOOTH_POSITION)
+    {
+        // If position snaps, snap everything to the end
+        float delta = (position_ - targetPosition_).LengthSquared();
+        if (delta > squaredSnapThreshold)
+            constant = 1.0f;
+        
+        if (delta < M_EPSILON || constant >= 1.0f)
+        {
+            position_ = targetPosition_;
+            smoothingFlags_ &= ~SMOOTH_POSITION;
+        }
+        else
+            position_ = position_.Lerp(targetPosition_, constant);
+    }
+    if (smoothingFlags_ & SMOOTH_ROTATION)
+    {
+        float delta = (rotation_ - targetRotation_).LengthSquared();
+        if (delta < M_EPSILON || constant >= 1.0f)
+        {
+            rotation_ = targetRotation_;
+            smoothingFlags_ &= ~SMOOTH_ROTATION;
+        }
+        else
+            rotation_ = rotation_.Slerp(targetRotation_, constant);
+    }
+    
+    if (!dirty_)
+        MarkDirty();
+}
+
 bool Node::Load(Deserializer& source, bool readChildren)
 {
     // Remove all children and components first in case this is not a fresh load
@@ -697,7 +815,7 @@ bool Node::Load(Deserializer& source, bool readChildren)
     {
         VectorBuffer compBuffer(source, source.ReadVLE());
         ShortStringHash newType = compBuffer.ReadShortStringHash();
-        Component* newComponent = CreateComponent(newType, compBuffer.ReadUInt(), false);
+        Component* newComponent = CreateComponent(newType, compBuffer.ReadUInt(), REPLICATED);
         if (newComponent)
         {
             if (!newComponent->Load(compBuffer))
@@ -711,7 +829,7 @@ bool Node::Load(Deserializer& source, bool readChildren)
     unsigned numChildren = source.ReadVLE();
     for (unsigned i = 0; i < numChildren; ++i)
     {
-        Node* newNode = CreateChild(source.ReadUInt(), false);
+        Node* newNode = CreateChild(source.ReadUInt(), REPLICATED);
         if (!newNode->Load(source))
             return false;
     }
@@ -732,7 +850,7 @@ bool Node::LoadXML(const XMLElement& source, bool readChildren)
     while (compElem)
     {
         String typeName = compElem.GetString("type");
-        Component* newComponent = CreateComponent(ShortStringHash(compElem.GetString("type")), compElem.GetInt("id"), false);
+        Component* newComponent = CreateComponent(ShortStringHash(compElem.GetString("type")), compElem.GetInt("id"), REPLICATED);
         if (newComponent)
         {
             if (!newComponent->LoadXML(compElem))
@@ -748,7 +866,7 @@ bool Node::LoadXML(const XMLElement& source, bool readChildren)
     XMLElement childElem = source.GetChild("node");
     while (childElem)
     {
-        Node* newNode = CreateChild(childElem.GetInt("id"), false);
+        Node* newNode = CreateChild(childElem.GetInt("id"), REPLICATED);
         if (!newNode->LoadXML(childElem))
             return false;
         
@@ -758,7 +876,7 @@ bool Node::LoadXML(const XMLElement& source, bool readChildren)
     return true;
 }
 
-Component* Node::CreateComponent(ShortStringHash type, unsigned id, bool local)
+Component* Node::CreateComponent(ShortStringHash type, unsigned id, CreateMode mode)
 {
     // Make sure the object in question is a component
     SharedPtr<Component> newComponent = DynamicCast<Component>(context_->CreateObject(type));
@@ -773,7 +891,7 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id, bool local)
     // If zero ID specified, let the scene assign
     if (scene_)
     {
-        newComponent->SetID(id ? id : scene_->GetFreeComponentID(local));
+        newComponent->SetID(id ? id : scene_->GetFreeComponentID(mode));
         scene_->ComponentAdded(newComponent);
     }
     else
@@ -784,13 +902,13 @@ Component* Node::CreateComponent(ShortStringHash type, unsigned id, bool local)
     return newComponent;
 }
 
-Node* Node::CreateChild(unsigned id, bool local)
+Node* Node::CreateChild(unsigned id, CreateMode mode)
 {
     SharedPtr<Node> newNode(new Node(context_));
     
     // If zero ID specified, let the scene assign
     if (scene_)
-        newNode->SetID(id ? id : scene_->GetFreeNodeID(local));
+        newNode->SetID(id ? id : scene_->GetFreeNodeID(mode));
     else
         newNode->SetID(id);
     

+ 46 - 14
Engine/Scene/Node.h

@@ -31,6 +31,20 @@ class Component;
 class Connection;
 class Scene;
 
+/// Component and child node creation mode for networking
+enum CreateMode
+{
+    REPLICATED = 0,
+    LOCAL = 1
+};
+
+// No ongoing smoothing
+static const unsigned SMOOTH_NONE = 0;
+// Ongoing position smoothing
+static const unsigned SMOOTH_POSITION = 1;
+// Ongoing rotation smoothing
+static const unsigned SMOOTH_ROTATION = 2;
+
 /// Scene node that may contain components and child nodes
 class Node : public Serializable
 {
@@ -93,10 +107,12 @@ public:
     void Scale(float scale);
     /// Modify scale
     void Scale(const Vector3& scale);
+    /// Enable or disable motion smoothing
+    void SetSmoothed(bool enable);
     /// Mark node and child nodes to need world transform recalculation. Notify listener components
     void MarkDirty();
     /// Create a child scene node
-    Node* CreateChild(const String& name = String(), bool local = false);
+    Node* CreateChild(const String& name = String(), CreateMode mode = REPLICATED);
     /// Add a child scene node
     void AddChild(Node* node);
     /// Remove a child scene node
@@ -104,9 +120,9 @@ public:
     /// Remove all child scene nodes
     void RemoveAllChildren();
     /// Create a component to this node
-    Component* CreateComponent(ShortStringHash type, bool local = false);
+    Component* CreateComponent(ShortStringHash type, CreateMode mode = REPLICATED);
     /// Create a component to this node if it does not exist already
-    Component* GetOrCreateComponent(ShortStringHash type, bool local = false);
+    Component* GetOrCreateComponent(ShortStringHash type, CreateMode mode = REPLICATED);
     /// Remove a component from this node
     void RemoveComponent(Component* component);
     /// Remove all components from this node
@@ -120,9 +136,9 @@ public:
     /// Set parent scene node. Same as parent->AddChild(this)
     void SetParent(Node* parent);
     /// Template version of creating a component
-    template <class T> T* CreateComponent(bool local = false);
+    template <class T> T* CreateComponent(CreateMode mode = REPLICATED);
     /// Template version of getting or creating a component
-    template <class T> T* GetOrCreateComponent(bool local = false);
+    template <class T> T* GetOrCreateComponent(CreateMode mode = REPLICATED);
     
     /// Return ID
     unsigned GetID() const { return id_; }
@@ -134,12 +150,16 @@ public:
     Node* GetParent() const { return parent_; }
     /// Return scene
     Scene* GetScene() const { return scene_; }
-    /// Return owner connection in multiplayer
+    /// Return owner connection in networking
     Connection* GetOwner() const { return owner_; }
     /// Return position
     const Vector3& GetPosition() const { return position_; }
     /// Return rotation
     const Quaternion& GetRotation() const { return rotation_; }
+    /// Return unsmoothed (target) position
+    const Vector3& GetTargetPosition() const { return smoothed_ ? targetPosition_ : position_; }
+    /// Return unsmoothed (target) rotation
+    const Quaternion& GetTargetRotation() const { return smoothed_ ? targetRotation_ : rotation_; }
     /// Return direction. Identity rotation equals positive Z
     Vector3 GetDirection() const { return rotation_ * Vector3::FORWARD; }
     /// Return scale
@@ -194,6 +214,8 @@ public:
     
     /// Return whether transform has changed and world transform needs recalculation
     bool IsDirty() const { return dirty_; }
+    /// Return whether motion smoothing is enabled
+    bool IsSmoothed() const { return smoothed_; }
     /// Return number of child scene nodes
     unsigned GetNumChildren(bool recursive = false) const;
     /// Return immediate child scene nodes
@@ -241,7 +263,7 @@ public:
     void SetID(unsigned id);
     /// Set scene. Called by Scene
     void SetScene(Scene* scene);
-    /// Set owner connection for multiplayer
+    /// Set owner connection for networking
     void SetOwner(Connection* owner);
     /// Set network rotation attribute
     void SetNetRotationAttr(const PODVector<unsigned char>& value);
@@ -251,6 +273,8 @@ public:
     const PODVector<unsigned char>& GetNetRotationAttr() const;
     /// Return network parent attribute
     const PODVector<unsigned char>& GetNetParentAttr() const;
+    /// Update motion smoothing. Called by Scene
+    void UpdateSmoothing(float constant, float squaredSnapThreshold);
     
     /// User variables
     VariantMap vars_;
@@ -261,9 +285,9 @@ protected:
     /// Load components from XML data and optionally load child nodes. Used internally
     bool LoadXML(const XMLElement& source, bool loadChildren);
     /// Create a component with specific ID. Used internally
-    Component* CreateComponent(ShortStringHash type, unsigned id, bool local);
+    Component* CreateComponent(ShortStringHash type, unsigned id, CreateMode mode);
     /// Create a child node with specific ID. Used internally
-    Node* CreateChild(unsigned id, bool local);
+    Node* CreateChild(unsigned id, CreateMode mode);
     
 private:
     /// Recalculate the world transform
@@ -281,7 +305,7 @@ private:
     Node* parent_;
     /// Scene (root node)
     Scene* scene_;
-    /// Owner connection in multiplayer
+    /// Owner connection in networking
     Connection* owner_;
     /// Position
     Vector3 position_;
@@ -291,6 +315,10 @@ private:
     Vector3 scale_;
     /// World-space transform matrix
     Matrix3x4 worldTransform_;
+    /// Target position for network motion smoothing
+    Vector3 targetPosition_;
+    /// Target rotation for network motion smoothing
+    Quaternion targetRotation_;
     /// Name
     String name_;
     /// Name hash
@@ -305,18 +333,22 @@ private:
     mutable VectorBuffer attrBuffer_;
     /// Consecutive rotation count for rotation renormalization
     unsigned char rotateCount_;
+    /// Active smoothing flags
+    unsigned char smoothingFlags_;
     /// World transform needs update flag
     bool dirty_;
+    /// Smoothed motion flag
+    bool smoothed_;
 };
 
-template <class T> T* Node::CreateComponent(bool local)
+template <class T> T* Node::CreateComponent(CreateMode mode)
 {
-    return static_cast<T*>(CreateComponent(T::GetTypeStatic(), local));
+    return static_cast<T*>(CreateComponent(T::GetTypeStatic(), mode));
 }
 
-template <class T> T* Node::GetOrCreateComponent(bool local)
+template <class T> T* Node::GetOrCreateComponent(CreateMode mode)
 {
-    return static_cast<T*>(GetOrCreateComponent(T::GetTypeStatic(), local));
+    return static_cast<T*>(GetOrCreateComponent(T::GetTypeStatic(), mode));
 }
 
 template <class T> void Node::GetChildrenWithComponent(PODVector<Node*>& dest, bool recursive) const

+ 54 - 26
Engine/Scene/Scene.cpp

@@ -35,21 +35,25 @@
 
 static const int ASYNC_LOAD_MIN_FPS = 50;
 static const int ASYNC_LOAD_MAX_MSEC = (int)(1000.0f / ASYNC_LOAD_MIN_FPS);
+static const float DEFAULT_SMOOTHING_CONSTANT = 50.0f;
+static const float DEFAULT_SNAP_THRESHOLD = 1.0f;
 
 OBJECTTYPESTATIC(Scene);
 
 Scene::Scene(Context* context) :
     Node(context),
-    nonLocalNodeID_(FIRST_NONLOCAL_ID),
-    nonLocalComponentID_(FIRST_NONLOCAL_ID),
+    replicatedNodeID_(FIRST_REPLICATED_ID),
+    replicatedComponentID_(FIRST_REPLICATED_ID),
     localNodeID_(FIRST_LOCAL_ID),
     localComponentID_(FIRST_LOCAL_ID),
+    smoothingConstant_(DEFAULT_SMOOTHING_CONSTANT),
+    snapThreshold_(DEFAULT_SNAP_THRESHOLD),
     checksum_(0),
     active_(true),
     asyncLoading_(false)
 {
     // Assign an ID to self so that nodes can refer to this node as a parent
-    SetID(GetFreeNodeID(false));
+    SetID(GetFreeNodeID(REPLICATED));
     NodeAdded(this);
     
     SubscribeToEvent(E_UPDATE, HANDLER(Scene, HandleUpdate));
@@ -70,10 +74,12 @@ 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, AM_DEFAULT);
-    ATTRIBUTE(Scene, VAR_INT, "Next Non-Local Component ID", nonLocalComponentID_, FIRST_NONLOCAL_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_INT, "Next Replicated Node ID", replicatedNodeID_, FIRST_REPLICATED_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_INT, "Next Replicated Component ID", replicatedComponentID_, FIRST_REPLICATED_ID, AM_DEFAULT);
     ATTRIBUTE(Scene, VAR_INT, "Next Local Node ID", localNodeID_, FIRST_LOCAL_ID, AM_DEFAULT);
     ATTRIBUTE(Scene, VAR_INT, "Next Local Component ID", localComponentID_, FIRST_LOCAL_ID, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_FLOAT, "Motion Smoothing Constant", smoothingConstant_, DEFAULT_SMOOTHING_CONSTANT, AM_DEFAULT);
+    ATTRIBUTE(Scene, VAR_FLOAT, "Motion Snap Threshold", snapThreshold_, DEFAULT_SNAP_THRESHOLD, AM_DEFAULT);
 }
 
 bool Scene::Load(Deserializer& source)
@@ -148,6 +154,18 @@ void Scene::Update(float timeStep)
     
     // Post-update variable timestep logic
     SendEvent(E_SCENEPOSTUPDATE, eventData);
+    
+    // Update smoothing if enabled (network client scenes)
+    if (IsSmoothed())
+    {
+        PROFILE(UpdateSmoothing);
+        
+        float constant = 1.0f - Clamp(powf(2.0f, -timeStep * smoothingConstant_), 0.0f, 1.0f);
+        float squaredSnapThreshold = snapThreshold_ * snapThreshold_;
+        
+        for (Map<unsigned, Node*>::ConstIterator i = allNodes_.Begin(); i != allNodes_.End() && i->first_ < FIRST_LOCAL_ID; ++i)
+            i->second_->UpdateSmoothing(constant, squaredSnapThreshold);
+    }
 }
 
 bool Scene::LoadXML(Deserializer& source)
@@ -258,11 +276,6 @@ void Scene::StopAsyncLoading()
     asyncProgress_.xmlElement_ = XMLElement();
 }
 
-void Scene::SetActive(bool enable)
-{
-    active_ = enable;
-}
-
 void Scene::Clear()
 {
     RemoveAllChildren();
@@ -271,6 +284,21 @@ void Scene::Clear()
     checksum_ = 0;
 }
 
+void Scene::SetActive(bool enable)
+{
+    active_ = enable;
+}
+
+void Scene::SetSmoothingConstant(float constant)
+{
+    smoothingConstant_ = Max(constant, M_EPSILON);
+}
+
+void Scene::SetSnapThreshold(float threshold)
+{
+    snapThreshold_ = Max(threshold, 0.0f);
+}
+
 void Scene::AddRequiredPackageFile(PackageFile* file)
 {
     if (file)
@@ -317,19 +345,19 @@ float Scene::GetAsyncProgress() const
         return (float)asyncProgress_.loadedNodes_ / (float)asyncProgress_.totalNodes_;
 }
 
-unsigned Scene::GetFreeNodeID(bool local)
+unsigned Scene::GetFreeNodeID(CreateMode mode)
 {
-    if (!local)
+    if (mode == REPLICATED)
     {
         for (;;)
         {
-            if (allNodes_.Find(nonLocalNodeID_) == allNodes_.End())
-                return nonLocalNodeID_;
+            if (allNodes_.Find(replicatedNodeID_) == allNodes_.End())
+                return replicatedNodeID_;
             
-            if (nonLocalNodeID_ != LAST_NONLOCAL_ID)
-                ++nonLocalNodeID_;
+            if (replicatedNodeID_ != LAST_REPLICATED_ID)
+                ++replicatedNodeID_;
             else
-                nonLocalNodeID_ = FIRST_NONLOCAL_ID;
+                replicatedNodeID_ = FIRST_REPLICATED_ID;
         }
     }
     else
@@ -347,19 +375,19 @@ unsigned Scene::GetFreeNodeID(bool local)
     }
 }
 
-unsigned Scene::GetFreeComponentID(bool local)
+unsigned Scene::GetFreeComponentID(CreateMode mode)
 {
-    if (!local)
+    if (mode == REPLICATED)
     {
         for (;;)
         {
-            if (allComponents_.Find(nonLocalComponentID_) == allComponents_.End())
-                return nonLocalComponentID_;
+            if (allComponents_.Find(replicatedComponentID_) == allComponents_.End())
+                return replicatedComponentID_;
             
-            if (nonLocalComponentID_ != LAST_NONLOCAL_ID)
-                ++nonLocalComponentID_;
+            if (replicatedComponentID_ != LAST_REPLICATED_ID)
+                ++replicatedComponentID_;
             else
-                nonLocalComponentID_ = FIRST_NONLOCAL_ID;
+                replicatedComponentID_ = FIRST_REPLICATED_ID;
         }
     }
     else
@@ -450,12 +478,12 @@ void Scene::UpdateAsyncLoading()
         // Read one child node either from binary or XML
         if (!asyncProgress_.xmlFile_)
         {
-            Node* newNode = CreateChild(asyncProgress_.file_->ReadUInt(), false);
+            Node* newNode = CreateChild(asyncProgress_.file_->ReadUInt(), REPLICATED);
             newNode->Load(*asyncProgress_.file_);
         }
         else
         {
-            Node* newNode = CreateChild(asyncProgress_.xmlElement_.GetInt("id"), false);
+            Node* newNode = CreateChild(asyncProgress_.xmlElement_.GetInt("id"), REPLICATED);
             newNode->LoadXML(asyncProgress_.xmlElement_);
             asyncProgress_.xmlElement_ = asyncProgress_.xmlElement_.GetNext("node");
         }

+ 22 - 10
Engine/Scene/Scene.h

@@ -30,9 +30,9 @@ class File;
 class PackageFile;
 
 /// First replicated node/component ID
-static const unsigned FIRST_NONLOCAL_ID = 0x1;
+static const unsigned FIRST_REPLICATED_ID = 0x1;
 /// Last replicated node/component ID
-static const unsigned LAST_NONLOCAL_ID = 0xffffff;
+static const unsigned LAST_REPLICATED_ID = 0xffffff;
 /// First local node/component ID
 static const unsigned FIRST_LOCAL_ID = 0x01000000;
 /// Last local node/component ID
@@ -87,11 +87,15 @@ public:
     bool LoadAsyncXML(File* file);
     /// Stop asynchronous loading
     void StopAsyncLoading();
-    /// Set active flag. Only active scenes will be updated automatically
-    void SetActive(bool enable);
     /// Clear scene completely of nodes and components
     void Clear();
-    /// Add a required package file for multiplayer. To be called on the server
+    /// Set active flag. Only active scenes will be updated automatically
+    void SetActive(bool enable);
+    /// Set motion smoothing constant
+    void SetSmoothingConstant(float constant);
+    /// Set motion smoothing snap threshold
+    void SetSnapThreshold(float threshold);
+    /// Add a required package file for networking. To be called on the server
     void AddRequiredPackageFile(PackageFile* file);
     /// Clear required package files
     void ClearRequiredPackageFiles();
@@ -112,6 +116,10 @@ public:
     const String& GetFileName() const { return fileName_; }
     /// Return source file checksum
     unsigned GetChecksum() const { return checksum_; }
+    /// Return motion smoothing constant
+    float GetSmoothingConstant() const { return smoothingConstant_; }
+    /// Return motion smoothing snap threshold
+    float GetSnapThreshold() const { return snapThreshold_; }
     /// Return required package files
     const Vector<SharedPtr<PackageFile> >& GetRequiredPackageFiles() const { return requiredPackageFiles_; }
     /// Return all nodes
@@ -120,9 +128,9 @@ public:
     const Map<unsigned, Component*>& GetAllComponents() const { return allComponents_; }
     
     /// Get free node ID, either non-local or local
-    unsigned GetFreeNodeID(bool local);
+    unsigned GetFreeNodeID(CreateMode mode);
     /// Get free component ID, either non-local or local
-    unsigned GetFreeComponentID(bool local);
+    unsigned GetFreeComponentID(CreateMode mode);
     /// Node added. Assign scene pointer and add to ID map
     void NodeAdded(Node* node);
     /// Node removed. Remove from ID map
@@ -150,18 +158,22 @@ private:
     AsyncProgress asyncProgress_;
     /// Source file name
     String fileName_;
-    /// Required package files for multiplayer
+    /// Required package files for networking
     Vector<SharedPtr<PackageFile> > requiredPackageFiles_;
     /// Next free non-local node ID
-    unsigned nonLocalNodeID_;
+    unsigned replicatedNodeID_;
     /// Next free local node ID
     unsigned localNodeID_;
     /// Next free non-local component ID
-    unsigned nonLocalComponentID_;
+    unsigned replicatedComponentID_;
     /// Next free local component ID
     unsigned localComponentID_;
     /// Scene source file checksum
     unsigned checksum_;
+    /// Motion smoothing constant
+    float smoothingConstant_;
+    /// Motion smoothing snap threshold
+    float snapThreshold_;
     /// Active flag
     bool active_;
     /// Asynchronous loading flag

+ 22 - 1
Engine/UI/UI.cpp

@@ -85,6 +85,9 @@ UI::~UI()
 
 void UI::SetCursor(Cursor* cursor)
 {
+    if (!rootElement_)
+        return;
+    
     // Remove old cursor (if any) and set new
     if (cursor_)
     {
@@ -106,6 +109,9 @@ void UI::SetCursor(Cursor* cursor)
 
 void UI::SetFocusElement(UIElement* element)
 {
+    if (!rootElement_)
+        return;
+    
     using namespace FocusChanged;
     
     VariantMap eventData;
@@ -147,6 +153,9 @@ void UI::SetFocusElement(UIElement* element)
 
 void UI::Clear()
 {
+    if (!rootElement_)
+        return;
+    
     rootElement_->RemoveAllChildren();
     if (cursor_)
         rootElement_->AddChild(cursor_);
@@ -154,6 +163,9 @@ void UI::Clear()
 
 void UI::Update(float timeStep)
 {
+    if (!rootElement_)
+        return;
+    
     PROFILE(UpdateUI);
     
     if (cursor_ && cursor_->IsVisible())
@@ -209,7 +221,7 @@ void UI::Update(float timeStep)
 
 void UI::RenderUpdate()
 {
-    if (!graphics_ || graphics_->IsDeviceLost())
+    if (!rootElement_ || !graphics_ || graphics_->IsDeviceLost())
         return;
     
     PROFILE(GetUIBatches);
@@ -337,6 +349,9 @@ void UI::SetClipBoardText(const String& text)
 
 UIElement* UI::GetElementAt(const IntVector2& position, bool activeOnly)
 {
+    if (!rootElement_)
+        return 0;
+    
     UIElement* result = 0;
     GetElementAt(result, rootElement_, position, activeOnly);
     return result;
@@ -349,6 +364,9 @@ UIElement* UI::GetElementAt(int x, int y, bool activeOnly)
 
 UIElement* UI::GetFocusElement() const
 {
+    if (!rootElement_)
+        return 0;
+    
     PODVector<UIElement*> allChildren = rootElement_->GetChildren(true);
     for (PODVector<UIElement*>::Iterator i = allChildren.Begin(); i != allChildren.End(); ++i)
     {
@@ -361,6 +379,9 @@ UIElement* UI::GetFocusElement() const
 
 UIElement* UI::GetFrontElement() const
 {
+    if (!rootElement_)
+        return 0;
+    
     PODVector<UIElement*> rootChildren = rootElement_->GetChildren(false);
     int maxPriority = M_MIN_INT;
     UIElement* front = 0;