// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 //Qt headers must be included before any others !!! #include #include #include #include #if QT_VERSION < 0x050000 # include # include #else # include # include #endif #include "shaveIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shaveHairShape.h" #include "ShavePerVertTexInfo.h" #include "shaveRender.h" #include "shaveSDK.h" #include "shaveTextureStore.h" #include "shaveUtil.h" #include "shaveHairUI.h" namespace { typedef struct { int pntid; float u; float v; } VertUVInfo; struct compMString { bool operator()(const MString s1, const MString s2) const { return (strcmp(s1.asChar(), s2.asChar()) < 0); } }; typedef std::vector FaceUVInfo; typedef std::vector SurfaceUVInfo; typedef std::vector UVSetUVInfo; typedef std::map UVInfoCache; }; static SurfaceUVInfo* cacheUVs( shaveHairShape* sNode, unsigned int surfIdx, const MString& uvSet, UVInfoCache& uvInfoCache ); static unsigned int* createFaceIDMap(const MIntArray& startIndices); static void makeTempColourConnections( const MObject& shavenode, MDGModifier& dgMod ); #define McheckErr(stat,m) \ if ( MS::kSuccess != stat ) { \ cerr << "ERROR: " << m << endl; \ } static bool buildingLookups = false; static int nodeCount = 0; static int* texIDMap = NULL; static unsigned texIDMapSize = 0; static NODETEXINFO* texInfoLookup = NULL; static unsigned texInfoLookupSize = 0; static std::map vertTexInfoMap; bool IsBuildingLookups() { return buildingLookups; } // Some of the Shave parameters apply to growth vertices (i.e. for use by // guides) while all the others apply to hairs. static unsigned int vertParams[] = { 8, 21, 40 }; static const unsigned int numVertParams = sizeof(vertParams) / sizeof(unsigned int); MObjectArray standinMeshes; MDagPathArray standinMeshOrigSurfaces; static inline void getHairUV( const SurfaceUVInfo& surfUVInfo, const unsigned int polyIdx, const int baryPointIDs[3], const float baryWeights[3], float& u, float& v ) { u = v = 0.0f; if (polyIdx < surfUVInfo.size()) { const FaceUVInfo& faceUVInfo = surfUVInfo[polyIdx]; size_t numVerts = faceUVInfo.size(); for (unsigned int i = 0; i < 3; ++i) { // faceUVInfo is a sparse array of VertUVInfo, with one for // each vertex of this poly. So we have to run through them to // find the one which has the same point ID as the bary coord. for (size_t j = 0; j < numVerts; ++j) { if (faceUVInfo[j].pntid == baryPointIDs[i]) { u += faceUVInfo[j].u * baryWeights[i]; v += faceUVInfo[j].v * baryWeights[i]; break; } } } } } // This evaluates the hair-root textures. These are needed during // rendering and export, but not during Live Mode. void initTexInfoLookup2( const MObjectArray& shaveHairShapes, MString meshUVSet, bool verbose, bool displayHairsOnly, MObject onlyThis ) { //if (verbose) cerr << endl << "Beginning texture pre-process." << endl; #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("initTexInfoLookup2", NULL); #endif // // There is a bit of recursion here which we need to be careful with. // To build the texture lookups, we'll need to get the positions of // each hair from Shave using either SHAVEmake_a_curve() or // SHAVEmake_a_curveROOT(). // // Both of those functions will call the SHAVEapply_texture() callback // to try and fill in any textured attributes for the hair. // // SHAVEapply_texture() will in turn call getTexInfoLookup() to // retrieve the shaveHairShape's texture information -- which is exactly // what we're in the process of building. // // To break the cycle, we set a flag which tells getTexInfoLookup() and // getVertInfoLookup() to return null pointers until we're done. // buildingLookups = true; MStatus status; shaveHairShape* onlyThisShape = NULL; if (onlyThis != MObject::kNullObj) { MFnDependencyNode shaveDependNode(onlyThis); onlyThisShape = (shaveHairShape*) shaveDependNode.userNode (&status); } nodeCount = (int)shaveHairShapes.length(); unsigned int numShaveIDs = (unsigned)(shaveHairShape::getMaxShaveID() + 1); // We can only reuse the existing texInfoLookup and texIDMap arrays if // we're initializing a single hair node (i.e. 'onlyThisShape' is not // NULL) and the sizes of the arrays haven't changed. Otherwise we have to // blow the old arrays away and make new ones. // bool needNewArrays = ( (onlyThisShape == NULL) || (texIDMapSize != numShaveIDs) || (nodeCount != texInfoLookupSize) || (texInfoLookup == NULL) ); if (needNewArrays) { freeTextureLookup(); } // if we have nodes (we always should if we get here) then initialize // the lookup structures. if (nodeCount > 0) { // // Allocate the top (node) level of the texture tables. // if (needNewArrays) { texInfoLookupSize = nodeCount; texInfoLookup = (NODETEXINFO*)malloc(texInfoLookupSize*sizeof(NODETEXINFO)); memset(texInfoLookup, 0, texInfoLookupSize*sizeof(NODETEXINFO)); // We need a lookup table to translate Shave node IDs into indices // into our texture tables. // texIDMapSize = numShaveIDs; texIDMap = new int[texIDMapSize]; for (int i = 0; i < (int)texIDMapSize; i++) texIDMap[i] = -1; } if (texInfoLookup == NULL) { cerr << "ERROR- COULD NOT ALLOCATE MEMORY FOR TEXTURE LOOKUP" << " STRUCTURES, PROCEEDING W/O TEXTURES." << endl; } else { for (int node = 0; node < nodeCount; node++) { // get the shaveHairShape; MFnDependencyNode shaveDependNode(shaveHairShapes[node]); shaveHairShape* sNode = (shaveHairShape*) shaveDependNode.userNode (&status); //printf("02 - hasPendingEvents: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); if (needNewArrays || (onlyThisShape == sNode)) { NODETEXINFO* nodeInfo = &texInfoLookup[node]; if (onlyThisShape != NULL) freeTextureLookup(nodeInfo); else { nodeInfo->displacement = NULL; nodeInfo->textureLookup = NULL; nodeInfo->u = NULL; nodeInfo->v = NULL; } nodeInfo->node = sNode; // // Get Shave's internal SHAVENODE structure. Note that // getting it this way will force the guides to update if // the geometry has changed, e.g. because we're on a new // frame. // SHAVENODE* hairNode = sNode->getHairNode(); int hairGroup = sNode->getHairGroup(); // // Make sure that our parameters are all up to date with // any changes the user may have made. // // %%% Shouldn't the 'getHairNode' call above already have // taken care of that? // //sNode->updateParams(); // // Add the node's Shave ID to our map. // texIDMap[sNode->getShaveID()] = node; // // Let Shave know which node our calls will be referring // to. // sNode->makeCurrent(); int numPasses = hairNode->shavep.passes[hairGroup]; nodeInfo->maxPasses = numPasses; // our lookup will return a float, indexed by // [pass][parm][hairid], here we allocate the [pass] // level, now that we know what that number is. nodeInfo->textureLookup = (float***) // malloc(1*sizeof(float**)); malloc(numPasses*sizeof(float**)); // nodeInfo->u = new float*[1]; // nodeInfo->v = new float*[1]; nodeInfo->u = new float*[numPasses]; nodeInfo->v = new float*[numPasses]; // get the haircount for this node if (displayHairsOnly) nodeInfo->count = sNode->getNumDisplayHairs(false); else nodeInfo->count = hairNode->shavep.haircount[hairGroup]*numPasses; // TODO: Why do we set these to 1 here? numPasses=1; nodeInfo->maxPasses=1; // init the textured boolean to false; for(int l = 0; l < SHAVE_NUM_PARAMS; l++) { nodeInfo->textured[l] = false; nodeInfo->maxIndex[l] = -1; } // we've got a bit of work to do to find the uv we want. // We'll need to get the selection list for this node, then // we'll need to be able to determing which mesh the // faceIndex belongs to, and then we'll need to correlate // the pointids to a local face vertid, in order to get the // UVs. Yuck. // get the texture node plug. We'll use this array plug in // the next loop to figure out where we need to do our // lookups from, and which parms are carrying textures. MDGModifier dgMod; MIntArray connectedIndexes; MPlug texPlug(shaveHairShapes[node], shaveHairShape::shaveTextureAttr); MPlugArray texPlugArray; MString currentNodeName = sNode->name(); unsigned int numGrowthSurfaces = 0; //Only need to do this if the growth type is not Spline. bool splineNode = (hairGroup == 4); if (!splineNode) { short uTess; short vTess; MPlug plug(shaveHairShapes[node], shaveHairShape::surfTessU); plug.getValue(uTess); plug.setAttribute(shaveHairShape::surfTessV); plug.getValue(vTess); short sDept; short sSamp; MPlug plug2(shaveHairShapes[node], shaveHairShape::subdTessDept); plug2.getValue(sDept); plug2.setAttribute(shaveHairShape::subdTessSamp); plug2.getValue(sSamp); tesselateGrowthSurfaces( sNode->exportData.meshes, uTess, vTess, sDept, sSamp ); numGrowthSurfaces = sNode->exportData.meshes.length(); } //printf("03 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); // // Most texture connections are made directly to elements // of the shaveHairShape's 'shaveTex' array attribute. // // However, a colour attribute is a compound, which can // be connected either as a single compound plug, or as // individual component plugs. Since the 'shaveTex' array // doesn't allow for compounds, the shaveHairShape's colour // attributes have their own separate connections. // // To make life easier on ourselves, we'd like everything // to be in the 'shaveTex' array, so let's break the // compounds up into their constituent parts and make // temporary connections to the appropriate 'shaveTex' // array elements. // makeTempColourConnections(shaveHairShapes[node], dgMod); status = dgMod.doIt(); //printf("04 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); // now we need to make sure that the plug is actually // connected to something. // Thanks, Maya. MPlug checkPlug; unsigned int pi = 0; for (pi = 0; pi < 60; pi++) // was 50 { // Filter out those parameters which apply to growth // vertices, not hairs. unsigned int j; for (j = 0; j < numVertParams; ++j) if (pi == vertParams[j]) break; if (j == numVertParams) { checkPlug = texPlug.elementByLogicalIndex(pi); if(checkPlug.isConnected()) connectedIndexes.append(pi); } } bool haveTextures = (connectedIndexes.length() > 0); //printf("05 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); // // This structure defines those few items of // information from each hair's CURVEINFO which we need // to keep track of. // struct HairBaryInfo { int UTpid; int pntid[3]; float wgt[3]; } **hairInfo = NULL; // // If we have textures, then we'll be needing the hairInfo // array. // if (haveTextures) hairInfo = new struct HairBaryInfo*[numPasses]; UVInfoCache uvInfoCache; nodeInfo->displacement = NULL; // Cache the vertex UVs so that we can later generate // hair-root UVs for use in SHAVEapply_texture(). unsigned int i; SurfaceUVInfo** uvInfo = NULL; if (!splineNode) { uvInfo = new SurfaceUVInfo*[numGrowthSurfaces]; for (i = 0; i < numGrowthSurfaces; i++) { uvInfo[i] = NULL; MDagPath surf = sNode->exportData.meshes[i]; surf.extendToShape(); #ifdef EVALUATE_POSITION_FIXED if (!surf.hasFn(MFn::kMesh)) #else if (!surf.hasFn(MFn::kMesh) && !surf.hasFn(MFn::kSubdiv)) #endif { // For non-mesh surfaces Shave uses a mesh to // approximate the surface. To get the hairs // rooted at the correct point we calculate // the 'displacement' (i.e. the difference // between the approximate mesh and the real // surface) for every hair root and cache it. if (nodeInfo->displacement == NULL) nodeInfo->displacement = new VERT*[numPasses]; // Non-meshes only have a single, default UV // parameterization. uvInfo[i] = cacheUVs(sNode, i, "", uvInfoCache); } else { uvInfo[i] = cacheUVs(sNode, i, meshUVSet, uvInfoCache); } } } //printf("06 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); // Cache the displacements and barycentric info for each hair. CURVEINFO curveInfo; unsigned int* faceToMesh = NULL; int hairnum; unsigned int meshIndex; MDagPath meshNode; int pass; float u; float v; WFTYPE wt; init_geomWF(&wt); for (pass = 0; pass < numPasses; pass++) { if (nodeInfo->displacement) nodeInfo->displacement[pass] = new VERT[nodeInfo->count]; nodeInfo->u[pass] = new float[nodeInfo->count]; nodeInfo->v[pass] = new float[nodeInfo->count]; if (haveTextures) { hairInfo[pass] = new struct HairBaryInfo[nodeInfo->count]; } for (hairnum = 0; hairnum < nodeInfo->count; hairnum++) { SHAVEmake_a_curveROOT( pass, hairGroup, hairnum, &wt, &curveInfo ); if (haveTextures) { // Save the barycentric info from curveInfo, // which we'll need later on. hairInfo[pass][hairnum].UTpid = curveInfo.UTpid; for (int i = 0; i < 3; i++) { hairInfo[pass][hairnum].pntid[i] = curveInfo.pntid[i]; hairInfo[pass][hairnum].wgt[i] = curveInfo.wgt[i]; } } // // Shave's hair number indices include killed // hairs, so we have to leave room for them in our // arrays. But we don't need to calculate their // uvs. // if (wt.totalfaces > 0) { if (splineNode) { // // Spline hair is a sheet, so there's a U // direction but no V. (Keep in mind that // we're talking about texture coords for // the *base* of the hair here, not along // the length of the hair itself.) // // Annoyingly, Maya never seems to // consider a V value of 0.0 to actually be // on the texture, so we have to nudge it // in a bit. // nodeInfo->u[pass][hairnum] = curveInfo.wgt[0]; nodeInfo->v[pass][hairnum] = 0.0001f; } else { // Create a map from Shave's UTpid to the // the mesh index, if it doesn't already // exist. if (faceToMesh == NULL) faceToMesh = createFaceIDMap(sNode->exportData.startFaces); // Get the index of the mesh that this // hair is growing on. meshIndex = faceToMesh[curveInfo.UTpid]; meshNode = sNode->exportData.meshes[meshIndex]; int normalizedPolyIdx = curveInfo.UTpid - sNode->exportData.startFaces[meshIndex]; float u = 0.0f; float v = 0.0f; getHairUV( *uvInfo[meshIndex], normalizedPolyIdx, curveInfo.pntid, curveInfo.wgt, u, v ); nodeInfo->u[pass][hairnum] = u; nodeInfo->v[pass][hairnum] = v; // Get this hair's displacement. if (nodeInfo->displacement) { getDisplacement( meshNode, curveInfo, wt.v[0], u, v, sNode->exportData.startVerts[meshIndex], nodeInfo->displacement[pass][hairnum] ); } } } else { if (nodeInfo->displacement) { nodeInfo->displacement[pass][hairnum].x = 0.0f; nodeInfo->displacement[pass][hairnum].y = 0.0f; nodeInfo->displacement[pass][hairnum].z = 0.0f; } } } } //printf("07 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); // // SHAVEmake_a_curveROOT will free up the old WFTYPE's info // on each call, but we have to free up the the final one // that it left us with. // free_geomWF(&wt); // // If we have any textured parameters then we need to // iterate through all the passes and determine the texture // information. // if (haveTextures) { if (verbose) cerr << "doing uv map preprocess... "; MObjectArray textures; unsigned int ti; // which texture? unsigned int numTextures = 0; // // Get an array of all the textures being used by this // shaveHairShape. // unsigned int i; for(i = 0; i < connectedIndexes.length(); i++) { int parmIndex; parmIndex = (int)connectedIndexes[i]; // // Get the plug which is driving this parameter. // texPlug .elementByLogicalIndex(parmIndex) .connectedTo(texPlugArray, true, false); // // Grab its node. // MObject texture = texPlugArray[0].node(); //{ // MFnDependencyNode dFn(texture); // printf("texture node name %s\n",dFn.name().asChar() );fflush(stdout); //} // // If we haven't yet seen this one, add it to the // array. // for (ti = 0; ti < numTextures; ti++) if (texture == textures[ti]) break; if (ti == numTextures) { textures.append(texture); numTextures++; } } // // Build a UV map for each texture. Each map will have // a single entry per hair, per pass. // HAIRUVS** textureUVMaps = new HAIRUVS*[numTextures]; struct HairBaryInfo* hair; for (ti = 0; ti < numTextures; ti++) { textureUVMaps[ti] = new HAIRUVS[nodeInfo->maxPasses]; // // Find the UV set used by each growth mesh for // this texture. // MStringArray uvSets; if (splineNode) { // // UV mapping for spline hair is handled // specially. // uvSets.append(""); } else { MString uvSet; for (unsigned int m = 0; m < numGrowthSurfaces; m++) { MDagPath objectPath = sNode->exportData.meshes[m]; if (!objectPath.hasFn(MFn::kMesh)) { // // This growth object is not a mesh, so // it doesn't support UV sets. // // We store a blank UV set name which // will be an indicator later on to // perform the default mapping for this // growth object. // uvSet = ""; } else { // // We have a mesh. // // Get the UV set used by this mesh for // this texture. // uvSet = sNode->getUVSet( objectPath.node(), textures[ti] ); } uvSets.append(uvSet); // Cache the uvs for this UV set. uvInfo[m] = cacheUVs(sNode, m, uvSet, uvInfoCache); } } for (pass = 0; pass < nodeInfo->maxPasses; pass++) { textureUVMaps[ti][pass].us = new float[nodeInfo->count]; textureUVMaps[ti][pass].vs = new float[nodeInfo->count]; for (hairnum = 0; hairnum < nodeInfo->count; hairnum++) { hair = &hairInfo[pass][hairnum]; if (splineNode) { // // Spline hair is a sheet, so there's a U // direction but no V. (Keep in mind that // we're talking about texture coords for // the *base* of the hair here, not along // the length of the hair itself.) // // Annoyingly, Maya never seems to // consider a V value of 0.0 to // actually be on the texture, so we // have to nudge it in a bit. // u = hair->wgt[0]; v = 0.0001f; } else { // Create a map from Shave's UTpid to the // the mesh index, if it doesn't already // exist. if (faceToMesh == NULL) faceToMesh = createFaceIDMap(sNode->exportData.startFaces); meshIndex = faceToMesh[hair->UTpid]; meshNode = sNode->exportData.meshes[meshIndex]; meshNode.extendToShape(); // If this growth surface is a not // really a mesh (i.e. it's a NURBS or // subd) then it will be using the // default UV parameterization, which // we have already stored in nodeInfo // so we can grab the value from there // rather than recalculating it here. // // Similarly, if it *is* a mesh and we're // using the same uv set as was cached // in nodeInfo, then just grab the // values from there. if (!meshNode.hasFn(MFn::kMesh) || (uvSets[meshIndex] == meshUVSet)) { u = nodeInfo->u[pass][hairnum]; v = nodeInfo->v[pass][hairnum]; } else { int normalizedPolyIdx = hair->UTpid - sNode->exportData.startFaces[meshIndex]; u = 0.0f; v = 0.0f; getHairUV( *uvInfo[meshIndex], normalizedPolyIdx, hair->pntid, hair->wgt, u, v ); } } textureUVMaps[ti][pass].us[hairnum] = u; textureUVMaps[ti][pass].vs[hairnum] = v; } } } //printf("08 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); if (verbose) { cerr << "done." << endl; cerr << "getting texture values from maya... " << endl; } int maxtex=100000; for (pass = 0; pass < nodeInfo->maxPasses; pass++) { // malloc the top level of our lookup; nodeInfo->textureLookup[pass] = (float**) calloc(SHAVE_NUM_PARAMS, sizeof(float*)); MFloatArray uArray; MFloatArray vArray; MFloatPointArray pointArray; MFloatPointArray pa; VERT pos[3]; struct HairBaryInfo* hair; // we need to to figure out the uv coords for each // of the roots for this pass, and also the point // locations for 3D texture evaluation. MFloatMatrix camMatrix; // if we store the lookups we have done then we // don't need to do them again. This will save both // time and memory for high density scenes. MStringArray doneLookups; MIntArray doneLookupsIndex; for (pi = 0; pi < connectedIndexes.length(); pi++) { int ind; int total; total=nodeInfo->count; int parmIndex = (int)connectedIndexes[pi]; texPlug .elementByLogicalIndex(parmIndex) .connectedTo(texPlugArray, true, false); MObject texture = texPlugArray[0].node(); for (ti = 0; ti < numTextures; ti++) if (textures[ti] == texture) break; nodeInfo->textureLookup[pass][parmIndex] = (float*) calloc( total, sizeof(float) ); for (ind=0;indcount;ind+=maxtex) { uArray.clear(); vArray.clear(); pointArray.clear(); int szz=total; if (total>=maxtex) szz=total-ind; if (szz>maxtex) szz=maxtex; //fprintf (stdout,"ind = %d szz= %d\n",ind,szz);fflush(stdout); for (hairnum = ind; hairnum < ind+szz; hairnum++) { hair = &hairInfo[pass][hairnum]; for (int k = 0; k < 3; k++) { pos[k] = sNode->memShaveObj .v[hair->pntid[k]]; pos[k].x *= hair->wgt[k]; pos[k].y *= hair->wgt[k]; pos[k].z *= hair->wgt[k]; } uArray.append( textureUVMaps[ti][pass].us[hairnum] ); vArray.append( textureUVMaps[ti][pass].vs[hairnum] ); pointArray.append( pos[0].x+pos[1].x+pos[2].x, pos[0].y+pos[1].y+pos[2].y, pos[0].z+pos[1].z+pos[2].z ); } // set aside a little memory for our lookup results. { { MFloatVectorArray vertColorArray; MFloatVectorArray vertTranspArray; //////debug////// //MFloatVectorArray uTang; //MFloatVectorArray vTang; //MFloatArray filter; //uTang.setLength(szz); //vTang.setLength(szz); //filter.setLength(szz); //for(unsigned int k = 0; k < szz; k++) //{ // uTang[k] = MFloatVector(1.0f,0.0f); // vTang[k] = MFloatVector(0.0f,1.0f); // filter[k] = 0.1f; //} ///////////////////////// //printf("texture name %s\n",texPlugArray[0].name().asChar() );fflush(stdout); // lets get the lookup values from the maya // shading engine. MStatus shRes = MRenderUtil::sampleShadingNetwork( texPlugArray[0].name(), //shade node name szz, //samples false, //shadows false, //reuse shad maps camMatrix, //camMatrix &pointArray, //points &uArray, //u coords &vArray, //v coords NULL, //normals &pointArray, //ref points NULL /*&uTang*/, //u tang NULL /*&vTang*/, //v tang NULL /*&filter*/, //filter size vertColorArray, //out color vertTranspArray //out transp ); //fprintf (stdout,"done with lookup\n");fflush(stdout); // we need to copy the returned values into the // lookup array our pointer references. also, // we'll store the name and parm index of this // lookup so we can reuse it if the opportunity // presents itself. nodeInfo->maxIndex[parmIndex] = (int)total; ///////// debug /////////////// //if(shRes != MStatus::kSuccess) printf(" MRenderUtil::sampleShadingNetwork failed with result %i\n",shRes); //for(int k=0; k < (int)szz; k++) //{ // printf("uv %f %f rgb %f %f %f t %f\n",uArray[k],vArray[k],vertColorArray[k].x,vertColorArray[k].y,vertColorArray[k].z,vertTranspArray[k]); //} //fflush(stdout); /////////////////////////////// for(int k=0; k < (int)szz; k++) { nodeInfo->textureLookup[pass][parmIndex][k+ind] = (float)vertColorArray[k].x; // nodeInfo->textureLookup[pass][parmIndex][k] // = (float)vertColorArray[k].x; } } } } } } //printf("09 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); if (verbose) { cerr << "done." << endl; cerr << "freeing cached curveinfos and uvlists..." << endl; } for (ti = 0; ti < numTextures; ti++) { for (pass = 0; pass < nodeInfo->maxPasses; pass++) { delete [] textureUVMaps[ti][pass].us; delete [] textureUVMaps[ti][pass].vs; } delete [] textureUVMaps[ti]; } delete [] textureUVMaps; for (pass = 0; pass < nodeInfo->maxPasses; pass++) delete [] hairInfo[pass]; delete [] hairInfo; } delete [] faceToMesh; UVInfoCache::iterator setIter; for (setIter = uvInfoCache.begin(); setIter != uvInfoCache.end(); ++setIter) { UVSetUVInfo* uvSetUVInfo = (*setIter).second; for (unsigned int ui = 0; ui < uvSetUVInfo->size(); ++ui) delete (*uvSetUVInfo)[ui]; delete uvSetUVInfo; } //printf("10 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); uvInfoCache.clear(); // we can't set the textured member true until we've taken // care of all the passes. Otherwise make a curve will fail // trying to do a lookup. for (pi = 0; pi < connectedIndexes.length(); pi++) { unsigned int pIndex = connectedIndexes[pi]; nodeInfo->textured[pIndex] = true; } // remove temporary connections that were made in the // hypergraph. not strictly necesary, but keeps thing // looking cleaner for the user. dgMod.undoIt(); // // Let the node know that its texture cache has changed. // MPlug plug( shaveHairShapes[node], shaveHairShape::textureCacheUpdatedAttr ); plug.setValue(true); } //end if(onlyThisShape != NULL && onlyThisShape == sNode) } //end for node } } // // It's now safe to serve up the texture lookup tables. // buildingLookups = false; #ifdef DO_PROFILE Profile::ProfileDump("initTexInfoLookup2 - done", NULL); #endif } // This evaluates the hair-root textures. These are needed during // rendering and export, but not during Live Mode. void initTexInfoLookup( const MObjectArray& shaveHairShapes, MString meshUVSet, bool verbose, bool displayHairsOnly ) { //if (verbose) cerr << endl << "Beginning texture pre-process." << endl; // // There is a bit of recursion here which we need to be careful with. // To build the texture lookups, we'll need to get the positions of // each hair from Shave using either SHAVEmake_a_curve() or // SHAVEmake_a_curveROOT(). // // Both of those functions will call the SHAVEapply_texture() callback // to try and fill in any textured attributes for the hair. // // SHAVEapply_texture() will in turn call getTexInfoLookup() to // retrieve the shaveHairShape's texture information -- which is exactly // what we're in the process of building. // // To break the cycle, we set a flag which tells getTexInfoLookup() and // getVertInfoLookup() to return null pointers until we're done. // buildingLookups = true; freeTextureLookup(); shaveHairShape* sNode; bool splineNode; MStringArray result; MStatus status; // get our shaveHairShapes in a selection list nodeCount = (int)shaveHairShapes.length(); // if we have nodes (we always should if we get here) then allocate // space for the lookup structures. if (nodeCount > 0) { // // We need a lookup table to translate Shave node ID's into indices // into our texture tables. // int i; texIDMapSize = (unsigned)(shaveHairShape::getMaxShaveID() + 1); texIDMap = new int[texIDMapSize]; for (i = 0; i < (int)texIDMapSize; i++) texIDMap[i] = -1; // // Allocate the top (node) level of the texture tables. // texInfoLookup = (NODETEXINFO*)malloc(nodeCount*sizeof(NODETEXINFO)); if (texInfoLookup == NULL) { cerr << "ERROR- COULD NOT ALLOCATE MEMORY FOR TEXTURE LOOKUP" << " STRUCTURES, PROCEEDING W/O TEXTURES." << endl; } else { for (int node = 0; node < nodeCount; node++) { NODETEXINFO* nodeInfo = &texInfoLookup[node]; nodeInfo->displacement = NULL; nodeInfo->textureLookup = NULL; nodeInfo->u = NULL; nodeInfo->v = NULL; // get the shaveHairShape; MFnDependencyNode shaveDependNode(shaveHairShapes[node]); sNode = (shaveHairShape*) shaveDependNode.userNode (&status); nodeInfo->node = sNode; // // Get Shave's internal SHAVENODE structure. Note that // getting it this way will force the guides to update if // the geometry has changed, e.g. because we're on a new // frame. // SHAVENODE* hairNode = sNode->getHairNode(); int hairGroup = sNode->getHairGroup(); // // Make sure that our parameters are all up to date with // any changes the user may have made. // // %%% Shouldn't the 'getHairNode' call above already have // taken care of that? // sNode->updateParams(); // // Add the node's Shave ID to our map. // texIDMap[sNode->getShaveID()] = node; // // Let Shave know which node our calls will be referring // to. // sNode->makeCurrent(); int numPasses = hairNode->shavep.passes[hairGroup]; nodeInfo->maxPasses = numPasses; // our lookup will return a float, indexed by // [pass][parm][hairid], here we allocate the [pass] // level, now that we know what that number is. nodeInfo->textureLookup = (float***) // malloc(1*sizeof(float**)); malloc(numPasses*sizeof(float**)); // nodeInfo->u = new float*[1]; // nodeInfo->v = new float*[1]; nodeInfo->u = new float*[numPasses]; nodeInfo->v = new float*[numPasses]; // get the haircount for this node if (displayHairsOnly) nodeInfo->count = sNode->getNumDisplayHairs(false); else nodeInfo->count = hairNode->shavep.haircount[hairGroup]*numPasses; numPasses=1; nodeInfo->maxPasses=1; // init the textured boolean to false; for(int l = 0; l < SHAVE_NUM_PARAMS; l++) { nodeInfo->textured[l] = false; nodeInfo->maxIndex[l] = -1; } // we've got a bit of work to do to find the uv we want. // We'll need to get the selection list for this node, then // we'll need to be able to determing which mesh the // faceIndex belongs to, and then we'll need to correlate // the pointids to a local face vertid, in order to get the // UVs. Yuck. // get the texture node plug. We'll use this array plug in // the next loop to figure out where we need to do our // lookups from, and which parms are carrying textures. MDGModifier dgMod; MIntArray connectedIndexes; MPlug texPlug(shaveHairShapes[node], shaveHairShape::shaveTextureAttr); MPlugArray texPlugArray; MString currentNodeName = sNode->name(); unsigned int numGrowthSurfaces = 0; //Only need to do this if the growth type is not Spline. splineNode = (hairGroup == 4); if (!splineNode) { short uTess; short vTess; MPlug plug(shaveHairShapes[node], shaveHairShape::surfTessU); plug.getValue(uTess); plug.setAttribute(shaveHairShape::surfTessV); plug.getValue(vTess); short sDept; short sSamp; MPlug plug2(shaveHairShapes[node], shaveHairShape::subdTessDept); plug2.getValue(sDept); plug2.setAttribute(shaveHairShape::subdTessSamp); plug2.getValue(sSamp); tesselateGrowthSurfaces( sNode->exportData.meshes, uTess, vTess, sDept, sSamp ); numGrowthSurfaces = sNode->exportData.meshes.length(); } // // Most texture connections are made directly to elements // of the shaveHairShape's 'shaveTex' array attribute. // // However, a colour attribute is a compound, which can // be connected either as a single compound plug, or as // individual component plugs. Since the 'shaveTex' array // doesn't allow for compounds, the shaveHairShape's colour // attributes have their own separate connections. // // To make life easier on ourselves, we'd like everything // to be in the 'shaveTex' array, so let's break the // compounds up into their constituent parts and make // temporary connections to the appropriate 'shaveTex' // array elements. // makeTempColourConnections(shaveHairShapes[node], dgMod); status = dgMod.doIt(); // now we need to make sure that the plug is actually // connected to something. // Thanks, Maya. MPlug checkPlug; unsigned int pi = 0; for (pi = 0; pi < 60; pi++) // was 50 { // Filter out those parameters which apply to growth // vertices, not hairs. unsigned int j; for (j = 0; j < numVertParams; ++j) if (pi == vertParams[j]) break; if (j == numVertParams) { checkPlug = texPlug.elementByLogicalIndex(pi); if(checkPlug.isConnected()) connectedIndexes.append(pi); } } bool haveTextures = (connectedIndexes.length() > 0); // // This structure defines those few items of // information from each hair's CURVEINFO which we need // to keep track of. // struct HairBaryInfo { int UTpid; int pntid[3]; float wgt[3]; } **hairInfo = NULL; // // If we have textures, then we'll be needing the hairInfo // array. // if (haveTextures) hairInfo = new struct HairBaryInfo*[numPasses]; UVInfoCache uvInfoCache; nodeInfo->displacement = NULL; // Cache the vertex UVs so that we can later generate // hair-root UVs for use in SHAVEapply_texture(). unsigned int i; SurfaceUVInfo** uvInfo = NULL; if (!splineNode) { uvInfo = new SurfaceUVInfo*[numGrowthSurfaces]; for (i = 0; i < numGrowthSurfaces; i++) { uvInfo[i] = NULL; MDagPath surf = sNode->exportData.meshes[i]; surf.extendToShape(); #ifdef EVALUATE_POSITION_FIXED if (!surf.hasFn(MFn::kMesh)) #else if (!surf.hasFn(MFn::kMesh) && !surf.hasFn(MFn::kSubdiv)) #endif { // For non-mesh surfaces Shave uses a mesh to // approximate the surface. To get the hairs // rooted at the correct point we calculate // the 'displacement' (i.e. the difference // between the approximate mesh and the real // surface) for every hair root and cache it. if (nodeInfo->displacement == NULL) nodeInfo->displacement = new VERT*[numPasses]; // Non-meshes only have a single, default UV // parameterization. uvInfo[i] = cacheUVs(sNode, i, "", uvInfoCache); } else { uvInfo[i] = cacheUVs(sNode, i, meshUVSet, uvInfoCache); } } } // Cache the displacements and barycentric info for each hair. CURVEINFO curveInfo; unsigned int* faceToMesh = NULL; int hairnum; unsigned int meshIndex; MDagPath meshNode; int pass; float u; float v; WFTYPE wt; init_geomWF(&wt); for (pass = 0; pass < numPasses; pass++) { if (nodeInfo->displacement) nodeInfo->displacement[pass] = new VERT[nodeInfo->count]; nodeInfo->u[pass] = new float[nodeInfo->count]; nodeInfo->v[pass] = new float[nodeInfo->count]; if (haveTextures) { hairInfo[pass] = new struct HairBaryInfo[nodeInfo->count]; } for (hairnum = 0; hairnum < nodeInfo->count; hairnum++) { SHAVEmake_a_curveROOT( pass, hairGroup, hairnum, &wt, &curveInfo ); if (haveTextures) { // Save the barycentric info from curveInfo, // which we'll need later on. hairInfo[pass][hairnum].UTpid = curveInfo.UTpid; for (int i = 0; i < 3; i++) { hairInfo[pass][hairnum].pntid[i] = curveInfo.pntid[i]; hairInfo[pass][hairnum].wgt[i] = curveInfo.wgt[i]; } } // // Shave's hair number indices include killed // hairs, so we have to leave room for them in our // arrays. But we don't need to calculate their // uvs. // if (wt.totalfaces > 0) { if (splineNode) { // // Spline hair is a sheet, so there's a U // direction but no V. (Keep in mind that // we're talking about texture coords for // the *base* of the hair here, not along // the length of the hair itself.) // // Annoyingly, Maya never seems to // consider a V value of 0.0 to actually be // on the texture, so we have to nudge it // in a bit. // nodeInfo->u[pass][hairnum] = curveInfo.wgt[0]; nodeInfo->v[pass][hairnum] = 0.0001f; } else { // Create a map from Shave's UTpid to the // the mesh index, if it doesn't already // exist. if (faceToMesh == NULL) faceToMesh = createFaceIDMap(sNode->exportData.startFaces); // Get the index of the mesh that this // hair is growing on. meshIndex = faceToMesh[curveInfo.UTpid]; meshNode = sNode->exportData.meshes[meshIndex]; int normalizedPolyIdx = curveInfo.UTpid - sNode->exportData.startFaces[meshIndex]; float u = 0.0f; float v = 0.0f; getHairUV( *uvInfo[meshIndex], normalizedPolyIdx, curveInfo.pntid, curveInfo.wgt, u, v ); nodeInfo->u[pass][hairnum] = u; nodeInfo->v[pass][hairnum] = v; // Get this hair's displacement. if (nodeInfo->displacement) { getDisplacement( meshNode, curveInfo, wt.v[0], u, v, sNode->exportData.startVerts[meshIndex], nodeInfo->displacement[pass][hairnum] ); } } } else { if (nodeInfo->displacement) { nodeInfo->displacement[pass][hairnum].x = 0.0f; nodeInfo->displacement[pass][hairnum].y = 0.0f; nodeInfo->displacement[pass][hairnum].z = 0.0f; } } } } // // SHAVEmake_a_curveROOT will free up the old WFTYPE's info // on each call, but we have to free up the the final one // that it left us with. // free_geomWF(&wt); // // If we have any textured parameters then we need to // iterate through all the passes and determine the texture // information. // if (haveTextures) { if (verbose) cerr << "doing uv map preprocess... "; MObjectArray textures; unsigned int ti; // which texture? unsigned int numTextures = 0; // // Get an array of all the textures being used by this // shaveHairShape. // unsigned int i; for(i = 0; i < connectedIndexes.length(); i++) { int parmIndex; parmIndex = (int)connectedIndexes[i]; // // Get the plug which is driving this parameter. // texPlug .elementByLogicalIndex(parmIndex) .connectedTo(texPlugArray, true, false); // // Grab its node. // MObject texture = texPlugArray[0].node(); //{ // MFnDependencyNode dFn(texture); // printf("texture node name %s\n",dFn.name().asChar() );fflush(stdout); //} // // If we haven't yet seen this one, add it to the // array. // for (ti = 0; ti < numTextures; ti++) if (texture == textures[ti]) break; if (ti == numTextures) { textures.append(texture); numTextures++; } } // // Build a UV map for each texture. Each map will have // a single entry per hair, per pass. // HAIRUVS** textureUVMaps = new HAIRUVS*[numTextures]; struct HairBaryInfo* hair; for (ti = 0; ti < numTextures; ti++) { textureUVMaps[ti] = new HAIRUVS[nodeInfo->maxPasses]; // // Find the UV set used by each growth mesh for // this texture. // MStringArray uvSets; if (splineNode) { // // UV mapping for spline hair is handled // specially. // uvSets.append(""); } else { MString uvSet; for (unsigned int m = 0; m < numGrowthSurfaces; m++) { MDagPath objectPath = sNode->exportData.meshes[m]; if (!objectPath.hasFn(MFn::kMesh)) { // // This growth object is not a mesh, so // it doesn't support UV sets. // // We store a blank UV set name which // will be an indicator later on to // perform the default mapping for this // growth object. // uvSet = ""; } else { // // We have a mesh. // // Get the UV set used by this mesh for // this texture. // uvSet = sNode->getUVSet( objectPath.node(), textures[ti] ); } uvSets.append(uvSet); // Cache the uvs for this UV set. uvInfo[m] = cacheUVs(sNode, m, uvSet, uvInfoCache); } } for (pass = 0; pass < nodeInfo->maxPasses; pass++) { textureUVMaps[ti][pass].us = new float[nodeInfo->count]; textureUVMaps[ti][pass].vs = new float[nodeInfo->count]; for (hairnum = 0; hairnum < nodeInfo->count; hairnum++) { hair = &hairInfo[pass][hairnum]; if (splineNode) { // // Spline hair is a sheet, so there's a U // direction but no V. (Keep in mind that // we're talking about texture coords for // the *base* of the hair here, not along // the length of the hair itself.) // // Annoyingly, Maya never seems to // consider a V value of 0.0 to // actually be on the texture, so we // have to nudge it in a bit. // u = hair->wgt[0]; v = 0.0001f; } else { // Create a map from Shave's UTpid to the // the mesh index, if it doesn't already // exist. if (faceToMesh == NULL) faceToMesh = createFaceIDMap(sNode->exportData.startFaces); meshIndex = faceToMesh[hair->UTpid]; meshNode = sNode->exportData.meshes[meshIndex]; meshNode.extendToShape(); // If this growth surface is a not // really a mesh (i.e. it's a NURBS or // subd) then it will be using the // default UV parameterization, which // we have already stored in nodeInfo // so we can grab the value from there // rather than recalculating it here. // // Similarly, if it *is* a mesh and we're // using the same uv set as was cached // in nodeInfo, then just grab the // values from there. if (!meshNode.hasFn(MFn::kMesh) || (uvSets[meshIndex] == meshUVSet)) { u = nodeInfo->u[pass][hairnum]; v = nodeInfo->v[pass][hairnum]; } else { int normalizedPolyIdx = hair->UTpid - sNode->exportData.startFaces[meshIndex]; u = 0.0f; v = 0.0f; getHairUV( *uvInfo[meshIndex], normalizedPolyIdx, hair->pntid, hair->wgt, u, v ); } } textureUVMaps[ti][pass].us[hairnum] = u; textureUVMaps[ti][pass].vs[hairnum] = v; } } } if (verbose) { cerr << "done." << endl; cerr << "getting texture values from maya... " << endl; } int maxtex=100000; for (pass = 0; pass < nodeInfo->maxPasses; pass++) { // malloc the top level of our lookup; nodeInfo->textureLookup[pass] = (float**) calloc(SHAVE_NUM_PARAMS, sizeof(float*)); MFloatArray uArray; MFloatArray vArray; MFloatPointArray pointArray; MFloatPointArray pa; VERT pos[3]; struct HairBaryInfo* hair; // we need to to figure out the uv coords for each // of the roots for this pass, and also the point // locations for 3D texture evaluation. MFloatMatrix camMatrix; // if we store the lookups we have done then we // don't need to do them again. This will save both // time and memory for high density scenes. MStringArray doneLookups; MIntArray doneLookupsIndex; for (pi = 0; pi < connectedIndexes.length(); pi++) { int ind; int total; total=nodeInfo->count; int parmIndex = (int)connectedIndexes[pi]; texPlug .elementByLogicalIndex(parmIndex) .connectedTo(texPlugArray, true, false); MObject texture = texPlugArray[0].node(); for (ti = 0; ti < numTextures; ti++) if (textures[ti] == texture) break; nodeInfo->textureLookup[pass][parmIndex] = (float*) calloc( total, sizeof(float) ); for (ind=0;indcount;ind+=maxtex) { uArray.clear(); vArray.clear(); pointArray.clear(); int szz=total; if (total>=maxtex) szz=total-ind; if (szz>maxtex) szz=maxtex; //fprintf (stdout,"ind = %d szz= %d\n",ind,szz);fflush(stdout); for (hairnum = ind; hairnum < ind+szz; hairnum++) { hair = &hairInfo[pass][hairnum]; for (int k = 0; k < 3; k++) { pos[k] = sNode->memShaveObj .v[hair->pntid[k]]; pos[k].x *= hair->wgt[k]; pos[k].y *= hair->wgt[k]; pos[k].z *= hair->wgt[k]; } uArray.append( textureUVMaps[ti][pass].us[hairnum] ); vArray.append( textureUVMaps[ti][pass].vs[hairnum] ); pointArray.append( pos[0].x+pos[1].x+pos[2].x, pos[0].y+pos[1].y+pos[2].y, pos[0].z+pos[1].z+pos[2].z ); } // set aside a little memory for our lookup results. { { MFloatVectorArray vertColorArray; MFloatVectorArray vertTranspArray; //////debug////// //MFloatVectorArray uTang; //MFloatVectorArray vTang; //MFloatArray filter; //uTang.setLength(szz); //vTang.setLength(szz); //filter.setLength(szz); //for(unsigned int k = 0; k < szz; k++) //{ // uTang[k] = MFloatVector(1.0f,0.0f); // vTang[k] = MFloatVector(0.0f,1.0f); // filter[k] = 0.1f; //} ///////////////////////// //printf("texture name %s\n",texPlugArray[0].name().asChar() );fflush(stdout); // lets get the lookup values from the maya // shading engine. MStatus shRes = MRenderUtil::sampleShadingNetwork( texPlugArray[0].name(), //shade node name szz, //samples false, //shadows false, //reuse shad maps camMatrix, //camMatrix &pointArray, //points &uArray, //u coords &vArray, //v coords NULL, //normals &pointArray, //ref points NULL /*&uTang*/, //u tang NULL /*&vTang*/, //v tang NULL /*&filter*/, //filter size vertColorArray, //out color vertTranspArray //out transp ); //fprintf (stdout,"done with lookup\n");fflush(stdout); // we need to copy the returned values into the // lookup array our pointer references. also, // we'll store the name and parm index of this // lookup so we can reuse it if the opportunity // presents itself. nodeInfo->maxIndex[parmIndex] = (int)total; ///////// debug /////////////// //if(shRes != MStatus::kSuccess) printf(" MRenderUtil::sampleShadingNetwork failed with result %i\n",shRes); //for(int k=0; k < (int)szz; k++) //{ // printf("uv %f %f rgb %f %f %f t %f\n",uArray[k],vArray[k],vertColorArray[k].x,vertColorArray[k].y,vertColorArray[k].z,vertTranspArray[k]); //} //fflush(stdout); /////////////////////////////// for(int k=0; k < (int)szz; k++) { nodeInfo->textureLookup[pass][parmIndex][k+ind] = (float)vertColorArray[k].x; // nodeInfo->textureLookup[pass][parmIndex][k] // = (float)vertColorArray[k].x; } } } } } } if (verbose) { cerr << "done." << endl; cerr << "freeing cached curveinfos and uvlists..." << endl; } for (ti = 0; ti < numTextures; ti++) { for (pass = 0; pass < nodeInfo->maxPasses; pass++) { delete [] textureUVMaps[ti][pass].us; delete [] textureUVMaps[ti][pass].vs; } delete [] textureUVMaps[ti]; } delete [] textureUVMaps; for (pass = 0; pass < nodeInfo->maxPasses; pass++) delete [] hairInfo[pass]; delete [] hairInfo; } delete [] faceToMesh; UVInfoCache::iterator setIter; for (setIter = uvInfoCache.begin(); setIter != uvInfoCache.end(); ++setIter) { UVSetUVInfo* uvSetUVInfo = (*setIter).second; for (unsigned int ui = 0; ui < uvSetUVInfo->size(); ++ui) delete (*uvSetUVInfo)[ui]; delete uvSetUVInfo; } uvInfoCache.clear(); // we can't set the textured member true until we've taken // care of all the passes. Otherwise make a curve will fail // trying to do a lookup. for (pi = 0; pi < connectedIndexes.length(); pi++) { unsigned int pIndex = connectedIndexes[pi]; nodeInfo->textured[pIndex] = true; } // remove temporary connections that were made in the // hypergraph. not strictly necesary, but keeps thing // looking cleaner for the user. dgMod.undoIt(); // // Let the node know that its texture cache has changed. // MPlug plug( shaveHairShapes[node], shaveHairShape::textureCacheUpdatedAttr ); plug.setValue(true); } } } // // It's now safe to serve up the texture lookup tables. // buildingLookups = false; //if (verbose) cerr << "done with lookups." << endl << endl; } MStatus freeTextureLookup(void) { if (texInfoLookup) { for (unsigned int node = 0; node < texInfoLookupSize; node++) { freeTextureLookup(&texInfoLookup[node]); } free(texInfoLookup); texInfoLookup = NULL; texInfoLookupSize = 0; } if (texIDMap) { delete [] texIDMap; texIDMap = NULL; texIDMapSize = 0; } return MS::kSuccess; } MStatus freeTextureLookup(NODETEXINFO* lookup) { if (lookup) { bool istextured = false; int clearPasses = lookup->maxPasses; for(int pass = 0; pass < clearPasses; pass++) { if (lookup->displacement) { delete [] lookup->displacement[pass]; lookup->displacement[pass] = NULL; } delete [] lookup->u[pass]; lookup->u[pass] = NULL; delete [] lookup->v[pass]; lookup->v[pass] = NULL; for(int parm = 0; parm < SHAVE_NUM_PARAMS; parm++) { if(lookup->textured[parm]) { free(lookup->textureLookup[pass][parm]); lookup->textureLookup[pass][parm] = NULL; istextured = true; } } if(istextured) { free(lookup->textureLookup[pass]); lookup->textureLookup[pass] = NULL; } } if (lookup->displacement) { delete [] lookup->displacement; lookup->displacement = NULL; } delete [] lookup->u; lookup->u = NULL; delete [] lookup->v; lookup->v = NULL; if (lookup->textureLookup) { free(lookup->textureLookup); lookup->textureLookup = NULL; } } return MS::kSuccess; } void getDisplacement( MDagPath& surface, CURVEINFO& ci, VERT& rootPos, float rootU, float rootV, int surfaceStartFaceIndex, VERT& displacement ) { displacement.x = 0; displacement.y = 0; displacement.z = 0; if (surface.hasFn(MFn::kNurbsSurface)) { MPoint actualPoint; MFnNurbsSurface nurbsFn(surface); MPoint shavePt(rootPos.x, rootPos.y, rootPos.z); actualPoint = nurbsFn.closestPoint( shavePt, NULL, NULL, true, MPoint_kTol, MSpace::kWorld ); displacement.x = (float)(actualPoint.x - shavePt.x); displacement.y = (float)(actualPoint.y - shavePt.y); displacement.z = (float)(actualPoint.z - shavePt.z); } #ifdef EVALUATE_POSITION_FIXED else if (surface.hasFn(MFn::kSubdiv)) { int i; int level1FaceIdx = (ci->UTpid - surfaceStartFaceIndex) / 2; MFnSubd subdFn(surface); int numLevel0Faces = subdFn.polygonCount(0); for (i = 0; i < numLevel0Faces; i++) { MUint64 level0FaceID; MUint64Array level1Faces; MFnSubdNames::toMUint64(level0FaceID, i, 0, 0, 0, 0); subdFn.polygonChildren(level0FaceID, level1Faces); if ((unsigned int)level1FaceIdx >= level1Faces.length()) { level1FaceIdx -= level1Faces.length(); } else { MPoint actualPoint; subdFn.evaluatePosition( level1Faces[level1FaceIdx], rootU, rootV, false, actualPoint ); // // 'actualPoint' is in object space. Convert it to world. // MMatrix mat = surface.inclusiveMatrix(); actualPoint = actualPoint * mat; displacement.x = actualPoint.x - rootPos.x; displacement.y = actualPoint.y - rootPos.y; displacement.z = actualPoint.z - rootPos.z; break; } } } else if (!surface.hasFn(MFn::kMesh) && !surface.hasFn(MFn::kNurbsCurve)) #else else if (!surface.hasFn(MFn::kMesh) && !surface.hasFn(MFn::kSubdiv) && !surface.hasFn(MFn::kNurbsCurve)) #endif { MGlobal::displayWarning( MString("shave: getDisplacement: got request for invalid") + " growth surface '" + surface.fullPathName() + "'." ); } } NODETEXINFO* getTexInfoLookup(unsigned shaveID) { if (buildingLookups || (texIDMap == NULL) || (shaveID < 0) || (shaveID >= texIDMapSize)) { return NULL; } int lookupID = texIDMap[shaveID]; if (lookupID == -1) return NULL; return &texInfoLookup[lookupID]; } // // Connect the source plug to the specified element of the given // shaveHairShape's texture array plug, using the supplied DGModifier. // static MStatus makeTextureConnection( MPlug sourcePlug, MObject shavenode, int texPlugIndex, MDGModifier& dgMod ) { MPlug texPlugArray(shavenode, shaveHairShape::shaveTextureAttr); dgMod.connect(sourcePlug, texPlugArray.elementByLogicalIndex(texPlugIndex)); return MS::kSuccess; } // // If a colour shaveHairShape parameter has an incoming connection, create // separate temporary connections to the 'tex' array for each of its RGB // components. // static void makeTempCompoundConnection( const MObject& shavenode, MObject& colourAttr, int startIndex, MDGModifier& dgMod ) { MPlug colourPlug(shavenode, colourAttr); if (colourPlug.isConnected()) { // // Find the node which is feeding this connection. // MPlugArray connections; colourPlug.connectedTo(connections, true, false); if(connections.length() > 0) { MFnDependencyNode texNode(connections[0].node()); // // Connect the component plugs to the shaveHairShape's 'shaveTex' // array. // colourPlug = texNode.findPlug("outColorR"); makeTextureConnection(colourPlug, shavenode, startIndex, dgMod); colourPlug = texNode.findPlug("outColorG"); makeTextureConnection(colourPlug, shavenode, startIndex+1, dgMod); colourPlug = texNode.findPlug("outColorB"); makeTextureConnection(colourPlug, shavenode, startIndex+2, dgMod); } } } static void makeTempColourConnections( const MObject& shavenode, MDGModifier& dgMod ) { // first we need to hook up the 'special' nodes- the color // ones. These nodes are handled differently than the rest // because they hook one tex up to a vector value, rather // than one float. makeTempCompoundConnection( shavenode, shaveHairShape::hairColorTexture, 9, dgMod ); makeTempCompoundConnection( shavenode, shaveHairShape::mutantHairColorTexture, 13, dgMod ); makeTempCompoundConnection( shavenode, shaveHairShape::rootHairColorTexture, 17, dgMod ); MObject colourAttrs[] = { shaveHairShape::hairColorTextureR, shaveHairShape::hairColorTextureG, shaveHairShape::hairColorTextureB, shaveHairShape::mutantHairColorTextureR, shaveHairShape::mutantHairColorTextureG, shaveHairShape::mutantHairColorTextureB, shaveHairShape::rootHairColorTextureR, shaveHairShape::rootHairColorTextureG, shaveHairShape::rootHairColorTextureB }; int attrIndices[] = { 9, 10, 11, 13, 14, 15, 17, 18, 19 }; int numAttrs = sizeof(attrIndices) / sizeof(int); int i; MPlugArray texturePlug; for (i = 0; i < numAttrs; i++) { MPlug colourPlug(shavenode, colourAttrs[i]); if (colourPlug.isConnected()) { colourPlug.connectedTo(texturePlug, true, false); makeTextureConnection( texturePlug[0], shavenode, attrIndices[i], dgMod ); } } } MStatus tesselateGrowthSurfaces( const MDagPathArray& growthSurfaces, short uTess, short vTess, short sDept, short sSamp ) { MDagPath surface; unsigned int i; standinMeshes.clear(); standinMeshOrigSurfaces.clear(); for (i = 0; i < growthSurfaces.length(); i++) { surface = growthSurfaces[i]; // // Make sure that we've got the shape and not its transform. // surface.extendToShape(); #ifdef EVALUATE_POSITION_FIXED // // If the shape is a NURBS surface, then we have to generate a // temporary mesh for it. // if (surface.hasFn(MFn::kNurbsSurface)) #else // // If the shape is a NURBS or subdivision surface, then we have to // generate a temporary mesh for it. // if (surface.hasFn(MFn::kNurbsSurface) || surface.hasFn(MFn::kSubdiv)) #endif { MObject tempMesh = shaveUtil::getMesh(surface, (int)uTess, (int)vTess, (int)sDept, (int)sSamp); standinMeshes.append(tempMesh); standinMeshOrigSurfaces.append(surface); } } return MS::kSuccess; } unsigned int* createFaceIDMap(const MIntArray& startIndices) { if (startIndices.length() == 0) return new unsigned int[1]; unsigned int numFaces = startIndices[startIndices.length()-1]; unsigned int* map = new unsigned int[numFaces]; unsigned int i; unsigned int j = 0; for (i = 1; i < startIndices.length(); i++) { while (j < (unsigned int)startIndices[i]) map[j++] = i-1; } return map; } const MString ShavePreprocTex::commandName("shaveUpdateTextures"); static const char* flVertexParams = "-vertexParams"; static const char* fsVertexParams = "-vp"; ShavePreprocTex::~ShavePreprocTex() {} void* ShavePreprocTex::createCmd() { return new ShavePreprocTex(); } MSyntax ShavePreprocTex::createSyntax() { MSyntax syntax; syntax.addFlag(fsVertexParams, flVertexParams); return syntax; } MStatus ShavePreprocTex::doIt( const MArgList& args ) { MStatus res = MS::kSuccess; MArgDatabase argdb(syntax(), args, &res); if (!res) return res; MObjectArray shaveHairShapes; shaveUtil::getShaveNodes(shaveHairShapes); if (shaveHairShapes.length() > 0) { MGlobal::displayInfo("command shaveUpdateTextures"); shaveGlobals::Globals globals; shaveGlobals::getGlobals(globals); if (argdb.isFlagSet(fsVertexParams)) initVertTexInfoLookup(shaveHairShapes); else #ifdef PER_NODE_TEXLOOKUP initTexInfoLookup2(shaveHairShapes, "", globals.verbose, true); #else initTexInfoLookup(shaveHairShapes, "", globals.verbose, true); #endif MGlobal::executeCommand("currentTime `currentTime -q`"); } return res; } // Initialize the per-vertex texture lookup for a single shaveHairShape. // // If this method is called during shaveHairShape::compute() then 'block' must // be provided and must point to the same datablock as was passed to // shaveHairShape::compute(). // void initVertTexInfoLookup(shaveHairShape* sNode, MObject node, MDataBlock* block) { MPlug checkPlug; MObject nodeObj = sNode->thisMObject(); MPlug texPlug(nodeObj, shaveHairShape::shaveTextureAttr); MPlugArray texPlugArray; unsigned int vertCount = sNode->memShaveObj.totalverts; // Get get the hair shape's per-vertex texture data. ShavePerVertTexInfo* vertInfo = &sNode->vertTexInfo; // Prepare it to accept new data. vertInfo->init(vertCount); // Store a pointer to the node's info in a map where it can be // quickly indexed by Shave ID. vertTexInfoMap[sNode->getShaveID()] = vertInfo; // Step through each of the per-vertex parameters and gather texture // data for any which are textured. unsigned int pi = 0; for (pi = 0; pi < ShavePerVertTexInfo::numParams(); pi++) { int paramID = ShavePerVertTexInfo::getParamNumber(pi); checkPlug = texPlug.elementByLogicalIndex(paramID); if (checkPlug.isConnected()) { // // Get the texture plug so we can determine what // node to sample. // texPlugArray.clear(); texPlug .elementByLogicalIndex(paramID) .connectedTo(texPlugArray, true, false); MFloatArray uArray; MFloatArray vArray; MFloatPointArray pointArray; MFloatVectorArray vertColorArray; MFloatVectorArray vertTranspArray; MFloatMatrix camMatrix; VERT wfpos; for (unsigned int vi = 0; vi < vertCount; ++vi) { //uArray.append(sNode->memShaveObj.uv[vi].x); //vArray.append(sNode->memShaveObj.uv[vi].y); wfpos = sNode->memShaveObj.v[vi]; pointArray.append( MFloatPoint(wfpos.x, wfpos.y, wfpos.z) ); //printf("x %f y %f z %f\n", (float)wfpos.x,(float)wfpos.y,(float)wfpos.z); } /// by some reason memShaveObj contains some weird UVs /// so vertex maps are not mappad same as others /// so we are trying to grab UVs from export mesh via Maya api /// if something is wrong with it, the uvs from memShaveObj are used unsigned int n = 0; //printf("num exp meshes %i\n",sNode->exportData.meshes.length());fflush(stdout); if(sNode->exportData.meshes.length() > 0) { short uTess; short vTess; short sDept; short sSamp; if (block != NULL) { uTess = block->inputValue(shaveHairShape::surfTessU).asShort(); vTess = block->inputValue(shaveHairShape::surfTessV).asShort(); sDept = block->inputValue(shaveHairShape::subdTessDept).asShort(); sSamp = block->inputValue(shaveHairShape::subdTessSamp).asShort(); } else { MPlug plug(node, shaveHairShape::surfTessU); plug.getValue(uTess); plug.setAttribute(shaveHairShape::surfTessV); plug.getValue(vTess); plug.setAttribute(shaveHairShape::subdTessDept); plug.getValue(sDept); plug.setAttribute(shaveHairShape::subdTessSamp); plug.getValue(sSamp); } //MItMeshVertex vIter(sNode->exportData.meshes[0]); for(unsigned int oo = 0; oo < sNode->exportData.meshes.length(); oo++) { MDagPath surface = sNode->exportData.meshes[oo]; surface.extendToShape(); MObject mesh = shaveUtil::getMesh(surface, (int)uTess, (int)vTess, (int)sDept, (int)sSamp); MItMeshVertex vIter(mesh); for (vIter.reset(); !vIter.isDone(); vIter.next()) { int numUVs; vIter.numUVs(numUVs); if (numUVs > 0) { float2 vertUV; vIter.getUV(vertUV); uArray.append(vertUV[0]); vArray.append(vertUV[1]); //printf("u %f v %f\n", vertUV[0], vertUV[1]); } else { uArray.append(0.0f); vArray.append(0.0f); } n++; } } } if(n != vertCount) { uArray.clear(); vArray.clear(); for (unsigned int vi = 0; vi < vertCount; ++vi) { uArray.append(sNode->memShaveObj.uv[vi].x); vArray.append(sNode->memShaveObj.uv[vi].y); } } //printf("texture %s\n",texPlugArray[0].name().asChar()); //printf("uvs stored %i vertices %i\n", n, vertCount);fflush(stdout); //////////////////////////// // Sample the texture at all vertices. MRenderUtil::sampleShadingNetwork( texPlugArray[0].name(), //shade node name uArray.length(), //samples false, //shadows false, //reuse shad maps camMatrix, //camMatrix &pointArray, //points &uArray, //u coords &vArray, //v coords NULL, //normals &pointArray, //ref points NULL, //u tang NULL, //v tang NULL, //filter size vertColorArray, //out color vertTranspArray //out transp ); // Store the sampled texture values. for (unsigned int k = 0; k < vertColorArray.length(); ++k) { vertInfo->setValue(pi, k, (float)vertColorArray[k].x); //vertInfo->setValue(pi, k, k < vertColorArray.length()/2 ? 0.0f : 1.0f); //test - OK //printf("r %f g %f b %f\n", (float)vertColorArray[k].x,(float)vertColorArray[k].y,(float)vertColorArray[k].z);fflush(stdout); } } } } // Vertex-level textures are used for dynamics runup and Live Mode. // They're not used in rendering or export. void initVertTexInfoLookup(MObjectArray& shaveHairShapes) { buildingLookups = true; unsigned int numNodes = shaveHairShapes.length(); for (unsigned int i = 0; i < numNodes; ++i) { // Get the hair shape. MFnDependencyNode shaveDependNode(shaveHairShapes[i]); shaveHairShape* node = (shaveHairShape*) shaveDependNode.userNode(); // Force the guides to update if the geometry has changed, // e.g. because we're on a new frame. node->getHairNode(); // Make sure that our parameters are all up to date with // any changes the user may have made. // // %%% Shouldn't the 'getHairNode' call above already have // taken care of that? node->updateParams(); // Let Shave know which node our calls will be referring to. node->makeCurrent(); // Update the node's per-vertex texture info. initVertTexInfoLookup(node,shaveHairShapes[i]); // // Let the node know that its texture cache has changed. // MPlug plug(shaveHairShapes[i], shaveHairShape::textureCacheUpdatedAttr); plug.setValue(true); } // It's now safe to serve up the texture lookup data. buildingLookups = false; } float applyVertTexValue(long shaveID, int vertID, int paramID, float baseValue) { if (!buildingLookups) { ShavePerVertTexInfo* vertTexInfo = vertTexInfoMap[shaveID]; if (vertTexInfo) { // Find the index of this parameter. unsigned int paramIdx; if (ShavePerVertTexInfo::getParamIdx(paramID, paramIdx)) return (baseValue * vertTexInfo->getValue(paramIdx, vertID)); } } return baseValue; } void cacheSurfaceUVs( MItMeshPolygon& polyIter, unsigned int pntidOffset, const MString& uvSet, SurfaceUVInfo* surfUVInfo ) { FaceUVInfo faceUVInfo; VertUVInfo vertUVInfo; surfUVInfo->reserve(polyIter.count ()); for (polyIter.reset(); !polyIter.isDone(); polyIter.next()) { unsigned int numFaceVerts = polyIter.polygonVertexCount(); unsigned int i; faceUVInfo.clear(); faceUVInfo.reserve(numFaceVerts); for (i = 0; i < numFaceVerts; ++i) { float2 uv = { 0.0f, 0.0f }; // If no UV set is specified, use the mesh's default set. if (uvSet.length() == 0) { polyIter.getUV(i, uv); } else { MStatus st = polyIter.getUV(i, uv, &uvSet); // If we couldn't find a UV it's probably because we have // an uninitialized UV set. So fall back to the default // mapping. if (!st) { polyIter.getUV(i, uv); } } vertUVInfo.u = uv[0]; vertUVInfo.v = uv[1]; vertUVInfo.pntid = pntidOffset + polyIter.vertexIndex(i); faceUVInfo.push_back(vertUVInfo); } surfUVInfo->push_back(faceUVInfo); } } SurfaceUVInfo* cacheUVs( shaveHairShape* sNode, unsigned int surfIdx, const MString& uvSet, UVInfoCache& uvInfoCache ) { UVSetUVInfo* uvSetUVInfo = NULL; // Do we have any data cached for this uv set? UVInfoCache::iterator it = uvInfoCache.find(uvSet); if (it == uvInfoCache.end()) { // No data for this uv set yet, so create an entry with empty // slots for each growth surface. uvSetUVInfo = new UVSetUVInfo; unsigned int numSurfaces = sNode->exportData.meshes.length(); uvSetUVInfo->reserve(numSurfaces); uvInfoCache.insert(UVInfoCache::value_type(uvSet, uvSetUVInfo)); for (unsigned int i = 0; i < numSurfaces; ++i) uvSetUVInfo->push_back(NULL); } else { uvSetUVInfo = (*it).second; // If we've already cached the UVs for this growth mesh, return // them. if ((*uvSetUVInfo)[surfIdx] != NULL) return (*uvSetUVInfo)[surfIdx]; } SurfaceUVInfo* surfUVInfo = new SurfaceUVInfo; MDagPath surfPath = sNode->exportData.meshes[surfIdx]; // If hair we are dealing with is growing from a nurbs surface, // then we cannot initialize the poly iterator with its path because // the path doesn't point to a mesh. Instead we must initialize it // with the tesselated mesh object that we created earlier. #ifdef EVALUATE_POSITION_FIXED if (surfPath.hasFn(MFn::kNurbsSurface)) #else if (surfPath.hasFn(MFn::kNurbsSurface) || surfPath.hasFn(MFn::kSubdiv)) #endif { unsigned int i; for (i = 0; i < standinMeshOrigSurfaces.length(); i++) { if (standinMeshOrigSurfaces[i] == surfPath) break; } if (i < standinMeshes.length()) { MItMeshPolygon polyIter(standinMeshes[i]); cacheSurfaceUVs( polyIter, sNode->exportData.startVerts[surfIdx], uvSet, surfUVInfo ); } } else { MItMeshPolygon polyIter(surfPath); // Meshes with history can sometimes get themselves into a state // where an MFnMesh created from the dag path (or node) reports // vertices but no faces. If that happens then we need to pull // the mesh from its output plug. if (polyIter.count() == 0) { MFnDagNode nodeFn(surfPath); MPlug plug = nodeFn.findPlug("outMesh"); MObject meshObj; plug.getValue(meshObj); MItMeshPolygon polyIter(meshObj); cacheSurfaceUVs( polyIter, sNode->exportData.startVerts[surfIdx], uvSet, surfUVInfo ); } else { cacheSurfaceUVs( polyIter, sNode->exportData.startVerts[surfIdx], uvSet, surfUVInfo ); } } (*uvSetUVInfo)[surfIdx] = surfUVInfo; return surfUVInfo; }