《实时碰撞检测算法技术》读书笔记(五):最近点计算(上)

本文详细介绍了在三维空间中计算不同几何对象间的距离方法,包括点到平面、线段、AABB、OBB的距离计算公式及其实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点到面的最近距离

一点P以及法线n定义了一个平面π,所有该平面上的点X都满足方程n·(X - P) = 0(即从点P指向X的向量垂直于n)。现令Q为空间内任意一点,则面内距Q的最近点R为点QQ在该面上的正交投影,即从顶点Q处向平面垂直(依据法线n)移动。同时,对于值t,有R = Q - tn,将该表达式代入平面方程求解t


将t代入到R = Q - tn并得到投影点R


从等式中不难发现t为正值,则Q位于该面的正面,t为负值则Q位于该面的背面。

当平面按照格式给出时,则有表达式。因此,一点距平面最近点的代码如下所示:

Point ClosestPtPointPlane(Point q, Plane p) {
    float t = (Dot(p.n, q) - p.d) / Dot(p.n, p.n);
    return q - t * p.n;
}
 

如果平面方程采用单位向量,则t简化为t = (n·Q) - d

Q至平面的有符号距离可以通过t的计算返回值得到:

float DistPointPlane(Point q, Plane p) {
    //return Dot(p.n, q) - p.d; if plane equation normalized (||p.n|| == 1)
    return (Dot(p.n, q) - p.d) / Dot(p.n, p.n);
}


点至线段的最近点

AB为两端点AB定义的线段,给定任意一点C,该问题为:确定线段AB上距C的最近点D。可以将点C投影至AB延长线上,如果投影点P位于线段内,则点P为所求;若投影点P位于线段外部,则线段上的近C端点为最近点。

 

直线AB上的一点可参数化为P(t) = A+ t(B - A)。利用点积的投影性质,t可表示为C在直线上的投影,且t = (C - A)·n / ||B - A||,其中n = (B - A)/(||B - A||)即为AB方向上的单位向量。

若在线段内获取最近点,则有0<=t<=1,且在参数方程中代入t值即可得到最近点D。代码如下所示:

//Given segment ab and point c, copmutes closest point d on ab.
//Also returns t for the position of d, d(t) = a + t*(b - a)
void ClosestPtPointSegment(Point c, Point a, Point b, float &t, Point &d)
{
    Vector ab = b - a;
    //Progect c onto ab, computing parameterized position d(t) = a + t*(b - a)
    t = Dot(c - a, ab) / Dot(ab, ab);
    //If outside segment, clamp t(and therefore d) to the closest endpoint
    if(t < 0.0f) t = 0.0f;
    if(t > 1.0f) t = 1.0f;
    //Compute projected position from the clamped t
    d = a + t * ab;
}


如果除法操作成本高昂,则可预先保存非负的分母值并延迟该除法操作。优化后的代码如下:

//Given segment ab and point c, copmutes closest point d on ab.
//Also returns t for the position of d, d(t) = a + t*(b - a)
void ClosestPtPointSegment(Point c, Point a, Point b, float &t, Point &d)
{
    Vector ab = b - a;
    //Project c onto ab,  but deferring divide by Dot(ab,ab)
    t = Dot(c - a, ab) ;
    if(t <= 0.0f) {
        //c projects outside the [a, b] interval, on the a side;clamp to a
        t = 0.0f;
        d = a;
    } else {
        float denom = Dot(ab,ab);
        if(t >= denom) {
            //c projects outside the [a,b] interval, on the b side; clamp to b
            t = 1.0f;
            d = b;
        } else {
            //c projects inside the [a,b] interval;must do deferred divide now
            t = t / denom;
            d = a + t * ab;
        }
    }
}


点到线段的距离

一点C与线段AB之间的(平方)距离可以直接获取,而无须显式计算线段AB上距C的最近点D。如前所诉,需要考查3种情形:当AC·AB <= 0 时,点A为距C的最近点且平方距离为AC·AC;当AC·AB >= AB·AB时,点B为距离C的最近点且平方距离为BC·BC;最后一种情况,当0 < AC·AB< AB·AB时,平方距离为CD·CD,其中


由于CD·CD可化简为


则点D无需计算。实现代码如下:

//Returns the squared distance between point c and segment ab
float SqDistPointSegment(Point a, Point b, Point c)
{
    Vector ab = b - a, ac = c - a, bc = c - b;
    float e = Dot(ac, ab);
    //Handle cases where c projects outside ab
    if(e <= 0.0f) return Dot(ac, ac);
    float f = Dot(ab, ab);
    if(e >= f) return Dot(bc,bc);
    //Handle cases where c projects onto ab
    return Dot(ac, ac) - e* e / f;
}


点至AABB的最近点

//Given point p, return the point q on or in AABB b that is closest to p
void ClosestPtPointAABB(Point p, AABB b, Point &q) {
    .//For each coordinate axis, if the point coordinate value is
    //outside box, clamp it to the box, else keep it as is
    for(int i = 0; i < 3; i++) {
        float v = p[i];
        if(v < b.min[i]) v = b.min[i];    //v = max(v, b.min[i]);
        if(v > b.max[i]) v = b.max[i];    //v = min(v, b.max[i]);
        q[i] = v;
    }
}

在支持SMID指令的CPU体系结构中,上诉函数常实现为两条指令:max指令及其后的min指令。

点到AABB的距离

对于顶点P,当获取包围盒上的最近点Q并计算两者距离时,实际上无须显式计算最近点Q,如下代码所示(为了简化计算呢过程并避免昂贵的开方计算,代码中使用了平方距离):

//Compute the square distance between a point and an AABB b
float SqDistPointAABB(Point p, AABB b)
{
    float sqDist = 0.0f;
    for(int i = 0; i < 3; i++) {
        //For each axis count any excess distance outside the box extents
        float v = p[i];
        if(v < b.min[i]) sqDist += (b.min[i] - v) * (b.min[i] - v);
        if(v > b.max[i]) sqDist += (v - b.max[i]) * (v - b.max[i]);
    }
    return sqDist;
}

点至OBB的最近点

设定B为中心点为COBB,且3个正交单位向量u0u1u2定义了Bxyz轴。3个标量e0e1e2确定了沿各轴向的1/2轴长。在该表达方式中,所有B中的点S可记为S = C + au0 + bu1 + cu2,其中|a| <= e0, |b| <= e1, |c| <= e2


世界坐标空间中的一点POBB局部坐标空间中的一点Q = xyz)之间存在下列关系:P = C + xu0 + yu1 + zu2.给定一点P,其OBB空间坐标可按下列方式计算。以下只给出坐标x的推导过程:


于是将顶点P转换至OBB的局部坐标系统中,并计算OBB上距该转换点的最近点,最后将结果转换回世界坐标系统。代码如下:

//Given point p, return point q on (or in) OBB b, closest to p
void ClosestPtPointOBB(Point p, OBB b, Point &q)
{
    Vector d = p - b.c;
    //Start result at center of box; make steps from there
    q = b.c;
    //For each OBB axis...
    for(int i = 0; i < 3; i++) {
         //...project d onto that axis to get the distance
        //along the axis of d from the box center
        float dist = Dot(d, b.u[i]);
        //if distance farther than the box extents, clamp to the box
        if(dist > b.e[i]) dist = b.e[i];
        if(disr < -b.e[i]) dist = -b.e[i];
        //Step that distance along the axis to get world coordinate
        q += dist * b.u[i];
    }
}

 

点到OBB的距离

计算POBB上最近点之间的平方距离时,前述函数可调用如下:

//Compute the square distance between point p and OBB b
float SqDistPointOBB(Point p, OBB b)
{
    Point closest;
    ClosestPtPointOBB(p, b, closest);
    float sqDist = Dot(closest - p, closest - p);
    return sqDist;
}

可以只计算平方距离而不获取最近点。

//Compute the square distance between point p and OBB b
float SqDistPointOBB(Point p, OBB b)
{
    Vector v = p - b.c;
    float sqDist = 0.0f;
    for(int i = 0; i < 3; i++) {
        //Project vector from box center to p on each axis, getting the distance
        //of p along that axis, and count any excess distance outside box extents
        float d = Dot(v, b.u[i]), excess = 0.0f;
        if(d < -b.e[i])
            excess = d + b.e[i];
        else if(d > b.e[i])
            excess = d - b.e[i];
        sqDist += excess * excess;
    }
    return sqDist;
}
 



内容简介  《实时碰撞检测算法技术》详细阐述了与碰撞检测问题相关的高效解决方案及相应的数据结构和算法,主要包括:碰撞检测系统中的设计问题、数学和几何学入门、包围体、基本图元测试、层次包围体技术、空间划分、BSP树层次结构、凸体算法、基于GPU的碰撞检测、数值健壮性、几何健壮性以及优化操作。另外,《实时碰撞检测算法技术》还提供了相应的算法、代码以及伪代码,以帮助读者进一步理解计算方案的实现过程。  《实时碰撞检测算法技术》适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学教材和参考手册。第1章 概述1.1 内容概览1.2 关于本书的代码第2章 碰撞检测系统中的设计问题2.1 碰撞算法的设计因素2.2 应用程序中对象的表达方式2.3 查询类型2.4 环境模拟参数2.5 性能2.6 健壮性2.7 实现与使用的简洁性2.8 小结第3章 数学和几何学入门3.1 矩阵3.2 坐标系统和顶3.3 向量3.4 质心坐标3.5 直线、光线和线段3.6 平面和半空间3.7 多边形3.8 多面体3.9 凸包计算3.10 域3.11 Minkowski和与Minkowski差3.12 小结第4章 包围体4.1 BV期望特征4.2 轴对齐包围盒4.3 Spheres球体4.4 方向包围盒4.5 球扫掠体4.6 半空间相交体4.7 其他类型的包围体4.8 小结第5章 基本图元测试5.1 最近点计算5.2 图元测试5.3 直线、光线和有向线段的相交测试5.4 其他类型的测试5.5 动态相交测试5.6 小结第6章 层次包围体技术6.1 层次结构设计问题6.2 层次结构的构建策略6.3 层次结构的遍历6.4 包围体层次结构示例6.5 合并包围体6.6 高效的树型表达方式及遍历6.7 通过缓存机制改善查询6.8 小结第7章 空间划分第8章 BSP树层次结构第9章 凸体算法第10章 基于GPU的碰撞检测第11章 数值健壮性第12章 几何健壮性第13章 优化操作参考文献
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值