OpenGL射线OBB相交
我想在 3D 中实现对象拾取,所以我使用 glm::unproject
方法从屏幕上的一个点到场景有一个 Ray
它返回 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=minXvec.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 ) {//标准情况GLfloat min = (e+minX)/f;//与左"平面相交GLfloat max = (e+maxX)/f;//与右"平面相交如果(最小<最大){tMin=分钟;tMax=max;}别的{tMin=最大值;tMax=min;}如果 (tMax < tMin)返回假;}别的{if(-e+minX > 0.0f || -e+maxX < 0.0f)返回假;}}{glm::vec3 yAxis=boundingBox->getMatrix()[1];GLfloat e = glm::dot(yAxis, delta);GLfloat f = glm::dot(ray.getDirection(), yAxis);如果(晶圆厂(f)> 0.001f){GLfloat min = (e+minY)/f;GLfloat max = (e+maxY)/f;如果(最小<最大){tMin=glm::max(tMin,min);tMax=glm::min(tMax,max);}别的{tMin=glm::max(tMin,max);tMax=glm::min(tMax,min);}如果 (tMax < tMin)返回假;}别的{if(-e+minY > 0.0f || -e+maxY < 0.0f)返回假;}}{glm::vec3 zAxis=boundingBox->getMatrix()[2];GLfloat e = glm::dot(zAxis, delta);GLfloat f = glm::dot(ray.getDirection(),zAxis);如果(晶圆厂(f)> 0.001f){GLfloat min = (e+minZ)/f;GLfloat max = (e+maxZ)/f;如果(最小<最大){tMin=glm::max(tMin,min);tMax=glm::min(tMax,max);}别的{tMin=glm::max(tMin,max);tMax=glm::min(tMax,min);}如果 (tMax < tMin)返回假;}别的{if(-e+minZ > 0.0f || -e+maxZ < 0.0f)返回假;}}距离 = tMin;返回真;}
解决方案 我正在使用:
遗憾的是,我的 GIF 捕获器没有捕获鼠标光标,因此您看不到我单击/拖动的位置......但是正如您所看到的,我的控制相当复杂,而且 OBB 没有多大帮助,因为环和箭头相交很多.断断续续是由于 GIF 捕获编码造成的,但是当使用对数深度缓冲区时,您可能期望远离 znear 平面的对象也有断断续续的情况.要补救,您可以使用:
- 线性深度缓冲区
在我的示例中,我没有任何对象只是单个控件,但您明白了……所以您的每个对象都应该有它的矩阵(用于渲染的矩阵),因此您只需添加一个引用它的控件.如果您的对象是动态添加和删除的,您也需要将它们的添加/删除添加到控件...
最重要的是函数
mouse_select
和mouse_edit
,它们将 3D 全局鼠标位置转换为 objetc/control 本地位置,使得很容易检测内部锥体等内容,圆柱内、旋转角度和平移尺寸等...I want to implement object picking in 3D so I have a
Ray
from a point on the screen towards the scene usingglm::unproject
method "it returns the Y flipped so I use its negative value", the following code success always when the object is centered on the world origin but with another object that is transformed and the camera moved or rotated it may success and may not, i simulated the ray and it is already intersect the object, all coordinates are in the world space.
解决方案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; }
I am doing this using:
- OpenGL 3D-raypicking with high poly meshes
The idea is to apart of rendering to screen also render index of each object into separate unseen buffer (color attachment, stencil, shadow,...) and than just pick pixel at mouse position from this buffer and depth ... which provides 3D position of the picked point and also index of object that it belongs to. This is very fast
O(1)
at almost no performance cost.Now You do not need OBB for your objects nor any intersection checking anymore. Instead have a local coordinate system in form of 4x4 homogenuous matrix with which you can easily convert the 3D position picked by mouse into object local coordinates making the manipulation like translation/rotation of the object really easy.
Here is my older C++ approach of mine for this:
- Compute objects moving with arrows and mouse
which does not require any additional libs and stuff. How ever I do it now using all above in fusion like this:
//--------------------------------------------------------------------------- #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 //--------------------------------------------------------------------------- //---------------------------------------------------------------------------
Unlike the example in the link above this uses a lot of stuff not provided (from my GL engine) so you can not use it directly however it should be enough to grasp the basics. Here some external stuff it uses (not all):
I also use mine dynamic list template so:
List<double> xxx;
is the same asdouble xxx[];
xxx.add(5);
adds5
to end of the listxxx[7]
access array element (safe)xxx.dat[7]
access array element (unsafe but fast direct access)xxx.num
is the actual used size of the arrayxxx.reset()
clears the array and setxxx.num=0
xxx.allocate(100)
preallocate space for100
itemsRendering:
//--------------------------------------------------------------------------- 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; } //---------------------------------------------------------------------------
vector and matrix math:
// 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])
is cross producta = b x c
a = vector_mul(b[3],c[3])
is dot producta = (b.c)
vector_one(a[3],b[3])
is unit vectora = b/|b|
vector_copy(a[3],b[3])
is just copya = b
vector_add(a[3],b[3],c[3])
is addinga = b + c
vector_sub(a[3],b[3],c[3])
is substractinga = b - c
vector_neg(a[3],b[3])
is negationa = -b
vector_ld(a[3],x,y,z)
is just loadinga = (x,y,z)
The
reper
class is just holding direct and inverse 4x4 matrix representing 3D coordinate system. Its implementation depends on your coordinate system and gfx notation (matrix row/column major order, multiplication order etc...) Everything you need to implement it is in the 4x4 homogenuous matrix link above.Now finally the usage:
Here is my BDS2006 C++/VCL/OpenGL project source code:
//--------------------------------------------------------------------------- #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(); } //---------------------------------------------------------------------------
You can ignore the VCL and my engine related stuff. For each controlled object you should have its 4x4 transform matrix (
reper
) and a control component (OpenGLctrl3D). Then just mimic the events and add relevant calls to draw and key/mouse events for each.Here preview how it looks like:
Sadly my GIF capturer does not capture the mouse cursor so you do not see where I click/drag ... But as you can see my control is rather complex and just OBB would not help much as the rings and arrows are intersecting a lot. The choppy ness is due to GIF capture encoding but when using logarithmic depth buffer you might expect chppyness also for object far from znear plane. To remedy that you can use:
- Linear depth buffer
In my example I do not have any objects just single control but you get the idea ... so each object of yours should have its matrix (the same that is used for its rendering) so you just add a control referencing it. In case your objects are dynamicaly added and removed you need to add their add/removal to controls too...
The most important stuff are the functions
mouse_select
andmouse_edit
which converts the 3D global mouse position into objetc/control local one making very easy to detect stuff like inside cone, inside cylinder, angle of rotation and translation size etc ...
相关文章