Я делаю это, используя:
Идея состоит в том, чтобы помимо рендеринга на экране также отображать индекс каждого объекта в отдельный невидимый буфер (приложение цвета, трафарет, тень,...), а затем просто выбирать пиксель в позиции мыши из этого буфера и глубины... что обеспечивает 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
O(1)
без математики пересечения... вы просто добавляете один рендеринг буфер для индекса объекта ... - person Spektre   schedule 20.10.2018