Data model and semantics#
This page explains the important notions of the read and write API and provides all the information required to interpret the values a read or write exchanges.
Groups and shape#
A read or write returns its data as groups, one per attribute. A group is a struct holding four things:
column: the values (
tensors).prims: the objects associated with the API call (
prim_list), row i ↔ prim i.attribute: the corresponding
attribute.role: the USD geometric
semantic(see below).
The column has one row per object, in one of two shapes:
Fixed group — rectangular:
Nobjects giveNrows, each row the value (a position is 3 numbers, an orientation 4).Array group — each object’s value is a list, and the lengths differ (e.g. a joint’s number of DOFs), so it can’t be rectangular: you get one array per object.
The shape is indicated by is_array.
The role is used mainly by ovstage to round-trip the data as the right type on a write. The read/write path uses five roles:
Role |
Width |
Meaning |
Attributes |
|---|---|---|---|
|
vec3 (3 × f32) |
a position |
|
|
quat (4 × f32, xyzw) |
a rotation |
|
|
vec3 (3 × f32) |
a free vector (no origin) |
all linear/angular velocities and accelerations |
|
9 × f32 (3×3, column-major) |
a matrix |
|
|
scalar / flag / count / any width |
no USD role |
masses, per-shape and per-axis scalars, flags, counts, inverse dynamics matrices, indices |
Two things to watch for:
The frame is not part of the role. A
POINTmay be world (position), body-local (centerOfMassPosition), or mesh-local (deformablepoints). Read the per-attribute tables for the frame.A pose is two columns, never one. There is no fused transform role: a body pose is a
POINTpositionplus aQUATERNIONorientation; a root pose isrootPosition+rootOrientation. Writing one half pre-reads the other to preserve it.
Articulation inverse dynamics cohorts#
The whole-articulation inverse dynamics columns (jacobian,
jacobianShape, massMatrix, coriolisForce, gravityForce, centroidalMomentum) are a special case
as their size can be quite large. In order to be memory efficient, the notion of cohort has
been introduced. A cohort is a set of structurally identical articulations, that is, same joint types and names, DOF
parentage, and link leaf names.
For a homogeneous scene, only one cohort is present, while for a heterogeneous scene, the results are cohort-partitioned with one group per cohort. The cohort can be identified by its prim list; the group index is not promised stable across reads. Every other column has one fixed shape for the whole read, so it needs no cohorts.
Layout. Matrices are row-major flattened. massMatrix is square (M = numDofs + (fixedBase ? 0 : 6)), so M is the square root of the lane count; coriolisForce and gravityForce are length M;
centroidalMomentum has six rows, so its column count is lanes / 6, and its last column is the
bias rather than a coordinate. jacobian’s two dimensions both vary, so they are published in
jacobianShape as (rows, cols).
Sign convention. Generalized coordinates follow each USD joint’s authored direction: positive when
body0 is the articulation parent, negative when body1 is. With S_dof the diagonal matrix of
those signs and T = S_dof (fixed base) or T = diag(I₆, S_dof) (floating base), the values returned
are J = J_physx·T, M = T·M_physx·T, c = T·c_physx, g = T·g_physx, and [A|b] = [A_physx·T | b_physx] — so centroidal’s six root columns and bias column are unchanged.
Units#
ovphysx follows one rule at the boundary: the parse/population layer converts to engine-native
units at the boundary, and consumers never re-convert. Positions are in the scene’s length unit,
which is the USD stage unit — see Stage units for metersPerUnit /
kilogramsPerUnit.
Within the session API the angular convention is not uniform — it depends on the quantity:
Quantity |
Unit |
Notes |
|---|---|---|
positions, poses ( |
scene length units |
world unless the table says local |
rigid/link |
length / s |
engine-native |
rigid/link |
radians / s |
engine-native — no degree fold |
rigid/link |
length/s² · rad/s² |
read-only |
joint DOF |
degrees (rotational axes) |
as USD-authored; linear axes in length |
joint DOF efforts ( |
force / torque |
no angular fold |
whole-articulation inverse dynamics ( |
radians for angular generalized coordinates |
no degree fold; linear dimensions keep their units |
vehicle |
radians |
write-only control |
|
mass, mass·length² |
Note. The asymmetry is deliberate to call out: a rigid body’s
angularVelocityis rad/s, but an articulation joint’sjointVelocityis in degrees/s (USD-authored). Read/write of the same quantity share one fold, so a read and a write on the same object never disagree — but two different object types do not share a convention.
Availability and fallback#
Step-first precondition (DirectGPU)#
On a DirectGPU scene (eENABLE_DIRECT_GPU_API) PhysX sizes its GPU structures during the first
step and rejects direct-GPU access until it has run. Therefore, before the first step:
a read emits no groups for device-sourced types (it reports end-of-iteration immediately — not an error, and currently indistinguishable from a genuinely empty match), and
a write is refused (it is not auto-warmed).
You control the first step with ovphysx_step / ovphysx_step_sync / ovphysx_warmup. A CPU
scene does report authored initial state before the first step. Readiness is per-scene: a
multi-scene read returns ready partitions while omitting an unready DirectGPU scene.
Missing and partial data#
Reads are dense — an unavailable quantity is omitted, never NaN- or zero-filled to a full row:
Unknown or inapplicable attribute for the type → no group is emitted for it; the drain ends with end-of-iteration (not an error). Name dispatch is exhaustive — a name never falls through to a neighbouring attribute.
Attribute inapplicable to a particular object (e.g.
masson an instanced body, a surface-only material field on a volume material,collisionElementIndiceson a surface) → that object contributes no row/column; a group’s prim set can be a subset of discovery. Always pair values to prims via the group’s ownprims.list.Disabled rigid body → on a DirectGPU scene it has no device state, so it is dropped from the group (fewer rows than discovery reports); on a CPU scene it stays in. A disabled point-instancer instance leaves its zero-filled slot.
Per-shape padding past
shapeCountis deterministic zero-fill, but0.0is a legal value — useshapeCount(not the zeros) as the terminator.There is no per-object “pending vs current” fallback in the read (that concept belongs to the write/kinematic path). Derived read-only columns (
jointProjectedForce,linkIncomingJointForce) are recomputed each read, never stale.
A short set is distinguishable from a complete one by the terminal status: a failed backend build/gather ends with an error, a complete drain ends with end-of-iteration.