Browse Source

Added vehicle physics example.
Added softness parameter to Constraint.
Fixed exposing Quaternion::Slerp() to script.
Removed package download code also from Physics & Terrain examples.

Lasse Öörni 12 years ago
parent
commit
bd3a4c30f4

+ 0 - 32
Bin/Data/Scripts/Physics.as

@@ -8,8 +8,6 @@ float yaw = 0.0;
 float pitch = 0.0;
 int drawDebug = 0;
 
-Text@ downloadsText;
-
 void Start()
 {
     if (!engine.headless)
@@ -41,21 +39,9 @@ void Start()
 
         // Disable physics interpolation to ensure clients get sent physically correct transforms
         testScene.physicsWorld.interpolation = false;
-
-        // Test package download by adding all package files in the cache as requirements for the scene
-        Array<PackageFile@> packages = cache.packageFiles;
-        for (uint i = 0; i < packages.length; ++i)
-            testScene.AddRequiredPackageFile(packages[i]);
     }
     if (runClient)
     {
-        // Test package download. Remove existing Data.pak from resource cache so that it will be downloaded
-        // However, be sure to add the Data directory so that resource requests do not fail in the meanwhile
-        String packageName = fileSystem.programDir + "Data.pak";
-        cache.RemovePackageFile(packageName, false);
-        cache.AddResourceDir(fileSystem.programDir + "Data");
-
-        network.packageCacheDir = fileSystem.programDir;
         network.Connect(serverAddress, serverPort, testScene);
     }
 }
@@ -82,11 +68,6 @@ void InitUI()
     ui.cursor = newCursor;
     if (GetPlatform() == "Android")
         ui.cursor.visible = false;
-
-    downloadsText = Text();
-    downloadsText.SetAlignment(HA_CENTER, VA_CENTER);
-    downloadsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 20);
-    ui.root.AddChild(downloadsText);
 }
 
 void InitScene()
@@ -217,19 +198,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         if (input.keyDown['D'])
             cameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
     }
-
-    // Update package download status
-    if (network.serverConnection !is null)
-    {
-        Connection@ connection = network.serverConnection;
-        if (connection.numDownloads > 0)
-        {
-            downloadsText.text = "Downloads: " + connection.numDownloads + " Current download: " +
-                connection.downloadName + " (" + int(connection.downloadProgress * 100.0) + "%)";
-        }
-        else if (!downloadsText.text.empty)
-            downloadsText.text = "";
-    }
 }
 
 void HandleKeyDown(StringHash eventType, VariantMap& eventData)

+ 0 - 32
Bin/Data/Scripts/Terrain.as

@@ -8,8 +8,6 @@ float yaw = 0.0;
 float pitch = 0.0;
 int drawDebug = 0;
 
-Text@ downloadsText;
-
 void Start()
 {
     if (!engine.headless)
@@ -41,21 +39,9 @@ void Start()
 
         // Disable physics interpolation to ensure clients get sent physically correct transforms
         testScene.physicsWorld.interpolation = false;
-
-        // Test package download by adding all package files in the cache as requirements for the scene
-        Array<PackageFile@> packages = cache.packageFiles;
-        for (uint i = 0; i < packages.length; ++i)
-            testScene.AddRequiredPackageFile(packages[i]);
     }
     if (runClient)
     {
-        // Test package download. Remove existing Data.pak from resource cache so that it will be downloaded
-        // However, be sure to add the Data directory so that resource requests do not fail in the meanwhile
-        String packageName = fileSystem.programDir + "Data.pak";
-        cache.RemovePackageFile(packageName, false);
-        cache.AddResourceDir(fileSystem.programDir + "Data");
-
-        network.packageCacheDir = fileSystem.programDir;
         network.Connect(serverAddress, serverPort, testScene);
     }
 }
@@ -82,11 +68,6 @@ void InitUI()
     ui.cursor = newCursor;
     if (GetPlatform() == "Android" || GetPlatform() == "iOS")
         ui.cursor.visible = false;
-
-    downloadsText = Text();
-    downloadsText.SetAlignment(HA_CENTER, VA_CENTER);
-    downloadsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 20);
-    ui.root.AddChild(downloadsText);
 }
 
 void InitScene()
@@ -206,19 +187,6 @@ void HandleUpdate(StringHash eventType, VariantMap& eventData)
         if (input.keyDown['D'])
             cameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
     }
-
-    // Update package download status
-    if (network.serverConnection !is null)
-    {
-        Connection@ connection = network.serverConnection;
-        if (connection.numDownloads > 0)
-        {
-            downloadsText.text = "Downloads: " + connection.numDownloads + " Current download: " +
-                connection.downloadName + " (" + int(connection.downloadProgress * 100.0) + "%)";
-        }
-        else if (!downloadsText.text.empty)
-            downloadsText.text = "";
-    }
 }
 
 void HandleKeyDown(StringHash eventType, VariantMap& eventData)

+ 395 - 0
Bin/Data/Scripts/Vehicle.as

@@ -0,0 +1,395 @@
+Scene@ testScene;
+Camera@ camera;
+Node@ cameraNode;
+Node@ vehicleHullNode;
+
+float yaw = 0.0;
+float pitch = 0.0;
+int drawDebug = 0;
+
+void Start()
+{
+    if (!engine.headless)
+    {
+        InitConsole();
+        InitUI();
+    }
+    else
+        OpenConsoleWindow();
+
+    InitScene();
+    InitVehicle();
+
+    SubscribeToEvent("Update", "HandleUpdate");
+    SubscribeToEvent("PostUpdate", "HandlePostUpdate");
+    SubscribeToEvent("KeyDown", "HandleKeyDown");
+    SubscribeToEvent("MouseMove", "HandleMouseMove");
+    SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+}
+
+void InitConsole()
+{
+    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+    engine.CreateDebugHud();
+    debugHud.defaultStyle = uiStyle;
+    debugHud.mode = DEBUGHUD_SHOW_ALL;
+
+    engine.CreateConsole();
+    console.defaultStyle = uiStyle;
+}
+
+void InitUI()
+{
+    XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
+
+    Cursor@ newCursor = Cursor("Cursor");
+    newCursor.SetStyleAuto(uiStyle);
+    newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
+    ui.cursor = newCursor;
+    ui.cursor.visible = false;
+}
+
+void InitScene()
+{
+    testScene = Scene("TestScene");
+
+    // Enable access to this script file & scene from the console
+    script.defaultScene = testScene;
+    script.defaultScriptFile = scriptFile;
+
+    // Create the camera outside the scene so it is unaffected by scene load/save
+    cameraNode = Node();
+    camera = cameraNode.CreateComponent("Camera");
+    camera.farClip = 1000;
+    camera.nearClip = 0.5;
+    cameraNode.position = Vector3(0, 20, 0);
+
+    if (!engine.headless)
+    {
+        renderer.viewports[0] = Viewport(testScene, camera);
+        audio.listener = cameraNode.CreateComponent("SoundListener");
+    }
+
+    PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
+    testScene.CreateComponent("Octree");
+    testScene.CreateComponent("DebugRenderer");
+
+    Node@ zoneNode = testScene.CreateChild("Zone");
+    Zone@ zone = zoneNode.CreateComponent("Zone");
+    zone.ambientColor = Color(0.15, 0.15, 0.15);
+    zone.fogColor = Color(0.5, 0.5, 0.7);
+    zone.fogStart = 500.0;
+    zone.fogEnd = 1000.0;
+    zone.boundingBox = BoundingBox(-2000, 2000);
+
+    {
+        Node@ lightNode = testScene.CreateChild("GlobalLight");
+        lightNode.direction = Vector3(0.5, -0.5, 0.5);
+
+        Light@ light = lightNode.CreateComponent("Light");
+        light.lightType = LIGHT_DIRECTIONAL;
+        light.castShadows = true;
+        light.shadowBias = BiasParameters(0.0001, 0.5);
+        light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8);
+        light.specularIntensity = 0.5;
+    }
+
+    Terrain@ terrain;
+
+    {
+        Node@ terrainNode = testScene.CreateChild("Terrain");
+        terrainNode.position = Vector3(0, 0, 0);
+        terrain = terrainNode.CreateComponent("Terrain");
+        terrain.patchSize = 64;
+        terrain.spacing = Vector3(2, 0.1, 2);
+        terrain.heightMap = cache.GetResource("Image", "Textures/HeightMap.png");
+        terrain.material = cache.GetResource("Material", "Materials/Terrain.xml");
+        terrain.occluder = true;
+
+        RigidBody@ body = terrainNode.CreateComponent("RigidBody");
+        body.collisionLayer = 2;
+        CollisionShape@ shape = terrainNode.CreateComponent("CollisionShape");
+        shape.SetTerrain();
+        shape.margin = 0.01;
+    }
+
+    for (uint i = 0; i < 1000; ++i)
+    {
+        Node@ objectNode = testScene.CreateChild("Mushroom");
+        Vector3 position(Random() * 2000 - 1000, 0, Random() * 2000 - 1000);
+        position.y = terrain.GetHeight(position) - 0.1;
+
+        objectNode.position = position;
+        objectNode.rotation = Quaternion(Vector3(0, 1, 0), terrain.GetNormal(position));
+        objectNode.SetScale(3);
+
+        StaticModel@ object = objectNode.CreateComponent("StaticModel");
+        object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
+        object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
+        object.castShadows = true;
+
+        RigidBody@ body = objectNode.CreateComponent("RigidBody");
+        body.collisionLayer = 2;
+        CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+        shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
+    }
+}
+
+void HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float timeStep = eventData["TimeStep"].GetFloat();
+    
+    // Nothing to do for now
+}
+
+void HandlePostUpdate(StringHash eventType, VariantMap& eventData)
+{
+    float timeStep = eventData["TimeStep"].GetFloat();
+
+    // Physics update has completed. Position camera behind vehicle
+    Quaternion dir(vehicleHullNode.rotation.yaw, Vector3(0, 1, 0));;
+    dir = dir * Quaternion(yaw, Vector3(0, 1, 0));
+    dir = dir * Quaternion(pitch, Vector3(1, 0, 0));
+
+    Vector3 cameraTargetPos = vehicleHullNode.position - dir * Vector3(0, 0, 10);
+    Vector3 cameraStartPos = vehicleHullNode.position;
+
+    // Raycast camera against static objects (physics collision mask 2)
+    // and move it closer to the vehicle if something in between
+    Ray cameraRay(cameraStartPos, (cameraTargetPos - cameraStartPos).Normalized());
+    float cameraRayLength = (cameraTargetPos - cameraStartPos).length;
+    PhysicsRaycastResult result = scene.physicsWorld.RaycastSingle(cameraRay, cameraRayLength, 2);
+    if (result.body !is null)
+        cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5f);
+
+    cameraNode.position = cameraTargetPos;
+    cameraNode.rotation = dir;
+}
+
+void HandleKeyDown(StringHash eventType, VariantMap& eventData)
+{
+    int key = eventData["Key"].GetInt();
+
+    if (key == KEY_ESC)
+    {
+        if (ui.focusElement is null)
+            engine.Exit();
+        else
+            console.visible = false;
+    }
+
+    if (key == KEY_F1)
+        console.Toggle();
+
+    if (ui.focusElement is null)
+    {
+        if (key == '1')
+        {
+            int quality = renderer.textureQuality;
+            ++quality;
+            if (quality > 2)
+                quality = 0;
+            renderer.textureQuality = quality;
+        }
+
+        if (key == '2')
+        {
+            int quality = renderer.materialQuality;
+            ++quality;
+            if (quality > 2)
+                quality = 0;
+            renderer.materialQuality = quality;
+        }
+
+        if (key == '3')
+            renderer.specularLighting = !renderer.specularLighting;
+
+        if (key == '4')
+            renderer.drawShadows = !renderer.drawShadows;
+
+        if (key == '5')
+        {
+            int size = renderer.shadowMapSize;
+            size *= 2;
+            if (size > 2048)
+                size = 512;
+            renderer.shadowMapSize = size;
+        }
+
+        if (key == '6')
+            renderer.shadowQuality = renderer.shadowQuality + 1;
+
+        if (key == '7')
+        {
+            bool occlusion = renderer.maxOccluderTriangles > 0;
+            occlusion = !occlusion;
+            renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
+        }
+
+        if (key == '8')
+            renderer.dynamicInstancing = !renderer.dynamicInstancing;
+
+        if (key == ' ')
+        {
+            drawDebug++;
+            if (drawDebug > 2)
+                drawDebug = 0;
+        }
+
+        if (key == 'T')
+            debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
+    }
+}
+
+void HandleMouseMove(StringHash eventType, VariantMap& eventData)
+{
+    int mousedx = eventData["DX"].GetInt();
+    int mousedy = eventData["DY"].GetInt();
+    yaw += mousedx / 10.0;
+    pitch += mousedy / 10.0;
+    if (pitch < -60.0)
+        pitch = -60.0;
+    if (pitch > 60.0)
+        pitch = 60.0;
+}
+
+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);
+    if (drawDebug == 2)
+        testScene.physicsWorld.DrawDebugGeometry(true);
+}
+
+void InitVehicle()
+{
+    vehicleHullNode = testScene.CreateChild("VehicleHull");
+    StaticModel@ hullObject = vehicleHullNode.CreateComponent("StaticModel");
+    RigidBody@ hullBody = vehicleHullNode.CreateComponent("RigidBody");
+    CollisionShape@ hullShape = vehicleHullNode.CreateComponent("CollisionShape");
+
+    vehicleHullNode.position = Vector3(0, 5, 0);
+    vehicleHullNode.scale = Vector3(1.5, 1, 3);
+    hullObject.model = cache.GetResource("Model", "Models/Box.mdl");
+    hullObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+    hullObject.castShadows = true;
+    hullShape.SetBox(Vector3(1, 1, 1));
+    hullBody.mass = 3;
+    hullBody.linearDamping = 0.2; // Some air resistance
+    hullBody.collisionLayer = 1;
+
+    Node@ fl = InitVehicleWheel("FrontLeft", vehicleHullNode, Vector3(-0.6, -0.4, 0.3));
+    Node@ fr = InitVehicleWheel("FrontRight", vehicleHullNode, Vector3(0.6, -0.4, 0.3));
+    Node@ rr = InitVehicleWheel("RearLeft", vehicleHullNode, Vector3(-0.6, -0.4, -0.3));
+    Node@ rl = InitVehicleWheel("RearRight", vehicleHullNode, Vector3(0.6, -0.4, -0.3));
+
+    Vehicle@ vehicle = cast<Vehicle>(vehicleHullNode.CreateScriptObject(scriptFile, "Vehicle"));
+    vehicle.SetWheels(fl, fr, rl, rr);
+}
+
+Node@ InitVehicleWheel(String name, Node@ vehicleHullNode, Vector3 offset)
+{
+    Node@ wheelNode = testScene.CreateChild(name);
+    wheelNode.position = vehicleHullNode.LocalToWorld(offset);
+    wheelNode.rotation = vehicleHullNode.worldRotation * (offset.x >= 0.0 ? Quaternion(0, 0, -90) : Quaternion(0, 0, 90));
+    wheelNode.scale = Vector3(0.75, 0.5, 0.75);
+
+    StaticModel@ wheelObject = wheelNode.CreateComponent("StaticModel");
+    RigidBody@ wheelBody = wheelNode.CreateComponent("RigidBody");
+    CollisionShape@ wheelShape = wheelNode.CreateComponent("CollisionShape");
+    Constraint@ wheelConstraint = wheelNode.CreateComponent("Constraint");
+
+    wheelObject.model = cache.GetResource("Model", "Models/Cylinder.mdl");
+    wheelObject.material = cache.GetResource("Material", "Materials/Stone.xml");
+    wheelObject.castShadows = true;
+    wheelShape.SetCylinder(1, 1);
+    wheelBody.friction = 1;
+    wheelBody.mass = 1;
+    wheelBody.linearDamping = 0.2; // Some air resistance
+    wheelBody.angularDamping = 0.75; // Current version of Bullet used by Urho doesn't have rolling friction, so mimic that with
+                                    // some angular damping on the wheels
+    wheelBody.collisionLayer = 1;
+    wheelConstraint.constraintType = CONSTRAINT_HINGE;
+    wheelConstraint.otherBody = vehicleHullNode.GetComponent("RigidBody");
+    wheelConstraint.worldPosition = wheelNode.worldPosition; // Set constraint's both ends at wheel's location
+    wheelConstraint.axis = Vector3(0, 1, 0); // Wheel rotates around its local Y-axis
+    wheelConstraint.otherAxis = offset.x >= 0.0 ? Vector3(1, 0, 0) : Vector3(-1, 0, 0); // Wheel's hull axis points either left or right
+    wheelConstraint.lowLimit = Vector2(-180, 0); // Let the wheel rotate freely around the axis
+    wheelConstraint.highLimit = Vector2(180, 0);
+    wheelConstraint.disableCollision = true; // Let the wheel intersect the vehicle hull
+
+    return wheelNode;
+}
+
+class Vehicle : ScriptObject
+{
+    Node@ frontLeft;
+    Node@ frontRight;
+    Node@ rearLeft;
+    Node@ rearRight;
+    Constraint@ frontLeftAxis;
+    Constraint@ frontRightAxis;
+    RigidBody@ frontLeftDrive;
+    RigidBody@ frontRightDrive;
+    RigidBody@ rearLeftDrive;
+    RigidBody@ rearRightDrive;
+
+    float steering = 0.0;
+    float enginePower = 5.0;
+    float maxWheelAngle = 22.5;
+
+    void SetWheels(Node@ fl, Node@ fr, Node@ rl, Node@ rr)
+    {
+        frontLeft = fl;
+        frontRight = fr;
+        rearLeft = rl;
+        rearRight = rr;
+
+        frontLeftAxis = frontLeft.GetComponent("Constraint");
+        frontRightAxis = frontRight.GetComponent("Constraint");
+        frontLeftDrive = frontLeft.GetComponent("RigidBody");
+        frontRightDrive = frontRight.GetComponent("RigidBody");
+        rearLeftDrive = rearLeft.GetComponent("RigidBody");
+        rearRightDrive = rearRight.GetComponent("RigidBody");
+    }
+
+    void FixedUpdate(float timeStep)
+    {
+        float newSteering = 0.0;
+        float accelerator = 0.0;
+
+        if (ui.focusElement is null)
+        {
+            if (input.keyDown['A'])
+                newSteering = -1.0f;
+            if (input.keyDown['D'])
+                newSteering = 1.0f;
+            if (input.keyDown['W'])
+                accelerator = 1.0f;
+            if (input.keyDown['S'])
+                accelerator = -0.5f;
+        }
+
+        steering = steering * 0.9 + newSteering * 0.1;
+        Quaternion steeringRot(0, steering * maxWheelAngle, 0);
+
+        frontLeftAxis.otherAxis = steeringRot * Vector3(-1, 0, 0);
+        frontRightAxis.otherAxis = steeringRot * Vector3(1, 0, 0);
+
+        if (accelerator != 0.0)
+        {
+            // Torques are applied in world space, so need to take the vehicle & wheel rotation into account
+            Vector3 torqueVec = Vector3(enginePower * accelerator, 0, 0);
+            
+            frontLeftDrive.ApplyTorque(node.rotation * steeringRot * torqueVec);
+            frontRightDrive.ApplyTorque(node.rotation * steeringRot * torqueVec);
+            rearLeftDrive.ApplyTorque(node.rotation * torqueVec);
+            rearRightDrive.ApplyTorque(node.rotation * torqueVec);
+        }
+    }
+}

+ 1 - 0
Bin/Vehicle.bat

@@ -0,0 +1 @@
+Urho3D.exe Scripts/Vehicle.as %*

+ 1 - 0
Bin/Vehicle.sh

@@ -0,0 +1 @@
+./Urho3D Scripts/Vehicle.as $@

+ 16 - 1
Docs/GettingStarted.dox

@@ -112,7 +112,7 @@ The scripting language used is AngelScript (http://www.angelcode.com/angelscript
 
 On Android and iOS there are no command line options, so running the NinjaSnowWar example is hardcoded. This can be changed from the file Urho3D/Urho3D.cpp.
 
-Currently ten example application scripts exist:
+Currently eleven example application scripts exist:
 
 \section Running_NinjaSnowWar NinjaSnowWar
 
@@ -172,6 +172,21 @@ For details on how to use the editor, see \ref EditorInstructions "Editor instru
 L           Toggle buoyant liquid volume
 \endverbatim
 
+\section Running_Vehicle Vehicle
+
+Simple vehicle physics example. To start, run Vehicle.bat, or use the command Urho3D.exe Scripts/Vehicle.as.
+
+Key and mouse controls:
+
+\verbatim
+WSAD        Steer vehicle
+Mouse       Rotate camera around vehicle
+Space       Toggle debug geometry
+F1          Toggle AngelScript console
+1 to 8      Toggle rendering options
+T           Toggle profiling display
+\endverbatim
+
 \section Running_Navigation Navigation
 
 A test of navigation mesh generation and path queries. Generates the same static scene as TestScene. To start, run Navigation.bat in the Bin directory, or use the command Urho3D.exe Scripts\Navigation.as. %Controls are like in TestScene, except:

+ 8 - 6
Docs/ScriptAPI.dox

@@ -417,7 +417,7 @@ Methods:<br>
 - Quaternion Normalized() const
 - Quaternion Inverse() const
 - float DotProduct(const Quaternion&) const
-- Quaternion Slerp(const Quaternion&, float) const
+- Quaternion Slerp(Quaternion, float) const
 - bool Equals(const Quaternion&) const
 - String ToString() const
 
@@ -2632,14 +2632,14 @@ Properties:<br>
 - Zone@ zone (readonly)
 
 
-ColorAnimation
+ColorFrame
 
 Properties:<br>
 - Color color
 - float time
 
 
-TextureAnimation
+TextureFrame
 
 Properties:<br>
 - Rect uv
@@ -2698,6 +2698,7 @@ Properties:<br>
 - bool relative
 - bool sorted
 - bool scaled
+- bool updateInvisible
 - float animationLodBias
 - bool emitting
 - uint numParticles
@@ -2729,10 +2730,10 @@ Properties:<br>
 - float dampingForce
 - float sizeAdd
 - float sizeMul
-- ColorAnimation@[] colors (readonly)
+- ColorFrame@[] colors (readonly)
 - uint numColors
-- TextureAnimation@[] textureAnimations (readonly)
-- uint numTextureAnimations
+- TextureFrame@[] textureFrames (readonly)
+- uint numTextureFrames
 - Zone@ zone (readonly)
 
 
@@ -5792,6 +5793,7 @@ Properties:<br>
 - Vector3 worldPosition
 - Vector2 highLimit
 - Vector2 lowLimit
+- float softness
 - bool disableCollision
 - RigidBody@ ownBody (readonly)
 - RigidBody@ otherBody

+ 1 - 1
Engine/Engine/MathAPI.cpp

@@ -463,7 +463,7 @@ static void RegisterQuaternion(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Quaternion", "Quaternion Normalized() const", asMETHOD(Quaternion, Normalized), asCALL_THISCALL);
     engine->RegisterObjectMethod("Quaternion", "Quaternion Inverse() const", asMETHOD(Quaternion, Inverse), asCALL_THISCALL);
     engine->RegisterObjectMethod("Quaternion", "float DotProduct(const Quaternion&in) const", asMETHOD(Quaternion, DotProduct), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Quaternion", "Quaternion Slerp(const Quaternion&in, float) const", asMETHOD(Quaternion, Slerp), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Quaternion", "Quaternion Slerp(Quaternion, float) const", asMETHOD(Quaternion, Slerp), asCALL_THISCALL);
     engine->RegisterObjectMethod("Quaternion", "bool Equals(const Quaternion&in) const", asMETHOD(Quaternion, Equals), asCALL_THISCALL);
     engine->RegisterObjectMethod("Quaternion", "String ToString() const", asMETHOD(Quaternion, ToString), asCALL_THISCALL);
     engine->RegisterObjectMethod("Quaternion", "Vector3 get_eulerAngles() const", asMETHOD(Quaternion, EulerAngles), asCALL_THISCALL);

+ 2 - 0
Engine/Engine/PhysicsAPI.cpp

@@ -205,6 +205,8 @@ static void RegisterConstraint(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Constraint", "const Vector2& get_highLimit() const", asMETHOD(Constraint, GetHighLimit), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_lowLimit(const Vector2&in)", asMETHOD(Constraint, SetLowLimit), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "const Vector2& get_lowLimit() const", asMETHOD(Constraint, GetLowLimit), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "void set_softness(float)", asMETHOD(Constraint, SetSoftness), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "float get_softness() const", asMETHOD(Constraint, GetSoftness), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_disableCollision(bool)", asMETHOD(Constraint, SetDisableCollision), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "bool get_disableCollision() const" ,asMETHOD(Constraint, GetDisableCollision), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "RigidBody@+ get_ownBody() const", asMETHOD(Constraint, GetOwnBody), asCALL_THISCALL);

+ 14 - 0
Engine/Physics/Constraint.cpp

@@ -65,6 +65,7 @@ Constraint::Constraint(Context* context) :
     otherRotation_(Quaternion::IDENTITY),
     highLimit_(Vector2::ZERO),
     lowLimit_(Vector2::ZERO),
+    softness_(0.0f),
     otherBodyNodeID_(0),
     disableCollision_(false),
     recreateConstraint_(true),
@@ -93,6 +94,7 @@ void Constraint::RegisterObject(Context* context)
     ATTRIBUTE(Constraint, VAR_INT, "Other Body NodeID", otherBodyNodeID_, 0, AM_DEFAULT | AM_NODEID);
     REF_ACCESSOR_ATTRIBUTE(Constraint, VAR_VECTOR2, "High Limit", GetHighLimit, SetHighLimit, Vector2, Vector2::ZERO, AM_DEFAULT);
     REF_ACCESSOR_ATTRIBUTE(Constraint, VAR_VECTOR2, "Low Limit", GetLowLimit, SetLowLimit, Vector2, Vector2::ZERO, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE(Constraint, VAR_FLOAT, "Softness", GetSoftness, SetSoftness, float, 0.0f, AM_DEFAULT);
     ATTRIBUTE(Constraint, VAR_BOOL, "Disable Collision", disableCollision_, false, AM_DEFAULT);
 }
 
@@ -320,6 +322,16 @@ void Constraint::SetLowLimit(const Vector2& limit)
     }
 }
 
+void Constraint::SetSoftness(float softness)
+{
+    if (softness != softness_)
+    {
+        softness_ = softness;
+        ApplyLimits();
+        MarkNetworkUpdate();
+    }
+}
+
 void Constraint::SetDisableCollision(bool disable)
 {
     if (disable != disableCollision_)
@@ -524,6 +536,8 @@ void Constraint::ApplyLimits()
     if (!constraint_)
         return;
     
+    constraint_->setParam(BT_CONSTRAINT_CFM, softness_);
+    
     switch (constraint_->getConstraintType())
     {
     case HINGE_CONSTRAINT_TYPE:

+ 6 - 0
Engine/Physics/Constraint.h

@@ -90,6 +90,8 @@ public:
     void SetHighLimit(const Vector2& limit);
     /// Set low limit. Interpretation is constraint type specific.
     void SetLowLimit(const Vector2& limit);
+    /// Set constraint softness. Default 0 (hard).
+    void SetSoftness(float softness);
     /// Set whether to disable collisions between connected bodies.
     void SetDisableCollision(bool disable);
     
@@ -117,6 +119,8 @@ public:
     const Vector2& GetHighLimit() const { return highLimit_; }
     /// Return low limit.
     const Vector2& GetLowLimit() const { return lowLimit_; }
+    /// Return constraint softness.
+    float GetSoftness() const { return softness_; }
     /// Return whether collisions between connected bodies are disabled.
     bool GetDisableCollision() const { return disableCollision_; }
     
@@ -161,6 +165,8 @@ private:
     Vector2 highLimit_;
     /// Low limit.
     Vector2 lowLimit_;
+    /// Softness value.
+    float softness_;
     /// Other body node ID for pending constraint recreation.
     int otherBodyNodeID_;
     /// Disable collision between connected bodies flag.