Browse Source

Changed Constraint API to specify the other body position / axis explicitly, and to specify limits as Vector2.
Fixed bugs in assigning parented RigidBody transforms after simulation step.
Optimized Constraint SetPosition() / SetAxis() to not recreate the constraint.

Lasse Öörni 13 years ago
parent
commit
9f331e56db

+ 3 - 3
Bin/Data/Scripts/Editor/EditorView.as

@@ -320,7 +320,7 @@ void HandlePostRenderUpdate()
     for (uint i = 0; i < selectedNodes.length; ++i)
     {
         Node@ node = selectedNodes[i];
-        debug.AddNode(node, false);
+        debug.AddNode(node, 1.0, false);
         for (uint j = 0; j < node.numComponents; ++j)
         {
             Drawable@ drawable = cast<Drawable>(node.components[j]);
@@ -375,7 +375,7 @@ void ViewRaycast(bool mouseClick)
             Drawable@ drawable = result.drawable;
             if (debug !is null)
             {
-                debug.AddNode(drawable.node, false);
+                debug.AddNode(drawable.node, 1.0, false);
                 drawable.DrawDebugGeometry(debug, false);
             }
             selected = drawable;
@@ -396,7 +396,7 @@ void ViewRaycast(bool mouseClick)
             RigidBody@ body = result.body;
             if (debug !is null)
             {
-                debug.AddNode(body.node, false);
+                debug.AddNode(body.node, 1.0, false);
                 body.DrawDebugGeometry(debug, false);
             }
             selected = body;

+ 6 - 3
Docs/Reference.dox

@@ -839,13 +839,16 @@ By default rigid bodies can move and rotate about all 3 coordinate axes when for
 
 To prevent tunneling of a fast moving rigid body through obstacles, continuous collision detection can be used. It approximates the object as a swept sphere, but has a performance cost, so it should be used only when necessary. Call \ref RigidBody::SetCcdRadius "SetCcdRadius()" and \ref RigidBody::SetCcdMotionThreshold "SetCcdMotionThreshold()" with non-zero values to enable. To prevent false collisions, the body's actual collision shape should completely contain the radius. The motion threshold is the required motion per simulation step for CCD to kick in: for example a box with size 1 should have motion threshold 1 as well.
 
-All physics calculations are performed in world space. Nodes containing a RigidBody component should be parented to the Scene (root node) to ensure correct operation.
+All physics calculations are performed in world space. Nodes containing a RigidBody component should preferably be parented to the Scene (root node) to ensure independent
+motion. For ragdolls this is not absolute, as retaining proper bone hierarchy is more important, but be aware that the ragdoll bones may drift far from the animated model's root scene node.
 
 \section Physics_ConstraintParameters Constraint parameters
 
-Hinge and slider constraints support defining limits for the motion. To be generic, these are encoded slightly unintuitively into Vector3's.
+Constraint position (and axis if relevant) needs to be defined relative to both connected bodies. If a constraint connects a body to the static world, then the "other body position" and "other body axis" mean the static end's position in world space.
+
+Hinge and slider constraints support defining limits for the motion. To be generic, these are encoded slightly unintuitively into Vector2's.
 For a hinge constraint, the low and high limit X coordinates define the minimum and maximum angle in degrees. For example -45 to 45. For a slider constraint,
-the X coordinates define the maximum linear motion in world space units, and the Y coordinates define maximum angular motion in degrees. The cone twist constraint uses only the high limit to define the maximum angles (minimum angle is always -maximum) in the following manner: The X coordinate is the limit of the twist (main) axis, while Y and Z are the limits of the swinging motion about the other axes.
+the X coordinates define the maximum linear motion in world space units, and the Y coordinates define maximum angular motion in degrees. The cone twist constraint uses only the high limit to define the maximum angles (minimum angle is always -maximum) in the following manner: The X coordinate is the limit of the twist (main) axis, while Y is the limit of the swinging motion about the other axes.
 
 \section Physics_Events Physics events
 

+ 7 - 3
Docs/ScriptAPI.dox

@@ -1457,7 +1457,7 @@ Methods:<br>
 - void Remove()
 - void MarkNetworkUpdate() const
 - void AddLine(const Vector3&, const Vector3&, const Color&, bool arg3 = true)
-- void AddNode(Node@, bool arg1 = true)
+- void AddNode(Node@, float arg1 = 1.0, bool arg2 = true)
 - void AddBoundingBox(const BoundingBox&, const Color&, bool arg2 = true)
 - void AddFrustum(const Frustum&, const Color&, bool arg2 = true)
 - void AddPolyhedron(const Polyhedron&, const Color&, bool arg2 = true)
@@ -4056,6 +4056,7 @@ Methods:<br>
 - void Remove()
 - void MarkNetworkUpdate() const
 - void DrawDebugGeometry(DebugRenderer@, bool)
+- void SetParameters(ConstraintType, RigidBody@, const Vector3&, const Vector3&, const Vector3&, const Vector3&, const Vector2&, const Vector2&, bool)
 
 Properties:<br>
 - ShortStringHash type (readonly)
@@ -4068,8 +4069,11 @@ Properties:<br>
 - ConstraintType constraintType
 - Vector3& position
 - Vector3& axis
-- Vector3& highLimit
-- Vector3& lowLimit
+- Vector3& otherPosition
+- Vector3& otherAxis
+- Vector2& highLimit
+- Vector2& lowLimit
+- bool disableCollision
 - RigidBody@ ownBody (readonly)
 - RigidBody@ otherBody
 

+ 1 - 1
Engine/Engine/GraphicsAPI.cpp

@@ -882,7 +882,7 @@ static void RegisterDebugRenderer(asIScriptEngine* engine)
 {
     RegisterComponent<DebugRenderer>(engine, "DebugRenderer", true, false);
     engine->RegisterObjectMethod("DebugRenderer", "void AddLine(const Vector3&in, const Vector3&in, const Color&in, bool depthTest = true)", asMETHODPR(DebugRenderer, AddLine, (const Vector3&, const Vector3&, const Color&, bool), void), asCALL_THISCALL);
-    engine->RegisterObjectMethod("DebugRenderer", "void AddNode(Node@+, bool depthTest = true)", asMETHOD(DebugRenderer, AddNode), asCALL_THISCALL);
+    engine->RegisterObjectMethod("DebugRenderer", "void AddNode(Node@+, float scale = 1.0, bool depthTest = true)", asMETHOD(DebugRenderer, AddNode), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddBoundingBox(const BoundingBox&in, const Color&in, bool depthTest = true)", asMETHODPR(DebugRenderer, AddBoundingBox, (const BoundingBox&, const Color&, bool), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddFrustum(const Frustum&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddFrustum), asCALL_THISCALL);
     engine->RegisterObjectMethod("DebugRenderer", "void AddPolyhedron(const Polyhedron&in, const Color&in, bool depthTest = true)", asMETHOD(DebugRenderer, AddPolyhedron), asCALL_THISCALL);

+ 11 - 4
Engine/Engine/PhysicsAPI.cpp

@@ -169,16 +169,23 @@ static void RegisterConstraint(asIScriptEngine* engine)
     engine->RegisterEnumValue("ConstraintType", "CONSTRAINT_CONETWIST", CONSTRAINT_CONETWIST);
     
     RegisterComponent<Constraint>(engine, "Constraint");
+    engine->RegisterObjectMethod("Constraint", "void SetParameters(ConstraintType, RigidBody@+, const Vector3&in, const Vector3&in, const Vector3&in, const Vector3&in, const Vector2&in, const Vector2&in, bool)", asMETHOD(Constraint, SetParameters), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_constraintType(ConstraintType)", asMETHOD(Constraint, SetConstraintType), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "ConstraintType get_constraintType() const", asMETHOD(Constraint, GetConstraintType), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_position(const Vector3&in)", asMETHOD(Constraint, SetPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "const Vector3& get_position() const", asMETHOD(Constraint, GetPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_axis(const Vector3&in)", asMETHOD(Constraint, SetAxis), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "const Vector3& get_axis() const", asMETHOD(Constraint, GetAxis), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Constraint", "void set_highLimit(const Vector3&in)", asMETHOD(Constraint, SetHighLimit), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Constraint", "const Vector3& get_highLimit() const", asMETHOD(Constraint, GetHighLimit), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Constraint", "void set_lowLimit(const Vector3&in)", asMETHOD(Constraint, SetLowLimit), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Constraint", "const Vector3& get_lowLimit() const", asMETHOD(Constraint, GetLowLimit), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "void set_otherPosition(const Vector3&in)", asMETHOD(Constraint, SetOtherPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "const Vector3& get_otherPosition() const", asMETHOD(Constraint, GetOtherPosition), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "void set_otherAxis(const Vector3&in)", asMETHOD(Constraint, SetOtherAxis), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "const Vector3& get_otherAxis() const", asMETHOD(Constraint, GetOtherAxis), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Constraint", "void set_highLimit(const Vector2&in)", asMETHOD(Constraint, SetHighLimit), asCALL_THISCALL);
+    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_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);
     engine->RegisterObjectMethod("Constraint", "void set_otherBody(RigidBody@+)", asMETHOD(Constraint, SetOtherBody), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "RigidBody@+ get_otherBody() const", asMETHOD(Constraint, GetOtherBody), asCALL_THISCALL);

+ 7 - 7
Engine/Graphics/DebugRenderer.cpp

@@ -84,7 +84,7 @@ void DebugRenderer::AddLine(const Vector3& start, const Vector3& end, unsigned c
         noDepthLines_.Push(DebugLine(start, end, color));
 }
 
-void DebugRenderer::AddNode(Node* node, bool depthTest)
+void DebugRenderer::AddNode(Node* node, float scale, bool depthTest)
 {
     if (!node)
         return;
@@ -94,15 +94,15 @@ void DebugRenderer::AddNode(Node* node, bool depthTest)
     
     if (depthTest)
     {
-        lines_.Push(DebugLine(start, start + rotation * Vector3::RIGHT, Color::RED.ToUInt()));
-        lines_.Push(DebugLine(start, start + rotation * Vector3::UP, Color::GREEN.ToUInt()));
-        lines_.Push(DebugLine(start, start + rotation * Vector3::FORWARD, Color::BLUE.ToUInt()));
+        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::RIGHT), Color::RED.ToUInt()));
+        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::UP), Color::GREEN.ToUInt()));
+        lines_.Push(DebugLine(start, start + rotation * (scale * Vector3::FORWARD), Color::BLUE.ToUInt()));
     }
     else
     {
-        noDepthLines_.Push(DebugLine(start, start + rotation * Vector3::RIGHT, Color::RED.ToUInt()));
-        noDepthLines_.Push(DebugLine(start, start + rotation * Vector3::UP, Color::GREEN.ToUInt()));
-        noDepthLines_.Push(DebugLine(start, start + rotation * Vector3::FORWARD, Color::BLUE.ToUInt()));
+        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::RIGHT), Color::RED.ToUInt()));
+        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::UP), Color::GREEN.ToUInt()));
+        noDepthLines_.Push(DebugLine(start, start + rotation * (scale * Vector3::FORWARD), Color::BLUE.ToUInt()));
     }
 }
 

+ 2 - 2
Engine/Graphics/DebugRenderer.h

@@ -81,8 +81,8 @@ public:
     void AddLine(const Vector3& start, const Vector3& end, const Color& color, bool depthTest = true);
     /// Add a line with color already converted to unsigned.
     void AddLine(const Vector3& start, const Vector3& end, unsigned color, bool depthTest = true);
-    /// Add a scene node represented as its local axes.
-    void AddNode(Node* node, bool depthTest = true);
+    /// Add a scene node represented as its coordinate axes.
+    void AddNode(Node* node, float scale = 1.0f, bool depthTest = true);
     /// Add a bounding box.
     void AddBoundingBox(const BoundingBox& box, const Color& color, bool depthTest = true);
     /// Add a bounding box with transform.

+ 140 - 49
Engine/Physics/Constraint.cpp

@@ -57,14 +57,13 @@ Constraint::Constraint(Context* context) :
     type_(CONSTRAINT_POINT),
     position_(Vector3::ZERO),
     axis_(Vector3::RIGHT),
-    otherBodyPosition_(Vector3::ZERO),
-    otherBodyAxis_(Vector3::RIGHT),
-    lowLimit_(Vector3::ZERO),
-    highLimit_(Vector3::ZERO),
+    otherPosition_(Vector3::ZERO),
+    otherAxis_(Vector3::RIGHT),
+    highLimit_(Vector2::ZERO),
+    lowLimit_(Vector2::ZERO),
     otherBodyNodeID_(0),
     disableCollision_(false),
-    recreateConstraint_(false),
-    otherBodyPositionValid_(false)
+    recreateConstraint_(false)
 {
 }
 
@@ -83,11 +82,11 @@ void Constraint::RegisterObject(Context* context)
     ENUM_ATTRIBUTE(Constraint, "Constraint Type", type_, typeNames, CONSTRAINT_POINT, AM_DEFAULT);
     ATTRIBUTE(Constraint, VAR_VECTOR3, "Position", position_, Vector3::ZERO, AM_DEFAULT);
     ATTRIBUTE(Constraint, VAR_VECTOR3, "Axis", axis_, Vector3::RIGHT, AM_DEFAULT);
-    ATTRIBUTE(Constraint, VAR_VECTOR3, "Other Body Position", otherBodyPosition_, Vector3::ZERO, AM_DEFAULT | AM_NOEDIT);
-    ATTRIBUTE(Constraint, VAR_VECTOR3, "Other Body Axis", otherBodyAxis_, Vector3::RIGHT, AM_DEFAULT | AM_NOEDIT);
-    REF_ACCESSOR_ATTRIBUTE(Constraint, VAR_VECTOR3, "High Limit", GetHighLimit, SetHighLimit, Vector3, Vector3::ZERO, AM_DEFAULT);
-    REF_ACCESSOR_ATTRIBUTE(Constraint, VAR_VECTOR3, "Low Limit", GetLowLimit, SetLowLimit, Vector3, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(Constraint, VAR_VECTOR3, "Other Body Position", otherPosition_, Vector3::ZERO, AM_DEFAULT);
+    ATTRIBUTE(Constraint, VAR_VECTOR3, "Other Body Axis", otherAxis_, Vector3::RIGHT, AM_DEFAULT);
     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);
     ATTRIBUTE(Constraint, VAR_BOOL, "Disable Collision", disableCollision_, false, AM_DEFAULT);
 }
 
@@ -97,11 +96,7 @@ void Constraint::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
     
     // Change of any non-accessor attribute requires recreation of the constraint
     if (!attr.accessor_)
-    {
         recreateConstraint_ = true;
-        if (attr.offset_ == offsetof(Constraint, otherBodyPosition_) || attr.offset_ == offsetof(Constraint, otherBodyAxis_))
-            otherBodyPositionValid_ = true;
-    }
 }
 
 void Constraint::ApplyAttributes()
@@ -176,9 +171,7 @@ void Constraint::SetPosition(const Vector3& position)
     if (position != position_)
     {
         position_ = position;
-        /// \todo Optimize and do not recreate the constraint
-        if (constraint_)
-            CreateConstraint();
+        ApplyFrames();
         MarkNetworkUpdate();
     }
 }
@@ -188,14 +181,32 @@ void Constraint::SetAxis(const Vector3& axis)
     if (axis != axis_)
     {
         axis_ = axis;
-        /// \todo Optimize and do not recreate the constraint
-        if (constraint_ && constraint_->getConstraintType() == HINGE_CONSTRAINT_TYPE)
-            CreateConstraint();
+        ApplyFrames();
         MarkNetworkUpdate();
     }
 }
 
-void Constraint::SetHighLimit(const Vector3& limit)
+void Constraint::SetOtherPosition(const Vector3& position)
+{
+    if (position != otherPosition_)
+    {
+        otherPosition_ = position;
+        ApplyFrames();
+        MarkNetworkUpdate();
+    }
+}
+
+void Constraint::SetOtherAxis(const Vector3& axis)
+{
+    if (axis != otherAxis_)
+    {
+        otherAxis_ = axis;
+        ApplyFrames();
+        MarkNetworkUpdate();
+    }
+}
+
+void Constraint::SetHighLimit(const Vector2& limit)
 {
     if (limit != highLimit_)
     {
@@ -205,7 +216,7 @@ void Constraint::SetHighLimit(const Vector3& limit)
     }
 }
 
-void Constraint::SetLowLimit(const Vector3& limit)
+void Constraint::SetLowLimit(const Vector2& limit)
 {
     if (limit != lowLimit_)
     {
@@ -215,6 +226,39 @@ void Constraint::SetLowLimit(const Vector3& limit)
     }
 }
 
+void Constraint::SetDisableCollision(bool disable)
+{
+    if (disable != disableCollision_)
+    {
+        disableCollision_ = disable;
+        CreateConstraint();
+        MarkNetworkUpdate();
+    }
+}
+
+void Constraint::SetParameters(ConstraintType type, RigidBody* otherBody, const Vector3& position, const Vector3& axis, const Vector3& otherPosition, const Vector3& otherAxis, const Vector2& highLimit, const Vector2& lowLimit, bool disableCollision)
+{
+    if (otherBody_)
+        otherBody_->RemoveConstraint(this);
+    
+    type_ = type;
+    otherBody_ = otherBody;
+    position_ = position;
+    axis_ = axis;
+    otherPosition_ = otherPosition;
+    otherAxis_ = otherAxis;
+    highLimit_ = highLimit;
+    lowLimit_ = lowLimit;
+    disableCollision_ = disableCollision;
+    
+    // Update the connected body attribute
+    Node* otherNode = otherBody_ ? otherBody_->GetNode() : 0;
+    otherBodyNodeID_ = otherNode ? otherNode->GetID() : 0;
+    
+    CreateConstraint();
+    MarkNetworkUpdate();
+}
+
 void Constraint::ReleaseConstraint()
 {
     if (constraint_)
@@ -254,8 +298,9 @@ void Constraint::OnNodeSet(Node* node)
 
 void Constraint::OnMarkedDirty(Node* node)
 {
+    /// \todo This does not catch the connected body node's scale changing
     if (!node->GetWorldScale().Equals(cachedWorldScale_))
-        CreateConstraint();
+        ApplyFrames();
 }
 
 void Constraint::CreateConstraint()
@@ -276,36 +321,25 @@ void Constraint::CreateConstraint()
     if (!otherBody)
         otherBody = &btTypedConstraint::getFixedBody();
     
-    btTransform ownInverse = ownBody->getWorldTransform().inverse();
-    btTransform otherInverse = otherBody->getWorldTransform().inverse();
-    
-    // If the deserialized constraint other body position is valid, use it, but only this time
-    if (otherBodyPositionValid_)
-        otherBodyPositionValid_ = false;
-    else
-    {
-        // Otherwise calculate it from own body's position
-        otherBodyPosition_ = ToVector3(otherInverse * (ownBody->getWorldTransform() * ToBtVector3(position_ *
-            cachedWorldScale_)));
-        otherBodyAxis_ = ToVector3(otherInverse.getBasis() * (ownBody->getWorldTransform().getBasis() *
-            ToBtVector3(axis_)));
-    }
+    Vector3 ownBodyScaledPosition = position_ * cachedWorldScale_;
+    Vector3 otherBodyScaledPosition = otherBody_ ? otherPosition_ * otherBody_->GetNode()->GetWorldScale() :
+        otherPosition_;
     
     switch (type_)
     {
     case CONSTRAINT_POINT:
         {
-            constraint_ = new btPoint2PointConstraint(*ownBody, *otherBody, ToBtVector3(position_ * cachedWorldScale_),
-                ToBtVector3(otherBodyPosition_));
+            constraint_ = new btPoint2PointConstraint(*ownBody, *otherBody, ToBtVector3(ownBodyScaledPosition),
+                ToBtVector3(otherBodyScaledPosition));
         }
         break;
         
     case CONSTRAINT_HINGE:
         {
             Quaternion ownRotation(Vector3::FORWARD, axis_);
-            Quaternion otherRotation(Vector3::FORWARD, otherBodyAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(position_ * cachedWorldScale_));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyPosition_));
+            Quaternion otherRotation(Vector3::FORWARD, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btHingeConstraint(*ownBody, *otherBody, ownFrame, otherFrame);
         }
         break;
@@ -313,9 +347,9 @@ void Constraint::CreateConstraint()
     case CONSTRAINT_SLIDER:
         {
             Quaternion ownRotation(Vector3::RIGHT, axis_);
-            Quaternion otherRotation(Vector3::RIGHT, otherBodyAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(position_ * cachedWorldScale_));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyPosition_));
+            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btSliderConstraint(*ownBody, *otherBody, ownFrame, otherFrame, false);
         }
         break;
@@ -323,9 +357,9 @@ void Constraint::CreateConstraint()
     case CONSTRAINT_CONETWIST:
         {
             Quaternion ownRotation(Vector3::RIGHT, axis_);
-            Quaternion otherRotation(Vector3::RIGHT, otherBodyAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(position_ * cachedWorldScale_));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyPosition_));
+            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btConeTwistConstraint(*ownBody, *otherBody, ownFrame, otherFrame);
         }
         break;
@@ -341,6 +375,63 @@ void Constraint::CreateConstraint()
     physicsWorld_->GetWorld()->addConstraint(constraint_, disableCollision_);
 }
 
+void Constraint::ApplyFrames()
+{
+    if (!constraint_)
+        return;
+    
+    if (node_)
+        cachedWorldScale_ = node_->GetWorldScale();
+    
+    Vector3 ownBodyScaledPosition = position_ * cachedWorldScale_;
+    Vector3 otherBodyScaledPosition = otherBody_ ? otherPosition_ * otherBody_->GetNode()->GetWorldScale() :
+        otherPosition_;
+    
+    switch (constraint_->getConstraintType())
+    {
+    case POINT2POINT_CONSTRAINT_TYPE:
+        {
+            btPoint2PointConstraint* pointConstraint = static_cast<btPoint2PointConstraint*>(constraint_);
+            pointConstraint->setPivotA(ToBtVector3(ownBodyScaledPosition));
+            pointConstraint->setPivotB(ToBtVector3(otherBodyScaledPosition));
+        }
+        break;
+        
+    case HINGE_CONSTRAINT_TYPE:
+        {
+            btHingeConstraint* hingeConstraint = static_cast<btHingeConstraint*>(constraint_);
+            Quaternion ownRotation(Vector3::FORWARD, axis_);
+            Quaternion otherRotation(Vector3::FORWARD, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            hingeConstraint->setFrames(ownFrame, otherFrame);
+        }
+        break;
+        
+    case SLIDER_CONSTRAINT_TYPE:
+        {
+            btSliderConstraint* sliderConstraint = static_cast<btSliderConstraint*>(constraint_);
+            Quaternion ownRotation(Vector3::RIGHT, axis_);
+            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            sliderConstraint->setFrames(ownFrame, otherFrame);
+        }
+        break;
+        
+    case CONETWIST_CONSTRAINT_TYPE:
+        {
+            btConeTwistConstraint* coneTwistConstraint = static_cast<btConeTwistConstraint*>(constraint_);
+            Quaternion ownRotation(Vector3::RIGHT, axis_);
+            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
+            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            coneTwistConstraint->setFrames(ownFrame, otherFrame);
+        }
+        break;
+    }
+}
+
 void Constraint::ApplyLimits()
 {
     if (!constraint_)
@@ -368,7 +459,7 @@ void Constraint::ApplyLimits()
     case CONETWIST_CONSTRAINT_TYPE:
         {
             btConeTwistConstraint* coneTwistConstraint = static_cast<btConeTwistConstraint*>(constraint_);
-            coneTwistConstraint->setLimit(highLimit_.y_ * M_DEGTORAD, highLimit_.z_ * M_DEGTORAD, highLimit_.x_ * M_DEGTORAD);
+            coneTwistConstraint->setLimit(highLimit_.y_ * M_DEGTORAD, highLimit_.y_ * M_DEGTORAD, highLimit_.x_ * M_DEGTORAD);
         }
         break;
     }

+ 24 - 10
Engine/Physics/Constraint.h

@@ -71,10 +71,18 @@ public:
     void SetPosition(const Vector3& position);
     /// %Set constraint axis relative to own body.
     void SetAxis(const Vector3& axis);
+    /// %Set constraint position relative to other body. If constraint connects to static world, this is the static position in world space.
+    void SetOtherPosition(const Vector3& position);
+    /// %Set constraint axis relative to other body. If constraint connects to static world, this is the static axis in world space.
+    void SetOtherAxis(const Vector3& axis);
     /// %Set high limit. Interpretation is constraint type specific.
-    void SetHighLimit(const Vector3& limit);
+    void SetHighLimit(const Vector2& limit);
     /// %Set low limit. Interpretation is constraint type specific.
-    void SetLowLimit(const Vector3& limit);
+    void SetLowLimit(const Vector2& limit);
+    /// %Set whether to disable collisions between connected bodies.
+    void SetDisableCollision(bool disable);
+    /// Set all constraint parameters at once.
+    void SetParameters(ConstraintType type, RigidBody* otherBody, const Vector3& position, const Vector3& axis, const Vector3& otherPosition, const Vector3& otherAxis, const Vector2& highLimit, const Vector2& lowLimit, bool disableCollision);
     
     /// Return physics world.
     PhysicsWorld* GetPhysicsWorld() const { return physicsWorld_; }
@@ -90,10 +98,16 @@ public:
     const Vector3& GetPosition() const { return position_; }
     /// Return constraint axis relative to own body.
     const Vector3& GetAxis() const { return axis_; }
+    /// Return constraint position relative to other body.
+    const Vector3& GetOtherPosition() const { return otherPosition_; }
+    /// Return constraint axis relative to other body.
+    const Vector3& GetOtherAxis() const { return otherAxis_; }
     /// Return high limit.
-    const Vector3& GetHighLimit() const { return highLimit_; }
+    const Vector2& GetHighLimit() const { return highLimit_; }
     /// Return low limit.
-    const Vector3& GetLowLimit() const { return lowLimit_; }
+    const Vector2& GetLowLimit() const { return lowLimit_; }
+    /// Return whether collisions between connected bodies are disabled.
+    bool GetDisableCollision() const { return disableCollision_; }
     
     /// Release the constraint.
     void ReleaseConstraint();
@@ -107,6 +121,8 @@ protected:
 private:
     /// Create the constraint.
     void CreateConstraint();
+    /// Apply constraint frames.
+    void ApplyFrames();
     /// Apply high and low constraint limits.
     void ApplyLimits();
     
@@ -125,21 +141,19 @@ private:
     /// Constraint axis.
     Vector3 axis_;
     /// Constraint other body position.
-    Vector3 otherBodyPosition_;
+    Vector3 otherPosition_;
     /// Constraint other body axis.
-    Vector3 otherBodyAxis_;
+    Vector3 otherAxis_;
     /// Cached world scale for determining if the constraint position needs update.
     Vector3 cachedWorldScale_;
     /// High limit.
-    Vector3 highLimit_;
+    Vector2 highLimit_;
     /// Low limit.
-    Vector3 lowLimit_;
+    Vector2 lowLimit_;
     /// Other body node ID for pending constraint recreation.
     int otherBodyNodeID_;
     /// Disable collision between connected bodies flag.
     bool disableCollision_;
     /// Recreate constraint flag.
     bool recreateConstraint_;
-    /// Other body position valid flag. Used to indicate that it should be used when recreating the joint.
-    bool otherBodyPositionValid_;
 };

+ 20 - 20
Engine/Physics/PhysicsWorld.cpp

@@ -100,6 +100,7 @@ PhysicsWorld::PhysicsWorld(Context* context) :
     timeAcc_(0.0f),
     maxNetworkAngularVelocity_(DEFAULT_MAX_NETWORK_ANGULAR_VELOCITY),
     interpolation_(true),
+    applyingTransforms_(false),
     debugRenderer_(0),
     debugMode_(btIDebugDraw::DBG_DrawWireframe | btIDebugDraw::DBG_DrawConstraints)
 {
@@ -189,6 +190,7 @@ void PhysicsWorld::Update(float timeStep)
     PROFILE(UpdatePhysics);
     
     float internalTimeStep = 1.0f / fps_;
+    delayedWorldTransforms_.Clear();
     
     if (interpolation_)
     {
@@ -204,6 +206,24 @@ void PhysicsWorld::Update(float timeStep)
             timeAcc_ -= internalTimeStep;
         }
     }
+    
+    // Apply delayed (parented) world transforms now
+    while (!delayedWorldTransforms_.Empty())
+    {
+        for (HashMap<RigidBody*, DelayedWorldTransform>::Iterator i = delayedWorldTransforms_.Begin();
+            i != delayedWorldTransforms_.End(); )
+        {
+            HashMap<RigidBody*, DelayedWorldTransform>::Iterator current = i++;
+            const DelayedWorldTransform& transform = current->second_;
+            
+            // If parent's transform has already been assigned, can proceed
+            if (!delayedWorldTransforms_.Contains(transform.parentRigidBody_))
+            {
+                transform.rigidBody_->ApplyWorldTransform(transform.worldPosition_, transform.worldRotation_);
+                delayedWorldTransforms_.Erase(current);
+            }
+        }
+    }
 }
 
 void PhysicsWorld::UpdateCollisions()
@@ -446,8 +466,6 @@ void PhysicsWorld::PreStep(float timeStep)
     eventData[P_TIMESTEP] = timeStep;
     SendEvent(E_PHYSICSPRESTEP, eventData);
     
-    delayedWorldTransforms_.Clear();
-    
     // Start profiling block for the actual simulation step
 #ifdef ENABLE_PROFILING
     Profiler* profiler = GetSubsystem<Profiler>();
@@ -464,24 +482,6 @@ void PhysicsWorld::PostStep(float timeStep)
         profiler->EndBlock();
 #endif
     
-    // Apply delayed (parented) world transforms now
-    while (!delayedWorldTransforms_.Empty())
-    {
-        for (HashMap<RigidBody*, DelayedWorldTransform>::Iterator i = delayedWorldTransforms_.Begin();
-            i != delayedWorldTransforms_.End(); )
-        {
-            HashMap<RigidBody*, DelayedWorldTransform>::Iterator current = i++;
-            const DelayedWorldTransform& transform = current->second_;
-            
-            // If parent's transform has already been assigned, can proceed
-            if (!delayedWorldTransforms_.Contains(transform.parentRigidBody_))
-            {
-                transform.rigidBody_->ApplyWorldTransform(transform.worldPosition_, transform.worldRotation_);
-                delayedWorldTransforms_.Erase(current);
-            }
-        }
-    }
-    
     SendCollisionEvents();
     
     // Send post-step event

+ 11 - 10
Engine/Physics/PhysicsWorld.h

@@ -124,20 +124,15 @@ public:
     void SetInterpolation(bool enable);
     /// %Set maximum angular velocity for network replication.
     void SetMaxNetworkAngularVelocity(float velocity);
-    /// %Set simulation step time accumulator.
-    void SetTimeAccumulator(float time);
     /// Perform a physics world raycast and return all hits.
-    void Raycast(PODVector<PhysicsRaycastResult>& result, const Ray& ray, float maxDistance, unsigned collisionMask =
-        M_MAX_UNSIGNED);
+    void Raycast(PODVector<PhysicsRaycastResult>& result, const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Perform a physics world raycast and return the closest hit.
-    void RaycastSingle(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, unsigned collisionMask =
-        M_MAX_UNSIGNED);
+    void RaycastSingle(PhysicsRaycastResult& result, const Ray& ray, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Perform a physics world swept sphere test and return the closest hit.
-    void SphereCast(PhysicsRaycastResult& result, const Ray& ray, float radius, float maxDistance, unsigned collisionMask =
-        M_MAX_UNSIGNED);
-    /// Perform a sphere test into the physics world.
+    void SphereCast(PhysicsRaycastResult& result, const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);
+    /// Return rigid bodies by a sphere query.
     void GetRigidBodies(PODVector<RigidBody*>& result, const Sphere& sphere, unsigned collisionMask = M_MAX_UNSIGNED);
-    /// Perform a bounding box test into the physics world.
+    /// Return rigid bodies by a box query.
     void GetRigidBodies(PODVector<RigidBody*>& result, const BoundingBox& box, unsigned collisionMask = M_MAX_UNSIGNED);
     /// Return gravity.
     Vector3 GetGravity() const;
@@ -175,6 +170,10 @@ public:
     void CleanupGeometryCache();
     /// Return the collision geometry cache.
     Map<String, SharedPtr<CollisionGeometryData> >& GetGeometryCache() { return geometryCache_; }
+    /// Set node dirtying to be disregarded.
+    void SetApplyingTransforms(bool enable) { applyingTransforms_ = enable; }
+    /// Return whether node dirtying should be disregarded.
+    bool IsApplyingTransforms() const { return applyingTransforms_; }
     
 protected:
     /// Handle node being assigned.
@@ -224,6 +223,8 @@ private:
     float maxNetworkAngularVelocity_;
     /// Interpolation flag.
     bool interpolation_;
+    /// Applying transforms flag.
+    bool applyingTransforms_;
     /// Debug renderer.
     DebugRenderer* debugRenderer_;
     /// Debug draw flags.

+ 5 - 6
Engine/Physics/RigidBody.cpp

@@ -72,7 +72,6 @@ RigidBody::RigidBody(Context* context) :
     lastRotation_(Quaternion::IDENTITY),
     kinematic_(false),
     phantom_(false),
-    inSetTransform_(false),
     hasSmoothedTransform_(false),
     readdBody_(false)
 {
@@ -640,7 +639,7 @@ bool RigidBody::IsActive() const
 
 void RigidBody::ApplyWorldTransform(const Vector3& newWorldPosition, const Quaternion& newWorldRotation)
 {
-    inSetTransform_ = true;
+    physicsWorld_->SetApplyingTransforms(true);
     
     // Apply transform to the SmoothedTransform component instead of node transform if available
     SmoothedTransform* transform = 0;
@@ -662,7 +661,7 @@ void RigidBody::ApplyWorldTransform(const Vector3& newWorldPosition, const Quate
         lastRotation_ = node_->GetWorldRotation();
     }
     
-    inSetTransform_ = false;
+    physicsWorld_->SetApplyingTransforms(false);
 }
 
 void RigidBody::UpdateMass()
@@ -727,7 +726,7 @@ void RigidBody::OnMarkedDirty(Node* node)
     // If node transform changes, apply it back to the physics transform. However, do not do this when a SmoothedTransform
     // is in use, because in that case the node transform will be constantly updated into smoothed, possibly non-physical
     // states; rather follow the SmoothedTransform target transform directly
-    if (!inSetTransform_ && !hasSmoothedTransform_)
+    if ((!physicsWorld_ || !physicsWorld_->IsApplyingTransforms()) && !hasSmoothedTransform_)
     {
         // Physics operations are not safe from worker threads
         Scene* scene = GetScene();
@@ -851,13 +850,13 @@ void RigidBody::AddBodyToWorld()
 void RigidBody::HandleTargetPosition(StringHash eventType, VariantMap& eventData)
 {
     // Copy the smoothing target position to the rigid body
-    if (!inSetTransform_)
+    if (!physicsWorld_ || !physicsWorld_->IsApplyingTransforms())
         SetPosition(static_cast<SmoothedTransform*>(GetEventSender())->GetTargetWorldPosition());
 }
 
 void RigidBody::HandleTargetRotation(StringHash eventType, VariantMap& eventData)
 {
     // Copy the smoothing target rotation to the rigid body
-    if (!inSetTransform_)
+    if (!physicsWorld_ || !physicsWorld_->IsApplyingTransforms())
         SetRotation(static_cast<SmoothedTransform*>(GetEventSender())->GetTargetWorldRotation());
 }

+ 0 - 2
Engine/Physics/RigidBody.h

@@ -240,8 +240,6 @@ private:
     bool kinematic_;
     /// Phantom flag.
     bool phantom_;
-    /// Whether is in Bullet's transform update. Node dirtying is ignored at this point to prevent endless recursion.
-    bool inSetTransform_;
     /// Smoothed transform mode.
     bool hasSmoothedTransform_;
     /// Readd body to world flag.