Трассировка лучей

В общем случае трассировка лучей является методом определения траектории частицы через систему координат. В SpongeAPI это реализовано с помощью класса BlockRay для обнаружения блоков на пути произвольной линии в пространстве. Частым случаем использования может быть поиск блока, на который смотрит Player.

Вы можете указать источник луча, используя метод BlockRay#from, передавая либо Location, Extent и Vector3d, либо Entity. Метод вернёт BlockRay.BlockRayBuilder.

Совет

Если вы указываете начало луча как Entity, то направление по умолчанию будет направлением, в которое смотрит Entity.

Чтобы указать конечную точку, вы можете использовать метод BlockRay.BlockRayBuilder#to(Vector3d), который будет устанавливать как направление, так и конечное положение. В качестве альтернативы вы можете указать направление, используя BlockRay.BlockRayBuilder#direction(Vector3d), а также ограничение по блоку с помощью BlockRay.BlockRayBuilder#distanceLimit(double).

Примечание

Ограничение по умолчанию - 1000 блоков в качестве защиты для предотвращения бесконечной итерации. Чтобы отключить ограничение по расстоянию, используйте отрицательное значение в методе BlockRayBuilder#distanceLimit(double).

Filtering

Фильтры определяют, какие блоки принимаются BlockRay. Чтобы добавить фильтр, используйте метод BlockRayBuilder#stopFilter или BlockRayBuilder#skipFilter, передавая один или несколько Predicate<BlockRayHit<E>> (где E наследует Extent). BlockRayBuilder#stopFilter останавливает трассировку луча, когда он попадает в блок из фильтра, BlockRayBuilder#skipFilter игнорирует все блоки, которые входят в фильтр. Эти фильтры могут быть объединены вместе для создания сложного BlockRay. Для удобства, BlockRay содержит следующие методы для распространенных случаев:

  • allFilter: возвращает фильтр, принимающий все блоки
  • onlyAirFilter: возвращает фильтр, принимающий только воздух (air)
  • blockTypeFilter(BlockType): возвращает фильтр, принимающий только указанный BlockType
  • continueAfterFilter(Predicate<BlockRayHit<E>>, int): возвращает фильтр, который продолжает выполняться после фильтра на указанное количество блоков.

Вы также можете написать свой собственный фильтр Predicate<BlockRayHit<E>>.

Наконец, используйте:javadoc:BlockRay.BlockRayBuilder#build() для завершения построения BlockRay. Пример использования BlockRayBuilder для получения первого не пустого блока, на который смотрит Player, выглядит следующим образом.

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)
    .stopFilter(BlockRay.continueAfterFilter(BlockRay.onlyAirFilter(), 1)).build();

Данный код мы можем переписать, чтобы использовать BlockRay#skipFilter(Predicate<BlockRayHit>) в дополнение к BlockRay#stopFilter(Predicate<BlockRayHit>). Этот фильтр пропустит все блоки воздуха и остановится в первом блоке не воздуха, в который он попадает.

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

Использование BlockRay

Поскольку BlockRay реализует Iterator, вы можете использовать такие методы, как hasNext и next (но не remove) для итерации через BlockRayHit сгенерированный с помощью BlockRay. Кроме того, вы можете использовать метод BlockRay#reset(), чтобы сбросить итератор в исходное положение. Вместо того, чтобы выполнять итерацию через BlockRayHit, вы также можете использовать метод BlockRay#end() для отслеживания луча до конца его пути и получить последний блок, принятый фильтром (или none, если был достигнут предел блоков).

Использование BlockRayHit

BlockRayHit содержит информацию о каждом блоке, пересекаемом лучом. Он содержит расположение блока, направление луча, координаты пересечения луча и блока и другие подобные данные. В следующем коде используется BlockRay из предыдущего примера, чтобы вывести расположение первого блока не воздуха перед игроком.

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