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
BlockRayHit
s, 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());
}