Monday, December 2, 2013

Voxels part 2. MAYA .NET API and MFnMesh::allIntersections.

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
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);
        }

        /// 
        /// 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);
        }
    }
}
File VoxelGrid.cs contains methods DeleteVoxels() and GenerateVoxels(). We call in these two methods on each update in order to regenerate voxels.

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


        /// 
        /// 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;
        }
    }
}
How to use this plugin: Load plugin and create voxel node:
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