SolidWorks_装配体设计10_干涉检查与碰撞检测

SolidWorks_装配体设计10_干涉检查与碰撞检测 干涉检查与碰撞检测摘要在机械设计、计算机图形学、机器人路径规划、虚拟仿真以及游戏开发等领域干涉检查Interference Check与碰撞检测Collision Detection是核心基础技术。干涉检查通常指在静态装配体中查找零件之间的重叠区域而碰撞检测则侧重于动态运动中物体是否发生接触或穿透。本文将从基本概念出发深入剖析这两类问题的数学原理与算法实现涵盖AABBAxis-Aligned Bounding Box、OBBOriented Bounding Box、分离轴定理SAT以及GJKGilbert-Johnson-Keerthi算法并提供完整的C代码示例帮助读者从理论到实践全面掌握该技术。1. 引言想象一下你设计了一个复杂的机械臂在装配时发现两个齿轮“长”在了一起或者你正在开发一款3D游戏角色穿墙而过——这些问题的根源都是干涉或碰撞没有被正确检测。干涉检查关注的是静态物体之间的几何重叠例如在CAD软件中检查装配体是否存在零件干涉。碰撞检测则关注动态过程中物体间的接触与穿透例如机器人运动规划中避免与环境障碍物相撞。尽管目标略有不同但底层算法高度重合都需要高效判断两个几何体是否相交。本文将以循序渐进的方式从最简单的包围盒方法讲起逐步深入到凸体碰撞检测的经典算法并给出可运行的代码实现。2. 基础概念与数学准备2.1 几何体表示在计算机中进行碰撞检测首先需要将物体抽象为数学表示。常见表示方法有多边形网格Polygon Mesh最通用但计算量大。隐式曲面Implicit Surface如球体、圆柱体检测简单。凸包Convex Hull许多高效算法如GJK要求物体为凸体。包围盒Bounding Volume用于快速排除不相交的物体。2.2 包围体Bounding Volume为了加速检测通常先使用简单的包围体进行粗略测试只有包围体相交时才进行精确检测。常见包围体有AABB轴对齐包围盒各边与坐标轴平行检测效率最高。OBB有向包围盒可旋转更紧密但检测稍复杂。包围球Bounding Sphere检测极快但紧密性差。k-DOP离散方向多面体紧密性与效率的折中。2.3 分离轴定理Separating Axis Theorem, SATSAT是凸体碰撞检测的核心理论如果两个凸体不相交则存在一条分离轴使得两个物体在该轴上的投影区间不重叠。反之如果所有可能的轴上投影都重叠则物体相交。对于凸多边形2D需要检查每条边的法线作为分离轴对于凸多面体3D需要检查每个面的法线以及每条边与另一物体每条边的叉积方向。3. AABB碰撞检测详解AABB是最简单也最常用的碰撞检测方法。一个AABB由最小点min和最大点max定义。3.1 算法原理两个AABB相交当且仅当它们在三个坐标轴上的投影区间都重叠。即重叠条件3D a.min.x b.max.x a.max.x b.min.x a.min.y b.max.y a.max.y b.min.y a.min.z b.max.z a.max.z b.min.z3.2 代码实现#includeiostreamstructAABB{floatminX,minY,minZ;floatmaxX,maxY,maxZ;AABB(floatminX_,floatminY_,floatminZ_,floatmaxX_,floatmaxY_,floatmaxZ_):minX(minX_),minY(minY_),minZ(minZ_),maxX(maxX_),maxY(maxY_),maxZ(maxZ_){}};boolAABBIntersect(constAABBa,constAABBb){// 检查X轴if(a.minXb.maxX||a.maxXb.minX)returnfalse;// 检查Y轴if(a.minYb.maxY||a.maxYb.minY)returnfalse;// 检查Z轴if(a.minZb.maxZ||a.maxZb.minZ)returnfalse;returntrue;}intmain(){AABBbox1(0,0,0,2,2,2);AABBbox2(1,1,1,3,3,3);AABBbox3(3,3,3,5,5,5);std::coutBox1与Box2相交: AABBIntersect(box1,box2)std::endl;// 1std::coutBox1与Box3相交: AABBIntersect(box1,box3)std::endl;// 0return0;}3.3 优缺点优点检测仅需6次比较速度极快更新AABB容易只需重新计算min/max。缺点紧密性差尤其对于旋转后的细长物体会产生大量误报。4. OBB碰撞检测与分离轴定理OBB可以随物体旋转因此比AABB更紧密。但检测也复杂得多需要应用SAT。4.1 OBB的定义一个OBB由中心点center、三个正交轴axes[3]表示方向以及三个半长halfSize[3]定义。4.2 SAT应用于OBB两个OBB的相交检测需要检查15条可能的分离轴3D中每个OBB的3个面法线共6条两个OBB的边方向两两叉积3×39条如果所有轴上投影都重叠则相交否则不相交。4.3 核心实现C#includeiostream#includecmath#includevectorstructVector3{floatx,y,z;Vector3(floatx_0,floaty_0,floatz_0):x(x_),y(y_),z(z_){}Vector3operator-(constVector3v)const{returnVector3(x-v.x,y-v.y,z-v.z);}floatdot(constVector3v)const{returnx*v.xy*v.yz*v.z;}Vector3cross(constVector3v)const{returnVector3(y*v.z-z*v.y,z*v.x-x*v.z,x*v.y-y*v.x);}floatlength()const{returnstd::sqrt(x*xy*yz*z);}};structOBB{Vector3 center;// 中心点Vector3 axes[3];// 三个正交轴单位向量Vector3 halfSize;// 三个方向的半长OBB(constVector3c,constVector3a0,constVector3a1,constVector3a2,constVector3h):center(c),halfSize(h){axes[0]a0;axes[1]a1;axes[2]a2;}};// 计算OBB在某个轴上的投影区间voidgetProjection(constOBBobb,constVector3axis,floatmin,floatmax){floatcenterProjobb.center.dot(axis);floatradius0.0f;for(inti0;i3;i){radiusstd::abs(obb.axes[i].dot(axis))*obb.halfSize.x;// 注意这里halfSize对应轴// 实际应使用对应分量但为了简化假设halfSize各分量对应各个轴}// 更精确的半径计算// radius std::abs(obb.axes[0].dot(axis)) * obb.halfSize.x// std::abs(obb.axes[1].dot(axis)) * obb.halfSize.y// std::abs(obb.axes[2].dot(axis)) * obb.halfSize.z;mincenterProj-radius;maxcenterProjradius;}// 检查两个OBB是否在给定轴上分离booloverlapOnAxis(constOBBa,constOBBb,constVector3axis){floataMin,aMax,bMin,bMax;getProjection(a,axis,aMin,aMax);getProjection(b,axis,bMin,bMax);return!(aMaxbMin||bMaxaMin);}boolOBBIntersect(constOBBa,constOBBb){// 收集所有可能的分离轴std::vectorVector3testAxes;// 添加两个OBB的面法线for(inti0;i3;i){testAxes.push_back(a.axes[i]);testAxes.push_back(b.axes[i]);}// 添加边方向叉积for(inti0;i3;i){for(intj0;j3;j){Vector3 axisa.axes[i].cross(b.axes[j]);if(axis.length()1e-6f){// 忽略零向量axisVector3(axis.x/axis.length(),axis.y/axis.length(),axis.z/axis.length());testAxes.push_back(axis);}}}// 逐轴测试for(constautoaxis:testAxes){if(!overlapOnAxis(a,b,axis)){returnfalse;// 找到分离轴不相交}}returntrue;// 所有轴都重叠相交}intmain(){// 创建两个简单的OBBOBBobb1(Vector3(0,0,0),Vector3(1,0,0),Vector3(0,1,0),Vector3(0,0,1),Vector3(1,1,1));OBBobb2(Vector3(1.5,0,0),Vector3(1,0,0),Vector3(0,1,0),Vector3(0,0,1),Vector3(1,1,1));OBBobb3(Vector3(3,0,0),Vector3(1,0,0),Vector3(0,1,0),Vector3(0,0,1),Vector3(1,1,1));std::coutOBB1与OBB2相交: OBBIntersect(obb1,obb2)std::endl;// 1std::coutOBB1与OBB3相交: OBBIntersect(obb1,obb3)std::endl;// 0return0;}4.4 注意事项代码中getProjection的半径计算使用了简化方式实际应分别乘以halfSize的对应分量。叉积可能为零向量两轴平行时需要跳过。SAT适用于凸体对于凹体需要先分解为凸体组合。5. GJK算法凸体碰撞检测的利器GJK算法是一种迭代算法用于计算两个凸体之间的最小距离并判断是否相交。它比SAT更高效尤其适合3D场景。5.1 核心思想GJK利用闵可夫斯基差Minkowski Difference两个凸体A和B的闵可夫斯基差定义为C A ⊖ B { a - b | a ∈ A, b ∈ B }两个凸体相交当且仅当原点在它们的闵可夫斯基差内部。GJK通过迭代构建一个单纯形simplex点、线段、三角形、四面体并判断它是否包含原点。5.2 支持函数Support Function支持函数返回凸体在给定方向上的最远点。例如对于球体支持函数就是沿方向移动半径对于凸多边形它是顶点中投影最大的点。// 支持函数返回凸体在方向d上的最远点Vector3support(conststd::vectorVector3vertices,constVector3d){floatmaxProj-1e9f;Vector3 bestPoint;for(constautov:vertices){floatprojv.dot(d);if(projmaxProj){maxProjproj;bestPointv;}}returnbestPoint;}// 两个凸体的闵可夫斯基差上的支持点Vector3supportMinkowski(conststd::vectorVector3vertsA,conststd::vectorVector3vertsB,constVector3d){Vector3 asupport(vertsA,d);Vector3 bsupport(vertsB,Vector3(-d.x,-d.y,-d.z));// 注意方向取反returna-b;}5.3 迭代过程GJK算法的伪代码如下1. 初始化单纯形为空选择一个初始方向d如(1,0,0) 2. 计算支持点p supportMinkowski(A, B, d) 3. 将p加入单纯形 4. 循环 a. 如果单纯形包含原点返回相交 b. 计算原点在单纯形上的最近点得到新的方向d c. 如果d点乘(p - 原点) 0说明无法接近原点返回不相交 d. 计算新支持点p supportMinkowski(A, B, d) e. 将p加入单纯形并更新单纯形只保留必要的顶点5.4 完整实现2D简化版为便于理解这里实现2D版本的GJK判断两个凸多边形是否相交。#includeiostream#includevector#includecmathstructPoint2D{floatx,y;Point2D(floatx_0,floaty_0):x(x_),y(y_){}Point2Doperator-(constPoint2Dp)const{returnPoint2D(x-p.x,y-p.y);}floatdot(constPoint2Dp)const{returnx*p.xy*p.y;}floatcross(constPoint2Dp)const{returnx*p.y-y*p.x;}Point2Doperator*(floats)const{returnPoint2D(x*s,y*s);}Point2Doperator(constPoint2Dp)const{returnPoint2D(xp.x,yp.y);}floatlength()const{returnstd::sqrt(x*xy*y);}};// 2D支持函数Point2Dsupport2D(conststd::vectorPoint2Dpoly,constPoint2Dd){floatmaxProj-1e9f;Point2D best;for(constautop:poly){floatprojp.dot(d);if(projmaxProj){maxProjproj;bestp;}}returnbest;}Point2DsupportMinkowski2D(conststd::vectorPoint2DA,conststd::vectorPoint2DB,constPoint2Dd){Point2D asupport2D(A,d);Point2D bsupport2D(B,Point2D(-d.x,-d.y));returna-b;}// 判断原点是否在线段上boolpointOnSegment(constPoint2Da,constPoint2Db,constPoint2Dp){// 先检查是否共线再检查范围floatcross(b-a).cross(p-a);if(std::abs(cross)1e-6f)returnfalse;floatdot(b-a).dot(p-a);floatlenSq(b-a).dot(b-a);returndot0dotlenSq;}// 判断原点是否在三角形内booloriginInTriangle(constPoint2Da,constPoint2Db,constPoint2Dc){// 使用重心坐标法Point2D v0c-a;Point2D v1b-a;Point2D v2Point2D(0,0)-a;floatdot00v0.dot(v0);floatdot01v0.dot(v1);floatdot02v0.dot(v2);floatdot11v1.dot(v1);floatdot12v1.dot(v2);floatinvDenom1.0f/(dot00*dot11-dot01*dot01);floatu(dot11*dot02-dot01*dot12)*invDenom;floatv(dot00*dot12-dot01*dot02)*invDenom;return(u0)(v0)(uv1);}// 2D GJK算法boolGJK2D(conststd::vectorPoint2DpolyA,conststd::vectorPoint2DpolyB){// 初始方向Point2Dd(1,0);