Rotate object to face towards point

Started by
18 comments, last by fng 14 years, 1 month ago
Hello all, So, the problem I'm trying to solve is this: I have a fish swimming towards an object representing some bait. I have no problem moving the fish towards the bait, however I also need to orient the fish in the direction it is swimming, and I can't figure out how to do that correctly. Here is what I'm doing now: VecDisplacement (VecNewPos - VecCurrentPos) VecHeading (starts out as (0, 1, 0), as the fish is facing that direction when loaded) So I find the axis to rotate about: VecAxis = VecHeading x VecDisplacement Then finding the angle to rotate: |VecHeading x VecDisplacement| = |VecHeading|*|VecDisplacement|*sin(a) Then updating the heading vector: VecHeading = VecDisplacement And then passing that to glRotatef: glRotatef(turnAngle, turnAxis->x, turnAxis->y, turnAxis->x); This doesn't quit work, so what am I doing wrong / missing? Note that I'm not trying to get motion rotation, but if someone could explain how to do that too, that would be great (not the primary thing though).
Advertisement
There's not quite enough information there to tell you why your current code isn't working, mostly because we don't know in what context glRotatef() is being used. (That is, what's the state of the matrix stack when it's invoked? Are you saving the orientation somehow after it's been modified, e.g. using glGetFloatv()? And so on.)

In general though, you wouldn't use glRotatef() to apply an incremental rotation, as you appear to be trying to do. Rather, you would store the orientation yourself (e.g. in matrix or quaternion form), apply an incremental rotation each update, and then upload the orientation to OpenGL (converting to matrix form first if necessary) for rendering purposes.

The basic idea you have looks right (although in reality, I imagine most fish make some effort to remain upright when swimming, a behavior that your current method won't necessarily exhibit). In order to make it work as you expect though, you'll need to do something like this:
// At initialization:matrix33 orientation = <initial orientation>;// For each update:// Compute the axis-angle rotation as in your current code, then:matrix33 rotation = matrix_from_axis_angle(axis, angle);orientation = rotation * orientation; // Assuming column-vector conventionorientation.orthogonalize(); // To correct for numerical error// Then, build a 4x4 matrix from the 3x3 orientation matrix and the current position:matrix44 transform = matrix_affine_transform(orientation, position);// And upload to OpenGL:glLoadMatrixf(transform.raw_data());
First of all, thanks for the great reply!

Most important stuff first:
Quote:
// At initialization:matrix33 orientation = <initial orientation>;// For each update:// Compute the axis-angle rotation as in your current code, then:matrix33 rotation = matrix_from_axis_angle(axis, angle);orientation = rotation * orientation; // Assuming column-vector conventionorientation.orthogonalize(); // To correct for numerical error// Then, build a 4x4 matrix from the 3x3 orientation matrix and the current position:matrix44 transform = matrix_affine_transform(orientation, position);// And upload to OpenGL:glLoadMatrixf(transform.raw_data());


I want to make sure I understand the concept:
1) Calculate the axis to rotate about and the angle (for the complete rotation?)
2) Build rotation matrix based on first step
3) Apply the rotation to the (old/current) orientation, to get a matrix representing the new orientation
4) Build transformation matrix from new orientation + current position
5) Apply to object

3 questions:
1: Why is it necessary to apply the orientation to the position? (Why) Wouldn't it be enough to just load the orientation matrix?
2: How exactly does the matrix_form_axis_angle look? (column-vector)
3: How exactly does the matrix_affine_transformation look?

Quote:
There's not quite enough information there to tell you why your current code isn't working, mostly because we don't know in what context glRotatef() is being used. (That is, what's the state of the matrix stack when it's invoked? Are you saving the orientation somehow after it's been modified, e.g. using glGetFloatv()? And so on.)


I didn't want to just throw my code in here and go "fix my code", but the stack looks as follows:
glLoadIdentity();//Do view transformation/rotationsglPushMatrix();//Do motion translationglPushMatrix();glRotatef(..);glBegin(...);......glEnd();glPopMatrix();glPopMatrix();


Quote:
The basic idea you have looks right (although in reality, I imagine most fish make some effort to remain upright when swimming, a behavior that your current method won't necessarily exhibit)


Yeah I thought about that, but I was even more clueless as how to achieve that - small steps :)
Quote:1: Why is it necessary to apply the orientation to the position? (Why) Wouldn't it be enough to just load the orientation matrix?
The orientation isn't applied to the position (see my answer to question 3, below).
Quote:2: How exactly does the matrix_form_axis_angle look? (column-vector)
Google 'axis angle matrix', and you should find plenty of examples. As for matrix majorness and notational convention, there are really only two ways the matrix can be arranged (they're transposes of each other). Ideally you would find an example that stated what conventions are being used, but unfortunately there's a lot of tutorials and code examples out there that don't do this. If you're uncertain whether you have it right or not, you can always post your implementation here for people to take a look at.
Quote:3: How exactly does the matrix_affine_transformation look?
All it does (essentially) is create a 4x4 matrix, set it to identity, load the 3x3 matrix into the upper-left 3x3 portion, and load the translation vector into the fourth row or column (assuming you're using OpenGL notational convention, you'd load it into the fourth column).
Quote:
Quote:
The basic idea you have looks right (although in reality, I imagine most fish make some effort to remain upright when swimming, a behavior that your current method won't necessarily exhibit)
Yeah I thought about that, but I was even more clueless as how to achieve that - small steps :)
Actually, the 'staying upright' version can actually be *easier* to implement than the 'arbitrary orientation' version; since all you really need to track is a pair of angles (yaw and pitch), you can dispense with doing the math manually and just use OpenGL's built-in math functions, as you were doing previously.

I'd stick with the 'arbitrary orientation' method though, regardless, since you'll probably get more realistic behavior if you first rotate into alignment with the direction vector, and then apply a 'stabilizing' rotation to keep the fish more or less upright.
Okay, I implemented the new calculation but it scared my fish away :(
(translated out of the scene I guess)

First, I found the axis-angle matrix (Rodrigues' rotation matrix) (http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToMatrix/index.htm)

So basically what happens is that without the rotation, my fish is in the scene moving normally. With the rotation applied however, is gone. I checked the matrix math twice (might still have missed something though). Anyways, here is the fish code and I hope you can spot the problem.


Here is the code for my fish class:
calculateTrajectory is called whenever the fish should swim to a new position
preFrame is called after calTraj and before draw.
#include "h.h"using namespace std;Fish::Fish(TriangleMesh* model) {	this->model = model;	this->trajectorySet = false;	this->avoidState = false;	this->heading = new Vector(0.0, 0.0, 1.0);	this->oldStopPosition = position;	this->orientation = new Matrix33(	1.0, 0.0, 0.0,										0.0, 1.0, 0.0,										0.0, 0.0, 1.0	);	axisAngleToRotation(orientation, new Vector(0.0, 0.0, 1.0), 0);	this->transformation = new Matrix44(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);}void Fish::calculateTrajectory(Vector* newPosition, float speed) {	displacement = subtractVectors(newPosition, position);	travelTime = displacement->length() / speed;	startTime = Clock::getTime();	startPosition = position;	stopPosition = newPosition;	trajectorySet = true;	if (!sameVectors(oldStopPosition, stopPosition)) {		// |v x w | = |v||w|sin(a)		// a = sin-1 ( | v x w | / |v||w| )		turnAxis = crossProductNew(heading, displacement);		double angleRad = asin( turnAxis->length() / (heading->length() * displacement->length()));		double angleDeg =  angleRad * (180 / M_PI);		turnAngle = angleDeg;		heading = displacement;		turnAxis->normalize();		Matrix33* rotation = new Matrix33( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); //Identity		axisAngleToRotation(orientation, turnAxis, turnAngle);		Matrix33* tmp = new Matrix33(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);		matrix33Mul(rotation, orientation, tmp);		orientation = tmp;	//	orientation.ortho();		affineTransformation(transformation, orientation, position);	//	affineTransformation(transformation, orientation, new Vector(0.0, 0.0, 0.0));		cout << "Orientation matrix" << endl;		orientation->print();	}}void Fish::preFrame(double time) {	if (trajectorySet) {		double elapsedTime = time-startTime;		if (elapsedTime > travelTime) {			position = stopPosition;			if (avoidState) {				avoidState = false;			}		} else {			Vector* currentDisplacement = scaleVectorNew(displacement, elapsedTime / travelTime);			position = addVectors(startPosition, currentDisplacement);		}	}}void Fish::draw() {	glPushMatrix();		glColor3f(colorR, colorB, colorG);		// Set heading		if (trajectorySet) {			GLfloat data[4][4] = {	{transformation->a, transformation->e, transformation->i, transformation->m},									{transformation->b, transformation->f, transformation->j, transformation->n},									{transformation->c, transformation->g, transformation->k, transformation->o},									{transformation->d, transformation->h, transformation->l, transformation->p}								};//			glLoadTransposeMatrixf(&data[4][4]);			glLoadMatrixf(&data[4][4]);		}		//Adjust model to fit scene		glScalef(5, 5, 5);		glBegin(GL_TRIANGLES);		for (unsigned int i = 0; i < model->faces.size(); i++) {			/* First Vertex */			glNormal3f(	model->normals[model->faces.vn[0]].x,						model->normals[model->faces.vn[0]].y,						model->normals[model->faces.vn[0]].z			);			glVertex3f(	model->verts[model->faces.v[0]].x,						model->verts[model->faces.v[0]].y,						model->verts[model->faces.v[0]].z			);			/* Second Vertex */			glNormal3f(	model->normals[model->faces.vn[1]].x,						model->normals[model->faces.vn[1]].y,						model->normals[model->faces.vn[1]].z			);			glVertex3f(	model->verts[model->faces.v[1]].x,						model->verts[model->faces.v[1]].y,						model->verts[model->faces.v[1]].z			);			/* Third Vertex */			glNormal3f(	model->normals[model->faces.vn[2]].x,						model->normals[model->faces.vn[2]].y,						model->normals[model->faces.vn[2]].z						);			glVertex3f(	model->verts[model->faces.v[2]].x,						model->verts[model->faces.v[2]].y,						model->verts[model->faces.v[2]].z			);		}		glEnd();	glPopMatrix();}Fish::~Fish() {}


For my matrix calculations:
#include "h.h"void axisAngleToRotation(Matrix33* m, Vector* v, GLfloat a) {	GLfloat s = sin(a) * (180 / M_PI);	GLfloat cplus = cos(a) * (180 / M_PI);	GLfloat cminus = 1 - cplus;	//Rodrigues' rotation matrix	m->a = cplus + (v->x*v->x) * cminus;	m->b = v->x * v->y * cminus + v->z * s;	m->c = v->x * v->z * cminus - v->y * s;	m->d = v->x * v->y * cminus - v->z * s;	m->e = cplus + (v->y * v->y) * cminus;	m->f = v->y * v->z * cminus + v->x * s;	m->g = v->x * v->z * cminus + v->y * s;	m->h = v->y * v->z * cminus - v->x * s;	m->i = cplus +(v->z * v->z) * cminus;}void affineTransformation(Matrix44* m, Matrix33* r, Vector* v) {	m->a = r->a; m->e = r->d; m->i = r->g; m->m = v->x;	m->b = r->b; m->f = r->e; m->j = r->h; m->n = v->y;	m->c = r->c; m->g = r->f; m->k = r->i; m->o = v->z;	m->d = 0;	 m->h = 0;	  m->l = 0;	   m->p = 1;}void matrix33Mul(Matrix33* m1, Matrix33* m2, Matrix33* dest) {	dest->a = m1->a * m2->a + m1->d * m2->b + m1->g * m2->c;	dest->b = m1->b * m2->a + m1->e * m2->b + m1->h * m2->c;	dest->c = m1->c * m2->a + m1->f * m2->b + m1->i * m2->c;	dest->d = m1->a * m2->d + m1->d * m2->e + m1->g * m2->f;	dest->e = m1->b * m2->d + m1->e * m2->e + m1->h * m2->f;	dest->f = m1->c * m2->d + m1->f * m2->e + m1->i * m2->f;	dest->g = m1->a * m2->g + m1->d * m2->h + m1->g * m2->i;	dest->h = m1->b * m2->g + m1->e * m2->h + m1->h * m2->i;	dest->i = m1->c * m2->g + m1->f * m2->h + m1->i * m2->i;}



For the matrix33 class (same for 44):
#include "h.h"using namespace std;Matrix33::Matrix33(	GLfloat a, GLfloat d, GLfloat g,					GLfloat b, GLfloat e, GLfloat h,					GLfloat c, GLfloat f, GLfloat i) {	this->a = a; this->d = d; this->g = g;	this->b = b; this->e = e; this->h = h;	this->c = c; this->f = f; this->i = i;}Matrix33::~Matrix33() {	// TODO Auto-generated destructor stub}void Matrix33::print() {	cout << "| " << a << "  " << d << "  " << g << " |" << endl;	cout << "| " << b << "  " << e << "  " << h << " |" << endl;	cout << "| " << c << "  " << f << "  " << i << " |" << endl;}
I didn't examine all the code carefully, but I did notice this:
axisAngleToRotation(orientation, turnAxis, turnAngle);
Should that be rotation rather than orientation?

Also, based on a quick look over your code, it looks to me like you might be making some basic programming errors with respect to C++. Are you perhaps coming to C++ from a garbage-collected language such as C# or Java? Do you have much familiarity with the C++ memory model?

I realize this is a little off-topic, but if you're leaking memory on a per-frame basis (which it looks like you may be), that's probably something you'll want to fix.
Quote:I didn't examine all the code carefully, but I did notice this:

axisAngleToRotation(orientation, turnAxis, turnAngle);

Should that be rotation rather than orientation?


Of cause it should! Thanks

Quote:
Are you perhaps coming to C++ from a garbage-collected language such as C# or Java? Do you have much familiarity with the C++ memory model?

That obvious ? :) Yes, Java's the one. I'm just as new to C++ as I am to OpenGL, so thanks for pointing that out - and it needs fixing naturally; can't have bad code.
Quote:I didn't examine all the code carefully, but I did notice this:

axisAngleToRotation(orientation, turnAxis, turnAngle);

Should that be rotation rather than orientation?


Of cause it should! Thanks

Quote:
Are you perhaps coming to C++ from a garbage-collected language such as C# or Java? Do you have much familiarity with the C++ memory model?

That obvious ? :) Yes, Java's the one. I'm just as new to C++ as I am to OpenGL, so thanks for pointing that out - and it needs fixing naturally; can't have bad code.

EDIT: Still no go, same result
Quote:Yes, Java's the one. I'm just as new to C++ as I am to OpenGL, so thanks for pointing that out - and it needs fixing naturally; can't have bad code.
Yeah, unfortunately, C++ is laden with pitfalls, and if you're used to different programming paradigms or language semantics, it's easy to fall right into them. (Actually, it's pretty easy to fall into them even if you've been programming in C++ for quite a while.)

The main thing you'll want to keep in mind is that C++ has no language-level garbage collection. What you create with new or new[], you are expected to release (in one way or another) using delete or delete []. Anything that isn't released explicitly remains in memory (in an abstract sense, at least) until the program terminates. If you continually create new objects without ever deleting them, your application will consume more and more memory the longer it runs.

For resources that do need to be allocated dynamically, it's common to automate the release of those resources using the RAII idiom. However, for the type of thing you're doing, the solution is to work with values rather than references (pointers, in this case).
Quote:EDIT: Still no go, same result
I think there's probably too much going on in your code for me to pick out the problem easily. I think it's at least a possibility that some of the pointer and memory mis-handling may be a contributing factor, so I think it might be a good idea to try and clean some of that up before proceeding further.

If you have questions on the C++ side of things, you can always post in one of the other forums, and people will be happy to help.

Also, what development tools are you using? If you have a debugger available, you'll want to become familiar with it, as that's usually the best way to solve problems like this one.
Quote:What you create with new or new[], you are expected to release (in one way or another) using delete or delete [].


That sounds simple enough, but I imagine the trick is to figure out what to release when. Say for example this:

foo(Bar* f) {   Bar tmp = new Bar(..); //temp holder for calculations   //calculations   f = &tmp   return;} 


Will deleting tmp before return result in f pointing to, well it would point to the same address, but will new stuff be allocated to that address, giving me wired results?

Anyway, I've picked up a C++ book, so I will read up on and rewrite the code from scratch step by step (my guess is that cleaning up, inserting deletes in one step will go BOOM and take a lot longer to get running again).

Quote:Also, what development tools are you using?

Eclipse and I've spend plenty time in the debugger :)

Well, again, thanks a lot for taking the time to help with my rookie mistakes!

-Niux

This topic is closed to new replies.

Advertisement