Skip to content

joints

FreeBodyEngine.core.physics.joints #

Physics constraints (joints) between RigidBody2D bodies.

Every joint here is solved the same way contacts are (see world.py): once per physics step, init_velocity_constraint() precomputes anchors and effective mass from the bodies' current position/rotation, then solve_velocity_constraint() runs once per solver iteration, applying a corrective impulse that (over several iterations, interleaved with every other joint and contact in the scene) converges the whole system toward satisfying every constraint at once. This is what makes chained, motor-driven limbs - like a procedurally-animated spider leg - possible: each leg segment is its own RigidBody2D, connected to its neighbor by a RevoluteJoint2D with a motor, and the solver reconciles all of them together rather than one at a time.

A joint's two bodies must already be in the scene (added via scene.add()) before the joint itself is constructed - anchors are converted from world space to each body's local space immediately, which needs a valid world_position/world_rotation to convert from.

MAX_JOINT_BIAS_SPEED = 4.0 module-attribute #

DistanceJoint2D(body_a, body_b, anchor_a=None, anchor_b=None, length=None, collide_connected=False) #

Bases: Joint2D

A rigid rod between a fixed point on each body - holds the distance between anchor_a/anchor_b fixed at length via a stiff Baumgarte-corrected constraint. For a springy, non-rigid version, see SpringJoint2D.

anchor_a/anchor_b are LOCAL offsets from each body's center (defaulting to (0, 0), the body's own center of mass) - stored local rather than as world points, since the joint has to track them as the bodies rotate. length defaults to the anchors' actual distance apart at creation time.

anchor_a = anchor_a if anchor_a is not None else Vector(0, 0) instance-attribute #

anchor_b = anchor_b if anchor_b is not None else Vector(0, 0) instance-attribute #

beta = 0.2 instance-attribute #

length = length instance-attribute #

init_velocity_constraint(dt) #

solve_velocity_constraint() #

Joint2D(body_a, body_b, collide_connected=False) #

Bases: Node

Base class for a physics constraint between two bodies. Not a Node2D - a joint has no position/rotation of its own, so it's added directly under the scene root (or anywhere in the tree, really - like everything else here it's discovered by find_nodes_with_type, not by transform hierarchy).

collide_connected controls whether body_a/body_b still generate contact-solver collisions with each other despite being jointed - off by default (matching every other engine's convention), since two directly-jointed bodies (e.g. a leg segment's two ends) are expected to overlap/touch by construction, and fighting that with the contact solver too would just make the joint fight itself. body_b is None only for joints that connect a single body to a fixed external point rather than to another body (see TargetJoint2D).

body_a = body_a instance-attribute #

body_b = body_b instance-attribute #

collide_connected = collide_connected instance-attribute #

init_velocity_constraint(dt) #

Called once per physics step, before any solver iterations - override to precompute anchors/effective mass (anything that only changes once per step, not per iteration).

solve_velocity_constraint() #

Called once per solver iteration (several times per physics step) - override to apply this joint's corrective impulse(s).

RevoluteJoint2D(body_a, body_b, anchor, collide_connected=False, enable_motor=False, motor_speed=0.0, max_motor_torque=0.0, enable_limit=False, lower_angle=0.0, upper_angle=0.0) #

Bases: Joint2D

A hinge: locks a shared world-space anchor point between the two bodies (they can rotate freely around it, but not translate apart), with an optional motor (drives their RELATIVE angular speed toward motor_speed, clamped to max_motor_torque) and/or angle limits (bounds their relative rotation to [lower_angle, upper_angle] degrees, measured from whatever their relative angle happened to be when this joint was created) - exactly what a motor-driven, limited-range limb joint (an elbow/knee/shoulder) needs.

anchor is a WORLD-space point - both bodies must already be in the scene so their current world transform is valid, since it's immediately converted to a local offset on each body (so the joint tracks the same material point on each as they move, rather than staying fixed in world space).

anchor_a = (anchor - body_a.world_position).rotated(-body_a.world_rotation) instance-attribute #

anchor_b = (anchor - body_b.world_position).rotated(-body_b.world_rotation) instance-attribute #

beta = 0.2 instance-attribute #

enable_limit = enable_limit instance-attribute #

enable_motor = enable_motor instance-attribute #

lower_angle = lower_angle instance-attribute #

max_motor_torque = max_motor_torque instance-attribute #

motor_speed = motor_speed instance-attribute #

reference_angle = body_b.world_rotation - body_a.world_rotation instance-attribute #

upper_angle = upper_angle instance-attribute #

init_velocity_constraint(dt) #

solve_velocity_constraint() #

SpringJoint2D(body_a, body_b, anchor_a=None, anchor_b=None, length=None, frequency=4.0, damping_ratio=0.5, collide_connected=False) #

Bases: Joint2D

A soft distance joint - stretches/compresses springily around length instead of holding it rigidly like DistanceJoint2D, via a damped-spring constraint (frequency in Hz, damping_ratio from 0 = undamped/bouncy to 1 = critically damped/no overshoot). The standard "soft constraints" derivation (as used by Box2D's own soft distance joint) folding the spring's frequency/damping into the impulse solve itself, rather than applying a separate explicit spring force.

anchor_a = anchor_a if anchor_a is not None else Vector(0, 0) instance-attribute #

anchor_b = anchor_b if anchor_b is not None else Vector(0, 0) instance-attribute #

damping_ratio = damping_ratio instance-attribute #

frequency = frequency instance-attribute #

length = length instance-attribute #

init_velocity_constraint(dt) #

solve_velocity_constraint() #

TargetJoint2D(body, target, local_anchor=None, max_force=1000.0, frequency=5.0, damping_ratio=0.7) #

Bases: Joint2D

Pulls a single point on body toward a movable world-space target, softly (the same frequency/damping soft-constraint math as SpringJoint2D) and clamped to max_force - for mouse-drag interactions, a grapple hook's pull, or as the drive behind procedural IK (aim a leg's foot at a target step position and let the spring pull the limb chain toward it through its other joints, instead of hand-solving the chain's inverse kinematics directly).

Only affects one body - the target point itself has no mass of its own, exactly like grabbing a body with the mouse.

damping_ratio = damping_ratio instance-attribute #

frequency = frequency instance-attribute #

local_anchor = local_anchor if local_anchor is not None else Vector(0, 0) instance-attribute #

max_force = max_force instance-attribute #

target = target.copy() instance-attribute #

init_velocity_constraint(dt) #

solve_velocity_constraint() #

WeldJoint2D(body_a, body_b, anchor, collide_connected=False) #

Bases: Joint2D

Rigidly fuses two bodies together at a shared world-space anchor point AND their current relative angle - like RevoluteJoint2D with its rotational freedom locked too, so the pair behaves as a single rigid body while still being two separate (and later detachable) ones. Useful for permanently gluing parts together without folding them into a single RigidBody2D/collider.

anchor_a = (anchor - body_a.world_position).rotated(-body_a.world_rotation) instance-attribute #

anchor_b = (anchor - body_b.world_position).rotated(-body_b.world_rotation) instance-attribute #

beta = 0.2 instance-attribute #

reference_angle = body_b.world_rotation - body_a.world_rotation instance-attribute #

init_velocity_constraint(dt) #

solve_velocity_constraint() #