Get bounding box of animated skinned mesh
by Milt-Tec Corp (#0001) · 08/13/2008 (6:16 am) · 5 comments
I am developing an app where I use the mouse to select animated skinned objects in a scene. Unfortunately, calling TSShapeInstance::computeBounds (...) always returns the bounding box of the objects in their rest states (before bone transformations have been applied). As a given object animates and changes shape, it quickly pokes out of its static bounding box. I needed a way to get the bounding box around the animated skins to use for my mouse pick logic.
We will look at a solution for this in two parts:
--------------------------------------------------------------------------
PART I
It turns out to be very simple to calculate the bounds itself right after it is rendered. In TSSkinMesh::computeBounds (...) the function calls the base class TSMesh::computeBounds (...) function, and passes it the initial unskinned vertices for use in the calculation as follows:
in order to get the bounding box around the skinned vertices for the last rendered keyframe, just change this line to read:
All this does is skip to the default base class function, which will use the current skinned verts array to calculated the bounding box.
To explain how this change works, let's go back to the normal sequence of events which take place during rendering. When you kick off an animation thread on a TSShapeInstance object, the next time you call its animate() function, Torque will call the animateNodes() function if the animation transformation flag is dirty. This function will in turn recalculate all of the mNodeTransforms[i] transformations to match the current state of the animated bones.
Now, when TSShapeInstance::render(...) starts to render all of its meshes, it will ultimately call the TSSkinMesh::render(...) function for each skinned mesh. The first thing TSSkinMesh::render(...) does is call updateSkin(). This function takes the initial vertex locations of the skin and transforms them into their final animated positions using the transformations setup by the TSShapeInstance::animateNodes(...) function. The transformed vertices are stored in the underlying TSMesh's verts vector array. The code then uses these transformed vertices to render the skin.
The good news for us is, the transformed vertex locations are left in the verts array. Since the default TSMesh::computeBounds(...) function uses the verts array for its calculation, we now have an easy way of calculating a bounding box around the skinned vertices in their current animated position.
Only one other comment: If you call TSSkinMesh::computeBounds(...) directly, the frame parameter is ignored. The bounding box will be calculated around the last keyframe locations rendered to the scene.
----------------------------------------------------
PART II
At this point we can calculate the bounding box of the skin mesh after it has been rendered. The problem is this technique is not adequete if you have multiple instances of the skinned object in your scene. For each instance of the skin, Torque will call the updateSkin function using the appropriate keyframe for that instance. Whichever skin is drawn last will leave its modified vertices in the TSMesh. So, if you called computeBounds on any of the instances of the skin mesh, you would get the same bounds for all of them (the bounds for the last rendered instance).
To get around this, we have to record the bounding box of the TSSkinMesh object as soon as it is rendered, and store the results at the MeshObjectInstance level in TSShapeInstance. We will then have the unique bounding box for each instance of the skin stored with the MeshObjectInstance structures. To get the final bounding box of the TSShapeInstance object, we will have to walk through all the MeshObjectInstances and substitute the skin mesh bounds for any TSSkinMesh meshes in the list.
The first step is to add a bounding box for skin meshes to MeshObjectInstance. Somewhere in the TSShapeInstance::MeshObjectInstance structure, add the following line:
Then in the tsShapeInstance.cc file, in the TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale) function, locate the following two lines of code:
and replace them with:
Now, anytime a TSSkinMesh instance is rendered, the bounding box for that instance will be recorded in TSShapeInstance::MeshObjectInstance. You can use that bounds information later to construct an overall bounding box for the TSShapeInstance which changes as its animated skin meshs change.
If you are curious, here is the routine I wrote to calculate the overall bounding box for a TSShapeInstance with animated skin meshes in it (there are a few class variables which are not explained, but should be fairly self documenting):
We will look at a solution for this in two parts:
--------------------------------------------------------------------------
PART I
It turns out to be very simple to calculate the bounds itself right after it is rendered. In TSSkinMesh::computeBounds (...) the function calls the base class TSMesh::computeBounds (...) function, and passes it the initial unskinned vertices for use in the calculation as follows:
TSMesh::computeBounds(initialVerts.address(),initialVerts.size(),transform,bounds,center,radius);
in order to get the bounding box around the skinned vertices for the last rendered keyframe, just change this line to read:
TSMesh::computeBounds (transform, bounds, frame, center, radius);
All this does is skip to the default base class function, which will use the current skinned verts array to calculated the bounding box.
To explain how this change works, let's go back to the normal sequence of events which take place during rendering. When you kick off an animation thread on a TSShapeInstance object, the next time you call its animate() function, Torque will call the animateNodes() function if the animation transformation flag is dirty. This function will in turn recalculate all of the mNodeTransforms[i] transformations to match the current state of the animated bones.
Now, when TSShapeInstance::render(...) starts to render all of its meshes, it will ultimately call the TSSkinMesh::render(...) function for each skinned mesh. The first thing TSSkinMesh::render(...) does is call updateSkin(). This function takes the initial vertex locations of the skin and transforms them into their final animated positions using the transformations setup by the TSShapeInstance::animateNodes(...) function. The transformed vertices are stored in the underlying TSMesh's verts vector array. The code then uses these transformed vertices to render the skin.
The good news for us is, the transformed vertex locations are left in the verts array. Since the default TSMesh::computeBounds(...) function uses the verts array for its calculation, we now have an easy way of calculating a bounding box around the skinned vertices in their current animated position.
Only one other comment: If you call TSSkinMesh::computeBounds(...) directly, the frame parameter is ignored. The bounding box will be calculated around the last keyframe locations rendered to the scene.
----------------------------------------------------
PART II
At this point we can calculate the bounding box of the skin mesh after it has been rendered. The problem is this technique is not adequete if you have multiple instances of the skinned object in your scene. For each instance of the skin, Torque will call the updateSkin function using the appropriate keyframe for that instance. Whichever skin is drawn last will leave its modified vertices in the TSMesh. So, if you called computeBounds on any of the instances of the skin mesh, you would get the same bounds for all of them (the bounds for the last rendered instance).
To get around this, we have to record the bounding box of the TSSkinMesh object as soon as it is rendered, and store the results at the MeshObjectInstance level in TSShapeInstance. We will then have the unique bounding box for each instance of the skin stored with the MeshObjectInstance structures. To get the final bounding box of the TSShapeInstance object, we will have to walk through all the MeshObjectInstances and substitute the skin mesh bounds for any TSSkinMesh meshes in the list.
The first step is to add a bounding box for skin meshes to MeshObjectInstance. Somewhere in the TSShapeInstance::MeshObjectInstance structure, add the following line:
Box3F m_SkinBounds
Then in the tsShapeInstance.cc file, in the TSShapeInstance::render(S32 dl, F32 intraDL, const Point3F * objectScale) function, locate the following two lines of code:
for (i=start; i<end; i++)
mMeshObjects[i].render(od,mMaterialList);and replace them with:
// render each of the meshes, keeping track of the bounding box
// of each TSSkinMesh instance
TSMesh * pMesh;
for (i = start; i < end; i++)
{
// render the mesh instance
mMeshObjects [i].render (od, mMaterialList);
// check to see if we just rendered a TSSkinMesh instance
pMesh = mMeshObjects [i].getMesh (od);
if (pMesh != NULL && pMesh->getMeshType () == TSMesh::SkinMeshType)
{
// it was a TSSkinMesh, let's calculate its new bounding box and
// store the value in the mesh object instance (we have to take this
// step because a given TSSkinMesh may be shared by multiple MeshObjectInstance
// entries, with each being in potentially different animation states)
MatrixF mtIdentity;
// get the bounding box for the skin mesh as it was just rendered
mtIdentity.identity ();
pMesh->computeBounds (mtIdentity, mMeshObjects [i].m_SkinBounds);
}
}Now, anytime a TSSkinMesh instance is rendered, the bounding box for that instance will be recorded in TSShapeInstance::MeshObjectInstance. You can use that bounds information later to construct an overall bounding box for the TSShapeInstance which changes as its animated skin meshs change.
If you are curious, here is the routine I wrote to calculate the overall bounding box for a TSShapeInstance with animated skin meshes in it (there are a few class variables which are not explained, but should be fairly self documenting):
void animatedSkinObject::getAnimatedBoundingBoxCCS (const int _iDetailLevel, Box3F & animatedBounds)
{
if (bHasBeenAnimated && iAnimationSequenceNum >= 0 &&
pAnimationThread != NULL && pShapeInstance != NULL &&
_iDetailLevel >= 0)
{
// find the bounding box of animated shapes
TSShape * pShape;
pShape = pShapeInstance->getShape ();
if (pShape != NULL && _iDetailLevel < pShape->details.size ())
{
int iSubShapeNum;
int iObjDetailNum;
int iMesh;
int iSubShapeStart;
int iSubShapeEnd;
const TSDetail * pDetail;
Box3F box;
TSShapeInstance::MeshObjectInstance * pMeshObjInst;
TSMesh * pMesh;
pDetail = &pShape->details [_iDetailLevel];
iSubShapeNum = pDetail->subShapeNum;
iObjDetailNum = pDetail->objectDetailNum;
// set up static data (includes mesh transforms array)
pShapeInstance->setStatics (_iDetailLevel);
iSubShapeStart = pShape->subShapeFirstObject [iSubShapeNum];
iSubShapeEnd = pShape->subShapeNumObjects [iSubShapeNum] + iSubShapeStart;
// run through objects and update bounds as we go
animatedBounds.min.set ( 10E30f, 10E30f, 10E30f);
animatedBounds.max.set (-10E30f, -10E30f, -10E30f);
for (iMesh = iSubShapeStart; iMesh < iSubShapeEnd; iMesh++)
{
pMeshObjInst = &(pShapeInstance->mMeshObjects [iMesh]);
if (iObjDetailNum >= pMeshObjInst->object->numMeshes)
continue;
pMesh = pMeshObjInst->getMesh (iObjDetailNum);
if (pMesh != NULL)
{
if ((pMesh->getMeshType () & TSMesh::SkinMeshType) > 0)
{
// this is a skinned mesh, retrieve the skin bounding box recorded for the mesh
// the last time it was rendered, and use that box to update our overall bounding box
box = pMeshObjInst->m_SkinBounds;
animatedBounds.min.setMin (box.min);
animatedBounds.max.setMax (box.max);
}
else
{
// this is NOT a skin mesh, check to see if we have a valid transform
if (pMeshObjInst->smTransforms != NULL &&
pMeshObjInst->getTransform () != NULL)
{
pMesh->computeBounds (*pMeshObjInst->getTransform (), box);
animatedBounds.min.setMin (box.min);
animatedBounds.max.setMax (box.max);
}
}
}
} // *** end FOR iMesh ***
// cleanup temp static items
pShapeInstance->clearStatics ();
}
else
{
// couldn't get the shape or else the detail level is invalid, fallback to using the static bounding box
pShapeInstance->computeBounds (_iDetailLevel, animatedBounds);
}
}
else
{
// the component has NEVER been animated (or we don't have all the info we need), so
// just use the basic bounding box
if (pShapeInstance != NULL)
pShapeInstance->computeBounds (_iDetailLevel, animatedBounds);
}
}
#2
08/13/2008 (4:39 pm)
I have only tested it with TGE v1.5.2
#3
so this might "just work", but if not it should just be a matter of minor adjustments, would be my guess.
08/13/2008 (11:33 pm)
cool resource. Ron, afaik, the skin deformation code is pretty much the same in TGEA as TGE,so this might "just work", but if not it should just be a matter of minor adjustments, would be my guess.
#4
08/15/2008 (1:34 am)
The auto bound resource has been broken for a while, this resource just what I need! 
Torque Owner Ronald J Nelson
Code Hammer Games