c++ - 射线平面相交 : inaccurate results - rounding errors?

标签 c++ collision-detection linear-algebra game-physics floating-accuracy

我已经使用 C++ 和 OpenGL 创建了一个简单的 3d 第一人称演示,它似乎运行良好。我的目标是:当用户将相机指向一个平面并单击鼠标左键时,我想绘制一条射线的交点,该射线从玩家的位置与该平面指向相机所面对的方向。

所以,我从两个 vector 开始,Vector positionVector rotation,其中 Vector 是一个非常标准的三维 vector 类:

class Vector
{
  public:
    GLfloat x, y, z;

    Vector() {};

    Vector(GLfloat x, GLfloat y, GLfloat z)
    {
      this->x = x;
      this->y = y;
      this->z = z;
    }

    GLfloat dot(const Vector &vector) const
    {
      return x * vector.x + y * vector.y + z * vector.z;
    }

    ... etc ...

Plane p,其中 Plane 是一个简单的结构,用于存储平面的法线和 d。我直接从 Christer Ericson 的“实时碰撞检测”一书中复制了这个结构:

struct Plane 
{
  Vector n; // Plane normal. Points x on the plane satisfy Dot(n,x) = d
  float d; // d = dot(n,p) for a given point p on the plane
};

首先,我将 position 作为射线的起点,我称之为 a。我使用该点和 rotation 找到射线的终点 b。然后我使用一种算法从同一本书中找到射线和平面的交点。我实际上自己实现了相同的方法,但我在这里直接使用书中的代码只是为了确保我没有搞砸任何事情:

void pickPoint()
{
    const float length = 100.0f;

    // Points a and b
    Vector a = State::position;
    Vector b = a;

    // Find point b of directed line ab
    Vector radians(Math::rad(State::rotation.x), Math::rad(State::rotation.y), 0);
    const float lengthYZ = Math::cos(radians.x) * length;

    b.y -= Math::sin(radians.x) * length;
    b.x += Math::sin(radians.y) * lengthYZ;
    b.z -= Math::cos(radians.y) * lengthYZ;

    // Compute the t value for the directed line ab intersecting the plane
    Vector ab = b - a;

    GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

    printf("Plane normal: %f, %f, %f\n", p.n.x, p.n.y, p.n.z);
    printf("Plane value d: %f\n", p.d);
    printf("Rotation (degrees): %f, %f, %f\n", State::rotation.x, State::rotation.y, State::rotation.z);
    printf("Rotation (radians): %f, %f, %f\n", radians.x, radians.y, radians.z);
    printf("Point a: %f, %f, %f\n", a.x, a.y, a.z);
    printf("Point b: %f, %f, %f\n", b.x, b.y, b.z);
    printf("Expected length of ray: %f\n", length);
    printf("Actual length of ray: %f\n", ab.length());
    printf("Value t: %f\n", t);

    // If t in [0..1] compute and return intersection point
    if(t >= 0.0f && t <= 1.0f) 
    {
        point = a + t * ab;
        printf("Intersection: %f, %f, %f\n", point.x, point.y, point.z);
    }
    // Else no intersection
    else
    {
        printf("No intersection found\n");
    }

    printf("\n\n");
}

当我用 OpenGL 渲染这个点时,它看起来非常接近光线和平面的交点。但是通过打印出实际值,我发现对于特定的位置和旋转,交点最多可以偏离 0.000004。这是交点不准确的示例 - 我知道交点不在平面上,因为它的 Y 值应该是 0,而不是 0.000002。我也可以将它代入平面方程并得到不等式:

Plane normal: 0.000000, 1.000000, 0.000000
Plane value d: 0.000000
Rotation (degrees): 70.100044, 1.899823, 0.000000
Rotation (radians): 1.223477, 0.033158, 0.000000
Point a: 20.818802, 27.240383, 15.124892
Point b: 21.947229, -66.788452, -18.894285
Expected length of ray: 100.000000
Actual length of ray: 100.000000
Value t: 0.289702
Intersection: 21.145710, 0.000002, 5.269455

现在,我知道 float 只是实数的近似值,所以我猜这种不准确只是 float 舍入的结果,尽管我可能在代码的其他地方犯了错误。我知道交点只有极小的偏差,但我仍然很关心它,因为我打算使用这些点来定义模型或关卡的顶点,方法是将它们捕捉到任意方向的网格,所以我实际上想要那些点在那个网格上,即使它们稍微不准确。这可能是一种误入歧途的方法 - 我真的不知道。

所以我的问题是:这种不准确只是工作中的浮点舍入,还是我在其他地方犯了错误?

如果只是 float 舍入,有什么办法处理吗?我已经尝试以各种方式舍入旋转和位置 vector 的值,这显然会导致交点不太准确,但有时我仍然会得到不在平面上的交点。我确实读过一个类似问题 ( Is this plane-ray intersection code correct? ) 的答案,其中提到保持尺寸较大,但我不确定这到底是什么意思。

如果之前有人问过这个问题,我很抱歉 - 我进行了搜索,但我没有看到任何我遇到的问题。谢谢!

最佳答案

您的数学似乎是正确的,这看起来确实像是一个舍入误差。我有一种强烈的感觉,就是这一行:

GLfloat t = (p.d - p.n.dot(a)) / p.n.dot(ab);

也就是说我没有看到任何其他方法来计算 t。您可以通过在 printf 语句中使用“%.12f”(或更多)来验证您是否正在失去精度。查明罪魁祸首的另一种方法是尝试逐步进行 t 计算并打印结果以查看是否在某处失去了精度。

如果精度对您来说真的那么重要,您是否尝试过使用 double float ?

关于c++ - 射线平面相交 : inaccurate results - rounding errors?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15102738/

相关文章:

C++ fstream 混淆

c++ - 在 C++ 中使用 STL 创建邻接表的有效方法是什么?

java - 轴对齐边界框和三角形的分离轴测试产生不正确的结果 (3D)

arrays - 正向替换打包数组

c++ - ReLU函数在C++上的实现

python - 保持 TCP 套接字连接事件和读/写协调

c++ - AABB Collision response(推回碰撞物体)

c# - Unity Physics2D.Raycast 命中自身

math - 如何找到平行六面体的 4d 模拟的超体积?

java - Jama - 寻找行列式时矩阵必须是平方异常(exception)