Restricting rotation about an axis

Problems building or running the engine, queries about how to use features etc.
Dar13
Gnoblar
Posts: 6
Joined: Sun Jun 27, 2010 1:02 am

Restricting rotation about an axis

Post by Dar13 »

Hello!

I've run into a perplexing problem with some quaternion math that I'm trying to perform, as I've never quite understood what certain operations between quaternions return ( quat1.inverse() * quat2 = quatDiff ?!).

I have this code:

Code: Select all

#define CHARACTER_SCALE_FACTOR .0275f

/* ...snip... */

void NPCCharacter::_actionLook(const Ogre::Vector3& target)
{
	Ogre::Bone* headBone;
	std::string n = _node->getName();
	Ogre::Skeleton* skel = static_cast<Ogre::Entity*>(_movableObject)->getSkeleton();
	headBone = skel->getBone("Bip01_Head");

	headBone->setManuallyControlled(true);
	headBone->setInheritOrientation(true);
	int nAnim = skel->getNumAnimations();

	//have to do this to allow the head to turn properly.
	for(int i = 0; i < nAnim; ++i)
	{
		skel->getAnimation(i)->destroyNodeTrack(headBone->getHandle());
	}

	Ogre::Vector3 test = headBone->_getDerivedPosition() * CHARACTER_SCALE_FACTOR + _node->getPosition();
	Ogre::Vector3 dir = target - test;
	Ogre::Quaternion nodeRot,boneRot;
	boneRot = _node->convertLocalToWorldOrientation(_node->getOrientation()) * headBone->_getDerivedOrientation();
	Ogre::Vector3 boneTest = boneRot * Ogre::Vector3::UNIT_Z;

	//turns the direction vector into a 2D normalized vector on the X/Z axis.
	dir.y = 0;
	dir.normalise();

	//All of this ray query stuff is to make sure that the AI can "see" the target before attempting to look at it.
	Ogre::SceneManager* scene = _node->getCreator();
	Ogre::Ray ray(headBone->_getDerivedPosition() * CHARACTER_SCALE_FACTOR + _node->getPosition(),dir);

	Ogre::RaySceneQuery* query = scene->createRayQuery(ray);
	query->setSortByDistance(true);
	query->setQueryMask(CHARACTER_MASK | SCENERY_MASK);

	Ogre::RaySceneQueryResult results = query->execute();

	bool withinView = false;
	if(results.size() == 0)
	{
		withinView = true;
	}
	else
	{
		if(results.begin()->movable->getParentNode()->getName() == getName())
		{
			if(results.size() == 1)
			{
				withinView = true;
			}
		}
		
		if(!withinView && results.size() > 1 && std::next(results.begin())->distance > test.distance(target))
		{
			withinView = true;
		}
	}

	scene->destroyQuery(query);

	if(withinView)
	{
		//need to restrict this rotation, so I don't get an exorcist-type head look.
		headBone->rotate(boneTest.getRotationTo(dir),Ogre::Node::TS_WORLD);

		Ogre::Quaternion boneRotation = _node->convertLocalToWorldOrientation(_node->getOrientation()) * headBone->_getDerivedOrientation() * (Ogre::Quaternion(Ogre::Degree(180),Ogre::Vector3::UNIT_Y));
		Ogre::Quaternion nodeRotation = _node->_getDerivedOrientation();
		Ogre::Quaternion diff = nodeRotation.Inverse() * boneRotation;

		if(abs(diff.getYaw().valueDegrees()) > 90.0f)
		{
			std::cout << boneRotation.getYaw().valueDegrees() << std::endl;
		}
		//now to figure out how to restrict this orientation(shouldn't be too hard now).
		//famous last words lol.
		if(abs(diff.getYaw().valueDegrees()) > 180.0f)
		{
			//headBone->_setDerivedOrientation(
		}
	}

	_isActFinished = true;
}
headBone is a part of a full bipedal skeleton, and _node is the SceneNode that holds the Entity the skeleton is bound to.

My problem is trying to rotate this headbone properly (always faces the player regardless of how the node is facing), and then restricting that rotation (so that it's a human head rather than a horror movie freak show. For some reason, whenever the node is facing on the negative Z-axis the head is always 180 degrees rotated on the Y-axis away from the player. But when the node is facing the positive Z-axis, it faces the player just fine.

I'm totally stumped, and desperately need some help.

Thank you in advance!
User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7157
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 535

Re: Restricting rotation about an axis

Post by Kojack »

At the bottom of the page on http://www.ogre3d.org/tikiwiki/tiki-ind ... ight=euler there's an example I made of head tracking using bones and euler angles (it's a sample from my euler angle class). It has both pitch and yaw angle limits, introduces no roll, and has angular velocity limits (easy to remove if you want instant turning).

A quaternion is effectively a relative rotation around an axis. It doesn't specify an absolute orientation in space, it's always relative to something else. It's like saying "Look 90 degrees clockwise" instead of "Look East". The former is relative (90 degrees from where?) while the latter is absolute. But in ogre when you set an orientation with a quaternion, it's relative to the default orientation of the object (how it appears if you don't rotate it in any way). It's a bit like saying "You are currently facing North. Look 90 degrees clockwise".

Multiplying two quaternions together adds their rotations. If you had a quaternion that looked 90 degrees clockwise and another that looked 45 degrees up, then multiplying them together will give a quaternion result that rotates 90 degrees clockwise and 45 degrees up.

quat1.inverse() * quat2 = quatDiff means find the quaternion that will change quat1 into quat2.
So if you then do quat1 * quatDiff, the answer will be quat2.
In your case diff = nodeRotation.Inverse() * boneRotation is saying "What quaternion (diff) do we need to rotate nodeRotation to become boneRotation".

The biggest problem is that there's an infinite number of euler angles that can represent the same quaternion. You can extract values from a quaternion like with getYaw(), but the result may not be what you expect (it will be right, but may have extra rotations that may not seem needed, such as instead of saying you yawed 180 degrees, it might say you pitched 180 then rolled 180. Both will look the same in the end, but watching it happen over time will be completely different). It's much easier to keep the angles as angles and only generate the quaternion at the end, rather than trying to extract values back out of it.
Dar13
Gnoblar
Posts: 6
Joined: Sun Jun 27, 2010 1:02 am

Re: Restricting rotation about an axis

Post by Dar13 »

Thank you Kojack! ... But I still can't seem to figure it out. The class you pointed out would be great for the restricting of the rotation, but I still don't know how to apply these rotations properly to the bone ( using the code below, the npc's head just nods up and down ). I was really confused by your example as it didn't even manipulate the bone and also didn't really explain how to set the Euler variables to the current orientation of the node.

Code I'm using now (using your class) :

Code: Select all

void NPCCharacter::_actionLook(const Ogre::Vector3& target)
{
	Ogre::Bone* headBone;
	std::string n = _node->getName();
	Ogre::Skeleton* skel = static_cast<Ogre::Entity*>(_movableObject)->getSkeleton();
	headBone = skel->getBone("Bip01_Head");

	headBone->setManuallyControlled(true);
	headBone->setInheritOrientation(true);
	int nAnim = skel->getNumAnimations();

	//have to do this to allow the head to turn properly.
	for(int i = 0; i < nAnim; ++i)
	{
		skel->getAnimation(i)->destroyNodeTrack(headBone->getHandle());
	}

	Ogre::Vector3 test = headBone->_getDerivedPosition() * CHARACTER_SCALE_FACTOR + _node->getPosition();
	Ogre::Vector3 dir = target - test;
	Ogre::Quaternion nodeRot,boneRot;
	Ogre::Euler boneEuler; boneEuler.setDirection(dir,true,false);
	/*boneRot = _node->convertLocalToWorldOrientation(_node->getOrientation()) * headBone->_getDerivedOrientation();
	Ogre::Vector3 boneTest = boneRot * Ogre::Vector3::UNIT_Z;*/
	//Ogre::Vector3 boneTest = headBone->getOrientation() * Ogre::Vector3::UNIT_Z;

	//turns the direction vector into a 2D normalized vector on the X/Z axis.
	dir.y = 0;
	dir.normalise();

	//All of this ray query stuff is to make sure that the AI can "see" the target before attempting to look at it.
	Ogre::SceneManager* scene = _node->getCreator();
	Ogre::Ray ray(headBone->_getDerivedPosition() * CHARACTER_SCALE_FACTOR + _node->getPosition(),dir);

	Ogre::RaySceneQuery* query = scene->createRayQuery(ray);
	query->setSortByDistance(true);
	query->setQueryMask(CHARACTER_MASK | SCENERY_MASK);

	Ogre::RaySceneQueryResult results = query->execute();

	bool withinView = false;
	if(results.size() == 0)
	{
		withinView = true;
	}
	else
	{
		if(results.begin()->movable->getParentNode()->getName() == getName())
		{
			if(results.size() == 1)
			{
				withinView = true;
			}
		}
		
		if(!withinView && results.size() > 1 && std::next(results.begin())->distance > test.distance(target))
		{
			withinView = true;
		}
	}

	scene->destroyQuery(query);

	if(withinView)
	{
		Ogre::Euler node;
		Ogre::Euler t = headOrientation.getRotationTo(dir);
		t.limitYaw(Ogre::Radian(3.0));
		t.limitPitch(Ogre::Radian(3.0));

		headOrientation = headOrientation + t;
		headOrientation.limitYaw(Ogre::Degree(100));
		headOrientation.limitPitch(Ogre::Degree(60));

		headBone->setOrientation(headOrientation);

		/*headBone->rotate(boneTest.getRotationTo(dir),Ogre::Node::TS_WORLD);

		Ogre::Quaternion boneRotation = _node->convertLocalToWorldOrientation(_node->getOrientation()) * headBone->_getDerivedOrientation() * (Ogre::Quaternion(Ogre::Degree(180),Ogre::Vector3::UNIT_Y));
		Ogre::Quaternion nodeRotation = _node->_getDerivedOrientation();
		Ogre::Quaternion diff = nodeRotation.Inverse() * boneRotation;*/
	}

	_isActFinished = true;
}
Thanks for all your help!
User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7157
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 535

Re: Restricting rotation about an axis

Post by Kojack »

Argh, I looked at the example and realised it's broken. :(
It was originally part of a bigger test program, I thought I'd modified it to be stand alone but I missed modifying a couple of lines. I'll fix it tonight after work.
Dar13
Gnoblar
Posts: 6
Joined: Sun Jun 27, 2010 1:02 am

Re: Restricting rotation about an axis

Post by Dar13 »

Hey Kojack, any luck on fixing that example? I don't mean to be bothersome, but I still can't figure this out.