In part one we created a simple script, which generates voxels for a selected mesh object using
MFnMesh::allIntersections function. In this part we are going to write a VoxelGenerator node using Maya .NET API. Why node? Because we want to update our voxel on mesh changes. Actually I believe this node can be created using Maya python API, but i did it using C# in order to learn it, and check how usefull it is.
Let's define node attributes. Number and position of voxels depend on a grid density and a base object position in 3D space. So the input attributes should be: input - (short) "density", (kMesh) "inputGeometry", and output - (int) "voxelsNumber".
I use Microsoft Visual Studio 2013 and Autodesk Maya 2014. For easy start download and install Maya C# Wizards for Visual Studio. You can find it in Cyrille Fauvel blog. After the installation create a new maya plugin in the Visual Studio and let's begin!
Maya Plugin Wizard creates files myNode.cs and myPlugin.cs.
We don't need to change anything in the file myPlugin.cs, so I cut out all comments to make it much shorter.
myPlugin.cs
myNode.cs
VoxelGrid.cs
Download Visual Studio project with the files above. Compiled in VS 2013 and tested in Maya 2014. Don't forget to add openmayacpp.dll and openmayacs.dll files to References.
MFnMesh::allIntersections function. In this part we are going to write a VoxelGenerator node using Maya .NET API. Why node? Because we want to update our voxel on mesh changes. Actually I believe this node can be created using Maya python API, but i did it using C# in order to learn it, and check how usefull it is.
Let's define node attributes. Number and position of voxels depend on a grid density and a base object position in 3D space. So the input attributes should be: input - (short) "density", (kMesh) "inputGeometry", and output - (int) "voxelsNumber".
I use Microsoft Visual Studio 2013 and Autodesk Maya 2014. For easy start download and install Maya C# Wizards for Visual Studio. You can find it in Cyrille Fauvel blog. After the installation create a new maya plugin in the Visual Studio and let's begin!
myPlugin.cs
using Autodesk.Maya.OpenMaya; // This line is not mandatory, but improves loading performances [assembly: ExtensionPlugin(typeof(voxelGenerator.MyPlugin))] namespace voxelGenerator { public class MyPlugin : IExtensionPlugin { public bool InitializePlugin() { return true; } public bool UninitializePlugin() { return true; } public string GetMayaDotNetSdkBuildVersion() { return ""; } } }File myNode.cs contains a primary node class. It defines node name, input and output attributes and compute() method. All magic is born in this file. I added my comments to make it more comprehensible.
myNode.cs
using System.Diagnostics; using Autodesk.Maya.OpenMaya; using voxelGenerator; [assembly: MPxNodeClass(typeof(MayaNetPlugin.VoxelGenerator), "VoxelGenerator", 0x00010111)] namespace MayaNetPlugin { // This class is instantiated by Maya for each node created in a Maya // scene. public class VoxelGenerator : MPxNode //, IMPxNode { // ------------------------- // Input node attributes private static MObject oVoxelType; [MPxNodeNumeric("density", "density", MFnNumericData.Type.kShort, Keyable = true, Min1 = 2)] public static MObject oDensity = null; private MFnNumericAttribute aDensity; public static MObject oInputMesh; // ------------------------- // Output node attribute public static MObject oOutput; // ------------------------- // internal Class attributes private VoxelGrid grid; private Stopwatch sw; public VoxelGenerator() { // set default value for the input attribute aDensity = new MFnNumericAttribute(oDensity); aDensity.setDefault(5); } ///File VoxelGrid.cs contains methods DeleteVoxels() and GenerateVoxels(). We call in these two methods on each update in order to regenerate voxels./// Compute Node method /// /// /// ///override public bool compute(MPlug plug, MDataBlock dataBlock) { MDataHandle meshDataHandle = dataBlock.inputValue(oInputMesh); MObject oConnectedMesh = meshDataHandle.asMesh; sw = new Stopwatch(); switch (oConnectedMesh.apiType) { case MFn.Type.kInvalid: MGlobal.displayInfo("No mesh connected."); dataBlock.setClean(plug); return false; case MFn.Type.kMeshData: { sw.Start(); // get connected mesh MPlug mesh_mplug = new MPlug(thisMObject(), oInputMesh); MPlugArray plugs = new MPlugArray(); mesh_mplug.connectedTo(plugs, true, true); MObject connectedMesh = plugs[0].node; // get value of the attribute Type MDataHandle voxelTypeDataHandle = dataBlock.inputValue(oVoxelType); short voxelType = voxelTypeDataHandle.asShort; // get value of the attribute Density MDataHandle densityDataHandle = dataBlock.inputValue(oDensity); short density = densityDataHandle.asShort; // delete existing voxels if exist if (grid != null) grid.DeleteVoxels(); // generate voxels based on input mesh, type and density attributes grid = new VoxelGrid(connectedMesh, voxelType, density); // set grid.VoxelCount as value of the output attribute MDataHandle outputHandle = dataBlock.outputValue(oOutput); outputHandle.asInt = grid.VoxelCount; sw.Stop(); MGlobal.displayInfo("calculation time (ms): " + sw.Elapsed.Milliseconds); break; } } dataBlock.setClean(plug); return true; } [MPxNodeInitializer] public static void initialize() { MFnEnumAttribute enumAttr = new MFnEnumAttribute(); MFnTypedAttribute typedAttr = new MFnTypedAttribute(); MFnNumericAttribute numAttr = new MFnNumericAttribute(); // ------------------------------- // Create attributes section // attribute Type VoxelGenerator.oVoxelType = enumAttr.create("voxelType", "type"); enumAttr.addField("World", 0); enumAttr.addField("Local", 1); // attribute inputMesh VoxelGenerator.oInputMesh = typedAttr.create("inputMesh", "iMesh", MFnData.Type.kMesh); typedAttr.disconnectBehavior = MFnAttribute.DisconnectBehavior.kReset; // attribute output number of voxels VoxelGenerator.oOutput = numAttr.create("output", "out", MFnNumericData.Type.kInt); numAttr.isReadable = true; numAttr.isStorable = false; // Add attributes section addAttribute(VoxelGenerator.oVoxelType); addAttribute(VoxelGenerator.oInputMesh); addAttribute(VoxelGenerator.oOutput); // Affect attributes section attributeAffects(oVoxelType, oOutput); attributeAffects(oDensity, oOutput); attributeAffects(oInputMesh, oOutput); } } }
VoxelGrid.cs
using System; using Autodesk.Maya.OpenMaya; namespace voxelGenerator { class VoxelGrid { private enum Type {world, local}; private Type voxelType; // Type of the VoxelGrid (world or object oriented) private MDagPath mesh_dpath; // Input mesh as MDagPath private MFnMesh mfnMesh; // Poly Function Set for input mesh private MBoundingBox bbox; // Bounding box of the input mesh private MObjectArray voxelObjects; // Array of voxels private short density; // Voxels density private const float borderOffset = 0.01f; // Grid offset (inside a volume) private float step; // Grid step double xlen; // Bounding box length along x double ylen; // Bounding box length along y double zlen; // Bounding box length along z public int VoxelCount; // Number of created voxel nodes private MMatrix mExclusive; // MDagPath.exclusiveMatrix of input mesh ///How to use this plugin: Load plugin and create voxel node:/// Class Constructor /// /// input mesh /// type of an algorithm used for a grid generation /// A number of voxels placed along /// the shortest dimension of a bounding box public VoxelGrid(MObject oMesh, short typeIndex, short density) { voxelType = (VoxelGrid.Type)typeIndex; this.density = density; mesh_dpath = MDagPath.getAPathTo(oMesh); mExclusive = mesh_dpath.exclusiveMatrix; voxelObjects = new MObjectArray(); bbox = GetBoundingBox(); // get lengths of a mesh bounding box xlen = bbox.max.x - bbox.min.x; ylen = bbox.max.y - bbox.min.y; zlen = bbox.max.z - bbox.min.z; //find which length is the minimal double minEdge = xlen; if (minEdge > ylen) minEdge = ylen; if (minEdge > zlen) minEdge = zlen; // calculate spacing between voxels step = (float)((minEdge - 2 * minEdge * borderOffset) / (this.density - 1)); // create voxels GenerateVoxels(); } ////// Generate voxels grid based on bounding box of the mesh /// and method returning true if voxel is inside mesh. /// private void GenerateVoxels() { // the second (outer) point for tracing ray MFloatPoint outerPoint = new MFloatPoint((float)(bbox.max.x * 2), (float)(bbox.max.y * 2), (float)(bbox.max.z * 2)); mfnMesh = new MFnMesh(mesh_dpath); var transformationMatrix = new MTransformationMatrix(mExclusive); var rotation = transformationMatrix.rotation; // -------------------------------------------------- // block of parameters for MFnMesh.allIntersections() // -------------------------------------------------- MFloatVector rayDirection = new MFloatVector(outerPoint); MIntArray faceIds = null; MIntArray triIds = null; const bool idsSorted = false; MSpace.Space space = MSpace.Space.kWorld; int maxParam = 999999; bool testBothDirections = false; MMeshIsectAccelParams accelParams = null; bool sortHits = true; MFloatPointArray hitPoints = new MFloatPointArray(); MFloatArray hitRayParams = new MFloatArray(); MIntArray hitFaces = new MIntArray(); // -------------------------------------------------- // create function set to work with voxels transforms MFnTransform newVoxel_mfnTransform = new MFnTransform(); MDagModifier dagModifier = new MDagModifier(); for (double x = bbox.min.x + xlen * borderOffset; x < bbox.max.x; x += step) { for (double y = bbox.min.y + ylen * borderOffset; y < bbox.max.y; y += step) { for (double z = bbox.min.z + zlen * borderOffset; z < bbox.max.z; z += step) { var point = new MPoint(x, y, z); if (voxelType == VoxelGrid.Type.local) LocalToWorldSpace(ref point); var fPoint = new MFloatPoint((float) point.x, (float) point.y, (float) point.z); hitPoints.clear(); mfnMesh.allIntersections(fPoint, rayDirection, faceIds, triIds, idsSorted, space, maxParam, testBothDirections, accelParams, sortHits, hitPoints, hitRayParams, hitFaces, null, null, null, 0.000001f); // check if number of intersections are odd // if true finish current loop if (hitPoints.length%2 == 0) continue; // if number of intersections are even // point is inside the mesh => create voxel MObject newVoxel = dagModifier.createNode("locator", MObject.kNullObj); MDagPath newVoxel_mdp = MDagPath.getAPathTo(newVoxel); voxelObjects.append(newVoxel); newVoxel_mfnTransform.setObject(newVoxel_mdp); newVoxel_mfnTransform.setTranslation(new MVector(fPoint.x, fPoint.y, fPoint.z), MSpace.Space.kWorld); if (voxelType == VoxelGrid.Type.local) newVoxel_mfnTransform.setRotation(rotation); } } } dagModifier.doIt(); // Create all generated voxels VoxelCount = (int)this.voxelObjects.length; } ////// Transform MPoint using mesh transformation matrix /// /// private void LocalToWorldSpace(ref MPoint point) { MMatrix mInclusive = mesh_dpath.inclusiveMatrix; point = point.multiply(mInclusive); } ////// Delete (MObject) voxels stored in this.voxelObjects /// Call befor grid update. /// public void DeleteVoxels() { for (int i = 0; i < voxelObjects.length; i++) { try { MGlobal.deleteNode(voxelObjects[i]); } catch (Exception) { } } voxelObjects.clear(); } ////// Return bounding box for a input mesh /// in object space or world space /// depending on VoxelGrid.Type /// ///private MBoundingBox GetBoundingBox() { MFnDagNode mesh_dagNode = new MFnDagNode(mesh_dpath); MBoundingBox bbox = mesh_dagNode.boundingBox; if (voxelType == VoxelGrid.Type.world) { bbox.transformUsing(mExclusive); } return bbox; } } }
cmds.loadPlugin('voxelGenerator.nll.dll') cmds.createNode('VoxelGenerator')create any polymesh object and connect its worldMesh to the input of created VoxelGenerator node e.g.
cmds.connectAttr('pPrismShape1.worldMesh', 'VoxelGenerator1.inputMesh')
Download Visual Studio project with the files above. Compiled in VS 2013 and tested in Maya 2014. Don't forget to add openmayacpp.dll and openmayacs.dll files to References.
No comments:
Post a Comment