Physics Umbrella Runtime#
Physics Umbrella is a framework that allows third-party physics engines to integrate with the Omni Physics ecosystem. It is part of the omni.physics extension system and provides a unified interface for registering custom simulators that can participate in the physics pipeline, receive stage updates, perform scene queries, handle user interactions, and provide benchmarking data.
Overview#
The system is built around the concept of a Simulation structure that contains function pointers organized into five functional groups:
SimulationFns - Core simulation lifecycle and physics operations
StageUpdateFns - Stage attachment and timeline event handling
SceneQueryFns - Raycasts, sweeps, and overlap queries
InteractionFns - User interaction handling (picking, reset behavior)
BenchmarkFns - Performance profiling and statistics
The Simulation structure serves as the main container for all physics functionality:
struct Simulation
{
SimulationFns simulationFns;
StageUpdateFns stageUpdateFns;
SceneQueryFns sceneQueryFns;
InteractionFns interactionFns;
BenchmarkFns benchmarkFns;
};
When a simulation is registered using IPhysics::registerSimulation(), the physics system calls the registered function pointers at appropriate times during the simulation lifecycle.
Registering a Simulation#
The IPhysics interface is the main entry point for registering and managing simulations. It provides:
- Registration Management:
registerSimulation()- Register a new simulation with the physics systemunregisterSimulation()- Remove a simulation from the physics system
- Query Functions:
getSimulation()- Get simulation structure by IDgetSimulationName()- Get simulation name by IDgetNumSimulations()- Get count of registered simulationsgetSimulationIds()- Get array of all registered simulation IDs
- Activation Control:
activateSimulation()- Enable a simulator to receive updatesdeactivateSimulation()- Disable a simulator from receiving updatesisSimulationActive()- Check if a simulator is currently active
- Event Subscriptions:
subscribeSimulationRegistryEvents()- Listen for simulation lifecycle eventsunsubscribeSimulationRegistryEvents()- Stop listening for events
Note
Simulations are automatically active upon registration but do not trigger the ACTIVATED event at that time.
C++ Example - Registering a Simulation#
#include <omni/physics/simulation/IPhysics.h>
using namespace omni::physics;
// Acquire the physics interface
IPhysics* physics = framework->acquireInterface<IPhysics>();
// Create and configure your simulation
Simulation simulation;
// Register with the physics system
const char* simulationName = "MyCustomSimulation";
SimulationId simId = physics->registerSimulation(simulation, simulationName);
if (simId != kInvalidSimulationId)
{
// Simulation registered successfully
// It is automatically active after registration
// Check if it's active
bool isActive = physics->isSimulationActive(simId);
// Deactivate if needed
physics->deactivateSimulation(simId);
// Reactivate later
physics->activateSimulation(simId);
}
// When done, unregister
physics->unregisterSimulation(simId);
Python Example - Registering a Simulation#
from omni.physics.core import (
Simulation,
get_physics_interface,
k_invalid_simulation_id
)
# Get the physics interface
physics = get_physics_interface()
# Create a simulation instance
simulation = Simulation()
# Register the simulation
simulation_name = "MyCustomSimulation"
simulation_id = physics.register_simulation(simulation, simulation_name)
if simulation_id != k_invalid_simulation_id:
# Simulation registered successfully
# Query simulation state
is_active = physics.is_simulation_active(simulation_id)
# Get simulation name
name = physics.get_simulation_name(simulation_id)
# Deactivate/activate as needed
physics.deactivate_simulation(simulation_id)
physics.activate_simulation(simulation_id)
# Unregister when done
physics.unregister_simulation(simulation_id)
Querying Registered Simulations#
Applications can query information about all registered simulations:
C++ Example - Querying Simulations#
#include <omni/physics/simulation/IPhysics.h>
#include <vector>
using namespace omni::physics;
IPhysics* physics = framework->acquireInterface<IPhysics>();
// Get number of registered simulations
size_t numSimulations = physics->getNumSimulations();
carb::log_info("Number of registered simulations: %zu", numSimulations);
// Get all simulation IDs
std::vector<SimulationId> simulationIds(numSimulations);
size_t actualCount = physics->getSimulationIds(simulationIds.data(), numSimulations);
// Iterate through all registered simulations
for (size_t i = 0; i < actualCount; ++i)
{
SimulationId simId = simulationIds[i];
const char* name = physics->getSimulationName(simId);
bool isActive = physics->isSimulationActive(simId);
carb::log_info("Simulation[%zu]: %s (ID: %zu, Active: %s)",
i, name, simId.id, isActive ? "Yes" : "No");
// Get the simulation structure
const Simulation* sim = physics->getSimulation(simId);
if (sim)
{
// Access simulation function pointers
// sim->simulationFns.initialize, etc.
}
}
Python Example - Querying Simulations#
from omni.physics.core import get_physics_interface
physics = get_physics_interface()
# Get number of registered simulations
num_simulations = physics.get_num_simulations()
print(f"Number of registered simulations: {num_simulations}")
# Get all simulation IDs
simulation_ids = physics.get_simulation_ids()
# Iterate through all registered simulations
for i, sim_id in enumerate(simulation_ids):
name = physics.get_simulation_name(sim_id)
is_active = physics.is_simulation_active(sim_id)
print(f"Simulation[{i}]: {name} (ID: {sim_id}, Active: {is_active})")
# Get the simulation structure
sim = physics.get_simulation(sim_id)
if sim:
# Access simulation functions
# sim.simulation_fns.initialize, etc.
pass
Simulation Registry Events#
Applications can subscribe to events that provide notifications when simulations are registered, unregistered, activated, or deactivated.
Event Types (SimulationRegistryEventType):
eSIMULATION_REGISTERED/SIMULATION_REGISTERED- A new simulation has been registeredeSIMULATION_UNREGISTERED/SIMULATION_UNREGISTERED- A simulation has been unregistered (simulation ID is no longer valid after this event)eSIMULATION_ACTIVATED/SIMULATION_ACTIVATED- A simulation has been activated (not triggered during initial registration)eSIMULATION_DEACTIVATED/SIMULATION_DEACTIVATED- A simulation has been deactivated
Important
Events are triggered after the associated action has been executed
Simulations are automatically active upon registration but do not trigger ACTIVATED event
After UNREGISTERED event, the simulation ID is no longer valid for API use
C++ Example - Subscribing to Registry Events#
// C++ Example
SubscriptionId subscriptionId = physics->subscribeSimulationRegistryEvents(
[](SimulationRegistryEventType::Enum eventType,
const SimulationId& id,
const char* name,
void* userData)
{
switch (eventType)
{
case SimulationRegistryEventType::eSIMULATION_REGISTERED:
// Handle registration
break;
case SimulationRegistryEventType::eSIMULATION_UNREGISTERED:
// Handle unregistration
break;
case SimulationRegistryEventType::eSIMULATION_ACTIVATED:
// Handle activation
break;
case SimulationRegistryEventType::eSIMULATION_DEACTIVATED:
// Handle deactivation
break;
}
},
nullptr // userData
);
// Unsubscribe when done
physics->unsubscribeSimulationRegistryEvents(subscriptionId);
# Python Example
from omni.physics.core import SimulationRegistryEventType
def on_registry_event(event_type, simulation_id, simulation_name):
if event_type == SimulationRegistryEventType.SIMULATION_REGISTERED:
print(f"Simulation registered: {simulation_name}")
elif event_type == SimulationRegistryEventType.SIMULATION_UNREGISTERED:
print(f"Simulation unregistered: {simulation_name}")
elif event_type == SimulationRegistryEventType.SIMULATION_ACTIVATED:
print(f"Simulation activated: {simulation_name}")
elif event_type == SimulationRegistryEventType.SIMULATION_DEACTIVATED:
print(f"Simulation deactivated: {simulation_name}")
subscription = physics.subscribe_simulation_registry_events(on_registry_event)
# Set to None to unsubscribe
subscription = None
Core Simulation Functions#
The SimulationFns structure contains the core physics simulation functionality. This structure is defined in the IPhysicsSimulation interface and groups all essential simulation operations.
Key Function Groups#
The SimulationFns structure organizes functions into logical groups:
Stage Management -
initialize,close,getAttachedStageSimulation Control -
simulate,simulateAsync,fetchResults,checkResults,flushChangesChange Tracking -
pauseChangeTracking,isChangeTrackingPausedTiming -
getSimulationTimeStepsPerSecond,getSimulationTimestamp,getSimulationStepCountBody Control -
addForceAtPos,addTorque,wakeUp,putToSleep,isSleepingCapability Checking -
isCapableOfSimulatingEvents -
subscribePhysicsContactReportEvents,subscribePhysicsOnStepEvents
For complete function signatures and detailed documentation, refer to the IPhysicsSimulation interface header.
Important Concepts#
Change Tracking
The physics system tracks changes to USD stage through a fabric listener. Change tracking can be paused when making many modifications to avoid unnecessary intermediate updates:
// Pause tracking while making bulk changes
simulation->pauseChangeTracking(true);
// Make many USD modifications...
simulation->pauseChangeTracking(false);
simulation->flushChanges(); // Process all buffered changes
Simulation Step Timing
simulate()executes for the exact elapsed time passed synchronously - it waits for results before returning and writes output data as iffetchResults()was called. No substepping occurs.simulateAsync()executes for the exact elapsed time passed asynchronously - it returns immediately and the caller must usefetchResults()(blocking) orcheckResults()(non-blocking) to retrieve results. No substepping occurs.Recommended maximum step size is 1/60 second for stability
getSimulationStepCount()resets to 0 when a new simulation startsgetSimulationTimestamp()increments with every simulation step
Stage Initialization and Teardown
The SimulationFns fields initialize and close replace the previous attachStage and detachStage names:
initialize(stageId)- Initializes the simulation with a USD stage. Runs the physics parser and populates simulation objects. Any previously attached stage is closed automatically. Returnstrueon success.close()- Closes the simulation and removes all objects.getAttachedStage()- Returns the currently attached USD stage ID (0means no stage).
simulate vs simulateAsync
The simulation provides two modes for stepping physics:
simulate(elapsedTime, currentTime)runs the physics step synchronously. It blocks until the simulation is complete, then writes output data automatically (equivalent to callingfetchResults()after the step). This is the simplest option when you do not need to overlap computation with the physics step.simulateAsync(elapsedTime, currentTime)runs the physics step asynchronously. It returns immediately, allowing the caller to perform other work while physics is running. After callingsimulateAsync, you must retrieve results explicitly:fetchResults()- blocks until the simulation finishes, then writes results.checkResults()- non-blocking check that returnstrueif the simulation has finished.
Note
Both functions simulate the exact elapsedTime passed with no substepping. It is the caller’s responsibility to provide a reasonable step size (recommended maximum: 1/60 second).
Typical usage patterns:
// Synchronous - simple, blocking
simulation->simulate(1.0f / 60.0f, currentTime);
// Results are already written out, ready to use
// Asynchronous - overlaps work with physics
simulation->simulateAsync(1.0f / 60.0f, currentTime);
// ... do other work while physics runs ...
simulation->fetchResults(); // block and write results
Event Subscription Restrictions
Warning
Subscriptions cannot be added or removed within
subscribePhysicsOnStepEventscallbacksContact data from
subscribePhysicsContactReportEventsis only valid for one simulation stepStep event callbacks with
order=0execute first (highest priority), higher values execute later
C++ Example - Implementing SimulationFns#
#include <omni/physics/simulation/IPhysics.h>
#include <omni/physics/simulation/IPhysicsSimulation.h>
using namespace omni::physics;
class MyPhysicsEngine
{
public:
MyPhysicsEngine()
{
// Initialize simulation functions
m_simulationFns.initialize = [this](long stageId) -> bool {
m_attachedStageId = stageId;
// Initialize physics world from USD stage
return initializeFromStage(stageId);
};
m_simulationFns.close = [this]() {
m_attachedStageId = 0;
cleanup();
};
m_simulationFns.getAttachedStage = [this]() -> long {
return m_attachedStageId;
};
m_simulationFns.simulate = [this](float elapsedTime, float currentTime) {
// Run physics simulation
stepPhysics(elapsedTime);
m_timestamp++;
m_stepCount++;
// Notify pre-step callbacks
PhysicsStepContext context;
context.scenePath = m_attachedStageId;
context.simulationId = m_simId;
for (auto& [id, callback] : m_stepCallbacks)
{
callback(elapsedTime, context);
}
};
m_simulationFns.fetchResults = [this]() {
// Write results back to USD/Fabric
writeResultsToStage();
// Notify contact callbacks
// IMPORTANT: Contact data is only valid during this callback
// Copy data if persistence beyond one step is needed
for (auto& callback : m_contactCallbacks)
{
callback(m_contactHeaders, m_contactData, m_frictionAnchors);
}
};
m_simulationFns.checkResults = [this]() -> bool {
return m_simulationComplete;
};
m_simulationFns.flushChanges = [this]() {
processBufferedChanges();
};
m_simulationFns.getSimulationTimestamp = [this]() -> uint64_t {
return m_timestamp;
};
m_simulationFns.getSimulationStepCount = [this]() -> uint64_t {
return m_stepCount;
};
m_simulationFns.subscribePhysicsOnStepEvents =
[this](bool preStep, int order, OnPhysicsStepEventFn callback) -> SubscriptionId {
SubscriptionId id = ++m_nextStepCallbackId;
m_stepCallbacks[id] = callback;
return id;
};
// Note: preStep=true means callback executes BEFORE physics step
// preStep=false means callback executes AFTER physics step
// order parameter: 0 = highest priority, higher values = lower priority
m_simulationFns.unsubscribePhysicsOnStepEvents =
[this](SubscriptionId id) {
m_stepCallbacks.erase(id);
};
m_simulationFns.isCapableOfSimulating =
[this](const char** schemaNames, size_t schemaNamesCount, bool* isCapable) -> bool {
// Check if this simulation can handle each schema
for (size_t i = 0; i < schemaNamesCount; ++i)
{
std::string schemaName(schemaNames[i]);
// Check against supported schemas
if (schemaName == "PhysicsRigidBodyAPI" ||
schemaName == "PhysicsCollisionAPI" ||
schemaName == "PhysicsMassAPI" ||
schemaName == "PhysicsJoint")
{
isCapable[i] = true;
}
else
{
isCapable[i] = false;
}
}
return true; // Return true if capability check was performed
};
}
SimulationFns getSimulationFns() const { return m_simulationFns; }
void setSimulationId(SimulationId id) { m_simId = id; }
private:
SimulationFns m_simulationFns;
SimulationId m_simId;
long m_attachedStageId = 0;
uint64_t m_timestamp = 0;
uint64_t m_stepCount = 0;
bool m_simulationComplete = false;
std::unordered_map<SubscriptionId, OnPhysicsStepEventFn> m_stepCallbacks;
SubscriptionId m_nextStepCallbackId = 0;
std::vector<OnContactReportEventFn> m_contactCallbacks;
ContactEventHeaderVector m_contactHeaders;
ContactDataVector m_contactData;
FrictionAnchorsDataVector m_frictionAnchors;
// Implementation methods
bool initializeFromStage(long stageId) { /* ... */ return true; }
void cleanup() { /* ... */ }
void stepPhysics(float dt) { /* ... */ }
void writeResultsToStage() { /* ... */ }
void processBufferedChanges() { /* ... */ }
};
// Usage
MyPhysicsEngine engine;
Simulation sim;
sim.simulationFns = engine.getSimulationFns();
SimulationId simId = physics->registerSimulation(sim, "MyEngine");
engine.setSimulationId(simId);
Python Example - Implementing SimulationFns#
from omni.physics.core import (
Simulation,
PhysicsStepContext,
get_physics_interface,
get_physics_simulation_interface,
k_invalid_simulation_id
)
class MyPhysicsEngine:
"""Custom physics engine implementation"""
def __init__(self):
self.attached_stage_id = 0
self.timestamp = 0
self.step_count = 0
self.step_callbacks = []
self.simulation_id = k_invalid_simulation_id
def initialize(self, stage_id):
"""Initialize physics simulation with a USD stage"""
self.attached_stage_id = stage_id
# Initialize physics from USD stage
return True
def close(self):
"""Close the simulation and cleanup"""
self.attached_stage_id = 0
def get_attached_stage(self):
"""Get currently attached stage"""
return self.attached_stage_id
def simulate(self, elapsed_time, current_time):
"""Execute one physics step"""
# Run physics simulation
self._step_physics(elapsed_time)
self.timestamp += 1
self.step_count += 1
# Notify step callbacks
context = PhysicsStepContext()
context.scene_path = self.attached_stage_id
context.simulation_id = self.simulation_id
for callback in self.step_callbacks:
callback(elapsed_time, context)
def fetch_results(self):
"""Write results back to USD"""
self._write_results_to_stage()
def check_results(self):
"""Check if simulation is complete"""
return True
def get_simulation_timestamp(self):
return self.timestamp
def get_simulation_step_count(self):
return self.step_count
def subscribe_on_step_events(self, pre_step, order, callback):
self.step_callbacks.append(callback)
return len(self.step_callbacks)
def unsubscribe_on_step_events(self, subscription_id):
if 0 < subscription_id <= len(self.step_callbacks):
self.step_callbacks.pop(subscription_id - 1)
def is_capable_of_simulating(self, schema_names):
"""Check if this simulation can simulate given USD schemas"""
supported_schemas = {
"PhysicsRigidBodyAPI",
"PhysicsCollisionAPI",
"PhysicsMassAPI",
"PhysicsJoint"
}
# Return list of booleans indicating capability for each schema
is_capable = [schema in supported_schemas for schema in schema_names]
return True, is_capable # Return (success, capabilities)
def _step_physics(self, dt):
"""Internal physics stepping"""
pass
def _write_results_to_stage(self):
"""Internal results writing"""
pass
# Usage
physics = get_physics_interface()
physics_simulation = get_physics_simulation_interface()
engine = MyPhysicsEngine()
# Create simulation and wire up functions
simulation = Simulation()
simulation.simulation_fns.initialize = engine.initialize
simulation.simulation_fns.close = engine.close
simulation.simulation_fns.get_attached_stage = engine.get_attached_stage
simulation.simulation_fns.simulate = engine.simulate
simulation.simulation_fns.fetch_results = engine.fetch_results
simulation.simulation_fns.check_results = engine.check_results
simulation.simulation_fns.get_simulation_timestamp = engine.get_simulation_timestamp
simulation.simulation_fns.get_simulation_step_count = engine.get_simulation_step_count
simulation.simulation_fns.subscribe_physics_on_step_events = engine.subscribe_on_step_events
simulation.simulation_fns.unsubscribe_physics_on_step_events = engine.unsubscribe_on_step_events
simulation.simulation_fns.is_capable_of_simulating = engine.is_capable_of_simulating
# Register simulation
simulation_id = physics.register_simulation(simulation, "MyEngine")
engine.simulation_id = simulation_id
Capability Checking - Using isCapableOfSimulating#
The isCapableOfSimulating function allows querying whether a registered simulation can simulate specific USD schema types or schema APIs. This is useful for determining compatibility before attempting to simulate a scene.
C++ Example - Checking Simulation Capabilities#
#include <omni/physics/simulation/IPhysics.h>
#include <omni/physics/simulation/IPhysicsSimulation.h>
using namespace omni::physics;
// Acquire interfaces
IPhysics* physics = framework->acquireInterface<IPhysics>();
IPhysicsSimulation* simulation = framework->acquireInterface<IPhysicsSimulation>();
// Get a registered simulation ID
SimulationId simId = physics->getSimulationIds()[0];
// Check single schema capability
const char* schemaNames[] = { "PhysicsRigidBodyAPI" };
bool isCapable[1] = { false };
bool success = simulation->isCapableOfSimulating(simId, schemaNames, 1, isCapable);
if (success)
{
if (isCapable[0])
{
carb::log("Simulation supports PhysicsRigidBodyAPI");
}
else
{
carb::log("Simulation does not support PhysicsRigidBodyAPI");
}
}
else
{
carb::log("Capability check not available for this simulation");
}
// Check multiple schemas at once
const char* multipleSchemas[] = {
"PhysicsRigidBodyAPI",
"PhysicsCollisionAPI",
"PhysicsMassAPI",
"PhysicsJoint"
};
bool multipleCapabilities[4] = { false, false, false, false };
success = simulation->isCapableOfSimulating(
simId, multipleSchemas, 4, multipleCapabilities);
if (success)
{
for (size_t i = 0; i < 4; ++i)
{
carb::log("%s: %s",
multipleSchemas[i],
multipleCapabilities[i] ? "supported" : "not supported");
}
}
Python Example - Checking Simulation Capabilities#
from omni.physics.core import (
get_physics_interface,
get_physics_simulation_interface
)
# Get interfaces
physics = get_physics_interface()
simulation = get_physics_simulation_interface()
# Get a registered simulation ID
simulation_ids = physics.get_simulation_ids()
if simulation_ids:
sim_id = simulation_ids[0]
# Check single schema capability
schema_names = ["PhysicsRigidBodyAPI"]
success, capabilities = simulation.is_capable_of_simulating(sim_id, schema_names)
if success:
if capabilities[0]:
print("Simulation supports PhysicsRigidBodyAPI")
else:
print("Simulation does not support PhysicsRigidBodyAPI")
else:
print("Capability check not available for this simulation")
# Check multiple schemas at once
schema_names = [
"PhysicsRigidBodyAPI",
"PhysicsCollisionAPI",
"PhysicsMassAPI",
"PhysicsJoint"
]
success, capabilities = simulation.is_capable_of_simulating(sim_id, schema_names)
if success:
for schema, is_capable in zip(schema_names, capabilities):
status = "supported" if is_capable else "not supported"
print(f"{schema}: {status}")
# Practical example: Check stage compatibility
def check_stage_compatibility(stage, simulation_id):
"""Check if a simulation can handle all schemas in a stage"""
from pxr import Usd
# Collect all unique schema types from stage
schema_names = set()
for prim in stage.Traverse():
if prim.HasAPI("PhysicsRigidBodyAPI"):
schema_names.add("PhysicsRigidBodyAPI")
if prim.HasAPI("PhysicsCollisionAPI"):
schema_names.add("PhysicsCollisionAPI")
if prim.HasAPI("PhysicsMassAPI"):
schema_names.add("PhysicsMassAPI")
# Add more schema checks as needed
if not schema_names:
return True, [] # No physics schemas in stage
# Check capabilities
success, capabilities = simulation.is_capable_of_simulating(
simulation_id, list(schema_names))
if not success:
return False, [] # Capability check not available
# Find unsupported schemas
unsupported = [
schema for schema, capable in zip(schema_names, capabilities)
if not capable
]
return len(unsupported) == 0, unsupported
Stage Update Functions#
The StageUpdateFns structure handles stage attachment and timeline events. This structure is defined in the IPhysicsStageUpdate interface and provides hooks for responding to stage lifecycle changes.
Key Functions#
onAttach- Called when a stage is attachedonDetach- Called when a stage is detachedonUpdate- Called on each frame updateonResume- Called when timeline playback resumesonPause- Called when timeline is pausedonReset- Called when timeline is stopped/resethandleRaycast- Handle raycast requests for pickingforceLoadPhysicsFromUSD- Force load physics objects from USDreleasePhysicsObjects- Release all physics objectsresetSimulation- Reset simulation statestartSimulation- Called when simulation starts, useful for storing the initial transformations
Simulation Events#
The IPhysicsStageUpdate interface provides a getSimulationEventStream() function that returns a Carb event stream which broadcasts simulation lifecycle events. These events allow applications to respond to timeline state changes.
Event Types (SimulationEvent enum):
eResumed/RESUMED- Sent whenonResume()is called (timeline playback starts)ePaused/PAUSED- Sent whenonPause()is called (timeline is paused)eStopped/STOPPED- Sent whenonReset()is called (timeline is stopped/reset)
Event Flow:
User presses Play →
onResume()called →eResumedevent sentUser presses Pause →
onPause()called →ePausedevent sentUser presses Play again →
onResume()called →eResumedevent sentUser presses Stop →
onReset()called →eStoppedevent sent
C++ Example - Implementing StageUpdateFns#
#include <omni/physics/simulation/simulator/StageUpdate.h>
using namespace omni::physics;
class MyStageUpdateHandler
{
public:
MyStageUpdateHandler()
{
m_stageUpdateFns.onAttach = [this](long stageId) {
m_stageId = stageId;
// Prepare for stage - but don't load physics yet
};
m_stageUpdateFns.onDetach = [this]() {
m_stageId = 0;
releaseAll();
};
m_stageUpdateFns.onUpdate = [this](float currentTime, float elapsedSecs, bool enableUpdate) {
if (enableUpdate)
{
// Perform physics update
update(elapsedSecs);
}
// Update other subsystems even if physics is disabled
updateVisuals();
};
m_stageUpdateFns.onResume = [this](float currentTime) {
m_isPlaying = true;
storeInitialState(); // Store state for reset
};
m_stageUpdateFns.onPause = [this]() {
m_isPlaying = false;
};
m_stageUpdateFns.onReset = [this]() {
m_isPlaying = false;
restoreInitialState(); // Restore to pre-play state
};
m_stageUpdateFns.forceLoadPhysicsFromUSD = [this]() {
// Create physics objects from the attached USD stage
loadPhysicsFromStage(m_stageId);
};
m_stageUpdateFns.releasePhysicsObjects = [this]() {
// Release all physics objects but keep stage attached
releasePhysicsObjects();
};
m_stageUpdateFns.startSimulation = [this]() {
// Called when simulation begins
// Store initial transforms for reset functionality
storeInitialTransforms();
};
m_stageUpdateFns.resetSimulation = [this]() {
// Reset simulation state while keeping stage attached
releasePhysicsObjects();
loadPhysicsFromStage(m_stageId);
};
}
StageUpdateFns getStageUpdateFns() const { return m_stageUpdateFns; }
private:
StageUpdateFns m_stageUpdateFns;
long m_stageId = 0;
bool m_isPlaying = false;
void releaseAll() { /* ... */ }
void update(float dt) { /* ... */ }
void updateVisuals() { /* ... */ }
void storeInitialState() { /* ... */ }
void restoreInitialState() { /* ... */ }
void loadPhysicsFromStage(long stageId) { /* ... */ }
void releasePhysicsObjects() { /* ... */ }
void storeInitialTransforms() { /* ... */ }
};
Python Example - Implementing StageUpdateFns#
from omni.physics.core import Simulation
class MyStageUpdateHandler:
"""Handle stage lifecycle and timeline events"""
def __init__(self):
self.stage_id = 0
self.is_playing = False
self.initial_state = None
def on_attach(self, stage_id):
"""Called when stage is attached"""
self.stage_id = stage_id
def on_detach(self):
"""Called when stage is detached"""
self.stage_id = 0
self._release_all()
def on_update(self, current_time, elapsed_secs, enable_update):
"""Called each frame"""
if enable_update:
self._update_physics(elapsed_secs)
self._update_visuals()
def on_resume(self, current_time):
"""Called when timeline playback starts"""
self.is_playing = True
self._store_initial_state()
def on_pause(self):
"""Called when timeline is paused"""
self.is_playing = False
def on_reset(self):
"""Called when timeline is stopped"""
self.is_playing = False
self._restore_initial_state()
def force_load_physics_from_usd(self):
"""Force create physics objects from USD"""
self._load_physics_from_stage()
def release_physics_objects(self):
"""Release physics objects but keep stage"""
pass
def start_simulation(self):
"""Called when simulation starts"""
self._store_initial_transforms()
def reset_simulation(self):
"""Reset simulation state"""
self.release_physics_objects()
self._load_physics_from_stage()
# Private methods
def _release_all(self): pass
def _update_physics(self, dt): pass
def _update_visuals(self): pass
def _store_initial_state(self): pass
def _restore_initial_state(self): pass
def _load_physics_from_stage(self): pass
def _store_initial_transforms(self): pass
# Wire up to simulation
handler = MyStageUpdateHandler()
simulation = Simulation()
simulation.stage_update_fns.on_attach = handler.on_attach
simulation.stage_update_fns.on_detach = handler.on_detach
simulation.stage_update_fns.on_update = handler.on_update
simulation.stage_update_fns.on_resume = handler.on_resume
simulation.stage_update_fns.on_pause = handler.on_pause
simulation.stage_update_fns.on_reset = handler.on_reset
simulation.stage_update_fns.force_load_physics_from_usd = handler.force_load_physics_from_usd
simulation.stage_update_fns.release_physics_objects = handler.release_physics_objects
simulation.stage_update_fns.start_simulation = handler.start_simulation
simulation.stage_update_fns.reset_simulation = handler.reset_simulation
Using Simulation Event Stream#
The simulation event stream allows listening for timeline state changes (play, pause, stop) without directly implementing the stage update callbacks. This is useful for extensions that need to respond to simulation state changes.
C++ Example - Subscribing to Simulation Events#
#include <omni/physics/simulation/IPhysicsStageUpdate.h>
#include <carb/events/IEvents.h>
using namespace omni::physics;
// Acquire the physics stage update interface
IPhysicsStageUpdate* stageUpdate = framework->acquireInterface<IPhysicsStageUpdate>();
// Get the simulation event stream
carb::events::IEventStreamPtr eventStream = stageUpdate->getSimulationEventStream();
if (eventStream)
{
// Create subscription to receive events
auto subscription = carb::events::createSubscriptionToPop(
eventStream.get(),
[](carb::events::IEvent* event)
{
SimulationEvent simEvent = static_cast<SimulationEvent>(event->type);
switch (simEvent)
{
case SimulationEvent::eResumed:
carb::log("Simulation resumed - timeline is playing");
// Start your simulation-dependent operations
break;
case SimulationEvent::ePaused:
carb::log("Simulation paused - timeline paused");
// Pause your operations
break;
case SimulationEvent::eStopped:
carb::log("Simulation stopped - timeline reset");
// Clean up and reset state
break;
}
}
);
// Events are delivered when the event stream is pumped
// This is typically done in the update loop
eventStream->pump();
// Keep subscription alive for as long as events should be received
// When subscription goes out of scope, it automatically unsubscribes
}
Example: Tracking Simulation State
class SimulationStateTracker
{
public:
SimulationStateTracker(IPhysicsStageUpdate* stageUpdate)
{
m_eventStream = stageUpdate->getSimulationEventStream();
if (m_eventStream)
{
m_subscription = carb::events::createSubscriptionToPop(
m_eventStream.get(),
[this](carb::events::IEvent* event)
{
handleSimulationEvent(static_cast<SimulationEvent>(event->type));
}
);
}
}
void update()
{
// Pump events in your update loop
if (m_eventStream)
{
m_eventStream->pump();
}
}
bool isPlaying() const { return m_isPlaying; }
bool isPaused() const { return m_isPaused; }
private:
void handleSimulationEvent(SimulationEvent event)
{
switch (event)
{
case SimulationEvent::eResumed:
m_isPlaying = true;
m_isPaused = false;
carb::log("State: Playing");
break;
case SimulationEvent::ePaused:
m_isPlaying = false;
m_isPaused = true;
carb::log("State: Paused");
break;
case SimulationEvent::eStopped:
m_isPlaying = false;
m_isPaused = false;
carb::log("State: Stopped");
break;
}
}
carb::events::IEventStreamPtr m_eventStream;
carb::events::SubscriptionPtr m_subscription;
bool m_isPlaying = false;
bool m_isPaused = false;
};
Python Example - Subscribing to Simulation Events#
from omni.physics.core import (
get_physics_stage_update_interface,
SimulationEvent
)
# Get the physics stage update interface
stage_update = get_physics_stage_update_interface()
# Get the simulation event stream
event_stream = stage_update.get_simulation_event_stream()
if event_stream:
# Define event handler
def on_simulation_event(event):
if event.type == SimulationEvent.RESUMED:
print("Simulation resumed - timeline is playing")
# Start your simulation-dependent operations
elif event.type == SimulationEvent.PAUSED:
print("Simulation paused - timeline paused")
# Pause your operations
elif event.type == SimulationEvent.STOPPED:
print("Simulation stopped - timeline reset")
# Clean up and reset state
# Subscribe to events
subscription = event_stream.create_subscription_to_pop(on_simulation_event)
# Events are delivered when the event stream is pumped
# This is typically done automatically by the framework,
# but manual pumping is also possible if needed
event_stream.pump()
# Keep subscription alive for as long as events should be received
# Set to None to unsubscribe
# subscription = None
Example: Extension with Simulation State Tracking
import omni.ext
from omni.physics.core import (
get_physics_stage_update_interface,
SimulationEvent
)
class MySimulationExtension(omni.ext.IExt):
"""Extension that responds to simulation events"""
def on_startup(self, ext_id):
self._is_playing = False
self._is_paused = False
self._subscription = None
# Get event stream
stage_update = get_physics_stage_update_interface()
event_stream = stage_update.get_simulation_event_stream()
if event_stream:
# Subscribe to simulation events
self._subscription = event_stream.create_subscription_to_pop(
self._on_simulation_event
)
def on_shutdown(self):
# Unsubscribe from events
self._subscription = None
def _on_simulation_event(self, event):
"""Handle simulation lifecycle events"""
if event.type == SimulationEvent.RESUMED:
self._is_playing = True
self._is_paused = False
print("Extension: Simulation started")
self._start_monitoring()
elif event.type == SimulationEvent.PAUSED:
self._is_playing = False
self._is_paused = True
print("Extension: Simulation paused")
self._pause_monitoring()
elif event.type == SimulationEvent.STOPPED:
self._is_playing = False
self._is_paused = False
print("Extension: Simulation stopped")
self._stop_monitoring()
def _start_monitoring(self):
"""Start simulation-dependent operations"""
print("Starting data collection...")
def _pause_monitoring(self):
"""Pause operations while simulation is paused"""
print("Pausing data collection...")
def _stop_monitoring(self):
"""Stop and reset when simulation stops"""
print("Stopping and resetting data collection...")
Example: Coordinating Multiple Systems
class SimulationCoordinator:
"""Coordinates multiple systems based on simulation state"""
def __init__(self):
self.systems = []
self._subscription = None
# Subscribe to simulation events
stage_update = get_physics_stage_update_interface()
event_stream = stage_update.get_simulation_event_stream()
if event_stream:
self._subscription = event_stream.create_subscription_to_pop(
self._on_simulation_event
)
def register_system(self, system):
"""Register a system to be notified of simulation events"""
self.systems.append(system)
def _on_simulation_event(self, event):
"""Broadcast simulation events to all registered systems"""
if event.type == SimulationEvent.RESUMED:
print("Coordinator: Notifying all systems - Play")
for system in self.systems:
system.on_play()
elif event.type == SimulationEvent.PAUSED:
print("Coordinator: Notifying all systems - Pause")
for system in self.systems:
system.on_pause()
elif event.type == SimulationEvent.STOPPED:
print("Coordinator: Notifying all systems - Stop")
for system in self.systems:
system.on_stop()
def shutdown(self):
self._subscription = None
self.systems.clear()
Scene Query Functions#
The SceneQueryFns structure provides scene query functionality for raycasts, sweeps, and overlap tests. This structure is defined in the IPhysicsSceneQuery interface.
Note
Scene queries only work after simulation has begun, as collision data is not fully initialized until that point.
Key Function Groups#
Raycasts -
raycastClosest,raycastAny,raycastAllSphere Sweeps -
sweepSphereClosest,sweepSphereAny,sweepSphereAllBox Sweeps -
sweepBoxClosest,sweepBoxAny,sweepBoxAllShape Sweeps -
sweepShapeClosest,sweepShapeAny,sweepShapeAllOverlaps -
overlapSphere,overlapSphereAny,overlapBox,overlapBoxAny,overlapShape,overlapShapeAny
For complete function signatures and detailed documentation, refer to the IPhysicsSceneQuery interface header.
C++ Example - Implementing SceneQueryFns#
#include <omni/physics/simulation/simulator/SceneQuery.h>
using namespace omni::physics;
class MySceneQueryProvider
{
public:
MySceneQueryProvider()
{
m_sceneQueryFns.raycastClosest =
[this](const carb::Float3& origin,
const carb::Float3& unitDir,
float distance,
RaycastHit& hit,
bool bothSides) -> bool
{
// Perform raycast in your physics engine
if (performRaycast(origin, unitDir, distance, bothSides, hit))
{
return true;
}
return false;
};
m_sceneQueryFns.raycastAny =
[this](const carb::Float3& origin,
const carb::Float3& unitDir,
float distance,
bool bothSides) -> bool
{
RaycastHit hit;
return performRaycast(origin, unitDir, distance, bothSides, hit);
};
m_sceneQueryFns.raycastAll =
[this](const carb::Float3& origin,
const carb::Float3& unitDir,
float distance,
RaycastHitReportFn reportFn,
bool bothSides)
{
std::vector<RaycastHit> hits;
performRaycastAll(origin, unitDir, distance, bothSides, hits);
for (const auto& hit : hits)
{
// Return false from callback to stop traversal
if (!reportFn(hit))
break;
}
};
m_sceneQueryFns.overlapSphere =
[this](float radius,
const carb::Float3& pos,
OverlapHitReportFn reportFn) -> uint32_t
{
std::vector<OverlapHit> hits;
performSphereOverlap(pos, radius, hits);
uint32_t count = 0;
for (const auto& hit : hits)
{
count++;
if (!reportFn(hit))
break;
}
return count;
};
m_sceneQueryFns.overlapSphereAny =
[this](float radius, const carb::Float3& pos) -> bool
{
return checkSphereOverlap(pos, radius);
};
// Implement other functions similarly...
}
SceneQueryFns getSceneQueryFns() const { return m_sceneQueryFns; }
private:
SceneQueryFns m_sceneQueryFns;
bool performRaycast(const carb::Float3& origin,
const carb::Float3& dir,
float distance,
bool bothSides,
RaycastHit& hit) { /* ... */ return false; }
void performRaycastAll(const carb::Float3& origin,
const carb::Float3& dir,
float distance,
bool bothSides,
std::vector<RaycastHit>& hits) { /* ... */ }
void performSphereOverlap(const carb::Float3& pos,
float radius,
std::vector<OverlapHit>& hits) { /* ... */ }
bool checkSphereOverlap(const carb::Float3& pos, float radius) { /* ... */ return false; }
};
Python Example - Using Scene Queries#
import carb
from omni.physics.core import get_physics_scene_query_interface
scene_query = get_physics_scene_query_interface()
# Raycast closest
origin = carb.Float3(0.0, 10.0, 0.0)
direction = carb.Float3(0.0, -1.0, 0.0)
distance = 100.0
hit_found, hit = scene_query.raycast_closest(origin, direction, distance, both_sides=False)
if hit_found:
print(f"Hit at position: {hit.position}")
print(f"Hit distance: {hit.distance}")
print(f"Hit normal: {hit.normal}")
# Raycast any (just check for hit)
has_hit = scene_query.raycast_any(origin, direction, distance, both_sides=False)
# Raycast all with callback
def on_hit(hit):
print(f"Found hit at: {hit.position}")
return True # Continue traversal (return False to stop)
scene_query.raycast_all(origin, direction, distance, on_hit, both_sides=False)
# Overlap tests
sphere_center = carb.Float3(0.0, 0.0, 0.0)
sphere_radius = 5.0
has_overlap = scene_query.overlap_sphere_any(sphere_radius, sphere_center)
def on_overlap(overlap_hit):
print(f"Overlapping with: {overlap_hit.collision}")
return True # Continue
count = scene_query.overlap_sphere(sphere_radius, sphere_center, on_overlap)
print(f"Total overlaps: {count}")
User Interaction Functions#
The InteractionFns structure handles user interactions such as picking, reset-on-stop behavior and debug data queries. This structure is defined in the IPhysicsInteraction interface.
Key Functions#
disableResetOnStop- Control whether simulation resets when stoppedisDisabledResetOnStop- Query reset-on-stop statehandleRaycast- Handle raycast for object pickinggetPrimDebugData- Get simulation debug data for a prim
Debug Data Format#
The getPrimDebugData function returns a dictionary where the keys are the debug data item names and the values are themselves dictionaries with the following keys:
"type"- One of theDebugDataItemTypeenum values"value"- The actual data (type depends on thetypefield, seeDebugDataItemTypefor the expected value format)"doc"- A string describing what this debug item represents
In C++, these are implemented as carb dictionaries, whereas in Python, they are implemented as Python native dictionaries.
DebugDataItemType is an enum that defines the types of data returned from the getPrimDebugData functions.
Debug data items are typed using the DebugDataItemType enum in C++, with the corresponding value format:
eFloat(0) - Single floating point value.eVector(1) - 3D vector (for directions, velocities, etc., where magnitude is a quantity) as a carb::Float3/Double3 (x, y, z).ePoint(2) - 3D point (for positions) as a carb::Float3/Double3 (x, y, z).eQuaternion(3) - Quaternion rotation/orientation as a carb::Float4/Double4 (x, y, z, w).eString(4) - String value.eBool(5) - Boolean value.eInt(6) - Integer value.eUndefined(7) - Undefined or unsupported type.
In Python, debug data item types are available from omni.physics.core through the DebugDataItemType class.
from omni.physics.core import DebugDataItemType
# Available type constants (all are plain integers):
DebugDataItemType.FLOAT # 0 - Single floating-point value
DebugDataItemType.VECTOR # 1 - 3D vector (for directions, velocities, etc., where magnitude is a quantity) as a tuple of 3 floats (x, y, z)
DebugDataItemType.POINT # 2 - 3D point (for positions) as a tuple of 3 floats (x, y, z)
DebugDataItemType.QUATERNION # 3 - Quaternion rotation/orientation as a tuple of 4 floats (x, y, z, w)
DebugDataItemType.STRING # 4 - String value
DebugDataItemType.BOOL # 5 - Boolean value
DebugDataItemType.INT # 6 - Integer value.
DebugDataItemType.UNDEFINED # 7 - Undefined or unsupported type.
Example structure of a debug data dictionary:
{
"Position": {
"type": DebugDataItemType.POINT,
"value": (0.0, 5.0, 0.0),
"doc": "The position of the body in world space"
},
"Mass": {
"type": DebugDataItemType.FLOAT,
"value": 10.0,
"doc": "The mass of the body in kilograms"
},
"Body type": {
"type": DebugDataItemType.STRING,
"value": "RigidDynamic",
"doc": "The type of physics body"
},
"Is sleeping": {
"type": DebugDataItemType.BOOL,
"value": False,
"doc": "Whether the body is currently sleeping"
}
}
C++ Example - Implementing InteractionFns#
#include <omni/physics/simulation/simulator/Interaction.h>
#include <carb/dictionary/DictionaryUtils.h>
using namespace omni::physics;
class MyInteractionHandler
{
public:
MyInteractionHandler()
{
m_interactionFns.disableResetOnStop = [this](bool disable) {
m_resetOnStopDisabled = disable;
};
m_interactionFns.isDisabledResetOnStop = [this]() -> bool {
return m_resetOnStopDisabled;
};
m_interactionFns.handleRaycast =
[this](const float* origin, const float* direction, bool input)
{
if (origin && direction)
{
carb::Float3 orig = { origin[0], origin[1], origin[2] };
carb::Float3 dir = { direction[0], direction[1], direction[2] };
if (input)
{
// Mouse down - start picking
startPicking(orig, dir);
}
else
{
// Mouse up - end picking
endPicking();
}
}
};
m_interactionFns.getPrimDebugData = [this](const char* primPath) -> carb::dictionary::Item*
{
carb::dictionary::IDictionary* dict = carb::getCachedInterface<carb::dictionary::IDictionary>();
carb::dictionary::Item* debugData = dict->createItem(nullptr, "",
carb::dictionary::ItemType::eDictionary);
// Get physics object for this prim (adjust this to your own implementation)
auto* body = getPhysicsBody(primPath);
if (!body)
return debugData;
// Add position (3D point) (adjust this to your own implementation)
auto pos = body->getPosition();
{
carb::dictionary::Item* item = dict->makeDictionaryAtPath(debugData, "Position");
dict->makeAtPath(item, "type", static_cast<int>(DebugDataItemType::ePoint));
dict->makeAtPath(item, "doc", "The position of the body in world space");
dict->makeAtPath(item, "value", carb::Double3{pos.x, pos.y, pos.z});
}
return debugData;
};
}
InteractionFns getInteractionFns() const { return m_interactionFns; }
private:
InteractionFns m_interactionFns;
bool m_resetOnStopDisabled = false;
void startPicking(const carb::Float3& origin, const carb::Float3& dir) { /* ... */ }
void endPicking() { /* ... */ }
};
Python Example - Implementing InteractionFns#
import carb
from omni.physics.core import (
Simulation,
get_physics_interaction_interface,
get_physics_interface,
)
class MyInteractionHandler:
"""Handle user interactions"""
def __init__(self):
self.reset_on_stop_disabled = False
self.picked_body = None
def disable_reset_on_stop(self, disable):
self.reset_on_stop_disabled = disable
def is_disabled_reset_on_stop(self):
return self.reset_on_stop_disabled
def handle_raycast(self, origin, direction, has_input):
"""Handle picking raycast"""
if has_input:
# Mouse down - start picking
self._start_picking(origin, direction)
else:
# Mouse up - release
self._end_picking()
def _start_picking(self, origin, direction):
# Perform raycast and grab nearest dynamic body
pass
def _end_picking(self):
self.picked_body = None
def get_prim_debug_data(self, prim_path: str) -> dict[str, dict[str, Any]]:
return {
"Position": {
"type": DebugDataItemType.POINT,
"value": (0.0, 5.0, 0.0),
"doc": "The position of the body in world space"
},
}
# Wire up to simulation
handler = MyInteractionHandler()
simulation = Simulation()
simulation.interaction_fns.disable_reset_on_stop = handler.disable_reset_on_stop
simulation.interaction_fns.is_disabled_reset_on_stop = handler.is_disabled_reset_on_stop
simulation.interaction_fns.handle_raycast = handler.handle_raycast
simulation.interaction_fns.get_prim_debug_data = handler.get_prim_debug_data
# Usage through interface
physics = get_physics_interface()
simulation_id = physics.register_simulation(simulation, "InteractionDemo")
interaction = get_physics_interaction_interface()
# Control reset behavior
interaction.disable_reset_on_stop(True) # Keep simulation state on stop
is_disabled = interaction.is_disabled_reset_on_stop(simulation_id)
# Manual raycast for picking
origin = carb.Float3(0.0, 10.0, 0.0)
direction = carb.Float3(0.0, -1.0, 0.0)
interaction.handle_raycast(origin, direction, True) # Start picking
from omni.physics.core import (
get_physics_interaction_interface,
DebugDataItemType,
)
# Get the interaction interface
interaction = get_physics_interaction_interface()
# Query debug data for a prim
prim_path = "/World/Cube"
debug_data = interaction.get_prim_debug_data(prim_path)
if debug_data:
# Iterate through all debug data items
for key, item in debug_data.items():
item_type = item['type']
item_value = item['value']
item_doc = item['doc']
print(f"{key}: {item_value}")
print(f" Type: {item_type}")
print(f" Description: {item_doc}")
Performance Profiling Functions#
The BenchmarkFns structure provides performance profiling and statistics. This structure is defined in the IPhysicsBenchmarks interface.
Key Functions#
subscribeProfileStatsEvents- Subscribe to receive profiling statisticsunsubscribeProfileStatsEvents- Unsubscribe from profiling events
C++ Example - Implementing BenchmarkFns#
#include <omni/physics/simulation/simulator/Benchmark.h>
using namespace omni::physics;
class MyBenchmarkProvider
{
public:
MyBenchmarkProvider()
{
m_benchmarkFns.subscribeProfileStatsEvents =
[this](ProfileStatsNotificationFn callback) -> SubscriptionId
{
m_statsCallbacks.push_back(callback);
return m_statsCallbacks.size();
};
m_benchmarkFns.unsubscribeProfileStatsEvents =
[this](SubscriptionId id)
{
if (id > 0 && id <= m_statsCallbacks.size())
{
m_statsCallbacks.erase(m_statsCallbacks.begin() + id - 1);
}
};
}
// Call this after each simulation step to report stats
void reportStats()
{
std::vector<PhysicsProfileStats> stats;
PhysicsProfileStats simStats;
simStats.zoneName = "Simulation";
simStats.ms = m_simulationTime;
stats.push_back(simStats);
PhysicsProfileStats collStats;
collStats.zoneName = "Collision Detection";
collStats.ms = m_collisionTime;
stats.push_back(collStats);
// Notify all subscribers
for (auto& callback : m_statsCallbacks)
{
callback(stats);
}
}
BenchmarkFns getBenchmarkFns() const { return m_benchmarkFns; }
private:
BenchmarkFns m_benchmarkFns;
std::vector<ProfileStatsNotificationFn> m_statsCallbacks;
float m_simulationTime = 0.0f;
float m_collisionTime = 0.0f;
};
Python Example - Using Benchmark Interface#
from omni.physics.core import (
Simulation,
PhysicsProfileStats,
get_physics_interface,
get_physics_benchmarks_interface,
k_invalid_subscription_id,
)
class MyBenchmarkProvider:
"""Provide profiling statistics"""
def __init__(self):
self.callbacks = []
self.simulation_time = 0.0
self.collision_time = 0.0
def subscribe_profile_stats_events(self, callback):
self.callbacks.append(callback)
return len(self.callbacks)
def unsubscribe_profile_stats_events(self, subscription_id):
if 0 < subscription_id <= len(self.callbacks):
self.callbacks.pop(subscription_id - 1)
def report_stats(self):
"""Call after each simulation step"""
stats = []
sim_stat = PhysicsProfileStats()
sim_stat.zone_name = "Simulation"
sim_stat.ms = self.simulation_time
stats.append(sim_stat)
coll_stat = PhysicsProfileStats()
coll_stat.zone_name = "Collision Detection"
coll_stat.ms = self.collision_time
stats.append(coll_stat)
for callback in self.callbacks:
callback(stats)
# Wire up to simulation
benchmark_provider = MyBenchmarkProvider()
simulation = Simulation()
simulation.benchmark_fns.subscribe_profile_stats_events = (
benchmark_provider.subscribe_profile_stats_events
)
simulation.benchmark_fns.unsubscribe_profile_stats_events = (
benchmark_provider.unsubscribe_profile_stats_events
)
# Register and use
physics = get_physics_interface()
simulation_id = physics.register_simulation(simulation, "BenchmarkDemo")
benchmarks = get_physics_benchmarks_interface()
def on_profile_stats(stats):
for stat in stats:
print(f"{stat.zone_name}: {stat.ms:.2f}ms")
subscription = benchmarks.subscribe_profile_stats_events(on_profile_stats)
# Later, unsubscribe
subscription = None # or call unsubscribe explicitly
Complete Example - Full Physics Engine Integration#
Here’s a complete example showing how to integrate a custom physics engine:
#include <omni/physics/simulation/IPhysics.h>
#include <omni/physics/simulation/simulator/Simulator.h>
using namespace omni::physics;
class MyCompletePhysicsEngine
{
public:
MyCompletePhysicsEngine(IPhysics* physics)
: m_physics(physics)
{
setupSimulationFns();
setupStageUpdateFns();
setupSceneQueryFns();
setupInteractionFns();
setupBenchmarkFns();
}
SimulationId registerWithPhysics(const char* name)
{
Simulation sim;
sim.simulationFns = m_simulationFns;
sim.stageUpdateFns = m_stageUpdateFns;
sim.sceneQueryFns = m_sceneQueryFns;
sim.interactionFns = m_interactionFns;
sim.benchmarkFns = m_benchmarkFns;
m_simId = m_physics->registerSimulation(sim, name);
return m_simId;
}
void unregister()
{
if (m_simId != kInvalidSimulationId)
{
m_physics->unregisterSimulation(m_simId);
m_simId = kInvalidSimulationId;
}
}
private:
void setupSimulationFns()
{
m_simulationFns.initialize = [this](long stageId) { return true; };
m_simulationFns.close = [this]() {};
m_simulationFns.getAttachedStage = [this]() { return m_stageId; };
m_simulationFns.simulate = [this](float dt, float t) { step(dt); };
m_simulationFns.fetchResults = [this]() { writeResults(); };
m_simulationFns.checkResults = [this]() { return true; };
// ... configure other functions
}
void setupStageUpdateFns()
{
m_stageUpdateFns.onAttach = [this](long stageId) { m_stageId = stageId; };
m_stageUpdateFns.onDetach = [this]() { m_stageId = 0; };
m_stageUpdateFns.onUpdate = [this](float t, float dt, bool enable) {};
m_stageUpdateFns.onResume = [this](float t) {};
m_stageUpdateFns.onPause = [this]() {};
m_stageUpdateFns.onReset = [this]() {};
// ... configure other functions
}
void setupSceneQueryFns()
{
m_sceneQueryFns.raycastClosest = [this](const carb::Float3& o,
const carb::Float3& d, float dist, RaycastHit& hit, bool both) {
return performRaycast(o, d, dist, hit);
};
// ... configure other functions
}
void setupInteractionFns()
{
m_interactionFns.disableResetOnStop = [this](bool d) { m_noReset = d; };
m_interactionFns.isDisabledResetOnStop = [this]() { return m_noReset; };
m_interactionFns.handleRaycast = [this](const float* o, const float* d, bool i) {};
}
void setupBenchmarkFns()
{
m_benchmarkFns.subscribeProfileStatsEvents =
[this](ProfileStatsNotificationFn fn) { return SubscriptionId(1); };
m_benchmarkFns.unsubscribeProfileStatsEvents =
[this](SubscriptionId id) {};
}
void step(float dt) { /* Physics stepping */ }
void writeResults() { /* Write back to USD */ }
bool performRaycast(const carb::Float3& o, const carb::Float3& d,
float dist, RaycastHit& hit) { return false; }
IPhysics* m_physics;
SimulationId m_simId = kInvalidSimulationId;
long m_stageId = 0;
bool m_noReset = false;
SimulationFns m_simulationFns;
StageUpdateFns m_stageUpdateFns;
SceneQueryFns m_sceneQueryFns;
InteractionFns m_interactionFns;
BenchmarkFns m_benchmarkFns;
};
Best Practices#
Always unregister simulations when shutting down to avoid dangling references.
Check for null function pointers - not all simulations need to implement all functions.
Use appropriate activation states - deactivate simulations that shouldn’t receive updates.
Thread safety - callbacks may be invoked from different threads; ensure thread-safe access to shared state.
Performance - keep callback implementations lightweight; heavy processing should be done asynchronously.
Scene queries - remember that scene queries only work after simulation has started.
Contact callbacks - contact data is only valid for one simulation step; data must be copied if persistence is needed.