Thursday, 15 December 2016

Bounding Box fun!

I was recently working with a colleague on a custom deformer which required access to bounding box information for input meshes. I looked into different ways of getting that data via the API and we ended up trying a few different methods which I thought might be worth sharing.

Since I only had access to kMeshData in the deformer, I couldn't create a dagPath (will talk a bit more about that later) but in terms of writing a utility script or testing the examples in the script editor, these will work just fine.

Method 1 - Using the boundingBox method of a MFnDagNode function set


So both a transform and a mesh have bounding box information. I suppose it makes sense..but I've never really thought about it.

The bounding box of a transform relates to its children, and the bounding box of the mesh relates to its components. Since a mesh needs a transform in order to live in the scene, it can be confusing which one relates to what exactly but all we need to really know is that the values displayed in the attribute editor are in object space.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import maya.cmds as cmds
import maya.OpenMaya as om

transform = 'pCube1'

mSel = om.MSelectionList()
dagPath = om.MDagPath()
mSel.add( transform )
mSel.getDagPath(0, dagPath)

dagFn = om.MFnDagNode(dagPath)

# Returns the bounding box for the dag node in object space.
boundingBox = dagFn.boundingBox()

# There's a few useful methods available, including min and max
# which will return the min/max values represented in the attribute editor.

min = boundingBox.min()
max = boundingBox.max()


Since the mesh is a child of the transform, the bounding box co-ordinates represented on the transform will inform us of the bounding box of the mesh.
However, as I mentioned above, this will be in object space. So if the transform is parented to another transform and the object is arbitrarily positioned in the world, these values may not be of much use depending on the circumstance.

Group the polyCube, then move that group somewhere in the world. If you check the min/max values you will notice they don't change.


Method 2 - Building a bounding box from points


We can build a bounding box using the expand method and the points of the mesh. All we have to do is find the positions of each vertex in world space and add them to the bounding box object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Using the dagPath from previous example, lets create a vertex iterator.
iter = om.MItMeshVertex( dagPath )
# Note: The iterator is clever enough to know we want to work on the mesh, 
# even though we are passing in a dagPath for a transform

boundingBox = om.MBoundingBox()
while not iter.isDone():
    
    # Get the position of the current vertex (in world space)
    position = iter.position(om.MSpace.kWorld) 
    boundingBox.expand(position) # Expand the bounding box
    iter.next()

min = boundingBox.min()
max = boundingBox.max()

If we again group the polyCube and move the group somewhere in the world, this time we will have the world space co-ordinates for the min and max.


Method 3 - Creating world-space bounding box using min/max point matrix multiplication


Using the expand method may be quite costly on a mesh with lots of vertices so here's a quicker way of doing it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
dagPath.extendToShape() # Set the dagPath object to the mesh itself
dagFn.setObject(dagPath) # Update the dag function set with the mesh object
boundingBox = dagFn.boundingBox() # Grab the bounding box (object space)

min = boundingBox.min() * dagPath.inclusiveMatrix()
max = boundingBox.max() * dagPath.inclusiveMatrix()

# Create new bounding box, initializing with new world-space points, min and max
boundingBox = om.MBoundingBox( min, max )

min = boundingBox.min()
max = boundingBox.max()

The important part is on line 5 and line 6 where we multiply the object-space bounding box min and max points by it's world matrix (inclusive) which transforms the points to world-space.

Method 4 - transformUsing method of a MBoundingBox object


We can reduce the amount of steps further by making use of the transformUsing method.  It takes a MMatrix and transforms the points for us.

1
2
3
4
5
boundingBox = dagFn.boundingBox()
boundingBox.transformUsing( dagPath.inclusiveMatrix() )

min = boundingBox.min()
max = boundingBox.max()





Bonus - Testing intersection


We can use the intersects method to check for collision with another bounding box.

1
2
3
4
5
6
7
8
9
anotherTransform = 'pCube2'
mSel.add( anotherTransform )
mSel.getDagPath(1, dagPath)
dagFn.setObject( dagPath )

anotherBoundingBox = dagFn.boundingBox()

# Returns a true or false
print boundingBox.intersects( anotherBoundingBox )


To make sure the intersect results are correct, you would need to make sure you build a world space bounding box object as well..

I'll leave that up to you :)

No comments:

Post a Comment