说明
三维空间中,点到三角形上最近点,可能有两种情况:最近点是三角形内部的点;最近点是三角形的某条边上一点。
几何
首先我们要做的,就是将点沿法线投影到三角形所在平面上,然后判断投影点在不在三角形内部,如果在,那么投影点就是最近点;
如果投影点不在三角形内部,则离投影点最近的某点,显然也是离原始点最近的点。
根据投影后,跨立实验的结果,可知:当投影点跨立两条边时,它显然是在顶点处。不过却不一定离顶点最近,如下图,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