Skip to content

Serialisation

Serialise all rigid bodies to JSON, for import into external software such as Unreal, Unity, Godot, CryEngine or any other engine able to read and parse JSON, including your own custom game engine.

Units

  • All linear units are in centimeters
  • All angular units are in radians
  • All quaternions are ordered XYZW


Overview

On playback, Ragdoll generates a physical representation of your Maya scene, suitable for simulation. You can gain access to that representation, independent of Maya, for use in your own software and for your own purposes.

This enables you to use Maya as an authoring environment for general-purpose physics scenes, including full or partial ragdolls, for characters or props and even full environments.

Target Audience

  • Game programmers working on a custom engine
  • Technical Directors working with e.g. Unreal Engine, Unity or CryEngine
  • Roboticists exploring algorithms on humanoids with rigid bodies
  • Scientists in Machine Learning in need of bespoke ragdolls for their work

Usecases

Once a ragdoll has been authored in Maya, it can be exported for later import into external software for a variety of purposes.

  • Game Development where main or secondary characters need a physics representation
  • Virtual Production where you need Motion Builder or Unreal Engine to reproduce physics happening in Maya for real-time feedback
  • Robotics where you want interactive control over parameters that are also applied to a physical real-world equivalent, like Boston Dynamics's Atlas
  • Reinforcement Learning where algorithm and articulation depend on each other and are iterated upon in parallel, like OpenAI's gym environments and algorithms
  • Debugging in cases where odd things happen and you just require deep insight into what the solver sees


Concepts

Ragdoll internally stores data as "entities" and "components".

  • Entity is a unique identifier for any "thing" in the solver, like a rigid body, a constraint or force.
  • Component is a set of data, like the transformation or rigid body properties, associated to an entity

The exported format reflects this relationship and looks something like this.

{
  "entities": {
    "10": {
      "components": {
        "NameComponent": "upperArm",
        "ColorComponent": [1.0, 0.0, 0.0],
        "GeometryDescriptionComponent": "Capsule",
        ...
      }
    },
    "15": {
      "components": {
        "NameComponent": "lowerArm",
        "ColorComponent": [0.0, 1.0, 0.0],
        "GeometryDescriptionComponent": "Box",
        ...
      }
  }
}


Rigid Body

A single translation/rotation pair.

Components

Component Description
NameComponent Name and path in Maya
ColorComponent Used in Maya viewport
SceneComponent Reference to the scene entity this rigid belongs to
RestComponent Initial transformation
RigidComponent Physics attributes
GeometryDescriptionComponent Shape attributes
"RigidComponent": {
  "type": "RigidComponent",
  "members": {
    "enabled": true,
    "mass": 1.0,
    "friction": 0.80,
    "restitution": 0.10,
    "thickness": 0.0,
    "disableGravity": false,
    "collide": true,
    "kinematic": false,
    "dynamic": true,
    "sleeping": false,
    "linearDamping": 0.5,
    "angularDamping": 1.0,
    "positionIterations": 8,
    "velocityIterations": 1,
    "maxContactImpulse": -1.0,
    "maxDepenetrationVelocity": -1.0,
    "sleepThreshold": 0.00,
    "enableCCD": false,

    # A value of -1 means "automatically computed"
    "angularMass": {
      "type": "Vector3",
      "values": [-1.0, -1.0, -1.0]
    },

    # A value of 0 means "automatically computed"
    "centerOfMass": {
      "type": "Vector3",
      "values": [0.0, 0.0, 0.0]
    }
  }
}

Shape

Every rigid has exactly one collision shape. The transformation of this shape can be optionally offset, and that offset happens in the frame of the rigid.

    ________
 ^ |\       \
 | | \_______\
 | |  |      |
 o--\-|->    |
  \  \|______|
   \
    v

In this example, the center of the box is offset from the center of the rigid along the X axis. Notice how the geometry is relative the axis of the rigid, so rotating the rigid along the Z axis would naturally take the geometry with it.

"GeometryDescriptionComponent": {
  "type": "GeometryDescriptionComponent",
  "members": {
    "type": "Capsule",

    # Used by Capsule
    "length": 0.123,

    # Used by Sphere
    "radius": 0.012,

    # Used by Box
    "extents": {
      "type": "Vector3",
      "values": [0.123, 0.024, 0.0247]
    },

    # Translation relative the associated rigid
    "offset": {
      "type": "Vector3",
      "values": [0.033, -0.05, 0.00]
    },

    # Rotation relative the associated rigid
    # Ordered as XYZW
    "rotation": {
      "type": "Quaternion",
      "values": [0.87, -0.47, 0.00, 0.00]
    }
  }
}


Constraint

A relationship between two rigid bodies is referred to as a "constraint". A constraint constrains the way two rigid bodies move relative each other.

For example, the position of the lower arm is typically associated with the tip of the upper arm. Wherever the upper arm goes, the lower arm must follow. It may also be further limited in how it is oriented, to e.g. prevent a lower arm from bending past the natural elbow limit; i.e. to rotate between 20-180 degrees along the Z axis, and 0-10 degrees around the X and Y axis (as that rotation would normally come from twisting the upper arm).

  • All constraints are bi-directional
  • Rigid A attaches to B, as B attaches to A
  • The point on A where B attaches is referred to as the parentFrame
  • The point on B where A attaches is referred to as the childFrame

Despite the name, there is no notion of hierarchy or "parent" in Ragdoll; the naming reflects the hierarchy as represented in Maya, where constraints are parented to the rigid representing the childFrame.

Components

Component Description
JointComponent References to associated rigids and frame matrices
LimitComponent Optional limits on translation and/or rotation
DriveComponent Optional target transformation, i.e. the animation
"JointComponent": {
  "type": "JointComponent",
  "members": {

    # Reference to the associated rigid body entities
    "parent": 1048586,
    "child": 1048584,

    # The translate/rotate of the parent
    # rigid in the frame of child rigid
    "parentFrame": {
      "type": "Matrix44",
      "values": [
        0.760, -0.594, -0.259, 0.0,
        -0.648, -0.680, -0.340, 0.0,
        0.0262, 0.4274, -0.903, 0.0,
        10.51, -0.646, 0.0, 1.0
      ]
    },

    # The translate/rotate of the child
    # rigid in the frame of parent rigid
    "childFrame": {
      "type": "Matrix44",
      "values": [
        0.606, -0.751, -0.259, 0.0,
        -0.785, -0.515, -0.340, 0.0,
        0.122, 0.4103, -0.903, 0.0,
        0.0, 0.0, 0.0, 1.0
      ]
    },

    # Allow intersections between connected rigids
    "disableCollision": true
  }
}


Limit

Constraints may optionally have a "limit", which means it can keep a rigid within a given angle ("angular limit") or position ("linear limit").

Min & Max

Values represent a upper end of a range. With x=5 the minimum value of the linear X axis is -5.

"LimitComponent": {
  "type": "LimitComponent",
  "members": {
    "enabled": true,
    "x": -1.0,           # Linear limit along the X-axis
    "y": -1.0,
    "z": -1.0,
    "twist": 0.78,       # Angular limit along the X-axis
    "swing1": 0.78,      # ..Y
    "swing2": 0.78,      # ..Z
    "angularStiffness": 1000000.0,
    "angularDamping": 10000.0,
    "linearStiffness": 1000000.0,
    "linearDamping": 10000.0
  }
}

Locked, Free or Limited

A value of -1 means the axis is "Locked", i.e. the value along this axis cannot change. A Point Constraint is typically locked on all linear axes, but free on the angular axes. A value of 0 means the axis if "Free", meaning it has no effect. It is "limitless". A value above 0 indicates the range of a given limit.

  • <0 means Locked
  • =0 means Free
  • >0 means Limited


Drive

A constraint may optionally have a "drive", which means having one rigid reach a target position and/or angle relative another rigid. The typical use case is having simulation match your input animation, where the animation provides the positions and angles.

"DriveComponent": {
  "type": "DriveComponent",
  "members": {
    "enabled": true,
    "linearStiffness": 0.0,
    "linearDamping": 0.0,
    "angularStiffness": 10000.0,
    "angularDamping": 1000.0,
    "acceleration": true,
    "target": {
      "type": "Matrix44",
      "values": [
        0.973, 0.2267, 0.0, 0.0,
        -0.226, 0.973, 0.0, 0.0,
        0.0, 0.0, 0.999, 0.0,
        0.1051, -0.006, 0.0, 1.0
      ]
    }
  }
}


Data Types

In addition to the plain-old-data types int, double and bool, these are all possible data types found in the exported JSON.

{
  "type": "Color4",
  "values": [
    0.4429999887943268,  # red
    0.7049999833106995,  # green
    0.9520000219345093   # blue
    1.0                  # alpha
  ]
}

{
  "type": "Vector3",
  "values": [
    100.00001788139201,  # x
    100.00001788139201,  # y
    100.00000000000003   # z
  ]
}

{
  "type": "Quaternion",
  "values": [
    0.8791841887437938,     # x
    -0.47648206919348187,   # y
    -2.7953360940182678e-8, # z
    1.191501461145112e-7    # w
  ]
}

{
  # Unscaled, unsheared matrix
  "type": "Matrix44",
  "values": [
    1.0, # rotation matrix
    0.0, # rotation matrix
    0.0, # rotation matrix
    0.0,
    0.0, # rotation matrix
    1.0, # rotation matrix
    0.0, # rotation matrix
    0.0,
    0.0, # rotation matrix
    0.0, # rotation matrix
    1.0, # rotation matrix
    0.0,
    0.0, # translateX
    0.0, # translateY
    0.0, # translateZ
    1.0
  ]
}


Reference

Components

These are all possible types of components found in the exported JSON.

  • SolverComponent
  • SceneComponent
  • NameComponent
  • ColorComponent
  • RestComponent
  • ScaleComponent
  • RigidComponent
  • GeometryDescriptionComponent
  • JointComponent
  • DriveComponent
  • LimitComponent
  • ConstraintUIComponent
  • RigidUIComponent
  • DriveUIComponent
  • LimitUIComponent


Output Example

Here's an example of what a complete dump looks like.


Code Example

Test your dump, by re-building the scene in Maya.

image

from maya import cmds
from ragdoll.vendor import cmdx


class Component(dict):
    """Simplified access to component members"""
    def __getattr__(self, key):
        value = self["members"][key]

        if not isinstance(value, dict):
            return value

        if value["type"] == "Vector3":
            return cmdx.Vector(value["values"])

        elif value["type"] == "Color4":
            return cmdx.Color(value["values"])

        elif value["type"] == "Matrix44":
            return cmdx.Matrix4(value["values"])

        elif value["type"] == "Quaternion":
            return cmdx.Quaternion(*value["values"])

        else:
            raise TypeError("Unsupported type: %s" % value)


def dedump(dump):
    with cmdx.DagModifier() as mod:
        root = mod.createNode("transform", name="dump")

    for entity, data in dump["entities"].items():
        comps = data["components"]

        if "RigidComponent" not in comps:
            continue

        name = Component(comps["NameComponent"])

        if not name.path:
            # Bad export
            continue

        joint = name.path.rsplit("|", 3)[-2]

        scale = Component(comps["ScaleComponent"])
        rest = Component(comps["RestComponent"])
        desc = Component(comps["GeometryDescriptionComponent"])

        # Establish rigid transformation
        tm = cmdx.TransformationMatrix(rest.matrix)

        # Establish shape
        if desc.type in ("Cylinder", "Capsule"):
            radius = desc.radius * scale.absolute.x
            length = desc.length * scale.absolute.y
            geo, _ = cmds.polyCylinder(axis=(1, 0, 0),
                                       radius=radius,
                                       height=length,
                                       roundCap=True,
                                       subdivisionsCaps=5)

        elif desc.type == "Box":
            extents = desc.extents
            extents.x *= scale.absolute.x
            extents.y *= scale.absolute.y
            extents.z *= scale.absolute.z
            geo, _ = cmds.polyCube(width=extents.x,
                                   height=extents.y,
                                   depth=extents.z)

        elif desc.type == "Sphere":
            radius = desc.radius * scale.absolute.x
            geo, _ = cmds.polySphere(radius=radius)

        else:
            print("Unsupported shape type: %s.type=%s" % (name.path, desc.type))
            continue

        with cmdx.DagModifier() as mod:
            transform = mod.createNode("transform", name=joint, parent=root)
            transform["translate"] = tm.translation()
            transform["rotate"] = tm.rotation()

        # Establish shape transformation
        offset = desc.offset
        offset.x *= scale.absolute.x
        offset.y *= scale.absolute.y
        offset.z *= scale.absolute.z

        geo = cmdx.encode(geo)
        geo["translate"] = offset
        geo["rotate"] = desc.rotation

        transform.addChild(geo)


# Usage Example
import json
dump = cmds.ragdollDump()
dump = json.loads(dump)
dedump(dump)


More Examples

Tiger

Courtesy of www.cgspectrum.com

Ragcar

Model from https://mecabricks.com

Advanced Skeleton

A generic human character, rigged with AS.