将2D线段与圆相交函数改造为3D倾斜无限圆柱相交检测函数的方法
3D线段与无限倾斜圆柱相交检测实现方案
核心思路
无限圆柱的几何定义是空间中所有到指定轴线的距离等于半径的点的集合,我们可以通过投影降维的方式直接复用你现有的2D线段与圆相交逻辑:
- 把3D空间的所有点投影到垂直于圆柱轴线的平面上,此时无限圆柱会投影为该平面上半径等于圆柱半径的圆
- 3D线段投影后变为该平面上的2D线段,沿用原有2D相交逻辑计算交点对应的线段缩放系数
- 把缩放系数映射回3D线段,得到真实的3D交点
前置依赖说明
假设你已经实现以下基础向量运算(你的Vector类和Point结构一致,可直接参与运算):
- 向量点积:
float Dot(const Vector& a, const Vector& b) - 向量叉积:
Vector Cross(const Vector& a, const Vector& b) - 向量归一化:
Vector Normalize(const Vector& a)
具体实现代码
// 参数说明: // cylinderOrigin:圆柱轴线上的任意参考原点 // cylinderAxisDir:圆柱轴线的方向向量,函数内部会自动归一化 // cylinderRadius:圆柱半径 // lineA、lineB:待检测3D线段的两个端点 // theResults:输出相交的3D点,最多返回2个 bool Math::GetLineInfiniteCylinderIntersections(Point cylinderOrigin, Vector cylinderAxisDir, float cylinderRadius, Point lineA, Point lineB, Array<Point>& theResults) { theResults.Reset(); // 归一化圆柱轴线方向,避免计算误差 Vector axisNorm = Normalize(cylinderAxisDir); // 构建垂直于圆柱轴线的2D平面正交基 Vector refVec = (fabs(Dot(axisNorm, Vector(1,0,0))) < 0.9f) ? Vector(1,0,0) : Vector(0,1,0); Vector u = Normalize(Cross(axisNorm, refVec)); Vector v = Cross(axisNorm, u); // 3D点转垂直平面上2D坐标的投影函数 auto projectTo2D = [&](const Point& p) -> Point { Vector delta = p - cylinderOrigin; // 剔除沿圆柱轴线方向的分量,只保留垂直平面的分量 delta = delta - Dot(delta, axisNorm) * axisNorm; return Point(Dot(delta, u), Dot(delta, v)); }; Point lineA2D = projectTo2D(lineA); Point lineB2D = projectTo2D(lineB); Point circleCenter2D = Point(0, 0); // 以下完全复用你原有2D相交函数的计算逻辑 Point aBA = lineB2D - lineA2D; Point aCA = circleCenter2D - lineA2D; float aA = aBA.mX*aBA.mX + aBA.mY*aBA.mY; // 处理线段投影后为单点的特殊情况 if (aA < FLT_EPSILON) { float distSq = aCA.mX*aCA.mX + aCA.mY*aCA.mY; if (fabs(distSq - cylinderRadius*cylinderRadius) < FLT_EPSILON) { theResults.Push(lineA); } return theResults.Size() > 0; } float aBBy2 = aBA.mX*aCA.mX + aBA.mY*aCA.mY; float aC = aCA.mX*aCA.mX + aCA.mY*aCA.mY - cylinderRadius*cylinderRadius; float aPBy2 = aBBy2 / aA; float aQ = aC / aA; float aDisc = aPBy2*aPBy2 - aQ; if (aDisc < 0) return false; float aTmpSqrt = (float)sqrt(aDisc); float aABScalingFactor1 = -aPBy2 + aTmpSqrt; float aABScalingFactor2 = -aPBy2 - aTmpSqrt; // 将2D计算得到的缩放系数映射回3D线段得到交点 Vector line3DDir = lineB - lineA; auto get3DPointFromScale = [&](float scaleFactor) -> Point { // 原有2D函数的缩放因子范围[-1, 0]对应线段参数t∈[0,1] float t = -scaleFactor; return lineA + line3DDir * t; }; if (aABScalingFactor1 <= 0.0f && aABScalingFactor1 >= -1.0f) { theResults.Push(get3DPointFromScale(aABScalingFactor1)); } if (aDisc == 0) { return theResults.Size() > 0; } if (aABScalingFactor2 <= 0.0f && aABScalingFactor2 >= -1.0f) { theResults.Push(get3DPointFromScale(aABScalingFactor2)); } return theResults.Size() > 0; }
额外说明
- 如果需要适配有限长度的圆柱,只需在拿到3D交点后,额外判断交点沿圆柱轴线方向的投影是否在圆柱的长度范围内即可
- 代码中的
FLT_EPSILON可根据项目精度要求替换为自定义的极小值
内容的提问来源于stack exchange,提问作者KiraHoneybee
相关产品推荐
相关产品推荐

