This post briefly details three examples of a deformer that can react to collisions with another object. The end video shows all three examples in action and as ever a basic python version of the compiled plugin is included to get started with.
Direct deformation
The first example is the most basic implementation of the node to achieve direct deformation.
Using the MFnMesh::allIntersections to detect intersection between two meshes and extracting and applying the delta between the intersecting points it is possible to create an effect of direct deformation.
It is worth noting that allIntersections has some caveats.
- The first is that any mesh that you are working with must be a closed surface. This is because it calculates collision by firing a ray from a given point and calculates how many surfaces it has passed through before it dies. If it passes through one it must be inside a mesh, if two it must be outside. An open mesh has the risk of only having one hit even if the point is inside the mesh.
- The second is that as all deltas are obtained by returning the closest point on the collision objects surface from a given point on the colliding object it is possible that the returned closest point might be on the opposite side of the collision object. This is because the colliding object has travelled past a centre line switching where the closest point will now be. This will give the result of vertices snapping to the wrong side of a mesh although the effect can be quite interesting.
Secondary deformation
The second example expands on the first and adds secondary deformation. This version retains all the features of the first but also pushes the intersecting vertex out along its normal to give an idea of volume retention. This is adjustable so that the result can be extended or switched off alltogether. This deformer gives control of the falloff shape using an MRampAttribute and an attribute to define how much of the surface the effect covers. it is also possible to paint its attributes to have fine control over the end result.
Sticky deformation
The third example changes direction and stores all colliding deformed points in an array only updating their position if their delta increases. Added to this is a compute based timer that gradually returns the mesh back to its original shape unless collided with again.
Collision Based Deformer from SBGrover on Vimeo.
Below is a python implementation of the first example to get started with. This will give you the direct deformation. Be aware that as this is using Python the results are much slower than a compiled plugin so it is best not to throw this at dense geometry. Included is a helper function to build a scene with the plugin.
PLUGIN
import maya.OpenMaya as OpenMaya
import maya.OpenMayaAnim as OpenMayaAnim
import maya.OpenMayaMPx as OpenMayaMPx
class collisionDeformer(OpenMayaMPx.MPxDeformerNode):
kPluginNodeId = OpenMaya.MTypeId(0x00000012)
kPluginNodeTypeName = "collisionDeformer"
def __init__(self):
OpenMayaMPx.MPxDeformerNode.__init__( self )
self.accelParams = OpenMaya.MMeshIsectAccelParams() #speeds up intersect calculation
self.intersector = OpenMaya.MMeshIntersector() #contains methods for efficiently finding the closest point to a mesh, required for collider
def deform( self, block, geoItr, matrix, index ):
#get ENVELOPE
envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope
envelopeHandle = block.inputValue(envelope)
envelopeVal = envelopeHandle.asFloat()
if envelopeVal!=0:
#get COLLIDER MESH (as worldMesh)
colliderHandle = block.inputValue(self.collider)
inColliderMesh = colliderHandle.asMesh()
if not inColliderMesh.isNull():
#get collider fn mesh
inColliderFn = OpenMaya.MFnMesh(inColliderMesh)
#get DEFORMED MESH
inMesh = self.get_input_geom(block, index)
#get COLLIDER WORLD MATRIX to convert the bounding box to world space
colliderMatrixHandle = block.inputValue(self.colliderMatrix)
colliderMatrixVal = colliderMatrixHandle.asMatrix()
#get BOUNDING BOX MIN VALUES
colliderBoundingBoxMinHandle = block.inputValue(self.colliderBoundingBoxMin)
colliderBoundingBoxMinVal = colliderBoundingBoxMinHandle.asFloat3()
#get BOUNDING BOX MAX VALUES
colliderBoundingBoxMaxHandle = block.inputValue(self.colliderBoundingBoxMax)
colliderBoundingBoxMaxVal = colliderBoundingBoxMaxHandle.asFloat3()
#build new bounding box based on given values
bbox = OpenMaya.MBoundingBox()
bbox.expand(OpenMaya.MPoint(colliderBoundingBoxMinVal[0], colliderBoundingBoxMinVal[1], colliderBoundingBoxMinVal[2]))
bbox.expand(OpenMaya.MPoint(colliderBoundingBoxMaxVal[0], colliderBoundingBoxMaxVal[1], colliderBoundingBoxMaxVal[2]))
#set up point on mesh and intersector for returning closest point and accelParams if required
pointOnMesh = OpenMaya.MPointOnMesh()
self.intersector.create(inColliderMesh, colliderMatrixVal)
#set up constants for allIntersections
faceIds = None
triIds = None
idsSorted = False
space = OpenMaya.MSpace.kWorld
maxParam = 100000
testBothDirs = False
accelParams = None
sortHits = False
hitRayParams = None
hitFaces = None
hitTriangles = None
hitBary1 = None
hitBary2 = None
tolerance = 0.0001
floatVec = OpenMaya.MFloatVector(0, 1, 0) #set up arbitrary vector n.b this is fine for what we want here but anything more complex may require vector obtained from vertex
#deal with main mesh
inMeshFn = OpenMaya.MFnMesh(inMesh)
inPointArray = OpenMaya.MPointArray()
inMeshFn.getPoints(inPointArray, OpenMaya.MSpace.kWorld)
#create array to store final points and set to correct length
length = inPointArray.length()
finalPositionArray = OpenMaya.MPointArray()
finalPositionArray.setLength(length)
#loop through all points. could also be done with geoItr
for num in range(length):
point = inPointArray[num]
#if point is within collider bounding box then consider it
if bbox.contains(point):
##-- allIntersections variables --##
floatPoint = OpenMaya.MFloatPoint(point)
hitPoints = OpenMaya.MFloatPointArray()
inColliderFn.allIntersections( floatPoint, floatVec, faceIds, triIds, idsSorted, space, maxParam, testBothDirs, accelParams, sortHits, hitPoints, hitRayParams, hitFaces, hitTriangles, hitBary1, hitBary2, tolerance )
if hitPoints.length()%2 == 1:
#work out closest point
closestPoint = OpenMaya.MPoint()
inColliderFn.getClosestPoint(point, closestPoint, OpenMaya.MSpace.kWorld, None)
#calculate delta and add to array
delta = point - closestPoint
finalPositionArray.set(point - delta, num)
else:
finalPositionArray.set(point, num)
#if point is not in bounding box simply add the position to the final array
else:
finalPositionArray.set(point, num)
inMeshFn.setPoints(finalPositionArray, OpenMaya.MSpace.kWorld)
def get_input_geom(self, block, index):
input_attr = OpenMayaMPx.cvar.MPxGeometryFilter_input
input_geom_attr = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom
input_handle = block.outputArrayValue(input_attr)
input_handle.jumpToElement(index)
input_geom_obj = input_handle.outputValue().child(input_geom_attr).asMesh()
return input_geom_obj
def creator():
return OpenMayaMPx.asMPxPtr(collisionDeformer())
def initialize():
gAttr = OpenMaya.MFnGenericAttribute()
mAttr = OpenMaya.MFnMatrixAttribute()
nAttr = OpenMaya.MFnNumericAttribute()
collisionDeformer.collider = gAttr.create( "colliderTarget", "col")
gAttr.addDataAccept( OpenMaya.MFnData.kMesh )
collisionDeformer.colliderBoundingBoxMin = nAttr.createPoint( "colliderBoundingBoxMin", "cbbmin")
collisionDeformer.colliderBoundingBoxMax = nAttr.createPoint( "colliderBoundingBoxMax", "cbbmax")
collisionDeformer.colliderMatrix = mAttr.create("colliderMatrix", "collMatr", OpenMaya.MFnNumericData.kFloat )
mAttr.setHidden(True)
collisionDeformer.multiplier = nAttr.create("multiplier", "mult", OpenMaya.MFnNumericData.kFloat, 1)
collisionDeformer.addAttribute( collisionDeformer.collider )
collisionDeformer.addAttribute( collisionDeformer.colliderMatrix )
collisionDeformer.addAttribute( collisionDeformer.colliderBoundingBoxMin )
collisionDeformer.addAttribute( collisionDeformer.colliderBoundingBoxMax )
collisionDeformer.addAttribute( collisionDeformer.multiplier )
outMesh = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom
collisionDeformer.attributeAffects( collisionDeformer.collider, outMesh )
collisionDeformer.attributeAffects( collisionDeformer.colliderBoundingBoxMin, outMesh )
collisionDeformer.attributeAffects( collisionDeformer.colliderBoundingBoxMax, outMesh )
collisionDeformer.attributeAffects( collisionDeformer.colliderMatrix, outMesh )
collisionDeformer.attributeAffects( collisionDeformer.multiplier, outMesh )
def initializePlugin(obj):
plugin = OpenMayaMPx.MFnPlugin(obj, 'Grover', '1.0', 'Any')
try:
plugin.registerNode('collisionDeformer', collisionDeformer.kPluginNodeId, creator, initialize, OpenMayaMPx.MPxNode.kDeformerNode)
except:
raise RuntimeError, 'Failed to register node'
def uninitializePlugin(obj):
plugin = OpenMayaMPx.MFnPlugin(obj)
try:
plugin.deregisterNode(collisionDeformer.kPluginNodeId)
except:
raise RuntimeError, 'Failed to deregister node'
HELPER CODE
#simply create two polygon spheres. Move the second away from the first, select the first and run the code below.
import maya.cmds as cmds
cmds.delete(cmds.ls(type='collisionDeformer'))
cmds.flushUndo()
cmds.unloadPlugin('collisionDeformer.py')
cmds.loadPlugin('collisionDeformer.py')
cmds.deformer(type='collisionDeformer')
cmds.connectAttr('pSphere2.worldMesh', 'collisionDeformer1.colliderTarget')
cmds.connectAttr('pSphere2.matrix', 'collisionDeformer1.colliderMatrix')
cmds.connectAttr('pSphere2.boundingBox.boundingBoxSize.boundingBoxSizeX', 'collisionDeformer1.colliderBoundingBox.colliderBoundingBoxX')
cmds.connectAttr('pSphere2.boundingBox.boundingBoxSize.boundingBoxSizeY', 'collisionDeformer1.colliderBoundingBox.colliderBoundingBoxY')
cmds.connectAttr('pSphere2.boundingBox.boundingBoxSize.boundingBoxSizeZ', 'collisionDeformer1.colliderBoundingBox.colliderBoundingBoxZ')
0 comments:
Post a Comment