OpenGL 3D Picking - opengl
I read many sample code about opengl picking. Nearly all of them use gluPerspective funcion for projection.I'm using glOrtho instead of gluPerspective function.
And my Selection function is as below(DrawBuffer is my paint code):
void Selection( int x, int y )
{
GLuint buffer[512];
GLint hits;
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(512, buffer);
(void)glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
GLdouble w = (double)m_ClientRect.Width();
GLdouble h = (double)m_ClientRect.Height();
gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), 500, 500, viewport);
glOrtho(-w / 2, w / 2, -h / 2, h / 2, -1000000.0, 100000.0);
glMatrixMode(GL_MODELVIEW);
DrawBuffer();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
hits = glRenderMode(GL_RENDER);
if (hits > 0)
{
TRACE(_T("%d %d %d %d %d\n"), hits, buffer[0], buffer[1], buffer[2], buffer[3]);
}
}
But it doesn't work, I can't figure out the reason?
Another problem is:
When I using glDrawArrays function to draw many lines, how can I call glLoadName to flag each of them?
My raytracer algorithm is as follow:
void CGraphicView::KDSearch( PICKING_VERTEX *root, CRay *pRay, PICKING_VERTEX **found, double *dCurSplit )
{
if (NULL == root)
{
return;
}
SearchNode(root, m_pRay, m_globaltMin, m_globaltMax, found, dCurSplit);
}
void CGraphicView::SearchNode( PICKING_VERTEX *node, CRay *pRay, double tmin, double tmax, PICKING_VERTEX **found, double *dCurSplit )
{
if (NULL == node)
{
return;
}
if (node->bLeaf)
{
SearchLeaf(node, pRay, tmin, tmax, found, dCurSplit);
}
else
{
SearchSplit(node, pRay, tmin, tmax, found, dCurSplit);
}
}
void CGraphicView::SearchSplit( PICKING_VERTEX *split, CRay *pRay, double tmin, double tmax, PICKING_VERTEX **found, double *dCurSplit )
{
if (NULL == split)
{
return;
}
int axis = split->axis;
double thit = pRay->GetSplit(axis, split->coor[axis]);
Point3D pSrc(split->coor[0], split->coor[1], split->coor[2]);
double scale = m_pCam->GetScale();
double disP2L = DistanceP2L(pSrc, m_RayStart, m_RayEnd);
if (disP2L * scale < MAX_DISTANCE && thit < *dCurSplit)
{
*found = split;
*dCurSplit = thit;
}
PICKING_VERTEX *first = NULL, *second = NULL;
if (IS_EQUAL_FLOAT(pRay->m_direction[axis], 0.0))
{
first = (pRay->m_origin[axis] < split->coor[axis]) ? split->left : split->right;
}
else
{
first = (pRay->m_direction[axis] > 0.0) ? split->left: split->right;
second = (pRay->m_direction[axis] < 0.0) ? split->left : split->right;
}
if ((thit >= tmax || thit < 0))
{
SearchNode(first, pRay, tmin, tmax, found, dCurSplit);
}
else if (thit <= tmin)
{
SearchNode(second, pRay, tmin, tmax, found, dCurSplit);
}
else
{
SearchNode(first, pRay, tmin, thit, found, dCurSplit);
}
}
void CGraphicView::SearchLeaf( PICKING_VERTEX *leaf, CRay *pRay, double tmin, double tmax, PICKING_VERTEX **found, double *dCurSplit )
{
if (NULL == leaf)
{
return;
}
int axis = leaf->axis;
double thit = pRay->GetSplit(axis, leaf->coor[axis]);
Point3D pSrc(leaf->coor[0], leaf->coor[1], leaf->coor[2]);
double scale = m_pCam->GetScale();
double disP2L = DistanceP2L(pSrc, m_RayStart, m_RayEnd);
if (disP2L * scale < MAX_DISTANCE && thit < *dCurSplit)
{
*found = leaf;
*dCurSplit = thit;
}
ContinueSearch(leaf, pRay, tmin, tmax, found, dCurSplit);
}
void CGraphicView::ContinueSearch( PICKING_VERTEX *leaf, CRay *pRay, double tmin, double tmax, PICKING_VERTEX **found, double *dCurSplit )
{
if (IS_EQUAL_FLOAT(tmax, m_globaltMax))
{
return;
}
else
{
tmin = tmax;
tmax = m_globaltMax;
SearchNode(m_root, pRay, tmin, tmax, found, dCurSplit);
}
}
When I using glDrawArrays function to draw many lines, how can I call glLoadName to flag each of them?
You can't. And frankly: You should not use OpenGL selection mode in the first place! It's slow, no current driver supports it (you'll always drop back into software emulation mode with it), it doesn't work (well) with shaders and is cumbersome to use.
A far better alternative is to either backproject selection rays into the scene or (if modern OpenGL is used) to use a transform feedback buffer applied in bounding boxes (or other kind of bounding volume) to sort geometry into a screen space Kd-tree from where you can quickly select what's been clicked onto.
Well, from memory one way to debug hits == 0 was to use exactly the same pick matrix for a normal render, or in this case just comment out the glRenderMode calls. If you don't see anything drawn, then no part of your scene intersects the pick area and the selection code is just doing what you told it to.
However, datenwolf is right and you really should avoid OpenGL selection mode. It's horrible.
A fairly simple way to implement picking that doesn't require raycasting or Kd-trees is to draw each object in a different color. Assuming every object has a unique identifier number (which you'd need for glLoadName anyway), convert it into an 3 byte RGB color value. Draw the scene into the back buffer only, then read the pixel under the mouse coordinates. That RGB value will be the identifier of the frontmost object.
Related
OpenGL ray OBB intersection
I want to implement object picking in 3D so I have a Ray from a point on the screen towards the scene using glm::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 as double xxx[]; xxx.add(5); adds 5 to end of the list xxx[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 array xxx.reset() clears the array and set xxx.num=0 xxx.allocate(100) preallocate space for 100 items Rendering: //--------------------------------------------------------------------------- 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 product a = b x c a = vector_mul(b[3],c[3]) is dot product a = (b.c) vector_one(a[3],b[3]) is unit vector a = b/|b| vector_copy(a[3],b[3]) is just copy a = b vector_add(a[3],b[3],c[3]) is adding a = b + c vector_sub(a[3],b[3],c[3]) is substracting a = b - c vector_neg(a[3],b[3]) is negation a = -b vector_ld(a[3],x,y,z) is just loading a = (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 and mouse_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 ...
Implementation of feature detection algorithm
I'm fairly new to programming and would like to know how to start implementing the following algorithm in C++, Given a binary image where pixels with intensity 255 show edges and pixels with intensity 0 show the background, find line segments longer than n pixels in the image. t is a counter showing the number of iterations without finding a line, and tm is the maximum number of iterations allowed before exiting the program. Let t=0. Take two edge points randomly from the image and find equation of the line passing through them. Find m, the number of other edge points in the image that are within distance d pixels of the line. If m > n, go to Step 5. Otherwise (m ≤ n), increment t by 1 and if t < tm go to Step 2, and if t ≥ tm exit program. Draw the line and remove the edge points falling within distance d pixels of it from the image. Then, go to Step 1 Basically, I just want to randomly pick two points from the image, find the distance between them, and if that distance is too small, I would detect a line between them. I would appreciate if a small code snippet is provided, to get me started. this is more like a RANSAC parametric line detection. I would also keep this post updated if I get it done. /* Display Routine */ #include "define.h" ByteImage bimg; //A copy of the image to be viewed int width, height; //Window dimensions GLfloat zoomx = 1.0, zoomy = 1.0; //Pixel zoom int win; //Window index void resetViewer(); void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); if ((w!=width) || (h!=height)) { zoomx=(GLfloat)w/(GLfloat)bimg.nc; zoomy=(GLfloat)h/(GLfloat)bimg.nr; glPixelZoom(zoomx,zoomy); } width=w; height=h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void mouse(int button, int state, int x, int y) { glutPostRedisplay(); if((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN) && (zoomx==1.0) && (zoomy==1.0)){ printf(" row=%d, col=%d, int=%d.\n", y,x, (int)bimg.image[(bimg.nr-1-y)*bimg.nc+x]); glutPostRedisplay(); } } void display() { glClear(GL_COLOR_BUFFER_BIT); glRasterPos2i(0, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glDrawPixels((GLsizei)bimg.nc,(GLsizei)bimg.nr, GL_LUMINANCE,GL_UNSIGNED_BYTE, bimg.image); glutSwapBuffers(); }
Let us assume you have an int[XDIMENSION][YDIMENSION] Let t=0. int t = 0; // ;-) Take two edge points randomly from the image and find equation of the line passing through them. Brute force: you could randomly search the image for points and re-search when they are not edge points struct Point { int x; int y; }; bool is_edge(Point a) { return image[a.x][a.y] == 255; } int randomUpto(int upto) { int r = rand() % upto; return r; } , which needs the pseudo-random number generator to be initialized via srand(time(NULL)); To find edge points Point a; do { a.x = randomUpto(XDIMENSION); a.y = randomUpto(YDIMENSION); } while ( ! is_edge(a) ); Find m, the number of other edge points in the image that are within distance d pixels of the line. You need the line between the points. Some searching yields this fine answer, which leads to std::vector<Point> getLineBetween(Point a, Point b) { double dx = b.x - a.x; double dy = b.y - a.y; double dist = sqrt(dx * dx + dy * dy); dx /= dist; dy /= dist; std::vector<Point> points; points.push_back(a); for ( int i = 0 ; i < 2*dist; i++ ) { Point tmp; tmp.x = a.x + (int)(i * dx /2.0); tmp.y = a.y + (int)(i * dy /2.0); if ( tmp.x != points.back().x || tmp.y != points.back().y ) { points.push_back(tmp); } } return points; } Do you see a pattern here? Isolate the steps into substeps, ask google, look at the documentation, try out stuff until it works. Your next steps might be to create a distance function, euclidean should suffice find all points next to line (or next to a point, which is easier) based on the distance function Try out some and come back if you still need help.
Wierd Raytracing Artifacts
I am trying to create a ray tracer using Qt, but I have some really weird artifacts going on. Before I implemented shading, I just had 4 spheres, 3 triangles and 2 bounded planes in my scene. They all showed up as expected and as the color expected however, for my planes, I would see dots the same color as the background. These dots would stay static from my view position, so if I moved the camera around the dots would move around as well. However they only affected the planes and triangles and would never appear on the spheres. One I implemented shading the issue got worse. The dots now also appear on spheres in the light source, so any part affected by the diffuse. Also, my one plane of pure blue (RGB 0,0,255) has gone straight black. Since I have two planes I switched their colors and again the blue one went black, so it's a color issue and not a plane issue. If anyone has any suggestions as to what the problem could be or wants to see any particular code let me know. #include "plane.h" #include "intersection.h" #include <math.h> #include <iostream> Plane::Plane(QVector3D bottomLeftVertex, QVector3D topRightVertex, QVector3D normal, QVector3D point, Material *material) { minCoords_.setX(qMin(bottomLeftVertex.x(),topRightVertex.x())); minCoords_.setY(qMin(bottomLeftVertex.y(),topRightVertex.y())); minCoords_.setZ(qMin(bottomLeftVertex.z(),topRightVertex.z())); maxCoords_.setX(qMax(bottomLeftVertex.x(),topRightVertex.x())); maxCoords_.setY(qMax(bottomLeftVertex.y(),topRightVertex.y())); maxCoords_.setZ(qMax(bottomLeftVertex.z(),topRightVertex.z())); normal_ = normal; normal_.normalize(); point_ = point; material_ = material; } Plane::~Plane() { } void Plane::intersect(QVector3D rayOrigin, QVector3D rayDirection, Intersection* result) { if(normal_ == QVector3D(0,0,0)) //plane is degenerate { cout << "degenerate plane" << endl; return; } float minT; //t = -Normal*(Origin-Point) / Normal*direction float numerator = (-1)*QVector3D::dotProduct(normal_, (rayOrigin - point_)); float denominator = QVector3D::dotProduct(normal_, rayDirection); if (fabs(denominator) < 0.0000001) //plane orthogonal to view { return; } minT = numerator / denominator; if (minT < 0.0) { return; } QVector3D intersectPoint = rayOrigin + (rayDirection * minT); //check inside plane dimensions if(intersectPoint.x() < minCoords_.x() || intersectPoint.x() > maxCoords_.x() || intersectPoint.y() < minCoords_.y() || intersectPoint.y() > maxCoords_.y() || intersectPoint.z() < minCoords_.z() || intersectPoint.z() > maxCoords_.z()) { return; } //only update if closest object if(result->distance_ > minT) { result->hit_ = true; result->intersectPoint_ = intersectPoint; result->normalAtIntersect_ = normal_; result->distance_ = minT; result->material_ = material_; } } QVector3D MainWindow::traceRay(QVector3D rayOrigin, QVector3D rayDirection, int depth) { if(depth > maxDepth) { return backgroundColour; } Intersection* rayResult = new Intersection(); foreach (Shape* shape, shapeList) { shape->intersect(rayOrigin, rayDirection, rayResult); } if(rayResult->hit_ == false) { return backgroundColour; } else { QVector3D intensity = QVector3D(0,0,0); QVector3D shadowRay = pointLight - rayResult->intersectPoint_; shadowRay.normalize(); Intersection* shadowResult = new Intersection(); foreach (Shape* shape, shapeList) { shape->intersect(rayResult->intersectPoint_, shadowRay, shadowResult); } if(shadowResult->hit_ == true) { intensity += shadowResult->material_->diffuse_ * intensityAmbient; } else { intensity += rayResult->material_->ambient_ * intensityAmbient; // Diffuse intensity += rayResult->material_->diffuse_ * intensityLight * qMax(QVector3D::dotProduct(rayResult->normalAtIntersect_,shadowRay), 0.0f); // Specular QVector3D R = ((2*(QVector3D::dotProduct(rayResult->normalAtIntersect_,shadowRay))* rayResult->normalAtIntersect_) - shadowRay); R.normalize(); QVector3D V = rayOrigin - rayResult->intersectPoint_; V.normalize(); intensity += rayResult->material_->specular_ * intensityLight * pow(qMax(QVector3D::dotProduct(R,V), 0.0f), rayResult->material_->specularExponent_); } return intensity; } }
So I figured out my issues. They are due to float being terrible at precision, any check for < 0.0 would intermittently fail because of floats precision. I had to add an offset to all my checks so that I was checking for < 0.001.
Clip line to screen coordinates
I have line that is defined as two points. start = (xs,ys) end = (xe, ye) Drawing function that I'm using Only accepts lines that are fully in screen coordinates. Screen size is (xSize, ySize). Top left corner is (0,0). Bottom right corner is (xSize, ySize). Some other funcions gives me line that that is defined for example as start(-50, -15) end(5000, 200). So it's ends are outside of screen size. In C++ struct Vec2 { int x, y }; Vec2 start, end //This is all little bit pseudo code Vec2 screenSize;//You can access coordinates like start.x end.y How can I calculate new start and endt that is at the screen edge, not outside screen. I know how to do it on paper. But I can't transfer it to c++. On paper I'm sershing for point that belongs to edge and line. But it is to much calculations for c++. Can you help?
There are many line clipping algorithms like: Cohen–Sutherland wikipedia page with implementation Liang–Barsky wikipedia page Nicholl–Lee–Nicholl (NLN) and many more. see Line Clipping on wikipedia [EDIT1] See below figure: there are 3 kinds of start point: sx > 0 and sy < 0 (red line) sx < 0 and sy > 0 (yellow line) sx < 0 and sy < 0 (green and violet lines) In situations 1 and 2 simply find Xintersect and Yintersect respectively and choose them as new start point. As you can see, there are 2 kinds of lines in situation 3. In this situation find Xintersect and Yintersect and choose the intersect point near the end point which is the point that has minimum distance to endPoint. min(distance(Xintersect, endPoint), distance(Yintersect, endPoint)) [EDIT2] // Liang-Barsky function by Daniel White # http://www.skytopia.com/project/articles/compsci/clipping.html // This function inputs 8 numbers, and outputs 4 new numbers (plus a boolean value to say whether the clipped line is drawn at all). // bool LiangBarsky (double edgeLeft, double edgeRight, double edgeBottom, double edgeTop, // Define the x/y clipping values for the border. double x0src, double y0src, double x1src, double y1src, // Define the start and end points of the line. double &x0clip, double &y0clip, double &x1clip, double &y1clip) // The output values, so declare these outside. { double t0 = 0.0; double t1 = 1.0; double xdelta = x1src-x0src; double ydelta = y1src-y0src; double p,q,r; for(int edge=0; edge<4; edge++) { // Traverse through left, right, bottom, top edges. if (edge==0) { p = -xdelta; q = -(edgeLeft-x0src); } if (edge==1) { p = xdelta; q = (edgeRight-x0src); } if (edge==2) { p = -ydelta; q = -(edgeBottom-y0src);} if (edge==3) { p = ydelta; q = (edgeTop-y0src); } r = q/p; if(p==0 && q<0) return false; // Don't draw line at all. (parallel line outside) if(p<0) { if(r>t1) return false; // Don't draw line at all. else if(r>t0) t0=r; // Line is clipped! } else if(p>0) { if(r<t0) return false; // Don't draw line at all. else if(r<t1) t1=r; // Line is clipped! } } x0clip = x0src + t0*xdelta; y0clip = y0src + t0*ydelta; x1clip = x0src + t1*xdelta; y1clip = y0src + t1*ydelta; return true; // (clipped) line is drawn }
Here is a function I wrote. It cycles through all 4 planes (left, top, right, bottom) and clips each point by the plane. // Clips a line segment to an axis-aligned rectangle // Returns true if clipping is successful // Returns false if line segment lies outside the rectangle bool clipLineToRect(int a[2], int b[2], int xmin, int ymin, int xmax, int ymax) { int mins[2] = {xmin, ymin}; int maxs[2] = {xmax, ymax}; int normals[2] = {1, -1}; for (int axis=0; axis<2; axis++) { for (int plane=0; plane<2; plane++) { // Check both points for (int pt=1; pt<=2; pt++) { int* pt1 = pt==1 ? a : b; int* pt2 = pt==1 ? b : a; // If both points are outside the same plane, the line is // outside the rectangle if ( (a[0]<xmin && b[0]<xmin) || (a[0]>xmax && b[0]>xmax) || (a[1]<ymin && b[1]<ymin) || (a[1]>ymax && b[1]>ymax)) { return false; } const int n = normals[plane]; if ( (n==1 && pt1[axis]<mins[axis]) || // check left/top plane (n==-1 && pt1[axis]>maxs[axis]) ) { // check right/bottom plane // Calculate interpolation factor t using ratio of signed distance // of each point from the plane const float p = (n==1) ? mins[axis] : maxs[axis]; const float q1 = pt1[axis]; const float q2 = pt2[axis]; const float d1 = n * (q1-p); const float d2 = n * (q2-p); const float t = d1 / (d1-d2); // t should always be between 0 and 1 if (t<0 || t >1) { return false; } // Interpolate to find the new point pt1[0] = (int)(pt1[0] + (pt2[0] - pt1[0]) * t ); pt1[1] = (int)(pt1[1] + (pt2[1] - pt1[1]) * t ); } } } } return true; } Example Usage: void testClipLineToRect() { int screenWidth = 320; int screenHeight = 240; int xmin=0; int ymin=0; int xmax=screenWidth-1; int ymax=screenHeight-1; int a[2] = {-10, 10}; int b[2] = {300, 250}; printf("Before clipping:\n\ta={%d, %d}\n\tb=[%d, %d]\n", a[0], a[1], b[0], b[1]); if (clipLineToRect(a, b, xmin, ymin, xmax, ymax)) { printf("After clipping:\n\ta={%d, %d}\n\tb=[%d, %d]\n", a[0], a[1], b[0], b[1]); } else { printf("clipLineToRect returned false\n"); } } Output: Before clipping: a={-10, 10} b=[300, 250] After clipping: a={0, 17} b=[285, 239]
C++ OpenGL: Ray Trace Shading Isn't Properly Shading
I'm a CS student and for our final we were told to construct the reflections on multiple spheres via ray tracing. That's almost literally what we got for directions except a picture for how it should look when finished. So I need spheres, with they're reflections (using ray tracing) mapped on them with the proper shading from a light. Well I have all of it working, except having multiple spheres and the fact that it doesn't look like the picture he gave us for a rubric. The multiple spheres thing I'm not too sure how to do, but I'd say I need to store them in a 2D array and modify a few sections of code. What I thought was modifying the sphere_intersect and find_reflect to include which sphere is being analyzed. Next, modify find_reflect so that when the new vector u is calculated its starting point (P0) is also updated. Then if the ray hits a sphere it will have to count how many times the ray has been reflected. At some point terminate (after 10 times maybe) and then I'll just draw the pixel. For an added touch I'd like to add solid colors to the spheres which would call for finding the normal of a sphere I believe. Anyways I'm going to attach a picture of his, a picture of mine, and the source code. Hopefully someone can help me out on this one. Thanks in advance! Professor's spheres My spheres #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <GL/glut.h> #include <math.h> #include <string> #define screen_width 750 #define screen_height 750 #define true 1 #define false 0 #define perpendicular 0 int gridXsize = 20; int gridZsize = 20; float plane[] = {0.0, 1.0, 0.0, -50.0,}; float sphere[] = {250.0, 270.0, -100.0, 100.0}; float eye[] = {0.0, 400.0, 550.0}; float light[] = {250.0, 550.0, -200.0}; float dot(float *u, float *v) { return u[0]*v[0] + u[1]*v[1] + u[2]*v[2]; } void norm(float *u) { float norm = sqrt(abs(dot(u,u))); for (int i =0; i <3; i++) { u[i] = u[i]/norm; } } float plane_intersect(float *u, float *pO) { float normt[3] = {plane[0], plane[1], plane[2]}; float s; if (dot(u,normt) == 0) { s = -10; } else { s = (plane[3]-(dot(pO,normt)))/(dot(u,normt)); } return s; } float sphere_intersect(float *u, float *pO) { float deltaP[3] = {sphere[0]-pO[0],sphere[1]-pO[1],sphere[2]-pO[2]}; float deltLen = sqrt(abs(dot(deltaP,deltaP))); float t=0; float answer; float det; if ((det =(abs(dot(u,deltaP)*dot(u,deltaP))- (deltLen*deltLen)+sphere[3]*sphere[3])) < 0) { answer = -10; } else { t =-1*dot(u,deltaP)- sqrt(det) ; if (t>0) { answer = t; } else { answer = -10; } } return answer; } void find_reflect(float *u, float s, float *pO) { float n[3] = {pO[0]+s *u[0]-sphere[0],pO[1]+s *u[1]-sphere[1],pO[2]+s *u[2]- sphere[2]}; float l[3] = {s *u[0],s *u[1],s *u[2]}; u[0] =(2*dot(l,n)*n[0])-l[0]; u[1] = (2*dot(l,n)*n[1])-l[1]; u[2] = (2*dot(l,n)*n[2])-l[2]; } float find_shade(float *u,float s, float *pO) { float answer; float lightVec[3] = {light[0]-(pO[0]+s *u[0]), light[1]-(pO[1]+s *u[1]), light[2]-(pO[2]+s *u[2])}; float n[3] = {pO[0]+s *u[0]-sphere[0],pO[1]+s *u[1]-sphere[1],pO[2]+s *u[2]-sphere[2]}; answer = -1*dot(lightVec,n)/(sqrt(abs(dot(lightVec,lightVec)))*sqrt(abs(dot(n,n)))); return answer; } void init() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0,screen_width,0,screen_height); } void display() { glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); for (int i=0; i < screen_width; i++) { for (int j=0; j < screen_height; j++) { float ray[3] = {1*(eye[0]-i),-1*(eye[1]-j),1*eye[2]}; float point[3] = {i,j,0}; norm(ray); int plotted = false; while (!plotted) { float s_plane = plane_intersect(ray, point); float s_sphere = sphere_intersect(ray, point); if (s_plane <= 0 && s_sphere <=0) { glColor3f(0,0,0); glBegin(GL_POINTS); glVertex3f(i,j,0); glEnd(); plotted = true; } else if (s_sphere >= 0 && (s_plane <=0 || s_sphere <= s_plane)) { find_reflect(ray, s_sphere, point); } else if (s_plane >=0 && (s_sphere <=0 ||s_plane <= s_sphere)) { float shade = find_shade(ray, s_plane, point); float xx = s_plane*ray[0] + eye[0]; float z = s_plane*ray[2] + eye[2]; if (abs((int)xx/gridXsize)%2 == abs((int)z/gridZsize)%2) { glColor3f(shade,0,0); } else { glColor3f(shade,shade,shade); } glBegin(GL_POINTS); glVertex3f(i,j,0); glEnd(); plotted = true; } } } } glFlush(); } int main(int argc, char **argv) { glutInit(&argc, argv); glutCreateWindow("Ray Trace with Sphere."); glutInitWindowSize(screen_width,screen_height); glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); glutDisplayFunc(display); init(); glutMainLoop(); return 0; }
The professor did not tell you too much, because such a topic is covered thousands of time over the web, just check-out "Whitted Raytracing" ;) It's homework, and 5mn of googling around would solve the issue... Some clues to help without doing your homework for you Do it step by step, don't try to reproduce the picture in one step Get one sphere working, if hit the plane green pixel, the sphere red pixel, nothing, black. It's enough to get the intersections computing right. It looks like, from your picture, that you don't have the intersections right, for a start Same as previous, with several spheres. Same as one sphere : check intersection for all objects, keep the closest intersection from the point of view. Same as previous, but also compute the amount of light received for each intersection found, to have shade of red for spheres, and shade of green for the plane. (hint: dot product ^^) Texture for the plane Reflection for the spheres. Protip: a mirror don't reflect 100% of the light, just a fraction of it.