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

点至3D矩形的最近点

实际上等同于计算OBB上的最近点,其中3D矩形可看做是z向为0OBB

struct Rect {
    Point c;
    Vector u[2];
    float e[2];
}

z轴为0并重写函数ClosestPtPointOBB()

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

点至三角形的最近点

给定三角形ABC以及一点P,且令ABC上距P的最近点为Q。一种解决方案是:如果P正交投影后位于三角形内部,则投影点即为最近点Q,如果P的正交投影位于三角形ABC的外部,则最近点位于三角形的某一边上。此时则遍历每一条边(ABBCCA),计算并返回P的最近点。但明显此种方法并不高效。一种较好的算法是:考查P位于三角形的哪一个Voronoi特征域中。一旦确定,只须将P正交投影至该特征域中即可获取最近点Q

P位于包含顶点AVoronoi域由穿越A的两个平面的负半空间的交集构成,其法线分别为B-AC-A


确定P位于某一边的Voronoi域,可以采用:计算PABC上正交投影的质心坐标。

若点P不属于任一顶点或边的Voronoi域,则Q必位于三角形ABC的内部,实际上为正交投影R


P点在ABC上正交投影的质心坐标为三角形RABRBCRCA的有符号面积与三角形ABC有符号面积之比。令n为三角形ABC的法线,且对于某t值,有R = P - tn,则R = uA + vB + wC的质心坐标(u,v,w)为:u = rbc/abc, v = rca/abc以及w = rab/abc。通过某些向量计算,可以达到一定程度上的简化目的。如,表达式rab可以按如下方式进行简化:

Vector n = Cross(b - a, c - a);

float rab = Dot(n, Cross(a - r, b - r));    //proportional to signed area of RAB

float rbc = Dot(n, Cross(b - r, c - r));    //proportional to signed area of RBC

float rca = Dot(n, Cross(c - r, a - r));    //proportional to signed area of RCA

float abc = rab + rbc + rca;           //proportional to signed of ABC

(上面代码中,n应该为垂直于三角形的向量(法线)。Cross代表叉乘,设α为ab夹角,则Cross(a,b)的数学意义是absinα*n。而三角形中,若已知两边及它们的夹角α,其面积则是0.5*absinα。即三角形ABC的面积=1/2*abs(AB×AC)


R的质心坐标可以直接通过P获得,而无须计算R

 

于是,首先判断P点是否在三角形3个顶点域内,是则取该顶点为最近点,否则继续判断是否在边域内,若在边域内,取投影在边上的点,否则位于三角形内部,取正交投影坐标R

 

对下面部分代码的解释:

点投影在直线上:P = A + s*AB, s = snom/(snom + sdenom)(snom + sdenom)AB长度,snom指投影到AB上的长度APsnom负表示PAAB角度大于90度)。

面与面之间夹角:float vc = Dot(n, Cross(a - p, b - p))vc负表示面之间夹角大于90度。

 

Point ClosestPtPointTriangle(Point p, Point a, Point b, Point c)
{
    Vector ab = b - a;
    Vector ac = c - a;
    Vector bc = c - b;
 
    //Compute parametric position s for projection P’ for P on AB.
    //P’ = A + s*AB, s = snom/(snom + sdenom)
    float snom = Dot(p - a, ab), sdenom = Dot(p - b, a - b);
 
    //Compute parametric position t for projection P’ of P on AC,
    //P’ = A + t*AC, t = tnom/(tnom + tdenom)
    float tnom = Dot(p - a, ac), tdenom = Dot(p - c, a - c);
 
    if(snom <= 0.0f && tnom <= 0.0f) return a;    //Vertex region early out
 
    //Compute parametric position u for projection P’ of P on BC,
    //P’ = B + u*BC, u = unom/(unom + udenom)
    float unom = Dot(p - b, bc), udenom = Dot(p - c, b - c);
 
    if(sdenom <= 0.0f && unom <= 0.0f) return b;      //Vertex region early out
    if(tdenom <= 0.0f && udenom <= 0.0f) return c;    //Vertex region early out
 
    //P is outside (or on) AB if the triple scalar product [N PA PB] <= 0
    Vector n = Cross(b - a, c - a);
    float vc = Dot(n, Cross(a - p, b - p));
    //If P outside AB and within feature region of AB,
    //return projection of P onto AB
    if(vc <= 0.0f && snom >= 0.0f && sdenom >= 0.0f)
        return a + snom / (snom + sdenom) * ab;
 
    //P is outside (or on) BC if the triple scalar product [N PB PC] <= 0
    float va = Dot(n, Cross(b - p, c - p));
    //If P outside BC and within feature region of BC,
    //return projection of P onto BC
    if(va <= 0.0f && unom >= 0.0f && udenom >= 0.0f)
        return b + unom / (unom + udenom) * bc;
 
    //P is outside (or on) CA if the triple scalar product [N PC PA] <= 0
    float vb = Dot(n, Cross(c - p, a - p));
    //If P outside CA and within feature region of CA,
    //return projection of P onto CA
    if(vb <= 0.0f && tnom >= 0.0f && tdenom >= 0.0f)
        return a + tnom / (tnom + tdenom) * ac;
 
    //P must project inside face region. Compute Q using barycentric coordinates
    float u = va / (va + vb + vc);
    float v = vb / (va + vb + vc);
    float w = 1.0f - u - v;    // = vc / (va + vb + vc)
    return u * a + v * b + w * c;
}

以上代码调用了4次叉积运算。与点积运算相比,叉积运算通常成本较高。因此,采用某种更加经济的表达式是值得考虑的。根据拉格朗日恒等式:


可表示3个标量三重积:

Vector n = Cross(b - a, c - a);

float va = Dot(n, Cross(b - p, c - p));

float vb = Dot(n, Cross(c - p, a - p));

float vc = Dot(n, Cross(a - p, b - p));

 

并利用6个点积计算对以下内容进行表达:

 

float d1 = Dot(b - a, p - a);

float d2 = Dot(c - a, p - a);

float d3 = Dot(b - a, p - b);

float d4 = Dot(c - a, p - b);

float d5 = Dot(b - a, p - c);

float d6 = Dot(c - a, p - c);

 

最终有:

float va = d3 * d6 - d5 * d4;

float vb = d5 * d2 - d1 * d6;

float vc = d1 * d4 - d3 * d2;

 

实际上,d1~d6也可以用于计算snomsdenomtnomtdenomunom以及udenom

float snom = d1;

float sdenom = -d3;

float tnom = d2;

float tdenom = -d6;

float unom = d4 - d3;

float udenom = d5 - d6;

 

其中向量n不再需要。

点到四面体的最近点

给定点P,当前问题为:计算四面体ABCD上(或之内)距离点P的最近点Q。一种较为直接的算法是:针对四面体中的每一个面,调用函数ClosestPointTriangle()并计算最近点。在多个计算结果中,返回距点P最近的点Q。独立于距离测试,还需要考查点P是否位于四面体中的面内。若P位于四面体中某一面内,则其即为最近点。


//Test if point and d lie on opposite side of plane through abc
int PointOutsideOfPlane(Point p, Point a, Point b, Point c, Point d)
{
    float signp = Dot(p - a, Cross(b - a, c - a));    //[AP AB AC]
    float signd = Dot(d - a, Cross(b - a, c - a));    //[AD AB AC]
    //Points on opposite sides if expression signs are opposite
    return signp * signd < 0.0f;
}


上诉算法运行良好且易于实现。然而该算法仍有改进余地。首先,假定顶点PVoronoi特征域存在,则QP在该正交域上的正交投影。针对四面体,需要考查总计14个特征域:4个顶点域、6个边域以及4个面域。若P不属于任何一个特征域,则必定位于四面体内部。

与前述测试方法相比,该算法似乎无明显的性能改观。然而,Voronoi域间却可以共享多数计算过程而无须重复计算。

点到凸多面体的最近点

一种易于实现的、复杂度为O(n)的算法是:计算多面体上每个面中距P的最近点,并返回一个最近结果。同时,还要兼顾测试点P是否位于H各面的背面,即P位于H内部这一情况。为了加速测试过程,面距离测试计算规定:点P位于各面的正面。

对于较大的多面体,一种较为快速的预计算方法为:以层次结构的方式构建多面体的各部分。利用这种预构造的层级方案将使最近点计算的时间复杂度达到log级别。如Dobkin-Kirkpatrick层次结构算法。

此外还有一些高效计算多面体最近点的其他一些算法如GJK算法等。

 

两直线间的最近点

对于2D空间,两直线不平行则相交,相交时最近点为交点,平行时的最近点为线上任意点。而在3D空间中则未必。故在3D空间中一个较好方案是:若两直线之间彼此并未达到足够接近,则设为不相交(未必平行)。基于此,相交测试转换为两直线间之间的距离,并检测该距离是否小于给定的阀值。

给定L1由顶点Q1P1连成,L2由顶点Q2P2连成。


针对st的几组计算结果,L1(s)lL2(t)分别对应直线上的最近点,且v(s, t) = L1(s) - L2(t)。当v具有最小长度值时,两顶点则为最近点。其实即为计算st满足以下垂直约束条件:


在此省略推导给出结果:

设a = d1·d1, b = d1·d2, c = d1·r, e = d2·d2, f = d2·r, d = ae - b^2

s = (bf - ce) / d     t = (af - bc) / d

注意:当d=0时两直线平行且需要另行处理。


两线段上的最近点


不难看出,在某些情况下,若直线间某一最近点位于相关线段的外部延长线上,该点是可以截取至相应线段的最近端点处。

若直线间的最近点皆位于各自线段的外部延长线上,则上述截取操作需要重复计算两次。

给定直线上L2一点S2(t) = P2 + td2,L1上最近点L1(s)为:

s = (S2(t) - P1)·d1/d1·d1 = (P2 + td2 - P1)·d1/d1·d1

类似地,给定直线S1上一点S1(s) = P1 + sd1,直线L2上最近点L2(t)为:

t = (S1(s) - P2)·d2/d2·d2 = (P1 + sd1 - P2)·d2/d2·d2

可以使用高斯消元法求解线性方程组。

s = (bt - c)/a

 t = (bs + f)/e

其中,a = d1·d1, b = d1·r, e = d2·d2, f = d2·r

2D线段相交计算

测试两线段ABCD是否相交,可以首先计算二者(延长线)的交点,且查看该交点是否位于各自线段的包围盒中。需注意的是,需要单独处理两线段平行这一特例。

计算两线段(延长线)交点时,显式定义直线L1的方程L1(t) = A + t(B - A),并隐式定义另一直线方程n·(X - C) = 0,其中垂直于CD。利用A+tB - A)带入方程n·(X - C) = 0并求解t


则交点P = L1(t)可将t值代入上述显式方程得到。另外,可通过检测0<=t<=1从而确定P是否位于AB包围盒内。

另一种2D线段相交测试方案为:两线段相交仅当两端点彼此位于线段两侧。可以通过判断三角形ABDABC的环绕方向加以解决,而三角形的有符号面积可以确定其环绕方向(|a × b| = |a||b|sino ,设点a(x1,y1), b(x2, y2)夹角为o|a||b|sino = |x1y2 - x2y1|,可得三角形有符号面积为0.5*x1y2 - x2y1)。 两个非零向量ab平行,当且仅当|a × b |0)。对于某些应用,可以在执行线段相交测试前线处理线段的AABB盒相交测试。对于3D空间内成本高昂的线段相交测试,这一点尤为重要。


//Return 2 times the signed triangle area. The result is positive if
//abc is ccw, negative if abc is cw, zero if abc is degenerate.
float Signed2DTriArea(Point a, Point b, Point c) {
    return (a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x);
}
 

线段和三角形最近点


在此不讨论平行情况。

最近点计算涉及下列对象:

线段PQ与三角形边AB

线段PQ与三角形边BC

线段PQ与三角形边CA

线段端点P与三角形平面(P的投影位于三角形ABC内)

线段端点Q与三角形平面(Q的投影位于三角形ABC内)

上述计算返回具有唯一最小距离的最近点作为结果。

在某些情况下,可以适当调整测试的数量以减少计算量。如,若线段两端点投影均位于三角形内部,则无需再执行线段-边的最近点测试,因为最近点必然产生于线段的某一投影点中;若只是线段的一个端点投影位于三角形内部,则只须执行一次相应的线段-边测试;当线段的投影均位于三角形外部,也只须执行一次相应的线段-边测试。对于后两种情况,可以通过计算线段端点所处的Voronoi域确定相关的线段-边测试。

两个三角形之间的最近点计算

两个三角形T1T2之间的最近点计算一般可认为:相应最近点位于某一个三角形的边上。因此,其结果转化成可对三角形中6条边之间的可能组合,执行线段-三角形的最近点计算。则包含最小(平方)距离的一组点即为最近点集合。

但线段-三角形之间距离测试成本较高。一种较好T1T2间最近点集测试方案为:一组最近点产生于两个三角形的相应边上,或产生于某一顶点与另一个三角形的内部点之间。问题转化成:计算两三角形间一对边的最近距离,以及顶点与三角形之间的最近点(相应顶点的投影位于三角形内部)。综上,需要执行6次顶点-三角形测试以及9次边-边测试。最终从多组最近点中,选择具有最小距离的一组顶点作为最终三角形间的最近点。


(三角形平行以及重合的情况需要剔除)


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内容简介  《实时碰撞检测算法技术》详细阐述了与碰撞检测问题相关的高效解决方案及相应的数据结构和算法,主要包括:碰撞检测系统中的设计问题、数学和几何学入门、包围体、基本图元测试、层次包围体技术、空间划分、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、付费专栏及课程。

余额充值