如何高效判断平面a*x+b*y+c*z+d=0是否与立方体相交?
嘿,这个问题我之前做3D碰撞检测时也纠结过——枚举8个顶点代入的方法确实直观,但计算量有点冗余,咱们可以用线性函数的极值特性来大幅优化,只需要2次计算就能搞定!
先明确前提:假设这里的立方体是轴对齐的(这是最常见的场景,如果是任意旋转的立方体,后面会补充说明),中心为(x1,y1,z1),半边长为s(也就是每个坐标轴上的范围是[x1-s, x1+s]、[y1-s, y1+s]、[z1-s, z1+s])。
核心思路
平面a*x + b*y + c*z + d = 0会把三维空间分成两个半空间:a*x + b*y + c*z + d > 0和a*x + b*y + c*z + d < 0。如果立方体完全处于其中一个半空间,就和平面不相交;只要跨了两个半空间(或者刚好相切),就一定相交。
而f(x,y,z) = a*x + b*y + c*z + d是个线性函数,它在立方体上的最大值和最小值必然出现在顶点,但我们不需要枚举所有8个顶点——只需要根据平面法向量(a,b,c)的符号,直接选每个轴上能让f取到极值的端点就行,这样只算2次就能得到极值。
具体步骤
计算f的最大值
f_max:
对每个坐标轴分量,跟着法向量的符号选立方体的极值点:- 若
a > 0,取x = x1 + s;若a < 0,取x = x1 - s;a=0的话x取任意值都不影响结果 - 同理处理y和z:
b>0取y1+s,b<0取y1-s;c>0取z1+s,c<0取z1-s
把这些值代入f(x,y,z)得到f_max
- 若
计算f的最小值
f_min:
和上面反过来,选每个轴上能让f最小的端点:- 若
a > 0,取x = x1 - s;若a < 0,取x = x1 + s;a=0任意 - 同理处理y和z
代入得到f_min
- 若
判断相交情况:
- 如果
f_max < 0:整个立方体在平面的负半空间,不相交 - 如果
f_min > 0:整个立方体在平面的正半空间,不相交 - 其他情况(
f_max >= 0且f_min <= 0):平面与立方体相交(包括刚好相切的场景)
- 如果
伪代码示例
def is_plane_cube_intersect(a, b, c, d, cube_center_x, cube_center_y, cube_center_z, half_size): # 计算f_max x_max = cube_center_x + half_size if a > 0 else cube_center_x - half_size if a < 0 else cube_center_x y_max = cube_center_y + half_size if b > 0 else cube_center_y - half_size if b < 0 else cube_center_y z_max = cube_center_z + half_size if c > 0 else cube_center_z - half_size if c < 0 else cube_center_z f_max = a * x_max + b * y_max + c * z_max + d # 计算f_min x_min = cube_center_x - half_size if a > 0 else cube_center_x + half_size if a < 0 else cube_center_x y_min = cube_center_y - half_size if b > 0 else cube_center_y + half_size if b < 0 else cube_center_y z_min = cube_center_z - half_size if c > 0 else cube_center_z + half_size if c < 0 else cube_center_z f_min = a * x_min + b * y_min + c * z_min + d # 最终判断 return not (f_max < 0 or f_min > 0)
额外说明
如果你的立方体是任意旋转的,那可以用分离轴定理(SAT):检查平面的法向量以及立方体的三个局部轴向是否为分离轴,只要存在一个分离轴,就说明不相交;否则就是相交。不过轴对齐的场景用上面的方法已经是最优解了,计算量只有顶点枚举的1/4,非常适合实时渲染、游戏碰撞检测这类对性能敏感的场景。
内容的提问来源于stack exchange,提问作者Exploring_Programming

