Physics¶
Snowpulse physics uses Box2D when available and exposes two gameplay components:
RigidBodyPhysicsCollider
If Box2D is unavailable or disabled, physics calls are safe no-ops.
To put a simulated object on screen first, start with Quick Start. World scaling and build configuration are covered after the basic component model.
World Scale Factor¶
Use AppContext::physicsScaleFactor(float) to set global world scaling for physics.
- Default:
100.0f - Minimum: clamped to a small positive value (
0.0001f) - Mapping:
- game -> physics (spatial only):
physics = game / scale - physics -> game (spatial only):
game = physics * scale - Runtime behavior: changing scale while a scene is active immediately rebuilds the Box2D world from current scene objects/transforms.
physicsScaleFactor only affects spatial values:
- Node/body positions synced into Box2D
- Collider geometry (
size,radius,offset) synced into Box2D - Simulated positions written back to node transforms
Dynamics values stay raw Box2D units (not auto-rescaled):
- World gravity
setLinearVelocity/getLinearVelocityapplyForce/applyImpulse- Collision
approachSpeed
Build Toggle¶
Physics integration is controlled at configure time:
- CMake option:
SNOWPULSE_ENABLE_BOX2D(ONby default) - Compile definition:
SNOWPULSE_HAS_BOX2D(1when Box2D is linked, otherwise0)
See Dependencies for build commands.
Quick Start¶
- Create a node and add it under the scene root (for
Scenesubclasses this isaddChild(node)). - Add any visual component (
SpriteRenderer,QuadRenderer, etc.). - Add
RigidBodyif the node should have dynamic/kinematic/static body behavior. - Add
PhysicsColliderto define collision shape/material/filtering.
auto* boxNode = createNode("box");
boxNode->transform().position = { 70.0f, 160.0f, 0.0f };
auto* quad = boxNode->addComponent<snowpulse::QuadRenderer>();
quad->size = { 52.0f, 52.0f };
quad->color = { 0.96f, 0.62f, 0.16f, 1.0f };
auto* body = boxNode->addComponent<snowpulse::RigidBody>();
body->bodyType = snowpulse::RigidBody::BodyType::Dynamic;
body->gravityScale = 120.0f;
body->linearDamping = 0.03f;
body->angularDamping = 0.05f;
body->isBullet = true;
auto* collider = boxNode->addComponent<snowpulse::PhysicsCollider>();
collider->shape = snowpulse::PhysicsCollider::Shape::Box;
collider->size = { 52.0f, 52.0f };
collider->density = 1.0f;
collider->friction = 0.35f;
collider->restitution = 0.82f;
addChild(boxNode);
RigidBody¶
Key fields:
bodyType:Static,Kinematic,DynamicgravityScale,linearDamping,angularDampingfixedRotation,isBulletlinearVelocity,angularVelocity
Key methods:
setLinearVelocity,getLinearVelocitysetAngularVelocity,getAngularVelocityapplyForceapplyImpulse
Collision callbacks:
onCollisionEnter(...)onCollisionExit(...)onCollisionHit(...)
Callback payload (RigidBody::CollisionInfo) includes:
otherNodeotherRigidBodyselfCollider,otherCollidernormalapproachSpeed(for hit events)
PhysicsCollider¶
Key fields:
shape:BoxorCirclesize(box),radius(circle)offsetdensity,friction,restitutionisSensor(overlap-only trigger; no physical collision response)categoryBits,maskBits,groupIndex(collision filtering)
Density notes:
- Engine accepts
density >= 0(negative values are clamped to0). - There is no hard upper clamp in Snowpulse; practical values depend on your chosen
physicsScaleFactorand collider sizes.
Notes:
- Colliders can exist without
RigidBody. isSensor = trueturns a collider into a trigger volume. UseonCollisionEnter/onCollisionExitto track overlap timing.- For collider-only nodes, runtime creates an internal proxy body so transforms still participate in collisions.
- Physics derives position and rotation from the node's world pose, so colliders and rigid bodies can live on child nodes under transformed parents.
- When Box2D updates a child body, Snowpulse writes the simulated world pose back into the node's local transform so rendering stays aligned with hierarchy-local authoring.
- Physics ignores node and parent scale. Collider
size,radius, andoffsetstay in authored physics units. - Collision callbacks are dispatched on
RigidBody, not onPhysicsCollider. onCollisionHitis only for non-sensor contact impacts.
Collision Filtering¶
Use filtering fields to control who collides with whom:
categoryBits: what this collider ismaskBits: what categories it collides withgroupIndex: force always-collide or never-collide behavior for same-group bodies
Example: disable all collisions for one collider:
Runtime Behavior¶
- Physics steps at fixed
1/60with substeps for stability. - Render interpolation is enabled by default through
AppContext::physicsInterpolation(true). - Scene transform and physics body are synchronized by the runtime.
- Dynamic bodies update node transforms from simulation.
- Static/kinematic (and manually moved bodies) push transform changes back to Box2D.
- Parent translation and Z rotation are included when syncing child physics nodes to Box2D.
- Disabling a
RigidBodyor its owner node pauses simulation; re-enabling it restores the latest cached linear and angular velocity, including commands issued while inactive. - Physics remains 2D only: only
position.x/yandrotation.zparticipate.
Render Interpolation¶
After fixed stepping, Snowpulse interpolates active dynamic bodies between their previous and current simulated world poses. The interpolated position and shortest-arc Z rotation are stored as render-only transform overrides; physics queries and gameplay transforms still use the current simulation state.
Disable interpolation when exact fixed-step presentation is preferable:
Disabling it clears render overrides before drawing, so dynamic bodies render at their latest simulated pose. Static and kinematic bodies are never interpolated.
Practical Tips¶
- In pixel-scale games, tune
gravityScale,size, andimpulsetogether. - Use
setLinearVelocity/setAngularVelocity(or impulses/forces) during gameplay rather than mutating only local cached fields. - Ensure both component
isActiveand owner nodeisActiveare true for simulation/collision participation. - Parent and child scale do not resize or reposition physics shapes; if you need a larger collider, change the collider data directly.