Пересечение OBB лучей OpenGL

Я хочу реализовать выбор объекта в 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;
}

Скриншот


person Mohamed Moussa    schedule 20.10.2018    source источник
comment
см. OpenGL 3D-raypicking с высокополигональными сетками для идеального пиксельного подхода O(1) без математики пересечения... вы просто добавляете один рендеринг буфер для индекса объекта ...   -  person Spektre    schedule 20.10.2018
comment
Я хочу использовать метод пересечения Ray-OBB, потому что я хочу знать выбранный объект на стороне ЦП, чтобы выполнять любые операции, такие как вращение перевода или масштабирование.   -  person Mohamed Moussa    schedule 20.10.2018
comment
поэтому, если я использовал графический процессор для выбора объекта, могу ли я узнать на процессоре, какой объект я выбираю?   -  person Mohamed Moussa    schedule 20.10.2018
comment
Да, в ссылке есть старый пример стиля OpenGL, поэтому нет шейдеров ... он просто преобразует положение мыши в положение OpenGL NDC, считывает глубину и индексный буфер, поэтому ЦП знает глобальную 3D-позицию и индекс объекта, на который вы указываете мышью ... Вот и все тебе нужно ...   -  person Spektre    schedule 20.10.2018
comment
Итак, вы делаете что-то вроде этого: Вычислить объекты, движущиеся стрелками и мышью? Я получил обновленный код из этого ответа, используя выбор Рэя, который я связал ранее, как для движения, так и для вращения вокруг локальных осей объекта. Если вы заинтересованы, я мог бы создать ответ из него ...   -  person Spektre    schedule 20.10.2018
comment
отлично, это то, что мне нужно, не могли бы вы опубликовать ответ?   -  person Mohamed Moussa    schedule 20.10.2018


Ответы (1)


Я делаю это, используя:

Идея состоит в том, чтобы помимо рендеринга на экране также отображать индекс каждого объекта в отдельный невидимый буфер (приложение цвета, трафарет, тень,...), а затем просто выбирать пиксель в позиции мыши из этого буфера и глубины... что обеспечивает 3D положение выбранной точки, а также индекс объекта, которому она принадлежит. Это очень быстро O(1) практически без ущерба для производительности.

Теперь вам больше не нужен ни OBB для ваших объектов, ни проверка пересечений. Вместо этого используйте локальную систему координат в виде однородная матрица 4x4, с помощью которой вы можете легко преобразовать 3D-позицию, выбранную мышью, в локальные координаты объекта, что делает манипуляции, такие как перемещение/вращение объекта, очень простыми.

Вот мой старый подход С++ для этого:

который не требует никаких дополнительных библиотек и прочего. Как бы я ни делал это сейчас, используя все вышеперечисленное в слиянии следующим образом:

//---------------------------------------------------------------------------
#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<double> 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 просто содержит прямую и обратную матрицы 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, но при использовании логарифмического буфера глубины вы можете ожидать прерывистости также для объекта, находящегося далеко от ближней плоскости. Чтобы исправить это, вы можете использовать:

В моем примере у меня нет никаких объектов, только один элемент управления, но вы поняли... поэтому каждый ваш объект должен иметь свою матрицу (такую ​​же, которая используется для его рендеринга), поэтому вы просто добавляете элемент управления, ссылающийся на него. В случае, если ваши объекты динамически добавляются и удаляются, вам также необходимо добавить их добавление/удаление в элементы управления...

Наиболее важными функциями являются функции mouse_select и mouse_edit, которые преобразуют глобальную позицию мыши в 3D в локальную позицию объекта/управления, что позволяет очень легко обнаруживать такие вещи, как внутренний конус, внутренний цилиндр, угол поворота и размер перемещения и т. д.

person Spektre    schedule 20.10.2018