Ray Tracing

Waarschuwing

These docs were written for SpongeAPI 7 and are likely out of date. If you feel like you can help update them, please submit a PR!

Generically, ray tracing is a method of determining the path of a particle through a coordinate system. In SpongeAPI, this is implemented with the BlockRay class to discover blocks in the path of an arbitrary line in space. A common use case could be to find the block that a Player is looking at.

You can specify the origin of the ray using the BlockRay#from method, passing in either a Location, an Extent and a Vector3d, or an Entity. The method will return a BlockRay.BlockRayBuilder.

Tip

If you specify the origin of the ray to be an Entity, the default direction will be the direction in which the Entity is pointing.

To specify the end point, you can use the BlockRay.BlockRayBuilder#to(Vector3d) method, which will set both the direction and ending location. Alternatively, you can specify a direction using BlockRay.BlockRayBuilder#direction(Vector3d) and also a distance limit using BlockRay.BlockRayBuilder#distanceLimit(double).

Notitie

The default distance limit is 1000 blocks as a safeguard to prevent infinite iteration. To disable the distance limit, use a negative value with BlockRayBuilder#distanceLimit(double).

Filtering

Filters determine what blocks are accepted by the BlockRay. To add a filter, use the BlockRayBuilder#stopFilter or BlockRayBuilder#skipFilter method, passing in one or many Predicate<BlockRayHit<E>>s (where E extends Extent). The BlockRayBuilder#stopFilter continues the ray cast when it hits a block that passes the given filter and the BlockRayBuilder#skipFilter skips all blocks that pass the filter. These filters can be chained together to create a complex BlockRay. For convenience, BlockRay contains the following methods for common filter use cases:

  • allFilter: returns a filter accepting all blocks

  • onlyAirFilter: returns a filter accepting only air

  • blockTypeFilter(BlockType): returns a filter accepting only the specified BlockType

  • continueAfterFilter(Predicate<BlockRayHit<E>>, int): returns a filter that continues past the given filter by the specified number of blocks.

Of course, you can also write your own Predicate<BlockRayHit<E>> filter.

Finally, use BlockRay.BlockRayBuilder#build() to finish building the BlockRay. An example usage of BlockRayBuilder to get the first non-air block a Player is looking at is provided below.

import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.util.blockray.BlockRay;
import org.spongepowered.api.world.World;

Player player;
BlockRay<World> blockRay = BlockRay.from(player)
    .skipFilter(BlockRay.onlyAirFilter()).stopFilter(BlockRay.onlyAirFilter()).build();

We can rewrite the above to use BlockRay#skipFilter(Predicate<BlockRayHit>) in addition to BlockRay#stopFilter(Predicate<BlockRayHit>). This will skip all air blocks and stop at the first non-air block it hits.

BlockRay<World> blockRay = BlockRay.from(player)
    .skipFilter(BlockRay.onlyAirFilter()).stopFilter(BlockRay.allFilter()).build();

Using BlockRay

Since BlockRay implements Iterator, you can use such methods as hasNext and next (but not remove) to iterate through the BlockRayHits generated by the BlockRay. Additionally, you can use the BlockRay#reset() method to reset the iterator to the starting location. Rather than iterating through the BlockRayHits, you can also use the BlockRay#end() method to trace the block ray to the end of its path to get the last block accepted by the filter (or none if the block limit was reached).

Using BlockRayHit

BlockRayHit contains information about each block intersected by the ray. It contains the location of the block, the direction of the ray, the coordinates of the intersection of the ray and the block, and other similar data. The following code uses the BlockRay from the previous example to print out the location of the first non-air block in front of the player.

import org.spongepowered.api.util.blockray.BlockRayHit;
import java.util.Optional;

BlockRay<World> blockRay = ...;
Optional<BlockRayHit<World>> hitOpt = blockRay.end();
if (hitOpt.isPresent()) {
    BlockRayHit<World> hit = hitOpt.get();
    System.out.println("Found " + hit.getLocation().getBlockType() + " block at "
        + hit.getLocation() + " with intersection at " + hit.getPosition());
}