尝试优化线对圆柱交叉点

问题描述 投票:2回答:4

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

 /// 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...
c++ optimization math 3d intersection
4个回答
3
投票

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

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


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;
}

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如果你解释你使用的技术而不仅仅是转储代码,你可能会得到更多的帮助,让读者知道你正在做什么。


1
投票

你这样想过吗?

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

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

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

从臀部拍摄虽然如此希望它有所帮助(:

© www.soinside.com 2019 - 2024. All rights reserved.