Raycasting
Etcetera provides highly optimized Raycaster
class, which is designed to reduce the number of
short-lived objects and improve the performance of long raycasts. In order to avoid unnecessary
object allocation, the Raycaster
object contains fields for the hit result, in contrast to
Minecraft’s function, which returns a RayTraceResult
object (and creates possibly hundreds of
temporary objects along the way).
Casting rays
When appropriate, you should reuse a single Raycaster
instance, and after you’re done with the
hit result you should always call reset()
. While the Raycaster
class is very lightweight,
unnecessarily creating new instances somewhat defeats the minimal object allocation aspect of it.
Calling reset()
is vitally important. When performing a raycast the result will be discarded
if it’s farther away than the current result. This both simplifies the internal implementation
and allows you to combine the results of multiple raycasts with different settings.
In the interest of brevity these examples will use temporary objects (e.g. Vec3d
), however if
you’re writing performance-critical code you should avoid these and use primitive variables.
Basic raycasting
~import com.teamwizardry.librarianlib.etcetera.Raycaster;
~import net.minecraft.entity.Entity;
~import net.minecraft.util.math.BlockPos;
~import net.minecraft.util.math.Vec3d;
~import org.jetbrains.annotations.Nullable;
~
~public class BasicRaycastExample {
// note: Raycaster is *not* thread-safe, though world should only be
// accessed from the main thread anyway.
private static Raycaster raycaster = new Raycaster();
@Nullable
public BlockPos basicBlockRaycast(Entity entity) {
Vec3d start = entity.getEyePosition(0);
Vec3d look = entity.getLookVec();
// cast the ray
raycaster.cast(entity.getEntityWorld(), Raycaster.BlockMode.VISUAL,
start.getX(), start.getY(), start.getZ(),
start.getX() + look.getX() * 100,
start.getY() + look.getY() * 100,
start.getZ() + look.getZ() * 100
);
// get the result out of it
BlockPos result = null;
if (raycaster.getHitType() == Raycaster.HitType.BLOCK) {
result = new BlockPos(
raycaster.getBlockX(),
raycaster.getBlockY(),
raycaster.getBlockZ()
);
}
// it is VITALLY important that you do this
raycaster.reset();
return result;
}
~}
Advanced raycasting
While Raycaster
has a convenience method for raycasting blocks, it can also cast against fluid or
entities using the full cast
method.
~import com.teamwizardry.librarianlib.etcetera.Raycaster;
~import net.minecraft.entity.Entity;
~import net.minecraft.entity.player.PlayerEntity;
~import net.minecraft.util.math.BlockPos;
~import net.minecraft.util.math.Vec3d;
~
~import java.util.function.Predicate;
~
~public class AdvancedRaycastExample {
// note: Raycaster is *not* thread-safe, though world should only be
// accessed from the main thread anyway.
private static Raycaster raycaster = new Raycaster();
private static Predicate<Entity> isPlayerPredicate =
(entity) -> entity instanceof PlayerEntity;
public void advancedRaycast(Entity entity) {
double rayLength = 100;
Vec3d start = entity.getEyePosition(0);
Vec3d look = entity.getLookVec();
look = new Vec3d(
look.getX() * rayLength,
look.getY() * rayLength,
look.getZ() * rayLength
);
// cast the ray
raycaster.cast(entity.getEntityWorld(),
Raycaster.BlockMode.VISUAL,
Raycaster.FluidMode.SOURCE,
isPlayerPredicate,
start.getX(), start.getY(), start.getZ(),
start.getX() + look.getX(),
start.getY() + look.getY(),
start.getZ() + look.getZ()
);
// get the result out of it
if(raycaster.getHitType() == Raycaster.HitType.NONE) {
return;
}
// the fraction along the raycast that the hit occurred
double distance = raycaster.getFraction() * rayLength;
// normal and hit position apply to all the hit types
Vec3d normal = new Vec3d(
raycaster.getNormalX(),
raycaster.getNormalY(),
raycaster.getNormalZ()
);
Vec3d hitPos = new Vec3d(
raycaster.getHitX(),
raycaster.getHitY(),
raycaster.getHitZ()
);
switch (raycaster.getHitType()) {
// block and fluid hits both have block positions
case BLOCK:
case FLUID:
BlockPos hitBlock = new BlockPos(
raycaster.getBlockX(),
raycaster.getBlockY(),
raycaster.getBlockZ()
);
break;
// entity hits have the entity that was hit
case ENTITY:
Entity hitEntity = raycaster.getEntity();
break;
}
// it is VITALLY important that you do this
raycaster.reset();
}
~}