How to rotate object around local axis in OpenGL?
I am working on an ongoing project where I want to align the links of a chain so that it follows the contours of a Bezier curve. I am currently following the steps below.
- Drawing the curve.
- Use a display list to create one link of the chain.
- Use a FOR loop to repeatedly call a function that calculates the angle between two points on the curve, returns the angle and the axis around which the link should be rotated.
- Rotate by the angle "a" and translate to new position, place the link at the new position.
Edit: I should also say that the centres of the two half torus must lie on the Bezier curve. Also I am aware that the method I use to draw the torus I tedious, I will use TRIANGLE_FAN or QUAD_STRIP later on to draw the torus in a more efficient way.
While at first glance this logic looks like it would render the chain properly, the end result is not what I had imagined it to be. Here is a picture of what the chain looks like.
I read that you have to translate the object to the origin before rotation? Would I just call glTranslate(0,0,0) and then follow step 4 from above?
I have included the relevant code from what I have done so far, I would appreciate any suggestions to get me code work properly.
/* this function calculates the angle between two vectors oldPoint and new point contain the x,y,z coordinates of the two points,axisOfRot is used to return the x,y,z coordinates of the rotation axis*/
double getAxisAngle(pointType oldPoint[],
pointType newPoint[],pointType axisOfRot[]){
float tmpPoint[3];
float normA = 0.0,normB = 0.0,AB = 0.0,angle=0.0;
int i;
axisOfRot->x= oldPoint->y * newPoint->z - oldPoint->z * newPoint->y;
axisOfRot->y= oldPoint->z * newPoint->x - oldPoint->x * newPoint->z;
axisOfRot->z= oldPoint->x * newPoint->y - oldPoint->y * newPoint->x;
normA=sqrt(oldPoint->x * oldPoint->x + oldPoint->y * oldPoint->y + oldPoint->z *
oldPoint->z);
normB=sqrt(newPoint->x * newPoint->x + newPoint->y * newPoint->y + newPoint->z *
newPoint->z);
tmpPoint[0] = oldPoint->x * newPoint->x;
tmpPoint[1] = oldPoint->y * newPoint->y;
tmpPoint[2] = oldPoint->z * newPoint->z;
for(i=0;i<=2;i++)
AB+=tmpPoint[i];
AB /= (normA * normB);
return angle = (180/PI)*acos(AB);
}
/* this function calculates and returns the next point on the curve give the 4 initial points for the curve, t is the tension of the curve */
void bezierInterpolation(float t,pointType cPoints[],
pointType newPoint[]){
newPoint->x = pow(1 - t, 3) * cPoints[0].x +3 * pow(1 - t , 2) * t * cPoints[1].x + 3
* pow(1 - t, 1) * pow(t, 2) * cPoints[2].x + po开发者_StackOverflow中文版w(t, 3) * cPoints[3].x;
newPoint->y = pow(1 - t, 3) * cPoints[0].y +3 * pow(1 - t , 2) * t * cPoints[1].y + 3
* pow(1 - t, 1) * pow(t, 2) * cPoints[2].y + pow(t, 3) * cPoints[3].y;
newPoint->z = pow(1 - t, 3) * cPoints[0].z +3 * pow(1 - t , 2) * t * cPoints[1].z + 3
* pow(1 - t, 1) * pow(t, 2) * cPoints[2].z + pow(t, 3) * cPoints[3].z;
}
/* the two lists below are used to create a single link in a chain, I realize that creating a half torus using cylinders is a bad idea, I will use GL_STRIP or TRIANGLE_FAN once I get the alignment right
*/
torusList=glGenLists(1);
glNewList(torusList,GL_COMPILE);
for (i=0; i<=180; i++)
{
degInRad = i*DEG2RAD;
glPushMatrix();
glTranslatef(cos(degInRad)*radius,sin(degInRad)*radius,0);
glRotated(90,1,0,0);
gluCylinder(quadric,Diameter/2,Diameter/2,Height/5,10,10);
glPopMatrix();
}
glEndList();
/*! create a list for the link , 2 half torus and 2 columns */
linkList = glGenLists(1);
glNewList(linkList, GL_COMPILE);
glPushMatrix();
glCallList(torusList);
glRotatef(90,1,0,0);
glTranslatef(radius,0,0);
gluCylinder(quadric, Diameter/2, Diameter/2, Height,10,10);
glTranslatef(-(radius*2),0,0);
gluCylinder(quadric, Diameter/2, Diameter/2, Height,10,10);
glTranslatef(radius,0, Height);
glRotatef(90,1,0,0);
glCallList(torusList);
glPopMatrix();
glEndList();
Finally here is the code for creating the three links in the chain
t=0.031;
bezierInterpolation(t,cPoints,newPoint);
a=getAxisAngle(oldPoint,newPoint,axisOfRot);
glTranslatef(newPoint->x,newPoint->y,newPoint->z);
glRotatef(a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glCallList(DLid);
glRotatef(-a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glTranslatef(-newPoint->x,-newPoint->y,-newPoint->z);
oldPoint[0]=newPoint[0];
bezierInterpolation(t+=GAP,cPoints,newPoint);
a=getAxisAngle(oldPoint,newPoint,axisOfRot);
glTranslatef(newPoint->x,newPoint->y,newPoint->z);
glRotatef(90,0,1,0);
glRotatef(a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glCallList(DLid);
glRotatef(-a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glRotatef(90,0,1,0);
glTranslatef(-newPoint->x,-newPoint->y,-newPoint->z);
oldPoint[0]=newPoint[0];
bezierInterpolation(t+=GAP,cPoints,newPoint);
a=getAxisAngle(oldPoint,newPoint,axisOfRot);
glTranslatef(newPoint->x,newPoint->y,newPoint->z);
glRotatef(-a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glCallList(DLid);
glRotatef(a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glTranslatef(-newPoint->x,-newPoint->y,newPoint->z);
One thing to note is that glTranslate function builds on previous translations. I.E. a glTranslatef(0.0,0.0,0.0); won't go to the origin, it will just move the "pen" nowhere. Luckily, the "pen" starts at the origin. if you translate out to 1.0,1.0,1.0 then try a glTranslatef(0.0,0.0,0.0); you will still be drawing at 1.0,1.0,1.0;
Also, you seem to grasp the fact that openGL post-multiplies matricies. To that end, you are correctly "undoing" your matrix operations after a draw. I only see one spot where you could potentially be off here and that is in this statement:
glRotatef(90,0,1,0);
glRotatef(a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glCallList(DLid);
glRotatef(-a,axisOfRot->x,axisOfRot->y,axisOfRot->z);
glRotatef(90,0,1,0);
Here you correctly undo the second rotation, but the first one you seem to rotate even more around the y axis. the very last glRotatef needs to read glRotatef(-90,0,1,0); if you want to be undoing that rotation.
I looked at your code and assuming that code performing bezierInterp and axis angle is correct. Based on code, I have following suggestions:
The way you are creating a single link looks very costly. As you are using gluCylinder for 180 times. This will generate a lot of vertices for a small link. You can create a single torus and apply scale such that it appears like a link!
Whenever you do any matrix operation, it is good idea to set the mode before. This is important before doing push and pop. In you display list you have push and pop without setting any mode and neither it is set in caller. This is not good practice and will result in lot of bugs/issues. You can remove push and pop from call list and keep only geometry in it.
You have heard advice suggesting to do translation to origin before rotation as translation * rotation! = rotation * translation. So the way you would write your render loop is:
// Set matrix mode glMatrixMode(GL_MODELVIEW); for(number of links) { glLoadIdentity(); // makes model view matrix identity - default location` glTranslatef(x,y,z); // Translate to a point on beizer curve glRotatef(..); // Rotate link glCallList(link); // can be simple torus, only geometry centered at origin }
Above code renders a link repeated at specified location. Read OpenGL Red book's chapter 3 - Example 3.6 (planetary system) example to understand how you can place each link at different location correctly.
精彩评论