Physics

Overview

Chisel uses Jolt Physics for its physics simulation, exposed through WorldEntity’s physics properties. Every entity gets a physics body when it’s spawned, as long as it has a valid bounding box. The body type (dynamic vs kinematic) is determined by the isSimulated flag.

Setting Up a Physics Entity

Configure physics in the WorldEntity constructor before spawning. The most important properties:

isSimulated

True means the physics engine drives the entity. Gravity, collision responses, and other forces all apply. False means it’s kinematic: the entity stays where you put it unless you move it directly.

axisAlignedBox

Locks the physics body to only translate, preventing rotation. Essential for characters and enemies that should stay upright. Physical props that should tumble should have this false.

physicsShape

Controls what collision shape the body uses. EntityPhysicsShapes.Box is a box sized to the bounding box (default). EntityPhysicsShapes.Cylinder is a vertical cylinder with the same dimensions. Cylinders slide around obstacles more smoothly than boxes and are useful for rolling props.

physicsRestitution

Bounciness. 0 is fully inelastic (no bounce), 1 is fully elastic (perfect bounce). Most gameplay objects should use 0 or a small value like 0.1.

physicsDensityMultiplier

Scales the mass calculated from the shape volume. Lighter objects use a multiplier below 1, heavier ones above 1. This affects how much impulse is needed to move the object.

public class HeavyCrate : WorldEntity
{
    public HeavyCrate()
    {
        controller = new HeavyCrateController();
        bounds = new BoundingBox(-Vector3.One * 0.5f, Vector3.One * 0.5f);
        isSimulated = true;
        physicsRestitution = 0.05f;
        physicsDensityMultiplier = 3f;
    }
}

Applying Forces and Impulses

Two ways to push an entity’s physics body:

AddImpulse(Vector3)

Applies an instantaneous velocity change. Best for discrete events like explosions, knockback, or being hit by a projectile. The mass of the object affects how much it moves.

AddForce(Vector3)

Applies a continuous force over the frame. Accumulates over time. Best for things like wind, buoyancy, or a thruster that pushes continuously.

Both have position variants (AddImpulseAtPosition, AddForceAtPosition) that apply the force at a specific point in world space, which causes torque if the entity is not axis-aligned.

// Launch the entity upward and slightly forward on death
entity.AddImpulse(new Vector3(0, 8f, -3f));


// Apply wind force each frame in OnUpdate
entity.AddForce(new Vector3(2f, 0, 0) * MainEngine.PreviousFrameDelta);

Forces and impulses are accumulated during OnUpdate and applied to the physics body at the end of the frame.

Velocity and Position

For simulated entities, reading entity.position and entity.velocity gives you the physics engine’s values from the previous frame. Writing to them during OnUpdate moves the physics body directly but the physics engine will integrate from there, so don’t rely on position writes to keep an entity perfectly in place against physics forces.

To directly set the physics body’s velocity, write to entity.velocity and it will be pushed to the physics body at the end of the frame. The base input SetVelocity that all entities have does exactly this.

Note

If you set entity.velocity each frame in OnUpdate for a simulated entity, the physics engine will apply your value but then add gravity and resolve collisions on top of it. For entities that need full manual control over velocity (like a character controller), you typically set velocity in OnUpdate and also set entity.physicsBody.SetFriction(0) in OnSpawn so the floor doesn’t bleed off horizontal speed.

Stepping

Characters need to step over small ledges and curbs without stopping dead. The engine provides two methods for this that you call manually from OnUpdate:

entity.TryStepUp()

Attempts to step up onto an obstacle. Only fires if the entity is on the ground and blocked horizontally. Returns the step height if a step occurred, 0 otherwise.

entity.TryStepDown()

Snaps the entity down to the floor when it walks off a ledge, preventing floating. Returns the step distance.

The standard pattern from the first entity guide calls them in sequence:

float s = entity.TryStepUp();
if (s <= 0f) entity.TryStepDown();

The step height is a constant (about 0.5 units). Large steps over that height won’t be automatically handled.

Grounded Detection

entity.isOnGround is updated each frame by the engine using physics contact normals. It’s true when the entity is resting on a surface with a normal that isn’t too steep (configurable slope angle in the engine, default 60 degrees). Use it to gate jump logic or landing sounds:

if (entity.isOnGround && !entity.wasOnGround)
{
    // Just landed
    SoundDevice.Device.PlaySound(landingSound, entity.position);
}

entity.wasOnGround holds the previous frame’s value, useful for detecting landing and takeoff transitions. entity.standingOnSurface gives you the material of the surface currently being stood on, which is what the FPS template uses for footstep sounds.

Changing Bounds at Runtime

You can change an entity’s bounds after spawning. The physics body’s shape is updated automatically when you set entity.bounds. This is used for things like crouching (shrinking the bounds) or disabling collision on death (setting bounds to an empty BoundingBox):

// Crouch: shrink the physics body
entity.bounds = crouchBounds;


// Kill physics on death
entity.isSimulated = false;
entity.ignoreCollision = true;
entity.bounds = new BoundingBox();

Note

Setting entity.bounds to a zero-size box disables collision detection entirely. The physics body still exists but won’t interact with anything. This is the standard pattern for dead entities that are ragdolling or just sitting as corpses.

Collision Layers

Every entity has a collisionID (a sbyte, defaulting to 0). You can define rules between layers using Collision.AddRule():

Collision.AddRule(0, 1, Collision.CollideType.Ignore);
// Layer 0 and layer 1 will not collide with each other

The three rule types are Collide, Ignore, and Trigger. Assign a layer to an entity:

entity.collisionID = 1;

Collision layer support is still being developed and may not cover all physics interaction types yet.

Raycasting Against the BSP

To trace a ray against the map geometry (not physics bodies), use BSPRoot.TraceRay():

var hit = BSPRoot.TraceRay(new Ray(origin, direction), maxDistance);
if (hit.hit)
{
    Vector3 hitPoint = hit.point;
    Vector3 hitNormal = hit.normal;
}

To cast against physics bodies (entities), use Collision.CastPhysicsWorld() or Collision.CastEntity():

// Returns the first entity hit
WorldEntity hitEntity = Collision.CastEntity(
    new Ray(origin, direction),
    maxDistance,
    out var hitResult,
    entity.physicsBodyID // ignore self
);

The FPS template’s GameEngine.ShootBullet() combines both: it traces the BSP first, then checks physics entities at shorter range, and handles decals and particle effects automatically.

Sphere Queries

Collision.GetEntitiesInSphere() returns all entities within a radius. This is the efficient way to find nearby enemies, objects to damage from an explosion, or collectibles a player is standing near:

var nearby = Collision.GetEntitiesInSphere(entity.position, 8f);
foreach (var e in nearby)
{
    if (e is Player) { /* do something */ }
}

The query uses the spatial blockmap internally, so it’s fast even with many entities in the world. Don’t iterate EntityManager.entities.GetValues() and distance-check every entity manually when you can use this instead.