Affine Body Revolute Joint
References:
A unified newton barrier method for multibody dynamics
#18 AffineBodyRevoluteJoint
The Affine Body Revolute Joint constitution constrains two affine bodies to rotate relative to each other about a shared axis. This joint allows for rotational motion while restricting translational movement between the two bodies.
We assume 2 affine body indices \(i\) and \(j\), each with their own state vector (to be concerete, transform) \(\mathbf{q}_i\) and \(\mathbf{q}_j\) as defined in the Affine Body constitution.
The joint axis in world space is defined by 2 points \(\mathbf{x}^0\) and \(\mathbf{x}^1\). At the beginning of the simulation, the relationships are kept:
and,
intuitively \(\mathbf{J}^0\) and \(\mathbf{J}^1\) tell the local coordinates of the two points on affine bodies \(i\) and \(j\).
The energy function for the Affine Body Revolute Joint is defined as:
where \(K\) is the stiffness constant of the joint, we choose \(K=\gamma (m_i + m_j)\), where \(\gamma\) is a user defined strength_ratio parameter, and \(m_i\) and \(m_j\) are the masses of the two affine bodies.
Angle State
The revolute joint tracks a scalar joint angle \(\theta\) that extra-per-joint constitutions (driving joint, angle limit, external torque) share. The backend builds, for each body \(k \in \{i,j\}\), an orthonormal basis \((\hat{\mathbf{t}}_k, \hat{\mathbf{n}}_k, \hat{\mathbf{b}}_k)\) aligned with the joint axis at setup, where
and the \((\hat{\mathbf{n}}, \hat{\mathbf{b}})\) pair is stored per body in the layout \([\hat{\mathbf{n}}_k, \hat{\mathbf{b}}_k]\). The current relative angle between the two bodies is extracted as
The sign follows the right-hand rule around \(+\hat{\mathbf{t}}\) (counterclockwise when viewed along \(+\hat{\mathbf{t}}\)).
State Update
At the end of each time step, the backend writes back the current relative angle, wrapped to \((-\pi, \pi]\), to the angle edge attribute:
where \(\theta(\mathbf{q})\) is extracted from the formulas above and \(\alpha_0\) is init_angle, a user-facing offset that shifts the "zero" of the reported value (default 0.0). The angle attribute is therefore available to any frontend and to all extra-per-joint constitutions built on top of this joint (driving joint, limit, external torque). When an external state write replaces the body DOF \(\mathbf{q}\) (e.g. via AffineBodyStateAccessorFeature), the backend re-syncs current_angles synchronously through the GlobalJointDofManager.
Convention for all user-facing bounds
All user-facing angle quantities on this joint (limit/lower, limit/upper on AffineBodyRevoluteJointLimit; aim_angle on AffineBodyDrivingRevoluteJoint) are expressed in the same "current_* frame" as the reported angle attribute. A user can therefore set them directly against the value they read back from angle, without having to reason about init_angle. Wherever init_angle appears inside a solver-side formula, it is subtracted from the user-facing quantity to translate it into the raw frame \(\theta(\mathbf{q})\).
Attributes
On the joint geometry (1D simplicial complex), on edges (one edge per joint):
l_geo_id(IndexT): scene geometry slot id for the left body \(i\)r_geo_id(IndexT): scene geometry slot id for the right body \(j\)l_inst_id(IndexT): instance index within the left geometryr_inst_id(IndexT): instance index within the right geometrystrength_ratio: \(\gamma\) in \(K = \gamma(m_i + m_j)\) aboveangle(Float): \(\theta_{\text{current}}\), current relative joint angle in radians, wrapped to \((-\pi, \pi]\). Written by the backend every time step (read-only for the user).init_angle(Float): \(\alpha_0\), initial angle offset added to the extracted relative angle. Default0.0.
When the joint is created via Local create_geometry, optional edge attributes (each Vector3) supply local joint geometry: l_position0, l_position1 (left body local frame), r_position0, r_position1 (right body local frame).