在Java中进行3D射线-四边形相交测试

7
在三维空间中,我正在尝试确定一条射线/直线是否与一个正方形相交,如果是,则确定它在正方形上的 x 和 y 位置。
我的射线由两个点表示:
R1 = (Rx1, Ry1, Rz1) and 
R2 = (Rx2, Ry2, Rz2)

正方形由四个顶点表示:

S1 = (Sx1, Sy1, Sz1), 
S2 = (Sx2, Sy2, Sz2), 
S3 = (Sx3, Sy3, Sz3) and 
S4 = (Sx4, Sy4, Sz4).

我在网上找到了很多与此相关的代数方程式,但都似乎不完全适用于这个问题。理想情况下,我希望得到Java代码的答案,但一个易于转换为代码的方程也可以。

非常感谢任何帮助。

1个回答

20
以下是解决方案的概述:
  1. 计算正方形的平面方程(假设四个点共面),

  2. 进行射线/平面相交,这将给出无结果(射线与正方形平行,并且我忽略了射线嵌入平面的情况)或一个点,

  3. 一旦您获得交点,请在正方形所在平面上的本地2D基础上对其进行投影,这将给出平面上点的2D坐标(u,v),

  4. 检查2D坐标(u,v)是否在正方形内(假设四个点构成平行四边形,并且您选择了两条相邻边用于本地2D基础),如果是,则存在交点(并且您具有u/v坐标)。

现在来看实际的方程式,假设四个正方形顶点如下放置:
   S1 +------+ S2
      |      |
      |      |
   S3 +------+ S4
  1. 平面的法向量为:n = (S2 - S1) x (S3 - S1)

    点M属于该平面,当且仅当它满足这个方程:n . ( M - S1 ) = 0

  2. 点M属于射线,当且仅当它可以表示为:M = R1 + t * dR,其中dR = R2 - R1

    计算射线与平面的交点(等同于上述两个方程):

    n . ( M - S1 ) = 0 = n . ( R1 + t * dR - S1 ) = n . (R1 - S1) + t * n . dR

    如果n . dR为0,则平面与射线平行,无交点(忽略射线嵌入平面的情况)。

    否则,t = -n . (R1 - S1) / n . dR,并将此结果代入前一个方程M = R1 + t * dR,即可得到交点M的三维坐标。

  3. 将向量M - S1投影到两个向量S2 - S1和S3 - S1(从S1开始的正方形边缘),得到两个数字(u,v):

    u = (M - S1) . (S2 - S1)

    v = (M - S1) . (S3 - S1)

  4. 如果0 <= u <= |S2 - S1|^2并且0 <= v <= |S3 - S1|^2,则交点M位于正方形内部,否则在外部。

最后附上一个Java实现示例(为了阅读便利进行了优化...):

public class Test {
    static class Vector3 {
        public float x, y, z;

        public Vector3(float x, float y, float z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Vector3 add(Vector3 other) {
            return new Vector3(x + other.x, y + other.y, z + other.z);
        }

        public Vector3 sub(Vector3 other) {
            return new Vector3(x - other.x, y - other.y, z - other.z);
        }

        public Vector3 scale(float f) {
            return new Vector3(x * f, y * f, z * f);
        }

        public Vector3 cross(Vector3 other) {
            return new Vector3(y * other.z - z * other.y,
                               z - other.x - x * other.z,
                               x - other.y - y * other.x);
        }

        public float dot(Vector3 other) {
            return x * other.x + y * other.y + z * other.z;
        }
    }

    public static boolean intersectRayWithSquare(Vector3 R1, Vector3 R2,
                                                 Vector3 S1, Vector3 S2, Vector3 S3) {
        // 1.
        Vector3 dS21 = S2.sub(S1);
        Vector3 dS31 = S3.sub(S1);
        Vector3 n = dS21.cross(dS31);

        // 2.
        Vector3 dR = R1.sub(R2);

        float ndotdR = n.dot(dR);

        if (Math.abs(ndotdR) < 1e-6f) { // Choose your tolerance
            return false;
        }

        float t = -n.dot(R1.sub(S1)) / ndotdR;
        Vector3 M = R1.add(dR.scale(t));

        // 3.
        Vector3 dMS1 = M.sub(S1);
        float u = dMS1.dot(dS21);
        float v = dMS1.dot(dS31);

        // 4.
        return (u >= 0.0f && u <= dS21.dot(dS21)
             && v >= 0.0f && v <= dS31.dot(dS31));
    }

    public static void main(String... args) {
        Vector3 R1 = new Vector3(0.0f, 0.0f, -1.0f);
        Vector3 R2 = new Vector3(0.0f, 0.0f,  1.0f);

        Vector3 S1 = new Vector3(-1.0f, 1.0f, 0.0f);
        Vector3 S2 = new Vector3( 1.0f, 1.0f, 0.0f);
        Vector3 S3 = new Vector3(-1.0f,-1.0f, 0.0f);

        boolean b = intersectRayWithSquare(R1, R2, S1, S2, S3);
        assert b;

        R1 = new Vector3(1.5f, 1.5f, -1.0f);
        R2 = new Vector3(1.5f, 1.5f,  1.0f);

        b = intersectRayWithSquare(R1, R2, S1, S2, S3);
        assert !b;
    }
}

非常感谢您的解释(和代码)。不过我有一个问题:我是否正确地假设点M是射线与平面相交的点? - xfx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接