aboutsummaryrefslogtreecommitdiff
path: root/mayaPlug/shaveObjExporter.cpp
diff options
context:
space:
mode:
authorBen Marsh <[email protected]>2019-10-22 09:07:59 -0400
committerBen Marsh <[email protected]>2019-10-22 09:07:59 -0400
commitbd0027e737c6512397f841c22786274ed74b927f (patch)
treef7ffbdb8f3741bb7f24635616cc189cba5cb865c /mayaPlug/shaveObjExporter.cpp
downloadshave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.tar.xz
shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.zip
Adding Shave-and-a-Haircut 9.6
Diffstat (limited to 'mayaPlug/shaveObjExporter.cpp')
-rw-r--r--mayaPlug/shaveObjExporter.cpp2054
1 files changed, 2054 insertions, 0 deletions
diff --git a/mayaPlug/shaveObjExporter.cpp b/mayaPlug/shaveObjExporter.cpp
new file mode 100644
index 0000000..c982550
--- /dev/null
+++ b/mayaPlug/shaveObjExporter.cpp
@@ -0,0 +1,2054 @@
+// Shave and a Haircut
+// (c) 2019 Epic Games
+// US Patent 6720962
+
+#include "shaveIO.h"
+
+#include "shaveHairShape.h"
+#include "shaveSDK.h"
+#include "shaveObjExporter.h"
+#include "shaveUtil.h"
+#include <maya/MDagModifier.h>
+#include <maya/MFloatMatrix.h>
+#include <maya/MFloatPointArray.h>
+#include <maya/MFnMesh.h>
+#include <maya/MFnNurbsSurface.h>
+#include <maya/MFnSingleIndexedComponent.h>
+#include <maya/MIntArray.h>
+#include <maya/MPlugArray.h>
+#include <maya/MRenderUtil.h>
+#include <maya/MUint64Array.h>
+
+#ifdef OSMac_
+#include "shaveMacCarbon.h"
+#endif
+
+
+#define MEMFILE_CHUNKSIZE 1024
+
+static const int numSamplesPerCurve = 30;
+
+
+// We've got a few globals we need to make sure are initialized.
+shaveObjTranslator::shaveObjTranslator()
+: space(MSpace::kWorld)
+{
+}
+
+
+void* shaveObjTranslator::creator()
+{
+ return new shaveObjTranslator;
+}
+
+
+//! export all of the objects we need to build hair for this shaveHairShape.
+/**
+exportThis is called by shaveHairShape and shaveRender to export geometry to
+.obj files and the WFType structure.
+*/
+MStatus shaveObjTranslator::exportThis(
+ MDataBlock& block, ShaveExportData& exportData
+)
+{
+ MFnDependencyNode nodeFn(exportData.shaveHairShape);
+ shaveHairShape* nodePtr = (shaveHairShape*)nodeFn.userNode();
+ MObjectArray tesselations;
+
+#if GET_GEOM_FROM_DATABLOCK
+ nodePtr->getGrowthGeom(block, hairObjects, hairComponents);
+ nodePtr->getCollisionGeom(block, skullObjects, skullComponents);
+#else
+ nodePtr->getGrowthList(hairSelections);
+ nodePtr->getCollisionList(skullSelections);
+#endif
+
+ int uTess = exportData.uSubdivs;
+ int vTess = exportData.vSubdivs;
+
+ int subdDepth = exportData.subdDepth;
+ int subdSamples = exportData.subdSamples;
+
+ newtopo = exportData.newtopo;
+
+ objListInit();
+ wfInit(uTess, vTess, subdDepth, subdSamples, exportData, tesselations);
+ doWFType(exportData.theObj, exportData.uvs, tesselations);
+
+ //
+ // If we detected new topology, then generate an OBJ file which the
+ // caller can pass to Shave in a SHAVExplant() call, to let it know the
+ // new geometry.
+ //
+ // However, if the caller did not pass us a filename for the OBJ file,
+ // then they don't want us to generate an OBJ file.
+ //
+ if (newtopo && (exportData.objFileName != ""))
+ {
+ bool** growthFaces = NULL;
+ bool** collisionFaces = NULL;
+
+ mtlLookupInit(growthFaces, collisionFaces);
+
+ doExport(
+ exportData.objFileName, tesselations, growthFaces, collisionFaces
+ );
+
+ mtlLookupCleanup(growthFaces, collisionFaces);
+ }
+
+ exportData.newtopo = newtopo;
+
+ tesselations.clear();
+ fullHairObjects.clear();
+ partialHairObjects.clear();
+ fullSkullObjects.clear();
+ partialSkullObjects.clear();
+#if GET_GEOM_FROM_DATABLOCK
+ partialHairComponents.clear();
+ partialSkullComponents.clear();
+#else
+ hairSelections.clear();
+ skullSelections.clear();
+#endif
+
+ return MS::kSuccess;
+}
+
+
+MStatus shaveObjTranslator::objListInit()
+{
+ MStatus st;
+
+ //
+ // Some growth or collision surfaces may encompass the entire object
+ // while others may only involve a subset of the object's faces.
+ //
+ // In the case of a subset of faces, we have to do extra processing to
+ // keep track of which faces are involved, and which are not. The
+ // first step of that is to separate the full and partial objects into
+ // separate lists.
+ //
+ st = separateOutPartialMeshes(
+#if GET_GEOM_FROM_DATABLOCK
+ hairObjects,
+ hairComponents,
+ fullHairObjects,
+ partialHairObjects,
+ partialHairComponents
+#else
+ hairSelections, fullHairObjects, partialHairObjects
+#endif
+ );
+
+ if (st)
+ {
+ st = separateOutPartialMeshes(
+#if GET_GEOM_FROM_DATABLOCK
+ skullObjects,
+ skullComponents.
+ fullSkullObjects,
+ partialSkullObjects,
+ partialSkullComponents
+#else
+ skullSelections, fullSkullObjects, partialSkullObjects
+#endif
+ );
+ }
+
+ return st;
+}
+
+
+#if GET_GEOM_FROM_DATABLOCK
+MStatus shaveObjTranslator::separateOutPartialMeshes(
+ MObjectArray& objects,
+ MObjectArray& components,
+ MObjectArray& fullObjects,
+ MObjectArray& partialMeshes,
+ MObjectArray& partialMeshComponents
+)
+{
+ fullObjects.clear();
+ partialMeshes.clear();
+ partialMeshComponents.clear();
+
+ unsigned i;
+
+ for (i = 0; i < objects.length(); i++)
+ {
+ if (!components[i].isNull() && objects[i].hasFn(MFn::kMesh))
+ {
+ partialMeshes.append(objects[i]);
+ partialMeshComponents.append(components[i]);
+ }
+ else
+ fullObjects.append(objects[i]);
+ }
+
+ return MS::kSuccess;
+}
+#else
+MStatus shaveObjTranslator::separateOutPartialMeshes(
+ MSelectionList& objectList,
+ MDagPathArray& fullObjects,
+ MSelectionList& partialMeshes
+)
+{
+ MObject component;
+ MFnDagNode nodeFn;
+ MDagPath nodePath;
+ MDagPath shapePath;
+
+ //
+ // Separate out the objects into those which are full objects, and
+ // those which are just a subset of a mesh.
+ //
+ fullObjects.clear();
+ partialMeshes.clear();
+
+ MItSelectionList iter(objectList);
+
+ for (iter.reset(); !iter.isDone(); iter.next())
+ {
+ iter.getDagPath(nodePath, component);
+
+ //
+ // 'nodePath' may be pointing to a transform, so get a path to
+ // the actual shape node beneath it.
+ //
+ shapePath = nodePath;
+ shapePath.extendToShape();
+
+ if (shapePath.hasFn(MFn::kMesh))
+ {
+ //
+ // If there's no component specified, then the entire mesh is
+ // being used.
+ //
+ if (component.isNull())
+ fullObjects.append(shapePath);
+ else
+ partialMeshes.add(nodePath, component);
+ }
+ else if (shapePath.hasFn(MFn::kNurbsCurve)
+ || shapePath.hasFn(MFn::kNurbsSurface)
+ || shapePath.hasFn(MFn::kSubdiv))
+ {
+ //
+ // We don't allow partial NURBS curves or surfaces, or
+ // partial subdivision surfaces, so they always go on the
+ // 'Full' list.
+ //
+ fullObjects.append(shapePath);
+ }
+ }
+
+ return MS::kSuccess;
+}
+#endif
+
+
+MStatus shaveObjTranslator::mtlLookupInit(
+ bool**& growthMtlTbl, bool**& collisionMtlTbl
+)
+{
+ MStatus st;
+
+ st = createMtlLookup(partialHairObjects, growthMtlTbl);
+
+ if (st) st = createMtlLookup(partialSkullObjects, collisionMtlTbl);
+
+ return st;
+}
+
+
+void shaveObjTranslator::mtlLookupCleanup(
+ bool**& growthMtlTbl, bool**& collisionMtlTbl
+)
+{
+ deleteMtlLookup(partialHairObjects, growthMtlTbl);
+ deleteMtlLookup(partialSkullObjects, collisionMtlTbl);
+}
+
+
+//
+// For a mesh, it is possible that only some of its faces are being used
+// to grow or collide with hair. So we need to build lookup tables to tell
+// use which faces those are.
+//
+MStatus shaveObjTranslator::createMtlLookup(
+ MSelectionList& objectList, bool**& lookupTbl
+)
+{
+ //
+ // Set up a lookup table for partial hair meshes.
+ //
+ unsigned int objectCount = objectList.length();
+
+ if (objectCount > 0)
+ {
+ MObject component;
+ unsigned int i;
+ int numFaces;
+ MDagPath shapePath;
+
+ lookupTbl = new bool*[objectCount];
+
+ for (i = 0; i < objectCount; i++)
+ {
+ objectList.getDagPath(i, shapePath, component);
+
+ MItMeshPolygon fullPolyIter(shapePath);
+
+ //
+ // Allocate an array of flags for all the faces.
+ //
+ numFaces = fullPolyIter.count();
+ lookupTbl[i] = new bool[numFaces];
+
+ if (lookupTbl[i] == NULL) return MS::kFailure;
+
+ //
+ // Initialize all the table entries to false, which means that
+ // those faces will use the 'default' material.
+ //
+ for (int k = 0; k < numFaces; k++)
+ lookupTbl[i][k] = false;
+
+ //
+ // For those faces listed in the component, set their table
+ // entries to true, which means that they will use the 'hair'
+ // material.
+ //
+ MItMeshPolygon partialPolyIter(shapePath, component);
+
+ for (partialPolyIter.reset();
+ !partialPolyIter.isDone();
+ partialPolyIter.next())
+ {
+ int faceID = partialPolyIter.index();
+
+ //
+ // It is possible that because of changes in the topology
+ // of the growth surface since the hair was assigned, our
+ // component list will contain faces which no longer exist.
+ // Oddly enough, the poly iterator will still return those
+ // faces to us, so we have to be careful to discard any
+ // which no longer exist.
+ //
+ if (faceID < numFaces)
+ lookupTbl[i][faceID] = true;
+ }
+ }
+ }
+
+ return MS::kSuccess;
+}
+
+
+void shaveObjTranslator::deleteMtlLookup(
+ MSelectionList& objectList, bool**& lookupTbl
+)
+{
+ if (lookupTbl != NULL)
+ {
+ unsigned int i;
+
+ for (i = 0; i < objectList.length(); i++)
+ delete [] lookupTbl[i];
+
+ delete [] lookupTbl;
+ }
+}
+
+
+//
+// Export an array of objects which are completely active. (i.e. hair grows
+// from the entire object, or the entire object can collide with hair, etc).
+//
+void shaveObjTranslator::exportFullObjects(
+ FILE* exportFile,
+#if GET_GEOM_FROM_DATABLOCK
+ MObjectArray& objects,
+#else
+ MDagPathArray& objects,
+#endif
+ MObjectArray tesselations,
+ const char* surfaceMaterial,
+ const char* curveMaterial,
+ int& objectIndex,
+ int& totalNumVerts,
+ int& totalNumFaces
+)
+{
+ int i;
+ int j;
+ int objectCount = objects.length();
+ int vertCount;
+#if GET_GEOM_FROM_DATABLOCK
+ MObject object;
+#else
+ MDagPath object;
+#endif
+ MPoint p;
+
+ for (i = 0; i < objectCount; i++)
+ {
+ unsigned int objStartVertIndex = totalNumVerts;
+
+ object = objects[i];
+
+ if (object.hasFn(MFn::kMesh))
+ {
+ //
+ // Output the vertex positions.
+ //
+ MItMeshPolygon polyIter(object);
+ MItMeshVertex vtxIter(object);
+
+ for (vtxIter.reset(); !vtxIter.isDone(); vtxIter.next())
+ {
+ p = vtxIter.position(space);
+
+ fprintf(
+ exportFile,
+ "v %f %f %f\n",
+ (float)p.x,
+ (float)p.y,
+ (float)p.z
+ );
+ }
+
+ //
+ // Output the material type.
+ //
+ fprintf(exportFile, "usemtl %s\n", surfaceMaterial);
+
+ //
+ // Output the face information.
+ //
+ for (polyIter.reset(); !polyIter.isDone(); polyIter.next() )
+ {
+ fprintf(exportFile, "f ");
+
+ vertCount = polyIter.polygonVertexCount();
+
+ for(j = 0; j < vertCount; j++)
+ {
+ fprintf(
+ exportFile,
+ "%d ",
+ objStartVertIndex + polyIter.vertexIndex(j) + 1
+ );
+ }
+
+ fprintf(exportFile, "\n");
+
+ totalNumFaces++;
+ }
+
+ totalNumVerts += vtxIter.count();
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kNurbsSurface))
+#else
+ else if (object.hasFn(MFn::kNurbsSurface)
+ || object.hasFn(MFn::kSubdiv))
+#endif
+ {
+ //
+ // Output the vertex positions of the temporary mesh which
+ // contains this surface's tesselation.
+ //
+ MItMeshVertex vtxIter(tesselations[objectIndex]);
+
+ for (vtxIter.reset(); !vtxIter.isDone(); vtxIter.next() )
+ {
+ p = vtxIter.position(space);
+
+ fprintf(
+ exportFile,
+ "v %f %f %f\n",
+ (float)p.x,
+ (float)p.y,
+ (float)p.z
+ );
+ }
+
+ //
+ // Output the material type.
+ //
+ fprintf(exportFile, "usemtl %s\n", surfaceMaterial);
+
+ //
+ // Output the face information.
+ //
+ MItMeshPolygon faceIter(tesselations[objectIndex]);
+
+ for (faceIter.reset(); !faceIter.isDone(); faceIter.next())
+ {
+ fprintf(exportFile, "f ");
+
+ vertCount = faceIter.polygonVertexCount();
+
+ for(j = 0; j < vertCount; j++)
+ {
+ fprintf(
+ exportFile,
+ "%d ",
+ objStartVertIndex + faceIter.vertexIndex(j) + 1
+ );
+ }
+
+ fprintf(exportFile, "\n");
+ totalNumFaces++;
+ }
+
+ totalNumVerts += vtxIter.count();
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kSubdiv))
+ {
+ //
+ // Output a list of all the vertices for the triangulated level
+ // 1 faces of the subd.
+ //
+ MPointArray objVerts;
+ MIntArray objVertIndices;
+ MUint64Array quadIDs;
+ unsigned int v;
+
+ shaveUtil::getSubdQuads(
+ objectPath,
+ quadIDs,
+ objVerts,
+ objVertIndices
+ );
+
+ for (v = 0; v < objVerts.length(); v++)
+ {
+ fprintf(
+ exportFile,
+ "v %f %f %f\n",
+ (float)objVerts[v].x,
+ (float)objVerts[v].y,
+ (float)objVerts[v].z
+ );
+ }
+
+ //
+ // Output the material type.
+ //
+ fprintf(exportFile, "usemtl %s\n", surfaceMaterial);
+
+ //
+ // Output the face information.
+ //
+ unsigned int numQuads = quadIDs.length();
+ unsigned int q;
+
+ v = 0;
+
+ for (q = 0; q < numQuads; q++)
+ {
+ //
+ // Each quad is split into two triangles: one using verts
+ // 0, 1 and 2, the other using 0, 2 and 3.
+ //
+ // Vertex numbering in the OBJ file begins at 1 but our
+ // internal indices are all 0-based, so we have to add one
+ // to the indices before writing them to the file.
+ //
+ fprintf(
+ exportFile,
+ "f %d %d %d\n",
+ objStartVertIndex + objVertIndices[v] + 1,
+ objStartVertIndex + objVertIndices[v+1] + 1,
+ objStartVertIndex + objVertIndices[v+2] + 1
+ );
+
+ fprintf(
+ exportFile,
+ "f %d %d %d\n",
+ objStartVertIndex + objVertIndices[v] + 1,
+ objStartVertIndex + objVertIndices[v+2] + 1,
+ objStartVertIndex + objVertIndices[v+3] + 1
+ );
+
+ v += 4;
+ }
+
+ totalNumVerts += objVerts.length();
+ totalNumFaces += numQuads * 2;
+ }
+#endif
+ else if (object.hasFn(MFn::kNurbsCurve) && (curveMaterial != NULL))
+ {
+ MFloatPointArray samples;
+
+ getCurveSamples(object, samples);
+
+ unsigned int s;
+
+ for (s = 0; s < samples.length(); s++)
+ {
+ fprintf(
+ exportFile,
+ "v %f %f %f\n",
+ samples[s].x,
+ samples[s].y,
+ samples[s].z
+ );
+ }
+
+ //
+ // Output the material type.
+ //
+ fprintf(exportFile, "usemtl spline\n");
+
+ //
+ // Output the entire curve as a single face.
+ //
+ fprintf(exportFile, "f ");
+
+ for (s = samples.length(); s > 0; s--)
+ {
+ //
+ // You may notice that in storeObjectInWFTYPE() we have a
+ // similar loop which subtracts 1 from the vertex index
+ // while here we don't. The reason is that the vertex
+ // indices in an OBJ file are 1-based, while the arrays in
+ // the WFTYPE are 0-based.
+ //
+ fprintf(exportFile, "%d ", objStartVertIndex + s);
+ }
+
+ fprintf(exportFile, "\n");
+
+ totalNumFaces++;
+ totalNumVerts += samples.length();
+ }
+
+ fprintf(exportFile, "# end of object.\n");
+ objectIndex++;
+ }
+}
+
+
+//
+// Export a list of meshes which have some active and some inactive faces.
+//
+void shaveObjTranslator::exportPartialMeshes(
+ FILE* exportFile,
+#if GET_GEOM_FROM_DATABLOCK
+ MObjectArray& meshList,
+#else
+ MSelectionList& meshList,
+#endif
+ bool** activeFaceMap,
+ const char* activeFaceMaterial,
+ int& objectIndex,
+ int& vertexOffset,
+ int& faceOffset
+)
+{
+ int objectCount = meshList.length();
+ int i;
+#if !GET_GEOM_FROM_DATABLOCK
+ MDagPath shapePath;
+#endif
+ MPoint p;
+
+ for (i = 0; i < objectCount; i++)
+ {
+#if !GET_GEOM_FROM_DATABLOCK
+ meshList.getDagPath(i, shapePath);
+#endif
+
+ //
+ // Write out the vertices.
+ //
+#if GET_GEOM_FROM_DATABLOCK
+ MItMeshVertex vtxIter(meshList[i]);
+#else
+ MItMeshVertex vtxIter(shapePath);
+#endif
+
+ for (vtxIter.reset(); !vtxIter.isDone(); vtxIter.next())
+ {
+ p = vtxIter.position(space);
+
+ fprintf(
+ exportFile, "v %f %f %f\n", (float)p.x, (float)p.y, (float)p.z
+ );
+ }
+
+ //
+ // Write out the face information.
+ //
+ bool firstTime = true;
+ bool faceIsActive = false;
+ bool prevFaceIsActive = true;
+
+ MItMeshPolygon polyIter(shapePath);
+
+ for (polyIter.reset(); !polyIter.isDone(); polyIter.next() )
+ {
+ faceIsActive = activeFaceMap[i][polyIter.index()];
+
+ if (firstTime || (faceIsActive != prevFaceIsActive))
+ {
+ fprintf(
+ exportFile,
+ "usemtl %s\n",
+ faceIsActive ? activeFaceMaterial : "default"
+ );
+
+ prevFaceIsActive = faceIsActive;
+ firstTime = false;
+ }
+
+ fprintf(exportFile, "f ");
+
+ int vertCount = polyIter.polygonVertexCount();
+
+ for (int j = 0; j < vertCount; j++)
+ {
+ fprintf(
+ exportFile, "%d ", polyIter.vertexIndex(j)+1+vertexOffset
+ );
+ }
+
+ fprintf(exportFile, "\n");
+ faceOffset++;
+ }
+
+ fprintf(exportFile, "# end of mesh.\n");
+
+ vertexOffset += vtxIter.count();
+ objectIndex++;
+ }
+}
+
+
+//
+// Export all surfaces and splines associated with the current shaveHairShape
+// into the specified object file.
+//
+MStatus shaveObjTranslator::doExport(
+ MString fileName,
+ MObjectArray tesselations,
+ bool** growthFaces,
+ bool** collisionFaces
+)
+{
+ FILE* fp = NULL;
+
+#if defined(OSMac_) && !defined(OSMac_MachO_)
+ fileName = shaveMacCarbon::makeMacFilename(fileName);
+#endif
+
+ if (fileName == "")
+ {
+ cerr << "Shave: internal error: no OBJ file supplied to doExport()."
+ << endl;
+ return MS::kFailure;
+ }
+
+ //
+ // Open the obj file for writing. Bail as gracefully as possible if
+ // this fails.
+ //
+ fp = fopen(fileName.asChar(), "w");
+
+ if (fp == NULL)
+ {
+ cerr << "Shave: doExport() could not write to '"
+#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700)
+ << fileName.asChar()
+#else
+ << fileName
+#endif
+ << "'." << endl;
+
+ return MS::kFailure;
+ }
+
+ int objectIndex = 0;
+ int faceOffset = 0;
+ int vertOffset = 0;
+
+ //
+ // Export the hair objects.
+ //
+ exportFullObjects(
+ fp,
+ fullHairObjects,
+ tesselations,
+ "hair",
+ "spline",
+ objectIndex,
+ vertOffset,
+ faceOffset
+ );
+
+ exportPartialMeshes(
+ fp,
+ partialHairObjects,
+ growthFaces,
+ "hair",
+ objectIndex,
+ vertOffset,
+ faceOffset
+ );
+
+ //
+ // Export the skull objects.
+ //
+ exportFullObjects(
+ fp,
+ fullSkullObjects,
+ tesselations,
+ "skull",
+ NULL,
+ objectIndex,
+ vertOffset,
+ faceOffset
+ );
+
+ exportPartialMeshes(
+ fp,
+ partialSkullObjects,
+ collisionFaces,
+ "skull",
+ objectIndex,
+ vertOffset,
+ faceOffset
+ );
+
+ fflush(fp);
+ fclose(fp);
+
+ return MS::kSuccess;
+}
+
+
+void shaveObjTranslator::getObjectCounts(
+#if GET_GEOM_FROM_DATABLOCK
+ MObject& object,
+#else
+ MDagPath& object,
+#endif
+ GeomType geomType,
+ int& totalNumVerts,
+ int& totalNumFaces,
+ int& totalNumFaceVerts,
+ int uTess,
+ int vTess,
+ int sDept,
+ int sSamp,
+ ShaveExportData* exportData,
+ MObjectArray& tesselations,
+ bool includeDisplacements
+)
+{
+ MObject tesselation = MObject::kNullObj;
+
+ if (exportData)
+ {
+ exportData->meshes.append(object);
+ exportData->startFaces.append(totalNumFaces);
+ exportData->startVerts.append(totalNumVerts);
+ }
+
+ if (object.hasFn(MFn::kMesh))
+ {
+ MStatus st;
+ MItMeshPolygon* faceIter = 0;
+ MItMeshVertex* vtxIter = 0;
+
+ // I used to have this inside the 'else' clause where it is used,
+ // except this was another of those cases where MObject ref
+ // counting screwed up, leaving the iterators invalid outside the
+ // else clause. So we put it here instead.
+ MObject meshDataObj;
+
+ if (includeDisplacements && shaveUtil::isSurfaceDisplaced(object))
+ {
+ tesselation = shaveUtil::getMesh(object, uTess, vTess, sDept, sSamp, true);
+ faceIter = new MItMeshPolygon(tesselation);
+ vtxIter = new MItMeshVertex(tesselation);
+ }
+ else
+ {
+#if GET_GEOM_FROM_PLUGS
+ // See comments at top of shaveObjExporter.h
+ MFnMesh meshFn(object, &st);
+ MPlug worldMeshArray = meshFn.findPlug("worldMesh", true, &st);
+ MPlug worldMeshPlug = worldMeshArray.elementByLogicalIndex(object.instanceNumber(), &st);
+
+ st = worldMeshPlug.getValue(meshDataObj);
+
+ faceIter = new MItMeshPolygon(meshDataObj, &st);
+ vtxIter = new MItMeshVertex(meshDataObj, &st);
+#else
+ faceIter = new MItMeshPolygon(object);
+ vtxIter = new MItMeshVertex(object);
+#endif
+ }
+
+ totalNumVerts += vtxIter->count();
+ totalNumFaces += faceIter->count();
+
+ for (faceIter->reset(); !faceIter->isDone(); faceIter->next() )
+ {
+ totalNumFaceVerts += faceIter->polygonVertexCount();
+ }
+
+ delete faceIter;
+ delete vtxIter;
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kNurbsSurface))
+#else
+ else if (object.hasFn(MFn::kNurbsSurface) || object.hasFn(MFn::kSubdiv))
+#endif
+ {
+ //
+ // Tesselate the surface into a temporary mesh.
+ //
+ tesselation = shaveUtil::getMesh(
+ object, uTess, vTess, sDept, sSamp, includeDisplacements
+ );
+
+ //
+ // Get the counts from the temporary mesh.
+ //
+ MItMeshPolygon faceIter(tesselation);
+ MItMeshVertex vtxIter(tesselation);
+
+ totalNumVerts += vtxIter.count();
+ totalNumFaces += faceIter.count();
+
+ for (faceIter.reset(); !faceIter.isDone(); faceIter.next() )
+ {
+ totalNumFaceVerts += faceIter.polygonVertexCount();
+ }
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kSubdiv))
+ {
+ MUint64Array quadIDs;
+ MPointArray verts;
+ MIntArray vertIndices;
+
+ shaveUtil::getSubdQuads(object, quadIDs, verts, vertIndices);
+
+ unsigned int numTriangles = quadIDs.length() * 2;
+
+ totalNumVerts += verts.length();
+ totalNumFaceVerts += numTriangles * 3;
+ totalNumFaces += numTriangles;
+ }
+#endif
+ // We can use curves as growth geometry, but we cannot collide with
+ // curves and they do not occlude.
+ //
+ else if (object.hasFn(MFn::kNurbsCurve) && (geomType == kGrowthGeom))
+ {
+ totalNumVerts += numSamplesPerCurve;
+ totalNumFaceVerts += numSamplesPerCurve;
+
+ //
+ // We treat a curve as a single face.
+ //
+ totalNumFaces += 1;
+ }
+
+ tesselations.append(tesselation);
+}
+
+
+//! initialize variables needed when we fill the WFType struct.
+/**
+Here we assign values to totalverts, totalfverts, and totalfaces so that we know
+how much memory to set aside when we go to fill the WFType struct. for this node.
+Originally it was planned that all of this would only happen once on the initial
+call to the exporter, but it turns out that because of the architecture, we end up
+calling this every time we come through the exporter. It might make sense to re-scope
+totalverts, totalfverts, and totalfaces, it makes no sense for them to be global any
+more.
+*/
+
+MStatus shaveObjTranslator::wfInit(
+ int uTess,
+ int vTess,
+ int sDept,
+ int sSamp,
+ ShaveExportData& exportData,
+ MObjectArray& tesselations
+)
+{
+ totalverts = 0;
+ totalfaces = 0;
+ totalfverts = 0;
+ MDagPath node;
+ MFnDagNode nodeFn;
+ MObject component;
+ MFnMesh meshFn;
+
+ exportData.meshes.clear();
+ exportData.startFaces.clear();
+ exportData.startVerts.clear();
+ tesselations.clear();
+
+ //
+ // In order to handle the material lookup, we need to get a count of
+ // which objects we will be exporting, and how many faces those objects
+ // have. Unfortunately, this means doing an iteration pass through the
+ // selections to get these counts. Once we know what we are dealing
+ // with, we can build the lookups on the next iteration through the
+ // selections. This first pass could be removed by doing a little
+ // extra bookkeeping during the selection process- it's not that big a
+ // deal though, since these lookups (and so this pre-process pass) only
+ // need to happen during the disk export, and so will not effect the
+ // time-critical WFTYPE creation.
+ //
+ MDagPath shapePath;
+ int objectCount;
+ int i;
+
+ objectCount = fullHairObjects.length();
+
+ for (i = 0; i < objectCount; i++)
+ {
+ getObjectCounts(
+ fullHairObjects[i],
+ kGrowthGeom,
+ totalverts,
+ totalfaces,
+ totalfverts,
+ uTess,
+ vTess,
+ sDept,
+ sSamp,
+ &exportData,
+ tesselations
+ );
+ }
+
+ objectCount = partialHairObjects.length();
+
+ for (i = 0; i < objectCount; i++)
+ {
+ partialHairObjects.getDagPath(i, shapePath);
+
+ getObjectCounts(
+ shapePath,
+ kGrowthGeom,
+ totalverts,
+ totalfaces,
+ totalfverts,
+ uTess,
+ vTess,
+ sDept,
+ sSamp,
+ &exportData,
+ tesselations
+ );
+ }
+
+ objectCount = fullSkullObjects.length();
+
+ for (i = 0; i < objectCount; i++)
+ {
+ getObjectCounts(
+ fullSkullObjects[i],
+ kCollisionGeom,
+ totalverts,
+ totalfaces,
+ totalfverts,
+ uTess,
+ vTess,
+ sDept,
+ sSamp,
+ &exportData,
+ tesselations
+ );
+ }
+
+ objectCount = partialSkullObjects.length();
+
+ for (i = 0; i < objectCount; i++)
+ {
+ partialSkullObjects.getDagPath(i, shapePath);
+
+ getObjectCounts(
+ shapePath,
+ kCollisionGeom,
+ totalverts,
+ totalfaces,
+ totalfverts,
+ uTess,
+ vTess,
+ sDept,
+ sSamp,
+ &exportData,
+ tesselations
+ );
+ }
+
+ // Add an extra element to each of these so that we can easily figure
+ // out the number of verts/faces in the last object.
+ exportData.startFaces.append(totalfaces);
+ exportData.startVerts.append(totalverts);
+
+ return MS::kSuccess;
+}
+
+
+void shaveObjTranslator::storeMeshInWFTYPE(
+ WFTYPE* wf,
+ WFTYPE* uvs,
+ MItMeshVertex& vertexIter,
+ MItMeshPolygon& faceIter,
+ int& vertexIndex,
+ int& faceIndex,
+ int& faceVertexIndex
+)
+{
+ float2 vertUV;
+ int startOfMesh = vertexIndex;
+
+ //
+ // Fill in the vertex table.
+ //
+ for (vertexIter.reset(); !vertexIter.isDone(); vertexIter.next())
+ {
+ MPoint p = vertexIter.position(space);
+
+ wf->v[vertexIndex].x = (float)p.x;
+ wf->v[vertexIndex].y = (float)p.y;
+ wf->v[vertexIndex].z = (float)p.z;
+
+ //
+ // Workaround for Maya bug #178517
+ //
+ // MItMeshVertex::getUV() will cause Maya to crash if the
+ // vertex has no UVs, so we check the UV count first.
+ //
+ int numUVs;
+ vertexIter.numUVs(numUVs);
+
+ if (numUVs > 0)
+ {
+ vertexIter.getUV(vertUV);
+// wf->uv[vertexIndex].x = (float)vertUV[0];
+// wf->uv[vertexIndex].y = (float)vertUV[1];
+// wf->uv[vertexIndex].z = 0.0f;
+ }
+
+ vertexIndex++;
+ }
+
+ //
+ // Fill in the face list. For each poly, iterate through its vertex
+ // references, and write them to the WFTYPE. voff serves as an index
+ // incremented each time we finish an iteration, provides the offset so
+ // we ref. the right verts.
+ //
+ if (newtopo)
+ {
+ for (faceIter.reset(); !faceIter.isDone(); faceIter.next())
+ {
+ bool hasUVs = faceIter.hasUVs();
+ int faceVertexCount = faceIter.polygonVertexCount();
+ wf->face_start[faceIndex] = faceVertexIndex;
+ uvs->face_start[faceIndex] = faceVertexIndex;
+
+ for (int vtx=0; vtx < faceVertexCount; vtx++)
+ {
+ wf->facelist[faceVertexIndex] = faceIter.vertexIndex(vtx)
+ + startOfMesh;
+ uvs->facelist[faceVertexIndex] = faceVertexIndex;
+
+ if (hasUVs)
+ {
+ faceIter.getUV(vtx, vertUV);
+ uvs->v[faceVertexIndex].x = vertUV[0];
+ uvs->v[faceVertexIndex].y = vertUV[1];
+ wf->uv[faceVertexIndex].x = vertUV[0];
+ wf->uv[faceVertexIndex].y = vertUV[1];
+
+
+ }
+
+ faceVertexIndex++;
+ }
+
+ wf->face_end[faceIndex] = faceVertexIndex;
+ uvs->face_end[faceIndex] = faceVertexIndex;
+ faceIndex++;
+ }
+ }
+}
+
+
+void shaveObjTranslator::storeObjectInWFTYPE(
+ WFTYPE* wf,
+ WFTYPE* uvs,
+#if GET_GEOM_FROM_DATABLOCK
+ MObject& object,
+#else
+ MDagPath& object,
+#endif
+ MObject tesselation,
+ int& totalNumVerts,
+ int& totalNumFaces,
+ int& totalNumFaceVerts
+)
+{
+ if (object.hasFn(MFn::kMesh))
+ {
+#if GET_GEOM_FROM_PLUGS
+ // See comments at top of shaveObjExporter.h
+ MFnMesh meshFn(object);
+ MPlug worldMeshArray = meshFn.findPlug("worldMesh");
+ MPlug worldMeshPlug = worldMeshArray.elementByLogicalIndex(object.instanceNumber());
+ MObject meshDataObj;
+
+ worldMeshPlug.getValue(meshDataObj);
+
+ MItMeshPolygon faceIter(meshDataObj);
+ MItMeshVertex vtxIter(meshDataObj);
+#else
+ MItMeshPolygon faceIter(object);
+ MItMeshVertex vtxIter(object);
+#endif
+
+ storeMeshInWFTYPE(
+ wf,
+ uvs,
+ vtxIter,
+ faceIter,
+ totalNumVerts,
+ totalNumFaces,
+ totalNumFaceVerts
+ );
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kNurbsSurface))
+#else
+ else if (object.hasFn(MFn::kNurbsSurface) || object.hasFn(MFn::kSubdiv))
+#endif
+ {
+ MItMeshVertex vtxIter(tesselation);
+ MItMeshPolygon faceIter(tesselation);
+
+ ////// debug ////////
+ //MGlobal::displayInfo(MString("storeObjectInWFTYPE: tesselation ") + vtxIter.count() + " " + faceIter.count());
+ /////////////////////
+
+ storeMeshInWFTYPE(
+ wf,
+ uvs,
+ vtxIter,
+ faceIter,
+ totalNumVerts,
+ totalNumFaces,
+ totalNumFaceVerts
+ );
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (object.hasFn(MFn::kSubdiv))
+ {
+ MPointArray objVerts;
+ MIntArray objVertIndices;
+ MUint64Array quadIDs;
+ MDoubleArray vertUs;
+ MDoubleArray vertVs;
+
+ shaveUtil::getSubdQuads(
+ object, quadIDs, objVerts, objVertIndices, &vertUs, &vertVs
+ );
+
+ //
+ // Save the vertex positions.
+ //
+ unsigned int i;
+ unsigned int objStartVertIndex = totalNumVerts;
+
+ for (i = 0; i < objVerts.length(); i++)
+ {
+ wf->v[totalNumVerts].x = objVerts[i].x;
+ wf->v[totalNumVerts].y = objVerts[i].y;
+ wf->v[totalNumVerts].z = objVerts[i].z;
+
+ if (newtopo)
+ {
+ wf->uv[totalNumVerts].x = vertUs[i];
+ wf->uv[totalNumVerts].y = vertVs[i];
+ }
+
+ totalNumVerts++;
+ }
+
+ if (newtopo)
+ {
+ //
+ // Save the per-face lists of vertex indices. Note that each
+ // quad becomes two triangular faces.
+ //
+ unsigned int numQuads = quadIDs.length();
+ unsigned int objVertIndex = 0;
+
+ for (i = 0; i < numQuads; i++)
+ {
+ //
+ // FIRST TRIANGLE
+ //
+ // Record the index of the triangle's first vertex.
+ //
+ wf->face_start[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_start[totalNumFaces] = totalNumFaceVerts;
+
+ //
+ // Record the indices of the triangle's three face-vertices.
+ //
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex+1];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex+2];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ //
+ // Record the index of the triangle's last vertex
+ // (actually, the index of the first vertex beyond the
+ // triangle's last vertex, since that's what Shave expects).
+ //
+ wf->face_end[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_end[totalNumFaces] = totalNumFaceVerts;
+
+ totalNumFaces++;
+
+ //
+ // SECOND TRIANGLE
+ //
+ wf->face_start[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_start[totalNumFaces] = totalNumFaceVerts;
+
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex+2];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ wf->facelist[totalNumFaceVerts] =
+ objStartVertIndex + objVertIndices[objVertIndex+3];
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts] = wf->uv[wf->facelist[totalNumFaceVerts]];
+
+ totalNumFaceVerts++;
+
+ wf->face_end[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_end[totalNumFaces] = totalNumFaceVerts;
+
+ totalNumFaces++;
+
+ //
+ // Advance by 4 object verts to get to the next quad.
+ //
+ objVertIndex += 4;
+ }
+ }
+ }
+#endif
+ else if (object.hasFn(MFn::kNurbsCurve))
+ {
+ MFloatPointArray samples;
+
+ getCurveSamples(object, samples);
+
+ unsigned int x;
+ unsigned int objStartVertIndex = totalNumVerts;
+
+ for (x = 0; x < samples.length(); x++)
+ {
+ wf->v[totalNumVerts].x = samples[x].x;
+ wf->v[totalNumVerts].y = samples[x].y;
+ wf->v[totalNumVerts].z = samples[x].z;
+
+ if(newtopo)
+ {
+ wf->uv[totalNumVerts].x = 0.0f;
+ wf->uv[totalNumVerts].y = 0.0f;
+ }
+
+ totalNumVerts++;
+ }
+
+ wf->face_start[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_start[totalNumFaces] = totalNumFaceVerts;
+
+ for (x = samples.length(); x > 0; x--)
+ {
+ wf->facelist[totalNumFaceVerts] = objStartVertIndex + x - 1;
+
+ uvs->facelist[totalNumFaceVerts] = totalNumFaceVerts;
+ uvs->v[totalNumFaceVerts].x = 0.0f;
+ uvs->v[totalNumFaceVerts].y = 0.0f;
+
+ totalNumFaceVerts++;
+ }
+
+ wf->face_end[totalNumFaces] = totalNumFaceVerts;
+ uvs->face_end[totalNumFaces] = totalNumFaceVerts;
+
+ totalNumFaces++;
+ }
+}
+
+
+// Here we write the WFType. Will be careful in here, and offload any
+// preprocessing to other places so that this segment can scream as quickly
+// as possible.
+
+MStatus shaveObjTranslator::doWFType(
+ WFTYPE* wf, WFTYPE* uvs, MObjectArray tesselations
+)
+{
+ // printf("totalverts is %d, totalfaces is %d.\n",totalverts, totalfaces);
+ // set aside a little memory within the WFTYPE so we can fill in the lists.
+ if((wf->totalverts != totalverts) || (wf->totalfaces != totalfaces))
+ {
+ newtopo = true;
+
+ free_geomWF(wf);
+ free_geomWF(uvs);
+
+ wf->totalverts = totalverts;
+ wf->totalfaces = totalfaces;
+ wf->totalfverts = totalfverts;
+
+ // We need the UVs to be per-face-vert, which means no sharing
+ // of UVs, so the total number of 'verts' must be the same as the
+ // number of face-verts.
+ uvs->totalverts = uvs->totalfverts = totalfverts;
+ uvs->totalfaces = totalfaces;
+
+ alloc_geomWF(wf);
+ alloc_geomWF(uvs);
+
+ if ((wf->v == NULL)
+ || (wf->face_start == NULL)
+ || (wf->face_end == NULL)
+ || (uvs->v == NULL)
+ || (uvs->face_start == NULL)
+ || (uvs->face_end == NULL))
+ {
+ return MS::kFailure;
+ }
+ }
+
+ int objectIndex = 0;
+ int faceIndex = 0;
+ int faceVertexIndex = 0;
+ int vertexIndex = 0;
+ int objectCount;
+ int i;
+#if !GET_GEOM_FROM_DATABLOCK
+ MDagPath object;
+#endif
+
+ //
+ // Store the hair objects into myObj.
+ //
+ objectCount = fullHairObjects.length();
+
+ for(i = 0; i < objectCount; i++)
+ {
+ storeObjectInWFTYPE(
+ wf,
+ uvs,
+ fullHairObjects[i],
+ tesselations[objectIndex++],
+ vertexIndex,
+ faceIndex,
+ faceVertexIndex
+ );
+ }
+
+ objectCount = partialHairObjects.length();
+
+ for(i = 0; i < objectCount; i++)
+ {
+#if !GET_GEOM_FROM_DATABLOCK
+ partialHairObjects.getDagPath(i, object);
+#endif
+
+ storeObjectInWFTYPE(
+ wf,
+ uvs,
+#if GET_GEOM_FROM_DATABLOCK
+ partialHairObjects[i],
+#else
+ object,
+#endif
+ tesselations[objectIndex++],
+ vertexIndex,
+ faceIndex,
+ faceVertexIndex
+ );
+ }
+
+ //
+ // Store the skull objects into myObj.
+ //
+ objectCount = fullSkullObjects.length();
+
+ for(i = 0; i < objectCount; i++)
+ {
+ storeObjectInWFTYPE(
+ wf,
+ uvs,
+ fullSkullObjects[i],
+ tesselations[objectIndex++],
+ vertexIndex,
+ faceIndex,
+ faceVertexIndex
+ );
+ }
+
+ objectCount = partialSkullObjects.length();
+
+ for(i = 0; i < objectCount; i++)
+ {
+#if !GET_GEOM_FROM_DATABLOCK
+ partialSkullObjects.getDagPath(i, object);
+#endif
+
+ storeObjectInWFTYPE(
+ wf,
+ uvs,
+#if GET_GEOM_FROM_DATABLOCK
+ partialSkullObjects[i],
+#else
+ object,
+#endif
+ tesselations[objectIndex++],
+ vertexIndex,
+ faceIndex,
+ faceVertexIndex
+ );
+ }
+
+ return MS::kSuccess;
+}
+
+
+void shaveObjTranslator::exportInstanceObj(
+ MObject mesh, MObject shader, MString objFileName
+)
+{
+ FILE* fp = NULL;
+ MFnMesh meshFn(mesh);
+ int vertCount;
+ int j;
+ MPoint p;
+
+#if defined(OSMac_) && !defined(OSMac_MachO_)
+ objFileName = shaveMacCarbon::makeMacFilename(objFileName);
+#endif
+
+ if (objFileName == "")
+ {
+ cerr << "Shave: internal error: no OBJ file supplied to"
+ << " exportInstanceObj()." << endl;
+ return;
+ }
+
+ //
+ // Open the obj file for writing. Bail as gracefully as possible if
+ // this fails.
+ //
+ fp = fopen (objFileName.asChar(), "w");
+
+ if(fp == NULL)
+ {
+ cerr << "Shave: exportInstanceObj() could not write to '"
+#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700)
+ << objFileName.asChar()
+#else
+ << objFileName
+#endif
+ << "'." << endl;
+ return;
+ }
+
+ fprintf(fp, "# obj file created by shaveObjExporter.\n\n");
+
+ MItMeshPolygon polyIter(mesh);
+ MFloatPointArray pointArray;
+ meshFn.getPoints(pointArray);
+ // iterate through the vertex list, writing each vertex to the file.
+
+ MFloatArray uArray;
+ MFloatArray vArray;
+ MFloatVectorArray vertColorArray;
+ MFloatVectorArray vertTranspArray;
+ MFloatMatrix camMatrix;
+ meshFn.getUVs(uArray, vArray);
+
+ MString texturePlugName(getShaderTexture(shader));
+ int vertn;
+
+ for(vertn = 0; vertn < (int)pointArray.length(); vertn++)
+ {
+ fprintf(fp,"v %f %f %f\n", pointArray[vertn].x, pointArray[vertn].y, pointArray[vertn].z);
+ }
+
+ //
+ // If the instance's shader doesn't have a texture driving its colour,
+ // then we'll let shave determine the colour for itself using the root,
+ // tip, etc, parameters.
+ //
+ if (texturePlugName == "")
+ {
+ fprintf(fp, "usemtl default\n\n");
+ }
+ else
+ {
+ MRenderUtil::sampleShadingNetwork(
+ texturePlugName, //plug to sample
+ (int)pointArray.length(), //number of samples
+ false, //shadows
+ false, //reuse shad maps
+ camMatrix, //camMatrix
+ &pointArray, //points
+ &uArray, //u coords
+ &vArray, //v coords
+ NULL, //normals
+ &pointArray, //ref points
+ NULL, //u tangents
+ NULL, //v tangents
+ NULL, //filter size
+ vertColorArray, //out color
+ vertTranspArray //out transparency
+ );
+
+ for(int vertn = 0; vertn < (int)vertColorArray.length(); vertn++)
+ {
+ fprintf(fp,"#vc %f %f %f\n", vertColorArray[vertn].x*255, vertColorArray[vertn].y*255, vertColorArray[vertn].z*255);
+ }
+
+ fprintf(fp, "usemtl colorlock\n\n");
+ }
+
+ //
+ // Output the list of verts in each face.
+ //
+ for (polyIter.reset(); !polyIter.isDone(); polyIter.next() )
+ {
+ fprintf(fp, "f ");
+ vertCount = polyIter.polygonVertexCount();
+ for(j = 0; j < vertCount; j++)
+ {
+ fprintf(fp, "%d ",polyIter.vertexIndex(j)+1);
+ }
+ fprintf(fp, "\n");
+ }
+
+ //
+ // Output the list of normals for each face's verts.
+ //
+ MVector norm;
+
+ for (polyIter.reset(); !polyIter.isDone(); polyIter.next() )
+ {
+ fprintf(fp, "n");
+ vertCount = polyIter.polygonVertexCount();
+ for(j = 0; j < vertCount; j++)
+ {
+ polyIter.getNormal(j, norm);
+ fprintf(fp, " %f %f %f", norm.x, norm.y, norm.z);
+ }
+ fprintf(fp, "\n");
+ }
+
+ fprintf(fp, "# end of obj file.\n\n");
+
+ fflush(fp);
+ fclose(fp);
+
+ return;
+}
+
+
+MStatus shaveObjTranslator::exportOcclusion(
+ MDagPathArray& objects, WFTYPE * memObj
+)
+{
+ MDagPath transformPath;
+ MDagPath shapePath;
+ unsigned int i;
+ unsigned int numObjects = objects.length();
+ MObjectArray tesselations;
+
+ //
+ // Before we can allocate the vertex arrays in the WFTYPE, we need to
+ // know how many vertices we have. So let's count them.
+ //
+ free_geomWF(memObj);
+
+ totalverts = 0;
+ totalfaces = 0;
+ totalfverts = 0;
+
+ for (i = 0; i < numObjects; i++)
+ {
+ shapePath = objects[i];
+ shapePath.extendToShape();
+
+ //
+ // Passing in -1 for the tesselation values will cause the object's
+ // own tesselation values to be used.
+ //
+ getObjectCounts(
+ shapePath,
+ kOcclusionGeom,
+ totalverts,
+ totalfaces,
+ totalfverts,
+ -1,
+ -1,
+ 1,
+ 1,
+ NULL,
+ tesselations,
+ true
+ );
+ }
+
+ //
+ // Allocate whatever space we need to store all the vertices.
+ //
+ memObj->totalverts = totalverts;
+ memObj->totalfaces = totalfaces;
+ memObj->totalfverts = totalfverts;
+
+ alloc_geomWF(memObj);
+
+ if ((memObj->v == NULL)
+ || (memObj->face_start == NULL)
+ || (memObj->face_end == NULL))
+ {
+ tesselations.clear();
+
+ return MS::kFailure;
+ }
+
+ //
+ // Now we can actually store the vertices into the WFTYPE.
+ //
+ int globalFaceIndex = 0;
+ int globalFaceVtxIndex = 0;
+ int globalVtxIndex = 0;
+ int objStartVertex = 0;
+
+ for (i = 0; i < numObjects; i++)
+ {
+ objStartVertex = globalVtxIndex;
+
+ shapePath = objects[i];
+ shapePath.extendToShape();
+ transformPath = shapePath;
+ transformPath.pop();
+
+ if(shapePath.hasFn(MFn::kMesh))
+ {
+ MItMeshPolygon* polyIter = 0;
+ MItMeshVertex* vtxIter = 0;
+
+ if (tesselations[i].isNull())
+ {
+ polyIter = new MItMeshPolygon(shapePath);
+ vtxIter = new MItMeshVertex(shapePath);
+ }
+ else
+ {
+ polyIter = new MItMeshPolygon(tesselations[i]);
+ vtxIter = new MItMeshVertex(tesselations[i]);
+ }
+
+ //
+ // Store the vertices into the WFTYPE object which was passed
+ // to us.
+ //
+ for (vtxIter->reset(); !vtxIter->isDone(); vtxIter->next())
+ {
+ MPoint p = vtxIter->position (space);
+
+ memObj->v[globalVtxIndex].x = (float) p.x;
+ memObj->v[globalVtxIndex].y = (float) p.y;
+ memObj->v[globalVtxIndex].z = (float) p.z;
+ globalVtxIndex++;
+ }
+
+ //
+ // Store the face-vertices into the WFTYPE object.
+ //
+ for (polyIter->reset(); !polyIter->isDone(); polyIter->next())
+ {
+ int polyVertexCount = polyIter->polygonVertexCount();
+
+ memObj->face_start[globalFaceIndex] = globalFaceVtxIndex;
+
+ for (int vtx=0; vtx < polyVertexCount; vtx++)
+ {
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + polyIter->vertexIndex(vtx);
+ }
+
+ memObj->face_end[globalFaceIndex++] = globalFaceVtxIndex;
+ }
+
+ delete polyIter;
+ delete vtxIter;
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (shapePath.hasFn(MFn::kNurbsSurface))
+#else
+ else if (shapePath.hasFn(MFn::kNurbsSurface)
+ || shapePath.hasFn(MFn::kSubdiv))
+#endif
+ {
+ //
+ // Store the vertices into the WFTYPE object which was passed
+ // to us.
+ //
+ MItMeshVertex vtxIter(tesselations[i]);
+
+ for (vtxIter.reset(); !vtxIter.isDone(); vtxIter.next())
+ {
+ MPoint p = vtxIter.position(space);
+ memObj->v[globalVtxIndex].x = (float)p.x;
+ memObj->v[globalVtxIndex].y = (float)p.y;
+ memObj->v[globalVtxIndex].z = (float)p.z;
+ globalVtxIndex++;
+ }
+
+ //
+ // Store the face-vertices into the WFTYPE object.
+ //
+ MItMeshPolygon polyIter(tesselations[i]);
+
+ for (polyIter.reset(); !polyIter.isDone(); polyIter.next())
+ {
+ int polyVertexCount = polyIter.polygonVertexCount();
+
+ memObj->face_start[globalFaceIndex] = globalFaceVtxIndex;
+
+ for (int vtx=0; vtx < polyVertexCount; vtx++)
+ {
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + polyIter.vertexIndex(vtx);
+ }
+
+ memObj->face_end[globalFaceIndex++] = globalFaceVtxIndex;
+ }
+ }
+#ifdef EVALUATE_POSITION_FIXED
+ else if (shapePath.hasFn(MFn::kSubdiv))
+ {
+ MPointArray objVerts;
+ MIntArray objVertIndices;
+ MUint64Array quadIDs;
+
+ shaveUtil::getSubdQuads(
+ shapePath, quadIDs, objVerts, objVertIndices
+ );
+
+ //
+ // Save the vertex positions.
+ //
+ unsigned int j;
+
+ for (j = 0; j < objVerts.length(); j++)
+ {
+ memObj->v[globalVtxIndex].x = objVerts[j].x;
+ memObj->v[globalVtxIndex].y = objVerts[j].y;
+ memObj->v[globalVtxIndex].z = objVerts[j].z;
+
+ globalVtxIndex++;
+ }
+
+ //
+ // Save the per-face lists of vertex indices. Note that each
+ // quad becomes two triangles.
+ //
+ unsigned int numQuads = quadIDs.length();
+ unsigned int objVertIndex = 0;
+
+ for (j = 0; j < numQuads; j++)
+ {
+ //
+ // Record the index of the first triangle's first vertex.
+ //
+ memObj->face_start[globalFaceIndex] = globalFaceVtxIndex;
+
+ //
+ // Record the indices of the first triangle's 3 vertices.
+ //
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex];
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex+1];
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex+2];
+
+ //
+ // Record the index of the first vertex beyond the first
+ // triangle's last vertex.
+ //
+ memObj->face_end[globalFaceIndex++] = globalFaceVtxIndex;
+
+ //
+ // Record the index of the second triangle's first vertex.
+ //
+ memObj->face_start[globalFaceIndex] = globalFaceVtxIndex;
+
+ //
+ // Record the indices of the second triangle's 3 vertices.
+ //
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex];
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex+2];
+ memObj->facelist[globalFaceVtxIndex++] =
+ objStartVertex + objVertIndices[objVertIndex+3];
+
+ //
+ // Record the index of the first vertex beyond the second
+ // triangle's last vertex.
+ //
+ memObj->face_end[globalFaceIndex++] = globalFaceVtxIndex;
+
+ //
+ // Advance to the next quad.
+ //
+ objVertIndex += 4;
+ }
+ }
+#endif
+ }
+
+ tesselations.clear();
+
+ return MS::kSuccess;
+}
+
+
+MObject shaveObjTranslator::getConnectedShader(MFnMesh meshFn)
+{
+ MObjectArray shaderList;
+ MIntArray indecies;
+ meshFn.getConnectedShaders(0, shaderList, indecies);
+ MObject ret(shaderList[0]);
+ return ret;
+}
+
+
+void shaveObjTranslator::getCurveSamples(
+#if GET_GEOM_FROM_DATABLOCK
+ MObject curve, MFloatPointArray& samples
+#else
+ MDagPath curve, MFloatPointArray& samples
+#endif
+)
+{
+ MFnNurbsCurve curveFn(curve);
+ double maxParam;
+ double minParam;
+ double param;
+ int s;
+
+ curveFn.getKnotDomain(minParam, maxParam);
+
+ double paramIncrement = (maxParam - minParam)
+ / (double)(numSamplesPerCurve - 1);
+
+ samples.clear();
+
+ for (s = 0, param = minParam;
+ s < numSamplesPerCurve;
+ s++, param += paramIncrement)
+ {
+ if (s == numSamplesPerCurve - 1) param = maxParam;
+
+ MPoint p ;;
+ curveFn.getPointAtParam(param, p, MSpace::kWorld );
+
+ samples.append((float)p.x, (float)p.y, (float)p.z);
+ }
+}
+
+
+MString shaveObjTranslator::getShaderTexture(MObject shadingGroup)
+{
+ MString retval = "";
+
+ if (!shadingGroup.isNull())
+ {
+ MStatus status;
+ MFnDependencyNode mtlDependNode(shadingGroup, &status);
+ MPlug tmpPlug;
+ MPlugArray texPlugArray;
+ tmpPlug = mtlDependNode.findPlug("surfaceShader");
+ tmpPlug.connectedTo(texPlugArray, true, false);
+
+ MObject shader = texPlugArray[0].node();
+ texPlugArray.clear();
+ mtlDependNode.setObject(shader);
+
+ tmpPlug = mtlDependNode.findPlug("color");
+
+ if (tmpPlug.isConnected())
+ {
+ tmpPlug.connectedTo(texPlugArray, true, false);
+ retval = texPlugArray[0].name();
+ }
+ }
+
+ return retval;
+}
+