Venus Lua API › Physics
FixedJoint
Constraint component on a FixedRigidBody — hinges, sliders, welds, limits, motors.
lua/venus_lua/src/lua_fixed_joint.rs
FixedJoint
A FixedJoint is a scenegraph component that constrains the FixedRigidBody
on its own GameObject — either to a static world anchor (the default) or to another
GameObject’s FixedRigidBody (SetConnected). The constraint is solved in the
deterministic FixedXpbdWorld.
The GameObject must also carry a FixedRigidBody; the joint has nothing to constrain without one.
Static calls use the type table (FixedJoint.Add(...)); instance methods use colon syntax on the
handle (joint:SetRevolute(...)).
Types
FixedJoint — an opaque handle (userdata) to a FixedJoint component, type-id tagged and
generation-checked. The internal FixedJointHandle never crosses the Lua boundary.
The two failure modes differ:
- Calling an instance method on a value that is not a FixedJoint userdata — including the
nil
a failedAdd/Getreturns — raises a Lua type error. Guard constructor results before use. - A stale but correctly-typed handle is tolerated, and degrades per method: the configuration
setters and the two lifecycle verbs are silent no-ops;IsRegistered,Activeand
ActiveInHierarchyreturnniland log aNotFoundwarning.
FixedJoint.Exists( joint ) is the safe test — it accepts any value, including nil, and returns
false rather than raising.
tostring(joint) always yields the constant string FixedJoint — it carries no object name and does
not change when the handle goes stale.
Creation & Destruction
FixedJoint.Add( GameObject object ) : FixedJoint or nil
Adds a joint component to object. The default configuration is a ball joint to a world anchor
at the body’s local origin, compliance 0. Reconfigure with the setters below before the first
fixed step. Raises a Lua type error if object is not a GameObject; returns nil and logs
InvalidArgument if the component cannot be added.
FixedJoint.Get( GameObject object ) : FixedJoint or nil
The joint already on object. Returns nil if there is none (no warning).
FixedJoint.Exists( FixedJoint joint ) : boolean
true only if the handle resolves to live component data. Never raises.
There is no joint:Destroy() — a joint is removed by destroying the GameObject or removing the
component, either of which unregisters it from the world.
Registration is deferred — read this before the setters
FixedJoint.Add does not create a world constraint. Creation is deferred to the component’s
fixed_update, deliberately, so that the enable order of the body and the joint cannot matter. The
joint goes live on the first fixed step after its body is registered.
Two consequences that govern every setter on this page:
- The authoring setters are pure configuration writes. They take effect at the next
(re)registration — they do not push to an already-live joint. Configure the joint in the same
callback that creates it, before any fixed step has run. - To change a live joint, cycle it.
Deactivate()unregisters it from the world immediately;
the next fixed step afterActivate()rebuilds it from the current configuration. See
Reconfiguring a live joint.
joint:IsRegistered() reports whether the constraint is live in the world right now.
Cascade death is self-healing. If either attached body is destroyed, the world’s destroy cascade
takes the joint with it; the component notices on its next fixed step, resets, and will rebuild the
joint if a body is registered again later.
Functions
joint:IsRegistered( ) : boolean or nil
Whether the constraint is currently live in the physics world. false before the first fixed step,
after Deactivate(), or after a cascade death. nil (+ logged NotFound) on a stale handle.
Methods
All setters below are configuration writes that apply at the next (re)registration. Every vector
argument must be a native Vector3 — three loose numbers raise a type error. All silently no-op on
a stale handle.
Kind
Mutually exclusive; the last call wins. Axes are in this body’s local frame and unit length is
expected.
joint:SetBall( ) : void
Point-to-point: the anchor is pinned, rotation about it is free. This is the default.
joint:SetFixed( ) : void
Weld — pins the anchor and locks the relative orientation to the construction pose.
joint:SetRevolute( Vector3 axis ) : void
Hinge about axis, leaving free spin about it.
joint:SetPrismatic( Vector3 axis ) : void
Slider along axis; perpendicular translation and relative rotation are locked.
Geometry & softness
joint:SetAnchor( Vector3 anchor ) : void
The pinned point, in this body’s local frame.
joint:SetCompliance( number compliance ) : void
Constraint softness — 0 (default) is rigid, larger yields. Compliance is 1 / stiffness.
Connection
joint:SetConnected( GameObject other ) : void
Constrain to other’s FixedRigidBody instead of the world — two dynamic bodies (the equivalent of
Unity’s connectedBody). The pin point (this body’s anchor, in world space) is automatically
expressed in the other body’s frame at creation. Raises a Lua type error if other is not a
GameObject.
joint:ClearConnected( ) : void
Revert to a static world anchor.
Limits & motors
Revolute and Prismatic only. On a Ball or Fixed joint these values are stored but never applied
— there is no error and no warning, so check the kind if a limit appears to do nothing.
Units follow the kind: radians / rad·s⁻¹ for Revolute, metres / m·s⁻¹ for Prismatic.
joint:SetLimit( number lo, number hi [, number compliance] ) : void
Clamps the free DOF to [lo, hi]. compliance defaults to 0 — a rigid stop; larger values give a
springy one.
joint:SetMotor( number targetVel, number maxForce ) : void
Velocity motor: drives the DOF rate toward targetVel, with the per-substep drive impulse capped at
±maxForce·h (a torque limit for Revolute, a force limit for Prismatic).
joint:SetPositionMotor( number target [, number compliance] ) : void
Position servo: drives the DOF toward target as a compliant constraint. compliance defaults to
0 (rigid). An alternative to the velocity motor — the last motor call wins. For a hinge, meaningful
for targets within ±π.
Lifecycle
On this component the lifecycle verbs are not merely bookkeeping — they are the supported way to
rebuild a live joint (see below).
joint:Active( ) : boolean or nil
The component’s own enabled flag — what Activate()/Deactivate() write. nil (+ a logged
NotFound) on a stale handle. This is intent, not effect: a joint whose own flag is enabled but
whose GameObject is deactivated still reports true, and is not registered.
joint:ActiveInHierarchy( ) : boolean or nil
true only when the own flag is enabled and the owning GameObject is active in hierarchy — i.e.
the component is actually running its fixed step. This is the predicate the engine uses to gate
update dispatch. nil on a stale handle.
joint:Activate( ) : void
Sets the own enabled flag. The joint re-registers on the next fixed step, using the configuration as
it stands at that moment.
joint:Deactivate( ) : void
Clears the own enabled flag and unregisters the joint from the world immediately (the disable
callback runs if the component was active).
Gaps
- Ball and Fixed joints have no limit or motor support.
- There is no query API for a joint’s live angle, slide distance, or reaction force. Track anything
you need script-side.
Example
-- A hinge between two bodies, limited and motored. Both bodies register during
-- add_component (on_enable is synchronous), and the joint goes live on the first
-- fixed step after that.
function Script.OnCreate(self)
FixedRigidBody.AddBox(self.object, 0.5, 0.5, 0.5, 1.0)
FixedRigidBody.AddBox(self.lidObject, 0.5, 0.5, 0.5, 1.0)
local hinge = FixedJoint.Add(self.object)
if not hinge then return end -- Add returns nil on failure; guard it
hinge:SetConnected(self.lidObject)
hinge:SetRevolute(Vector3.new(1, 0, 0)) -- hinge axis, this body's local frame
hinge:SetAnchor(Vector3.new(0, 0.5, 0)) -- pin point, this body's local frame
hinge:SetLimit(0.0, 1.57) -- 0..~90 degrees, rigid stops
hinge:SetMotor(2.0, 50.0) -- 2 rad/s, torque cap 50
self.hinge = hinge
end
Reconfiguring a live joint
Setters do not affect an already-registered joint. Cycle the component to rebuild it:
-- Turn the hinge into a slider at runtime.
self.hinge:Deactivate() -- unregisters from the world now
self.hinge:SetPrismatic(Vector3.new(0, 1, 0)) -- reconfigure while it is down
self.hinge:SetLimit(0.0, 2.0) -- metres, for a prismatic joint
self.hinge:Activate() -- rebuilds on the next fixed step
-- IsRegistered() is false until that step runs.
A world-anchored joint is the same pattern with no SetConnected — FixedJoint.Add(go) plus
SetAnchor is a complete, working joint.