// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 #include "shaveIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "shaveGlobals.h" #include "shaveHairShape.h" #include "shaveNodeCmd.h" #include "shaveSDK.h" #include "shaveUtil.h" static const char* flCollisionList = "-collisionList"; static const char* fsCollisionList = "-cl"; static const char* flCopyHair = "-copyHair"; static const char* fsCopyHair = "-cph"; static const char* flCurve = "-curve"; static const char* fsCurve = "-crv"; static const char* flDump = "-dump"; static const char* fsDump = "-d"; static const char* flGrowthList = "-growthList"; static const char* fsGrowthList = "-gl"; static const char* flGuidesToCurves = "-guidesToCurves"; static const char* fsGuidesToCurves = "-gtc"; static const char* flHairsToCurves = "-hairsToCurves"; static const char* fsHairsToCurves = "-htc"; static const char* flHairsToPolygons = "-hairsToPolygons"; static const char* fsHairsToPolygons = "-htp"; static const char* flParent = "-parent"; static const char* fsParent = "-p"; static const char* flRecomb = "-recomb"; static const char* fsRecomb = "-rc"; static const char* flRepair = "-repair"; static const char* fsRepair = "-rep"; static const char* flSplineLock = "-splineLock"; static const char* fsSplineLock = "-slk"; const MString shaveNodeCmd::commandName("shaveNode"); shaveNodeCmd::shaveNodeCmd() {} shaveNodeCmd::~shaveNodeCmd() {} void* shaveNodeCmd::createCmd() { return new shaveNodeCmd(); } MSyntax shaveNodeCmd::createSyntax() { MSyntax syntax; syntax.enableEdit(true); syntax.enableQuery(true); syntax.addFlag(fsCollisionList, flCollisionList); syntax.addFlag(fsCopyHair, flCopyHair, MSyntax::kSelectionItem); syntax.addFlag(fsCurve, flCurve, MSyntax::kSelectionItem); syntax.makeFlagMultiUse(fsCurve); syntax.addFlag(fsDump, flDump); syntax.addFlag(fsGrowthList, flGrowthList); syntax.addFlag(fsGuidesToCurves, flGuidesToCurves); syntax.addFlag(fsHairsToCurves, flHairsToCurves); syntax.addFlag(fsHairsToPolygons, flHairsToPolygons); syntax.addFlag(fsParent, flParent, MSyntax::kSelectionItem); syntax.addFlag(fsRecomb, flRecomb); syntax.addFlag(fsRepair, flRepair); syntax.addFlag(fsSplineLock, flSplineLock); syntax.setObjectType(MSyntax::kSelectionList, 1, 1); syntax.useSelectionAsDefault(false); return syntax; } MStatus shaveNodeCmd::copyInputConnections( MDGModifier& dgMod, MObject srcNode, MObject destNode, MObject attr ) { MPlugArray conns; MPlug destPlug(destNode, attr); unsigned int i; MPlug srcPlug(srcNode, attr); srcPlug.connectedTo(conns, true, false); if (conns.length() > 0) dgMod.connect(conns[0], destPlug); // // If this is a compound attribute, do its children recursively. // if (srcPlug.numChildren() > 0) { unsigned int i; for (i = 0; i < srcPlug.numChildren(); i++) { copyInputConnections( dgMod, srcNode, destNode, destPlug.child(i).attribute() ); } } // // If this is an array attribute, then do its elements as well. // Note that we don't do them recursively because this method does not // properly handle the children of an array element. At the time that // this was written, the shaveHairShape didn't have any such // attributes, so it wasn't necessary to support them. // // Maya Bug: I used to just get numConnectedElements() then step // through them using connectionByPhysicalIndex(). But // the latter continues to report connections after // they've been broken, which puts it out of synch with // numConnectedElements(). So I now run through every // element and check for connections. // unsigned int numElements = srcPlug.evaluateNumElements(); for (i = 0; i < numElements; i++) { MPlug element = srcPlug.elementByPhysicalIndex(i); element.connectedTo(conns, true, false); if (conns.length() > 0) { unsigned int logicalIndex = element.logicalIndex(); dgMod.connect( conns[0], destPlug.elementByLogicalIndex(logicalIndex) ); } } return MS::kSuccess; } MStatus shaveNodeCmd::doEditFlags( const MArgDatabase& args, shaveHairShape* nodePtr ) { MStatus st; if (args.isFlagSet(fsCopyHair)) { MSelectionList list; MObject srcNode; args.getFlagArgument(fsCopyHair, 0, list); list.getDependNode(0, srcNode); MFnDependencyNode srcFn(srcNode, &st); if (srcNode.isNull() || !st || (srcFn.typeId() != shaveHairShape::id)) { displayError( "-cp/-copyParams flag requires a shaveHairShape as" " its argument." ); return MS::kInvalidParameter; } // If any of the source node's params are being supplied by // connections, then make the same connections to the destination // node's params. MDGModifier dgMod; MObject node = nodePtr->thisMObject(); copyInputConnections( dgMod, srcNode, node, shaveHairShape::hairColorTexture ); copyInputConnections( dgMod, srcNode, node, shaveHairShape::mutantHairColorTexture ); copyInputConnections( dgMod, srcNode, node, shaveHairShape::rootHairColorTexture ); copyInputConnections( dgMod, srcNode, node, shaveHairShape::shaveTextureAttr ); dgMod.doIt(); // Get a pointer to the source node's instance. shaveHairShape* srcNodePtr = (shaveHairShape*)srcFn.userNode(); // Copy the internal hairnode. SHAVEcopy_node( &(nodePtr->hairnode), &(srcNodePtr->hairnode), nodePtr->getShaveID() ); // Copy the param values. SHAVEset_parms(&(srcNodePtr->hairnode.shavep)); SHAVEfetch_parms(&(nodePtr->hairnode.shavep)); // Update the destination's node's plugs to reflect its new // parameter values. nodePtr->updatePlugsFromParams(); } else if (args.isFlagSet(fsRepair) && !args.isFlagSet(fsCollisionList) && !args.isFlagSet(fsGrowthList)) { displayError( "-rep/-repair must be used with either -cl/-collisionList or " "-gl/-growthList." ); return MS::kInvalidParameter; } else if (args.isFlagSet(fsCollisionList)) { if (args.isFlagSet(fsRepair)) { st = nodePtr->repairGeomList( shaveHairShape::aCollisionSet, shaveHairShape::collisionObjectsGroupIDAttr ); if (!st) { displayWarning( MString("Shave: could not repair collision list for ") + nodePtr->name() + "." ); st = MS::kSuccess; } } else { MSelectionList list; MGlobal::getActiveSelectionList(list); nodePtr->setCollisionList(list); } } else if (args.isFlagSet(fsGrowthList)) { if (args.isFlagSet(fsRepair)) { st = nodePtr->repairGeomList( shaveHairShape::aGrowthSet, shaveHairShape::growthObjectsGroupIDAttr ); if (!st) { displayWarning( MString("Shave: could not repair growth list for ") + nodePtr->name() + "." ); st = MS::kSuccess; } } else { MSelectionList list; MGlobal::getActiveSelectionList(list); nodePtr->setGrowthList(list); } } else if (args.isFlagSet(fsGuidesToCurves)) { MDagModifier dm; MDagPath group = getParentTransform(args, dm, st); if (!st) return st; MObject curve; MFnNurbsCurve curveFn; SOFTGUIDE guide; int guideIndex; int hairGroup = nodePtr->getHairGroup(); int numCurveSegs = nodePtr->hairnode.shavep.segs[hairGroup]; int numCurveVerts = numCurveSegs + 1; int numGuideSegs = SHAVE_VERTS_PER_GUIDE - 1; numCurveSegs = numCurveSegs > numGuideSegs ? numGuideSegs : numCurveSegs; numCurveVerts = numCurveVerts > SHAVE_VERTS_PER_GUIDE ? SHAVE_VERTS_PER_GUIDE : numCurveVerts; double guideSegsPerHairSeg = (float)numGuideSegs/(float)(numCurveSegs); //printf("numCurveSegs %i numCurveVerts %i numGuideSets %i \n",numCurveSegs,numCurveVerts,numGuideSegs);fflush(stdout); int guideVertIndex; int i; MDoubleArray knots(numCurveVerts); MPointArray points(numCurveVerts); MVector p1, p2, p3, p4; double u; MVector vert; for (guideIndex = 0; SHAVEfetch_guide(guideIndex, &guide) != -1; guideIndex++) { MVectorArray guideVerts(SHAVE_VERTS_PER_GUIDE); for (i = 0; i < SHAVE_VERTS_PER_GUIDE; i++) { guideVerts.set( MVector( guide.guide[i].x, guide.guide[i].y, guide.guide[i].z ), i ); } // Make sure that the first curve vert lies precisely on // the first guide vert. points.set(0, guideVerts[0].x, guideVerts[0].y, guideVerts[0].z); knots.set(0.0, 0); for (i = 1; i < numCurveVerts - 1; i++) { // Interpolate the curve point along the guide. u = guideSegsPerHairSeg * (double)i; guideVertIndex = (int)u; u -= (double)guideVertIndex; p2 = guideVerts[guideVertIndex]; p3 = guideVerts[guideVertIndex+1]; if (guideVertIndex > 0) p1 = guideVerts[guideVertIndex-1]; else p1 = p2; if (guideVertIndex < numGuideSegs) p4 = guideVerts[guideVertIndex+2]; else p4 = p3; vert = interpolate(p1, p2, p3, p4, u); points.set(i, vert.x, vert.y, vert.z); knots.set((double)i, i); } // Make sure that the last curve vert lies precisely on // the last guide vert. points.set( numCurveSegs, guideVerts[numGuideSegs].x, guideVerts[numGuideSegs].y, guideVerts[numGuideSegs].z ); knots.set((double)numCurveSegs, numCurveSegs); curve = curveFn.create( points, knots, 1, MFnNurbsCurve::kOpen, false, true, MObject::kNullObj, &st ); if (!st) { displayError( commandName + ": error creating curve: " + st.errorString() ); return st; } st = dm.reparentNode(curve, group.node()); if (!st) { displayError( commandName + ": error reparenting curve under group transform: " + st.errorString() ); return st; } } if (guideIndex == 0) { displayError( commandName + ": shave node contains no guides." ); return MS::kFailure; } st = dm.doIt(); if (!st) { displayError( commandName + ": error committing changes to DAG: " + st.errorString() ); return st; } MSelectionList list; list.add(group); MGlobal::setActiveSelectionList(list); setResult(group.partialPathName()); } else if (args.isFlagSet(fsHairsToCurves)) { MDagModifier dm; MDagPath group = getParentTransform(args, dm, st); if (!st) return st; CURVEINFO ci; MObject curve; MFnNurbsCurve curveFn; bool gotACurve = false; int hairNum; int hairGroup = nodePtr->getHairGroup(); SHAVENODE* hairnode = nodePtr->getHairNode(); int numHairs = hairnode->shavep.haircount[hairGroup]; int numSegs = hairnode->shavep.segs[hairGroup]; WFTYPE wf; init_geomWF(&wf); for (hairNum = 0; hairNum < numHairs; hairNum++) { SHAVEmake_a_curve(0, hairGroup, hairNum, numSegs, &wf, &ci); if (wf.totalverts > 0) { int face; for (face = 0; face < wf.totalfaces; face++) { MDoubleArray knots; MPointArray points; int vert; for (vert = wf.face_start[face]; vert < wf.face_end[face]; vert++) { points.append( wf.v[wf.facelist[vert]].x, wf.v[wf.facelist[vert]].y, wf.v[wf.facelist[vert]].z ); knots.append((double)(vert - wf.face_start[face])); } gotACurve = true; curve = curveFn.create( points, knots, 1, MFnNurbsCurve::kOpen, false, true, MObject::kNullObj, &st ); if (!st) { displayError( commandName + ": error creating curve: " + st.errorString() ); return st; } st = dm.reparentNode(curve, group.node()); if (!st) { displayError( commandName + ": error reparenting curve under group transform: " + st.errorString() ); return st; } } } } free_geomWF(&wf); if (!gotACurve) { displayError( commandName + ": shave node contains no hairs." ); return MS::kFailure; } st = dm.doIt(); if (!st) { displayError( commandName + ": error committing changes to DAG: " + st.errorString() ); return st; } MSelectionList list; list.add(group); MGlobal::setActiveSelectionList(list); setResult(group.partialPathName()); } else if (args.isFlagSet(fsHairsToPolygons)) { MDagModifier dm; MDagPath parent = getParentTransform(args, dm, st); if (!st) return st; MObject meshNode = nodePtr->createExternalMesh(parent.node(), &st); if (!st) { displayError( commandName + ": could not create hair mesh: " + st.errorString() ); return st; } st = dm.doIt(); if (!st) { displayError( commandName + ": error committing changes to DAG: " + st.errorString() ); return st; } MSelectionList list; list.add(parent); MGlobal::setActiveSelectionList(list); setResult(parent.partialPathName()); } else if (args.isFlagSet(fsRecomb)) { MDagPathArray curves; st = getCurves(args, curves); if (st) { if (curves.length() == 0) { displayError( MString("The ") + flRecomb + " flag requires that you " + "specify one or more curves using the " + flCurve + " flag." ); st = MS::kInvalidParameter; } else { // Copy the curves into an object array. MObjectArray curveObjs; for (unsigned int i = 0; i < curves.length(); ++i) { MFnNurbsCurve curveFn(curves[i]); curveObjs.append(curveFn.object()); } // Shave wants points on the curve, not cvs. Since // Shave reparameterizes all curves to // SHAVE_VERTS_PER_GUIDE points, we might as well just // give it that many points per curve in the first // place. WFTYPE curveData; shaveUtil::sampleCurves( curveObjs, SHAVE_VERTS_PER_GUIDE, curveData ); nodePtr->recomb(curveData); free_geomWF(&curveData); } } } else if (args.isFlagSet(fsSplineLock)) { MDagPathArray curves; st = getCurves(args, curves); if (st) { if (curves.length() == 0) nodePtr->clearSplineLocks(); else nodePtr->setSplineLocks(curves); } } else { displayError("No editable flags specified."); st = MS::kInvalidParameter; } return st; } MStatus shaveNodeCmd::doIt(const MArgList& argList) { MStatus st; MArgDatabase args(syntax(), argList, &st); if (!st) return st; shaveHairShape* nodePtr = getShaveNode(args); if (nodePtr == NULL) return MS::kInvalidParameter; if (args.isQuery()) { st = doQueryFlags(args, nodePtr); } else if (args.isEdit()) { st = doEditFlags(args, nodePtr); } else { displayError(commandName + ": must specify one of -edit or -query."); st = MS::kInvalidParameter; } return st; } MStatus shaveNodeCmd::doQueryFlags( const MArgDatabase& args, shaveHairShape* nodePtr ) { MStatus st; MObject node = nodePtr->thisMObject(); if (args.isFlagSet(fsDump)) { MPlugArray connections; MTime timeVal; MFnAttribute attrFn; MString indent(" "); cout << "Warning: The -dump flag is not yet fully implemented." << endl << " Here's what we have so far..." << endl; #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cout << "Attribute values for '" << nodePtr->name().asChar() << "':" << endl; #else cout << "Attribute values for '" << nodePtr->name() << "':" << endl; #endif // // time // MPlug plug(node, shaveHairShape::timeAttr); plug.getValue(timeVal); attrFn.setObject(shaveHairShape::timeAttr); #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cout << indent.asChar() << attrFn.name().asChar() << ": " << timeVal.value() << endl; #else cout << indent << attrFn.name() << ": " << timeVal.value() << endl; #endif displayInputConnection(node, shaveHairShape::inputCurve, indent); displayInputConnection(node, shaveHairShape::inputMesh, indent); displayInputConnection(node, shaveHairShape::inputSurf, indent); displayInputConnection(node, shaveHairShape::collisionObjectsAttr, indent); } else if (args.isFlagSet(fsCollisionList)) { MSelectionList collisionList; nodePtr->getCollisionList(collisionList); MStringArray collisionObjectNames; collisionList.getSelectionStrings(collisionObjectNames); setResult(collisionObjectNames); } else if (args.isFlagSet(fsGrowthList)) { MSelectionList growthList; nodePtr->getGrowthList(growthList); MStringArray growthObjectNames; growthList.getSelectionStrings(growthObjectNames); setResult(growthObjectNames); } else { displayError("No queryable flags specified."); st = MS::kInvalidParameter; } return st; } void shaveNodeCmd::displayInputConnection( MObject node, MObject attr, MString indent ) { MPlug plug(node, attr); MFnAttribute attrFn(attr); MPlugArray connections; MString otherPlugName; if (plug.isArray()) { // // Maya Bug: I used to just get numConnectedElements() then step // through them using connectionByPhysicalIndex(). But // the latter continues to report connections after // they've been broken, which puts it out of synch with // numConnectedElements(). So I now run through every // element and check for connections. // unsigned int numElements = plug.evaluateNumElements(); unsigned int i; bool foundConnections = false; for (i = 0; i < numElements; i++) { MPlug element = plug.elementByPhysicalIndex(i); element.connectedTo(connections, true, false); if (connections.length() > 0) { MString elementName = element.name(); // // Strip the node name from the start of the element name. // #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cout << indent.asChar() << stripNode(element.name()).asChar() << ": " << connections[0].name().asChar() << endl; #else cout << indent << stripNode(element.name()) << ": " << connections[0].name() << endl; #endif foundConnections = true; } } if (!foundConnections) { #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cout << indent.asChar() << stripNode(plug.name()).asChar() << ": No Connections" << endl; #else cout << indent << stripNode(plug.name()) << ": No Connections" << endl; #endif } } else { plug.connectedTo(connections, true, false); if (connections.length() > 0) otherPlugName = connections[0].name(); else otherPlugName = "Not Connected"; #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cout << indent.asChar() << stripNode(plug.name()).asChar() << ": " << otherPlugName.asChar() << endl; #else cout << indent << stripNode(plug.name()) << ": " << otherPlugName << endl; #endif } } MStatus shaveNodeCmd::getCurves(const MArgDatabase& args, MDagPathArray& curves) { MDagPath curve; MArgList flagArgs; MSelectionList list; unsigned int numCurves = args.numberOfFlagUses(flCurve); for (unsigned int i = 0; i < numCurves; i++) { list.clear(); // // Get a DAG path to this flag's curve. // args.getFlagArgumentList(fsCurve, i, flagArgs); list.add(flagArgs.asString(0)); list.getDagPath(0, curve); curve.extendToShape(); if (!curve.isValid() || !curve.hasFn(MFn::kNurbsCurve)) { displayError( MString("'") + flagArgs.asString(0) + "' is not a NURBS curve." ); curves.clear(); return MS::kInvalidParameter; } curves.append(curve); } return MS::kSuccess; } MDagPath shaveNodeCmd::getParentTransform( const MArgDatabase& args, MDagModifier& dm, MStatus& st ) { MDagPath parent; if (args.isFlagSet(fsParent)) { MSelectionList list; args.getFlagArgument(fsParent, 0, list); list.getDagPath(0, parent); if (!parent.isValid()) { displayError( commandName + ": argument of '" + flParent + "' flag is not a transform." ); st = MS::kInvalidParameter; } } else { MObject node = dm.createNode("transform", MObject::kNullObj, &st); if (!st) { displayError( commandName + ": could not create group transform: " + st.errorString() ); } else { if (args.isFlagSet(flHairsToPolygons)) dm.renameNode(node, "shaveHairMesh#"); else dm.renameNode(node, "shaveCurveGroup#"); st = MDagPath::getAPathTo(node, parent); if (!st) { displayError( commandName + ": error getting path to group transform: " + st.errorString() ); } } } return parent; } shaveHairShape* shaveNodeCmd::getShaveNode(const MArgDatabase& args) { MStatus st; shaveHairShape* nodePtr = NULL; // // Get the shaveHairShape to be operated on. // MSelectionList selection; args.getObjects(selection); MObject node(MObject::kNullObj); selection.getDependNode(0, node); MFnDependencyNode nodeFn(node, &st); if (node.isNull() || !st || (nodeFn.typeId() != shaveHairShape::id)) { st = MS::kInvalidParameter; displayError("No shaveHairShape specified."); } else { nodePtr = (shaveHairShape*)nodeFn.userNode(); // Make sure that this is the shaveHairShape which is loaded into the // engine. if (nodePtr) { nodePtr->makeCurrent(); nodePtr->getHairNode(); } } return nodePtr; } MVector shaveNodeCmd::interpolate( const MVector& p1, const MVector& p2, const MVector& p3, const MVector& p4, double u ) { double u3, u2; if (u >= 1.0) return p3; if (u <= 0.0) return p2; u3 = u * u * u; u2 = u * u; return ((-u3 + (2.0 * u2) - u) * p1 + (3.0 * u3 - 5.0 * u2 + 2.0) * p2 + (-3.0 * u3 + (4.0 * u2) + u) * p3 + (u3 + -u2) * p4) / 2.0; } MString shaveNodeCmd::stripNode(MString plugName) { return plugName.substring(plugName.index('.')+1, plugName.length()-1); }