Finding the centre of a rotated object

I want to find the centre of my objects so that they will sit on the ground and look realistic - I translate them up by half their height, and the ground is spread along the x-z axes with y = 0.

I can do this perfectly well when the objects are not rotated (and only have their own 90 degree rotations to make the models sit on the ground and face the front). I wanted to try rotating them at random angles and still have their lowest point touch the ground. I can’t make it work though. I don’t know if my RotateVec function isn’t having the effect I expect (I want it to do the exact same thing as glRotatef will when the models are being drawn), or if my code is wrong in the FindRotCentre function.

Doing the 90 degree rotations was easy, I figured out on paper how the axes changed and then swapped the axes. To do the other rotations, I tried two things:

  1. Get the points as they are given in the model, do the rotations, then swap the axes for the 90 degree rotations. This doesn’t work at all, the floor goes off in the negative z and x directions.
  2. Get the points and swap the axes at the same time, then do the rotations and try different ways of swapping the rotations around (like swapping the Y angle and the Z angle). Here the objects stay mostly where they are supposed to be, but the centre is obviously wrong — some objects float above the floor, some are stuck in it, and when I rotate one of them individually I can see the centre isn’t in the real centre.

Some background on the code, ColladaObjects own a ColladaParser which parses the file, and it has a m_Centre, m_RotX etc which are the 90 degree rotations. In main there is one global for each different sort of object (car, chair, etc). There is another type of object that has a m_Centre, m_RotX etc. This m_Centre is the actual placement in the world and the m_Rots are the random rotations. In main, g_Objects holds these objects. Just so you don’t get confused reading the code and seeing the same variable used in different ways.

Here is some code now.

RotateVec function: (ignore the Pivot part, I always send (0,0,0))


vec4 RotateVec (float XAngle, float YAngle, float ZAngle, vec4 Pivot, vec4 P)
{
//..Translate to origin
	mat4 RotationMatrix = translate(mat4(1.0f), vec3(-Pivot.x, 0.0f, 0.0f)); 
	RotationMatrix = translate(RotationMatrix, vec3(0.0f, -Pivot.y, 0.0f)); 
	RotationMatrix = translate(RotationMatrix, vec3(0.0f, 0.0f, -Pivot.z)); 
	
//..Do rotations
	RotationMatrix = rotate(RotationMatrix, XAngle, vec3(1,0,0));
	RotationMatrix = rotate(RotationMatrix, YAngle, vec3(0,1,0));
	RotationMatrix = rotate(RotationMatrix, ZAngle, vec3(0,0,1));

//..Put back
	RotationMatrix = translate(RotationMatrix, vec3(Pivot.x, 0.0f, 0.0f)); 
	RotationMatrix = translate(RotationMatrix, vec3(0.0f, Pivot.y, 0.0f)); 
	RotationMatrix = translate(RotationMatrix, vec3(0.0f, 0.0f, Pivot.z));

	vec4 ResultVec =  P * RotationMatrix;

	return ResultVec;
}

This is the part of the draw function in main.cpp that does the random rotations:


for(int i = 0; i < g_Objects.size (); i ++)
{
	glPushMatrix ();	
		
	glTranslatef(g_Objects[i].m_Centre.x, g_Objects[i].m_Centre.y, g_Objects[i].m_Centre.z);
	glRotatef(g_Objects[i].m_RotX, 1, 0, 0);
	glRotatef(g_Objects[i].m_RotY, 0, 1, 0);
	glRotatef(g_Objects[i].m_RotZ, 0, 0, 1);

This is the beginning of the model’s draw function that does their 90 degree rotations (called after the above):


void ColladaObject::Draw (int ModelMatrixHandle, int PosHandle, int TextureCoordHandle, int ProjectionMatrixHandle, bool bPick /*=false*/)
{
	glScalef(m_Scale, m_Scale, m_Scale); 
	glTranslatef ((-1)*m_Centre.x, (-1)*m_Centre.y, (-1)*m_Centre.z);
	glRotatef(m_Rot.x, 1, 0, 0);
	glRotatef(m_Rot.y, 0, 1, 0);
	glRotatef(m_Rot.z, 0, 0, 1);	

This is the finding centre function in its entirety, called before any drawing is done (in init in main.cpp). I am going with option #2 as specified in the body of my post, above. TPos is just a simple class that has members x, y, and z. X_AXIS is defined as 0, etc for the other two. Also m_Rot.x is always -90, the others are zero, so it’s OK if they won’t work (if I get a model rotated on those axes in its collada file, I’ll make them work properly then).


void ColladaObject::FindRotCentre (vec4 Pivot, float XAngle, float YAngle, float ZAngle, Pos& Max, Pos& Min)
{
	m_pCollada->m_MaterialMap.StartIterator ();
	while(m_pCollada->m_MaterialMap.Get()->m_Mesh.size () == 0)
	{
		m_pCollada->m_MaterialMap.MoveNext ();
	}
	TPos<int> Axes (X_AXIS, Y_AXIS, Z_AXIS);
	TPos<float> Angles (XAngle, YAngle, ZAngle);
	int mult = 1;

	if(m_Rot.x == 90 || m_Rot.x == -90) //this section may need tweaking if things end up facing backward or upside down or anything
	{
		Axes.y = Z_AXIS;
		Angles.y = ZAngle;
		Axes.z = Y_AXIS;
		Angles.z = YAngle;
		if(m_Rot.x == -90)
		{
			mult = -1;
		}
	}
	if(m_Rot.y == 90 || m_Rot.y == -90)
	{
		Axes.x = Z_AXIS;
		Angles.x = ZAngle;
		Axes.z = X_AXIS;
		Angles.z = XAngle;
	}
	if(m_Rot.z == 90 || m_Rot.z == -90)
	{
		Axes.y = X_AXIS;
		Angles.y = XAngle;
		Axes.x = Y_AXIS;
		Angles.x = YAngle;
	}
	
	vec4 P;

	P.x = m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.x];
	P.y = m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.y];
	P.z = mult*m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.z];
	P.w = 1;

	P = RotateVec (Angles.x, Angles.y, Angles.z, Pivot, P);

	float MaxX = P.x;
	float MinX = P.x;
	float MaxY = P.y;
	float MinY = P.y;
	float MaxZ = P.z;
	float MinZ = P.z;

	while(!m_pCollada->m_MaterialMap.IsEOM ())
	{
		shared_ptr<Material> pMaterial = m_pCollada->m_MaterialMap.Get();
		for (int i = 0; i < pMaterial->m_Mesh.size (); i ++)
		{
			P.x = m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.x];
			P.y = m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.y];
			P.z = mult*m_pCollada->m_MaterialMap.Get()->m_Mesh.at(0).m_Positions[Axes.z];
			P.w = 1;

			P = RotateVec (Angles.x, Angles.y, Angles.z, Pivot, P);

			if(MaxX < P.x)
			{
				MaxX = P.x;
			}
			else if(MinX > P.x)
			{
				MinX = P.x;
			}
			if(MaxY < P.y)
			{
				MaxY = P.y;
			}
			else if(MinY > P.y)
			{
				MinY = P.y;
			}
			if(MaxZ < P.z)
			{
				MaxZ = P.z;
			}
			else if(MinZ > P.z)
			{
				MinZ = P.z;
			}
		}
		m_pCollada->m_MaterialMap.MoveNext ();
	}

	m_Centre.x = MinX + (MaxX - MinX)/2;
	m_Centre.y = MinY + (MaxY - MinY)/2;
	m_Centre.z = MinZ + (MaxZ - MinZ)/2;

	Min.x = MinX - m_Centre.x;
	Max.x = MaxX - m_Centre.x;
	Min.y = MinY - m_Centre.y;
	Max.y = MaxY - m_Centre.y;
	Min.z = MinZ - m_Centre.z;
	Max.z = MaxZ - m_Centre.z;
}

So basically I am hoping someone can either tell me how I need to swap the angles around to make it work, or tell me the correct order in which to swap the axes for the 90 degree rotations and rotate the point, or tell me that my RotateVec function does not do what it is supposed to and why (and that one of my two approaches was correct, only the RotateVec function was letting me down). It is really hard to debug, all I can know is whether I’ve done the wrong thing (floating objects and incorrect centres) but I can’t figure out exactly what the wrong thing is.