S02E18:射线与三角形交点

592 阅读1分钟

说明

射线与三角形,并不一定相交,而且交点也不一定在三角形内。

几何

首先,我们要求出射线与三角形所在平面的交点。

方法是,在射线上找一点,该点必须在射线正方向,且与三角形顶点连线垂直于三角形法线。

设未知数 x,交点 E 在射线上可得方程一:E = ray.position + x * ray.direction,而 EA 垂直于法线 AF ,可得方程二:dot(EA, AF) = 0,结合两个方程,简化一下可得:x = -dot(ray.position - A, AF) / dot(ray.direction, AF),可求得 x 的值,最终求得 E 点坐标。还需要注意的是,如果 x 大于 0,则交点在射线正方向,小于 0 则不存在交点。 接着,求得交点后,我们需要判断交点是不是在三角形内部。

这里我们使用了判断线段是否交叉的“跨立实验”,轮流判断交点与各个顶点的关系,看是否在对边的同一侧: 如上图,先判断点 A、点 D 都在直线 BC 的同一侧,再判断点 B、点 D 都在直线 AC 的同一侧,最后判断点 C、点 D 都在直线 AB 的同一侧。这样点 D 就在三角形 ABC 内部。

而图中,点 E 显然与点 B 分别在直线 AC 的两侧;点 F 不仅与点 B 分别在直线 AC 的两侧,而且与点 C 分别在直线 AB 的两侧;故它们都不在三角形内部。

代码

///射线与三角形相交,交点
static func intersectionPointBarycenricCoordinate(ray:Ray, triangle:Triangle) -> simd_float3? {
    let e1 = triangle.point3 - triangle.point2
    let e2 = triangle.point1 - triangle.point3
    let e3 = triangle.point2 - triangle.point1
    
    let n = cross(e1, e2)
    
    let v = ray.position - triangle.point1
    let rdn = dot(ray.direction, n)
    if rdn < Float.leastNormalMagnitude {
        // 射线与三角形所在平面,平行
        return nil
    }
    let x = -dot(v, n) / rdn
    if x < 0 {
        return nil
    } else {
        let targetPoint = ray.position + x * ray.direction
        let d1 = triangle.point1 - targetPoint
        let d2 = triangle.point2 - targetPoint
        let d3 = triangle.point3 - targetPoint
        // 计算点是否在三角形内部
        let p2Cross = dot(cross(d3, d1), cross(e1, -e3))
        let p3Cross = dot(cross(d2, d1), cross(e2, -e1))
        let p1Cross = dot(cross(d2, d3), cross(e3, -e2))
        
        if p1Cross >= 0 && p2Cross >= 0 && p3Cross >= 0 {
            // 全大于 0,点在三角形内部
            return targetPoint
        } else {
            return nil
        }
    }
}