Rotate a Node, face another node

A forum to store posts deemed exceptionally wise and useful
Gorgon Zola
Posts: 118
Joined: Thu Sep 18, 2003 10:05 pm
Location: switzerland

Rotate a Node, face another node

Post by Gorgon Zola »

Rotate a node to face another node, LookAt
Walk forward
Walk sideways
==================================

Hi

I've seen in the forum quiet a few questions and answers about
turning scenenodes to walk forward or to face some other scenenode.

I have here some code example that should clarify things a bit for newbies.
This is certainly not the only way to do things, so feel free to post more ways
and/or corrections/enhancements that deal with this sort of problems.
I think this will help every one to find things faster.

The code should work with irrlicht 0.4.2
I assume you have something like this in your code:

Code: Select all

// the initial up direction of the hero
vector3df up(0,1,0);
// the up direction during gameplay
vector3df curup(up);
// the initial forward direction
vector3df vtHeroInitDir(0,0,1); 
// the forward direction during gameplay
vector3df vtHeroCurrentDir(vtHeroInitDir); 

// the hero of the game
ISceneNode* myHero;

// the bad guy
ISceneNode* myMonster;
Now, every time you want the hero to <b>turn towards<b> the monster,
you just call the following function
(note that this function can also be modified to turn to a point!):

Code: Select all

void faceTarget(irr::scene::ISceneNode& hero, irr::scene::ISceneNode& target){
	using namespace irr;
	using namespace core;
	vector3df dir=vtHeroInitDir;
	vector3df targetdir=target.getPosition()-hero.getPosition();
	
	// now build the rotation matrix 
	vector3df z(targetdir-dir); 
	z.normalize(); 
	vector3df x( up.crossProduct(z) );
	x.normalize(); 
	vector3df y( z.crossProduct(x) );
	y.normalize();
	
	transform(0,0) = x.X;
	transform(0,1) = x.Y;
	transform(0,2) = x.Z;
	transform(0,3) = 0;
	
	transform(1,0) = y.X;
	transform(1,1) = y.Y;
	transform(1,2) = y.Z;
	transform(1,3) = 0;
	
	transform(2,0) = z.X;
	transform(2,1) = z.Y;
	transform(2,2) = z.Z;
	transform(2,3) = 0;
	
	transform(3,0) = 0;
	transform(3,1) = 0;
	transform(3,2) = 0;
	transform(3,3) = 1;
	
	irr::core::vector3df rot=MatrixToEulerAngles(transform);
	transform.transformVect(vtHeroInitDir,vtHeroCurrentDir);
	transform.transformVect(up,curup);
	hero.setRotation(rot);
	vtHeroCurrentDir.normalize();
	curup.normalize();
}

For matrix to eulerangles conversion I've taken the function that Maureen
[url]http://irrlicht.sourceforge.net/phpBB2/ ... php?t=1288[\url]
has posted. The aproach for the 'faceTarget' function mentioned there doesn't work though.
Also, You have to keep in mind that this function rotates the hero completely, that is,
it doesn't only turn the node in the xz-plane,
which is what you normaly would want in a game where your object's can't fly.

<b>Walking forward</b> is really easy if you update the vector 'vtHeroCurrentDir' every time the direction
changes. just call 'goForward(myHero, vtHeroCurrentDir , MillisSinceLastFrame)'

Code: Select all

void goForward(ISceneNode& hero, vector3df& direction, long MillisSinceLastFrame){
	// scale the direction to match different framerates
	vector3df newPos=hero.getPosition()+ (direction * MillisSinceLastFrame/1000.0);
	hero->setPosition(newPos);
}
<b>Going sideways</b> is also a minor problem.

Code: Select all

void goSide(ISceneNode& hero, vector3df& forward, vector3df& updir, bool left){
	vector3df s=forward.crossProduct(updir);
	s.normalize();
	vector3df newPos=hero.getPosition() + ( s * (left?1:-1) );
}
cheers
Gorgon Zola
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

The functionality for faceTarget() is now part of the engine (at least since version 0.6). It's all in core::matrix4.

To get the rotation matrix use:

void matrix4::buildCameraLookAtMatrixLH(position, target, upVector);

(there's also buildCameraLookAtMatrixRH() (right handed))

To achieve the same as in the example above, you could use it somehow like this:

Code: Select all

...
translation.buildCameraLookAtMatrixLH(vector3df(0,0,0), targetdir, up);
...
(so the position doesn't get affected)

The MatrixToEulerAngles() is also there:

vector3df matrix4::getRotationDegrees();
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

To make Gorgons helpful faceTarget code work with Irrlicht 0.6 I had to mirror the matrix assignments. (Maybe this is because of matrix4.h has changed in some way? It seems to have some inconsistensies anyway, but I'm not sure yet, still investigating).

Also the targetdir-dir is actually a bug. You don't need to substract dir from targetdir. It adds a position offset which makes it behave not quite correctly. dir is not needed in the calculation at all, only up. (you still can update dir at the end for further use.)

Here is the updated faceTarget function:

Code: Select all

void faceTarget(irr::scene::ISceneNode& hero, irr::scene::ISceneNode& target){ 
   using namespace irr; 
   using namespace core; 
   vector3df dir=vtHeroInitDir; 
   vector3df targetdir=target.getPosition()-hero.getPosition(); 
    
   // now build the rotation matrix 
   vector3df z(targetdir); 
   z.normalize(); 
   vector3df x( up.crossProduct(z) ); 
   x.normalize(); 
   vector3df y( z.crossProduct(x) ); 
   y.normalize(); 
    
   transform(0,0) = x.X; 
   transform(0,1) = y.X; 
   transform(0,2) = z.X; 
   transform(0,3) = 0; 
    
   transform(1,0) = x.Y; 
   transform(1,1) = y.Y; 
   transform(1,2) = z.Y; 
   transform(1,3) = 0; 
    
   transform(2,0) = x.Z; 
   transform(2,1) = y.Z; 
   transform(2,2) = z.Z; 
   transform(2,3) = 0; 
    
   transform(3,0) = 0; 
   transform(3,1) = 0; 
   transform(3,2) = 0; 
   transform(3,3) = 1; 
    
   irr::core::vector3df rot=MatrixToEulerAngles(transform); 
   transform.transformVect(vtHeroInitDir,vtHeroCurrentDir); 
   transform.transformVect(up,curup); 
   hero.setRotation(rot); 
   vtHeroCurrentDir.normalize(); 
   curup.normalize(); 
} 
hope it's correct now!

cheese, :)
jox
jox
Bug Slayer
Posts: 726
Joined: Thu Apr 22, 2004 6:55 pm
Location: Germany

Post by jox »

Oh, and the matrix4::getRotationDegrees(); (matrix to eulerangles) has a bug in Irrlicht v0.6 which I fixed:

http://irrlicht.sourceforge.net/phpBB2/ ... php?t=2234

(btw. the code from Maureen that Gorgon were refering to is not working properly neither)
Gorgon Zola
Posts: 118
Joined: Thu Sep 18, 2003 10:05 pm
Location: switzerland

Post by Gorgon Zola »

:) Cool thanks jox
now it's helpfull and right :D
smartwhiz
Posts: 120
Joined: Wed Jan 14, 2004 6:03 pm
Location: India

Post by smartwhiz »

ok.... wht if i am designing a RPG type game where the target position if found and i want my model to look at that point when moving towards it...? plz help!!!
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Put it in the drawing loop. Best way I can think of is to have the functionality always there and to have some kind of flag to say that the character is staring at something. Then store the look at target in an entity class ( you will have to create one sooner or later ).

I think I need to get a little more aquanted with matricies, I don't really know much about them. One thing: When a node rotates, it appears to rotate on it's origin rather than the centre of the model. Is this correct?
Gorgon Zola
Posts: 118
Joined: Thu Sep 18, 2003 10:05 pm
Location: switzerland

Post by Gorgon Zola »

@smartwhiz: I agree with Tyn. exept that my flag is a pointer to the node i want to follow if its valid(!=NULL) a scenenode animator turns the node to face that node.

@Tyn :
Yes, rotation is alway around the local origin and not the center of the model
I have made all my models such that the center of the model will be the nodes origin. This way I don't have problems with physic libraries etc. that sometimes tend to define the center of mass to be the origin (at least ODE bbehaves like that :wink: )
Tyn
Posts: 932
Joined: Thu Nov 20, 2003 7:53 pm
Location: England
Contact:

Post by Tyn »

Hmm, I prefer to model in the +ve. Makes things simple for me when I position things, something I have always done. Pretty sure this is what most games do from viewing model files, I'll have to research it a little. I wonder if there is a way to offset the rotation?

I also wonder if the FlyCircle animator could be of use with a 0 radius?
Gorgon Zola
Posts: 118
Joined: Thu Sep 18, 2003 10:05 pm
Location: switzerland

Post by Gorgon Zola »

You could always use a dummy scenenode (I thinks there's one already availabel through the scenemanager) as parent which you use as rotation offset. I doubt though that this will be easier .
smartwhiz
Posts: 120
Joined: Wed Jan 14, 2004 6:03 pm
Location: India

Post by smartwhiz »

think it in this way i am having two point vector3df's ok..........
one -> the curr position of the model
2nd->the target position....
now how do i turn the model at the current position to point at target point
BableOff

Post by BableOff »

I'm having a problem with this code.

The target(my goblin charector with attached camera) and my node are looking at each other, if I straft the target to the right the node should turn left and still be looking at me.

In this instance if I straft right the node turns right also and does not face me.

example:

Image
Image
Image

Here is my code:

Code: Select all

void faceTarget(irr::core::vector3df targetPos) {

  irr::core::vector3df up(0,1,0);
  irr::core::vector3df curup(up);
  irr::core::vector3df forward(0,0,1);
  irr::core::vector3df curforward(forward);

   vector3df dir=forward;
   vector3df targetdir = targetPos - mynode->getPosition();

   // now build the rotation matrix
   vector3df z(targetdir);
   z.normalize();
   vector3df x( up.crossProduct(z) );
   x.normalize();
   vector3df y( z.crossProduct(x) );
   y.normalize();

   irr::core::matrix4 transform;
   transform(0,0) = x.X;
   transform(0,1) = y.X;
   transform(0,2) = z.X;
   transform(0,3) = 0;

   transform(1,0) = x.Y;
   transform(1,1) = y.Y;
   transform(1,2) = z.Y;
   transform(1,3) = 0;

   transform(2,0) = x.Z;
   transform(2,1) = y.Z;
   transform(2,2) = z.Z;
   transform(2,3) = 0;

   transform(3,0) = 0;
   transform(3,1) = 0;
   transform(3,2) = 0;
   transform(3,3) = 1;

   irr::core::vector3df rot = myMatrixToEulerAngles(transform);
   transform.transformVect(forward,curforward);
   transform.transformVect(up,curup);
   mynode->setRotation(rot);
   curforward.normalize();
   curup.normalize();
}

irr::core::vector3df myMatrixToEulerAngles(irr::core::matrix4& mat)
{
  irr::f32 angle_y,D,C,angle_x,angle_z,frx,fry;
  angle_y = D = -asin( mat(0,2) );
  C           =  cos( angle_y );
  angle_y    *= (f32)irr::core::GRAD_PI;
  if ( fabs( C ) > 0.005 )             /* Gimball lock? */
  {
    frx      =  mat(2,2) / C;
    fry      = -mat(1,2)  / C;
    angle_x  = atan2( fry, frx ) * (f32)irr::core::GRAD_PI;
    frx      =  mat(0,0) / C;
    fry      = -mat(0,1) / C;
    angle_z  = atan2( fry, frx ) * (f32)irr::core::GRAD_PI;
  }
  else                                 /* Gimball lock has occurred */
  {
    angle_x  = 0;
    frx      = mat(1,1);
    fry      = mat(1,0);
    angle_z  = atan2( fry, frx ) * (f32)irr::core::GRAD_PI;
  }
  angle_x = angle_x<0 ? angle_x+360.0f : angle_x;
  angle_y = angle_y<0 ? angle_y+360.0f : angle_y;
  angle_z = angle_z<0 ? angle_z+360.0f : angle_z;
  return irr::core::vector3df(angle_x,angle_y,angle_z);
}
And I execute this from the rendering loop as such:

Code: Select all

  irr::core::vector3df target = myself->getPosition();
  faceTarget(target);
Any ideas why he is turing the wrong direction?
BableOff

Post by BableOff »

My solution
mynode is global

Code: Select all

void faceTarget(irr::core::vector3df targetPos) {

  core::vector3df nodePos = targetPos - mynode->getPosition();
  float degree = atan(nodePos.Z/nodePos.X) * (180.0f / irr::core::PI);
  if((targetPos.X - mynode->getPosition().X) > 0) {
   degree = 90 - degree;
  } else {
    degree = -90 - degree;
  }
  degree -= 90;
  mynode->setRotation(vector3df(0,(float) degree, 0));
}
BableOff

Post by BableOff »

Updated solution.

Fixing a bug when targetPos.X == myNode->getPosition().X the rotation was off 180 degrees;

I have not had this issue with the Z axis, only the X.

myNode is global

myRotation is global but you can use "float degree" insted of "myRotation.Y" and set myNode->setRotation(vector3df(0,(float) degree, 0));

Code: Select all

void faceTarget(irr::core::vector3df targetPos) {
  core::vector3df nodePos = targetPos - myNode->getPosition();
  myRotation.Y = atan(nodePos.Z/nodePos.X) * (180.0f / irr::core::PI);
  if((targetPos.X - myNode->getPosition().X) > 0) {
    myRotation.Y = 90 - myRotation.Y;
  } else if((targetPos.X - myNode->getPosition().X) < 0) {
    myRotation.Y = -90 - myRotation.Y;
  }
  myRotation.Y -= 90;
  myNode->setRotation(myRotation);
}
Nick_Japan
Posts: 98
Joined: Mon Dec 13, 2004 11:47 am
Location: Japan

Post by Nick_Japan »

I second BableOff's solution - works for me, only I found that I didn't need the last myRotation.Y -= 90; commenting that out gives me the correct angle.
Post Reply