Table of Contents

Constraints

Constraints limit how two bodies can move relative to each other. A joint combines constraints into a familiar connection, such as a hinge or a weld.

For a complete example with two boxes, a hinge, and a debug drawer, follow the Hinge tutorial.

Degrees of Freedom

A body can move along three axes and rotate around three axes. These are its six degrees of freedom (DOF). A constraint restricts one or more of them.

Constraint Removed DOF Description
BallSocket 3 translational Anchors a point on each body together
FixedAngle 3 rotational Locks relative orientation between bodies
HingeAngle 2 rotational (3 with limit) Allows rotation around a single axis only, optionally enforces angular limits
PointOnLine 2 translational (3 with limit) Constrains a point to a line, optionally limits distance along the axis
PointOnPlane 1 translational (when limit active) Keeps an anchor on a plane or within a range of distances from it
DistanceLimit 1 translational (when limit active) Constrains the distance between anchor points, only enforced when outside the limit range
TwistAngle 1 rotational (when limit active) Limits relative twist around an axis, only enforced when outside the limit range
ConeLimit 1 rotational (when limit active) Limits angular tilt between two axes
EllipticalConeLimit 1 rotational (when limit active) Limits swing between inner and outer elliptical cones; leaves twist free
AngularMotor — Drives angular velocity (does not remove DOF)
LinearMotor — Drives linear velocity (does not remove DOF)
SpringConstraint 1 translational Spring-like force along the anchor connection

Joints

Joints are combinations of constraints for common use cases:

Joint Constraints used Remaining DOF
HingeJoint BallSocket + HingeAngle (+ optional AngularMotor or TwistServo) 1 rotational
PrismaticJoint PointOnLine + FixedAngle or HingeAngle (+ optional LinearMotor or LinearAxisServo) 1 translational; also 1 rotational when pinned is false
UniversalJoint BallSocket + TwistAngle (+ optional AngularMotor or AngularServo) 2 rotational
WeldJoint BallSocket + FixedAngle 0 (fully locked)

Joint drives

HingeJoint, PrismaticJoint, and UniversalJoint can create an optional motor or servo. Use JointDrive.Motor to control speed or JointDrive.Servo to reach a target position or angle. Omit the drive, or use JointDrive.None, for an undriven joint.

var elbow = new HingeJoint(
    world, upperArm, lowerArm, center, axis,
    angle: AngularLimit.FromDegree(0, 135),
    drive: JointDrive.Servo);

elbow.Servo!.TargetAngle = JAngle.FromDegree(90);
elbow.Servo.MaximumTorque = 20;

A hinge's Servo is a TwistServo. Its target uses the same zero angle and positive rotation direction as the hinge's HingeAngle.Angle.

A prismatic joint's Servo is a LinearAxisServo. Its target position uses the same zero and direction as the joint's Slider.Distance:

var slider = new PrismaticJoint(
    world, body1, body2, center, axis,
    limit: new LinearLimit(0, 2),
    drive: JointDrive.Servo);

slider.Servo!.TargetPosition = 1.5f;
slider.Servo.MaximumForce = 100;

A universal joint's Servo is an AngularServo. Its target is the orientation of body 2 relative to body 1. Initially, it holds their current relative orientation:

var universal = new UniversalJoint(
    world, body1, body2, center, axis1, axis2,
    drive: JointDrive.Servo);

universal.Servo!.TargetRelativeOrientation = targetRelativeOrientation;
universal.Servo.MaximumTorque = 20;

The twist constraint remains active, so only orientations allowed by the joint can be reached.

Drives start with a maximum force or torque of zero. Set this limit to make the drive act. The drive does not replace the joint's mechanical limits. Enabling, disabling, removing, or drawing the joint also applies to its drive.

The Motor property is non-null only with JointDrive.Motor; Servo is non-null only with JointDrive.Servo.

The older hasMotor constructor overloads are obsolete and remain supported. Replace hasMotor: true with drive: JointDrive.Motor, or omit the drive when no motor is needed.

Creating constraints

Create constraints through the world:

var constraint = world.CreateConstraint<ConstraintType>(body1, body2);

Jitter2 allocates and assigns unmanaged memory to constraints internally, allowing them to be used like regular classes. The solver interacts with unmanaged memory structures of type ConstraintData (256 bytes) or SmallConstraintData (128 bytes), depending on whether constraint.IsSmallConstraint is set. Small constraints are designed for simpler constraints with a smaller memory footprint, such as spring constraints for soft bodies.

Caution

Constraint Initialization For all default constraints, constraint.Initialize must be called once after world.CreateConstraint.

World space initialization

Supply anchor points and axes in world coordinates. Most constraints use the bodies' current positions and orientations as their reference, so arrange the bodies as intended before calling Initialize. The anchors and axes then move with their respective bodies. Servos can instead be initialized with a target they have not yet reached.

Example: Hinge joint

For a swinging door, BallSocket keeps the hinge anchors together and HingeAngle allows rotation around the hinge axis:

var hingeAngle = world.CreateConstraint<HingeAngle>(body1, body2);
hingeAngle.Initialize(hingeAxis, AngularLimit.Full);

var ballSocket = world.CreateConstraint<BallSocket>(body1, body2);
ballSocket.Initialize(hingeCenter);

Alternatively, the HingeJoint can be used for convenience:

var hinge = new HingeJoint(world, body1, body2, hingeCenter, hingeAxis);

Cone limits

ConeLimit restricts the angle between two axes to a range from 0° to 180°. Ranges such as [0°, 45°], [135°, 180°], and [0°, 180°] are valid. Equal limits fix the angle when it lies strictly between 0° and 180°, for example [30°, 30°].

The fixed ranges [0°, 0°] and [180°, 180°] are rejected by Initialize and the Limit property. To keep axes parallel or antiparallel while allowing free twist, use HingeAngle with AngularLimit.Full:

var hingeAngle = world.CreateConstraint<HingeAngle>(body1, body2);
hingeAngle.Initialize(hingeAxis, AngularLimit.Full);

Position the bodies with the intended axes aligned or antiparallel before initialization. HingeAngle stores their current relative orientation as its reference; it does not pull two arbitrarily misaligned axes into alignment during initialization.

Elliptical cone limits

EllipticalConeLimit allows different tilt limits in two perpendicular directions. The cone is attached to body 1 and limits an axis attached to body 2. Supply the directions in world space; they will rotate with their respective bodies.

var cone = world.CreateConstraint<EllipticalConeLimit>(body1, body2);
cone.Initialize(
    axis: JVector.UnitY,
    referenceDirection: JVector.UnitX,
    limitAlongReference: AngularLimit.FromDegree(15, 60),
    limitAlongPerpendicular: AngularLimit.FromDegree(5, 25));

The lower bounds define the inner ellipse (15° and 5°); the upper bounds define the outer ellipse (60° and 25°). Swing is allowed between these boundaries. Setting both lower bounds to zero removes the inner boundary. Setting each lower bound equal to its upper bound constrains swing to one elliptical cone surface. Identical ranges in both directions give a circular cone.

The limits form an ellipse, rather than two independent angle limits. Tilting partway toward one limit leaves less room in the perpendicular direction. GetSwing returns these two tilt components, measured along the reference direction and axis cross referenceDirection. Each boundary satisfies (swing1 / angle1)^2 + (swing2 / angle2)^2 = 1.

The reference direction is projected onto the plane perpendicular to the cone axis. A direction parallel to the axis is rejected.

Each range must satisfy 0 <= From <= To < 180°, with a positive upper bound. The lower bounds must either both be zero or both be positive. Fixed zero and 180° tilt are unsupported. Change the ranges together using SetLimits; inspect the current coordinates using GetSwing. The two-axis Initialize overload supports an initially tilted body 2. The single-axis overload starts at zero tilt, which violates a positive inner boundary and will be corrected by the solver.

Translation and twist of body 2 about its constrained axis remain free. Combine this constraint with BallSocket for a joint, and optionally TwistAngle to limit twist.

Servos

Servos drive bodies toward a target configuration. They can start outside that configuration, and their targets can change between simulation steps. Changing a target wakes sleeping connected dynamic bodies on the next step. Motors control velocity; servos control position or orientation.

Servo Controls Target
AngularServo 3 angular directions TargetRelativeOrientation: body 2's orientation in body 1's frame
TwistServo 1 angular direction TargetAngle: twist relative to the initial zero-twist pose
LinearServo 3 linear directions TargetPosition: offset from anchor 1 to anchor 2, in body 1's frame
LinearAxisServo 1 linear direction TargetPosition: signed anchor separation along the axis attached to body 1

Maximum force or torque defaults to zero, as with motors. Set MaximumForce or MaximumTorque to enable the drive. Positive infinity allows unlimited force or torque. Bias controls how quickly the servo corrects its error. Higher Softness lets it yield more under load.

For example, drive two robot arm segments toward a relative orientation:

var servo = world.CreateConstraint<AngularServo>(upperArm, lowerArm);
servo.Initialize(JQuaternion.CreateRotationX(0.5f));
servo.MaximumTorque = 20;

// Choose a different configuration later without moving the bodies directly.
servo.TargetRelativeOrientation = JQuaternion.CreateRotationX(1.0f);

The parameterless AngularServo.Initialize() captures the current relative orientation instead. LinearServo.Initialize(anchor1, anchor2) accepts separate world-space attachment points; its default target of zero pulls them together. LinearAxisServo.Initialize also accepts the desired signed separation. TwistServo.Initialize accepts a target angle relative to its initial reference, so a nonzero target begins with an angular error.

TwistServo uses signed joint angles from -180° to +180°. Moving from -170° to +170° travels 340° through zero. Targets outside this interval are rejected; revolutions are not tracked. Use mechanical limits to keep the joint's motion inside the angular interval. AngularServo targets an orientation and uses the shortest rotation to reach it, so equivalent orientations represent the same target.

Servos act alongside mechanical constraints. For an elbow, use a hinge to keep the arm segments connected and enforce the joint's angle limits, then add a TwistServo around the same axis. Its torque limit controls the actuator's strength while the hinge retains its mechanical limits.

Fixed constraints

To constrain a rigid body relative to world space, use world.NullBody as one of the bodies. For example, to keep a capsule upright:

var upright = world.CreateConstraint<HingeAngle>(capsule, world.NullBody);
upright.Initialize(JVector.UnitY, AngularLimit.Full);

Softness and Bias

Bias controls how quickly a constraint corrects position or angle errors. Higher Softness lets the constraint yield more under load, which can improve stability at the cost of accuracy.

Substeps can also improve joint stability.

Enable/Disable constraints

Constraints can be temporarily enabled or disabled using constraint.IsEnabled.

Debug drawing

Constraints and joints can draw their anchors, axes, and limits through an IDebugDrawer:

constraint.DebugDraw(drawer);
joint.DebugDraw(drawer);

See setting up a debug drawer to connect this to your renderer. Call these methods after the physics step, while the world is not being modified by another thread.

Green shows reference guides and upper or fixed limits, red shows lower limits, and white shows current anchors and directions. Coordinate frames use red for X, green for Y, and blue for Z.

Constraint Reading the drawing
BallSocket The two anchor markers should coincide. The line between them shows separation.
DistanceLimit Spheres around the first anchor show the allowed distances to the second anchor.
PointOnLine Crosses mark stops along the line; arrows mark unbounded ends. A white line shows displacement away from the line.
PointOnPlane Grids show the limiting planes. A white projection line appears when the anchor is outside the allowed region. The planes extend beyond the grids.
ConeLimit, EllipticalConeLimit The white axis must stay within the outer cone and outside any red inner cone. Equal limits allow motion along one cone surface.
HingeAngle An arc shows the allowed angles, or a green ray marks a fixed angle. The white ray shows the current angle. Short axis arrows reveal hinge misalignment.
TwistAngle The dial shows the current twist and its limits. The white axis shows the current body direction; swing remains free.
FixedAngle Each body has a coordinate frame adjusted for the initial relative orientation. Corresponding axes stay parallel when the constraint is satisfied. No frame is drawn for World.NullBody.
LinearMotor Green arrows show body 1's contribution and white arrows show body 2's contribution to the requested relative motion. These show target direction, not measured velocity; a static body stays still.
AngularMotor A green circle and white arrow show the target rotation of body 2, or body 1 when body 2 is static or kinematic. Short lines show both motor axes. The arrow shows target direction, not measured speed; a zero target has no arrow.
AngularServo A green target frame and an RGB current frame share an origin. Corresponding axes align when the target is reached.
TwistServo The dial marks the target twist and the current twist angle.
LinearServo Green and white markers show the target and current anchor positions; the connecting line shows the position error.
LinearAxisServo Markers show target and current position projected onto the reference axis. Perpendicular motion is uncontrolled.
SpringConstraint The coil joins the anchors; the green cross marks the rest length along the spring direction.

Pass DebugDrawSettings to change the appearance:

constraint.DebugDraw(drawer, new DebugDrawSettings
{
    GuideSize = 2,
    FrameSize = 1.5f
});

GuideSize changes the size of guides. FrameSize sets the length of coordinate-frame axes in world units, and PointRadius sets the radius of anchor spheres. Arrowhead size and shaft thickness are adjusted separately using ArrowHeadSize and ArrowShaftRadius. Physical distances and angular limits keep their actual values. See DebugDrawSettings for color, arrow, and detail settings.

Arrows are solid by default. Set SolidArrows = false for wire arrows. ArrowSegments controls how round arrowheads and shafts look. Your drawer's DrawTriangle implementation controls whether solid arrows and anchor markers appear filled or as triangle edges. When drawing your own guides, DrawArrow and DrawFrame use the same defaults; DrawWireArrow and DrawWireFrame explicitly draw wire geometry.

Constraint lifetime

Removing a constraint makes its simulation data unavailable. This also happens automatically when:

  • either connected body is removed;
  • world.Clear() removes the connected bodies; or
  • the world is disposed.

Changing a body's motion type does not remove the constraint. It is suspended while neither connected body is dynamic.

Check constraint.IsValid before using a reference that may have been removed:

world.Remove(body1); // also removes constraints attached to body1

if (constraint.IsValid)
{
    constraint.IsEnabled = false;
}

If IsValid is false, do not use properties or methods that access the constraint's simulation data. Rigid bodies have the same body.IsValid check after removal or world disposal.

Use World.ConstraintRemoved to clean up application data when a constraint is removed:

world.ConstraintRemoved += removedConstraint =>
{
    // Drop any application-side state associated with removedConstraint.
};

The constraint is already invalid when the callback runs. Disabling a constraint and disposing the world do not raise this event.