Ray casts
This guide shows how to find the items along a line. If you don’t have a tree set up yet, read the Getting started guide first.
A ray cast goes from a start position to an end position. Positions along the ray are fractions: 0f is start and 1f is end.
Every hit
Raycast(start, end) returns every item whose AABB the ray crosses:
foreach (Entity e in _tree.Raycast(start, end)) {
// The ray crosses e's AABB.
}
Items closer to start tend to come first, but the order isn’t sorted.
Like queries, ray casts are a broad phase search. They test the expanded AABBs, so the ray can cross an item’s AABB and still miss the item itself. Testing each result against your own shape is the narrow phase. The Queries guide goes over it.
Closest hit
Raycast(start, end, callback) calls the callback for each item whose AABB the ray crosses. The callback gets the item and maxFraction, how far along the ray the search currently goes. What it returns decides what happens next:
-1fignores the item and keeps going.maxFractionkeeps going without changing anything.- A fraction between
0fandmaxFractionclips the ray there. Items past that point get skipped. 0fstops the ray cast.
To find the closest hit, return where the ray hit each item. The ray gets shorter with each hit, so the tree skips everything that’s farther away:
float closest = 1f;
Entity? hit = null;
_tree.Raycast(start, end, (e, maxFraction) => {
if (e.Raycast(start, end) is float fraction && fraction < maxFraction) {
closest = fraction;
hit = e;
return fraction;
}
return -1f;
});
Vector2 hitPosition = Vector2.Lerp(start, end, closest);
Here e.Raycast stands for your own test against the item’s shape. It returns the fraction where the ray hits it, or null when it misses. The fraction < maxFraction check matters since the ray can cross an AABB before the clip point while the shape inside is hit after it. The example project has a test against rectangles called RayVsRect in GameRoot.cs.
Any hit
If you only need to know whether something is in the way, for example for line of sight, return 0f on the first hit to stop right away:
bool blocked = false;
_tree.Raycast(eye, target, (e, maxFraction) => {
if (e.BlocksSight && e.Raycast(eye, target) is not null) {
blocked = true;
return 0f;
}
return -1f;
});
Changing the tree from inside the callback throws an InvalidOperationException.
Follow up
Interval tree, the same tree in one dimension.
Edit this page on GitHub