S02E19:点到三角形上的最近点坐标

1,983 阅读2分钟

说明

三维空间中,点到三角形上最近点,可能有两种情况:最近点是三角形内部的点;最近点是三角形的某条边上一点。

几何

首先我们要做的,就是将点沿法线投影到三角形所在平面上,然后判断投影点在不在三角形内部,如果在,那么投影点就是最近点; 如果投影点不在三角形内部,则离投影点最近的某点,显然也是离原始点最近的点。

根据投影后,跨立实验的结果,可知:当投影点跨立两条边时,它显然是在顶点处。不过却不一定离顶点最近,如下图,A 是钝角,点 D 显然离边 AB 更近。 所以我们需要根据投影点跨立了多少条边进行判断处理。

  • 只跨立一条边,最近点就在这条边上;
  • 跨立了两条边,最近点可能在这两条边上;

当然,也可以根据最近的顶点角度进行判断:锐角时,显然顶点就是最近点;钝角时,再判断两条边。但是考虑到整体复杂度相差不大,代码中就直接比较了。

代码

///点到三角形的最近点坐标
static func nearestPoint(point:simd_float3, triangle:Triangle) -> simd_float3 {
    let e1 = triangle.point3 - triangle.point2
    let e2 = triangle.point1 - triangle.point3
    let e3 = triangle.point2 - triangle.point1
    
    var midWayPoint = point
    var d1 = point - triangle.point1
    var d2 = point - triangle.point2
    var d3 = point - triangle.point3
    
    let n = cross(e1, e2)
    if !n.isPerpendicular(to: d1) {
        // 点与三角形不共面
        let normalizedNormal = normalize(n)
        
        let dotValue = dot(d1, normalizedNormal)
        midWayPoint = point - dotValue * normalizedNormal
        // 点与三角形不共面,求投影点
        d1 = midWayPoint - triangle.point1
        d2 = midWayPoint - triangle.point2
        d3 = midWayPoint - triangle.point3
    }
    // 计算点是否在三角形内部
    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 midWayPoint
    } else if p1Cross * p2Cross * p3Cross > 0 {
        // 有两个小于0的,点在顶点处。(如果是钝角,可能离边更近;锐角则离端点更近)
        var segment1:Segment!
        var segment2:Segment!
        if p1Cross > 0 {
            segment1 = Segment(point1: triangle.point1, point2: triangle.point3)
            segment2 = Segment(point1: triangle.point2, point2: triangle.point1)
        } else if p2Cross > 0 {
            segment1 = Segment(point1: triangle.point2, point2: triangle.point3)
            segment2 = Segment(point1: triangle.point2, point2: triangle.point1)
        } else {
            segment1 = Segment(point1: triangle.point2, point2: triangle.point3)
            segment2 = Segment(point1: triangle.point1, point2: triangle.point3)
        }
        let t1 = Segment.nearestPointOnSegment(from: point, to: segment1)
        let t2 = Segment.nearestPointOnSegment(from: point, to: segment2)
        return distance_squared(t1, point) > distance_squared(t2, point) ? t2 : t1
    } else {
        // 有一个小于0,点在边处
        var segment:Segment!
        if p1Cross < 0 {
            segment = Segment(point1: triangle.point2, point2: triangle.point3)
        } else if p2Cross < 0 {
            segment = Segment(point1: triangle.point1, point2: triangle.point3)
        } else {
            segment = Segment(point1: triangle.point2, point2: triangle.point1)
        }
        return Segment.nearestPointOnSegment(from: point, to: segment)
    }
}

项目代码

本系列文章代码已发布在 github:ComputationalGeometrySwift