2010-11-02 33 views
2

Mon cerveau a fondu sur une routine d'intersection de segment de ligne et de cylindre sur laquelle j'ai travaillé.Essaie d'optimiser l'intersection entre la ligne et le cylindre

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

J'ai pensé de cette façon pour accélérer la découverte de savoir si la projection du point d'intersection se trouve à l'intérieur de la longueur du cylindre. Cependant, cela ne fonctionne pas et je ne peux pas vraiment l'obtenir parce que cela semble si logique: si les coordonnées x, y ou z du point projeté ne sont pas dans les limites du cylindre, cela doit être considéré comme extérieur. Il semble cependant que cela ne fonctionne pas dans la pratique.

Toute aide serait grandement appréciée!

Cheers,

Bill Kotsias

Edit: Il semble que les problèmes augmentent avec limites VITRINES, i.e. lorsque le cylindre est parallèle à l'un de l'axe. Les erreurs d'arrondi entrent dans l'équation et "l'optimisation" cesse de fonctionner correctement.

Peut-être, si la logique est correcte, les problèmes vont disparaître en insérant un peu de tolérance comme:

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

Répondre

3

Le cylindre est circulaire, à droite? Vous pouvez transformer les coordonnées de sorte que la ligne centrale du cylindre fonctionne comme l'axe Z. Ensuite, vous avez un problème 2D d'intersection d'une ligne avec un cercle. Les points d'intersection seront en termes d'un paramètre allant de 0 à 1 le long de la ligne, de sorte que vous pouvez calculer leurs positions dans ce système de coordonnées et les comparer au haut et au bas du cylindre.

Vous devriez être capable de tout faire sous une forme fermée. Aucune tolérance. Et bien sûr, vous aurez des singularités et des solutions imaginaires. Vous semblez avoir pensé à tout cela, alors je suppose que je ne suis pas sûr de la question.

2

La réponse de Mike est bonne. Pour toute forme délicate, il est préférable de trouver la matrice de transformation T qui la mappe en une belle version verticale, dans ce cas un cylindre pur avec un rayon 1. hauteur 1, ferait bien le travail. Figure votre nouvelle ligne dans ce nouvel espace, effectuez le calcul, reconvertir. Cependant, si vous cherchez à optimiser (et cela sonne comme vous l'êtes), il y a probablement des charges que vous pouvez faire. Par exemple, vous pouvez calculer la distance la plus courte entre deux lignes - probablement en utilisant la règle du produit scalaire - imaginez que vous joignez deux lignes par un fil. Ensuite, si ce thread est le plus court de tous les threads possibles, alors il sera perpendiculaire aux deux lignes, donc Thread.LineA = Thread.LineB = 0

Si la distance la plus courte est supérieure au rayon du cylindre, il est un raté.

Vous pouvez définir le lieu du cylindre en utilisant x, y, z, et le décomposer comme une équation quadratique horrible, et optimiser en calculant le discriminant en premier, et en renvoyant le non-impact si cela est négatif.

Pour définir le lieu, prendre n'importe quel point P = (x, y, z). déposez-le comme une perpendiculaire sur la ligne centrale de votre cylindre, et regardez sa grandeur au carré. si cela est égal à R^2 ce point est dans.

Ensuite, vous jetez votre ligne {s = U + lamda * V} dans ce désordre, et vous vous retrouveriez avec un peu de quadratique moche dans lamda. mais ce serait probablement plus rapide que de jouer avec des matrices, sauf si vous pouvez obtenir le matériel pour le faire (je suppose qu'OpenGL a une fonction pour que le matériel le fasse très rapidement).

Tout dépend de combien d'optimisation vous voulez; Personnellement, j'irais avec la réponse de Mike à moins qu'il y ait une très bonne raison de ne pas le faire. PS Vous pourriez obtenir plus d'aide si vous expliquez la technique que vous utilisez plutôt que de simplement jeter du code, laissant au lecteur le soin de comprendre ce que vous faites.

3

C'est ce que je l'utilise, il peut aider:

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

Avez-vous pensé à cette façon? Un cylindre est essentiellement un segment de ligne «gras». Pour ce faire, trouvez le point le plus proche sur le segment de droite (l'axe du cylindre) sur le segment de droite (le segment de ligne que vous testez pour intersection). De là, vous vérifiez la distance entre ce point le plus proche et l'autre segment de ligne, et comparez-le au rayon. À ce stade, vous avez un test «Pill vs Line Segment», mais vous pourriez probablement faire quelques tests en plan pour «couper» les capsules de la pilule pour en faire un cylindre.

Tir de la hanche un peu mais espérons que cela aide (: