Tuesday, 18 April 2017

Visibility Node v2.0

Here is an update to the previous visibility node. As discussed in the last post the intention with this version was to test out a quicker alternative in changing the visibility for individual faces in a mesh for the purpose of visualising internal meshes and details.
This time however rather than adjusting the vertex face alpha values the node would make use of the component modifiers already available in Maya.These modifiers are created and maintained as historical entities to your mesh. If you apply a poly smooth then a modifier will be created for the purpose into which will hook your mesh 'orig shape', the output passing into your mesh itself. It is possible to chain the modifiers together each one maintaining a list of edited components that pass down the chain until they reach the actual mesh where the result can be seen.
It is the fact that these modifiers allow the input of a component list that allows them to be used for the purpose of dynamically adjusting a mesh based on fluctuating input data.
In the case of this example we want to see inside a mesh when a collider intersects. For this we use a deleteComponent modifier which allows us to input the original mesh shape and a list of components to be considered for deletion. These components are derived by using the ever useful MFnMesh::allIntersections method which based on casting rays can define if a mesh is colliding with another before returning the vertices that are inside.
By querying the faces that are made up of these vertices it is possible to pass a complete list over to the modifier to be 'modified'.
Below is an example of how the nodes hook into one another to achieve the desired effect.

One drawback of note is that you are always limited to the shape of your 'orig mesh'. It is possible to update this shape but this can prove to be a bit of a pain so do not expect this method to work for skinned and deformed meshes with ease.
I had initially created the node in such a way that it was able to accept a continually changing face count from the orig shape without the requirement of updating class variables. However this was at a sacrifice to speed as I was rebuilding the vertex lists for every update. Fevsy of Constrain n' Bake suggested that I only read in the vertex list once on the initialisation and then maintain it as required which is why I trigger an update by switching the update channel on the node. It speeds up the evaluation at the cost of making the node less fluid to interact with. Which is better? You decide.
Thanks also go to Hans Goddard for inspiring me with a demonstration of his implementation in this video.

..and here is mine

Visibility Node v2.0 from SBGrover on Vimeo.
If you want to have a go yourself below is a basic Python Plugin to get started with. This is an example from the first version of the node and does not support the component modifiers. Note that this plugin is also an MPxDeformer rather than an MPxNode.

 import maya.cmds as cmds  
 import maya.OpenMaya as OpenMaya  
 import maya.OpenMayaAnim as OpenMayaAnim  
 import maya.OpenMayaMPx as OpenMayaMPx  
 class visibilityDeformer(OpenMayaMPx.MPxDeformerNode):  
      kPluginNodeId = OpenMaya.MTypeId(0x00000013)  
      kPluginNodeTypeName = "visibilityDeformer"  
      accelParams = OpenMaya.MMeshIsectAccelParams() #speeds up intersect calculation  
      intersector = OpenMaya.MMeshIntersector() #contains methods for efficiently finding the closest point to a mesh, required for collider  
      def __init__(self):  
           OpenMayaMPx.MPxDeformerNode.__init__( self )  
      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 DEFORMED MESH  
                inMesh = self.get_input_geom(block, index)  
                #get COLLIDER MESH (as worldMesh)  
                colliderHandle = block.inputValue(self.collider)  
                inColliderMesh = colliderHandle.asMesh()  
                if not inColliderMesh.isNull():  
                     inColliderFn = OpenMaya.MFnMesh(inColliderMesh)  
                     #get COLLIDER WORLD MATRIX  
                     colliderMatrixHandle = block.inputValue(self.colliderMatrix)  
                     colliderMatrixVal = colliderMatrixHandle.asMatrix()  
                     #get BOUNDING BOX MIN VALUES  
                     colliderBoundingBoxMinHandle = block.inputValue(self.colliderBoundingBoxMin)  
                     colliderBoundingBoxMinValDouble = colliderBoundingBoxMinHandle.asFloat3()  
                     #get BOUNDING BOX MAX VALUES  
                     colliderBoundingBoxMaxHandle = block.inputValue(self.colliderBoundingBoxMax)  
                     colliderBoundingBoxMaxValDouble = colliderBoundingBoxMaxHandle.asFloat3()  
                     colliderBoundingBoxMinVal = OpenMaya.MPoint(colliderBoundingBoxMinValDouble[0], colliderBoundingBoxMinValDouble[1], colliderBoundingBoxMinValDouble[2])  
                     colliderBoundingBoxMaxVal = OpenMaya.MPoint(colliderBoundingBoxMaxValDouble[0], colliderBoundingBoxMaxValDouble[1], colliderBoundingBoxMaxValDouble[2])  
                     #build new bounding box based on given values  
                     bbox = OpenMaya.MBoundingBox()  
                     self.accelParams = inColliderFn.autoUniformGridParams()  
                     #deal with main mesh  
                     inMeshFn = OpenMaya.MFnMesh(inMesh)  
                     inPointArray = OpenMaya.MPointArray()  
                     inMeshFn.getPoints(inPointArray, OpenMaya.MSpace.kWorld)  
                     ##BEGIN DIRECT COLLISION##  
                     deformed_list = []  
                     col = OpenMaya.MColor(1,1,1,0)  
                     for num in range(inPointArray.length()):  
                          point = OpenMaya.MPoint(inPointArray[num])  
                          bbox_flag = False  
                          if bbox.contains(point):  
                               bbox_flag = True  
                               vec = OpenMaya.MVector()  
                               inMeshFn.getVertexNormal(num, vec, OpenMaya.MSpace.kWorld)  
                               ##-- allIntersections arguments --##  
                               floatPoint = OpenMaya.MFloatPoint(point)  
                               floatVec = OpenMaya.MFloatVector(vec)  
                               faceIds = None  
                               triIds = None  
                               idsSorted = False  
                               space = OpenMaya.MSpace.kWorld  
                               maxParam = 100000  
                               testBothDirs = False  
                               accelParams = None#self.accelParams  
                               sortHits = False  
                               hitPoints = OpenMaya.MFloatPointArray()  
                               hitRayParams = None  
                               hitFaces = OpenMaya.MIntArray()  
                               hitTriangles = OpenMaya.MIntArray()  
                               hitBary1 = None  
                               hitBary2 = None  
                               tolerance = 0.0001  
                               inColliderFn.allIntersections( floatPoint, floatVec, faceIds, triIds, idsSorted, space, maxParam, testBothDirs, accelParams, sortHits, hitPoints, hitRayParams, hitFaces, hitTriangles, hitBary1, hitBary2, tolerance )  
                               #for all hits of length of 1 do the following  
                               if hitPoints.length()%2 == 1:  
                                    iterator = OpenMaya.MItMeshVertex(inMesh)  
                                    util = OpenMaya.MScriptUtil()  
                                    pInt = util.asIntPtr()  
                                    iterator.setIndex(num, pInt)  
                                    faceArray = OpenMaya.MIntArray()  
                                    for f_idx in range(faceArray.length()):   
                                         polyVertArray = OpenMaya.MIntArray()  
                                         inMeshFn.getPolygonVertices(faceArray[f_idx], polyVertArray)  
                                         for v_idx in range(polyVertArray.length()):   
                                              inMeshFn.setFaceVertexColor(col, faceArray[f_idx], polyVertArray[v_idx])  
                string = "dgdirty %s;"%self.name()  
                OpenMaya.MGlobal.executeCommand(string, False, False)  
      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_geom_obj = input_handle.outputValue().child(input_geom_attr).asMesh()  
           return input_geom_obj  
 def creator():  
      return OpenMayaMPx.asMPxPtr(visibilityDeformer())  
 def initialize():  
      gAttr = OpenMaya.MFnGenericAttribute()  
      mAttr = OpenMaya.MFnMatrixAttribute()  
      nAttr = OpenMaya.MFnNumericAttribute()  
      visibilityDeformer.collider = gAttr.create( "colliderTarget", "col")  
      gAttr.addDataAccept( OpenMaya.MFnData.kMesh )  
      visibilityDeformer.colliderBoundingBoxMin = nAttr.createPoint( "colliderBoundingBoxMin", "cbbmin")  
      visibilityDeformer.colliderBoundingBoxMax = nAttr.createPoint( "colliderBoundingBoxMax", "cbbmax")  
      visibilityDeformer.colliderMatrix = mAttr.create("colliderMatrix", "collMatr")  
      visibilityDeformer.multiplier = nAttr.create("multiplier", "mult", OpenMaya.MFnNumericData.kFloat, 1)  
      visibilityDeformer.addAttribute( visibilityDeformer.collider )  
      visibilityDeformer.addAttribute( visibilityDeformer.colliderMatrix )  
      visibilityDeformer.addAttribute( visibilityDeformer.colliderBoundingBoxMin )  
      visibilityDeformer.addAttribute( visibilityDeformer.colliderBoundingBoxMax )  
      visibilityDeformer.addAttribute( visibilityDeformer.multiplier )  
      outMesh = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom  
      visibilityDeformer.attributeAffects( visibilityDeformer.collider, outMesh )  
      visibilityDeformer.attributeAffects( visibilityDeformer.colliderBoundingBoxMin, outMesh )  
      visibilityDeformer.attributeAffects( visibilityDeformer.colliderBoundingBoxMax, outMesh )  
      visibilityDeformer.attributeAffects( visibilityDeformer.colliderMatrix, outMesh )  
      visibilityDeformer.attributeAffects( visibilityDeformer.multiplier, outMesh )  
 def initializePlugin(obj):  
      plugin = OpenMayaMPx.MFnPlugin(obj, 'Grover', '1.0', 'Any')  
           plugin.registerNode('visibilityDeformer', visibilityDeformer.kPluginNodeId, creator, initialize, OpenMayaMPx.MPxNode.kDeformerNode)  
           raise RuntimeError, 'Failed to register node'  
 def uninitializePlugin(obj):  
      plugin = OpenMayaMPx.MFnPlugin(obj)  
           raise RuntimeError, 'Failed to deregister node'  
The support code. Run this to build a quick scene which supports the plugin above.
 import maya.cmds as cmds  
 cmds.select('pSphere1', 'pSphere2')  
 cmds.polyColorPerVertex(r=0.5, g=0.5, b=0.5 ,a=1 , cdo=True)  
 deform = cmds.deformer(type='visibilityDeformer')[0]  
 cmds.connectAttr('pSphere2.worldMesh',deform + ".colliderTarget", f=True)  
 cmds.connectAttr('pSphere2.worldMatrix',deform + ".colliderMatrix", f=True)  
 cmds.connectAttr('pSphere2.boundingBoxMin',deform + ".colliderBoundingBoxMin", f=True)  
 cmds.connectAttr('pSphere2.boundingBoxMax',deform + ".colliderBoundingBoxMax", f=True)  


Post a Comment