Should a mesh always be centered around '0'?

Started by
7 comments, last by cozzie 10 years ago
Hi,

While I was developing/ coding to make childs of a parent mesh have it's own orientation, I've found out that not all my meshes have 0, 0, 0 as it's center. This means having to add some transformation at loading the scene (parent: transform modelspace center with worldspace position and update worldspace position).

Now my question is, is this the way to go or is there an unwritten law that says the meshes/ objects should always be centered around 0, 0, 0 in their own (model)space?

I'm not sure if in the "real" world artists and modellers will keep to this principle / versus making flexible code in my engine so it works always. Another thing I don't know is if I should expect other issues when meshes are not centered around 0,0,0.

Any input is appreciated.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Advertisement

Often artists will be able to place nodes/nulls/locators/thingamajigs (named positions) in their models, which the code can use to identify different attachment points, etc...

Other times there will be conventions, such as pretending that the origin is the floor, and the artists create their models as if they are sitting just on the floor. Othertimes they'll be modelled with the origin in their center, and you can use the AABB of the model to find out how far to translate it when trying to place it somewhere.

So yeah, depends on the game and usage I guess.


is this the way to go or is there an unwritten law that says the meshes/ objects should always be centered around 0, 0, 0 in their own (model)space?

There's no unwritten law about centering meshes at 0,0,0. Actually, for many meshes, having an origin elsewhere is more convenient.

As Hodgman mentions, a box or crate may work better if the center of the bottom panel is at 0. For a character mesh, halfway between the feet and directly below the hip or spine may be more convenient. Similar for a tree - the bottom of the trunk is on the "ground."


making flexible code in my engine so it works always

Yep. I'd recommend that, for every mesh, you maintain base orientation data - a set of scale/rotation/translation vectors/quaternions, or a matrix - appropriate for the use of the mesh. If it most often sits on the ground or floor, the orientation should place it at the world origin appropriately oriented. If it, for instance, is a "flying" object, the orientation may move the center of gravity for the mesh to the world origin.


if I should expect other issues when meshes are not centered around 0,0,0.

Yep. Whether they are centered at 0,0,0 or not, you'll run across meshes modeled in a different reference frame (e.g., right-/left-handed axes system) with a different "up" vector, and much smaller or larger than you want. The mesh's orientation data can rotate/scale to your world, and translate it to an appropriate position.

Determining the orientation data appropriate for your use can be done by experimentation (which is painful), or by using a level or scene editor. You may want to think about a model/mesh format file, perhaps your own custom format, that provides the orientation data that you want when the model is loaded.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Hi both. Thanks for the input.

For now I'm gonna make sure the all meshes I load get their own orientation around center 0,0,0. Giving me flexibility.

Moving to another/ DIY mesh/ level format is something I have to get into.
I currently model the base of the level in max, and export it twice (once with all blended objects hidden and once with opaque hidden). So the level consists of 'x' childs (objects in mesh), which now have their own orientation. For eithing, moving/rotating/scaling, but also for collisions and culling. Game objects I import in the max scene to position them. Not ideal, with for now it works.

To be sure I understand the math correctly, a use case.

The goal is to have 'world position' oriented at the center of the mesh:

A - world position in my scene: 10, 3, 2

B - center in modelspace is 3, 2, 1

1. create worldmatrix based on A

2. transform B using this worldmatrix

3. create new worldmatrix using the transformed world position

Would this be correct?

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me


The goal is to have 'world position' oriented at the center of the mesh:

A - world position in my scene: 10, 3, 2

B - center in modelspace is 3, 2, 1

1. create worldmatrix based on A
2. transform B using this worldmatrix
3. create new worldmatrix using the transformed world position

Would this be correct?

I'm not sure how you plan to do 2, but that sequence may not be what you want. Maybe it's a matter of semantics.

Just for clarity, if you're looking to keep a transform that you can use to render the mesh vertices, and you want the center of the mesh at (10,3,2), then the result of your step 3 would be (7, 1, 1). Is that what you intended?

That is, if you want the mesh center to be at (10, 3, 2), you'd translate by (7, 1, 1).

Hopefully I didn't misunderstand what you were saying.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

I think your right. I'm basically making sure that the world position of an object in the scene, is always the center of that object in worldspace.

Meaning I could multiply a childs transform matrix with the parent world matrix, to get the child world matrix. I need to do the 'transform' first for meshes where 0,0,0 is not the center, because offset child to parent (child pos) is "center to center".

Ps: is it correct that I multiply child orientation matrix with parent world matrix? (and not the other way around)
It sounds logical that the vertices are first transformed in their own space and then "moved" to the parent. Otherwise a child would be for example rotating around the origin of the parent. Ps; I use d3d(x). Something strange though is that using child transform * parent world (to get child world matrix), gives me as a result that X/Y and Z rotation are switched, so rotating on Y axis makes the child rotate on the Z axis etc. Might be something completely different, but it made me doubt which of the 2 options would be correct (because when doing parentworld * child transform, X, Y and Z are not switched around but other things like positions are messed up).

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me


is it correct that I multiply child orientation matrix with parent world matrix? (and not the other way around)

If you're using the default settings for DX9, the multiplication order should be child * parent.


It sounds logical that the vertices are first transformed in their own space and then "moved" to the parent.

Absolutely.


Something strange though is that using child transform * parent world (to get child world matrix), gives me as a result that X/Y and Z rotation are switched

You haven't mentioned where you're getting your data. It sounds like you may be importing from a file modeled in a right-handed reference frame. E.g., modeled in Blender and exported without conversion to a left-hand system. Are you saying if you use Parent-world * child-local every renders correctly?

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

Thanks. That means I shold stop switching between child world = local transfrom * world parent vs world parent * local transform.

Having one truth on this, enables me to focus on solving the left over issues.

Here's a screenshot of both:

http://www.sierracosworth.nl/gamedev/screen_transformXparent.jpg

http://www.sierracosworth.nl/gamedev/screen_parentXlocal.jpg

Note: don't mind the knife/ weapon, that's probably for another reason, that it's child are 'overlapping' (weapon mesh not going into the same code/ functions).

I've tried switching Y and Z values of the individual vertices, to check if that's the problem. But that just 'rotates' the whole scene 90 degrees. I'm quite sure this is tackled by the exporter I use to export max to X file (KwXPort).

So basically with the right approach on parent/child, I have these issues left:

- Movement of childs Z = Y, Y = Z (X = OK) => note that for parent movement Y = Y, Z = Z, is OK

- Rotation of childs is oriented fine to parent, but Axis'es for rotations are: Z = Y, Y = Z (X = OK)

When I switch to worldchild = parentworld * childtransform, the issues are:

- parent - child offsets are off

- rotations Y =Y, Z = Z is correct, but childs are rotated around origin of parent (not OK)

UPDATE: when I set the local transform matrix for a child a world matrix, obviously all objects and childs are centered in the worlds origin, BUT the Y and Z is not switched. So in the local transform matrix on itself, there's no issue with Y and Z. The same for the parent world matrix. But somehow both combined switches Y and Z and vice versa.

Here are some code snippets, any input on where I't goes wrong with Y/Z with childs would be great.


// after loading the mesh, transform child vertices to their 'own origin'

bool CD3dmesh::UpdateVtxBufferRenderables()
{
	mChildCenters.resize(GetSubMeshSize());

//	Allocate memory for the vertices of the full mesh
	TVERTEX *verticesPointer;
	TVERTEX *vertices = new TVERTEX[mMesh->GetNumVertices()];

//	Lock the vertexbuffer of the full mesh and copy data to pointer
	mVtxBuffer->Lock(0, 0, (void**)&verticesPointer, 0);		// ,0 = default
	memcpy(vertices, verticesPointer, mMesh->GetNumVertices()*D3DXGetDeclVertexSize(vtxdecl, 0)); 

//	Find the sphere center in modelspace of FULL MESH
	if(!SUCCEEDED(D3DXComputeBoundingSphere(&vertices[0].position, mMesh->GetNumVertices(), D3DXGetDeclVertexSize(vtxdecl, 0), &mParentCenter, &mParentRadius))) return false;	

//	Go through all renderables and transform their vertices
	for(DWORD obj=0;obj<GetSubMeshSize();++obj)
	{
		D3DXVECTOR3 childCenter;
		float sphereBoundingRadiusBase;
		if(!SUCCEEDED(D3DXComputeBoundingSphere(&vertices[mSubMeshTable[obj].VertexStart].position, (DWORD)mSubMeshTable[obj].VertexCount, D3DXGetDeclVertexSize(vtxdecl, 0), 
												&mChildCenters[obj], &sphereBoundingRadiusBase))) return false; 

		for(DWORD vtx=mSubMeshTable[obj].VertexStart;vtx<mSubMeshTable[obj].VertexStart + mSubMeshTable[obj].VertexCount;++vtx)
		{
			vertices[vtx].position += D3DXVECTOR3(mParentCenter.x - mChildCenters[obj].x, 
			 					   				  mParentCenter.y - mChildCenters[obj].y,
												  mParentCenter.z - mChildCenters[obj].z);
		}
	}

//	Copy the newly transformed vertices back into the vertexbuffer
	memcpy(verticesPointer, vertices, mMesh->GetNumVertices()*D3DXGetDeclVertexSize(Crealysm_dxmath::vtxdecl, 0)); 

//	Unlock the vertexbuffer and delete the vertices data
	mVtxBuffer->Unlock();
	delete[] vertices;

	return true;
}

// seting up orientation etc. for mesh instances, using 'mesh X'
// OBB / AABB etc. code removed for readability

bool CD3dmeshInst::SetupBVWorldSpace(const CD3dmesh &pMesh)
{
	// resize vectors for renderables
	mRenderables.resize(pMesh.mSubMeshSize);

	// RETRIEVE ORINGINAL BOUNDING SPHERE FROM MESH & CREATE BOUNDING VOLUMES
	mSphereCenterModel			= pMesh.GetParentCenter();
	mSphereBoundingRadiusBase	= pMesh.GetParentRadius();
	
	// CREATE BOUNDING VOLUMES (SPHERE & AABB) FOR RENDERABLES, MODEL SPACE
	for(size_t obj=0;obj<GetNrRenderables();++obj)
	{
		D3DXVECTOR3 parentToChild = pMesh.mChildCenters[obj] - pMesh.mParentCenter;		// both in the same space
		mRenderables[obj].Move(parentToChild.x, parentToChild.y, parentToChild.z);
	}

	// CREATE WORLD MATRIX AND TRANFORM BOUNDING VOLUMES TO WORLDSPACE (SPHERE, AABB/OBB): FULL MESH
	CreateWorldMatrix();

	D3DXVec3TransformCoord(&mSphereCenterWorld, &mSphereCenterModel, &mMatWorld);
	mSphereBoundingRadius = mSphereBoundingRadiusBase * mScale;

	// Move worldPos of parents to their center, for correct orientation to childs
	// Child pos = based on center parent and center child
	mWorldPos = mSphereCenterWorld;
	UpdateWorldMatrix();

	// RENDERABLES: SETUP (i.e. CREATE WORLD MATRIX + BOUNDING VOLUMES TO WORLDSPACE)
	for(size_t obj=0;obj<GetNrRenderables();++obj)
	{
		mRenderables[obj].CreateWorldMatrix(mMatWorld);			// pass parent world matrix
	}
	return true;
}

// RENDERABLE: creating and updating the world matrix

/**************************************************************************************/
/***								CREATE WORLDMATRIX 								***/
/*** ==> usage: at scene loading, initial for each renderable 						***/
/*** ==> create D3DXMATRIX for worldspace, translation, rotation, scaling			***/
/**************************************************************************************/

void CD3drenderable::CreateWorldMatrix(const D3DXMATRIX &pMatWorldParent)
{
	ComposeD3DXWorldMatrix(&mMatTransform, D3DXVECTOR3(mScale, mScale, mScale), DEGTORAD(mRot.x), DEGTORAD(mRot.y), DEGTORAD(mRot.z), mPos);
//	D3DXMatrixMultiply(&mMatWorld, &pMatWorldParent, &mMatTransform);
	D3DXMatrixMultiply(&mMatWorld, &mMatTransform, &pMatWorldParent);
}

/**************************************************************************************/
/***								UPDATE WORLDMATRIX								***/
/*** ==> usage: before rendering, ONLY when object is moved, rotated or scaled		***/
/*** ==> updates WorldMatrix, local transform * parent world 						***/
/**************************************************************************************/

bool CD3drenderable::UpdateWorldMatrix(const D3DXMATRIX &pMatWorldParent)
{
	if(mDynamic)
	{
		if(mIsMoved || mIsRotated || mIsScaled)
		{
			ComposeD3DXWorldMatrix(&mMatTransform, D3DXVECTOR3(mScale, mScale, mScale), DEGTORAD(mRot.x), DEGTORAD(mRot.y), DEGTORAD(mRot.z), mPos);
		}
	}
	D3DXMatrixMultiply(&mMatWorld, &mMatTransform, &pMatWorldParent);
//	D3DXMatrixMultiply(&mMatWorld, &pMatWorldParent, &mMatTransform);
	return true;
}

// math function: creating a world matrix in 1 step

/**************************************************************************************/
/***							COMPOSE D3DX WORLD MATRIX							***/
/*** ==> usage: when a world matrix has to be created or updated 					***/
/*** ==> returns the newly created D3DXMATRIX										***/
/**************************************************************************************/

D3DXMATRIX* ComposeD3DXWorldMatrix(D3DXMATRIX *pOut, const D3DXVECTOR3 &pScale, const float pXrot, const float pYrot, const float pZrot, const D3DXVECTOR3 pTranslation)
{
    pOut->m[0][0] = std::cos(pYrot) * std::cos(pZrot) * pScale.x;
    pOut->m[0][1] = std::cos(pYrot) * std::sin(pZrot) * pScale.x;
    pOut->m[0][2] = -std::sin(pYrot) * pScale.x;
    pOut->m[0][3] = 0.0f;

    pOut->m[1][0] = (((std::sin(pXrot) * std::sin(pYrot)) * std::cos(pZrot)) + (std::cos(pXrot) * -std::sin(pZrot))) * pScale.y;
    pOut->m[1][1] = (((std::sin(pXrot) * std::sin(pYrot)) * std::sin(pZrot)) + (std::cos(pXrot) * std::cos(pZrot))) * pScale.y;
    pOut->m[1][2] = std::sin(pXrot) * std::cos(pYrot) * pScale.y;
    pOut->m[1][3] = 0.0f;

    pOut->m[2][0] = (((std::cos(pXrot) * std::sin(pYrot)) * std::cos(pZrot)) + (-std::sin(pXrot) * -std::sin(pZrot))) * pScale.z;
    pOut->m[2][1] = (((std::cos(pXrot) * std::sin(pYrot)) * std::sin(pZrot)) + (-std::sin(pXrot) * std::cos(pZrot))) * pScale.z;
    pOut->m[2][2] = std::cos(pXrot) * std::cos(pYrot) * pScale.z;
    pOut->m[2][3] = 0.0f;

    pOut->m[3][0] = pTranslation.x;
    pOut->m[3][1] = pTranslation.y;
    pOut->m[3][2] = pTranslation.z;
    pOut->m[3][3] = 1.0f;

    return pOut;
}

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

Found it!

The problem is likely to be in my head instead of the code :)

- parent is rotated -90 degrees on the X axis

- afterwards a child is rotated on the Y axis

- because of parent rotation Y becomes Z and Z becomes Y

If I don't rotate the parent then Y and Z are fine when I rotate the child. The same goes for 'moving'.

So basically (and logical when I think of it after hours of debugging....) the child is translated and rotated relative to it's parent, not to the world X/Y and Z axis.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

This topic is closed to new replies.

Advertisement