Physics component to an entity and the framework handles simulation automatically. This is the recommended approach for most use cases.ScenePhysicsObject and ScenePhysicsConstraint directly for advanced scenarios like compound collision shapes or runtime force application.PhysicsFeature to your Activity’s feature list. This loads the native physics engine and registers the systems that run the simulation.class MyActivity : AppSystemActivity() {
override fun registerFeatures(): List<SpatialFeature> {
return listOf(
PhysicsFeature(spatial),
VRFeature(this),
)
}
}
PhysicsFeature accepts optional parameters to configure the physics world:PhysicsFeature(
spatial = spatial,
useGrabbablePhysics = true,
worldBounds = PhysicsWorldBounds(minY = -10f),
gravity = Vector3(0f, -10f, 0f)
)
| Parameter | Default | Description |
|---|---|---|
useGrabbablePhysics | true | Automatically switch grabbed objects to KINEMATIC and restore on release |
worldBounds | null | Destroy entities that leave these bounds |
gravity | (0, -10, 0) | Gravity vector in m/s² |
Physics component to make an entity participate in the simulation. Here’s a bouncy ball that falls under gravity:val ball = Entity.create(
Mesh(Uri.parse("mesh://sphere")),
Transform(Pose(Vector3(0f, 2f, -1f))),
Physics(
shape = "sphere",
state = PhysicsState.DYNAMIC,
dimensions = Vector3(0.1f),
density = 1.0f,
restitution = 0.8f
)
)
val floor = Entity.create(
Mesh(Uri.parse("mesh://box")),
Transform(Pose(Vector3(0f, 0f, -1f))),
Physics(
shape = "box",
state = PhysicsState.STATIC,
dimensions = Vector3(5f, 0.05f, 5f)
)
)
| State | Moves? | Affected by forces? | Use for |
|---|---|---|---|
STATIC | No | No | Walls, floors, terrain |
DYNAMIC | Yes | Yes | Balls, crates, debris |
KINEMATIC | Programmatically | No | Moving platforms, elevators |
val physics = entity.getComponent<Physics>() physics.state = PhysicsState.KINEMATIC entity.setComponent(physics)
useGrabbablePhysics is enabled (the default), the SDK handles this transition automatically for entities that have both Grabbable and Physics components. The object switches to KINEMATIC when grabbed and restores its original state on release.shape parameter determines what collision geometry is used for the entity.| Shape | Value | Size parameter |
|---|---|---|
Box | "box" | dimensions = half-extents (width, height, depth) |
Sphere | "sphere" | dimensions.x = radius |
Cylinder | "cylinderX", "cylinderY", "cylinderZ" | dimensions.x = radius, dimensions.y = height |
shape to a glTF file for collision geometry derived from a 3D model:Physics(
shape = "table.glb",
collisionShapeType = CollisionShapeType.CONVEX_HULL,
state = PhysicsState.DYNAMIC
)
| CollisionShapeType | Best for | Notes |
|---|---|---|
PRIMITIVE | Simple shapes (box, sphere) | Default. Fastest option |
TRIANGLE_MESH | Exact collision with environment geometry | STATIC only |
CONVEX_HULL | Approximate collision for moving objects | Fast, works for DYNAMIC |
CONVEX_DECOMPOSITION | Concave shapes (bowls, L-shapes) | Generates multiple convex hulls. Slower to create |
COMPOUND | Multi-part shapes | Built from multiple primitives |
TRIANGLE_MESH only works with STATIC objects. If you need accurate collision on a moving object, use CONVEX_HULL or CONVEX_DECOMPOSITION.Physics(
shape = "https://example.com/models/rock.glb",
collisionShapeType = CollisionShapeType.CONVEX_HULL,
state = PhysicsState.DYNAMIC
)
Physics(
shape = "sphere",
state = PhysicsState.DYNAMIC,
density = 1.0f,
restitution = 0.8f,
friction = FrictionObject(
sliding = 0.5f,
rolling = 0.1f,
spinning = 0.0f
)
)
| Property | Range | Description |
|---|---|---|
density | > 0 | Mass density in kg/m³. Mass = density × volume |
restitution | 0.0 – 1.0 | Bounciness. 0 = no bounce, 1 = perfect bounce |
friction.sliding | ≥ 0 | Resistance to sliding motion |
friction.rolling | ≥ 0 | Resistance to rolling motion |
friction.spinning | ≥ 0 | Resistance to spinning in place |
val crate = Physics(shape = "box", state = PhysicsState.DYNAMIC)
.applyMaterial(PhysicsMaterial.WOOD)
// Launch a ball upward and forward
Physics(
shape = "sphere",
state = PhysicsState.DYNAMIC,
linearVelocity = Vector3(0f, 3f, -5f),
angularVelocity = Vector3(10f, 0f, 0f)
)
val physics = entity.getComponent<Physics>() physics.applyForce = Vector3(0f, 0f, -10f) entity.setComponent(physics)
ball.registerEventListener<PhysicsCollisionCallbackEventArgs>("physicscollision")
{ entity, args ->
val other = args.collidedEntity
val position = args.collisionPosition
val normal = args.collisionNormal
val impulse = args.impulse
if (impulse > 5f) {
playImpactSound(position)
}
}
| Property | Type | Description |
|---|---|---|
collidedEntity | Entity | The other entity involved in the collision |
collisionPosition | Vector3 | World-space contact point |
collisionNormal | Vector3 | Surface normal at the contact |
impulse | Float | Strength of the impact |
Entity.create(
HingeConstraint(
bodyA = doorFrame,
bodyB = door,
anchorA = Vector3(0.5f, 0f, 0f),
anchorB = Vector3(-0.5f, 0f, 0f),
axis = Vector3(0f, 1f, 0f),
lowerLimit = 0f,
upperLimit = 1.57f
)
)
HingeConstraint(
bodyA = motor,
bodyB = wheel,
axis = Vector3(0f, 0f, 1f),
motorEnabled = true,
motorTargetVelocity = 3.14f,
motorMaxForce = 50f
)
Entity.create(
BallSocketConstraint(
bodyA = ceiling,
bodyB = pendulum,
anchorA = Vector3(0f, -0.1f, 0f),
anchorB = Vector3(0f, 0.5f, 0f)
)
)
Entity.create(
FixedConstraint(
bodyA = vehicle,
bodyB = antenna
)
)
Entity.create(
SliderConstraint(
bodyA = cabinet,
bodyB = drawer,
axis = Vector3(0f, 0f, 1f),
lowerLimit = 0f,
upperLimit = 0.4f
)
)
Entity.create(
SpringConstraint(
bodyA = anchor,
bodyB = weight,
anchorA = Vector3(0f, 0f, 0f),
anchorB = Vector3(0f, 0f, 0f),
stiffness = 100f,
damping = 1f,
restLength = 0.5f
)
)
Entity.create(
ConeTwistConstraint(
bodyA = upperArm,
bodyB = forearm,
anchorA = Vector3(0f, -0.15f, 0f),
anchorB = Vector3(0f, 0.15f, 0f),
twistAxis = Vector3(0f, 1f, 0f),
swingSpan1 = 1.2f,
swingSpan2 = 0.8f,
twistSpan = 0.5f
)
)
breakForce and optionally breakTorque, then listen for the break event:val shelf = Entity.create(
HingeConstraint(
bodyA = wall,
bodyB = shelfBoard,
axis = Vector3(0f, 0f, 1f),
breakForce = 100f,
breakTorque = 50f
)
)
shelf.registerEventListener<PhysicsConstraintBreakEventArgs>("physics_constraint_break")
{ entity, args ->
spawnDebrisAt(entity)
}
| Attribute | Type | Description |
|---|---|---|
bodyA | Entity | First connected body |
bodyB | Entity | Second connected body |
anchorA | Vector3 | Connection point on body A (local space) |
anchorB | Vector3 | Connection point on body B (local space) |
breakForce | Float | Force threshold to break (0 = unbreakable) |
breakTorque | Float | Torque threshold to break |
isBroken | Boolean | Read-only. Whether the constraint has broken |
PlayerAvatarPhysics component creates kinematic colliders that follow the player’s head and hands, allowing the player to push dynamic objects in the scene.Entity.create(
PlayerAvatarPhysics(
bodyEnabled = true,
bodyRadius = 0.25f,
handsEnabled = true,
handRadius = 0.08f
)
)
KINEMATIC — they push dynamic objects but aren’t pushed back.| Attribute | Default | Description |
|---|---|---|
bodyEnabled | true | Enable the body capsule collider |
bodyRadius | 0.25f | Capsule radius in meters |
handsEnabled | false | Enable hand sphere colliders |
handRadius | 0.08f | Hand sphere radius in meters |
friction | 0.5f | Friction for all avatar colliders |
restitution | 0.0f | Bounciness (0 = no bounce off the player) |
useGrabbablePhysics is enabled (the default), entities with both Grabbable and Physics components get automatic state transitions:KINEMATIC (follows hand)DYNAMIC)Entity.create(
Mesh(Uri.parse("ball.glb")),
Transform(Pose(Vector3(0f, 1f, -1f))),
Grabbable(),
Physics(
shape = "sphere",
state = PhysicsState.DYNAMIC,
restitution = 0.6f
)
)
val physicsFeature = featureManager.findFeature<PhysicsFeature>() // Moon gravity physicsFeature.setGravity(Vector3(0f, -1.6f, 0f)) // Zero gravity physicsFeature.setGravity(Vector3(0f, 0f, 0f))
PhysicsFeature(
spatial = spatial,
worldBounds = PhysicsWorldBounds(
minY = -20f,
minX = -50f, maxX = 50f,
minZ = -50f, maxZ = 50f
)
)
val physicsFeature = featureManager.findFeature<PhysicsFeature>() physicsFeature.enablePhysicsDebugLines(true)
ScenePhysicsCollider is the low-level API for creating collision shapes directly. The Physics component uses colliders internally, but you can create and manage them yourself for more control — shared shapes, capsules, or multi-part compound geometry.| Factory method | Shape | Parameters |
|---|---|---|
createBox | Box | width, height, depth (half-extents) |
createSphere | Sphere | radius |
createCylinderX/Y/Z | Cylinder | radius, halfHeight (aligned to X, Y, or Z axis) |
createCapsuleX/Y/Z | Capsule | radius, halfHeight (aligned to X, Y, or Z axis) |
createConvexHull | Convex hull from glTF | filename |
createGLTF | Triangle mesh from glTF | filename |
createCompound | Empty compound (add children) | — |
createCapsuleX/Y/Z) are only available through the collider API. The Physics component does not expose capsule shapes directly.Scene reference:val scene = getScene() val boxCollider = ScenePhysicsCollider.createBox(scene, 1f, 0.5f, 1f) val sphereCollider = ScenePhysicsCollider.createSphere(scene, 0.3f) val capsuleCollider = ScenePhysicsCollider.createCapsuleY(scene, 0.1f, 0.4f)
val scene = getScene()
val bulletShape = ScenePhysicsCollider.createSphere(scene, 0.02f)
for (entity in bulletEntities) {
ScenePhysicsObject.createFromCollider(scene, entity, bulletShape, 0.01f)
}
val scene = getScene() val compound = ScenePhysicsCollider.createCompound(scene) // T-shape: horizontal bar + vertical bar compound.addChildBox(0.5f, 0.05f, 0.05f, Pose()) // horizontal compound.addChildBox(0.05f, 0.3f, 0.05f, Pose(Vector3(0f, -0.3f, 0f))) // vertical compound.addChildSphere(0.08f, Vector3(0f, -0.6f, 0f)) // knob val physicsObject = ScenePhysicsObject.createFromCollider(scene, entity, compound, 2f)
| Method | Parameters |
|---|---|
addChildBox | halfW, halfH, halfD, localPose |
addChildSphere | radius, localPos |
addChildCylinder | radius, halfHeight, localPose |
PhysicsCreationSystem so they work with the Physics component:val physicsCreationSystem = systemManager.findSystem<PhysicsCreationSystem>()
physicsCreationSystem.physicsCreators["physics://dumbbell"] = { entity ->
val scene = getScene()
val compound = ScenePhysicsCollider.createCompound(scene)
compound.addChildSphere(0.15f, Vector3(-0.4f, 0f, 0f))
compound.addChildBox(0.4f, 0.04f, 0.04f, Pose())
compound.addChildSphere(0.15f, Vector3(0.4f, 0f, 0f))
val physics = entity.getComponent<Physics>()
val mass = if (physics.state == PhysicsState.DYNAMIC) physics.densityInternal else 0f
ScenePhysicsObject.createFromCollider(scene, entity, compound, mass)
}
Entity.create(
Transform(Pose(Vector3(0f, 1f, -1f))),
Physics(
shape = "physics://dumbbell",
state = PhysicsState.DYNAMIC,
density = 7800f
)
)
destroy():collider.destroy()
ScenePhysicsObject directly through the creation system:val physicsSystem = systemManager.findSystem<PhysicsCreationSystem>()
val physicsObject = physicsSystem.getPhysicsObject(entity)
// Apply an impulse force
physicsObject?.applyForce(Vector3(0f, 500f, 0f))
// Apply force at a specific point on the body (relative to center of mass)
physicsObject?.applyForceAtRelativePosition(
Vector3(0f, 0f, -10f), // force direction
Vector3(0.5f, 0f, 0f) // offset from center
)
// Apply torque
physicsObject?.applyTorque(0f, 10f, 0f)
// Teleport to a new position
physicsObject?.setPose(Pose(Vector3(0f, 5f, 0f)))
// Read current position
val currentPose = physicsObject?.getPose()
// Set velocity directly
physicsObject?.setLinearVelocity(Vector3(0f, 5f, 0f))
physicsObject?.setAngularVelocity(Vector3(0f, 3.14f, 0f))
// Adjust damping (controls how quickly motion decays)
physicsObject?.setDamping(linearDamping = 0.1f, angularDamping = 0.95f)
// Disable gravity for this object only
physicsObject?.setGravityEnabled(false)
// Change body type at runtime
physicsObject?.setBodyType(PhysicsState.KINEMATIC, 0f)
// Tune sleeping behavior
physicsObject?.setDeactivationTime(2f)
physicsObject?.setSleepingThresholds(linear = 0.8f, angular = 1f)
ScenePhysicsObject method reference:| Method | Description |
|---|---|
applyForce(force) | Apply a force at center of mass |
applyForceAtRelativePosition(force, offset) | Apply a force at an offset from center of mass |
applyTorque(x, y, z) | Apply rotational force |
setPose(pose) | Teleport to a new position and rotation |
getPose() | Read current world-space position and rotation |
setLinearVelocity(velocity) | Set linear velocity directly |
setAngularVelocity(velocity) | Set angular velocity directly |
setRestitution(value) | Change bounciness |
setFriction(sliding, rolling, spinning) | Change friction values |
setDamping(linear, angular) | Control motion decay |
setGravityEnabled(enabled) | Toggle gravity for this body |
setBodyType(state, mass) | Switch between STATIC, DYNAMIC, KINEMATIC |
setDeactivationTime(seconds) | How long until the body sleeps |
setSleepingThresholds(linear, angular) | Velocity thresholds for sleep |
destroy() | Release native resources |
Physics component and its systems handle most use cases. When you need full control — for example, a projectile system with custom velocity logic, or physics objects that don’t use the standard component — you can build your own system using ScenePhysicsObject and ScenePhysicsCollider directly.PhysicsFeature registered. It initializes the native physics engine and runs the simulation tick. Your custom system replaces the Physics component, not the engine itself.<?xml version="1.0" ?>
<ComponentSchema packageName="com.example.physics">
<Component name="Projectile">
<Vector3Attribute
name="initialVelocity"
defaultValue="0.0f, 0.0f, 0.0f"
/>
<FloatAttribute
name="radius"
defaultValue="0.05"
/>
</Component>
</ComponentSchema>
ScenePhysicsObject instances for them, and cleans up when entities are deleted:class ProjectileSystem : SystemBase() {
private val physicsObjects = HashMap<Entity, ScenePhysicsObject>()
override fun execute() {
val q = Query.where { changed(Projectile.id) and has(Transform.id) }
for (entity in q.eval()) {
if (physicsObjects.containsKey(entity)) continue
val projectile = entity.getComponent<Projectile>()
val scene = getScene()
// Create a sphere collider and physics body
val collider = ScenePhysicsCollider.createSphere(scene, projectile.radius)
val physicsObject = ScenePhysicsObject.createFromCollider(
scene, entity, collider, 0.1f // mass in kg
)
// Set initial transform and velocity
physicsObject.setPose(entity.getComponent<Transform>().transform)
physicsObject.setLinearVelocity(projectile.initialVelocity)
physicsObject.setRestitution(0.3f)
physicsObject.setGravityEnabled(true)
physicsObjects[entity] = physicsObject
}
}
override fun delete(entity: Entity) {
physicsObjects.remove(entity)?.destroy()
}
}
PhysicsFeature. The feature handles the simulation tick; your system handles creation:class MyActivity : AppSystemActivity() {
override fun registerFeatures(): List<SpatialFeature> {
return listOf(
PhysicsFeature(spatial),
VRFeature(this),
)
}
override fun registerSystems(): List<SystemBase> {
return listOf(
ProjectileSystem(),
)
}
override fun registerComponents(): List<ComponentRegistration> {
return listOf(
ComponentRegistration(Projectile.Companion),
)
}
}
Physics:Entity.create(
Mesh(Uri.parse("mesh://sphere")),
Transform(Pose(Vector3(0f, 1.5f, -0.5f))),
Projectile(
initialVelocity = Vector3(0f, 5f, -10f),
radius = 0.05f
)
)
ScenePhysicsConstraint directly instead of constraint components:val scene = getScene()
val hinge = ScenePhysicsConstraint.createHinge(
scene, entityA, entityB,
anchorA = Vector3(0.5f, 0f, 0f),
anchorB = Vector3(-0.5f, 0f, 0f),
axis = Vector3(0f, 1f, 0f)
)
// Configure after creation
hinge.setBreakingThreshold(force = 200f, torque = 100f)
hinge.setMotor(enabled = true, targetVelocity = 3.14f, maxForce = 50f)
hinge.setLimits(lower = 0f, upper = 1.57f)
// Query state at runtime
val impulse = hinge.getAppliedImpulse()
val broken = hinge.isBroken()
// Disable without destroying
hinge.setEnabled(false)
| Method | Parameters |
|---|---|
createHinge | entityA, entityB, anchorA, anchorB, axis, lowerLimit, upperLimit |
createBallSocket | entityA, entityB, anchorA, anchorB |
createFixed | entityA, entityB, anchorA, anchorB |
createSlider | entityA, entityB, axis, lowerLimit, upperLimit |
createSpring | entityA, entityB, anchorA, anchorB, stiffness, damping, restLength |
createConeTwist | entityA, entityB, anchorA, anchorB, twistAxis, swingSpan1, swingSpan2, twistSpan, softness, biasFactor, relaxationFactor |
CONVEX_HULL or TRIANGLE_MESH only when the shape matters for gameplay.PhysicsWorldBounds to clean them up.ScenePhysicsCollider.createCapsuleY gives better collision than a box for upright figures. This shape is only available through the collider API.ScenePhysicsCollider and pass it to each ScenePhysicsObject.createFromCollider.