Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/me/cortex/voxy/client/api/RaycastResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.cortex.voxy.client.api;

import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;

public record RaycastResult(BlockPos blockPos, Direction face, Vec3 hitLocation, BlockState blockState) {}
175 changes: 175 additions & 0 deletions src/main/java/me/cortex/voxy/client/api/VoxyRaycastAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package me.cortex.voxy.client.api;

import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;

public class VoxyRaycastAPI {

private static final int SECTION_MASK = 31;
private static final int MAX_LOD = 4;

private VoxyRaycastAPI() {}

public static RaycastResult raycast(Level level, Vec3 start, Vec3 direction, double maxDistance) {
WorldEngine engine = WorldIdentifier.ofEngineNullable(level);
if (engine == null) {
return null;
}

double dx = direction.x;
double dy = direction.y;
double dz = direction.z;

int stepX;
int stepY;
int stepZ;
double tDeltaX;
double tDeltaY;
double tDeltaZ;

if (dx == 0) {
stepX = 0;
tDeltaX = Double.POSITIVE_INFINITY;
} else {
stepX = dx > 0 ? 1 : -1;
tDeltaX = Math.abs(1.0 / dx);
}
if (dy == 0) {
stepY = 0;
tDeltaY = Double.POSITIVE_INFINITY;
} else {
stepY = dy > 0 ? 1 : -1;
tDeltaY = Math.abs(1.0 / dy);
}
if (dz == 0) {
stepZ = 0;
tDeltaZ = Double.POSITIVE_INFINITY;
} else {
stepZ = dz > 0 ? 1 : -1;
tDeltaZ = Math.abs(1.0 / dz);
}

int x = (int) Math.floor(start.x);
int y = (int) Math.floor(start.y);
int z = (int) Math.floor(start.z);

double tMaxX = dx == 0 ? Double.POSITIVE_INFINITY :
(dx > 0 ? (x + 1 - start.x) : (start.x - x)) * tDeltaX;
double tMaxY = dy == 0 ? Double.POSITIVE_INFINITY :
(dy > 0 ? (y + 1 - start.y) : (start.y - y)) * tDeltaY;
double tMaxZ = dz == 0 ? Double.POSITIVE_INFINITY :
(dz > 0 ? (z + 1 - start.z) : (start.z - z)) * tDeltaZ;

// Acquire section for the starting voxel
WorldSection currentSection = null;
int currentSectionX = x >> 5;
int currentSectionY = y >> 5;
int currentSectionZ = z >> 5;
int currentLod = 0;
for (int lod = 0; lod <= MAX_LOD; lod++) {
int shift = 5 + lod;
WorldSection sec = engine.acquireIfExists(lod, x >> shift, y >> shift, z >> shift);
if (sec != null) {
currentSection = sec;
currentLod = lod;
break;
}
}

//Check starting voxel, the ray originates inside it so entry distance is 0
if (currentSection != null) {
int lx = (x >> currentLod) & SECTION_MASK;
int ly = (y >> currentLod) & SECTION_MASK;
int lz = (z >> currentLod) & SECTION_MASK;
long voxel = currentSection._unsafeGetRawDataArray()[WorldSection.getIndex(lx, ly, lz)];
if (!Mapper.isAir(voxel)) {
int blockId = Mapper.getBlockId(voxel);
BlockState state = engine.getMapper().getBlockStateFromBlockId(blockId);
currentSection.release();
return new RaycastResult(new BlockPos(x, y, z), Direction.UP, start, state);
}
}

Direction lastStepFace = null;

while (true) {
double traveled = Math.min(tMaxX, Math.min(tMaxY, tMaxZ));
if (traveled > maxDistance) {
break;
}

if (tMaxX < tMaxY) {
if (tMaxX < tMaxZ) {
x += stepX;
lastStepFace = stepX > 0 ? Direction.WEST : Direction.EAST;
tMaxX += tDeltaX;
} else {
z += stepZ;
lastStepFace = stepZ > 0 ? Direction.NORTH : Direction.SOUTH;
tMaxZ += tDeltaZ;
}
} else {
if (tMaxY < tMaxZ) {
y += stepY;
lastStepFace = stepY > 0 ? Direction.DOWN : Direction.UP;
tMaxY += tDeltaY;
} else {
z += stepZ;
lastStepFace = stepZ > 0 ? Direction.NORTH : Direction.SOUTH;
tMaxZ += tDeltaZ;
}
}

//Reacquire section if we crossed a LOD 0 section boundary
int scx = x >> 5;
int scy = y >> 5;
int scz = z >> 5;
if (currentSection == null || scx != currentSectionX || scy != currentSectionY || scz != currentSectionZ) {
if (currentSection != null) {
currentSection.release();
currentSection = null;
}
currentSectionX = scx;
currentSectionY = scy;
currentSectionZ = scz;
for (int lod = 0; lod <= MAX_LOD; lod++) {
int shift = 5 + lod;
WorldSection sec = engine.acquireIfExists(lod, x >> shift, y >> shift, z >> shift);
if (sec != null) {
currentSection = sec;
currentLod = lod;
break;
}
}
}

if (currentSection != null) {
int lx = (x >> currentLod) & SECTION_MASK;
int ly = (y >> currentLod) & SECTION_MASK;
int lz = (z >> currentLod) & SECTION_MASK;
long voxel = currentSection._unsafeGetRawDataArray()[WorldSection.getIndex(lx, ly, lz)];
if (!Mapper.isAir(voxel)) {
int blockId = Mapper.getBlockId(voxel);
BlockState state = engine.getMapper().getBlockStateFromBlockId(blockId);
Vec3 hitLocation = start.add(direction.scale(traveled));
RaycastResult result = new RaycastResult(new BlockPos(x, y, z),
lastStepFace != null ? lastStepFace : Direction.UP, hitLocation, state);
currentSection.release();
return result;
}
}
}

if (currentSection != null) {
currentSection.release();
}
return null;
}
}