尝试优化线对圆柱交叉点

时间:2010-11-02 13:59:02

标签: c++ optimization math 3d intersection

我的大脑已经融化了我一直在努力的线段与圆柱相交的例程。

 /// Line segment VS <cylinder>
 // - cylinder (A, B, r) (start point, end point, radius)
 // - line has starting point (x0, y0, z0) and ending point (x0+ux, y0+uy, z0+uz) ((ux, uy, uz) is "direction")
 // => start = (x0, y0, z0)
 //   dir = (ux, uy, uz)
 //   A
 //   B
 //   r
 //   optimize? (= don't care for t > 1)
 // <= t  = "time" of intersection
 //   norm = surface normal of intersection point
 void CollisionExecuter::cylinderVSline(const Ogre::Vector3& start, const Ogre::Vector3& dir, const Ogre::Vector3& A, const Ogre::Vector3& B, const double r,
             const bool optimize, double& t, Ogre::Vector3& normal) {
  t = NaN;

  // Solution : http://www.gamedev.net/community/forums/topic.asp?topic_id=467789
  double cxmin, cymin, czmin, cxmax, cymax, czmax;
  if (A.z < B.z) { czmin = A.z - r; czmax = B.z + r; } else { czmin = B.z - r; czmax = A.z + r; }
  if (A.y < B.y) { cymin = A.y - r; cymax = B.y + r; } else { cymin = B.y - r; cymax = A.y + r; }
  if (A.x < B.x) { cxmin = A.x - r; cxmax = B.x + r; } else { cxmin = B.x - r; cxmax = A.x + r; }
  if (optimize) {
   if (start.z >= czmax && (start.z + dir.z) > czmax) return;
   if (start.z <= czmin && (start.z + dir.z) < czmin) return;
   if (start.y >= cymax && (start.y + dir.y) > cymax) return;
   if (start.y <= cymin && (start.y + dir.y) < cymin) return;
   if (start.x >= cxmax && (start.x + dir.x) > cxmax) return;
   if (start.x <= cxmin && (start.x + dir.x) < cxmin) return;
  }

  Ogre::Vector3 AB = B - A;
  Ogre::Vector3 AO = start - A;
  Ogre::Vector3 AOxAB = AO.crossProduct(AB);
  Ogre::Vector3 VxAB  = dir.crossProduct(AB);
  double ab2 = AB.dotProduct(AB);
  double a = VxAB.dotProduct(VxAB);
  double b = 2 * VxAB.dotProduct(AOxAB);
  double c = AOxAB.dotProduct(AOxAB) - (r*r * ab2);
  double d = b * b - 4 * a * c;
  if (d < 0) return;
  double time = (-b - sqrt(d)) / (2 * a);
  if (time < 0) return;

  Ogre::Vector3 intersection = start + dir * time;        /// intersection point
  Ogre::Vector3 projection = A + (AB.dotProduct(intersection - A) / ab2) * AB; /// intersection projected onto cylinder axis
  if ((projection - A).length() + (B - projection).length() > AB.length()) return; /// THIS IS THE SLOW SAFE WAY
  //if (projection.z > czmax - r || projection.z < czmin + r ||
  // projection.y > cymax - r || projection.y < cymin + r ||
  // projection.x > cxmax - r || projection.x < cxmin + r ) return; /// THIS IS THE FASTER BUGGY WAY

  normal = (intersection - projection);
  normal.normalise();
  t = time; /// at last
 }

我想到了这种方法可以加快发现交点的投影是否位于圆柱长度内。但是,它不起作用,我不能真正得到它,因为它看起来如此合乎逻辑: 如果投影点的x,y或z坐标不在圆柱体的限制范围内,则应将其视为外部。虽然这在实践中似乎不起作用。

非常感谢任何帮助!

干杯,

比尔科茨亚斯

编辑:似乎问题随着边界情况而上升,即当圆柱体与其中一个轴平行时。舍入误差进入等式,“优化”停止正常工作。

也许,如果逻辑 正确,那么通过插入一些容差就会消除问题:

  if (projection.z > czmax - r + 0.001 || projection.z < czmin + r - 0.001 || ... etc...

4 个答案:

答案 0 :(得分:4)

这是我使用的,它可能会有所帮助:

bool d3RayCylinderIntersection(const DCylinder &cylinder,const DVector3 &org,const DVector3 &dir,float &lambda,DVector3 &normal,DVector3 &newPosition)
// Ray and cylinder intersection
// If hit, returns true and the intersection point in 'newPosition' with a normal and distance along
// the ray ('lambda')
{
  DVector3 RC;
  float d;
  float t,s;
  DVector3 n,D,O;
  float ln;
  float in,out;

  RC=org; RC.Subtract(&cylinder.position);
  n.Cross(&dir,&cylinder.axis);

  ln=n.Length();

  // Parallel? (?)
  if((ln<D3_EPSILON)&&(ln>-D3_EPSILON))
    return false;

  n.Normalize();

  d=fabs(RC.Dot(n));

  if (d<=cylinder.radius)
  {
    O.Cross(&RC,&cylinder.axis);
    //TVector::cross(RC,cylinder._Axis,O);
    t=-O.Dot(n)/ln;
    //TVector::cross(n,cylinder._Axis,O);
    O.Cross(&n,&cylinder.axis);
    O.Normalize();
    s=fabs( sqrtf(cylinder.radius*cylinder.radius-d*d) / dir.Dot(O) );

    in=t-s;
    out=t+s;

    if (in<-D3_EPSILON)
    {
      if(out<-D3_EPSILON)
        return false;
      else lambda=out;
    } else if(out<-D3_EPSILON)
    {
      lambda=in;
    } else if(in<out)
    {
      lambda=in;
    } else
    {
      lambda=out;
    }

    // Calculate intersection point
    newPosition=org;
    newPosition.x+=dir.x*lambda;
    newPosition.y+=dir.y*lambda;
    newPosition.z+=dir.z*lambda;
    DVector3 HB;
    HB=newPosition;
    HB.Subtract(&cylinder.position);

    float scale=HB.Dot(&cylinder.axis);
    normal.x=HB.x-cylinder.axis.x*scale;
    normal.y=HB.y-cylinder.axis.y*scale;
    normal.z=HB.z-cylinder.axis.z*scale;
    normal.Normalize();
    return true;
  }

  return false;
}

答案 1 :(得分:3)

圆筒是圆形的,对吗?您可以变换坐标,使圆柱的中心线作为Z轴。然后你有一个2D线与圆相交的问题。交点将是沿着线的长度从0到1的参数,因此您可以计算它们在该坐标系中的位置,并与圆柱的顶部和底部进行比较。

你应该能够以封闭的形式完成所有这些工作。没有宽容。当然,你会得到奇点和想象的解决方案。你似乎已经想到了这一切,所以我想我不确定问题是什么。

答案 2 :(得分:2)

迈克的回答很好。对于任何棘手的形状,你最好找到转换矩阵T,它将它映射成一个漂亮的直立版本,在这种情况下,一个半径为1.高度1的直接圆柱体,可以很好地完成工作。在这个新空间找出你的新行,执行计算,转换回来。

但是,如果你想要优化(听起来像你这样),你可以做一些装载。

例如,您可以计算两条线之间的最短距离 - 可能使用点积规则 - 想象一下线程连接两条线。然后如果这个线程是所有可能线程中最短的,那么它将垂直于两条线,所以Thread.LineA = Thread.LineB = 0

如果最短距离大于圆柱体的半径,则为未命中。

您可以使用x,y,z来定义圆柱的轨迹,并将整个事物作为一些可怕的二次方程式进行抖动,并通过首先计算判别式来优化,如果是负的则返回无击中。

要定义轨迹,请取任意点P =(x,y,z)。将它作为垂直于圆柱体的中心线,然后观察它的平方。如果那等于R ^ 2那个点在。

然后你将你的行{s = U + lamda * V}扔进那个混乱中,你最终会得到lamda中一些丑陋的二次方。但这可能比摆弄矩阵更快,除非你能得到硬件(我猜测OpenGL有一些功能让硬件做到这一点超高速)。

这完全取决于你想要多少优化;除非有一个非常好的理由不这样做,否则我个人会接受迈克的回答。

PS如果你解释你使用的技术而不仅仅是转储代码,你可以得到更多的帮助,让读者知道你正在做什么。

答案 3 :(得分:1)

你有没有这样想过?

圆柱体本质上是一个“胖”线段,因此一种方法是找到线段(圆柱体的中心线)上的最近点到线段(您正在测试交叉线的线段)。 / p>

从那里,检查这个最近点和另一个线段之间的距离,并将其与半径进行比较。

此时,你有一个“Pill vs Line Segment”测试,但是你可以做一些平面测试来“切掉”药丸上的盖子来制作一个圆柱体。

从臀部拍摄一点但是希望它有所帮助(: