Raycast, sweep and overlap
Open the live demo · Read the source · View on GitHub
A CollisionWorld answers three different questions about the same level:
what does a ray meet, how far can a moving shape travel before something
stops it, and what does a shape already overlap. This page asks all three
against one small scene and prints the answers as text, because a raycast is
a number and a picture cannot show you whether it is the right one.
Step 1: A wall and a pickup #
The wall is solid; the pickup is a trigger, which means it reports overlap but blocks nothing.
_world = CollisionWorld();
final Collider wall = _world.add(
Collider(
shape: CollisionBox(Vector3(2.0, 1.0, 0.2)),
position: Vector3(0.0, 1.0, 0.0),
),
);
_pickup = _world.add(
Collider(
shape: CollisionSphere(0.4),
position: Vector3(2.0, 0.5, 2.0),
kind: ColliderKind.trigger,
),
);
// A trigger is not level geometry: `add` files anything that is not
// `ColliderKind.static` as a mover, and a mover's grid cell is only
// filled in when the world is asked to index it.
_world.reindex();
Step 2: What a ray meets #
raycast walks the broadphase cell by cell along the ray rather than over
its bounding box, which matters on a long, mostly empty shot down a
corridor.
_world.raycast(Vector3(0.0, 1.0, 5.0), Vector3(0.0, 0.0, -1.0), 10.0, _ray);
Step 3: How far a move gets #
sweep answers the question a raycast cannot: not just whether something is
in the way, but how far a shape of a given size can travel before it
touches it. ContactFilter is bool Function(SweptContact), one object
rather than two parameters, so a filter can grow without breaking every
filter anybody has written against it.
_world.sweep(
CollisionBox(Vector3(0.3, 0.5, 0.3)),
Vector3(0.0, 0.5, 3.0),
Vector3(0.0, 0.0, -4.0),
_sweep,
);
Step 4: What is already touching #
overlap is the exact question, with no motion involved: everything a shape
at a position currently intersects, triggers included by default.
_world.overlap(
CollisionBox(Vector3(0.1, 0.1, 0.1)),
_pickup.position,
_overlapping,
);
Note. All three read the answer back through a reusable object rather than a fresh one. A sweep runs several times a step for a character controller, sixty times a second, and an allocation there is an allocation on the hottest path a game has.
Step 5: What the page checks #
if (!_ray.hit || (_ray.distance - 4.8).abs() > 0.01) {
throw StateError('the ray should meet the wall 4.8m out');
}
if (!_sweep.hit || _sweep.normal.z < 0.9) {
throw StateError('the sweep should stop on the wall\'s near face');
}
if (_overlapping.length != 1 || !identical(_overlapping.first, _pickup)) {
throw StateError('the overlap should find only the pickup');
}