Table of Contents

Shapes

A Shape is collision geometry attached to a body. A body can carry as many as it needs, and each one has its own material, filter and events.

Body body = world.CreateDynamicBody(spawn);

body.AddSphere(new Sphere(0.5f));
body.AddBox(Box.Cube(0.5f));
body.AddCapsule(Capsule.Upright(height: 1.8f, radius: 0.3f));

The geometry kinds

Geometry Built with Copied on attach Body types Dispose
Sphere value yes any
Capsule value yes any
Box value yes any
ConvexHull FromPoints, Cylinder, Cone, Rock yes, interned in the world any any time
CollisionMesh FromTriangles, Grid, Wave no, borrowed static only after the world
HeightField FromHeights, Grid, Wave no, borrowed static only after the world
CompoundGeometry CompoundBuilder no, borrowed static only after the world

The first four are the ones to reach for on anything that moves. The last three are covered in Terrain and meshes, and their lifetime rules in Memory and ownership.

Primitives

The three value types have constructors and a few factories for the shapes people actually build:

new Sphere(0.5f);                             // centred on the body origin
new Sphere(offset, 0.5f);

Box.Cube(0.5f);                               // half-extent, so a 1m cube
Box.FromSize(new Vector3(2.0f, 0.1f, 2.0f));  // full size instead
new Box(halfExtents, centre);

Capsule.Upright(height: 1.8f, radius: 0.3f);  // a character
new Capsule(start, end, radius);              // any orientation

Box is described by half-extents, which is a common source of a level that comes out twice the intended size. FromSize takes the measurement you would read off a model.

Convex hulls

A hull is how a moving body gets a shape that is not a primitive: meshes and height fields are static only, so a rock, a wheel or a crate with a chamfered edge is a hull.

using ConvexHull rock = ConvexHull.Rock(0.4f);
using ConvexHull wheel = ConvexHull.Cylinder(height: 0.2f, radius: 0.35f);
using ConvexHull custom = ConvexHull.FromPoints(vertices, maxVertexCount: 32);

body.AddHull(rock);

A hull is interned into the world when it is attached, so it may be disposed as soon as the shape exists. That is the one disposable geometry with no ordering rule attached to it.

Density and material

Density gives the body its mass; the material decides how the surface behaves.

body.AddBox(Box.Cube(0.5f), ShapeDefinition.Default with
{
    Density = 500.0f,
    Material = PhysicsMaterial.Default with
    {
        Friction = 0.9f,
        Restitution = 0.4f,
    },
});

PhysicsMaterial carries five things:

Friction Resistance to sliding
Restitution Bounciness
RollingResistance Resistance to rolling, which is what stops a ball rolling forever
SurfaceVelocity A surface that moves without the body moving — a conveyor
UserMaterialId Your own identifier, handed back by ray casts and hit events

UserMaterialId is the way to answer "what did this hit sound like": tag the ice and the gravel when you build them, then read RaycastHit.UserMaterialId or the UserMaterialIdA and UserMaterialIdB on a hit event.

Density can be changed after the fact, and a shape can be told not to recompute the body's mass while several change at once:

shape.SetDensity(200.0f, updateBodyMass: false);
// ... more changes ...
body.RecomputeMass();

Several shapes on one body

Attaching more than one shape is a run-time compound, and it works on any body type. This is how you build an L-shaped piece, a character with a separate head volume, or a vehicle chassis with a bumper.

Body character = world.CreateDynamicBody(spawn);

Shape torso = character.AddCapsule(Capsule.Upright(1.4f, 0.3f));
Shape head = character.AddSphere(new Sphere(new Vector3(0.0f, 1.5f, 0.0f), 0.2f));

torso.UserData = (ulong)HitZone.Torso;
head.UserData = (ulong)HitZone.Head;

Each shape keeps its own identifier, filter, material and events, which is what lets a hit be attributed to a part rather than to the whole object. The cost is one broad-phase proxy per shape.

When the geometry is large, static and never needs to be told apart — a colonnade, a rock field — bake it into a single CompoundGeometry instead and pay for one proxy.

Sensors

A sensor reports overlaps and never pushes anything:

Body trigger = world.CreateStaticBody(doorway);

trigger.AddBox(new Box(new Vector3(1.0f, 1.0f, 0.2f)), ShapeDefinition.Default with
{
    IsSensor = true,
    EnableSensorEvents = true,
    Density = 0.0f,          // a sensor still weighs something otherwise
});

The visitor needs EnableSensorEvents too. See Events.

Changing a shape later

shape.Friction = 0.2f;
shape.Restitution = 0.6f;
shape.SetFilter(newFilter);              // recomputes contacts by default
shape.SetDensity(300.0f);
shape.Destroy();                         // updates the body's mass by default

ApplyWind is there too, for shapes that should be pushed by air rather than by contacts.