c# - 如何修复我的 3D 光线投射算法以获取玩家正在查看的方块
问题描述
我用于计算玩家正在查看哪个方块(基于体素的世界)的算法无法正常工作。我已将本教程中的 2D 改编为 3D。有时它会显示正确的块,但有时它要么什么都不返回,要么是完全不同的方向,为什么会发生这种情况?
public (Block, Box?) GetLookAtBlock(Vector3 pos, Vector3 look) {
try {
look = look.Normalized() * 4;
float deltaX = Math.Abs(look.Normalized().X);
float deltaY = Math.Abs(look.Normalized().Y);
float deltaZ = Math.Abs(look.Normalized().Z);
int stepX, stepY, stepZ;
float distX, distY, distZ;
if (look.X < 0) {
distX = (pos.X - SandboxMath.RoundDown(pos.X)) * deltaX;
stepX = -1;
} else {
distX = (SandboxMath.RoundDown(pos.X) + 1 - pos.X) * deltaX;
stepX = 1;
}
if (look.Y < 0) {
distY = (pos.Y - SandboxMath.RoundDown(pos.Y)) * deltaY;
stepY = -1;
} else {
distY = (SandboxMath.RoundDown(pos.Y) + 1 - pos.Y) * deltaY;
stepY = 1;
}
if (look.Z < 0) {
distZ = (pos.Z - SandboxMath.RoundDown(pos.Z)) * deltaZ;
stepZ = -1;
} else {
distZ = (SandboxMath.RoundDown(pos.Z) + 1 - pos.Z) * deltaZ;
stepZ = 1;
}
int endX = SandboxMath.RoundDown(pos.X + look.X);
int endY = SandboxMath.RoundDown(pos.Y + look.Y);
int endZ = SandboxMath.RoundDown(pos.Z + look.Z);
int x = (int)pos.X;
int y = (int)pos.Y;
int z = (int)pos.Z;
Block start = GetBlock(x, y, z);
if (start != 0) {
return (start, new Box(new Vector3(x, y, z), new Vector3(x + 1, y + 1, z + 1)));
}
while (x != endX && y != endY && z != endZ) {
if (distX < distY) {
if (distX < distZ) {
distX += deltaX;
x += stepX;
} else {
distZ += deltaZ;
z += stepZ;
}
} else {
if (distY < distZ) {
distY += deltaY;
y += stepY;
} else {
distZ += deltaZ;
z += stepZ;
}
}
Block b = GetBlock(x, y, z);
if (b != 0) {
return (b, new Box(new Vector3(x, y, z), new Vector3(x + 1, y + 1, z + 1)));
}
}
return (0, null);
} catch (IndexOutOfRangeException) {
return (0, null);
}
}
解决方案
乍一看,您的DDA有两个问题:
仅当 Z 为主轴时才有效
所以只有当你在相机空间或者有固定的相机在 Z 方向看
你的三角洲很奇怪
为什么:
delta? = abs(1 / look.Normalized().?);
我希望:
delta? = abs(look.Normalized().?);
我不会用C#编写代码,所以我没有信心修复您的代码,但是这里是我的-dimensional DDA的C++模板,所以只需根据它比较和修复您的...n
template<const int n>class DDA
{
public:
int p0[n],p1[n],p[n];
int d[n],s[n],c[n],ix;
DDA(){};
DDA(DDA& a) { *this=a; }
~DDA(){};
DDA* operator = (const DDA *a) { *this=*a; return this; }
//DDA* operator = (const DDA &a) { ..copy... return this; }
void start()
{
int i;
for (ix=0,i=0;i<n;i++)
{
p[i]=p0[i]; s[i]= 0; d[i]=p1[i]-p0[i];
if (d[i]>0) s[i]=+1;
if (d[i]<0){ s[i]=-1; d[i]=-d[i]; }
if (d[ix]<d[i]) ix=i;
}
for (i=0;i<n;i++) c[i]=d[ix];
}
void start(double *fp0) // this will add the subpixel offset according to first point as double
{
int i; start();
for (i=0;i<n;i++)
{
if (s[i]<0) c[i]=double(double(d[ix])*( fp0[i]-floor(fp0[i])));
if (s[i]>0) c[i]=double(double(d[ix])*(1.0-fp0[i]+floor(fp0[i])));
}
}
bool update()
{
int i;
for (i=0;i<n;i++){ c[i]-=d[i]; if (c[i]<=0){ c[i]+=d[ix]; p[i]+=s[i]; }}
return (p[ix]!=p1[ix]+s[ix]);
}
};
start()
初始化DDA的变量和位置(来自p0,p1
控制点),这update()
只是DDA的一个步骤......结果迭代点在p
s
是步长,d
是增量,c
是计数器,ix
是长轴索引。
用法是这样的:
DDA<3> A; // 3D
A.p0[...]=...; // set start point
A.p1[...]=...; // set end point
for (A.start();A.update();)
{
A.p[...]; // here use the iterated point
}
DDA 通过
好吧,DDA只是两个端点 ( p0
, ) 之间某条线上的整数位置的插值(光栅化p1
)。直线方程是这样的:
p(t) = p0 + t*(p1-p0);
t = <0.0,1.0>
然而,这涉及到浮动数学,我们想要整数,所以我们可以重写为这样的东西:
dp = p1-p0
D = max (|dp.x|,|dp.y|,|dp.z|,...)
p.x(i) = p0.x + (dp.x*i)/D
p.y(i) = p0.y + (dp.y*i)/D
p.z(i) = p0.z + (dp.z*i)/D
...
i = { 0,1,...D }
在哪里i,D
匹配主轴(变化最大的轴)。如果您仔细观察,我们使用*i/D
的是慢速操作,我们通常需要速度,因此我们可以将 therm(利用i
从 0 到 D 的顺序)重写为类似的东西(对于 x 轴,只有其他轴是相同的具有不同的索引...):
p.x=p0.x; // start position
s.x=0; d.x=p1.x-p0.x; // step and abs delta
if (d.x>0) s.x=+1;
if (d.x<0){ s.x=-1; d.x=-d.x; }
D = max(d.x,d.y,d.z,...); // major axis abs delta
c.x=D; // counter for the iteration
for (i=0;i<D;i++)
{
c.x-=d.x; // update counter with axis abs delta
if (c.x<=0) // counter overflowed?
{
c.x+=D; // update counter with major axis abs delta
p.x+=s.x; // update axis by step
}
}
现在仔细看看c
我们正在添加D
和减去的计数器,d.x
它被i/D
重写为D
迭代。所有其他轴都以相同的方式计算,您只需为每个轴添加计数器c
步长s
和绝对增量......d
顺便说一句,如果它有助于看看这个:
-
这是(我假设)你在做什么,但在 GLSL 着色器中实现(参见片段代码)但是它不使用DDA,而是将单位方向向量添加到初始位置,直到碰到某些东西或体素空间的结尾......
顺便说一句,它基于:
就像你的链接一样。
[编辑] 错误点击(根据您的评论猜测)
这很可能与DDA无关。当您直接站在单元格交叉处或错误地截断位置或错误地对命中进行 z 排序时,它更像是边缘情况。我记得我遇到了麻烦。我最终在GLSL中得到了非常奇怪的解决方案,请参阅上面的链接并查看片段代码。寻找
// YZ plane voxels hits
它直接在非“DDA”射线投射代码之后。它检测体素的哪个平面会被击中我认为你应该做类似的事情。这很奇怪,因为在 2D DOOM 中(也在上面的链接中)我没有这样的问题......但这是因为它们是通过使用不同的数学来解决的(仅适用于 2D)。
光线投射之前的 GLSL 代码稍微改变了位置以避免边缘情况。注意floor
andceil
但是我的工作是浮点数,所以它需要对 int 数学进行一些调整。幸运的是,我正在基于此修复我的其他光线投射引擎:
解决方法是通过光线的亚像素起始位置偏移DDA。我更新了上面的DDA代码,新用法是:
DDA<3> A; // 3D
A.p0[...]=...; // set start point
A.p1[...]=...; // set end point
for (A.start(start_point_as_double[3]);A.update();)
{
A.p[...]; // here use the iterated point
}
同样在第二次教导时,请确保在您的DDA中,c,d,s
如果它们是浮点数,则它们是整数,那么它也可能导致您所描述的问题......
推荐阅读
- android - 如何启用 Android 的所有内部 SQL cmd 的 LOGging?
- elixir - 如何使用另一个地图列表中的值更新地图列表?
- plugins - 原子文本编辑器的这个插件的名称是什么?
- extjs - 单击单元格后如何打开下拉列表
- mysql - 带有 SET @variables 和特定 SELECT 查询的 MySQL 视图
- html - HTML5 视频规范说不使用百分比,但它们有效吗?
- reactjs - 页面刷新后服务器端渲染失败
- flutter - 显示 Flutter 更改错误的下拉菜单。无法在 onchange 回调函数中设置状态
- excel - 从另一个工作表更改下拉选择时自动刷新过滤器
- powershell - 哈希表,键中的倍数值,Foreach 循环 Powershell