Rotate 3D object to align to Vector

This problem has been troubling me for quite some time now and I can not seem to figure out the answer. I have a vector which I would like to align a 3d object along, in this case a ofxCone.

The cone should point along the white line towards the red sphere, which is slightly ahead in z space. However, I can not seem to figure out the math behind this to apply the transformations via glrotatef in one call or glrotatef for x, then y and then z.

How it should look:

  
  
ofxLightsOn();  
	  
	ofxVec3f up(0, 1, 0);  
	ofxVec3f right(1, 0, 0);  
	ofxVec3f forward(0, 0, 1);  
	  
	ofxVec3f center(0, 0, 0);  
	ofxVec3f tar(mouseX- ofGetWidth()*.5, mouseY- ofGetHeight()*.5, 200);  
	//ofxVec3f tar(100, 100, 100);  
	  
	ofxVec3f normal = tar - center;  
	  
	float rotx = normal.angle( forward );  
	float roty = normal.angle(right);  
	float rotz = normal.angle(up);  
	  
	glPushMatrix();  
	glTranslatef(ofGetWidth()*.5, ofGetHeight()*.5, 0);  
	ofSetColor(255, 0, 0);  
	ofxSphere(tar.x, tar.y, tar.z, 50);  
	  
	ofSetColor(150, 150, 150);  
	glPushMatrix();  
	glRotatef(rotx, 1, 0, 0);  
	glRotatef(roty, 0, 1, 0);  
	glRotatef(rotz, 0, 0, 1);  
	ofxCone(center, ofxVec3f(50, 180, 50), ofxVec3f(0, 0, 0) );  
	glPopMatrix();  
	  
	  
	ofxLightsOff();  
	  
	ofSetColor(255, 255, 255);  
	drawLine(center, tar);  
	glPopMatrix();  
  

You can download the project out here:

http://nickhardeman.com/labs/Axis-Align.zip

This page explains how to get the angle and axis for 3D rotations http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm, I use this for my projects but for some reason is not working with your code, well it sort of work, this is what I change:

  
void testApp::draw() {  
	  
	ofxLightsOn();  
	  
	float z = sin( ofGetElapsedTimef() ) * 200.0f;  
	  
	ofxVec3f center(0, 0, 0);  
	ofxVec3f tar(mouseX- ofGetWidth()*.5, mouseY- ofGetHeight()*.5, z);  
	//ofxVec3f tar(100, -200, z);  
	  
	  
	glPushMatrix();  
	glTranslatef(ofGetWidth()*.5, ofGetHeight()*.5, 0);  
	ofSetColor(255, 0, 0);  
	ofxSphere(tar.x, tar.y, tar.z, 50.0f);  
	  
	// point to sphere   
	ofxVec3f newDirection(tar);  
	newDirection.normalize();  
	ofxVec3f newUp( 0, 1, 0 );  
	ofxVec3f axis = newDirection.cross( newUp );  
	axis.normalize();  
	  
	float dotP = newDirection.dot( newUp );  
	float angle = acos( dotP );  
	  
	//cout << "axis:" << axis.x << ", " << axis.y << ", " << axis.z << endl;  
	  
	ofSetColor(150, 150, 150);  
	glPushMatrix();  
	glRotatef( ofRadToDeg(angle), axis.x, axis.y, axis.z );  
	ofxCone(center, ofxVec3f(50, 180, 50) );  
	glPopMatrix();  
	  
	ofxLightsOff();  
	  
	ofSetColor(255, 255, 255);  
	drawLine(center, tar);  
	glPopMatrix();  
	  
}  

Not sure why the axis.y is always = 0 and I believe this is what we are missing to get a proper rotation, the strange thing is that, like I said, I use the same logic in my projects and it works fine

Hope that helps

Cheers

  • rS

Thanks for the reply. I have tried some various combinations and this seems to get the correct result, except that it needs to be rotated by 90 degrees along an axis so that the point faces the ball. Trying to figure out how to get that axis.

The forum isn’t allowing me to post images. :frowning:

  
  
//--------------------------------------------------------------  
void testApp::draw() {  
	  
	ofxLightsOn();  
	  
	float z = sin( ofGetElapsedTimef() ) * 200.0f;  
	  
	ofxVec3f center(0, 0, 0);  
	ofxVec3f tar(mouseX- ofGetWidth()*.5, mouseY- ofGetHeight()*.5, z);  
	  
	glPushMatrix();  
	glTranslatef(ofGetWidth()*.5, ofGetHeight()*.5, 0);  
	ofSetColor(255, 0, 0);  
	ofxSphere(tar.x, tar.y, tar.z, 50.0f);  
	  
  
	ofxVec3f normal = tar - center;  
	normal.normalize();  
	  
	ofxVec3f up(0, 1, 0);  
	ofxVec3f right	= up.crossed(normal).normalized();  
	up = normal.crossed(right).normalized();  
	  
	ofxVec3f axis	= normal.crossed(right).normalized();  
	  
	float angle		= normal.angle(right);  
	  
	cout << "axis:" << axis.x << ", " << axis.y << ", " << axis.z << endl;  
	  
	ofSetColor(150, 150, 150);  
	glPushMatrix();  
	glTranslatef(0, 0, 0);  
	//glRotatef(90, 0, 0, 1);  
	glRotatef( angle, axis.x, axis.y, axis.z );  
	ofxCone(center, ofxVec3f(50, 180, 50) );  
	glPopMatrix();  
	  
	  
	ofxLightsOff();  
	  
	ofSetColor(255, 255, 255);  
	drawLine(center, tar);  
	glPopMatrix();  
	  
}  
  

I figured out the solution with help from the following link:
http://stackoverflow.com/questions/764313/quaternion-math-for-rotation

The forward vector that should be crossed with the normal is (0,-1,0), since that is the orientation of an ofxCone without any transformations applied, it points straight up.

Here is the complete draw function

  
  
void testApp::draw() {  
	  
	ofxLightsOn();  
	  
	float z = sin( ofGetElapsedTimef() ) * 200.0f;  
	  
	ofxVec3f center(0, 0, 0);  
	ofxVec3f tar(mouseX- ofGetWidth()*.5, mouseY- ofGetHeight()*.5, z);  
	  
	  
	glPushMatrix();  
	glTranslatef(ofGetWidth()*.5, ofGetHeight()*.5, 0);  
	ofSetColor(255, 0, 0);  
	ofxSphere(tar.x, tar.y, tar.z, 50.0f);  
	  
	  
	/***********************/  
	ofxVec3f normal = tar - center;  
	normal.normalize();  
	  
	ofxVec3f forward(0, -1, 0);  
	ofxVec3f axis	= forward.crossed(normal);  
	axis.normalize();  
	float angle		= forward.angle(normal);  
	  
	//not necessary - draw the other axis for perspective //  
	ofxVec3f right = axis.crossed(normal);  
	right.normalize();  
	right *= 250;  
	  
	// extend the axis so that we can draw it larger //  
	ofxVec3f da = axis;  
	da *= 250;  
	  
	glPushMatrix();  
	glTranslatef(0, 0, 0);  
	  
	ofxLightsOff();  
	ofSetColor(0, 0, 255);  
	drawLine(center, right);  
	  
	ofSetColor(0, 255, 0);  
	drawLine(center, da);  
	ofxLightsOn();  
	glRotatef( angle, axis.x, axis.y, axis.z );  
	ofSetColor(150, 150, 150);  
	ofxCone(center, ofxVec3f(50, 180, 50) );  
	//ofxBox(center, ofxVec3f(50, 50, 50));  
	glPopMatrix();  
	  
	/*********************************************************/  
	  
	ofxLightsOff();  
	  
	ofSetColor(255, 255, 255);  
	drawLine(center, tar);  
	glPopMatrix();  
	  
}  
  

Thanks for your help @nardove :wink:

And the complete project is here:
http://nickhardeman.com/labs/Axis-Align-v2.zip

Hi Nick,

That is awesome, every time I attempt this kind of animations I find something new, now there is a behaviour I would like to know how to address, in our example, if we move the tar vector to the bottom you can see how the rotation flips from right to left very quickly, any ideas how to avoid that?

Cheers

  • rS

[quote author=“nardove”]Hi Nick,

That is awesome, every time I attempt this kind of animations I find something new, now there is a behaviour I would like to know how to address, in our example, if we move the tar vector to the bottom you can see how the rotation flips from right to left very quickly, any ideas how to avoid that?

Cheers

  • rS[/quote]

It seems to happen when the target vector is parallel or close to parallel with the forward vector. But I am not sure how to fix it? :?

this is most probably gimbal-lock. You’d have to use quaternions-to-avoid-it. Elegant but complicated.

Thanks for a great tutorial on this. I have been struggling with 3d object alignment for a while now and couldn’t figure out till I found this thread.

Pictures were definitely helpful to see the big picture.

Kutos to you!
John Stain

Really nice, thanks!

Ok so I try to figure out how to solve the problem I mention in my last post, from this site
http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm I quote

If the vectors are parallel (angle = 0 or 180 degrees) then the length of v1 x v2 will be zero because sin(0)=sin(180)=0. In the zero case the axis does not matter and can be anything because there is no rotation round it. In the 180 degree case the axis can be anything at 90 degrees to the vectors so there is a whole range of possible axies.

Which I believe describes the problem, so I try to change the code to use ofQuaternions

Here is the code in the draw() method:

  
  
        mCam.begin();  
	  
	glEnable( GL_DEPTH_TEST );  
	  
	// Target  
	float t = ofGetElapsedTimef() * 0.9f;  
	ofVec3f target( mouseX - ofGetWidth()/2, ofGetHeight()/2 - mouseY, sin(t) * 150 );  
	  
	// Calculate rotation  
	ofVec3f newTarget = target;  
	newTarget.normalize();  
	ofVec3f up( 0, 1, 0 );  
	ofVec3f axis = up.crossed( newTarget );  
	axis.normalize();  
	  
	float angle = up.angle( newTarget );  
	//float angle = ofRadToDeg( acos( up.dot( newTarget ) ) );  
	  
	// Using quats  
	/*float qx = axis.x * sin( angle/2 );  
	float qy = axis.y * sin( angle/2 );  
	float qz = axis.z * sin( angle/2 );  
	float qw = cos( angle/2 );  
	ofQuaternion q( qx, qy, qz, qw );*/  
	ofQuaternion q;  
	q.makeRotate( angle, axis );  
	  
	cout << "q.w: " << ofRadToDeg(q.w()) << endl;  
	cout << "q.x: " << q.x() << endl;  
	cout << "q.y: " << q.y() << endl;  
	cout << "q.z: " << q.z() << endl;  
	cout << "--------------" << endl;  
	  
	// Draw rotation target  
	ofSetHexColor( mColorPalette[1] );  
	ofSphere( target, 15 );  
	  
	// Draw a box that points to the mouse position  
	ofPushMatrix();  
	//ofRotate( angle, axis.x, axis.y, axis.z );  
	ofRotate( ofRadToDeg(q.w()), q.x(), q.y(), q.z() );  
	ofSetHexColor( mColorPalette[3] );  
	ofBox( 0, 0, 0, 150 );  
	drawAxis( 180 );  
	ofPopMatrix();  
	  
	mCam.end();  
  

I still dont get how to use quats to achieve this kind of orientations, can someone please share some light in the subject?

Any help will be much appreciated

  • rS

Hi :slight_smile:
Thank you for sharing the code.

I did Quaternion rotation using ofxQuaternion(reside in ofxVectorMath).
It looks work, but it is out of alignment in some degree.
Does anyone know how can I align object correctly?

[attachment=1:blhiz2ck]scrn.png[/attachment:blhiz2ck]

  
  
//--------------------------------------------------------------  
void testApp::draw() {  
	  
	ofxLightsOn();  
	  
	float z = sin( ofGetElapsedTimef() ) * 200.0f;  
	  
	ofxVec3f center(0, 0, 0);  
	ofxVec3f tar(mouseX- ofGetWidth()*.5, mouseY- ofGetHeight()*.5, z);  
	  
	  
	glPushMatrix();  
	glTranslatef(ofGetWidth()*.5, ofGetHeight()*.5, 0);  
	ofSetColor(255, 0, 0);  
	ofxSphere(tar.x, tar.y, tar.z, 50.0f);  
	  
	  
	/***********************/  
	ofxVec3f normal = tar - center;  
	normal.normalize();  
	  
	ofxVec3f forward(0, 1, 0);  
	ofxVec3f axis	= forward.crossed(normal);  
	axis.normalize();  
	float angle		= forward.angle(normal);  
	  
	// set Quaternion  
	ofxQuaternion quat(axis.x, axis.y, axis.z, ofDegToRad(angle));  
	  
	//not necessary - draw the other axis for perspective //  
	ofxVec3f right = axis.crossed(normal);  
	right.normalize();  
	right *= 250;  
	  
	// extend the axis so that we can draw it larger //  
	ofxVec3f da = axis;  
	da *= 250;  
	  
	glPushMatrix();  
	glTranslatef(0, 0, 0);  
	  
	ofxLightsOff();  
	ofSetColor(0, 0, 255);  
	drawLine(center, right);  
	  
	ofSetColor(0, 255, 0);  
	drawLine(center, da);  
	ofxLightsOn();  
	  
	// get rot from Quaternion  
	ofxVec3f qaxis; float qangle;  
	quat.getRotate(qangle, qaxis);	  
	glRotatef(ofRadToDeg(qangle), -qaxis.x, qaxis.y, -qaxis.z);	  
  
	ofSetColor(150, 150, 150);  
	ofxCone(center, ofxVec3f(50, 180, 50) );  
	//ofxBox(center, ofxVec3f(50, 50, 50));  
	glPopMatrix();  
	  
	/*********************************************************/  
	  
	  
	ofxLightsOff();  
	  
	ofSetColor(255, 255, 255);  
	drawLine(center, tar);  
	glPopMatrix();  
	  
}  
  

I appreciate any reply!
Thanks in advance.

Axis_Align_v3.zip

Hi, apparently the solution to solve the flip issue is in this post, http://forum.openframeworks.cc/t/render-worm-body-tube/3383/9 I will try it when I get a chance