说明
在三维空间中,3 个点确定一个平面(3 个点不能共线)。但是有时,我们会得到多个点,进而反推出平面位置,比如知道了多边形的顶点,但是位置精确度不够高,需要根据所有点集找到最接近的平面时。
几何
最简单的算法就是,我们把这些点每三个当成一个三角形,逐个计算法线,最后把得到的法线加起来,平均一下,就得到了“最佳”法线。当然,这个计算还可以化简,化简过程我们不再推算,根据书本,化简后的公式如下:
代码
static func estimatePlane(from points:[simd_float3]) -> Plane? {
if points.count < 3 {
return nil
}
var normal = simd_float3.zero
var position = simd_float3.zero
// 从最后一个顶点开始,避免在循环中做 if 判断
var second = points.last!
for vector in points {
// 边向量乘积相加
normal.x += (second.z + vector.z) * (second.y - vector.y)
normal.y += (second.x + vector.x) * (second.z - vector.z)
normal.z += (second.y + vector.y) * (second.x - vector.x)
// 下一个顶点
second = vector
// 顺便求中心点,做为平面位置
position += (vector / Float(points.count))
}
if length_squared(normal) < 0.0001 {
return nil
}
normal = normalize(normal)
return Plane(position: position, normal: normal)
}
其他
这个方法好处是计算简单效率高,复杂度只需要 O(n)
。缺点是对顶点的排列顺序有要求:顶点顺时针排列时,得到法线为正。当点集的排列呈下面这种交叉时,正负方向的法线会互相抵消,当完全对称时,法线为 0。
那么这种情况应该怎么处理呢?很难处理,一种是调整数组顺序再算一遍(但不保证一定管用),另一种只能是更换其他算法,比如最小二乘法等。