flutter3d
Showcase Changelog 38 packages API reference

Raycast, sweep and overlap

since 0.5.0 Physics and particles

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');
}