OpenGL ray OBB intersection - c++

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 ...

Related

OpenGL:How to make a "Polyman's" mouth open

I'm writing a program that has a polygon "man" walk from one side of the screen to the middle, open his mouth, jump up and do a flip, land, close his mouth, and walk left off the screen. I am a bit confused on how to make the lad's mouth open. I was thinking I could create a triangle of appropriate size the same color as the background and slowly translate it to where his mouth would be. What steps would I need to go through to make this possible, and where would I put the code to do so?
#include<Windows.h>
#include<GL/glut.h>
#include<stdlib.h>
#include<math.h>
#include<conio.h>
#include<stdio.h>
#include<iostream>
#include<iomanip>
#include<gl/glut.h>
using namespace std;
//***********************GLOBAL VALUES*********************************************
float theta=00.0; //global angular value for rotation
float scale1=1.0; //global scaling value
float dx=7.0,dy=-3.0;
int frame = 1;
void init(void); //This is a function to initialize the window clear color
void RenderScene(void); //This a function to draw polyman in an opened window
void loadicon(float[],float[],float[],float[], float[], float[]); //Load the polyman icon
void drawicon(float[],float[],float[],float[], float[], float[]); //Draw the icon the two first float for the square and the others for the line
void settrans(float[][3],float,float,float); //Sets the transformation matrix for desired scale, rotation, new pos
float xprime(float,float,float[][3]); //Calculates x' from x and transform
float yprime(float,float,float[][3]); //Calculates y' from y and transform
void transform(float[],float[],float[],float[], float[], float[], float[][3],float[],float[],float[],float[], float[], float[]); //performs the transformation on the icon pattern
void myidle(void);
void SetupRC(void); //Sets up the clear color
void TimerFunction(int); //This call back function is called each 30ms and changes the location, scale and rotation
//***********************MAIN PROGRAM**********************************************
int main(int argc, char** argv)
{
//Set up window title
char header[]="Polyman's journey";
glutInit(&argc,argv);
//Set up the display mode with two buffers and RGB colors
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
//Initialize window size and position
glutInitWindowSize(560,440);
glutInitWindowPosition(140,20);
//Initialize background color of the window
SetupRC();
//Open and label window
glutCreateWindow(header);
glutDisplayFunc(RenderScene);
glutTimerFunc(30,TimerFunction, 1); //Call the TimerFunction each 30s
//Now draw the scene
glutMainLoop();
return 0;
}
//*************************RenderScene Function*************************************
void RenderScene(void)
{
float xdel=0.25;
float px[7],py[7],plx[4],ply[4], pl2x[4], pl2y[4];// These variables hold the pattern for the icon square plus line
float pxp[7],pyp[7],plxp[4],plyp[4], pl2xp[4], pl2yp[4],t[3][3]; //These varables hold the pattern after transformation, t is the transformation matrix
//clear the window with the current background color
cout<<"in RenderScene"<<endl;
//set the current drawing color to white
glColor3f(1.0,1.0,1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//set the viewport to the window dimensions
glViewport(0,0,560,440);
//Establish the clipping volumn in user units, first clear all the translation matrices
glOrtho(-7.0,7.0,-7.0,7.0,1.0,-1.0);
loadicon(px,py,plx,ply, pl2x, pl2y);
//draw the icon untransformed
settrans(t,scale1,dx,dy);
transform(pxp,pyp,plxp,plyp,pl2xp,pl2yp, t,px,py,plx,ply, pl2x, pl2y);
//clear the window with the background color
glClear(GL_COLOR_BUFFER_BIT);
//set the current drawing color to white
glColor3f(1.0,1.0,1.0);
//now draw the figure
drawicon(pxp,pyp,plxp,plyp,pl2xp, pl2yp);
glEnd();
glutSwapBuffers();
return;
}//end of render scene
//************************LOAD ICON FUNCTION***********************************
void loadicon(float px[],float py[],float plx[],float ply[],float pl2x[],float pl2y[]) //Loads the polyman
{
//Set the coordinates of the square
px[0]=-0.625 ; py[0]=0.625 ;
px[1]=0.625 ; py[1]=0.625 ;
px[2]=1.0 ; py[2]=0.0 ;
px[3]=0.625 ; py[3]=-0.625 ;
px[4]=-0.625 ; py[4]=-0.625 ;
px[5]= -1.0 ; py[5]= 0.0 ;
px[6]=-0.625 ; py[6]=0.625 ;
//set the right foot
plx[0]= 0.25 ; ply[0]= -0.625 ;
plx[1]= 0.25 ; ply[1]= -0.875 ;
plx[2]= 0.0 ; ply[2]= -0.875 ;
plx[3] = 0.25 ; ply[3] = -0.875;
//set the left foot
pl2x[0]= -0.125 ; pl2y[0]= -0.375 ;
pl2x[1]= -0.125 ; pl2y[1]= -0.875 ;
pl2x[2]= -0.375 ; pl2y[2]= -0.875 ;
pl2x[3]= -0.125 ; pl2y[3]= -0.875 ;
return;
} //end of loadicon
//************************FUNCTION DRAWICON***********************************
void drawicon(float pxp[],float pyp[],float plxp[],float plyp[], float pl2xp[], float pl2yp[])
{
//draw the square icon at the transformed position
int i;
cout<<"in drawicon"<<endl;
glBegin(GL_LINE_STRIP);
//move to first point in the icon
glVertex2f(pxp[0],pyp[0]);
//now draw the rest of the box
for(i = 1; i <= 6; i++)
{
glVertex2f(pxp[i],pyp[i]);
}
glEnd();
//now draw the line
glBegin(GL_LINES);
glVertex2f(plxp[0],plyp[0]);
for (i=1; i <=3; i++)
{
glVertex2f(plxp[i],plyp[i]);
}//glVertex2f(plxp[2],plyp[2]);
glEnd();
glBegin(GL_LINES);
glVertex2f(pl2xp[0], pl2yp[0]);
for (i=1; i <=3; i++)
{
glVertex2f(pl2xp[i], pl2yp[i]);
}
glEnd();
//now fill the rectangle which is made by half of the square
//set the shading color to green
glColor3f(0.0,1.0,0.0);
glShadeModel(GL_FLAT);
//redraw the polygon
glBegin(GL_POLYGON);
//Firts point is where the line intersects the top of the square
glVertex2f(pxp[0], pyp[0]);
//rigth corner upper
glVertex2f(pxp[1],pyp[1]);
//right corner lower
glVertex2f(pxp[2],pyp[2]);
//left intersect
glVertex2f(pxp[3],pyp[3]);
glVertex2f(pxp[4],pyp[4]);
glVertex2f(pxp[5],pyp[5]);
glVertex2f(pxp[6],pyp[6]);
return;
} //end of draw icon
//************************FUNCTION SETTRANS***********************************
void settrans(float t[][3],float scale1,float dx,float dy)
{
cout<<"in settrans"<<endl;
int i,j;
float ts,ct,st;
double theta1;
//setup identity matrix
for(i=0;i<=2;i++)
{
for(j=0;j<=2;j++)
{
t[i][j]=0.0;
if(i==j) t[i][j]=1.0;
}
}
//set scale parameters
if(scale1!=-9.0)
{
t[0][0]=scale1;
t[1][1]=scale1;
}
if(theta!=-9.0)
{
theta1=(3.1416/180.0)*theta;
ct=cos(theta1);
st=sin(theta1);
ts=t[0][0];
t[0][0]=ts*ct;
t[0][1]=ts*st;
ts=t[1][1];
t[1][0]=-ts*st;
t[1][1]=ts*ct;
}
//translate the figure
if((dx+dy) != -18.0)
{
t[2][0]=dx;
t[2][1]=dy;
}
return;
}//end of settrans
//************************FUNCTION XPRIME***********************************
float xprime(float x1,float y1, float t[][3])
{
//this function pultiples the x vector by the transformation matrix
float xp1;
xp1=x1*t[0][0]+y1*t[1][0]+t[2][0];
return xp1;
}
//************************FUNCTION YPRIME***********************************
float yprime(float x1,float y1, float t[][3])
{
//this function pultiples the y vector by the transformation matrix
float yp1;
yp1=x1*t[0][1]+y1*t[1][1]+t[2][1];
return yp1;
}
//************************FUNCTION TRANSFORM***********************************
void transform(float pxp[],float pyp[],float plxp[],float plyp[],float pl2xp[],float pl2yp[],float t[][3],float px[],float py[],float plx[],float ply[], float pl2x[], float pl2y[])
{
int i;
cout<<"in transform"<<endl;
//transform the figure
for(i=0;i<=6;i++)
{
pxp[i] = xprime(px[i],py[i],t);
pyp[i] = yprime(px[i],py[i],t);
}
//transform the line
for(i=0;i<=3;i++)
{
plxp[i] = xprime(plx[i],ply[i],t);
plyp[i] = yprime(plx[i],ply[i],t);
}
for(i=0;i<=3;i++)
{
pl2xp[i] = xprime(pl2x[i],pl2y[i],t);
pl2yp[i] = yprime(pl2x[i],pl2y[i],t);
}
return;
}//end of transform
//************************ FUNCTION SetupRC***********************************
void SetupRC(void)
{
//sets the clear color of an open window and clears the open window
//set clear color to green
glClearColor(0.0,1.0,0.0,1.0);
return;
}//end of setupRC
//************************ FUNCTION Timer***********************************
void TimerFunction(int value)
{
//this call back function is called each 30ms and changes the location, scale and rotation
static float swc=0.1,sdx=0.1,sdy=0.1;
switch(frame)
{
case 1:
//theta+=5.0;
dx-=0.15;
if(dx<=0.0)
{
dx=0.0;
frame=2;
}
break;
case 2:
dy+=0.2;
if(dy>5.0)
{
dy=5.0;
frame=3;
}
break;
case 3:
theta+=5.0;
if(theta>360.0)
{
frame=4;
theta=0.0;
scale1=1.0;
}
break;
case 4:
dy-=0.2;
if(dy<=-3.0)
{
dy=-3.0;
frame=5;
}
break;
case 5:
dx-=0.15;
//theta+=5.0;
if(dx<=-8.0) dx=-8.0;
break;
}
//redraw the scene with new coordinate
glutPostRedisplay();
glutTimerFunc(33,TimerFunction,1);
}
Also, we aren't allowed to use any of the built in translate, rotate, or scale functions that OpenGL provides since it's our first assignment. Thanks for all the help, I really appreciate it.

How do I compose a rotation matrix with human readable angles from scratch?

The one thing that has always hindered me from doing 3D programming is failing to understand how math works. I can go along with math fine in programming flow using methods and functions, then its all clear and logical to me, but in mathematical notation, I just can't make heads or tails from it.
I have been reading websites, a watching videos of institutes trying to explain this, but they all use mathematical notation and I simply get lost in it, my mind won't translate it to something understandable. I might have a defect there.
Also, Just using someone's code isn't my interest, I want to understand the mechanics behind it, the logic. I'd be happy to use someone else's code, but I really want to understand how it works.
The question
Can you explain to me in simple terms without mathematical notation, just programming notation/functions/psuedocode, how to implement a matrix transform along all 3 axes?
Ideally what I want is the material/understanding to write a method/object where I can define the angles of 3 axes similar to glRotate to rotate the collection of quads/triangles I have. (I am trying to program a 3D rotation of a cube shapes without having access to OpenGL functions to do it for me because this is done in one draw call every time something changes in the display list.)
What have I done?
I have attempted at making a 90 degrees transform function to get the hang of the math but failed utterly in making a proper matrix which in theory should have been the simplest to do. You can see my failed attempt in all its glory on http://jsfiddle.net/bLfg0tj8/5/
Vec3 = function(x,y,z) {
this.x = x;
this.y = y;
this.z = z;
}
Matrix = function Matrix() {
this.matrixPoints = new Array();
this.rotationPoint = new Vec3(0,0,0);
this.rotationAngle = 90;
}
Matrix.prototype.addVector = function(vector) {
this.matrixPoints.push(vector);
}
Matrix.prototype.setRotationPoint = function(vector) {
this.rotationPoint = vector;
}
Matrix.prototype.setRotationAngle = function(angle) {
this.rotationAngle = angle;
}
Matrix.prototype.populate = function() {
translateToOrigin = [[1,0,0-this.rotationPoint.x],
[0,1,0-this.rotationPoint.y],
[0,0,0-this.rotationPoint.z]];
rotationMatrix = [[0,-1,0],
[0,1,0],
[0,0,1]];
translateEnd = [[1,0,this.rotationPoint.x],
[0,1,this.rotationPoint.y],
[0,0,this.rotationPoint.z]];
currentColumn = 0;
currentRow = 0;
this.combomatrix = this.mergeMatrices(this.mergeMatrices(translateEnd,rotationMatrix),
translateToOrigin);
}
Matrix.prototype.transform = function() {
newmatrix = new Array();
for(c = 0;c<this.matrixPoints.length;c++) {
newmatrix.push(this.applyToVertex(this.matrixPoints[c]));
}
return newmatrix;
}
Matrix.prototype.applyToVertex = function(vertex) {
ret = new Vec3(vertex.x,vertex.y,vertex.z);
ret.x = ret.x + this.combomatrix[0][0] * vertex.x +
this.combomatrix[0][1] * vertex.y +
this.combomatrix[0][2] * vertex.z;
ret.y = ret.y + this.combomatrix[1][0] * vertex.x +
this.combomatrix[1][1] * vertex.y +
this.combomatrix[1][2] * vertex.z;
ret.z = ret.z + this.combomatrix[2][0] * vertex.x +
this.combomatrix[2][1] * vertex.y +
this.combomatrix[2][2] * vertex.z;
return ret;
}
Matrix.prototype.mergeMatrices = function(lastStep, oneInFront) {
step1 = [[0,0,0],[0,0,0],[0,0,0]];
step1[0][0] = lastStep[0][0] * oneInFront[0][0] +
lastStep[0][1] * oneInFront[1][0] +
lastStep[0][2] * oneInFront[2][0];
step1[0][1] = lastStep[0][0] * oneInFront[0][1] +
lastStep[0][1] * oneInFront[1][1] +
lastStep[0][2] * oneInFront[2][1];
step1[0][2] = lastStep[0][0] * oneInFront[0][2] +
lastStep[0][1] * oneInFront[1][2] +
lastStep[0][2] * oneInFront[2][2];
//============================================================
step1[1][0] = lastStep[1][0] * oneInFront[0][0] +
lastStep[1][1] * oneInFront[1][0] +
lastStep[1][2] * oneInFront[2][0];
step1[1][1] = lastStep[1][0] * oneInFront[0][1] +
lastStep[1][1] * oneInFront[1][1] +
lastStep[1][2] * oneInFront[2][1];
step1[1][2] = lastStep[1][0] * oneInFront[0][2] +
lastStep[1][1] * oneInFront[1][2] +
lastStep[1][2] * oneInFront[2][2];
//============================================================
step1[2][0] = lastStep[2][0] * oneInFront[0][0] +
lastStep[2][1] * oneInFront[1][0] +
lastStep[2][2] * oneInFront[2][0];
step1[2][1] = lastStep[2][0] * oneInFront[0][1] +
lastStep[2][1] * oneInFront[1][1] +
lastStep[2][2] * oneInFront[2][1];
step1[2][2] = lastStep[2][0] * oneInFront[0][2] +
lastStep[2][1] * oneInFront[1][2] +
lastStep[2][2] * oneInFront[2][2];
return step1;
}
Matrix.prototype.getCurrentMatrix = function() {
return this.matrixPoints;
}
myvectors = [new Vec3(50,50,0), new Vec3(20,80,0), new Vec3(80, 80, 0)];
function drawVectors(vectors,color) {
for(c=0;c<vectors.length;c++) {
document.getElementById("whoa").innerHTML += '<div style="color:'+color+';position:absolute;left:'+vectors[c].x+'px; top:'+vectors[c].y+'px;z-index:'+vectors[c].z+';">('+c+').</div>';
}
}
matrix = new Matrix();
for(c=0;c<myvectors.length;c++) {
matrix.addVector(myvectors[c]);
}
matrix.setRotationPoint(new Vec3(50,70,0));
matrix.populate();
somematrix = matrix.transform();
drawVectors(matrix.getCurrentMatrix(),"lime"); // draw current matrix that was hand coded
drawVectors([matrix.rotationPoint],'white'); // draw rotation point
drawVectors(somematrix,"red"); // transformed matrix... somehow two points merge
<div id="whoa" style="position:relative;top:50px;left:150px;background-color:green;color:red;width:400px;height:300px;">
</div>
The green text is the original triangle, the white point the center point, the red points the failed transformation(I think, because it isn't aligned around the center point). The tutorial I was in thought me how to combine matrices into a combined matrix, but I guess I screwed up somewhere.
As I said, it's really really hard for me to understand mathematical notation and speak. And not helping is that most teachers skip parts of the explanation. Took me 2 hours alone to understand when multiplying matrices you need to add each step together instead of just keep on multiplying. Yay for explanations.
A practical example what I work with/want to work with
For example I have a cube, loaded from a wavefront obj file located in the world at
x = 50
y = 100
z = 200
The cube is drawn using quads and some uv mapping. No problems here. It renders beautifully with all the textures showing correctly.
These are the location coordinates for each "face" of the cube which is drawn using a quad.
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0
So this works all great. But what if I want this cube rotated 90 degrees along the x axis and 45 degrees around the z axis? I cannot use glRotate because at the moment I pass the data to the tesselator object I cannot do any matrix transforms to it via the opengl functions because it's just taking in the data, not actually rendering it per se.
The way the data is stored is as following:
WaveFrontObject()
|
|-> Groups(String groupname)
|
|-> Faces()
|
|-> Vertex(float x, float y, float z)[]
|-> Float UVmap[] corresponding to each vertex
|-> drawFace() // Draws the face as a quad or triangle
So each of the above coordinates I gave is stored as a face of the wavefront object in the group "cube".
When the cube is added to the tesselator it is translated to the right coordinates in the world and it renders normal.
It always renders the same however. If I would want it to render at an angle I would have to make a seperate wavefront object at this moment to be able to do that. In my opnion that is madness to do when it can be solved with some math.
Needed in the answer
Explanation step by step how to build a translation matrix and an attempt to explain the math to me.
Explanation how to apply the translation matrix to the quads/triangles in the faces whist they keep oriented around the center of their location
x = 50.5
y = 100.5
z = 200.5
Some example/pseudo code to go along with the explanation.
The used programming language used to explain isn't really relevant as long as its in the C family
Please try to stay away from mathematical notation/speak. I don't know what alpha beta, thetha is, I do know what x axis, y axis and z axis is. I do know what angles are, but I do not know the names mathematicians find for it.
If you wish to use math names, please explain to me what they are in the 3D world/code and how they are formed/calculated.
I simply want to make a method/object along the lines of
Matrix.transformVertices(vertices[], 90deg x, 45 deg y, 0 deg z);
So the question really is Understanding 4x4 homogenous transform matrices
well without the math behind the only thing that left is geometric representation/meaning which is far better for human abstraction/understanding.
So what the 4x4 matrix is?
It is representation of some Cartesian coordinate system and it is composed of:
3 basis vectors (one for each axis) red,green,blue
So if the red,green,blue vectors are perpendicular to each other then the coordinate system is orthogonal. If they are also unit vectors then it is orthonormal (like for example unit matrix).
origin point gray
projection and homogenous side (unmarked bottom rest of the matrix)
This part is there only for enabling rotation and translation at once, therefore point used must be homogenous that means in form (x,y,z,w=1) for points and (x,y,z,w=0) for direction vectors. If it was just (x,y,z) then the matrix would be 3x3 and that is not enough for translation. I will not use any projections they are uneasy to explain geometrically.
This layout is from OpenGL notation there are also transposed representation out there (vectors are rows not columns)
now how to transform any point to/from this coordinate system:
g=M*l;
l=Inverse(M)*g;
where:
M is transform matrix
l is M local coordinate system point (LCS)
g is global coordinate system point (GCS)
for the transposed version (DirectX) it is:
l=M*g;
g=Inverse(M)*l;
That is because transposed orthogonal rotation matrix is also inverse of itself
for more info see transform matrix anatomy and 3D graphic pipeline
how to visualize it
Yes you can draw the matrix numbers but they do not make sense at first look especially if the numbers are changing so draw the axises vectors as on image above. Where each axis is a line from origin to origin + line_size*axis_vector
how to construct it
Just compute axis vectors and origin and put them inside matrix. To ensure orthogonality exploit cross product (but be careful with order of multiplicants to use the right direction) Here example of getting 3 basis vectors from direction
effects
rotation is done by rotating the axises so you can compute each axis by parametric circle equation ...
scaling is done by multiplying axises by scale factor
skewing is just using non perpendicular axises
rotation
For most cases the incremental rotation is used. There are two types
local rotation M'=M*rotation_matrix it rotates around local coordinate axises like you will control plane or car or player ... Most engines/games do not use these and fake it with euler angles instead which is a cheap solution (have many quirks and problems) because most people who using OpenGL do not even know this is possible and rather stack list of glRotate/glTranslate calls...
global rotation M'=Inverse(Inverse(M)*rotation_matrix) it rotates around global coordinate system axises.
where rotation_matrix is any standard rotation transform matrix.
If you have different matrix layout (transposed) then the rotations local and global are computed the other way around ...
You can also compute your rotation_matrix from 3 angles like:
rotation_matrix=rotation_around_x(ax)*rotation_around_y(ay)*rotation_around_z(az);
see Wiki rotation matrices the 3D Rx,Ry,Rz from Basic rotations are what you need. As you can see they are just unit circle parametric equation really. The order of multiplication change how the angles converge to target position. This is called Euler angles and I do not use it (I integrate step changes instead which has no restrictions if done properly not to mention it is simpler).
Anyway if you need you can convert transform matrix into euler angles relatively easily see:
Is there a way to calculate 3D rotation on X and Y axis from a 4x4 matrix
glRotate
If you want glRotate which is rotation around arbitrary axis not by 3 angles then There is workaround:
create transform matrix N for that axis
then transform your matrix M to it
rotate N by angle
then transform M back from N to global coordinates
Or you can use Rodrigues_rotation_formula instead
To transform Matrix to/from Matrix in this case just transform axises as points and leave the origin as is but the origin of N must be (0,0,0)!!! or the vectors transformed must have w=0 instead.
usage
Transformations are cumulative that means:
p'=M1*M2*M3*M4*p; is the same as M=M1*M2*M3*M4; p'=M*p
So if you have many points to transform then you precompute all transformations to single matrix and use just it. Do not need to multiply points by all subsequent matrices. OK now the concept:
you should have 3 coordinate systems:
camera C
world (usually unit matrix)
object O (each object have its own matrix)
so if you have cube with 8 vertexes p0,...,p7 then you have to perform transformation on each point from object local coordinates to camera local coordinates. Some gfx api do some of it so you apply only what you have to so you really need:
p(i)'=inverse(C)*unit*M*p(i);
the transforms are cumulative and unit matrix does not change anything so:
Q=inverse(C)*M; p(i)'=Q*p(i);
so before drawing compute Q for drawed object then take each point p(i) of the object and compute the transformed p(i)' and draw/use the transformed one ... The p(i)' is in local camera coordinate system (x,y of the screen) but there is no perspective there so before drawing you can also add any of the projection matrices and divide by z cordinate at the end ... The projection is also cumulative so it can be also inside Q
[edit1] C++ example
//$$---- Form CPP ----
//---------------------------------------------------------------------------
// apart from math.h include you can ignore this machine generated VCL related code
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
#include <math.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main; // pointer to main window ...
//---------------------------------------------------------------------------
// Here is the important stuff some math first
//---------------------------------------------------------------------------
const double deg=M_PI/180.0;
double divide(double x,double y);
void matrix_mul (double *c,double *a,double *b); // c[16] = a[16] * b[16]
void matrix_mul_vector(double *c,double *a,double *b); // c[ 4] = a[16] * b[ 4]
void matrix_subdet (double *c,double *a); // c[16] = all subdets of a[16]
double matrix_subdet ( double *a,int r,int s);// = subdet(r,s) of a[16]
double matrix_det ( double *a); // = det of a[16]
double matrix_det ( double *a,double *b); // = det of a[16] and subdets b[16]
void matrix_inv (double *c,double *a); // c[16] = a[16] ^ -1
//---------------------------------------------------------------------------
double divide(double x,double y)
{
if (!y) return 0.0;
return x/y;
}
void matrix_mul (double *c,double *a,double *b)
{
double q[16];
q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
for(int i=0;i<16;i++) c[i]=q[i];
}
void matrix_mul_vector(double *c,double *a,double *b)
{
double q[3];
q[0]=(a[ 0]*b[0])+(a[ 1]*b[1])+(a[ 2]*b[2])+(a[ 3]);
q[1]=(a[ 4]*b[0])+(a[ 5]*b[1])+(a[ 6]*b[2])+(a[ 7]);
q[2]=(a[ 8]*b[0])+(a[ 9]*b[1])+(a[10]*b[2])+(a[11]);
for(int i=0;i<3;i++) c[i]=q[i];
}
void matrix_subdet (double *c,double *a)
{
double q[16];
int i,j;
for (i=0;i<4;i++)
for (j=0;j<4;j++)
q[j+(i<<2)]=matrix_subdet(a,i,j);
for (i=0;i<16;i++) c[i]=q[i];
}
double matrix_subdet ( double *a,int r,int s)
{
double c,q[9];
int i,j,k;
k=0; // q = sub matrix
for (j=0;j<4;j++)
if (j!=s)
for (i=0;i<4;i++)
if (i!=r)
{
q[k]=a[i+(j<<2)];
k++;
}
c=0;
c+=q[0]*q[4]*q[8];
c+=q[1]*q[5]*q[6];
c+=q[2]*q[3]*q[7];
c-=q[0]*q[5]*q[7];
c-=q[1]*q[3]*q[8];
c-=q[2]*q[4]*q[6];
if (int((r+s)&1)) c=-c; // add signum
return c;
}
double matrix_det ( double *a)
{
double c=0;
c+=a[ 0]*matrix_subdet(a,0,0);
c+=a[ 4]*matrix_subdet(a,0,1);
c+=a[ 8]*matrix_subdet(a,0,2);
c+=a[12]*matrix_subdet(a,0,3);
return c;
}
double matrix_det ( double *a,double *b)
{
double c=0;
c+=a[ 0]*b[ 0];
c+=a[ 4]*b[ 1];
c+=a[ 8]*b[ 2];
c+=a[12]*b[ 3];
return c;
}
void matrix_inv (double *c,double *a)
{
double d[16],D;
matrix_subdet(d,a);
D=matrix_det(a,d);
if (D) D=1.0/D;
for (int i=0;i<16;i++) c[i]=d[i]*D;
}
//---------------------------------------------------------------------------
// now the object representation
//---------------------------------------------------------------------------
const int pnts=8;
double pnt[pnts*3]= // Vertexes for 100x100x100 cube centered at (0,0,0)
{
-100.0,-100.0,-100.0,
-100.0,+100.0,-100.0,
+100.0,+100.0,-100.0,
+100.0,-100.0,-100.0,
-100.0,-100.0,+100.0,
-100.0,+100.0,+100.0,
+100.0,+100.0,+100.0,
+100.0,-100.0,+100.0,
};
const int facs=6;
int fac[facs*4]= // faces (index of point used) no winding rule
{
0,1,2,3,
4,5,6,7,
0,1,5,4,
1,2,6,5,
2,3,7,6,
3,0,4,7,
};
double rep[16]= // 4x4 transform matrix of object (unit from start) at (0,0,+100)
{
1.0,0.0,0.0, 0.0,
0.0,1.0,0.0, 0.0,
0.0,0.0,1.0,100.0,
0.0,0.0,0.0,1.0,
};
double eye[16]= // 4x4 transform matrix of camera at (0,0,-150)
{
1.0,0.0,0.0, 0.0,
0.0,1.0,0.0, 0.0,
0.0,0.0,1.0,-150.0,
0.0,0.0,0.0,1.0,
};
//---------------------------------------------------------------------------
// this is how to draw it
//---------------------------------------------------------------------------
void obj(double *pnt,int pnts,int *fac,int facs,double *rep,double *ieye)
{
// variables for drawing
int i;
double p0[3],p1[3],p2[3],p3[3],m[16],d;
// gfx api variables (change to your stuff) Main is the main form of this application
TCanvas *scr=Main->bmp->Canvas;
double xs2=Main->ClientWidth/2,ys2=Main->ClientHeight/2;
double v=xs2*tan(30.0*deg); // 60 degree viewing angle perspective projection
matrix_mul(m,ieye,rep); // cumulate all needed transforms
for (i=0;i<facs*4;) // go through all faces
{
// convert all points of face
matrix_mul_vector(p0,m,&pnt[fac[i]*3]); i++;
matrix_mul_vector(p1,m,&pnt[fac[i]*3]); i++;
matrix_mul_vector(p2,m,&pnt[fac[i]*3]); i++;
matrix_mul_vector(p3,m,&pnt[fac[i]*3]); i++;
// here goes perspective divide by z coordinate if needed
d=divide(v,p0[2]); p0[0]*=d; p0[1]*=d;
d=divide(v,p1[2]); p1[0]*=d; p1[1]*=d;
d=divide(v,p2[2]); p2[0]*=d; p2[1]*=d;
d=divide(v,p3[2]); p3[0]*=d; p3[1]*=d;
// here is viewport transform (just translate (0,0) to middle of screen in this case
p0[0]+=xs2; p0[1]+=ys2;
p1[0]+=xs2; p1[1]+=ys2;
p2[0]+=xs2; p2[1]+=ys2;
p3[0]+=xs2; p3[1]+=ys2;
// draw quad
// I use VCL GDI TCanvas you use what you have ...
// and wireframe only to keep this simple (no Z buffer,winding culling,...)
scr->Pen->Color=clAqua; // perimeter wireframe
scr->MoveTo(p0[0],p0[1]);
scr->LineTo(p1[0],p1[1]);
scr->LineTo(p2[0],p2[1]);
scr->LineTo(p3[0],p3[1]);
scr->LineTo(p0[0],p0[1]);
// scr->Pen->Color=clBlue; // face cross to visualy check if I correctly generate the fac[]
// scr->MoveTo(p0[0],p0[1]);
// scr->LineTo(p2[0],p2[1]);
// scr->MoveTo(p1[0],p1[1]);
// scr->LineTo(p3[0],p3[1]);
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void TMain::draw()
{
if (!_redraw) return;
bmp->Canvas->Brush->Color=clBlack;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
// compute inverse of camera need to compute just once for all objects
double ieye[16];
matrix_inv(ieye,eye);
// draw all objects
obj(pnt,pnts,fac,facs,rep,ieye);
Main->Canvas->Draw(0,0,bmp);
_redraw=false;
}
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
{
// window constructor you can ignore this ... (just create a backbuffer bitmap here)
bmp=new Graphics::TBitmap;
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
pyx=NULL;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
{
// window destructor release memory ... also ignoe this
if (pyx) delete pyx;
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
{
// on resize event ... just resize/redraw backbuffer also can ignore this
xs=ClientWidth; xs2=xs>>1;
ys=ClientHeight; ys2=ys>>1;
bmp->Width=xs;
bmp->Height=ys;
if (pyx) delete pyx;
pyx=new int*[ys];
for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
{
// repaint event can ignore
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
{
// timer event to animate the cube ...
_redraw=true;
// rotate the object to see it in motion
double ang,c,s;
ang=5.0*deg; c=cos(ang); s=sin(ang); // rotate baround z by 5 degrees per timer step
double rz[16]= { c, s, 0, 0,
-s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1 };
ang=1.0*deg; c=cos(ang); s=sin(ang); // rotate baround x by 1 degrees per timer step
double rx[16]= { 1, 0, 0, 0,
0, c, s, 0,
0,-s, c, 0,
0, 0, 0, 1 };
matrix_mul(rep,rep,rz);
matrix_mul(rep,rep,rx);
draw();
}
//---------------------------------------------------------------------------
here is how it looks like:
And GIF animation with back face culling:
[notes]
If you have more questions then comment me ...
[Edit2] basic 3D vector operations often needed
If you do not know how to compute vector operations like cross/dot products or absolute value see:
// 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))
here my C++ vector math:
static double vector_tmp[3];
double divide(double x,double y) { if ((y>=-1e-30)&&(y<=+1e-30)) return 0.0; return x/y; }
double* vector_ld(double x,double y,double z) { double *p=vector_tmp; p[0]=x; p[1]=y; p[2]=z; return p;}
double* vector_ld(double *p,double x,double y,double z) { p[0]=x; p[1]=y; p[2]=z; return p;}
void vector_copy(double *c,double *a) { for(int i=0;i<3;i++) c[i]=a[i]; }
void vector_abs(double *c,double *a) { for(int i=0;i<3;i++) c[i]=fabs(a[i]); }
void vector_one(double *c,double *a)
{
double l=divide(1.0,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
c[0]=a[0]*l;
c[1]=a[1]*l;
c[2]=a[2]*l;
}
void vector_len(double *c,double *a,double l)
{
l=divide(l,sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])));
c[0]=a[0]*l;
c[1]=a[1]*l;
c[2]=a[2]*l;
}
void vector_neg(double *c,double *a) { for(int i=0;i<3;i++) c[i]=-a[i]; }
void vector_add(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]+b[i]; }
void vector_sub(double *c,double *a,double *b) { for(int i=0;i<3;i++) c[i]=a[i]-b[i]; }
void vector_mul(double *c,double *a,double *b) // cross
{
double q[3];
q[0]=(a[1]*b[2])-(a[2]*b[1]);
q[1]=(a[2]*b[0])-(a[0]*b[2]);
q[2]=(a[0]*b[1])-(a[1]*b[0]);
for(int i=0;i<3;i++) c[i]=q[i];
}
void vector_mul(double *c,double *a,double b) { for(int i=0;i<3;i++) c[i]=a[i]*b; }
void vector_mul(double *c,double a,double *b) { for(int i=0;i<3;i++) c[i]=a*b[i]; }
double vector_mul( double *a,double *b) { double c=0; for(int i=0;i<3;i++) c+=a[i]*b[i]; return c; } // dot
double vector_len(double *a) { return sqrt((a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2])); }
double vector_len2(double *a) { return (a[0]*a[0])+(a[1]*a[1])+(a[2]*a[2]); }
[Edit3] local rotations for camera and object control via keyboard
As this has been asked a lot lately here some example answers of mine with demos:
stationary camera view control (partial pseudo inverse matrix)
camera and player control (inverse matrix)
How to preserve accuracy with cumulative transforms over time (full pseudo inverse matrix)
rotundus style simple OpenGL/C++/VCL player control example

3D Fireworks Effect in C/C++ using sine or cosine function

I am trying to implement fireworks effect in C. I have a cube with dimensions 10x10x10. A rocket starts from the ground, and when it reaches 8th floor it explodes. Here's the point I cannot do - the explosion. How can I implement this using sine or cosine function?
so in point(5,0,7) //(x,y,z)// a rocket goes in the air
for (j=0; j<9; j++) {
setpoint(x, y, j);
delay(100);
clean(); //clears everything
}
Here comes the point to make the explosion. How this can be achieved? It can sparkle in random positions, too. Thanks in advance.
Heh I did find some time (done in 1.5 hod) and will for this funny stuff :)
OK first some updates in the LED_cube class to support voxel point output and dimming the rest is the same as for the sphere from your another question ...
//---------------------------------------------------------------------------
//--- LED cube class ver: 1.01 ----------------------------------------------
//---------------------------------------------------------------------------
#ifndef _LED_cube_h
#define _LED_cube_h
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
const int _LED_cube_size=32;
//---------------------------------------------------------------------------
class LED_cube
{
public:
int n,map[_LED_cube_size][_LED_cube_size][_LED_cube_size];
LED_cube() { n=_LED_cube_size; }
LED_cube(LED_cube& a) { *this=a; }
~LED_cube() { }
LED_cube* operator = (const LED_cube *a) { *this=*a; return this; }
//LED_cube* operator = (const LED_cube &a) { /*...copy...*/ return this; }
void cls(int col); // clear cube with col 0x00BBGGRR
void mul(int mul); // mull all channels by mul and then shr by 8
void point(int x,int y,int z,int col); // draws voxel with col 0x00BBGGRR
void sphere(int x0,int y0,int z0,int r,int col); // draws sphere surface with col 0x00BBGGRR
void glDraw(); // render cube by OpenGL as 1x1x1 cube at 0,0,0
};
//---------------------------------------------------------------------------
void LED_cube::cls(int col)
{
int x,y,z;
for (x=0;x<n;x++)
for (y=0;y<n;y++)
for (z=0;z<n;z++)
map[x][y][z]=col;
}
//---------------------------------------------------------------------------
void LED_cube::mul(int mul)
{
union { BYTE db[4]; int dd; } c;
int x,y,z,i;
for (x=0;x<n;x++)
for (y=0;y<n;y++)
for (z=0;z<n;z++)
{
c.dd=map[x][y][z];
i=c.db[0]; i=(i*mul)>>8; c.db[0]=i;
i=c.db[1]; i=(i*mul)>>8; c.db[1]=i;
i=c.db[2]; i=(i*mul)>>8; c.db[2]=i;
map[x][y][z]=c.dd;
}
}
//---------------------------------------------------------------------------
void LED_cube::point(int x,int y,int z,int col)
{
if ((x>=0)&&(x<n))
if ((y>=0)&&(y<n))
if ((z>=0)&&(z<n))
map[x][y][z]=col;
}
//---------------------------------------------------------------------------
void LED_cube::sphere(int x0,int y0,int z0,int r,int col)
{
int x,y,z,xa,ya,za,xb,yb,zb,xr,yr,zr,xx,yy,zz,rr=r*r;
// bounding box
xa=x0-r; if (xa<0) xa=0; xb=x0+r; if (xb>n) xb=n;
ya=y0-r; if (ya<0) ya=0; yb=y0+r; if (yb>n) yb=n;
za=z0-r; if (za<0) za=0; zb=z0+r; if (zb>n) zb=n;
// project xy plane
for (x=xa,xr=x-x0,xx=xr*xr;x<xb;x++,xr++,xx=xr*xr)
for (y=ya,yr=y-y0,yy=yr*yr;y<yb;y++,yr++,yy=yr*yr)
{
zz=rr-xx-yy; if (zz<0) continue; zr=sqrt(zz);
z=z0-zr; if ((z>0)&&(z<n)) map[x][y][z]=col;
z=z0+zr; if ((z>0)&&(z<n)) map[x][y][z]=col;
}
// project xz plane
for (x=xa,xr=x-x0,xx=xr*xr;x<xb;x++,xr++,xx=xr*xr)
for (z=za,zr=z-z0,zz=zr*zr;z<zb;z++,zr++,zz=zr*zr)
{
yy=rr-xx-zz; if (yy<0) continue; yr=sqrt(yy);
y=y0-yr; if ((y>0)&&(y<n)) map[x][y][z]=col;
y=y0+yr; if ((y>0)&&(y<n)) map[x][y][z]=col;
}
// project yz plane
for (y=ya,yr=y-y0,yy=yr*yr;y<yb;y++,yr++,yy=yr*yr)
for (z=za,zr=z-z0,zz=zr*zr;z<zb;z++,zr++,zz=zr*zr)
{
xx=rr-zz-yy; if (xx<0) continue; xr=sqrt(xx);
x=x0-xr; if ((x>0)&&(x<n)) map[x][y][z]=col;
x=x0+xr; if ((x>0)&&(x<n)) map[x][y][z]=col;
}
}
//---------------------------------------------------------------------------
void LED_cube::glDraw()
{
#ifdef __gl_h_
int x,y,z;
float p[3],dp=1.0/float(n-1);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glPointSize(2.0);
glBegin(GL_POINTS);
for (p[0]=-0.5,x=0;x<n;x++,p[0]+=dp)
for (p[1]=-0.5,y=0;y<n;y++,p[1]+=dp)
for (p[2]=-0.5,z=0;z<n;z++,p[2]+=dp)
{
glColor4ubv((BYTE*)(&map[x][y][z]));
glVertex3fv(p);
}
glEnd();
glDisable(GL_BLEND);
glPointSize(1.0);
#endif
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//--------------------------------------------------------------------------
the important stuff are:
void mul(int mul); - used for dimming out the whole voxel map
void point(int x,int y,int z,int col); - used to set collor of single voxel
Now the particles
//---------------------------------------------------------------------------
class particle
{
public:
double x, y, z; // position
double vx,vy,vz; // velocity
double ax,ay,az; // acceleration driving force/m after update is reseted
double i; // intensity
particle()
{
x=0.0; y=0.0; z=0.0;
vx=0.0; vy=0.0; vz=0.0;
ax=0.0; ay=0.0; az=0.0;
i=0.0;
};
particle(particle& a){ *this=a; };
~particle(){};
particle* operator = (const particle *a) { *this=*a; return this; };
// particle* operator = (const particle &a) { ...copy... return this; };
void update(double dt)
{
double c0,c;
// gravity
ay-=9.81;
// friction in gass
c=0.001;
if (vx>0.0) c0=-c; else c0=+c; ax+=vx*vx*c0;
if (vy>0.0) c0=-c; else c0=+c; ay+=vy*vy*c0;
if (vz>0.0) c0=-c; else c0=+c; az+=vz*vz*c0;
// friction in liquid
c=0.0;
ax-=vx*vx*vx*c;
ay-=vy*vy*vy*c;
az-=vz*vz*vz*c;
// D'ALembert
vx+=ax*dt;
vy+=ay*dt;
vz+=az*dt;
x+=vx*dt;
y+=vy*dt;
z+=vz*dt;
// reset acceleration
ax=0.0; ay=0.0; az=0.0;
}
};
//---------------------------------------------------------------------------
List<particle> particles; // use any list/array you have at your disposal you need just function add and delete item
//---------------------------------------------------------------------------
this is how to draw the scene:
cube.mul(200); // dimm the voxel map insted of clearing it (intensity*=200/256)
for (int i=0;i<particles.num;i++)
{
particle *p=&particles[i];
int j=double(255.0*p->i);
if (j<0) j=0;
if (j>255) j=255;
cube.point(p->x,p->y,p->z,0x00010101*j);
}
cube.glDraw();
This is how to update simulation in some timer (double dt=timer interval in seconds !!!)
double i0=1.0; // intensity at shoot start
double i1=0.9*i0; // intensity after explosion
double v0=0.6*double(_LED_cube_size); // shoot start speed
double v1=0.5*v0,v1h=0.5*v1; // explosion speed
if (particles.num==0) // shoot new particle if none in list
{
particle p;
p.x=_LED_cube_size>>1;
p.y=0.0;
p.z=_LED_cube_size>>1;
p.vy=v0;
p.i=i0;
particles.add(p);
}
for (int i=0;i<particles.num;i++) // update all particles in list
{
particle *p=&particles[i];
p->update(dt);
if (fabs(p->i-i0)<1e-6) // intensity detect state before explosion
{
if (p->vy<=0.0) // explode near/after peak reached
{
particle q;
q.x=p->x; // copy position
q.y=p->y;
q.z=p->z;
q.i=i1; // new intensity
particles.del(i); // remove old particle
i--;
for (int j=0;j<50;j++) // add new random particles
{
q.vx=v1*Random()-v1h;
q.vy=v1*Random()-v1h;
q.vz=v1*Random()-v1h;
particles.add(q)-v1h;
}
continue; // avoid usage of p pointer after delete
}
}
else{ // after explosion
p->i*=0.95; // dimm intensity
}
if ((p->y<0.0)||(p->i<0.01))// remove particles below the ground or too dimmed out
{
particles.del(i);
i--;
continue; // avoid usage of p pointer after delete
}
}
This is how it looks
Sorry for the banner but I do not have anything solid for gif conversion and this site will not accept wmv ... You have to play with constants to match desired output on your LED cube size constants to play with:
whole cube map dimm rate (cube.mul(200)) currently (200/256) per frame
speeds,intensities v0,v1,i0,i1
number of new particles after explosion currently 50
particle intensity dimm rate after explosion currently 0.95
[Notes]
List<> is just template for dynamic array can use anything from std:: or own array ...
Do not forget to set dt constant to time elapsed between updates. Hope I did not forget to copy something. Hope it helps
It's better to do this using an upside down parabola instead of sin/cos. At the point of explosion give each particle a random horizontal speed. This speed is constant till the particle hits the ground. You also need to give each particle a random vertical speed. this time, however, you'll add to this speed an amount proportional to -0.5*g*dt^2 (strictly speaking, this is numerically wrong, but you won't notice unless you're doing scientific analysis). Here, g is the acceleration due to gravitation and dt is the time step. That's all.

OpenGL 3D Picking

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.

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.