Noob needs help with camera rotation :)

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
xirtamatrix
Posts: 219
Joined: Fri Feb 19, 2010 4:03 pm
Location: Estonia

Noob needs help with camera rotation :)

Post by xirtamatrix »

Hi folks,

Before jumping into writing my own camera class, I figured it would be good idea to fully understand how cameras work, so I'm writing a short simple program to move/rotate a static camera as follows:

Code: Select all


int main() 
{    
    // create device 
   MyEventReceiver receiver; 

   IrrlichtDevice* device = createDevice(EDT_OPENGL, 
         dimension2d<u32>(1024, 768), 16, false, false, false, &receiver); 

   if (device == 0) 
      return 1; // could not create selected driver. 

   IVideoDriver* driver = device->getVideoDriver(); 
   ISceneManager* smgr = device->getSceneManager(); 
    
    // add background

	device->getFileSystem()->addZipFileArchive("map-20kdm2.pk3");
    scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
    scene::ISceneNode* room = 0;

    if (mesh)
            room = smgr->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024);
    if (room)
            room->setPosition(core::vector3df(-1300,-144,-1249));


    // add a camera 
	ICameraSceneNode *camera = smgr->addCameraSceneNode(); 
	camera->setPosition(vector3df(0,0,0)); 
	camera->bindTargetAndRotation(true); 
    
   // hide mouse 
    device->getCursorControl()->setVisible(false); 
    
   int lastFPS = -1; 

   // In order to do framerate independent movement, we have to know 
   // how long it was since the last frame 
   u32 then = device->getTimer()->getTime(); 

   // This is the movemen speed in units per second. 
   const f32 MOVEMENT_SPEED = 50.0f;  // how fast camera moves
   const f32 ROTATION_SPEED	= 50.0f;	// how fast camera rotates


   while(device->run()) 
   { 

   core::vector3df camPosition = camera->getPosition(); 

   core::vector3df camRotation = camera->getRotation(); 


      // Work out a frame delta time. 
      const u32 now = device->getTimer()->getTime(); 
      const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds 
      then = now; 

      /* Check if keys W, S, A or D are being held down, and move the 
      camera around respectively. */ 
 
      if(receiver.IsKeyDown(KEY_KEY_W)) 
         camPosition.Z += MOVEMENT_SPEED * frameDeltaTime; 
      else if(receiver.IsKeyDown(KEY_KEY_S)) 
         camPosition.Z -= MOVEMENT_SPEED * frameDeltaTime; 

      if(receiver.IsKeyDown(KEY_KEY_A)) 
         camRotation.Y -= ROTATION_SPEED * frameDeltaTime;
      else if(receiver.IsKeyDown(KEY_KEY_D)) 
         camRotation.Y += ROTATION_SPEED * frameDeltaTime; 


    camera->setRotation(camRotation);
    camera->setPosition(camPosition); 

        
      driver->beginScene(true, true, SColor(255,113,113,133)); 

      smgr->drawAll(); // draw the 3d scene 
      device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo) 

      driver->endScene(); 

      int fps = driver->getFPS(); 

      if (lastFPS != fps) 
      { 
         //stringw tmp(L"Movement Example - Irrlicht Engine ["); 
         //tmp += driver->getName(); 
         //tmp += L"] fps: "; 
         //tmp += fps; 

	wchar_t tmp[1024];
	swprintf(tmp, 1024, L"FP Camera Example (%s)(fps:%d)(Cam:%2.2f %2.2f %2.2f) (Tar:%2.2f %2.2f %2.2f) )", 
			driver->getName(), fps,camera->getAbsolutePosition().X,camera->getAbsolutePosition().Y, camera->getAbsolutePosition().Z, camera->getTarget().X, camera->getTarget().Y, camera->getTarget().Z);
		device->setWindowCaption(tmp);


         //device->setWindowCaption(tmp.c_str()); 
         lastFPS = fps; 
      } 
   } 

   /* 
   In the end, delete the Irrlicht device. 
   */ 
   device->drop(); 
    
   return 0; 
} 

Basically, I'm just increasing and decreasing the value of camPosition along the Z axis when moving forward or backward:

Code: Select all

      if(receiver.IsKeyDown(KEY_KEY_W)) 
         camPosition.Z += MOVEMENT_SPEED * frameDeltaTime; 
      else if(receiver.IsKeyDown(KEY_KEY_S)) 
         camPosition.Z -= MOVEMENT_SPEED * frameDeltaTime; 

This works, though I'm not sure igf its the right way, please correct me if its not.

And I rotate the camera in same way, by simply increasing/decreasing the value of camRotate along Y axis :

Code: Select all

      if(receiver.IsKeyDown(KEY_KEY_A)) 
         camRotation.Y -= ROTATION_SPEED * frameDeltaTime;
      else if(receiver.IsKeyDown(KEY_KEY_D)) 
         camRotation.Y += ROTATION_SPEED * frameDeltaTime; 
The camera does rotate with A and D keys, but if you try to move forward/backward again after rotating everything gets mixed-up.

I'm pretty sure this is not the right way to rotate since I'm not taking any drgrees/radians into account at all.

Could someone please provide me a simple formula to use here, which would achieve rotation of the camera?

Many thanks in advance!

cheers!
to live, is natural; to die, is not!
zillion42
Posts: 324
Joined: Wed Aug 29, 2007 12:32 am
Location: Hamburg, Germany

Post by zillion42 »

you have to un - bind your camera from its target to be able to directly rotate it.

Code: Select all

		//! Binds the camera scene node's rotation to its target position and vice vera, or unbinds them.
		/** When bound, calling setRotation() will update the camera's
		target position to be along its +Z axis, and likewise calling
		setTarget() will update its rotation so that its +Z axis will
		point at the target point. FPS camera use this binding by
		default; other cameras do not.
		\param bound True to bind the camera's scene node rotation
		and targetting, false to unbind them.
		@see getTargetAndRotationBinding() */
		virtual void bindTargetAndRotation(bool bound) =0;
randomMesh
Posts: 1186
Joined: Fri Dec 29, 2006 12:04 am

Post by randomMesh »

randomMesh wrote:You can have a look at the implementation of ISceneNodeAnimatorCameraFPS (CSceneNodeAnimatorCameraFPS.h and CSceneNodeAnimatorCameraFPS.cpp) to get an idea of how it is done.
https://irrlicht.svn.sourceforge.net/svnroot/irrlicht/trunk/source/Irrlicht/CSceneNodeAnimatorCameraFPS.h
https://irrlicht.svn.sourceforge.net/svnroot/irrlicht/trunk/source/Irrlicht/CSceneNodeAnimatorCameraFPS.cpp

The important stuff is in void CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode* node, u32 timeMs).
"Whoops..."
xirtamatrix
Posts: 219
Joined: Fri Feb 19, 2010 4:03 pm
Location: Estonia

Post by xirtamatrix »

zillion42 wrote:you have to un - bind your camera from its target to be able to directly rotate it.

Code: Select all

		//! Binds the camera scene node's rotation to its target position and vice vera, or unbinds them.
		/** When bound, calling setRotation() will update the camera's
		target position to be along its +Z axis, and likewise calling
		setTarget() will update its rotation so that its +Z axis will
		point at the target point. FPS camera use this binding by
		default; other cameras do not.
		\param bound True to bind the camera's scene node rotation
		and targetting, false to unbind them.
		@see getTargetAndRotationBinding() */
		virtual void bindTargetAndRotation(bool bound) =0;
Thanks Zillion42, yes I unbind it now, but still, I need the formulas to do the rotation :)
to live, is natural; to die, is not!
xirtamatrix
Posts: 219
Joined: Fri Feb 19, 2010 4:03 pm
Location: Estonia

Post by xirtamatrix »

randomMesh wrote:You can have a look at the implementation of ISceneNodeAnimatorCameraFPS (CSceneNodeAnimatorCameraFPS.h and CSceneNodeAnimatorCameraFPS.cpp) to get an idea of how it is done.

Thanks randomMesh, but FPS camera does not rotate on left/right key, instead it strafe to left and right.

It does rotate with mouse cursor though, but in that case a lot of other factors come into play like mouse cursor position etc. and I'm finding it hard to seperate the needed stuff from the unneeded one. :?
to live, is natural; to die, is not!
blAaarg
Posts: 94
Joined: Tue Mar 02, 2010 9:11 pm
Location: SoCal

Post by blAaarg »

randomMesh is right in his hint that you should not just look at scene node animators but also inherit from ISceneNodeAnimator and put the functionality in the animateNode() method. And you're right, that code does a lot of confusing things if you're not already familiar with the process. I can't claim to be an expert myself. But, if it does help to go back to your original code, here's what I notice:

When you setPosition() on any node (including cameras) you're not actually changing its local position, but rather, the position relative to its parent. In this case, since your camera doesn't have a parent, you're essentially setting it's global position, not its "forward" position. In other words, changing its Z coordinate will only move it towards that wall it was first facing when the program started--even though your camera might now be facing a different direction.

In order to move the camera towards the direction it's currently facing, you'll need a position vector that describes where the camera is pointed at, and that can be gotten from its target. Then you need to subtract the camera's (absolute) position from the target's (position) vector. Now you have a direction vector (posVector -posVector=dirVector). That vector can now be normalized and scaled for frame-rate independent speed (as you've done, already).

NOW, you can add that direction vector back to the camera position vector and also to the target's position vector (both) to get the new position vectors for each of them. (posVector + dirVector=newPosVector)

the code to explain/illustrate might look something like this:

Code: Select all

	// Check if keys W, S, A or D are being held down, and move the camera around respectively.

// *************** new stuff:

	// get the 'position' of the thing that the camera (node) is pointed at
	vector3df camTarget = camera->getTarget();

	// figure out the 'direction' vector that describes the relative position of the camera to it's target:
	vector3df camDirection = camTarget - camPosition;

	// scale the direction vector for frame-rate independent speed:
	vector3df camMovement = camDirection.normalize();
	camMovement = camMovement * MOVEMENT_SPEED * frameDeltaTime;

// **************** changes:
 
      if(receiver.IsKeyDown(KEY_KEY_W))
        // camPosition.Z += MOVEMENT_SPEED * frameDeltaTime;
		{
			camPosition += camMovement;
			camera->setTarget(camTarget + camMovement);
		}
      else if(receiver.IsKeyDown(KEY_KEY_S))
        // camPosition.Z -= MOVEMENT_SPEED * frameDeltaTime;
		{
			camPosition -= camMovement;
			camera->setTarget(camTarget - camMovement);
		};

// **************** end of changes.
It's kind of like holding a carrot out in front of a horse to keep it moving forward. By getting your camera to move towards its target, but also moving its target an equal distance in the same direction, you make sure the horse(camera) never quite catches its target(carrot). It's kind of mean but computers are supposed to work for us!:twisted: :lol:

Also I set bindTargetAndRotation() back to true. But I have to admit when my little laptop gets hot (as it quickly does running Irrlicht) it "hiccups" or something and starts hanging and missing messages probably. Then it starts behaving strangely like your code does. :x :? The FPS camera code seems more stable and is better at handling that little annoyance. It uses matrices as well and I don't really know if that's a factor, but hopefully that helps show you something of the 'formula' for this so when you look through the FPS animator some more things will make since. :)
"Computers don't make mistakes! What they do they do on purpose!!"

-Dale Gribble
xirtamatrix
Posts: 219
Joined: Fri Feb 19, 2010 4:03 pm
Location: Estonia

Post by xirtamatrix »

@blAaarg dude, you have a gift of explaining :) I particularly enjoyed that horse and carrot example (do horses eat carrots though?.. humn...) A bundle of thanks... no.. actually... a huge, gigantic, collosal bundle of thanks! :) (now figure out where you gonna put it :wink: )

ok, so.. using your tips, I managed to do the forward/backward movement correctly.

Code: Select all

      if(receiver.IsKeyDown(KEY_UP)) 
	  {
			camPosition += camMovement; 
			camTarget += camMovement;
			  
			camera->setPosition(camPosition);
			camera->setTarget(camTarget); 
	  } 
      else if(receiver.IsKeyDown(KEY_DOWN)) 
	  {
			camPosition -= camMovement; 
			camTarget -= camMovement; 
					
			camera->setPosition(camPosition);
			camera->setTarget(camTarget); 
	  }
Next step was to implement rotation of the camera, which I attempted to do as follows:

Code: Select all

      if(receiver.IsKeyDown(KEY_LEFT))
	  {
			camRotation.Y -= ROTATION_SPEED * frameDeltaTime;
			camera->setRotation(camRotation);

	  }  
      else if(receiver.IsKeyDown(KEY_RIGHT)) 
	{
			camRotation.Y += ROTATION_SPEED * frameDeltaTime;
			camera->setRotation(camRotation);
	  }

and it actually worked ( keeping camera->bindTargetAndRotation(true);
)


So, now I could move forward/backward as well as rotate. But the problem was, if I'm moving, I could'nt rotate and vice versa. I wanted to be able to rotate while moving, so I could just choose which way to go using only 4 arrow keys and nothing else.

So I added a little trick by defining two boolean variables inside the game loop :

Code: Select all

	bool moving = 0;
	bool rotating = 0;

and then modifying the above code like this:

Code: Select all

      if(receiver.IsKeyDown(KEY_LEFT))
	  {
		  if(!moving){
			camRotation.Y -= ROTATION_SPEED * frameDeltaTime;
			rotating= true;
			camera->setRotation(camRotation);

		  }
	  }  
      else if(receiver.IsKeyDown(KEY_RIGHT)) 
			{
				if(!moving){
					camRotation.Y += ROTATION_SPEED * frameDeltaTime;
					rotating= true;
					camera->setRotation(camRotation);
				}
	  }

      if(receiver.IsKeyDown(KEY_UP)) 
	  {
		  if(!rotating){
			  camPosition += camMovement; 
			  camTarget += camMovement;
			  moving = true;
			  camera->setPosition(camPosition);
			  camera->setTarget(camTarget); 
		  }
	  } 
      else if(receiver.IsKeyDown(KEY_DOWN)) 
			{
				if(!rotating){
					camPosition -= camMovement; 
					camTarget -= camMovement; 
					moving = true;
					camera->setPosition(camPosition);
					camera->setTarget(camTarget); 
				}
	  }
This has the net effect that while I am moving, I can rotate left or right by a single press of of a key and as soon as I unpress the key I start moving again. :D This is probably not the right way of achieveing this, so if someone can advise a better way I'd be grateful.

Notice though that I have flipped the code-blocks of checking LEFT/ RIGHT with UP/DOWN. If you check the UP/DOWN first, this would have the effect that you can move while rotating, but in this way, I can rotate while moving :)

I hae also added collision detection so its possible to actually move around the map without going through the walls.

There is however a wierd problem with this. If you aremoving downwards, i.e. if for example you are moving down the stairs and you try to rotate at the same time, boom, the camera falls on its face :) Somehow, it seems if the camera position is changing vertically and you try to rotate, it's target changes towards the direction it was falling. Can't figure out why is that or how to correct it. Any ideas are welcome.

Here's the completye code. If you run it then go up the stairs and while comming down the stairs try to rotate, you'd see what I mean. (hehehe, while writing this, I realized that if you were to try doing that in real life, i.e. try to "rotate" while climbing down the stairs, you'd most probably end up on your face too, so the camera is just immitaing real-life :D ROFL)

Code: Select all

#include <irrlicht.h> 
#include <iostream> 

using namespace irr; 

using namespace core; 
using namespace scene; 
using namespace video; 
using namespace io; 
using namespace gui; 

#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif

float cam_rotate=0;
float node_rotate=0;

// event reciever 
class MyEventReceiver : public IEventReceiver 
{ 
public: 
   // This is the one method that we have to implement 
   virtual bool OnEvent(const SEvent& event) 
   { 
      // Remember whether each key is down or up 
      if (event.EventType == irr::EET_KEY_INPUT_EVENT) 
         KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown; 

      return false; 
   } 

   // This is used to check whether a key is being held down 
   virtual bool IsKeyDown(EKEY_CODE keyCode) const 
   { 
      return KeyIsDown[keyCode]; 
   } 
    
   MyEventReceiver() 
   { 
      for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i) 
         KeyIsDown[i] = false; 
   } 

private: 
   // We use this array to store the current state of each key 
   bool KeyIsDown[KEY_KEY_CODES_COUNT]; 
}; 


int main() 
{    
    // create device 
   MyEventReceiver receiver; 

   IrrlichtDevice* device = createDevice(EDT_OPENGL, 
         dimension2d<u32>(1024, 768), 16, false, false, false, &receiver); 

   if (device == 0) 
      return 1; // could not create selected driver. 

   IVideoDriver* driver = device->getVideoDriver(); 
   ISceneManager* smgr = device->getSceneManager(); 

	//Make the room
	device->getFileSystem()->addZipFileArchive("map-20kdm2.pk3");
	scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
	scene::ISceneNode* room = smgr->addOctreeSceneNode(mesh->getMesh(0));
	room->setPosition(core::vector3df(-1300,-144,-1249));
	
	//Set triangle selector
	scene::ITriangleSelector* selector = 0;
	selector = smgr->createOctreeTriangleSelector(mesh->getMesh(0), room, 128);
	room->setTriangleSelector(selector);
	selector->drop();

    //Set up camera 
	ICameraSceneNode *camera = smgr->addCameraSceneNode(); 
	camera->setPosition(vector3df(0,10,0)); 
	camera->bindTargetAndRotation(true); 

	
	//Set collision for camera
		scene::ISceneNodeAnimator *collision = smgr->createCollisionResponseAnimator(
		selector,camera,core::vector3df(20,60,20),
		core::vector3df(0,-2,0),
		core::vector3df(0,0,0), 
		0.0005f);
	camera->addAnimator(collision);

	collision->drop();

   int lastFPS = -1; 

   // In order to do framerate independent movement, we have to know 
   // how long it was since the last frame 
   u32 then = device->getTimer()->getTime(); 

   // This is the movemen speed in units per second. 
   const f32 MOVEMENT_SPEED = 50.0f; 
   const f32 ROTATION_SPEED	= 30.0f;	// how fast camera rotates


   while(device->run()) 
   {

	bool moving = 0;
	bool rotating = 0;

    // Work out a frame delta time. 
    const u32 now = device->getTimer()->getTime(); 
    const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds 
    then = now; 

	core::vector3df camPosition = camera->getPosition(); 
	core::vector3df camRotation = camera->getRotation(); 
	// get the 'position' of the thing that the camera (node) is pointed at 
	core::vector3df camTarget = camera->getTarget(); 

	// figure out the 'direction' vector that describes the relative position of the camera to it's target: 
	core::vector3df camDirection = camTarget - camPosition; 

	// scale the direction vector for frame-rate independent speed: 
	core::vector3df camMovement = camDirection.normalize(); 
	camMovement = camMovement * MOVEMENT_SPEED * frameDeltaTime; 

      /* Check if keys UP, DOWN, LEFT or RIGHT are being held down, and move the 
      camre around respectively. */ 

      if(receiver.IsKeyDown(KEY_LEFT))
	  {
		  if(!moving){
			camRotation.Y -= ROTATION_SPEED * frameDeltaTime;
			rotating= true;
			camera->setRotation(camRotation);

		  }
	  }  
      else if(receiver.IsKeyDown(KEY_RIGHT)) 
			{
				if(!moving){
					camRotation.Y += ROTATION_SPEED * frameDeltaTime;
					rotating= true;
					camera->setRotation(camRotation);
				}
	  }

      if(receiver.IsKeyDown(KEY_UP)) 
	  {
		  if(!rotating){
			  camPosition += camMovement; 
			  camTarget += camMovement;
			  moving = true;
			  camera->setPosition(camPosition);
			  camera->setTarget(camTarget); 
		  }
	  } 
      else if(receiver.IsKeyDown(KEY_DOWN)) 
			{
				if(!rotating){
					camPosition -= camMovement; 
					camTarget -= camMovement; 
					moving = true;
					camera->setPosition(camPosition);
					camera->setTarget(camTarget); 
				}
	  }
   

      driver->beginScene(true, true, SColor(255,113,113,133)); 

      smgr->drawAll(); // draw the 3d scene 
      device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo) 

      driver->endScene(); 

      int fps = driver->getFPS(); 

      if (lastFPS != fps) 
      { 
		wchar_t tmp[1024];
		swprintf(tmp, 1024, L"FP Camera Example (%s)(fps:%d)(Cam:%2.2f %2.2f %2.2f) (Tar:%f %f %f)",driver->getName(), fps,camera->getPosition().X,camera->getPosition().Y, camera->getPosition().Z, camera->getTarget().X, camera->getTarget().Y, camera->getTarget().Z);
		device->setWindowCaption(tmp);


         //device->setWindowCaption(tmp.c_str()); 
         lastFPS = fps; 
	  } 
   }// end While

   /* 
   In the end, delete the Irrlicht device. 
   */ 
   device->drop(); 
    
   return 0; 
} 


Honestly, such a simple kind of camera node should be available in Irrlicht by default, because you dont need tens of keys or a combination of keys and mouse to manouver, just four arrow keys provide all the manouvering, plain and simple!


cheers!
to live, is natural; to die, is not!
lazerblade
Posts: 194
Joined: Thu Mar 18, 2010 3:31 am
Contact:

Post by lazerblade »

I've used you're example to create a simple camera class that only translates locally along Z.
But after I translate along -Z a little bit, the rotation locks up and the camera snaps to face straight up.
LazerBlade

When your mind is racing, make sure it's not racing in a circle.

3d game engine: http://sites.google.com/site/lazerbladegames/home/ray3d
lazerBlade blog: http://lazerbladegames.blogspot.com/
telmopereira
Posts: 13
Joined: Thu Apr 01, 2010 2:45 pm

Post by telmopereira »

Lazerblade i also try out the code of xirtamatrix and happens exactly the same with me. Do you already figure out a solution for that problem ?

Thanks,

Telmo
pilesofspam
Posts: 62
Joined: Mon May 11, 2009 4:31 am

Post by pilesofspam »

Hope I'm not too late to the party to help. I ran across the same problem and naturally found this thread. I wanted to be able to rotate and move at the same time with joystick control, so I did the forward backward the same way, but then translated the camera target left and right by the rotation factor with respect to the camera direction for rotation. Here's the code that matters:

Code: Select all

 vector3df camTarget;
 vector3df camDirection;
 vector3df camMovement;
 vector3df camshift;
and

Code: Select all

/*******************8
camera stuff
*********************/

        camPosition = Camera->getPosition();

        camRotation = Camera->getRotation();
        // First, find out where our camera is pointed
        camTarget = Camera->getTarget();
        camDirection = camTarget - camPosition;
        camMovement = camDirection.normalize();
        camMovement = camMovement * MOV_SPEED * frameDeltaTime;



        if(receiver.IsKeyDown(KEY_KEY_W))
        {
            camPosition += camMovement;
            camTarget += camMovement;
            Camera->setPosition(camPosition);
            Camera->setTarget(camTarget);
        }
        else if(receiver.IsKeyDown(KEY_KEY_S))
        {

            camPosition -= camMovement;
            camTarget -= camMovement;
            Camera->setPosition(camPosition);
            Camera->setTarget(camTarget);
        }
        if(receiver.IsKeyDown(KEY_KEY_A))
        {
            camshift=camDirection.normalize();
            camTarget= (core::vector3df(camTarget.X - ROT_SPEED*frameDeltaTime*camshift.Z, camTarget.Y, camTarget.Z+ROT_SPEED*frameDeltaTime*camshift.X));
            Camera->setTarget(camTarget);
        }
        else if(receiver.IsKeyDown(KEY_KEY_D))
        {
            camshift=camDirection.normalize();
            camTarget= (core::vector3df(camTarget.X + ROT_SPEED*frameDeltaTime*camshift.Z, camTarget.Y, camTarget.Z-ROT_SPEED*frameDeltaTime*camshift.X));
            Camera->setTarget(camTarget);;
        }
The only confusing part is the left and right target translation, which is easier to understand if you draw a line from the origin at any angle on a piece of graph paper, assume it's the XZ plane and rotate it 90 degrees about the origin. You get a line to (Z, Y, -X) unless you rotate it the other way, and then it's (-Z, Y, X).
Post Reply