OpenGL 射线 OBB 相交

Posted

技术标签:

【中文标题】OpenGL 射线 OBB 相交【英文标题】:OpenGL ray OBB intersection 【发布时间】:2018-10-20 07:41:15 【问题描述】:

我想在 3D 中实现对象拾取,所以我有一个 Ray 从屏幕上的一个点到场景使用 glm::unproject 方法“它返回 Y 翻转所以我使用它的负值”,以下代码成功总是当对象以世界原点为中心但另一个对象被变换并且相机移动或旋转时,它可能成功也可能不成功,我模拟了光线并且它已经与对象相交,所有坐标都在世界空间中。

bool Engine::IntersectBox(Ray& ray,BoundingBox* boundingBox,GLfloat& distance)
V3* v=boundingBox->getVertices();
glm::vec4 vec(v->x,v->y,v->z,1);
vec=boundingBox->getMatrix()*vec;
GLfloat minX=vec.x;
GLfloat minY=vec.y;
GLfloat minZ=vec.z;
GLfloat maxX=vec.x;
GLfloat maxY=vec.y;
GLfloat maxZ=vec.z;
for(int i=0;i<8;i++)
    v++;
    vec=glm::vec4(v->x,v->y,v->z,1);
    vec=boundingBox->getMatrix()*vec;
    minX=minX<vec.x?minX:vec.x;
    minY=minY<vec.y?minY:vec.y;
    minZ=minZ<vec.z?minZ:vec.z;
    maxX=maxX>vec.x?maxX:vec.x;
    maxY=maxY>vec.y?maxY:vec.y;
    maxZ=maxZ>vec.z?maxZ:vec.z;

GLfloat tMin = 0.0f;
GLfloat tMax = 100000.0f;
glm::vec3 delta=glm::vec3(boundingBox->getMatrix()[3])-ray.getOrigin();

    glm::vec3 xAxis=boundingBox->getMatrix()[0];
    GLfloat e = glm::dot(xAxis, delta);
    GLfloat f = glm::dot(ray.getDirection(), xAxis);
    if ( fabs(f) > 0.001f )  // Standard case
        GLfloat min = (e+minX)/f; // Intersection with the "left" plane
        GLfloat max = (e+maxX)/f; // Intersection with the "right" plane
        if(min<max)
            tMin=min;
            tMax=max;
        
        else
            tMin=max;
            tMax=min;
        
        if (tMax < tMin)
            return false;
    
    else
        if(-e+minX > 0.0f || -e+maxX < 0.0f)
            return false;
    


    glm::vec3 yAxis=boundingBox->getMatrix()[1];
    GLfloat e = glm::dot(yAxis, delta);
    GLfloat f = glm::dot(ray.getDirection(), yAxis);
    if ( fabs(f) > 0.001f )
        GLfloat min = (e+minY)/f;
        GLfloat max = (e+maxY)/f;
        if(min<max)
            tMin=glm::max(tMin,min);
            tMax=glm::min(tMax,max);
        
        else
            tMin=glm::max(tMin,max);
            tMax=glm::min(tMax,min);
        
        if (tMax < tMin)
            return false;
    else
        if(-e+minY > 0.0f || -e+maxY < 0.0f)
            return false;
    


    glm::vec3 zAxis=boundingBox->getMatrix()[2];
    GLfloat e = glm::dot(zAxis, delta);
    GLfloat f = glm::dot(ray.getDirection(),zAxis);
    if ( fabs(f) > 0.001f )
        GLfloat min = (e+minZ)/f;
        GLfloat max = (e+maxZ)/f;
        if(min<max)
            tMin=glm::max(tMin,min);
            tMax=glm::min(tMax,max);
        
        else
            tMin=glm::max(tMin,max);
            tMax=glm::min(tMax,min);
        
        if (tMax < tMin)
            return false;
    else
        if(-e+minZ > 0.0f || -e+maxZ < 0.0f)
            return false;
    

distance = tMin;
return true;

【问题讨论】:

请参阅OpenGL 3D-raypicking with high poly meshes 获取像素完美O(1) 方法,无需交集数学...您只需为对象索引添加一个渲染缓冲区... 我想使用 Ray-OBB 相交方法,因为我想知道 CPU 端的选定对象来进行平移旋转或缩放等任何操作 那么如果我使用 GPU 选择对象,我可以在 CPU 上知道我选择了哪个对象吗? 是的,该链接有一个旧的 OpenGL 样式示例,因此没有着色器......它只是将鼠标位置转换为 OpenGL NDC 位置读取深度和索引缓冲区,因此 CPU 知道您所在的对象的全局 3D 位置和索引鼠标点...这就是您所需要的... 所以你正在做这样的事情:Compute objects moving with arrows and mouse ?我通过使用我之前为围绕对象局部轴的移动和旋转链接的光线拾取从该答案中获得了升级的代码。如果你感兴趣,我可以从中创建一个答案... 【参考方案1】:

我正在使用:

OpenGL 3D-raypicking with high poly meshes

这个想法是除了渲染到屏幕之外,还将每个对象的索引渲染到单独的看不见的缓冲区(颜色附件、模板、阴影......),而不仅仅是从这个缓冲区和深度中选择鼠标位置的像素......它提供了拾取点的 3D 位置以及它所属的对象的索引。这是非常快的O(1),几乎没有性能成本。

现在您的对象不再需要 OBB,也不再需要任何交叉点检查。取而代之的是 4x4 homogenuous matrix 形式的局部坐标系,您可以使用它轻松地将鼠标选择的 3D 位置转换为对象局部坐标,从而使对象的平移/旋转等操作变得非常容易。

这是我的旧 C++ 方法:

Compute objects moving with arrows and mouse

它不需要任何额外的库和东西。我现在如何在融合中使用以上所有内容:

//---------------------------------------------------------------------------
#ifndef _OpenGLctrl3D_h
#define _OpenGLctrl3D_h
//---------------------------------------------------------------------------
#include "gl/OpenGL3D_double.cpp" // vector and matrix math keyboard and mouse handler
//---------------------------------------------------------------------------
static reper NULL_rep;
AnsiString dbg="";
//---------------------------------------------------------------------------
class OpenGLctrl3D      // arrow translation controls (you need one for each objet)
    
public:
    reper *rep;             // points to bounded object model matrix
    double l[3],r0,r1,r2,a; // l  - size of each straight arrow
                            // r0 - tube radius
                            // r1 - arrow radius
                            // r2 - arced arrow radius
                            // a  - arrowhead size
    double a0,a1,aa;        // start,end, cone size [rad] of the arced arrow

    OpenGLctrl3D()
        
        rep=&NULL_rep;
        l[0]=3.5; r0=0.05; a0=  0.0*deg; a=0.10;
        l[1]=3.5; r1=0.25; a1=360.0*deg;
        l[2]=3.5; r2=0.50; aa= 15.0*deg;
        
    OpenGLctrl3D(OpenGLctrl3D& a)    *this=a; 
    ~OpenGLctrl3D() 
    OpenGLctrl3D* operator = (const OpenGLctrl3D *a)  *this=*a; return this; 
    //OpenGLctrl3D* operator = (const OpenGLctrl3D &a)  ...copy... return this; 

    void draw(int sel);                 // render arrows
    void mouse_select(void* sys);       // handle [camera local] mouse events (no active button)
    void mouse_edit  (void* sys);       // handle [camera local] mouse events (active button)
    ;
//---------------------------------------------------------------------------
class OpenGLctrls3D     // arrow translation controls (you need one for each objet)
    
public:
    reper *eye;                         // camera matrix
    double per[16],ndc[16];             // perspective and viewport matrices
    TShiftState sh; double mw[3],ms[3]; // actual mouse [buttons],[world units],[camera units]
    bool _redraw;                       // redraw needed?
    int sel0,sel1,_sel;                 // actualy selected item ctrl[sel0].axis=sel1 the _sel is for iteration variable
    double psel[3];                     // selected point [object local units]

    List<OpenGLctrl3D> ctrl;
    OpenGLctrls3D()  eye=&NULL_rep; matrix_one(per); matrix_one(ndc); ctrl.num=0; 
    OpenGLctrls3D(OpenGLctrls3D& a)  *this=a; 
    ~OpenGLctrls3D()
    OpenGLctrls3D* operator = (const OpenGLctrls3D *a)  *this=*a; return this; 
    //OpenGLctrls3D* operator = (const OpenGLctrls3D &a)  ...copy... return this; 
    void add(reper &rep,double *l,double r0,double r1,double r2,double a)   // add new control bounded to rep
        
        // l  - size of each straight arrow
        // r0 - tube radius
        // r1 - arrow radius
        // r2 - arced arrow radius
        // a  - arrowhead size
        ctrl.add();
        OpenGLctrl3D *c=ctrl.dat+ctrl.num-1;
        c->rep=&rep;
        vector_copy(c->l,l);
        c->r0=r0;
        c->r1=r1;
        c->r2=r2;
        c->a=a;
        
    void resize(int x0,int y0,int xs,int ys)
        
        matrix_one(ndc);
        ndc[ 0]=+divide(2.0,double(xs));
        ndc[ 5]=-divide(2.0,double(ys));
        ndc[12]=-1.0;
        ndc[13]=+1.0;
        glGetDoublev(GL_PROJECTION_MATRIX,per);
        mouse_refresh();
        
    void draw()
        
        int i;
        OpenGLctrl3D *c;
        for (c=ctrl.dat,i=0;i<ctrl.num;i++,c++)
            
            glPushMatrix();
            c->rep->use_rep();
            glMatrixMode(GL_MODELVIEW);
            glMultMatrixd(c->rep->rep);
            if (i==sel0) c->draw(sel1);
             else        c->draw(-1);
            glMatrixMode(GL_MODELVIEW);
            glPopMatrix();
            
        
    bool mouse(double mx,double my,TShiftState _sh) // handle mouse events return if redraw is needed
        
        // mouse depth [camera units]
        ms[0]=mx; ms[1]=my; sh=_sh;
        ms[2]=glReadDepth(mx,divide(-2.0,ndc[5])-my-1,per);
        // mouse x,y [pixel] ->  <-1,+1> NDC
        matrix_mul_vector(ms,ndc,ms);
        // mouse x,y <-1,+1> NDC -> [camera units]
        scr2world(mw,ms);
        return mouse_refresh();
        
    bool mouse_refresh()    // call after any view change
        
        _redraw=false;
        if (!sh.Contains(ssLeft))
            
            int _sel0=sel0; sel0=-1;
            int _sel1=sel1; sel1=-1;
            for (_sel=0;_sel<ctrl.num;_sel++) ctrl.dat[_sel].mouse_select(this);
            _redraw=((_sel0!=sel0)||(_sel1!=sel1));
            
        else
            if ((sel0>=0)&&(sel0<ctrl.num)) ctrl.dat[sel0].mouse_edit(this);
            
        return _redraw;
        
    void world2scr(double *s,double *w)
        
        // camera [LCS]
        eye->g2l(s,w);
        // [camera units] -> <-1,+1> NDC
        s[0]=-divide(s[0]*per[0],s[2]);
        s[1]=-divide(s[1]*per[5],s[2]);
        
    void scr2world(double *w,double *s)
        
        // <-1,+1> NDC -> [camera units]
        w[0]=-divide(s[0]*s[2],per[0]);
        w[1]=-divide(s[1]*s[2],per[5]);
        w[2]=s[2];
        // world [GCS]
        eye->l2g(w,w);
        
    ;
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void OpenGLctrl3D::draw(int sel)
    
    if (sel==0) glColor3f(1.0,0.0,0.0); else glColor3f(0.5,0.0,0.0); glArrowx(0.0,0.0,0.0,r0,r1,l[0],a);
    if (sel==1) glColor3f(0.0,1.0,0.0); else glColor3f(0.0,0.5,0.0); glArrowy(0.0,0.0,0.0,r0,r1,l[1],a);
    if (sel==2) glColor3f(0.0,0.0,1.0); else glColor3f(0.0,0.0,0.5); glArrowz(0.0,0.0,0.0,r0,r1,l[2],a);
    if (sel==3) glColor3f(1.0,0.0,0.0); else glColor3f(0.5,0.0,0.0); glCircleArrowyz(0.0,0.0,0.0,r2,r0,r1,a0,a1,aa);
    if (sel==4) glColor3f(0.0,1.0,0.0); else glColor3f(0.0,0.5,0.0); glCircleArrowzx(0.0,0.0,0.0,r2,r0,r1,a0,a1,aa);
    if (sel==5) glColor3f(0.0,0.0,1.0); else glColor3f(0.0,0.0,0.5); glCircleArrowxy(0.0,0.0,0.0,r2,r0,r1,a0,a1,aa);
    
//---------------------------------------------------------------------------
void OpenGLctrl3D::mouse_select(void *_sys)
    
    OpenGLctrls3D *sys=(OpenGLctrls3D*)_sys;
    int i,x,y,z; double p[3],q[3],pm[3],t,r;
    // mouse [object local units]
    rep->g2l(pm,sys->mw);
    // straight arrows
    for (i=0;i<3;i++)
        
        t=pm[i]; pm[i]=0.0; r=vector_len(pm); pm[i]=t;
        t=divide(l[i]-t,a);
        if ((t>=0.0)&&(t<=1.0)&&(r<=r1*t))  // straight cone
            
            sys->sel0=sys->_sel;
            sys->sel1=i;
            vector_ld(sys->psel,0.0,0.0,0.0); sys->psel[i]=pm[i];
            
        
    // arced arrows
    for (i=0;i<3;i++)
        
        if (i==0) x=1; y=2; z=0; 
        if (i==1) x=2; y=0; z=1; 
        if (i==2) x=0; y=1; z=2; 
        t=atanxy(pm[x],pm[y]);
        p[x]=r2*cos(t);
        p[y]=r2*sin(t);
        p[z]=0.0;
        vector_sub(q,p,pm);
        r=vector_len(q);
        if (r<=r0*2.0)
            
            sys->sel0=sys->_sel;
            sys->sel1=i+3;
            vector_copy(sys->psel,p);
            
        
    
//---------------------------------------------------------------------------
void OpenGLctrl3D::mouse_edit(void *_sys)
    
    OpenGLctrls3D *sys=(OpenGLctrls3D*)_sys;
    // drag straight arrows (active button)
    if ((sys->sel1>=0)&&(sys->sel1<3))
        
        double z0,z1,z2,t0;
        double q[3],q0[3],q1[3],t;
        // q0 = mouse change in 2D screen space
        rep->l2g(q0,sys->psel);                 // selected point position
        sys->world2scr(q0,q0);
        vector_sub(q0,q0,sys->ms); q0[2]=0.0;   // actual mouse position
        // q1 = selected axis step in 2D screen space
        rep->l2g(q,sys->psel);                  // selected point position
        sys->world2scr(q,q);
        vector_copy(q1,sys->psel);              // axis step
        q1[sys->sel1]+=1.0;
        rep->l2g(q1,q1);
        sys->world2scr(q1,q1);
        vector_sub(q1,q1,q); q1[2]=0.0;
        // compute approx change
        t=-vector_mul(q0,q1);                   // dot(q0,q1)
        // enhance precision of t
        int i; double len0,len,dq[3]=0.0,0.0,0.0,dt;
        // selected arrow direction
        dq[sys->sel1]=1.0;
        // closest point on axis to psel
        for (len0=-1.0,dt=0.25*t;fabs(dt)>1e-5;t+=dt)
            
            // position on axis p(t) = p0 + t*dp
            for (i=0;i<3;i++) q[i]=sys->psel[i]+(t*dq[i]);
            // len = distance to mouse
            rep->l2g(q,q);
            sys->world2scr(q,q);
            vector_sub(q,q,sys->ms); q[2]=0.0;
            len=vector_len2(q);
            // handle iteration step
            if (len0<-0.5) len0=len;
            if (len>len0) dt=-0.1*dt;
            len0=len;
            
        // translate by change
        double m[16]=
            
            1.0,0.0,0.0,0.0,
            0.0,1.0,0.0,0.0,
            0.0,0.0,1.0,0.0,
            0.0,0.0,0.0,1.0,
            ;
        m[12+sys->sel1]=t;
        rep->use_rep();
        matrix_mul(rep->rep,m,rep->rep);
        rep->_inv=0;
        sys->_redraw=true;
        
    // rotate arced arrows (active button)
    if ((sys->sel1>=3)&&(sys->sel1<6))
        
        int i,x,y,z; double t,t0,tt,dt,len,len0,q[3];
        if (sys->sel1==3) x=1; y=2; z=0; 
        if (sys->sel1==4) x=2; y=0; z=1; 
        if (sys->sel1==5) x=0; y=1; z=2; 
        t0=atanxy(sys->psel[x],sys->psel[y]);
        // initial search
        for (i=10,t=0.0,dt=divide(1.0,i),len0=-1.0;i--;t+=dt)
            
            q[x]=r2*cos(t0+t);
            q[y]=r2*sin(t0+t);
            q[z]=0.0;
            rep->l2g(q,q);
            sys->world2scr(q,q);
            vector_sub(q,q,sys->ms); q[2]=0.0;
            len=vector_len2(q);
            if ((len0<-0.5)||(len<len0))  len0=len; tt=t; 
            
        // closest angle to psel
        for (t=tt;fabs(dt)>0.1*deg;t+=dt)
            
            q[x]=r2*cos(t0+t);
            q[y]=r2*sin(t0+t);
            q[z]=0.0;
            rep->l2g(q,q);
            sys->world2scr(q,q);
            vector_sub(q,q,sys->ms); q[2]=0.0;
            len=vector_len2(q);
            // handle iteration step
            if (len>len0) dt=-0.1*dt; else  tt=t; 
            len0=len;
            
        // rotate
        if (sys->sel1==3) rep->lrotx(tt);
        if (sys->sel1==4) rep->lroty(tt);
        if (sys->sel1==5) rep->lrotz(tt);
        sys->_redraw=true;
        
    
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

与上面链接中的示例不同,它使用了很多未提供的东西(来自我的 GL 引擎),因此您不能直接使用它,但它应该足以掌握基础知识。这里有一些它使用的外部东西(不是全部):

我也使用我的动态列表模板,所以:

List&lt;double&gt; xxx;double xxx[]; 相同 xxx.add(5);5 添加到列表末尾 xxx[7]访问数组元素(安全) xxx.dat[7]访问数组元素(不安全但快速直接访问) xxx.num是数组实际使用的大小 xxx.reset() 清除数组并设置xxx.num=0 xxx.allocate(100)100 项目预分配空间

渲染:

//---------------------------------------------------------------------------
void glArrowx(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r0,GLfloat r1,GLfloat l0,GLfloat l1)
    
    double pos[3]= x0, y0, z0;
    double dir[3]=1.0,0.0,0.0;
    glArrow3D(pos,dir,r0,r1,l0,l1);
    
//---------------------------------------------------------------------------
void glArrowy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r0,GLfloat r1,GLfloat l0,GLfloat l1)
    
    double pos[3]= x0, y0, z0;
    double dir[3]=0.0,1.0,0.0;
    glArrow3D(pos,dir,r0,r1,l0,l1);
    
//---------------------------------------------------------------------------
void glArrowz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r0,GLfloat r1,GLfloat l0,GLfloat l1)
    
    double pos[3]= x0, y0, z0;
    double dir[3]=0.0,0.0,1.0;
    glArrow3D(pos,dir,r0,r1,l0,l1);
    
//---------------------------------------------------------------------------
void glCircleArrowxy(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
    
    double pos[3]= x0, y0, z0;
    double nor[3]=0.0,0.0,1.0;
    double bin[3]=1.0,0.0,0.0;
    glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
    
//---------------------------------------------------------------------------
void glCircleArrowyz(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
    
    double pos[3]= x0, y0, z0;
    double nor[3]=1.0,0.0,0.0;
    double bin[3]=0.0,1.0,0.0;
    glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
    
//---------------------------------------------------------------------------
void glCircleArrowzx(GLfloat x0,GLfloat y0,GLfloat z0,GLfloat r,GLfloat r0,GLfloat r1,GLfloat a0,GLfloat a1,GLfloat aa)
    
    double pos[3]= x0, y0, z0;
    double nor[3]=0.0,1.0,0.0;
    double bin[3]=0.0,0.0,1.0;
    glCircleArrow3D(pos,nor,bin,r,r0,r1,a0,a1,aa);
    
//---------------------------------------------------------------------------
void glArrow3D(double *pos,double *dir,double r0,double r1,double l0,double l1)
    
    int i,n=_glCircleN;
    double nn=1.0,a,da=divide(pi2,n),p[3],dp[3],x[3],y[3],p0[3],p1[3],c,s,q;
    if (l0<0.0)  da=-da; nn=-nn; l1=-l1; 
    // TBN
         if (fabs(dir[0]-dir[1])>1e-6) vector_ld(x,dir[1],dir[0],dir[2]);
    else if (fabs(dir[0]-dir[2])>1e-6) vector_ld(x,dir[2],dir[1],dir[0]);
    else if (fabs(dir[1]-dir[2])>1e-6) vector_ld(x,dir[0],dir[2],dir[1]);
    else                       vector_ld(x,1.0,0.0,0.0);
    vector_one(dir,dir);
    vector_mul(x,x,dir);
    vector_mul(y,x,dir);
    vector_mul(p0,dir,l0-l1); vector_add(p0,pos,p0);
    vector_mul(p1,dir,l0   ); vector_add(p1,pos,p1);
    // disc r0, 0
    vector_len(x,x,r0);
    vector_len(y,y,r0);
    glBegin(GL_TRIANGLE_FAN);
    vector_mul(p,dir,-nn);
    glNormal3dv(p);
    glVertex3dv(pos);
    for (a=0.0,i=0;i<=n;i++,a+=da)
        
        vector_mul(dp,x,cos(a)); vector_add(p,pos,dp);
        vector_mul(dp,y,sin(a)); vector_add(p,p  ,dp);
        glVertex3dv(p);
        
    glEnd();

    // tube r0, 0..l0-l1
    q=divide(1.0,r0);
    glBegin(GL_QUAD_STRIP);
    for (a=0.0,i=0;i<=n;i++,a+=da)
        
        vector_mul( p,x,cos(a));
        vector_mul(dp,y,sin(a)); vector_add(dp,p ,dp); vector_add(p,pos,dp);
        vector_mul(dp,dp,q);
        glNormal3dv(dp);
        glVertex3dv(p);
        vector_sub(p,p,pos);
        vector_add(p,p,p0);
        glVertex3dv(p);
        
    glEnd();

    // disc r1, l0-l1
    vector_len(x,x,r1);
    vector_len(y,y,r1);
    glBegin(GL_TRIANGLE_FAN);
    vector_mul(p,dir,-nn);
    glNormal3dv(p);
    glVertex3dv(p0);
    for (a=0.0,i=0;i<=n;i++,a+=da)
        
        vector_mul(dp,x,cos(a)); vector_add(p,p0 ,dp);
        vector_mul(dp,y,sin(a)); vector_add(p,p  ,dp);
        glVertex3dv(p);
        
    glEnd();

    // cone r1..0, l0-l1..l0
    glBegin(GL_TRIANGLE_STRIP);
    q=divide(1.0,sqrt((l1*l1)+(r1*r1)));
    for (a=0.0,i=0;i<=n;i++,a+=da)
        
        vector_mul( p,x,cos(a));
        vector_mul(dp,y,sin(a)); vector_add(dp,p ,dp); vector_add(p,p0,dp);
        vector_mul(dp,dp,q);
        glNormal3dv(dp);
        glVertex3dv(p);
        glVertex3dv(p1);
        
    glEnd();
    
//---------------------------------------------------------------------------
void glCircleArrow3D(double *pos,double *nor,double *bin,double r,double r0,double r1,double a0,double a1,double aa)
    
    int e,i,j,N=3*_glCircleN;
    double U[3],V[3],u,v;
    double a,b,da,db=pi2/double(_glCircleN-1),a2,rr;
    double *ptab,*p0,*p1,*n0,*n1,*pp,p[3],q[3],c[3],n[3],tan[3];
    // buffers
    ptab=new double [12*_glCircleN]; if (ptab==NULL) return;
    p0=ptab+(0*_glCircleN);
    n0=ptab+(3*_glCircleN);
    p1=ptab+(6*_glCircleN);
    n1=ptab+(9*_glCircleN);
    // prepare angles
    a2=a1; da=db; aa=fabs(aa);
    if (a0>a1)  da=-da; aa=-aa; 
    a1-=aa;
    // compute missing basis vectors
    vector_copy(U,nor);         // U is normal to arrow plane
    vector_mul(tan,nor,bin);    // tangent is perpendicular to normal and binormal
    // arc interpolation a=<a0,a2>
    for (e=0,j=0,a=a0;e<5;j++,a+=da)
        
        // end conditions
        if (e==0)   // e=0
            
            if ((da>0.0)&&(a>=a1))  a=a1; e++; 
            if ((da<0.0)&&(a<=a1))  a=a1; e++; 
            rr=r0;
            
        else       // e=1,2,3,4
            if ((da>0.0)&&(a>=a2))  a=a2; e++; 
            if ((da<0.0)&&(a<=a2))  a=a2; e++; 
            rr=r1*fabs(divide(a-a2,a2-a1));
            
        // compute actual tube segment center c[3]
        u=r*cos(a);
        v=r*sin(a);
        vector_mul(p,bin,u);
        vector_mul(q,tan,v);
        vector_add(c,p,  q);
        vector_add(c,c,pos);
        // V is unit direction from arrow center to tube segment center
        vector_sub(V,c,pos);
        vector_one(V,V);
        // tube segment interpolation
        for (b=0.0,i=0;i<N;i+=3,b+=db)
            
            u=cos(b);
            v=sin(b);
            vector_mul(p,U,u);      // normal
            vector_mul(q,V,v);
            vector_add(n1+i,p,q);
            vector_mul(p,n1+i,rr);  // vertex
            vector_add(p1+i,p,c);
            
        if (e>1)                    // recompute normals for cone
            
            for (i=3;i<N;i+=3)
                
                vector_sub(p,p0+i  ,p1+i);
                vector_sub(q,p1+i-3,p1+i);
                vector_mul(p,p,q);
                vector_one(n1+i,p);
                
            vector_sub(p,p0    ,p1);
            vector_sub(q,p1+N-3,p1);
            vector_mul(p,q,p);
            vector_one(n1,p);
            if (da>0.0) for (i=0;i<N;i+=3) vector_neg(n1+i,n1+i);
            if (e==  3) for (i=0;i<N;i+=3) vector_copy(n0+i,n1+i);
            
        // render base disc
        if (!j)
            
            vector_mul(n,V,U);
            glBegin(GL_TRIANGLE_FAN);
            glNormal3dv(n);
            glVertex3dv(c);
            if (da<0.0) for (i=  0;i< N;i+=3) glVertex3dv(p1+i);
            else        for (i=N-3;i>=0;i-=3) glVertex3dv(p1+i);
            glEnd();
            
        // render tube
        else
            glBegin(GL_QUAD_STRIP);
            if (da<0.0) for (i=0;i<N;i+=3)
                
                glNormal3dv(n0+i); glVertex3dv(p0+i);
                glNormal3dv(n1+i); glVertex3dv(p1+i);
                
            else for (i=0;i<N;i+=3)
                
                glNormal3dv(n1+i); glVertex3dv(p1+i);
                glNormal3dv(n0+i); glVertex3dv(p0+i);
                
            glEnd();
            
        // swap buffers
        pp=p0; p0=p1; p1=pp;
        pp=n0; n0=n1; n1=pp;
        // handle r0 -> r1 edge
        if (e==1) a-=da;
        if ((e==1)||(e==2)||(e==3)) e++;
        
    // release buffers
    delete[] ptab;
    
//---------------------------------------------------------------------------
void glLinearArrow3D(double *pos,double *dir,double r0,double r1,double l,double al)
    
    int e,i,N=3*_glCircleN;
    double U[3],V[3],W[3],u,v;
    double a,da=pi2/double(_glCircleN-1),r,t;
    double *ptab,*p0,*p1,*n1,*pp,p[3],q[3],c[3],n[3];
    // buffers
    ptab=new double [9*_glCircleN]; if (ptab==NULL) return;
    p0=ptab+(0*_glCircleN);
    p1=ptab+(3*_glCircleN);
    n1=ptab+(6*_glCircleN);
    // compute basis vectors
    vector_one(W,dir);
    vector_ld(p,1.0,0.0,0.0);
    vector_ld(q,0.0,1.0,0.0);
    vector_ld(n,0.0,0.0,1.0);
    a=fabs(vector_mul(W,p));            pp=p; t=a;
    a=fabs(vector_mul(W,q)); if (t>a)  pp=q; t=a; 
    a=fabs(vector_mul(W,n)); if (t>a)  pp=n; t=a; 
    vector_mul(U,W,pp);
    vector_mul(V,U,W);
    vector_mul(U,V,W);
    for (e=0;e<4;e++)
        
        // segment center
        if (e==0)  t=0.0;  r= r0; 
        if (e==1)  t=l-al; r= r0; 
        if (e==2)  t=l-al; r= r1; 
        if (e==3)  t=l;    r=0.0; 
        vector_mul(c,W,t);
        vector_add(c,c,pos);
        // tube segment interpolation
        for (a=0.0,i=0;i<N;i+=3,a+=da)
            
            u=cos(a);
            v=sin(a);
            vector_mul(p,U,u);      // normal
            vector_mul(q,V,v);
            vector_add(n1+i,p,q);
            vector_mul(p,n1+i,r);   // vertex
            vector_add(p1+i,p,c);
            
        if (e>2)                    // recompute normals for cone
            
            for (i=3;i<N;i+=3)
                
                vector_sub(p,p0+i  ,p1+i);
                vector_sub(q,p1+i-3,p1+i);
                vector_mul(p,p,q);
                vector_one(n1+i,p);
                
            vector_sub(p,p0    ,p1);
            vector_sub(q,p1+N-3,p1);
            vector_mul(p,q,p);
            vector_one(n1,p);
            
        // render base disc
        if (!e)
            
            vector_neg(n,W);
            glBegin(GL_TRIANGLE_FAN);
            glNormal3dv(n);
            glVertex3dv(c);
            for (i=0;i<N;i+=3) glVertex3dv(p1+i);
            glEnd();
            
        // render tube
        else
            glBegin(GL_QUAD_STRIP);
            for (i=0;i<N;i+=3)
                
                glNormal3dv(n1+i);
                glVertex3dv(p0+i);
                glVertex3dv(p1+i);
                
            glEnd();
            
        // swap buffers
        pp=p0; p0=p1; p1=pp;
        
    // release buffers
    delete[] ptab;
    
//---------------------------------------------------------------------------

向量和矩阵数学:

// cross product: W = U x V
W.x=(U.y*V.z)-(U.z*V.y)
W.y=(U.z*V.x)-(U.x*V.z)
W.z=(U.x*V.y)-(U.y*V.x)
// dot product: a = (U.V)
a=U.x*V.x+U.y*V.y+U.z*V.z
// abs of vector a = |U|
a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))

vector_mul(a[3],b[3],c[3]) 是叉积 a = b x c a = vector_mul(b[3],c[3]) 是点积 a = (b.c) vector_one(a[3],b[3]) 是单位向量 a = b/|b| vector_copy(a[3],b[3]) 只是复制a = b vector_add(a[3],b[3],c[3]) 正在添加a = b + c vector_sub(a[3],b[3],c[3]) 正在减去 a = b - c vector_neg(a[3],b[3]) 是否定的a = -b vector_ld(a[3],x,y,z) 正在加载 a = (x,y,z)

reper 类只是持有代表 3D 坐标系的直接和逆 4x4 矩阵。它的实现取决于您的坐标系和 gfx 表示法(矩阵行/列主要顺序、乘法顺序等...)您需要实现它的一切都在上面的 4x4 齐次矩阵链接中。

现在终于有了用法:

这是我的 BDS2006 C++/VCL/OpenGL 项目源代码:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "OpenGLctrl3D.h" // only this is important
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1; // this form/window
//---------------------------------------------------------------------------
reper eye,obj; // camera and object matrices
double perspective[16]; // projection matrix
OpenGLscreen scr; // my GL engine can ignore this
OpenGLctrls3D ctrl; // control component (important)
bool _redraw=true; // need repaint ?
//---------------------------------------------------------------------------
void gl_draw()      // main rendering code
    
    _redraw=false;
    scr.cls();
    glEnable(GL_CULL_FACE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);

    // set view
    glMatrixMode(GL_MODELVIEW);
    eye.use_inv();
    glLoadMatrixd(eye.inv);

    // draw all controls
    ctrl.draw();

    // draw all objects
    glPushMatrix();
    obj.use_rep();
    glMatrixMode(GL_MODELVIEW);
    glMultMatrixd(obj.rep);

    glColor3f(1.0,1.0,1.0);
//  glBox(0.0,0.0,0.0,1.0,1.0,1.0);

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    scr.exe();
    scr.rfs();
    
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    
    // application init
    scr.init(this);
    scr.views[0].znear=0.1;
    scr.views[0].zfar=100.0;
    scr.views[0].zang=60.0;
    // matrices
    eye.reset();
    eye.gpos_set(vector_ld(0.0,0.0,+5.0));
    eye.lrotz(25.0*deg);
    obj.reset();
    obj.gpos_set(vector_ld(-1.0,-0.5,-1.0));
    obj.lroty(-35.0*deg);
    // controls
    ctrl.eye=&eye;
    ctrl.add(obj,vector_ld(2.5,2.5,2.5),0.04,0.10,1.25,0.5);
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
    
    // application exit
    scr.exit();
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    
    // window resize
    scr.resize();
    ctrl.resize(scr.x0,scr.y0,scr.xs,scr.ys);
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    
    // window repaint
    gl_draw();
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
    
    // mouse wheel translates camera (like zoom)
    GLfloat dz=2.0;
    if (WheelDelta>0) dz=-dz;
    eye.lpos_set(vector_ld(0.0,0.0,dz));
    ctrl.mouse_refresh();
    _redraw=true;
    
//---------------------------------------------------------------------------
// mouse events
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)  _redraw|=ctrl.mouse(X,Y,Shift); 
void __fastcall TForm1::FormMouseUp(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)    _redraw|=ctrl.mouse(X,Y,Shift); 
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)                      _redraw|=ctrl.mouse(X,Y,Shift); 
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    
//  double *p=ctrl.pm; Caption=AnsiString().sprintf("(%7.3lf,%7.3lf,%7.3lf)",p[0],p[1],p[2]);
    Caption=dbg;
    //  obj.lroty(3.0*deg); ctrl.mouse_refresh(); _redraw=true;
    if (_redraw) gl_draw();
    
//---------------------------------------------------------------------------

你可以忽略 VCL 和我的引擎相关的东西。对于每个受控对象,您应该有它的 4x4 变换矩阵 (reper) 和一个控制组件 (OpenGLctrl3D)。然后只需模仿这些事件并为每个事件添加相关调用以绘制和键/鼠标事件。

在这里预览它的样子:

遗憾的是,我的 GIF 捕捉器没有捕捉到鼠标光标,所以你看不到我点击/拖动的位置......但是正如你所看到的,我的控制相当复杂,只是 OBB 无济于事,因为环和箭头相交很多。不稳定是由于 GIF 捕获编码造成的,但是当使用对数深度缓冲区时,您可能会期望远离 znear 平面的对象也会出现 chppyness。要解决此问题,您可以使用:

Linear depth buffer

在我的示例中,我没有任何对象,只有单个控件,但你明白了……所以你的每个对象都应该有它的矩阵(用于渲染的相同)所以你只需添加一个引用它的控件.如果您的对象是动态添加和删除的,您还需要将它们的添加/删除添加到控件中......

最重要的是函数mouse_selectmouse_edit,它们将3D 全局鼠标位置转换为objetc/控制本地位置,从而非常容易检测诸如内锥、内圆柱、旋转角度和平移大小等内容...

【讨论】:

以上是关于OpenGL 射线 OBB 相交的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL 3D拾取文章(转)

Opengl:尝试绘制线条以可视化鼠标射线投射时出现意外行为

将 android-opengl 上的触摸转换为射线/矢量并检查它是不是击中平面

基于Qt的OpenGL可编程管线学习- X射线

基于Qt的OpenGL可编程管线学习- X射线

基于Qt的OpenGL可编程管线学习- X射线