Browse Source

Improved ragdoll stability in TestScene.
Create ragdolls when hit by the boxes (both TestScene & TestSceneOld.)
Fixed component ID clash when creating ragdolls in networked TestScene.
Constraints can specify rotation frame directly. Specifying the axis is still provided for convenience, but does not give exact control over the orientation.
Constraint adjusts static world position automatically when the own body position is edited.
Optimized away redundant Constraint recreation when deserializing attributes.
Added RemoveComponent by component type to Node.
Show also write-only properties in the generated scripting API documentation.

Lasse Öörni 13 years ago
parent
commit
4666520d0b

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

@@ -33,6 +33,7 @@ void Start()
     SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
     SubscribeToEvent("SpawnBox", "HandleSpawnBox");
+    SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision");
 
     network.RegisterRemoteEvent("SpawnBox");
 
@@ -219,6 +220,12 @@ void InitScene()
         object.material = cache.GetResource("Material", "Materials/Jack.xml");
         object.castShadows = true;
 
+        // Create a capsule shape for detecting collisions
+        RigidBody@ body = objectNode.CreateComponent("RigidBody");
+        body.phantom = true;
+        CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
+        shape.SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0));
+
         AnimationController@ ctrl = objectNode.CreateComponent("AnimationController");
         ctrl.Play("Models/Jack_Walk.ani", 0, true, 0.0f);
     }
@@ -450,10 +457,6 @@ void HandlePostRenderUpdate()
             Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
             testScene.debugRenderer.AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
                 Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
-                
-            // Test creating a ragdoll
-            if (input.keyPress['R'] && result.drawable.typeName == "AnimatedModel")
-                CreateRagdoll(result.drawable);
         }
     }
 }
@@ -465,28 +468,49 @@ void HandleClientConnected(StringHash eventType, VariantMap& eventData)
     connection.logStatistics = true;
 }
 
+void HandlePhysicsCollision(StringHash eventType, VariantMap& eventData)
+{
+    // Check if either of the nodes has an AnimatedModel component
+    Node@ nodeA = eventData["NodeA"].GetNode();
+    Node@ nodeB = eventData["NodeB"].GetNode();
+    if (nodeA.HasComponent("AnimatedModel"))
+    {
+        // Remove the trigger physics shape, and create the ragdoll
+        nodeA.RemoveComponent("RigidBody");
+        nodeA.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeA.GetComponent("AnimatedModel"));
+    }
+    else if (nodeB.HasComponent("AnimatedModel"))
+    {
+        // Remove the trigger physics shape, and create the ragdoll
+        nodeB.RemoveComponent("RigidBody");
+        nodeB.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeB.GetComponent("AnimatedModel"));
+    }
+}
+
 void CreateRagdoll(AnimatedModel@ model)
 {
     Node@ root = model.node;
     
-    CreateRagdollBone(root, "Bip01_Pelvis", 1.0, SHAPE_BOX, Vector3(0.2, 0.2, 0.2), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
-    CreateRagdollBone(root, "Bip01_Spine1", 1.0, SHAPE_BOX, Vector3(0.25, 0.2, 0.2), Vector3(0.15, 0, 0), Quaternion(0, 0, 0));
-    CreateRagdollBone(root, "Bip01_L_Thigh", 0.75, SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_R_Thigh", 0.75, SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_L_Calf", 0.75, SHAPE_CAPSULE, Vector3(0.15, 0.45, 0.15), Vector3(0.2, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_R_Calf", 0.75, SHAPE_CAPSULE, Vector3(0.15, 0.45, 0.15), Vector3(0.2, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_Head", 0.75, SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
-    CreateRagdollBone(root, "Bip01_L_UpperArm", 0.5, SHAPE_CAPSULE, Vector3(0.125, 0.3, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_R_UpperArm", 0.5, SHAPE_CAPSULE, Vector3(0.125, 0.3, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_L_Forearm", 0.5, SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
-    CreateRagdollBone(root, "Bip01_R_Forearm", 0.5, SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
-
-    CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 15), Vector2(0, 0));
-    CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 15), Vector2(0, 0));
+    CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_CAPSULE, Vector3(0.3, 0.3, 0.3), Vector3(0.0, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_Spine1", SHAPE_CAPSULE, Vector3(0.3, 0.4, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_Head", SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+
+    CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
     CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
     CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
-    CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(30, 15), Vector2(0, 0));
-    CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 15), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(90, 0), Vector2(-25, 0));
+    CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
     CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
     CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
     CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
@@ -498,16 +522,21 @@ void CreateRagdoll(AnimatedModel@ model)
         skel.bones[i].animated = false;
 }
 
-void CreateRagdollBone(Node@ root, String boneName, float mass, ShapeType type, Vector3 size, Vector3 position, Quaternion rotation)
+void CreateRagdollBone(Node@ root, String boneName, ShapeType type, Vector3 size, Vector3 position, Quaternion rotation)
 {
     Node@ boneNode = root.GetChild(boneName, true);
     if (boneNode is null || boneNode.HasComponent("RigidBody"))
         return;
     
+    // In networked operation both client and server detect collisions separately, and create ragdolls on their own
+    // (bones are not synced over network.) To prevent replicated component ID range clashes when the client creates
+    // any components, it is important that the LOCAL creation mode is specified.
     RigidBody@ body = boneNode.CreateComponent("RigidBody", LOCAL);
-    body.mass = mass;
-    body.linearDamping = 0.75;
-    body.angularDamping = 0.75;
+    body.mass = 1.0;
+    body.linearDamping = 0.05;
+    body.angularDamping = 0.85;
+    body.linearRestThreshold = 1.5;
+    body.angularRestThreshold = 2.5;
 
     CollisionShape@ shape = boneNode.CreateComponent("CollisionShape", LOCAL);
     shape.shapeType = type;
@@ -523,7 +552,7 @@ void CreateRagdollConstraint(Node@ root, String boneName, String parentName, Con
     if (boneNode is null || parentNode is null || boneNode.HasComponent("Constraint"))
         return;
         
-    Constraint@ constraint = boneNode.CreateComponent("Constraint");
+    Constraint@ constraint = boneNode.CreateComponent("Constraint", LOCAL);
     constraint.constraintType = type;
     constraint.disableCollision = true;
     // The connected body must be specified before setting the world position

+ 104 - 0
Bin/Data/Scripts/TestSceneOld.as

@@ -45,6 +45,7 @@ void Start()
     SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
     SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
     SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
+    SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision");    
 }
 
 void InitScene()
@@ -166,6 +167,12 @@ void InitScene()
         object.castShadows = true;
         object.maxLights = 2;
 
+        // Create a capsule shape for detecting collisions
+        RigidBody@ body = newNode.CreateComponent("RigidBody");
+        body.phantom = true;
+        CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
+        shape.SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0));
+
         AnimationState@ anim = object.AddAnimationState(cache.GetResource("Animation", "Models/Jack_Walk.ani"));
         anim.looped = true;
         anim.weight = 1.0;
@@ -544,6 +551,103 @@ void HandlePostRenderUpdate()
             Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
             testScene.debugRenderer.AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
                 Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
+                
+            // Test creating a ragdoll
+            if (input.keyPress['R'] && result.drawable.typeName == "AnimatedModel")
+                CreateRagdoll(result.drawable);
         }
     }
 }
+
+void HandlePhysicsCollision(StringHash eventType, VariantMap& eventData)
+{
+    // Check if either of the nodes has an AnimatedModel component
+    Node@ nodeA = eventData["NodeA"].GetNode();
+    Node@ nodeB = eventData["NodeB"].GetNode();
+    if (nodeA.HasComponent("AnimatedModel"))
+    {
+        // Remove the trigger physics shape, and create the ragdoll
+        nodeA.RemoveComponent("RigidBody");
+        nodeA.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeA.GetComponent("AnimatedModel"));
+    }
+    else if (nodeB.HasComponent("AnimatedModel"))
+    {
+        // Remove the trigger physics shape, and create the ragdoll
+        nodeB.RemoveComponent("RigidBody");
+        nodeB.RemoveComponent("CollisionShape");
+        CreateRagdoll(nodeB.GetComponent("AnimatedModel"));
+    }
+}
+
+void CreateRagdoll(AnimatedModel@ model)
+{
+    Node@ root = model.node;
+    
+    CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_CAPSULE, Vector3(0.3, 0.3, 0.3), Vector3(0.0, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_Spine1", SHAPE_CAPSULE, Vector3(0.3, 0.4, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_Head", SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
+    CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+    CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
+
+    CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(90, 0), Vector2(-25, 0));
+    CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+    CreateRagdollConstraint(root, "Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
+
+    // Disable animation from all bones (both physical and non-physical) to not interfere
+    Skeleton@ skel = model.skeleton;
+    for (uint i = 0; i < skel.numBones; ++i)
+        skel.bones[i].animated = false;
+}
+
+void CreateRagdollBone(Node@ root, String boneName, ShapeType type, Vector3 size, Vector3 position, Quaternion rotation)
+{
+    Node@ boneNode = root.GetChild(boneName, true);
+    if (boneNode is null || boneNode.HasComponent("RigidBody"))
+        return;
+    
+    RigidBody@ body = boneNode.CreateComponent("RigidBody", LOCAL);
+    body.mass = 1.0;
+    body.linearDamping = 0.05;
+    body.angularDamping = 0.85;
+    body.linearRestThreshold = 1.5;
+    body.angularRestThreshold = 2.5;
+
+    CollisionShape@ shape = boneNode.CreateComponent("CollisionShape", LOCAL);
+    shape.shapeType = type;
+    shape.size = size;
+    shape.position = position;
+    shape.rotation = rotation;
+}
+
+void CreateRagdollConstraint(Node@ root, String boneName, String parentName, ConstraintType type, Vector3 axis, Vector3 parentAxis, Vector2 highLimit, Vector2 lowLimit)
+{
+    Node@ boneNode = root.GetChild(boneName, true);
+    Node@ parentNode = root.GetChild(parentName, true);
+    if (boneNode is null || parentNode is null || boneNode.HasComponent("Constraint"))
+        return;
+        
+    Constraint@ constraint = boneNode.CreateComponent("Constraint", LOCAL);
+    constraint.constraintType = type;
+    constraint.disableCollision = true;
+    // The connected body must be specified before setting the world position
+    constraint.otherBody = parentNode.GetComponent("RigidBody");
+    constraint.worldPosition = boneNode.worldPosition;
+    constraint.axis = axis;
+    constraint.otherAxis = parentAxis;
+    constraint.highLimit = highLimit;
+    constraint.lowLimit = lowLimit;
+}

+ 1 - 2
Docs/GettingStarted.dox

@@ -49,7 +49,7 @@ Key and mouse controls:
 
 \verbatim
 WSAD        Move
-Left mouse  Create a new physics object
+Left mouse  Create a new physics object; characters will ragdoll when hit
 Right mouse Hold and move mouse to rotate view
 Space       Toggle debug geometry
 F1          Toggle AngelScript console
@@ -60,7 +60,6 @@ T           Toggle profiling display
 C           Toggle orthographic camera
 F           Toggle FXAA edge filter
 B           Toggle bloom post-process
-R           Create ragdoll (when pointing at an animated model)
 \endverbatim
 
 TestScene also includes a network replication test, where clients can connect, move around as invisible cameras, and create new physics objects. For this, a server needs to be started with the command TestScene.bat server (-headless switch can optionally given so that the server will not open a graphics window) and clients can connect by specifying the server address on the command line, for example TestScene.bat 127.0.0.1

+ 4 - 4
Docs/Reference.dox

@@ -844,11 +844,11 @@ motion. For ragdolls this is not absolute, as retaining proper bone hierarchy is
 
 \section Physics_ConstraintParameters Constraint parameters
 
-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.
+%Constraint position (and rotation if relevant) need to be defined in relation to both connected bodies, see \ref Constraint::SetPosition "SetPosition()" and \ref Constraint::SetOtherPosition "SetOtherPosition()". If the constraint connects a body to the static world, then the "other body position" and "other body rotation" mean the static end's transform in world space. There is also a helper function \ref Constraint::SetWorldPosition "SetWorldPosition()" to assign the constraint to a world-space position; this sets both relative positions.
 
-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 is the limit of the swinging motion about the other axes.
+Specifying the constraint's motion axis instead of rotation is provided as an alternative as it can be more intuitive, see \ref Constraint::SetAxis "SetAxis()". However, by explicitly specifying a rotation you can be sure the constraint is oriented precisely as you want.
+
+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 is the limit of the swinging motion about the other axes.
 
 \section Physics_Events Physics events
 

File diff suppressed because it is too large
+ 199 - 186
Docs/ScriptAPI.dox


+ 7 - 1
Engine/Engine/APITemplates.h

@@ -391,6 +391,11 @@ static Component* NodeGetOrCreateComponent(const String& typeName, CreateMode mo
     return ptr->GetOrCreateComponent(ShortStringHash(typeName), mode);
 }
 
+static void NodeRemoveComponent(const String& typeName, Node* ptr)
+{
+    ptr->RemoveComponent(ShortStringHash(typeName));
+}
+
 static Component* NodeGetComponent(unsigned index, Node* ptr)
 {
     const Vector<SharedPtr<Component> >& components = ptr->GetComponents();
@@ -527,7 +532,8 @@ template <class T> void RegisterNode(asIScriptEngine* engine, const char* classN
     engine->RegisterObjectMethod(className, "void Remove()", asMETHOD(T, Remove), asCALL_THISCALL);
     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, "void RemoveComponent(Component@+)", asMETHOD(T, RemoveComponent), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void RemoveComponent(Component@+)", asMETHODPR(T, RemoveComponent, (Component*), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod(className, "void RemoveComponent(const String&in)", asFUNCTION(NodeRemoveComponent), 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);

+ 4 - 2
Engine/Engine/PhysicsAPI.cpp

@@ -173,12 +173,14 @@ static void RegisterConstraint(asIScriptEngine* engine)
     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_rotation(const Quaternion&in)", asMETHOD(Constraint, SetRotation), 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", "const Quaternion& get_rotation() const", asMETHOD(Constraint, GetRotation), 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_otherRotation(const Quaternion&in)", asMETHOD(Constraint, SetOtherRotation), 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", "const Quaternion& get_otherRotation() const", asMETHOD(Constraint, GetOtherRotation), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_worldPosition(const Vector3&in)", asMETHOD(Constraint, SetWorldPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "Vector3 get_worldPosition() const", asMETHOD(Constraint, GetWorldPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Constraint", "void set_highLimit(const Vector2&in)", asMETHOD(Constraint, SetHighLimit), asCALL_THISCALL);

+ 93 - 42
Engine/Physics/Constraint.cpp

@@ -54,16 +54,17 @@ OBJECTTYPESTATIC(Constraint);
 Constraint::Constraint(Context* context) :
     Component(context),
     constraint_(0),
-    type_(CONSTRAINT_POINT),
+    constraintType_(CONSTRAINT_POINT),
     position_(Vector3::ZERO),
-    axis_(Vector3::RIGHT),
+    rotation_(Quaternion::IDENTITY),
     otherPosition_(Vector3::ZERO),
-    otherAxis_(Vector3::RIGHT),
+    otherRotation_(Quaternion::IDENTITY),
     highLimit_(Vector2::ZERO),
     lowLimit_(Vector2::ZERO),
     otherBodyNodeID_(0),
     disableCollision_(false),
-    recreateConstraint_(false)
+    recreateConstraint_(false),
+    framesDirty_(false)
 {
 }
 
@@ -79,11 +80,11 @@ void Constraint::RegisterObject(Context* context)
 {
     context->RegisterFactory<Constraint>();
     
-    ENUM_ATTRIBUTE(Constraint, "Constraint Type", type_, typeNames, CONSTRAINT_POINT, AM_DEFAULT);
+    ENUM_ATTRIBUTE(Constraint, "Constraint Type", constraintType_, 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_QUATERNION, "Rotation", rotation_, Quaternion::IDENTITY, 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_QUATERNION, "Other Body Rotation", otherRotation_, Quaternion::IDENTITY, 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);
@@ -94,9 +95,25 @@ void Constraint::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
 {
     Component::OnSetAttribute(attr, src);
     
-    // Change of any non-accessor attribute requires recreation of the constraint
     if (!attr.accessor_)
-        recreateConstraint_ = true;
+    {
+        // Convenience for editing static constraints: if not connected to another body, adjust world position to match local
+        // (when deserializing, the proper other body position will be read after own position, so this calculation is safely
+        // overridden and does not accumulate constraint error
+        if (attr.offset_ == offsetof(Constraint, position_) && constraint_ && !otherBody_)
+        {
+            btTransform ownBody = constraint_->getRigidBodyA().getWorldTransform();
+            btVector3 worldPos = ownBody * ToBtVector3(position_ * cachedWorldScale_);
+            otherPosition_ = ToVector3(worldPos);
+        }
+        
+        // Certain attribute changes require recreation of the constraint
+        if (attr.offset_ == offsetof(Constraint, constraintType_) || attr.offset_ == offsetof(Constraint, otherBodyNodeID_) ||
+            attr.offset_ == offsetof(Constraint, disableCollision_))
+            recreateConstraint_ = true;
+        else
+            framesDirty_ = true;
+    }
 }
 
 void Constraint::ApplyAttributes()
@@ -118,6 +135,12 @@ void Constraint::ApplyAttributes()
         
         CreateConstraint();
         recreateConstraint_ = false;
+        framesDirty_ = false;
+    }
+    else if (framesDirty_)
+    {
+        ApplyFrames();
+        framesDirty_ = false;
     }
 }
 
@@ -140,9 +163,9 @@ void Constraint::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 
 void Constraint::SetConstraintType(ConstraintType type)
 {
-    if (type != type_)
+    if (type != constraintType_)
     {
-        type_ = type;
+        constraintType_ = type;
         CreateConstraint();
         MarkNetworkUpdate();
     }
@@ -176,16 +199,35 @@ void Constraint::SetPosition(const Vector3& position)
     }
 }
 
-void Constraint::SetAxis(const Vector3& axis)
+void Constraint::SetRotation(const Quaternion& rotation)
 {
-    if (axis != axis_)
+    if (rotation != rotation_)
     {
-        axis_ = axis;
+        rotation_ = rotation;
         ApplyFrames();
         MarkNetworkUpdate();
     }
 }
 
+void Constraint::SetAxis(const Vector3& axis)
+{
+    switch (constraintType_)
+    {
+    case CONSTRAINT_POINT:
+    case CONSTRAINT_HINGE:
+        rotation_ = Quaternion(Vector3::FORWARD, axis);
+        break;
+        
+    case CONSTRAINT_SLIDER:
+    case CONSTRAINT_CONETWIST:
+        rotation_ = Quaternion(Vector3::RIGHT, axis);
+        break;
+    }
+    
+    ApplyFrames();
+    MarkNetworkUpdate();
+}
+
 void Constraint::SetOtherPosition(const Vector3& position)
 {
     if (position != otherPosition_)
@@ -196,16 +238,35 @@ void Constraint::SetOtherPosition(const Vector3& position)
     }
 }
 
-void Constraint::SetOtherAxis(const Vector3& axis)
+void Constraint::SetOtherRotation(const Quaternion& rotation)
 {
-    if (axis != otherAxis_)
+    if (rotation != otherRotation_)
     {
-        otherAxis_ = axis;
+        otherRotation_ = rotation;
         ApplyFrames();
         MarkNetworkUpdate();
     }
 }
 
+void Constraint::SetOtherAxis(const Vector3& axis)
+{
+    switch (constraintType_)
+    {
+    case CONSTRAINT_POINT:
+    case CONSTRAINT_HINGE:
+        otherRotation_ = Quaternion(Vector3::FORWARD, axis);
+        break;
+        
+    case CONSTRAINT_SLIDER:
+    case CONSTRAINT_CONETWIST:
+        otherRotation_ = Quaternion(Vector3::RIGHT, axis);
+        break;
+    }
+    
+    ApplyFrames();
+    MarkNetworkUpdate();
+}
+
 void Constraint::SetWorldPosition(const Vector3& position)
 {
     if (constraint_)
@@ -220,6 +281,8 @@ void Constraint::SetWorldPosition(const Vector3& position)
         ApplyFrames();
         MarkNetworkUpdate();
     }
+    else
+        LOGWARNING("Constraint not created, world position could not be stored");
 }
 
 void Constraint::SetHighLimit(const Vector2& limit)
@@ -329,7 +392,7 @@ void Constraint::CreateConstraint()
     Vector3 otherBodyScaledPosition = otherBody_ ? otherPosition_ * otherBody_->GetNode()->GetWorldScale() :
         otherPosition_;
     
-    switch (type_)
+    switch (constraintType_)
     {
     case CONSTRAINT_POINT:
         {
@@ -340,30 +403,24 @@ void Constraint::CreateConstraint()
         
     case CONSTRAINT_HINGE:
         {
-            Quaternion ownRotation(Vector3::FORWARD, axis_);
-            Quaternion otherRotation(Vector3::FORWARD, otherAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btHingeConstraint(*ownBody, *otherBody, ownFrame, otherFrame);
         }
         break;
         
     case CONSTRAINT_SLIDER:
         {
-            Quaternion ownRotation(Vector3::RIGHT, axis_);
-            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btSliderConstraint(*ownBody, *otherBody, ownFrame, otherFrame, false);
         }
         break;
         
     case CONSTRAINT_CONETWIST:
         {
-            Quaternion ownRotation(Vector3::RIGHT, axis_);
-            Quaternion otherRotation(Vector3::RIGHT, otherAxis_);
-            btTransform ownFrame(ToBtQuaternion(ownRotation), ToBtVector3(ownBodyScaledPosition));
-            btTransform otherFrame(ToBtQuaternion(otherRotation), ToBtVector3(otherBodyScaledPosition));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             constraint_ = new btConeTwistConstraint(*ownBody, *otherBody, ownFrame, otherFrame);
         }
         break;
@@ -404,10 +461,8 @@ void Constraint::ApplyFrames()
     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));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             hingeConstraint->setFrames(ownFrame, otherFrame);
         }
         break;
@@ -415,10 +470,8 @@ void Constraint::ApplyFrames()
     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));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             sliderConstraint->setFrames(ownFrame, otherFrame);
         }
         break;
@@ -426,10 +479,8 @@ void Constraint::ApplyFrames()
     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));
+            btTransform ownFrame(ToBtQuaternion(rotation_), ToBtVector3(ownBodyScaledPosition));
+            btTransform otherFrame(ToBtQuaternion(otherRotation_), ToBtVector3(otherBodyScaledPosition));
             coneTwistConstraint->setFrames(ownFrame, otherFrame);
         }
         break;

+ 20 - 14
Engine/Physics/Constraint.h

@@ -69,13 +69,17 @@ public:
     void SetOtherBody(RigidBody* body);
     /// %Set constraint position relative to own body.
     void SetPosition(const Vector3& position);
-    /// %Set constraint axis relative to own body.
+    /// %Set constraint rotation relative to own body.
+    void SetRotation(const Quaternion& rotation);
+    /// %Set constraint rotation relative to own body by specifying the axis.
     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.
+    /// %Set constraint position relative to the other body. If connected to the static world, is a world-space position.
     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.
+    /// %Set constraint rotation relative to the other body. If connected to the static world, is a world-space rotation.
+    void SetOtherRotation(const Quaternion& rotation);
+    /// %Set constraint rotation relative to the other body by specifying the axis.
     void SetOtherAxis(const Vector3& axis);
-    ///% Set constraint world position. Sets both own and other body positions to the same point.
+    ///% Set constraint world-space position. Resets both own and other body relative position, ie. zeroes the constraint error.
     void SetWorldPosition(const Vector3& position);
     /// %Set high limit. Interpretation is constraint type specific.
     void SetHighLimit(const Vector2& limit);
@@ -89,20 +93,20 @@ public:
     /// Return Bullet constraint.
     btTypedConstraint* GetConstraint() const { return constraint_; }
     /// Return constraint type.
-    ConstraintType GetConstraintType() const { return type_; }
+    ConstraintType GetConstraintType() const { return constraintType_; }
     /// Return rigid body in own scene node.
     RigidBody* GetOwnBody() const { return ownBody_; }
     /// Return the other rigid body. May be null if connected to the static world.
     RigidBody* GetOtherBody() const { return otherBody_; }
     /// Return constraint position relative to own body.
     const Vector3& GetPosition() const { return position_; }
-    /// Return constraint axis relative to own body.
-    const Vector3& GetAxis() const { return axis_; }
+    /// Return constraint rotation relative to own body.
+    const Quaternion& GetRotation() const { return rotation_; }
     /// 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 constraint world position.
+    /// Return constraint rotation relative to other body.
+    const Quaternion& GetOtherRotation() const { return otherRotation_; }
+    /// Return constraint world position, calculated from own body.
     Vector3 GetWorldPosition() const;
     /// Return high limit.
     const Vector2& GetHighLimit() const { return highLimit_; }
@@ -137,15 +141,15 @@ private:
     /// Bullet constraint.
     btTypedConstraint* constraint_;
     /// Constraint type.
-    ConstraintType type_;
+    ConstraintType constraintType_;
     /// Constraint position.
     Vector3 position_;
-    /// Constraint axis.
-    Vector3 axis_;
+    /// Constraint rotation.
+    Quaternion rotation_;
     /// Constraint other body position.
     Vector3 otherPosition_;
     /// Constraint other body axis.
-    Vector3 otherAxis_;
+    Quaternion otherRotation_;
     /// Cached world scale for determining if the constraint position needs update.
     Vector3 cachedWorldScale_;
     /// High limit.
@@ -158,4 +162,6 @@ private:
     bool disableCollision_;
     /// Recreate constraint flag.
     bool recreateConstraint_;
+    /// Frames need update flag.
+    bool framesDirty_;
 };

+ 24 - 0
Engine/Scene/Node.cpp

@@ -596,6 +596,30 @@ void Node::RemoveComponent(Component* component)
     }
 }
 
+void Node::RemoveComponent(ShortStringHash type)
+{
+    for (Vector<SharedPtr<Component> >::Iterator i = components_.Begin(); i != components_.End(); ++i)
+    {
+        if ((*i)->GetType() == type)
+        {
+            WeakPtr<Component> componentWeak(*i);
+            
+            RemoveListener(*i);
+            if (scene_)
+                scene_->ComponentRemoved(*i);
+            components_.Erase(i);
+            
+            // If the component is still referenced elsewhere, reset its node pointer now
+            if (componentWeak)
+                componentWeak->SetNode(0);
+            
+            // Mark node dirty in all replication states
+            MarkReplicationDirty();
+            return;
+        }
+    }
+}
+
 void Node::RemoveAllComponents()
 {
     if (components_.Empty())

+ 2 - 0
Engine/Scene/Node.h

@@ -145,6 +145,8 @@ public:
     Component* GetOrCreateComponent(ShortStringHash type, CreateMode mode = REPLICATED);
     /// Remove a component from this node.
     void RemoveComponent(Component* component);
+    /// Remove the first component of specific type from this node.
+    void RemoveComponent(ShortStringHash type);
     /// Remove all components from this node.
     void RemoveAllComponents();
     /// Clone scene node, components and child nodes. Return the clone.

+ 33 - 13
Engine/Script/Script.cpp

@@ -91,9 +91,37 @@ void ExtractPropertyInfo(const String& functionName, const String& declaration,
             info->indexed_ = true;
             info->type_ += "[]";
         }
+        
+        // Sanitate the reference operator away
+        info->type_.Replace("&", "");
     }
     if (functionName.Find("set_") != String::NPOS)
+    {
         info->write_ = true;
+        if (info->type_.Empty())
+        {
+            // Extract type from parameters
+            unsigned begin = declaration.Find(',');
+            if (begin == String::NPOS)
+                begin = declaration.Find('(');
+            else
+                info->indexed_ = true;
+            
+            if (begin != String::NPOS)
+            {
+                ++begin;
+                unsigned end = declaration.Find(')');
+                if (end != String::NPOS)
+                {
+                    info->type_ = declaration.Substring(begin, end - begin);
+                    // Sanitate const & reference operator away
+                    info->type_.Replace("const ", "");
+                    info->type_.Replace("&in", "");
+                    info->type_.Replace("&", "");
+                }
+            }
+        }
+    }
 }
 
 OBJECTTYPESTATIC(Script);
@@ -241,13 +269,7 @@ void Script::DumpAPI()
     LOGRAW("\\section ScriptAPI_GlobalProperties Global properties\n");
     
     for (unsigned i = 0; i < globalPropertyInfos.Size(); ++i)
-    {
-        // For now, skip write-only properties
-        if (!globalPropertyInfos[i].read_)
-            continue;
-        
         OutputAPIRow(globalPropertyInfos[i].type_ + " " + globalPropertyInfos[i].name_, true);
-    }
     
     LOGRAW("\\section ScriptAPI_GlobalConstants Global constants\n");
     
@@ -328,15 +350,13 @@ void Script::DumpAPI()
                 LOGRAW("\nProperties:<br>\n");
                 for (unsigned j = 0; j < propertyInfos.Size(); ++j)
                 {
-                    // For now, skip write-only properties
-                    if (!propertyInfos[j].read_)
-                        continue;
-                    
-                    String readOnly;
+                    String remark;
                     if (!propertyInfos[j].write_)
-                        readOnly = " (readonly)";
+                        remark = " (readonly)";
+                    else if (!propertyInfos[j].read_)
+                        remark = " (writeonly)";
                     
-                    OutputAPIRow(propertyInfos[j].type_ + " " + propertyInfos[j].name_ + readOnly);
+                    OutputAPIRow(propertyInfos[j].type_ + " " + propertyInfos[j].name_ + remark);
                 }
             }
             

Some files were not shown because too many files changed in this diff