在类似Minecraft的项目中实现John Amanatides的算法时遇到问题。该算法似乎按预期工作,但光线无法以正确的方向或从正确的位置投射。我采用旋转角度(存储在3D向量中)并将其转换为航向向量的dx,dy和dz分量。射线实际去除的块(大部分时间)与立方体的实际位置非常接近。我多次使用不同的策略对算法进行了重新设计,甚至将相机的位置和旋转次数都翻了一倍,但它没有任何改变。
这是我的代码格式:
-EventHandler
类每帧处理鼠标输入,并将挖掘请求发送到RayMaster
类。此类在单独的线程上创建Ray
对象,并循环直到命中一个块或达到最大半径(8)。在命中的情况下,光线大师将事件分派回事件处理程序,该事件处理程序告诉世界删除该块。有了基本解释,下面是代码:
public class EventHandler {
public static void proccessInput () {
if (Mouse.pressed(GLFW.GLFW_MOUSE_BUTTON_LEFT)) {
RayMaster.castRay(Camera.getPosition(), Camera.getRotation(), EventType.DIG_EVENT);
}
}
public static void dispatchEvent (Event e, SubChunk chunk) {
Vectori location;
switch (e.getType()) {
case DIG_EVENT:
location = e.getLocation();
chunk.setCube(location.x(), location.y(), location.z(), BlockLookup.AIR);
ChunkScape.addToUpdateQueue(chunk);
break;
}
}
}
castRay()
类中的RayMaster
方法:
public static void castRay (Vector3 position, Vector3 rotation, EventType onHitEvent) {
Thread raycast = new Thread(()-> {
Ray ray = new Ray(position, rotation);
Vectori rayPos = ray.step();
int chunkX = STUMath.fastfloor(rayPos.x()/16d)*16;
int chunkY = STUMath.fastfloor(rayPos.y()/16d)*16;
int chunkZ = STUMath.fastfloor(rayPos.z()/16d)*16;
SubChunk currentChunk = null;
for (Chunk chunk : ChunkScape.getChunks()) {
for (SubChunk subChunk : chunk.getSubChunks()) {
if (subChunk.getAbsX() == chunkX && subChunk.getAbsY() == chunkY && subChunk.getAbsZ() == chunkZ) {
currentChunk = subChunk;
}
}
}
while (rayPos != null) {
int nextChunkX = STUMath.fastfloor(rayPos.x()/16d)*16;
int nextChunkY = STUMath.fastfloor(rayPos.y()/16d)*16;
int nextChunkZ = STUMath.fastfloor(rayPos.z()/16d)*16;
if (nextChunkX != chunkX || nextChunkY != chunkY || nextChunkZ != chunkZ) {
currentChunk = null;
for (Chunk chunk : ChunkScape.getChunks()) {
for (SubChunk subChunk : chunk.getSubChunks()) {
if (subChunk.getAbsX() == nextChunkX && subChunk.getAbsY() == nextChunkY && subChunk.getAbsZ() == nextChunkZ) {
currentChunk = subChunk;
}
}
}
}
chunkX = nextChunkX;
chunkY = nextChunkY;
chunkZ = nextChunkZ;
if (currentChunk == null) return;
int locX =STUMath.wrap(rayPos.x(), 0, SubChunk.CHUNK_SIZE);
int locY =STUMath.wrap(rayPos.y(), 0, SubChunk.CHUNK_SIZE);
int locZ =STUMath.wrap(rayPos.z(), 0, SubChunk.CHUNK_SIZE);
rayPos = ray.step();
if (currentChunk.getCube(locX, locY, locZ).getID() == BlockData.AIR_ID) continue;
EventHandler.dispatchEvent(new Event(onHitEvent, new Vectori(locX, locY, locZ)), currentChunk);
return;
}
});
raycast.start();
}
这是Ray
类:
public class Ray {
public static final float STEP_SIZE = 0.01f;
public static final int MAX_DIST = 8;
private int x, y, z;
private int stepX, stepY, stepZ;
private float tMaxX, tMaxY, tMaxZ;
private float tDeltaX, tDeltaY, tDeltaZ;
private Face faceEntered;
public Ray (Vector3 position, Vector3 rotation) {
x = STUMath.fastfloor(position.x());
y = STUMath.fastfloor(position.y());
z = STUMath.fastfloor(position.z());
float cosX = (float)Math.cos(Math.PI/180 * rotation.x());
float dx = (float)-Math.sin(Math.PI/180 * rotation.y())*cosX;
float dz = (float)Math.cos(Math.PI/180 * rotation.y())*cosX;
float dy = (float)-Math.sin(Math.PI/180 * rotation.x());
stepX = dx > 0? 1 : dx < 0? -1 : 0;
stepY = dy > 0? 1 : dy < 0? -1 : 0;
stepZ = dz > 0? 1 : dz < 0? -1 : 0;
tMaxX = findBound(position.x(), dx);
tMaxY = findBound(position.y(), dy);
tMaxZ = findBound(position.z(), dz);
tDeltaX = stepX/dx;
tDeltaY = stepY/dy;
tDeltaZ = stepZ/dz;
}
public Vectori step () {
if (stepX == 0 && stepY == 0 && stepZ == 0) return null;
if (tMaxX < tMaxY) {
if (tMaxX < tMaxZ) {
if (tMaxX > MAX_DIST) return null;
x += stepX;
tMaxX += tDeltaX;
faceEntered = stepX > 0? Face.LEFT : Face.RIGHT;
} else {
if (tMaxZ > MAX_DIST) return null;
z += stepZ;
tMaxZ += tDeltaZ;
faceEntered = stepZ > 0? Face.FRONT : Face.BACK;
}
} else {
if (tMaxY < tMaxZ) {
if (tMaxY > MAX_DIST) return null;
y += stepY;
tMaxY += tDeltaY;
faceEntered = stepY > 0? Face.DOWN : Face.UP;
} else {
if (tMaxZ > MAX_DIST) return null;
z += stepZ;
tMaxZ += tDeltaZ;
faceEntered = stepZ > 0? Face.FRONT : Face.BACK;
}
}
return new Vectori(x, y, z);
}
public Face getFaceEntered () {
return faceEntered;
}
private float findBound (float s, float ds) {
return s<0? findBound(-s, -ds) : (1-s%1)/ds;
}
}
我在Camera
类中的轮换代码:
rotation.setI(0, rotation.x() + (float)(Mouse.getDY() * ROT_SENSITIVITY)); // sets the x-component of rotation
rotation.setI(1, rotation.y() + (float)(Mouse.getDX() * ROT_SENSITIVITY)); // sets the y-component of rotation
joul
库是由我创建的程序包,其中包含一些方便的实用程序。 Vector3
类等效于Java开放数学库中的Vector3f
对象。
STUMath
中的数学函数很容易解释,所以我现在不需要解释它们,但是如果我不清楚地做过什么,请告诉我。
编辑: 如果对任何人都有用,我会尝试使用Hopson的技术,从他为期一周的挑战中取胜。它具有相同的输出:略微偏离光线的投射。人们使用Moue Picking算法时,必须撤消投影矩阵。这可能是原因吗?我对矩阵还不太满意(所以,对我来说很容易:P),但是希望您能投入很多。
编辑: 这很难调试。我真的不知道如何轻松地绘制线条,但是我可以肯定,相机的位置和旋转都会使初始值掉线。
编辑: 在最近的版本中,我重新编写了Java-12中的代码,我尝试再次实现此算法。我尝试不投影屏幕中心坐标(0、0、1,-1),但是这也给我带来了有时准确而又非常不准确的光线投射。我不知道我一般是使用错误的方法进行光线投射还是这是某种内部误差。
感谢您的帮助!