Physics Scene and Simulation Configuration#

Every stage that ovphysx simulates needs a physics scene: a PhysicsScene prim that defines gravity and the PhysX solver configuration. This page explains how to author the scene and configure global simulation behavior (solver, iteration counts, GPU buffers, sleeping, stabilization) for scenes that ovphysx loads and steps.

This is the foundation the other Simulation Setup pages build on (Collision, Rigid Bodies, Joints, Articulations, Deformables, Particles).

How ovphysx Simulates a Scene#

ovphysx consumes pre-authored USD owned by the application through ovstage. Once a stage is attached, a step proceeds roughly as follows:

  1. On attach_ovstage(), the USD stage is parsed and PhysX objects are created for the prims that carry physics schemas.

  2. Colliders that need cooking (convex hull, convex decomposition, SDF, triangle mesh) are cooked. Results are cached on disk so recomputation is usually avoided — refer to the cooked-collider cache (UJITSO) in the Developer Guide.

  3. step(dt) advances the simulation by dt. step() is asynchronous: it enqueues the step and returns an op_index immediately. Subsequent in-stream ovphysx calls (such as tensor read() / write()) automatically wait for it, but to consume results outside the ovphysx stream you must synchronize — wait_op() on the returned index (or wait_all()). step_sync(dt) is a convenience that steps and waits in one call (the typical choice for RL and control loops). See the Execution Model.

  4. Results are read back explicitly by the application — either with tensor bindings or with the ovstage output read API. Unlike Omni PhysX in Kit, ovphysx does not write simulation output back to the attached USD stage; the application owns writing state back to ovstage.

The rest of this page is about authoring the PhysicsScene prim and the attributes that configure this pipeline.

Setting Up a USD Stage and a Physics Scene#

To simulate physics, a stage must have at least one PhysicsScene prim. The scene is authored with the core UsdPhysics schema (part of stock usd-core). PhysX-specific solver settings live on the codeless PhysxSceneAPI schema (see Physics Schemas).

.usda#

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    kilogramsPerMass = 1
)

def Xform "World"
{
    def PhysicsScene "physicsScene"
    {
        vector3f physics:gravityDirection = (0, 0, -1)
        float physics:gravityMagnitude = 9.81
    }
}

Python#

import ovphysx
from pxr import Usd, UsdGeom, UsdPhysics, Gf

stage = Usd.Stage.CreateNew("scene.usda")

# Units and up axis (see below).
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
UsdGeom.SetStageMetersPerUnit(stage, 1.0)
UsdPhysics.SetStageKilogramsPerUnit(stage, 1.0)

world = UsdGeom.Xform.Define(stage, "/World")

scene = UsdPhysics.Scene.Define(stage, "/World/physicsScene")
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(9.81)

For a complete, CI-tested minimal scene (physics scene + static ground + dynamic bodies), refer to the simple_physics_scene.usda reference used by Hello World.

Units and Up Axis#

USD supports scaling of units through layer metadata. For correct simulation results, the units and up axis of your stage must match your content.

  • metersPerUnit sets the length unit (1 = meters). Keep all sizes, positions, and velocities in that unit.

  • upAxis is "Y" or "Z". Point physics:gravityDirection down the same axis (up axis Z -> gravity (0, 0, -1)).

  • kilogramsPerUnit (set through UsdPhysics.SetStageKilogramsPerUnit) sets the mass unit.

  • If no gravity is authored, ovphysx scales default and auto-computed values by the layer unit metadata. For example, at metersPerUnit = 0.01 (centimeter scale) the default gravity magnitude is 981.0 cm/s^2.

Angles are always in degrees. Every rotation-related physics parameter is in degrees, including drive stiffness (units of torque/degree for an angular drive).

Keep units consistent across all layers. Automatic reconciliation of unit scales between layers is not supported; author every layer and asset at the same scale.

Multiple Physics Scenes#

A stage may contain multiple PhysicsScene prims. PhysicsRigidBodyAPI and PhysicsCollisionAPI have a physics:simulationOwner relationship that assigns a body or collider to a specific scene. If it is not set, the first PhysicsScene found during traversal is used. Objects in separate physics scenes do not collide with each other.

def PhysicsScene "physicsScene0"
{
}

def PhysicsScene "physicsScene1"
{
}

def Cube "cube0" (
    prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
    rel physics:simulationOwner = </physicsScene0>
}

ovphysx steps all scenes together. Multiple PhysicsScene prims in one stage become separate PhysX scenes, but they all advance together on each step(); per-scene stepping is not surfaced. For isolated stages or truly independent parallel sims, use separate subprocesses (refer to the threading and single-attached-stage notes in the Developer Guide).

Physics Solver#

The solver iteratively resolves constraints (joints, contacts) to reach a physically plausible state without overlaps or disconnected joints. Two strategies are available, selected on PhysxSceneAPI:

  • TGS (Temporal Gauss-Seidel, the default): converges faster; can overshoot (objects gaining excess velocity) in complex collision scenarios. TGS benefits from physxScene:enableExternalForcesEveryIteration and needs only very few (around 1) velocity iterations.

  • PGS (Projected Gauss-Seidel): converges more slowly but is less prone to overshooting.

Iteration counts control accuracy vs. cost. Position iterations keep bodies from overlapping; velocity iterations prevent bodies from picking up artificial velocity on interaction. Both are clamped per-scene with min/max attributes.

.usda#

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    vector3f physics:gravityDirection = (0, 0, -1)
    float physics:gravityMagnitude = 9.81
    uniform token physxScene:solverType = "TGS"
    uniform uint physxScene:minPositionIterationCount = 4
    uniform uint physxScene:maxPositionIterationCount = 16
    uniform uint physxScene:minVelocityIterationCount = 1
    uniform uint physxScene:maxVelocityIterationCount = 1
}

Python (codeless PhysX schema)#

PhysX-specific attributes live on codeless schemas that have no typed Python class. Register the schemas once, apply by identifier, then author the attributes generically:

import ovphysx
from pxr import Plug, Sdf

# Register the bundled codeless PhysX schemas so Physx* APIs resolve.
Plug.Registry().RegisterPlugins([str(p) for p in ovphysx.codeless_schema_paths()])

scene_prim = scene.GetPrim()
scene_prim.ApplyAPI("PhysxSceneAPI")
scene_prim.CreateAttribute("physxScene:solverType", Sdf.ValueTypeNames.Token).Set("TGS")
scene_prim.CreateAttribute("physxScene:maxPositionIterationCount", Sdf.ValueTypeNames.UInt).Set(16)

See Physics Schemas for the full codeless-schema registration story, and Performance for guidance on clamping iteration counts in bulk/RL scenes.

PhysX GPU Memory Buffers#

When simulating on the GPU, PhysX allocates internal buffers at initialization. If a buffer overflows, the runtime reports an error and you must increase its capacity on PhysxSceneAPI. The most common one to tune is the found/lost pairs capacity:

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    uint physxScene:gpuFoundLostPairsCapacity = 10240
}

CPU vs GPU Simulation#

GPU dynamics are enabled per scene in USD with physxScene:enableGPUDynamics (and a GPU broadphase through physxScene:broadphaseType = "GPU"):

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    bool physxScene:enableGPUDynamics = true
    uniform token physxScene:broadphaseType = "GPU"
}

ovphysx also exposes process- and instance-level controls that complement the per-scene USD settings:

  • ovphysx.PhysX.set_cpu_mode(True) forces a process-wide CPU-only mode (must be called before any instance is created; it cannot be reverted in that process).

  • PhysX(active_cuda_gpus="0,1") selects which CUDA GPUs an instance uses.

  • PhysXConfig(scene_multi_gpu_mode=...) distributes multiple PhysicsScene prims across GPUs.

GPU features (deformables, particles, SDF meshes, DirectGPU tensor workloads) require GPU dynamics. See the GPU/CPU and determinism sections of the Developer Guide.

Sleeping#

Sleeping lets PhysX save computation when dynamic objects are at rest. An object that stops moving for a short time goes to sleep until it is woken — by the application (for example, applying a force or changing a joint drive target) or by another object colliding with it.

Each dynamic object type exposes sleep-related properties: for rigid bodies, physxRigidBody:sleepThreshold sets the mass-normalized kinetic-energy threshold below which the body may sleep (set it to 0 to keep a body always awake).

From the runtime, rigid-body tensor bindings expose explicit wake_up() / sleep() controls.

Sleeping and effective iteration counts (GPU). In a GPU simulation it is efficient to apply the same iteration count to every awake object — the maximum over all awake objects in the scene. If a high-iteration object goes to sleep, the effective iteration count for the remaining awake objects can drop, which can change their behavior (notably for deformables). Keep this in mind when mixing objects with very different iteration settings.

Stabilization#

Stabilization improves the settling behavior of rigid bodies in large-pile or high-interaction scenes by applying extra damping to slow-moving objects. When a body’s mass-normalized kinetic energy falls below its stabilization threshold, damping is applied so it settles faster.

Stabilization reduces momentum and can cause unphysical effects, so it is not recommended for robotics, industrial applications, or any simulation that needs precise interactions.

Enable it at the scene level, then set the threshold per rigid body:

def PhysicsScene "physicsScene" (
    prepend apiSchemas = ["PhysxSceneAPI"]
)
{
    bool physxScene:enableStabilization = true
}

def Cube "box" (
    prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysxRigidBodyAPI", "PhysicsCollisionAPI"]
)
{
    float physxRigidBody:stabilizationThreshold = 0.0001
}

Reading Simulation Output#

ovphysx does not write results back to the attached stage. To observe simulated state, read it explicitly:

  • Tensor bindings — bulk, DLPack-friendly read/write of poses, velocities, joint state, and more.

  • ovstage output read — ovstage-native read of positions, orientations, velocities, and joint state.