Irrlicht 中使用 plane3d 表示一个 3D平面
plane3d 中使用 平面上一点(MPoint) 和 平面法线向量(Normal) 来唯一标识一个平面
( 当然标识一个平面还可以使用 三个点 或者 一条直线 AND 直线外一点 ,但是它们不方便于运算)
( 比如 对正面的表示,如果是三个点的话就要根据三个点的排列顺序是顺时针还是逆时针来判断 )
( 但是有法向量的话,法向量正方向指向的一边就是正面 )
【构造函数】
有五种构造一个 3D平面 的方法
0. 无参构造函数:默认的平面是 XOZ 平面, 即是 X轴 和 Z轴 所在的平面
1. 使用 平面上一点 和 法向量 构造:这也是 plane3d 里的表示方法
2. 使用六个数值来构造:前面三个是平面上点的坐标,后面三个是法向量的三个分量
3. 使用另外一个 plane3d 来复制构造
4. 使用 三个点 来构造:三个不同的点确定一个平面,在内部也是转换成平面内一点和法向量的表示形式
plane3d(): MPoint(0,0,0), Normal(0,1,0) {}; // 默认平面是 XOZ 平面,即 X轴 和 Z轴 所在平面
plane3d(const vector3d& MPoint, const vector3d& Normal) : MPoint(MPoint), Normal(Normal) {}
plane3d(T px, T py, T pz, T nx, T ny, T nz) : MPoint(px, py, pz), Normal(nx, ny, nz) {}
plane3d(const plane3d& other) :MPoint(other.MPoint), Normal(other.Normal) {}
plane3d(const vector3d& point1, const vector3d& point2, const vector3d& point3) {setPlane(point1, point2, point3);}
【setPlane()】
这里只简单分析一下使用三个点设置一个平面的 setPlane()
首先挑出两对不同的点连接成平面上的向量,两向量求叉积即可得到法向量
再随便取其中一个点作为平面中一点,就妥了~
【求直线与平面的交点】
这是目前 plane3d 中最复杂的一个方法,也是很有用的一个方法
首先说明一下方法中直线的表示方法: 直线上一点(linePoint) 和平行于直线的向量(lineVect)
源码中首先判断直线是否平行于平面,只有在直线不平行于平面的情况下才可以求交点
判断直线是否平行于平面的方法十分简单,即是求 法向量 与 平行于直线的向量 的点积
1. 如果直线平行于平面,直线与法向量垂直
2. 如果两向量垂直,则点积为 0 ( a·b = |a|·|b|·cosθ , 而 cos90° == 0 )
所以直接判断 Normal.dotProduct(lineVect) 是否为 0 就可以了
其次是求直线与平面的交点坐标
源码中对于求交点坐标的功能只用了两行代码 ( 但还是直接把博主第一眼给看得蒙圈了 ヾ(。`Д´。) )
不过博主还是比较成功地推导出了这个计算的方法
( 当然,大家也可以跳过这一部分去网上看资料或者自己推导一下 )
//////////////////////////////////////////////// "学霸" buff 开启 ////////////////////////////////////////////////
首先设当前平面上点 MPoint ( mx, my, mz )
当前平面法向量 Normal ( nx, ny, nz )
直线上一点 linePoint( lx, ly, lz )
与直线平行的向量 lineVect ( vx, vy, vz )
直线与平面的交点 P ( px, py, pz )
因为 P 在直线上,所以有 linePoint + t * lineVect = linePoint->P,即存在 ①式 :
px = lx + t * vx
py = ly + t * vy ( t 为未知量 )
pz = lz + t * vz
又因为 P 在平面上,所以 MPoint->P 垂直于法向量 Normal,即存在 ②式 :
(px - mx) * nx + (py - my) * ny + (pz - mz) * nz == 0
将两式联立,使用 ①式 中 px, py, pz 替换 ②式 中 pz, py, pz , 得到
(lx + t*vx - mx) * nx + (py + t*vy - my) * ny + (pz + t*vz - mz) * nz == 0
(lx - mx) * nx + (py - my) * ny + (pz - mz) * nz + t*vx*nx + t*vy*ny + t*vz*nz == 0 ( 提取带有 t 的项 )
(lx - mx) * nx + (py - my) * ny + (pz - mz) * nz + t * (vx*nx + vy*ny + vz*nz) == 0 ( 提取未知量 t )
t * (vx*nx + vy*ny + vz*nz) == - ( (lx - mx) * nx + (py - my) * ny + (pz - mz) * nz )
t == - ( (lx - mx) * nx + (py - my) * ny + (pz - mz) * nz ) / (vx*nx + vy*ny + vz*nz)
最后求出了 t 的表示,看上去还是有点长,但是仔细分析一下就能很大的化简
下面将等式的分子和分母分别标记为 红色部分 和 蓝色部分
t == - ( (lx - mx) * nx + (py - my) * ny + (pz - mz) * nz ) / (vx*nx + vy*ny + vz*nz)
首先看蓝色的分母部分
可以很容易就发现分母就是 直线向量lineVect 与 法向量Normal 的点积结果
然后看红色的分子部分
也可以很容易地发现这其实就是 向量 MPoint->linePoint 与 法线向量的点积结果
当然,拆开之后就是源码中的表示方法,即将 linePoint 和 MPoint 都看作是起点为原点的向量
然后分别与 Normal 求点积再相减
//////////////////////////////////////////////// "学霸" buff 结束 ////////////////////////////////////////////////
bool getIntersectionWithLine(const vector3d& linePoint, const vector3d& lineVect, vector3d& outIntersection) const
{
T t2 = Normal.dotProduct(lineVect);
if (t2 == 0) return false;
T t = - (Normal.dotProduct(linePoint) - MPoint.dotProduct(Normal)) / t2;
outIntersection = linePoint + (lineVect * t);
return true;
}
【判断点在平面的哪一侧】
plane3d 中使用点积的正负来判断点位于平面的哪一侧
两向量 a , b , a · b = |a| · |b| · cosθ
当 0° < θ < 90° , cosθ > 0
当 90° < θ < 180° , cosθ < 0
当 θ == 90° , cosθ == 0
于是构造一个从当前点到平面上点的向量,即 point->MPoint
使之与法向量 Normal 求点积结果
根据结果的符号即可判断点位于平面的哪一侧
EIntersectionRelation3D classifyPointRelation(const vector3d& point) const
{
f32 pktprd = (MPoint - point).dotProduct(Normal);
if (pktprd < -ROUNDING_ERROR) // 在正面一侧
return ISREL3D_FRONT;
if (pktprd > ROUNDING_ERROR) // 在背面一侧
return ISREL3D_BACK;
return ISREL3D_PLANAR; // 在平面上
}
plane3d 中对3D数学有了进一步的要求
其中关于向量点积叉积等运算在前面的【vector3d】笔记中有介绍
如果有错误或者理解不周到的地方,还请大家多多提出意见哦!
【plane3dex.h】
咳咳。。。博主好像前面把它落下了
这个类比起 plane3d 好像并没有什么太神奇的地方
只是增加了一个 D 变量,记录的是 ( -MPoint.dotProduct(Normal) ),并在每一次调用 setPlane() 时更新
于是在每一次 getIntersectionWithLine() 时可以省去计算 MPoint.dotProduct(Normal) 的步骤
所以对于频繁调用 getIntersection() 的情景中可以节约运算时间
刚写完就要被拉去调 BUG ,不要啊 ... ヽ(≧□≦)ノ