Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ categories = ["graphics", "rendering", "rendering::data-formats"]
keywords = ["usd", "usda", "usdc", "usdz", "pxr"]

[features]
physics = []
serde = ["dep:serde", "half/serde"]

[dependencies]
Expand Down Expand Up @@ -45,3 +46,7 @@ required-features = ["serde"]
[[test]]
name = "cross_format_roundtrip"
required-features = ["serde"]

[[test]]
name = "physics_reader"
required-features = ["physics"]
136 changes: 136 additions & 0 deletions fixtures/usdPhysics_scene.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#usda 1.0
(
defaultPrim = "World"
upAxis = "Y"
metersPerUnit = 1.0
kilogramsPerUnit = 1.0
doc = "UsdPhysics reader integration test fixture: covers PhysicsScene, RigidBody+Mass+Collision, every joint kind, multi-apply LimitAPI + DriveAPI, PhysicsMaterial binding, CollisionGroup, FilteredPairsAPI, ArticulationRoot."
)

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

def Material "Rubber" (
prepend apiSchemas = ["PhysicsMaterialAPI"]
)
{
float physics:dynamicFriction = 0.8
float physics:staticFriction = 0.9
float physics:restitution = 0.6
float physics:density = 1100
}

def Cube "Base" (
prepend apiSchemas = [
"PhysicsRigidBodyAPI",
"PhysicsMassAPI",
"PhysicsCollisionAPI",
"PhysicsArticulationRootAPI",
]
)
{
double size = 1.0
float physics:mass = 2.5
point3f physics:centerOfMass = (0, 0, 0)
float3 physics:diagonalInertia = (0.1, 0.1, 0.1)
quatf physics:principalAxes = (1, 0, 0, 0)
bool physics:kinematicEnabled = 1
vector3f physics:velocity = (0, 0, 0)
}

def Cube "Arm" (
prepend apiSchemas = [
"PhysicsRigidBodyAPI",
"PhysicsCollisionAPI",
"MaterialBindingAPI",
"PhysicsFilteredPairsAPI",
]
)
{
double size = 0.5
rel material:binding:physics = </World/Rubber>
rel physics:filteredPairs = </World/Base>
double3 xformOp:translate = (1, 0.5, 0)
uniform token[] xformOpOrder = ["xformOp:translate"]
}

def PhysicsRevoluteJoint "Hinge"
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
point3f physics:localPos0 = (0.5, 0.5, 0)
quatf physics:localRot0 = (1, 0, 0, 0)
point3f physics:localPos1 = (-0.25, 0, 0)
quatf physics:localRot1 = (1, 0, 0, 0)
uniform token physics:axis = "Z"
float physics:lowerLimit = -45
float physics:upperLimit = 45
float physics:breakForce = 1000
float physics:breakTorque = 500
}

def PhysicsPrismaticJoint "Slider"
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
uniform token physics:axis = "X"
float physics:lowerLimit = -1
float physics:upperLimit = 1
}

def PhysicsSphericalJoint "Ball"
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
uniform token physics:axis = "Y"
float physics:coneAngle0Limit = 30
float physics:coneAngle1Limit = 45
}

def PhysicsDistanceJoint "Tether"
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
float physics:minDistance = 0.5
float physics:maxDistance = 2.0
}

def PhysicsFixedJoint "Lock"
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
bool physics:jointEnabled = 0
}

def PhysicsJoint "Generic" (
prepend apiSchemas = [
"PhysicsLimitAPI:transX",
"PhysicsLimitAPI:rotZ",
"PhysicsDriveAPI:rotZ",
]
)
{
rel physics:body0 = </World/Base>
rel physics:body1 = </World/Arm>
float limit:transX:physics:low = 1
float limit:transX:physics:high = 0
float limit:rotZ:physics:low = -30
float limit:rotZ:physics:high = 30
uniform token drive:rotZ:physics:type = "force"
float drive:rotZ:physics:targetVelocity = 90
float drive:rotZ:physics:stiffness = 100
float drive:rotZ:physics:damping = 10
float drive:rotZ:physics:maxForce = 50
}

def PhysicsCollisionGroup "Group"
{
rel collection:colliders:includes = [</World/Base>, </World/Arm>]
string physics:mergeGroup = "default"
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub mod ar;
pub mod expr;
pub mod layer;
pub mod pcp;
#[cfg(feature = "physics")]
pub mod physics;
pub mod sdf;
pub mod stage;
pub mod usda;
Expand Down
69 changes: 69 additions & 0 deletions src/physics/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! UsdPhysics schema reader.
//!
//! Decodes Pixar's `UsdPhysics` schema family from a composed
//! [`crate::Stage`]. Mirrors the C++ surface in
//! `pxr/usd/usdPhysics/`:
//!
//! Concrete prim types:
//! - [`tokens::T_PHYSICS_SCENE`] — simulation-wide settings (gravity).
//! - [`tokens::T_PHYSICS_JOINT`] — generic 6-DOF joint base.
//! - [`tokens::T_PHYSICS_FIXED_JOINT`] — locks all DOFs.
//! - [`tokens::T_PHYSICS_REVOLUTE_JOINT`] — single-axis rotation.
//! - [`tokens::T_PHYSICS_PRISMATIC_JOINT`] — single-axis translation.
//! - [`tokens::T_PHYSICS_SPHERICAL_JOINT`] — ball joint with cone limits.
//! - [`tokens::T_PHYSICS_DISTANCE_JOINT`] — min/max distance constraint.
//! - [`tokens::T_PHYSICS_COLLISION_GROUP`] — coarse collision filtering.
//!
//! Single-apply API schemas:
//! - [`tokens::API_RIGID_BODY`] — mark prim as physics-driven.
//! - [`tokens::API_MASS`] — explicit mass / inertia / centre-of-mass.
//! - [`tokens::API_COLLISION`] — enable collision on a prim.
//! - [`tokens::API_MESH_COLLISION`] — mesh shape approximation token.
//! - [`tokens::API_PHYSICS_MATERIAL`] — friction / restitution / density.
//! - [`tokens::API_ARTICULATION_ROOT`] — mark a reduced-coordinate articulation.
//! - [`tokens::API_FILTERED_PAIRS`] — fine-grained pair filtering.
//!
//! Multi-apply API schemas (one instance per DOF):
//! - [`tokens::API_LIMIT`] — per-DOF lock / range.
//! - [`tokens::API_DRIVE`] — per-DOF spring-damper actuator.
//!
//! ## Conventions
//!
//! Reader functions return values in the scene's authored units:
//! - Linear values stay in scene units (caller applies `metersPerUnit`).
//! - Mass values stay in scene mass units (caller applies `kilogramsPerUnit`).
//! - Rotational values stay in degrees (USD's authoring convention).
//! - Quaternions stay in USD's textual `(w, x, y, z)` order.
//! - `lower > upper` on any limit encodes a locked DOF.
//!
//! ## Example
//!
//! ```ignore
//! use openusd::{physics, Stage};
//!
//! let stage = Stage::open("scene.usd")?;
//! let physics = physics::find_physics_prims(&stage)?;
//! for joint_path in &physics.joints {
//! let path = openusd::sdf::path(joint_path)?;
//! if let Some(joint) = physics::read_joint(&stage, &path)? {
//! println!("{}: {:?} body0={:?} body1={:?}",
//! joint.path, joint.kind, joint.body0, joint.body1);
//! }
//! }
//! # anyhow::Ok(())
//! ```

pub mod tokens;

mod read;
mod types;

pub use read::{
find_physics_prims, read_collision_group, read_collision_shape, read_filtered_pairs, read_has_articulation_root,
read_has_collision, read_has_rigid_body, read_is_physics_scene, read_joint, read_joint_drives, read_joint_limits,
read_mass, read_physics_material, read_physics_scene, read_rigid_body,
};
pub use types::{
CollisionApprox, Dof, DriveType, JointKind, PhysicsPrims, ReadCollisionGroup, ReadCollisionShape, ReadDrive,
ReadFilteredPairs, ReadJoint, ReadLimit, ReadMass, ReadPhysicsMaterial, ReadPhysicsScene, ReadRigidBody,
};
Loading