// 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 #include #include #include #include #include #include #include #include #include #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; }