// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 int dprint=0; // debug printfs //Qt headers must be included before any others !!! # include # include # include # include #if QT_VERSION < 0x050000 # include # include #else # include # include #endif #include #include #include #include #include #include #include #include #include "shaveIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # include # include # define unlink _unlink #else # ifdef OSMac_ # include # include # endif # include #endif #include "shaveCursorCtx.h" #include "shaveBlindData.h" #include "shaveConstant.h" #include "shaveDebug.h" #include "shaveError.h" #include "shaveGlobals.h" #include "shaveHairGeomIt.h" #include "shaveHairShape.h" #include "shaveMaya.h" #include "shaveRender.h" #include "shaveRenderer.h" #include "shaveSDK.h" #include "shaveTextureStore.h" #include "shaveUtil.h" #include "shaveHairUI.h" #include "shaveStyleCmd.h" #ifdef USE_PROCEDURAL # include "shaveProcedural.h" #endif #include #if defined(OSMac_) && !defined(OSMac_MachO_) #include "shaveMacCarbon.h" #endif #ifdef REUSABLE_THREADS #ifdef _WIN32 LEvent::LEvent( bool signalled, LPSECURITY_ATTRIBUTES sa) #else LEvent::LEvent( bool signalled) #endif { #ifdef _WIN32 handle = CreateEvent(sa,TRUE,signalled ? TRUE : FALSE,NULL); if(!handle) printf("CreateEvent failed.\n"); #else pthread_mutex_init(&mutex, 0); pthread_cond_init(&cond, 0); triggered = false; if(signalled) Set(); #endif } LEvent::~LEvent() { #ifdef _WIN32 if(handle) CloseHandle(handle); #else pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); triggered = false; #endif } void LEvent::Set() { #ifdef _WIN32 if(handle) { if(!SetEvent(handle)) { printf("SetEvent failed.\n"); return; } } #else pthread_mutex_lock(&mutex); triggered = true; pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); #endif } void LEvent::Reset() { #ifdef _WIN32 if(handle) { if(!ResetEvent(handle)) { printf("ResetEvent failed.\n"); return; } } #else pthread_mutex_lock(&mutex); triggered = false; pthread_mutex_unlock(&mutex); #endif } void LEvent::Wait() { #ifdef _WIN32 if(handle) { WaitForSingleObject(handle,INFINITE); } #else pthread_mutex_lock(&mutex); while (!triggered) pthread_cond_wait(&cond, &mutex); pthread_mutex_unlock(&mutex); #endif } LThread::LThread(THPROC* pThreadFunc, void* pThreadFuncParameter, unsigned int exeFlags, unsigned int sSize, bool inheritable) { _state() = eAvilable; #ifdef _WIN32 _sa() = (LPSECURITY_ATTRIBUTES)HeapAlloc(GetProcessHeap(),0,sizeof(SECURITY_ATTRIBUTES)); _sa()->nLength = sizeof(SECURITY_ATTRIBUTES); _sa()->lpSecurityDescriptor = NULL; _sa()->bInheritHandle = inheritable; _exitCode() = 0xFFFFFFFF; #endif _thFn() = pThreadFunc; _thArg() = pThreadFuncParameter; _exeFlags() = exeFlags; _stackSize() = sSize; _th() = 0; _estart() = NULL; _efinish() = NULL; } void LThread::Create() { //run thread but hit should hit event in non signeled stat //and get into wait state untill ::Execute is called if( !th() ) { #ifdef WIN32 _estart() = new LEvent(false,sa()); _efinish() = new LEvent(true,sa()); if ( (_th() = CreateThread(sa(),stackSize(),thFnEx,this,exeFlags(),&_thId())) == NULL) { //error printf("CreateThread failed.\n"); return; } if(!SetThreadPriority(th(),THREAD_PRIORITY_HIGHEST))//does not change much { printf("SetThreadPriority failed.\n"); } #else _estart() = new LEvent(false); _efinish() = new LEvent(true); if ( pthread_create( &_th(), NULL, thFnEx, this ) != 0) { printf("pthread_create failed.\n"); return; } #endif } } void LThread::Execute(THPROC* pThreadFunc, void* pThreadData) { if(pThreadData != NULL) _thArg() = pThreadData; if(pThreadFunc != NULL) _thFn() = pThreadFunc; if(estart()) { if(efinish()) _efinish()->Reset(); _estart()->Set(); } } //each thread immediately runs for execution void LThread::execute() { //wait for an event while(true) { _estart()->Wait(); if(thFn()) { _state() = eRunning; _thFn()(thArg()); _state() = eWaiting; } _estart()->Reset(); _efinish()->Set(); } } #ifdef _WIN32 DWORD /*unsigned int*/ APIENTRY LThread::thFnEx(void* param) #else void* APIENTRY LThread::thFnEx( void* param) #endif { if(param) { LThread* tth = (LThread*)param; tth->execute(); } #ifdef _WIN32 return 0; #else return NULL; #endif } LThread::~LThread() { #ifdef _WIN32 if(th() != 0) { TerminateThread(th(),0); CloseHandle(th()); _th() = 0; } if (sa()) { HeapFree(GetProcessHeap(),0,sa()); _sa() = NULL; } #else pthread_cancel(th()); pthread_join(th(),NULL); #endif if(estart()) delete estart(); if(efinish()) delete efinish(); } void LThread::WaitToFinish() { #ifdef WIN32 WaitForSingleObject(th(),INFINITE); #else pthread_join( th(), NULL ); #endif } LThreadGroup::LThreadGroup(THPROC *thProc) { unsigned int ncpus = GetNumCPUs(); _ths().resize(ncpus); for(unsigned int i = 0; i < ncpus; i++) _th(i) = NULL; for(unsigned int i = 0; i < ncpus; i++) _th(i) = new LThread(thProc); for(unsigned int i = 0; i < ncpus; i++) { _th(i)->Create(); #ifdef _WIN32 SetThreadIdealProcessor(th(i)->GetHandle(),i);//does not do much #endif } } LThreadGroup::~LThreadGroup() { for(unsigned int i = 0; i < ths().size(); i++) if(th(i)) delete _th(i); _ths().clear(); } unsigned int LThreadGroup::GetNumCPUs() { #if defined(_WIN32) SYSTEM_INFO si; GetSystemInfo(&si); return si.dwNumberOfProcessors; #elif defined(__linux__) int nc = sysconf(_SC_NPROCESSORS_ONLN); return nc > 1 ? (unsigned int)nc : 1; #elif defined(OSMac_) int nc = 0; size_t resultSize = sizeof(int); sysctlbyname("hw.logicalcpu", &nc, &resultSize, NULL, 0); return nc; #else # error Unsupported OS. return 1; #endif } bool LThreadGroup::Start(unsigned int i, THPROC* pThreadFunc, void *thData) { if(th(i)) { _th(i)->Execute(pThreadFunc,thData); return true; } return false; } void LThreadGroup::WaitToFinish(int ms) { unsigned int ntotal = (unsigned int)ths().size(); if(ntotal > 0) { #ifdef _WIN32 HANDLE* handles = (HANDLE*)malloc(ntotal*sizeof(HANDLE)); memset(handles,0,ntotal*sizeof(HANDLE)); unsigned int nrunning = 0; for(unsigned int i = 0; i < ntotal; i++) { if(th(i)) { //handles[nrunning] = th(i)->GetHandle(); //handles[nrunning] = th(i)->GetFinish(); handles[nrunning] = th(i)->GetFinish()->handle; nrunning++; } } if(nrunning > 0) { DWORD wait_interval = (ms == -1) ? INFINITE : ms; WaitForMultipleObjects(nrunning,handles,TRUE,wait_interval); } #else for(unsigned int i = 0; i < ntotal; i++) { if(th(i)/* && th(i)->GetState() == LThread::eRunning*/) { //th(i)->WaitToFinish(); th(i)->GetFinish()->Wait(); } } #endif } } #endif const MString shaveHairShape::nodeTypeName("shaveHair"); const int shaveHairShape::kNodeVersion = 5; const char* shaveHairShape::kShapeSelectionMaskName = "shaveHair"; const char* shaveHairShape::kGuideSelectionMaskName = "shaveGuide"; const char* shaveHairShape::kStrandSelectionMaskName = "shaveStrand"; const char* shaveHairShape::kVertSelectionMaskName = "shaveGuideVert"; int shaveHairShape::numShaveNodes = 0; int shaveHairShape::mNextShaveID = 0; MString shaveHairShape::drawDbClassification("drawdb/geometry/shaveHair"); MString shaveHairShape::drawRegistrantId("shave_and_haircut"); MTypeId shaveHairShape::id(0x001029B7); /* * The shaveHairShape constructor. Set the initial values of Shave data. */ shaveHairShape::shaveHairShape() : mAfterDuplicateCB(0) , mBeforeDuplicateCB(0) , mBrushIsActive(false) , mBrushIsDown(false) , mDisplayHairCacheDirty(true) , mDisplayInstCacheDirty(true) , mFirstTime(true) , mGuideCacheDirty(true) , mGuideCountDirty(true) , mHairDisplayEnabled(true) , mHaveSelections(false) , mIsInstanced(false) , mNodeInitialized(false) , mNodeVersion(0) , mXplantRequired(false) //, mDirtyDisplaycount(true) //, mDirtyTexture(false) //, mDirtyHaircount(false) { ENTER(); mShaveID = mNextShaveID++; // // %%% SHAVEinit_node() should initialize this to zero, but currently // does not. // SHAVEinit_node(&hairnode, mShaveID); hairnode.shavep.tipfade = 1; hairnode.shavep.squirrel = 0; hairnode.shavep.flyaway_percent = 0.0f; hairnode.shavep.clumps = 0; doDyn = kDynamicsOff; mShaveFrame = 0; // initialize a few flags we'll use down the road. beenHereBefore = false; blindInit = false; doneSelections = false; shaveRenderCancelled = true; exportData.lastUSubdiv = 0; exportData.lastVSubdiv = 0; exportData.lastSubdDepth = 0; exportData.lastSubdSamples = 0; availableMem = 0; // initialize the shave data objects this node will be using. init_geomWF(&MAYAdata); init_geomWF(&memShaveObj); init_geomWF(&growthCollisionUVs); init_geomWF(&mLockSplines); // Let's find a unique filename we can use for our OBJ export file. shaveObjFile = shaveUtil::makeUniqueTempFileName("", "shave_", ".obj"); #if defined(OSMac_) && !defined(OSMac_MachO_) shaveObjFileHFS = shaveMacCarbon::makeMacFilename(shaveObjFile); #endif MStatus status = MGlobal::executeCommand("shaveEnsureVrayRenderCallbacksSet"); if(status != MStatus::kSuccess) MGlobal::displayWarning("shaveNode: can not ensure V-Ray callbacks are set."); #ifdef REUSABLE_THREADS _lthGrp() = new LThreadGroup(); #endif LEAVE(); } shaveHairShape::~shaveHairShape() { ENTER(); if (mBeforeDuplicateCB != 0) MMessage::removeCallback(mBeforeDuplicateCB); if (mAfterDuplicateCB != 0) MMessage::removeCallback(mAfterDuplicateCB); if (shaveObjFile.length() != 0) { // Get rid of our temp file, if it exists. // // Maya can crash if a node deletion gets pushed off the undo queue // and then its destructor tries to execute a MEL command. // // shaveUtil::fileDelete() executes MEL commands, so we can't use // it here. Instead, we execute an equivalent MEL script on idle. // MGlobal::executeCommandOnIdle( MString("shave_deleteFile \"") + shaveObjFile + "\"" ); } free_geomWF(&MAYAdata); // come back here free_geomWF(&memShaveObj); free_geomWF(&growthCollisionUVs); free_geomWF(&mLockSplines); #ifdef REUSABLE_THREADS if(lthGrp()) delete lthGrp(); #endif LEAVE(); } void* shaveHairShape::creator() { return new shaveHairShape; } void shaveHairShape::postConstructor() { ENTER(); setMPSafe(false); setRenderable(true); // Maya's 'duplicate' command can mess up our geometry sets, so we // need to keep an eye on them. mBeforeDuplicateCB = MModelMessage::addBeforeDuplicateCallback( beforeDuplication, (void*)this ); mAfterDuplicateCB = MModelMessage::addAfterDuplicateCallback( afterDuplication, (void*)this ); LEAVE(); } MStatus shaveHairShape::setInstanceObject( MObject instanceMesh, MObject shader, MDataBlock* block ) { ENTER(); // // The defaults used for growing uninstanced hair are not appropriate // for instanced hair. For example, it makes no sense to use multiple // transparency passes, and the default hair count should be much // lower. // // So if this is the first time that this node has been instanced, then // give it some more appropriate defaults. // MPlug plug(thisMObject(), neverBeenInstanced); bool hasNeverBeenInstanced; if (block != NULL) { hasNeverBeenInstanced = block->inputValue(neverBeenInstanced).asBool(); } else { plug.getValue(hasNeverBeenInstanced); } if (hasNeverBeenInstanced) { for(int m = 0; m < 5; m++) { // // Make the tip colour white. // hairnode.shavep.slider_val[9][m] = 255; hairnode.shavep.slider_val[10][m] = 255; hairnode.shavep.slider_val[11][m] = 255; // // Make the mutant hair colour white. // hairnode.shavep.slider_val[13][m] = 255; hairnode.shavep.slider_val[14][m] = 255; hairnode.shavep.slider_val[15][m] = 255; // // Make the root colour white. // hairnode.shavep.slider_val[17][m] = 255; hairnode.shavep.slider_val[18][m] = 255; hairnode.shavep.slider_val[19][m] = 255; // // No root frizz or kink. // hairnode.shavep.slider_val[0][m] = 0; hairnode.shavep.slider_val[38][m] = 0; // // Only one pass: no transparency required. // hairnode.shavep.passes[m] = 1; // // If there are more than 100 hairs, reduce it to just 100, // otherwise we could end up with a ton of geometry. // if (hairnode.shavep.haircount[m] > 100) { hairnode.shavep.haircount[m] = 100; setShadowHaircount(m); } } // It's been instanced now! // if (block != NULL) { block->outputValue(neverBeenInstanced).set(false); } else { plug.setValue(false); } } MString fileName = shaveUtil::expandTempFileName( MString("shaveInstance_") + nodeName() + ".obj" ); shaveObjTranslator objDump; objDump.exportInstanceObj(instanceMesh, shader, fileName); #if defined(OSMac_) && !defined(OSMac_MachO_) fileName = shaveMacCarbon::makeMacFilename(fileName); #endif SHAVEget_instance((char*)fileName.asChar(), &hairnode); mIsInstanced = true; updateBlindData(block); // Set the display mode to geometry. // if (block != NULL) { block->outputValue(dspyMode).set(kHairDisplayGeom); } else { plug.setAttribute(dspyMode); plug.setValue(kHairDisplayGeom); } RETURN(MS::kSuccess); } MStatus shaveHairShape::clearInstanceObject(MDataBlock& block) { ENTER(); SHAVEclear_instance(&hairnode); mIsInstanced = false; updateBlindData(&block); // Set the display mode to geometry. // block.outputValue(dspyMode).set(kHairDisplayHair); RETURN(MS::kSuccess); } bool shaveHairShape::getInstancingStatus() { return mIsInstanced; } MStatus shaveHairShape::shouldSave(const MPlug& plug, bool& saveIt) { ENTER(); // // If it's a dynamic attribute then do the default handling. // MFnDependencyNode nodeFn(thisMObject()); MFnDependencyNode::MAttrClass attrClass; attrClass = nodeFn.attributeClass(plug.attribute()); if ((attrClass == MFnDependencyNode::kLocalDynamicAttr)) { RETURN(MS::kInvalidParameter); } // // If it's one of the standard MPxNode attributes, or a deprecated // attribute, then do the default handling. // if ((plug == MPxNode::message) || (plug == MPxNode::isHistoricallyInteresting) || (plug == MPxNode::caching) || (plug == MPxNode::state)) { RETURN(MS::kInvalidParameter); } // // For all other attributes, if they're marked storable, then store // them. We don't want Maya's store-only-if-not-default functionality // because that screws up scene files if we subsequently change // defaults. // MFnAttribute attr(plug.attribute()); saveIt = attr.isStorable(); RETURN(MS::kSuccess); } /***************************************************************** * NAME: COMPUTE * * DESCRIPTON: This is our main compute funtion. This gets called * when any of the "Affects" attributes are changedin the GUI. Most * importantly, it gets called when there is a frame (time) change. * *****************************************************************/ MStatus shaveHairShape::compute (const MPlug& plug, MDataBlock& data) { //MGlobal::displayInfo("compute"); //printf("::compute\n");fflush(stdout); ENTER(); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::compute", NULL); #endif MStatus status = MS::kSuccess; CheckGlobalQtViewport20Tracker(); //////////////////////////////////// #ifdef GLOBAL_FALLBACK CheckAndSetGlobalQtWatcher(); #endif //////////////////////////////////// if(dirties.DIRTY_HAIRCOUNT) dirties.this_haircount = data.inputValue(shaveParamHaircount).asInt(); else if(dirties.DIRTY_PASSES) dirties.this_passes = data.inputValue(shaveParamPasses).asInt(); else if(dirties.DIRTY_DISPLAY_COUNT) { int totalCount = data.inputValue(shaveParamHaircount).asInt(); unsigned displayCount = (unsigned)(displayRatioGlob * (float)totalCount + 0.5); if (displayCount > (unsigned)totalCount) { displayCount = (unsigned)totalCount; } //unsigned int c = passes*displayCount; #ifdef GLOBAL_FALLBACK bool fallback = doFallbackGlob; if(liveModeGlob || (fallback && (liveModeGlob || dirties.BRUSH_MOUSE_DOWN || dirties.GLOBAL_MOUSE_DOWN || (dirties.BRUSH_JUST_MOVED && IsToolActive())))) #else if(doFallbackGlob) #endif { float fallback_ratio = fallbackRatioGlob; if (fallback_ratio < 0.0f) fallback_ratio = 0.0f; else if (fallback_ratio > 1.0f) fallback_ratio = 1.0f; displayCount *= fallback_ratio; } dirties.this_display_count = displayCount; } else if(dirties.DIRTY_DISPLAYSEGS) dirties.this_display_segs = data.inputValue(aDisplaySegmentLimit).asInt(); else if(dirties.DIRTY_GUIDE_SEGS) dirties.this_guide_segs = SHAVE_VERTS_PER_GUIDE-1; //////////////////////////////////// //not sure a good place //updateTexLookups(); ////////////////////////////////////// //bool doingCreate = false; //MString cmdFlag = data.inputValue(aEvalOption, &status).asString(); //if(cmdFlag != "") //{ // MStringArray cmdOptions; // cmdFlag.split(' ', cmdOptions); // cmdFlag = cmdOptions[0]; // cmdOptions.remove(0); // if(cmdFlag == "create") doingCreate = true; //} //short hairGroup = data.inputValue(aHairGroup).asShort(); //if(!doingCreate) // updateParamsFromDatablock(data, hairGroup); ////////////////////////////////////// if ((plug == aCachedBBox) || (plug == aCachedBBoxMin) || (plug == aCachedBBoxMax)) { MBoundingBox bbox; computeBoundingBox(bbox, data); MPoint bboxMin = bbox.min(); MPoint bboxMax = bbox.max(); MDataHandle hdl = data.outputValue(aCachedBBoxMin); double3& bboxMinData = hdl.asDouble3(); bboxMinData[0] = bboxMin.x; bboxMinData[1] = bboxMin.y; bboxMinData[2] = bboxMin.z; hdl = data.outputValue(aCachedBBoxMax); double3& bboxMaxData = hdl.asDouble3(); bboxMaxData[0] = bboxMax.x; bboxMaxData[1] = bboxMax.y; bboxMaxData[2] = bboxMax.z; data.setClean(aCachedBBox); } else if (plug == aHairGroup) { // // Shave supports (or at least did so at some point in time) different // types of hair: eyebrows, beard, etc. These are known as 'hair // groups'. This plugin just supports two hair groups: spline hair // (group 4) and scalp hair (group 0). If we have any input curves // then we make spline hair, otherwise it's scalp hair. // // The reason that we retrieve and then discard all of the surfaces // and curves is because I believe it necessary for the proper // propagation of dirty signals through the DG but I have yet to // prove or disprove it. // MArrayDataHandle inputCurveArray(data.inputArrayValue(inputCurve)); MArrayDataHandle inputMeshArray(data.inputArrayValue(inputMesh)); MArrayDataHandle inputSurfArray(data.inputArrayValue(inputSurf)); short hairGroup = 0; while (inputMeshArray.next()) inputMeshArray.inputValue(); while (inputSurfArray.next()) inputSurfArray.inputValue(); // If splineLock is on then the curves in inputCurveArray are used // for controlling regular hair, not spline hair. bool splineLock = data.inputValue(aSplineLockTrigger).asBool(); if (!splineLock && (inputCurveArray.elementCount() > 0)) { hairGroup = 4; // If you're wondering why we don't also run the loop below // when splineLock is on, that's because the compute call for // the splineLockTrigger plug will have already done that if // splineLock is on. Not doing it again is a minor // optimization. while (inputCurveArray.next()) inputCurveArray.inputValue(); } data.outputValue(aHairGroup).set(hairGroup); } else if (plug == instancingStatusChanged) { MDataHandle hdl = data.outputValue(instancingStatusChanged); // // If the shaveParamInstancingStatus attribute hadn't changed, then // Maya would simply have returned the existing value without // calling compute(). So the very fact that we're here means that // it must have changed. // hdl.set(true); } else if (plug == instanceMeshChanged) { MDataHandle hdl = data.outputValue(instanceMeshChanged); // // If the instanceMesh attribute hadn't changed, then Maya would // simply have returned the existing value without calling // compute(). So the very fact that we're here means that it must // have changed. // hdl.set(true); } else if ((plug == outputMesh) || (plug == triggerAttr)) { // // We often cannot properly compute the output mesh until the // entire scene is in place: hair objects, skull object, // shaveGlobals, etc. // // Once the scene has finished loading, an event handler will call // our initializeNode() method, which will set the mNodeInitialized // flag to true. At that point we can start normal processing. // // However, there is another possibility: this may be a new // shaveHairShape created within an existing scene. Since there is no // file load going on, there is no 'done loading' event to reset // our flag. // // So, if we are creating a new node within an existing scene, then // we'll just reset the flag ourselves. // MString cmdStr = data.inputValue(aEvalOption).asString(); if(cmdStr != "") { MStringArray cmdParams; cmdStr.split(' ', cmdParams); if (cmdParams[0] == "create") { mNodeInitialized = true; } } // // If we've already been fully initialized, then go ahead and // compute the output mesh in the normal way. Otherwise, just // output a null object. // if (mNodeInitialized) { status = computeOutputMesh(data, false); } else { MDataHandle hdl = data.outputValue(outputMesh, &status); hdl.set(MObject::kNullObj); } // // Grab the input time, just to clear its dirty bit. // MDataHandle timeData = data.inputValue(timeAttr); MTime timeIn = timeData.asTime(); } else if (plug == aSplineLockTrigger) { MArrayDataHandle inputCurveArray(data.inputArrayValue(inputCurve)); // Is spline locking on? bool splineLock = data.inputValue(aSplineLock).asBool(); if (splineLock && (inputCurveArray.elementCount() > 0)) { MObject curve; MObjectArray curves; do { curve = inputCurveArray.inputValue().asNurbsCurveTransformed(); curves.append(curve); } while (inputCurveArray.next()); // Compute new curve data. shaveUtil::sampleCurves(curves, SHAVE_VERTS_PER_GUIDE, mLockSplines); } else free_geomWF(&mLockSplines); // Copy the splineLock value over to the trigger. data.outputValue(plug).set(splineLock); } else RETURN(MS::kInvalidParameter); data.setClean(plug); #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::compute -- done", NULL); #endif RETURN(status); } MStatus shaveHairShape::initializeNode() { ENTER(); MStatus status; MDataBlock datablock = forceCache(); if (mXplantRequired) { scheduleXplant(); } status = computeOutputMesh(datablock, true); mNodeInitialized = true; // The new mesh value has been placed on our output in such a way // that it won't appear dirty to anyone connected to it. Let's // remedy that. // dirtyOutputMesh(); childChanged(); RETURN(status); } MStatus shaveHairShape::computeOutputMesh(MDataBlock& data, bool nodeJustLoaded) { //MGlobal::displayInfo("computeOutputMesh"); ENTER(); MStatus status; // // Set triggerAttr clean. // data.setClean(triggerAttr); // Create an empty mesh object. MFnMeshData dataCreator; MObject newOutputData = dataCreator.create (&status); MChkErr(status, "ERROR creating outputData"); // Get handles to the input geom arrays. MArrayDataHandle inputMeshArray(data.inputArrayValue(inputMesh)); MArrayDataHandle inputSurfArray(data.inputArrayValue(inputSurf)); MArrayDataHandle inputCurveArray(data.inputArrayValue(inputCurve)); bool splineLock = data.inputValue(aSplineLockTrigger).asBool(); bool haveGrowthGeom = (inputMeshArray.elementCount() > 0) || (inputSurfArray.elementCount() > 0) || (!splineLock && (inputCurveArray.elementCount() > 1)); bool visible = data.inputValue(MPxSurfaceShape::visibility).asBool(); // If this node is invisible or it doesn't have any growth geometry, // then just output an empty mesh. The exception is if the node was // just loaded from a scene file in which case we have to run through // the motions the first time around to ensure that it is properly // registered with the Shave engine. // if ((!visible && !nodeJustLoaded) || !haveGrowthGeom) { createEmptyMesh(newOutputData); MDataHandle hdl = data.outputValue(outputMesh); hdl.set(newOutputData); RETURN(MS::kSuccess); } shaveGlobals::getGlobals(); MDataHandle inputData; bool doXplant = false; bool replaceRest = false; // // Before doing any Shave calls, we want to make sure that the // SHAVEPARAMS reflect any changes the user may have made to the // corresponding node attributes. So we must first copy the // attribute values into the SHAVEPARAMS structure. // // However, there is one exception. If we are doing a hair create, // then we *don't* want to use the attribute values but will // instead go the other way, getting default param values from Shave and // applying them to the attributes. // // So, that means that we must process the 'aEvalOption' // attribute enough to know whether or not we are doing a 'create' // command. // MString cmdFlag; MStringArray cmdOptions; bool doingCreate = false; bool doingRecreate = false; bool keepMaterials = true; cmdFlag = data.inputValue(aEvalOption, &status).asString(); if(cmdFlag != "") { cmdFlag.split(' ', cmdOptions); cmdFlag = cmdOptions[0]; cmdOptions.remove(0); if(cmdFlag == "create") { doingCreate = true; } } // // Now, at long last, if we're not doing a create, then copy the // attribute values into the SHAVEPARAMS structure. // short hairGroup = data.inputValue(aHairGroup).asShort(); if(!doingCreate) { updateParamsFromDatablock(data, hairGroup); } else { updateNodeName(); } // // Have any of the instance dependencies changed? // // Note that this code must execute even if 'checkInstancing' is false, // because retrieving the attribute values will reset their dirty // flags, which we want to have happen. // bool instanceMeshHasChanged = false; bool instancingStatusHasChanged = false; MDataHandle hdl; hdl = data.inputValue(instanceMeshChanged); instanceMeshHasChanged = hdl.asBool(); hdl.set(false); hdl = data.inputValue(instancingStatusChanged); instancingStatusHasChanged = hdl.asBool(); hdl.set(false); mIsInstanced = data.inputValue(shaveParamInstancingStatus).asBool(); // // Should we check for changes in instancing? // // One case in which we shouldn't is if this is the first run of a node // which was just loaded from a scene file. In that case all of its // instancing data will already be in its blind data and the 'change' // that we've detected is simply the various attributes being // initialized at file load. // if (!nodeJustLoaded && !doingCreate) { // // Is this node doing instancing? // if (mIsInstanced) { // // Instancing is on. If it just changed to that, or if the // instance mesh has changed, then we need to update the // instance data. // if (instancingStatusHasChanged || instanceMeshHasChanged) { // // Get the shader which is assigned to our display node. // MObject shader = getShader(); // // Get the source mesh. We have to be careful here because // someone may have set our instancingStatus attr without // also setting a valid mesh, in which case calling // asMeshTransformed() on the input handle will crash Maya. // // So let's take things one step at a time. // MObject srcMeshObj; MDataHandle srcMeshHdl = data.inputValue(instanceMesh, &status); bool meshValid = false; if (status) { srcMeshObj = srcMeshHdl.data(); if (!srcMeshObj.isNull() && srcMeshObj.hasFn(MFn::kMesh)) { srcMeshObj = srcMeshHdl.asMeshTransformed(); MFnMesh mFn(srcMeshObj); #if 0 //werid , why bbox is empty MBoundingBox bbox = mFn.boundingBox(&status); fprintf(stdout,"pmin %f %f %f\n",bbox.min().x, bbox.min().y, bbox.min().z); fprintf(stdout,"pmax %f %f %f\n",bbox.max().x, bbox.max().y, bbox.max().z); fflush(stdout); if(bbox.max().y - bbox.min().y > 0) #endif float ymin, ymax = 0.0f; MFloatPointArray pointArray; mFn.getPoints(pointArray); for(int k = 0; k < (int)pointArray.length(); k++) { if(k == 0) { ymin = ymax = pointArray[0].y; } else { ymin = ( ymin < pointArray[k].y ) ? ymin : pointArray[k].y; ymax = ( ymax > pointArray[k].y ) ? ymax : pointArray[k].y; } } //fprintf(stdout,"miny %f maxy %f\n", ymin, ymax);fflush(stdout); if(fabs(ymax - ymin) > 0.000001f) { meshValid = true; } else MGlobal::displayError("Shave: picked instance mesh is not valid, 'y' span is zero."); } else MGlobal::displayError("Shave: picked instance object is not a mesh."); } if(meshValid) { // // Let Shave know about the new instance object. // setInstanceObject(srcMeshObj, shader, &data); } #if 0 // // If we don't have a valid mesh, then create an empty one. // if (!meshValid) { MFnMeshData meshCreator; srcMeshObj = meshCreator.create(); createEmptyMesh(srcMeshObj); } // // Let Shave know about the new instance object. // setInstanceObject(srcMeshObj, shader, &data); #endif } } else { // // Instancing is off. If it just changed to that, then we // need to clear the instance data. // if (instancingStatusHasChanged) clearInstanceObject(data); } } // // Process the command, if any. // bool preserveParams = false; if(cmdFlag != "") { if(cmdFlag == "doTransplant") { doXplant = true; mXplantRequired = false; if (cmdOptions.length() > 0) { if (cmdOptions[0] == "nomat") { keepMaterials = false; } else if (cmdOptions[0] == "preserveParams") { preserveParams = true; } } } else if(cmdFlag == "create") { cleanStatFiles(data); } else if (cmdFlag == "recreate") { doingRecreate = true; } else if(cmdFlag == "resetRest") { replaceRest = true; } cmdFlag = ""; cmdOptions.clear(); data.outputValue(aEvalOption).set(cmdFlag); } MDataHandle outputHandle = data.outputValue (outputMesh, &status); MChkErr(status, "ERROR getting polygon data handle\n"); // // Get time - if we don't, it won't act as a trigger anymore. // MDataHandle timeData = data.inputValue(timeAttr, &status); MChkErr(status, "Error getting time data handle\n"); MTime tempTime = timeData.asTime(); // // If we are in Live Mode, we need to artificially increment // time so that we can see the effects of the shave Engine's // frizz animation. // if (liveModeGlob) { mShaveFrame++; } else { mShaveFrame = (float)tempTime.value(); } // // Now that we have a time value, we need to update the time // value for the engine. // // %%% Should we really be setting restMEM's time here? Shouldn't // it remain at the time of the original rest state? // hairnode.restMEM.time = (int)mShaveFrame; hairnode.statMEM.time = (int)mShaveFrame; // // What is our current Display mode? // shaveDspyMode = data.inputValue(dspyMode, &status ).asShort(); // // Prep for running dynamics. We do the actual math in MAYA_xform in // createMesh() // inputData = data.inputValue(blockDynamics, &status); if(inputData.asBool()) doDyn = kDynamicsOff; else { inputData = data.inputValue(runDynamics, &status); doDyn = (DynamicsMode)inputData.asLong(); } // // exportThis() will take our growth and collision geometry and build // a WFTYPE (our 'memShaveObj' member variable) from it. If it detects // that the topology of any of our geometry has changed since the // WFTYPE was last built, then it will also export the geometry // information to an OBJ file which will later be used in a call to // SHAVExplant() so that Shave gets the new geometry. // // If this is the first time through for a node, exportThis() will // necessarily detect a topology change since the old WFTYPE is empty. // If the shaveHairShape was just created, then everything will work as // intended. However, if the shaveHairShape is an existing node which was // just read in from file, then we won't be doing a SHAVExplant() call // because Shave already has the right geometry. That in turn means // that we shouldn't let exportThis() create an OBJ file: while // harmless, it is a waste of CPU cycles. // // So, if this is the first time through, and we are not in the process // of creating (or recreating) this node, then pass exportThis() an // empty filename, otherwise pass it a good one. // if (!beenHereBefore && !doingCreate && !doingRecreate && !doXplant) { exportData.objFileName = ""; } else { exportData.objFileName = shaveObjFile; } int uSubdivs = (int)data.inputValue(surfTessU).asShort(); int vSubdivs = (int)data.inputValue(surfTessV).asShort(); int subdDepth = (int)data.inputValue(subdTessDept).asShort(); int subdSamples = (int)data.inputValue(subdTessSamp).asShort(); exportData.newtopo = doXplant; exportData.theObj = &memShaveObj; exportData.uvs = &growthCollisionUVs; exportData.shaveHairShape = thisMObject(); exportData.uSubdivs = uSubdivs; exportData.vSubdivs = vSubdivs; exportData.subdDepth = subdDepth; exportData.subdSamples= subdSamples; shaveObjTranslator x; x.exportThis(data, exportData); // // it can be useful for debugging to export an obj // built from the WFTYPE. // //save_geomWF(exportData.theObj ,"c:\\debug.obj"); // // Store the values for the last tesselation params // passed to the exporter. This will be used later to // determine whether a retesselation is necesary or not. // exportData.lastUSubdiv = uSubdivs; exportData.lastVSubdiv = vSubdivs; exportData.lastSubdDepth = subdDepth; exportData.lastSubdSamples = subdSamples; if (beenHereBefore) doXplant = exportData.newtopo; /*************** * BLIND DATA * ***************/ // // Normally, blind data is generated by Shave and passed out to the // node. The node then passes it back, unchanged, whenever Shave needs // it. // // One exception is when a shaveHairShape is first loaded from file. In // that case we have to take the blind data which was stored in the // file and pass it on to Shave so that it can initialize its state. // // If blindInit is false, that means we haven't done that yet for this // node, so we should do so now. // if ((blindInit == false) && !doingCreate && !doingRecreate) { inputData = data.inputValue(shaveBlindHair, &status); if (status) { if (dprint) cerr << "doing the blind init" << endl; blindShaveData * xx = (blindShaveData *) inputData.asPluginData(); if (xx != NULL) { if (dprint) cerr << "we have blind data" << endl; // // As a final check, we only fill the hairnode with data if // it doesn't already have some. // if (hairnode.restMEM.size == 0) { if (dprint) cerr << "allocate a shavenode" << endl; // need to do load/save for SHAVENODE hairnode.restMEM.size = (long) xx->m.size; hairnode.restMEM.pos = (long) xx->m.pos; if (hairnode.restMEM.size > 0) { alloc_MEMFILE(&(hairnode.restMEM)); copy_MEMFILE(&(hairnode.restMEM), &(xx->m)); } } // // The Shave ID stored in the hairnode may not be correct. // For example, the file loaded in may be a reference file // in which case its Shave IDs would start at 0, causing // conflicts with the IDs of the shaveHairShapes already in the // scene. So we must reset their IDs. // hairnode.restMEM.ID = getShaveID(); // 20070220: Joe told me in email "can you just coment // it out for the moment - I'll try some scenes" #if 0 // // I'm not completely sure why we need to do this. We had // a bug such that dynamics wouldn't work right on older // scenes: the hair would fly off. Joe couldn't fix it on // his end for some reason, so he asked me to do it here. // replaceRest = true; #endif } } // // shave creates the 'stat' data dynamically, so we don't need to // load that. // hairnode.statMEM.data = NULL; hairnode.statMEM.size = 0; hairnode.statMEM.pos = 0; hairnode.statMEM.ID = getShaveID(); } if (!beenHereBefore) { // // MAYA REFRESH - we're making hairs from scratch. // if (doingCreate) { if (dprint) cerr << "creating the node 'from scratch'" << endl; #if defined(OSMac_) && !defined(OSMac_MachO_) SHAVEcreate_node((char*)shaveObjFileHFS.asChar(), &hairnode); #else SHAVEcreate_node((char*)shaveObjFile.asChar(), &hairnode); #endif SHAVEfetch_parms(&hairnode.shavep); // Update the attribute values to reflect the default // parameter values which we got from Shave. // updateDatablockFromParams(data); // // Set the displaySegmentLimit according to whether we have // spline or normal hair. // if (hairGroup == 4) //does not make sense becuse attrib moved to globals data.outputValue(aDisplaySegmentLimit).set(12); else data.outputValue(aDisplaySegmentLimit).set(6); // we want to dirty the kernal // so it gets created on display dirties.DIRTY_DISPLAY_COUNT = 1; } else if (doingRecreate) { #if defined(OSMac_) && !defined(OSMac_MachO_) SHAVEcreate_node((char*)shaveObjFileHFS.asChar(), &hairnode); #else SHAVEcreate_node((char*)shaveObjFile.asChar(), &hairnode); #endif updateParamsFromDatablock(data, hairGroup); dirties.DIRTY_DISPLAY_COUNT = 1; } // // If we have new blind data from Shave, then store it out to the // plugs. // // How do we know if we have new data? Well, if 'blindInit' is // false, then we just got through setting Shave up to use // whatever was in the plugs, so unless that has changed since // then, there's no need to write it back out. // // The only chance for it to have changed since then is if // 'doingCreate' or 'doingRecreate' is true. // // So if 'blindInit', 'doingCreate' and 'doingRecreate' are // all false, we don't need to update the plugs. // // Why does it matter? Because if the shaveHairShape is from a // referenced file, changing the blind data will cause a massive // 'setAttr' to be registered with the referencing file, bloating // its size. So we want to avoid that if the blind data really // hasn't changed. // if (blindInit || doingCreate || doingRecreate) { updateBlindData(&data); } beenHereBefore = true; } blindInit = true; // // If this is a freshly loaded node then give Shave a chance to update // its internal data structures, in case they've changed since the // scene was last saved. // if (nodeJustLoaded) { SHAVEupgrade_node(&hairnode); } loadShaveEngine(mShaveFrame, doXplant, keepMaterials, preserveParams, data); createMesh(data, newOutputData); // // if the replaceRest flag is set, we need to call replace_rest. // this copies the if (replaceRest) { SHAVEreplace_rest(&hairnode); replaceRest = false; } // // Set the results to the output plug. // outputHandle.set(newOutputData); RETURN(MS::kSuccess); } /**************************************************************************** * CREATE MESH -- This is the main body of code that does most of the calls * to shavelib (the Shave library). *****************************************************************************/ void shaveHairShape::loadShaveEngine( float frame, bool doXplant, bool keepMaterials, bool preserveParams, MDataBlock& block ) { ENTER(); /* * THREE POSSIBLE CASES: Refresh, Transplant, and Transform. */ if (doXplant) { /* * TRANSPLANT - there's been a change in material assignments. */ if (dprint) cerr << "doing Xplant" << endl; MGlobal::executeCommand( MString("shave_removeStatFiles(\"") + nodeName() + "\")" ); #if defined(OSMac_) && !defined(OSMac_MachO_) if (keepMaterials) SHAVExplant((char*)shaveObjFileHFS.asChar(), &hairnode); else SHAVExplantNOMAT((char*)shaveObjFileHFS.asChar(), &hairnode); #else if (keepMaterials) SHAVExplant((char*)shaveObjFile.asChar(), &hairnode); else SHAVExplantNOMAT((char*)shaveObjFile.asChar(), &hairnode); #endif updateBlindData(&block, !preserveParams); } // Is splineLock on? bool splineLock = block.inputValue(aSplineLockTrigger).asBool(); /* * TRANSFORM - just changing frames, possibly computing/showing * dynamics. */ /* * doDyn: -1 To initialize the dynamics engine, calculate first frame. * 0 Don't calculate/display dynamics * 1 Calculate & display dynamics for this frame. * 2 Don't calculate, but just display */ // // Load the hairnode into Shave's engine and set the dynamics state for // this frame, if dynamics are active. // if (liveModeGlob) { if (shaveRender::renderState() == shaveRender::kRenderNone) { //MGlobal::displayInfo("SHAVExform"); SHAVExform(&memShaveObj, &hairnode, 1, NULL); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } } else if (doDyn == kDynamicsUse) { // // Shave only understands integer frame numbers. So find the // bounding frames. // int leftFrame = (int)frame; int rightFrame = leftFrame; float partialFrame = frame - (float)leftFrame; if (partialFrame > 0.001) rightFrame++; MString leftStatFile = shaveUtil::makeStatFileName( nodeName(), (float)leftFrame ); if (shaveUtil::fileExists(leftStatFile)) { // // If the bounding frames are the same, then we're at an integer // frame number, so we can just use that frame's statfile to set // the dynamic state. // if (rightFrame == leftFrame) { #if defined(OSMac_) && !defined(OSMac_MachO_) leftStatFile = shaveMacCarbon::makeMacFilename(leftStatFile); #endif } { // // Now to handle the dynamics info. // // We're between frames. If the statfile for the other // frame exists, then we can interpolate between them. // // Otherwise, we'll just have to pretend that there are no // dynamics. // MString rightStatFile = shaveUtil::makeStatFileName( nodeName(), (float)rightFrame ); if (shaveUtil::fileExists(rightStatFile)) { #if defined(OSMac_) && !defined(OSMac_MachO_) leftStatFile = shaveMacCarbon::makeMacFilename( leftStatFile ); rightStatFile = shaveMacCarbon::makeMacFilename( rightStatFile ); #endif SHAVEset_state_between_and_glue( &memShaveObj, &hairnode, partialFrame, (char*)leftStatFile.asChar(), (char*)rightStatFile.asChar() ); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } else { //MGlobal::displayInfo("SHAVExform"); SHAVExform(&memShaveObj, &hairnode, 0, NULL); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } } } else { // // We don't have state information for this frame, so treat it // as if it doesn't have any dynamics. // //MGlobal::displayInfo("SHAVExform"); SHAVExform(&memShaveObj, &hairnode, 0, NULL); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } } else if (doDyn == kDynamicsCreate) { MString statFile = shaveUtil::makeStatFileName(nodeName(), frame); if (statFile == "") { MGlobal::displayError( "Cannot create stat file. Please check your stat file" " directory to ensure that you have write access to it" " and that the disk is not full." ); } else { MGlobal::displayInfo(statFile); // Make sure that vertex-based texture info is up-to-date. initVertTexInfoLookup(this, thisMObject(), &block); // Have Shave calculate and cache dynamics for this frame. #if defined(OSMac_) && !defined(OSMac_MachO_) statFile = shaveMacCarbon::makeMacFilename(statFile); #endif //MGlobal::displayInfo("SHAVExform"); SHAVExform( &memShaveObj, &hairnode, (int)doDyn, (char*)statFile.asChar() ); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } } else // doDyn == kDynamicsOff || doDyn == kDynamicsInit { if (dprint) cerr << "doing base xform - no dyn" << endl; //MGlobal::displayInfo("SHAVExform"); SHAVExform(&memShaveObj, &hairnode, (int)doDyn, NULL); if (splineLock) SHAVEsplinelock(&hairnode, &mLockSplines); } LEAVE(); } void shaveHairShape::doXform() { SHAVExform(&memShaveObj, &hairnode, 0, NULL); } void shaveHairShape::createMesh(MDataBlock& datablock, MObject& outData) { // Is this node visible? // if (datablock.inputValue(MPxSurfaceShape::visibility).asBool()) { // Are we rendering? // if (shaveRender::renderState() != shaveRender::kRenderNone) { // What are my render and shadow modes? // shaveRenderer* renderer = shaveRender::getRenderer(); bool renderInstances; shaveConstant::RenderMode renderMode; renderMode = renderer->getRenderMode(&renderInstances); if (mIsInstanced) { if (renderInstances && renderer->isGeomNode(this)) makePolyMeshData(datablock, outData, false, false); else createEmptyMesh(outData); } else { shaveConstant::ShadowSource shadowSource; shadowSource = renderer->getShadowSource(); switch (renderMode) { case shaveConstant::kBufferRender: { // Do we need geometry for the shadows? // if (shadowSource == shaveConstant::kMayaGeomShadows) { // Display geometry, but reduce it by the // shadow/hair ratio. // makePolyMeshData(datablock, outData, false, true); } else createEmptyMesh(outData); } break; case shaveConstant::kGeometryRender: { // Display geometry for every hair. // makePolyMeshData(datablock, outData, false, false); } break; default: createEmptyMesh(outData); } } } else { // If we're displaying hair as geometry and have some hairs to // display, then generate the corresponding output mesh. // Otherwise just generate an empty output mesh. // unsigned numHairsToDisplay = getNumDisplayHairs(false, &datablock); //#ifdef NEW_INSTNACE_DISPLAY if(glisntGlob) createEmptyMesh(outData); else { //#else if ((numHairsToDisplay > 0) && (shaveDspyMode == kHairDisplayGeom)) { // Display geometry, but reduce it by the display LOD. // makePolyMeshData(datablock, outData, true, false); } else { createEmptyMesh(outData); } } //#endif } } else { createEmptyMesh(outData); } } void shaveHairShape::createEmptyMesh(MObject& outData) { ENTER(); MPointArray noPoints; MIntArray noInts; MFnMesh meshFn; meshFn.create(0, 0, noPoints, noInts, noInts, outData); LEAVE(); } /**************************************************************************** * MAKE MESH DATA -- This is where we call MAYAmake_a_hair and create * the MFnMesh data, (MAYA's datastructure for Meshes) *****************************************************************************/ #define SINGLE_HAIR_POLYS MObject shaveHairShape::makePolyMeshData( MDataBlock& datablock, MObject parent, bool useDisplayLOD, bool forShadowsOnly ) { ENTER(); MFloatPointArray points; MFnMesh meshFS; int numVertices; int numFaces; int x, i, vertexIndex; MColorArray vertexColours; MIntArray faceCounts; MIntArray faceConnects; MIntArray vertexIndexArray; MObject newMesh; vertexIndex = 0; /* * Make NUMBER_OF_HAIRS */ int totalNumHairs = 0; int totalNumVertices = 0; int totalNumFaces = 0; MFloatArray Us; MFloatArray Vs; MIntArray UVids; MIntArray UVCounts; short hairGroup = datablock.inputValue(aHairGroup).asShort(); int numHairs = hairnode.shavep.haircount[hairGroup]; shaveGlobals::getGlobals(); if (useDisplayLOD) { numHairs = getNumDisplayHairs(false, &datablock); } else if (forShadowsOnly) { numHairs = hairnode.shavep.shadowHaircount[hairGroup]; // We seen some renderers crash when they get an empty mesh during // shadow passes, so let's make sure we have at least one shadow // hair. // if (numHairs < 1) numHairs = 1; } int hair; int pass; pass=0; hair=0; #ifdef SINGLE_HAIR_POLYS for (pass = 0; pass < hairnode.shavep.passes[hairGroup]; pass++) #endif { #ifdef SINGLE_HAIR_POLYS for (hair = 0; hair < numHairs; hair++) #endif { #ifdef SINGLE_HAIR_POLYS SHAVEmake_a_hair( pass, hairGroup, hair, hairnode.shavep.segs[hairGroup], &MAYAdata ); #else int *list; CURVEINFO cinfo; list=(int *) malloc (numHairs*hairnode.shavep.passes[hairGroup]*sizeof(int)); for (hair=0;hair 0) { /* * Do vertices: * 1) Make vertex array. * 2) Make vertex color array * 3) Make vertex index array (0,1,2,etc). Used to set vertex colors. */ for (x=0; x < numVertices; x++) { // Check it out: We get weird numbers points.append (MAYAdata.v[x].x, MAYAdata.v[x].y, MAYAdata.v[x].z); vertexColours.append( MAYAdata.color[x].x, MAYAdata.color[x].y, MAYAdata.color[x].z, 1.0 ); vertexIndexArray.append (vertexIndex); vertexIndex++; } /* * Do facecounts */ for (i = 0; i < numFaces; i++) { faceCounts.append( MAYAdata.face_end[i] - MAYAdata.face_start[i] ); } /* * Do face connects index is relative to one hair * so we multiply it to index into multiple hairs */ for (int f=0;f < MAYAdata.totalfaces;f++) { for (x = MAYAdata.face_start[f]; x < MAYAdata.face_end[f]; x++) { int index; index=MAYAdata.facelist[x]; faceConnects.append ( index + totalNumVertices ); } } totalNumHairs++; totalNumVertices += numVertices; totalNumFaces += numFaces; } } } MStatus stat; newMesh = meshFS.create (totalNumVertices, totalNumFaces, points, faceCounts, faceConnects, parent, &stat); meshFS.setVertexColors(vertexColours, vertexIndexArray); if (mIsInstanced) { // // Get the source mesh for the instances. // MFnMesh sourceMeshFn( datablock.inputValue(instanceMesh).asMesh() ); // get the uv index mapping table MIntArray indexTable; MIntArray countTable; sourceMeshFn.getAssignedUVs(countTable, indexTable); // get the uv arrays MFloatArray refUs; MFloatArray refVs; sourceMeshFn.getUVs(refUs, refVs); // set the table for our new mesh. The new table is a duplicate of // the originals. meshFS.setUVs(refUs, refVs); if ((countTable.length() > 0) && (indexTable.length() > 0)) { // how many polys do we need to process? int instMeshPolyCount = meshFS.numPolygons(); int instRefPolyCount = sourceMeshFn.numPolygons(); if(instMeshPolyCount%instRefPolyCount) cerr << "Sanity check: poly counts do not evenly map." << endl; // iterate through the polys, and do the mapping. Hopefully // everyting is aligned... int uvi, vi, pi, rpi = 0; // outer loop interates over every poly in our output mesh for(pi = 0; pi < instMeshPolyCount; pi++) { // ref poly index is outMeshPolyIndex mod ref object poly count. rpi = pi%instRefPolyCount; // inner loop iterates over current poly verts, vert count // determined via uv assignment face count table (should always // be 3) for(vi = 0; vi < countTable[rpi]; vi++) { // get, then assign the index into our uv table. sourceMeshFn.getPolygonUVid(rpi, vi, uvi); meshFS.assignUV(pi, vi, uvi); } } } } if (!stat) { #if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) cerr << "Error creating hair mesh: " << stat.errorString().asChar() << endl; #else cerr << "Error creating hair mesh: " << stat.errorString() << endl; #endif } RETURN(newMesh); } bool GetDiffuseTexture(MObject shaderSG, MObject& map) { #ifdef _DEBUG MGlobal::displayInfo("GetDiffuseTexture"); #endif MStatus stat; MFnDependencyNode dFn(shaderSG); MPlug ssPlug = dFn.findPlug("surfaceShader",&stat); if(!ssPlug.isNull() && stat == MStatus::kSuccess) { if(ssPlug.isConnected()) { MPlugArray sscons; if(ssPlug.connectedTo(sscons,true,false,&stat) && stat == MStatus::kSuccess && sscons.length() > 0) { if(!sscons[0].isNull()) { MObject shader = sscons[0].node(); if(!shader.isNull()) { MFnDependencyNode dFn(shader); MPlug colorPlug = dFn.findPlug("color",&stat); if(colorPlug.isNull() || stat != MStatus::kSuccess) { //let try other plugs colorPlug = dFn.findPlug("outColor",&stat); } if(!colorPlug.isNull() && stat == MStatus::kSuccess) { if(colorPlug.isConnected()) { MPlugArray ccons; if(colorPlug.connectedTo(ccons,true,false,&stat) && stat == MStatus::kSuccess && ccons.length() > 0) { if(!ccons[0].isNull()) { MObject color_tex = ccons[0].node(); if(!color_tex.isNull()) { map = color_tex; return true; #if 0 if ( color_tex.hasFn( MFn::kFileTexture ) ) { map = color_tex; #ifdef _DEBUG MFnDependencyNode dFn(map); MGlobal::displayInfo(dFn.name()); MGlobal::displayInfo(MString("typeId " ) + dFn.typeId().id()); #endif return true; } #endif } } } } } } } } } } return false; } bool GetTransparencyTexture(MObject shaderSG, MObject& map) { #ifdef _DEBUG MGlobal::displayInfo("GetDiffuseTexture"); #endif MStatus stat; MFnDependencyNode dFn(shaderSG); MPlug ssPlug = dFn.findPlug("surfaceShader",&stat); if(!ssPlug.isNull() && stat == MStatus::kSuccess) { if(ssPlug.isConnected()) { MPlugArray sscons; if(ssPlug.connectedTo(sscons,true,false,&stat) && stat == MStatus::kSuccess && sscons.length() > 0) { if(!sscons[0].isNull()) { MObject shader = sscons[0].node(); if(!shader.isNull()) { MFnDependencyNode dFn(shader); MPlug colorPlug = dFn.findPlug("transparency",&stat); //Blin, Phong, Anizotropic if(!colorPlug.isNull() && stat == MStatus::kSuccess) { if(colorPlug.isConnected()) { MPlugArray ccons; if(colorPlug.connectedTo(ccons,true,false,&stat) && stat == MStatus::kSuccess && ccons.length() > 0) { if(!ccons[0].isNull()) { MObject color_tex = ccons[0].node(); if(!color_tex.isNull()) { map = color_tex; return true; #if 0 if ( color_tex.hasFn( MFn::kFileTexture ) ) { #ifdef _DEBUG MFnDependencyNode dFn(map); MGlobal::displayInfo(dFn.name()); MGlobal::displayInfo(MString("typeId " ) + dFn.typeId().id()); #endif return true; } #endif } } } } } } } } } } return false; } void shaveInstanceDisplay::Reset() { points.clear(); normals.clear(); faceCounts.clear(); faceStart.clear(); faceEnd.clear(); faceVerts.clear(); uvs.clear(); #if 0 for(unsigned int i = 0;i < groups.size();i++) if(groups[i]) delete groups[i]; groups.clear(); #endif } void shaveInstanceDisplay::CreateMaps(MObject shader) { hasXparency = false; MObject tex; if(GetDiffuseTexture(shader,tex)) { //MImage img; SampleMap(tex,img); if(diffuse != 0) glDeleteTextures(1,&diffuse); glGenTextures(1, &diffuse); MObject tex2; if(GetTransparencyTexture(shader,tex2)) { MImage img2; SampleMap(tex2,img2); unsigned char* pix = img.pixels(); unsigned char* pix2 = img2.pixels(); int p = 0; for (int i = 0; i < eTexH; i++) { for (int j = 0; j < eTexW; j++) { pix[p+3] = (unsigned char)(((int)pix2[p] + (int)pix2[p+1] + (int)pix2[p+2])/3); //pix[p+3] = 100; //pix[p+3] = 255; p+=4; } } hasXparency = true; } glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, diffuse); glTexImage2D(GL_TEXTURE_2D, 0, //mip GL_RGBA, //GL_RGB, //internal format eTexW, eTexH, 0, //border GL_RGBA, //format GL_UNSIGNED_BYTE, //type img.pixels()); //pixels glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(GL_TEXTURE_2D, 0); } } void shaveInstanceDisplay::SampleMap(MObject tex, MImage& img) { MStatus stat; MFnDependencyNode tFn(tex); MString tname = tFn.name(); MFloatMatrix camMatrix; MFloatVectorArray col; MFloatVectorArray tra; MFloatArray u; MFloatArray v; u.setLength(eTexW*eTexH); v.setLength(eTexW*eTexH); int k = 0; float istep = 1.0f / (float)eTexH; float jstep = 1.0f / (float)eTexW; for (int i = 0;i < eTexH; i++) { float is = istep*(float)i; for (int j = 0;j < eTexW; j++) { float js = jstep*(float)j; u[k] = js; v[k] = is; k++; } } col.setLength(eTexW*eTexH); tra.setLength(eTexW*eTexH); stat = MRenderUtil::sampleShadingNetwork(tname+".outColor",u.length(),false,false,camMatrix, NULL,&u,&v,NULL,NULL,NULL,NULL,NULL,col,tra); img.create(eTexW,eTexH); unsigned char* pix = img.pixels(); k = 0; int p = 0; for (int i = 0; i < eTexH; i++) { for (int j = 0; j < eTexW; j++) { pix[p] = (unsigned char)(col[k].x*255.0f); pix[p+1] = (unsigned char)(col[k].y*255.0f); pix[p+2] = (unsigned char)(col[k].z*255.0f); pix[p+3] = 255; k++; p+=4; } } } #if 0 shaveInstanceDisplay::Group::Group( int start, int end, const MFloatPointArray& points, const MIntArray& faceCounts, const MIntArray& faceStart, const MIntArray& faceEnd, const MIntArray& faceVerts) { this->start = start; this->end = end; int n = 0; MFloatPoint c(0.0f,0.0f,0.0f); for(int i = start; i <= end; i++) { int fc = faceCounts[i]; if(fc != 3) continue; //trianglular faces expected int fs = faceStart[i]; int fe = faceEnd[i]; for(int j = fs; j < fe; j++) { int k = faceVerts[j]; c += points[k]; n++; } } worldPos = c/n; } #endif /*const*/ shaveInstanceDisplay& shaveHairShape::getInstanceDisplay( ) { if (!mNodeInitialized) return mDisplayInstCache; if (!mDisplayInstCacheDirty) return mDisplayInstCache; ENTER(); updateTexLookups(); makeCurrent(); mDisplayInstCache.Reset(); MObject shader = getShader(); if(!shader.isNull()) mDisplayInstCache.CreateMaps(shader); int numVertices; int numFaces; int x, i, vertexIndex; MColorArray vertexColours; vertexIndex = 0; int totalNumHairs = 0; int totalNumVertices = 0; int totalNumFaces = 0; MFloatArray Us; MFloatArray Vs; MIntArray UVids; MIntArray UVCounts; short hairGroup; MPlug hairGroupPlug(thisMObject(),aHairGroup); hairGroupPlug.getValue(hairGroup); int numHairs = getNumDisplayHairs(false); int hair; int pass; pass=0; hair=0; int *list; CURVEINFO cinfo; list=(int *) malloc (numHairs*hairnode.shavep.passes[hairGroup]*sizeof(int)); for (hair=0;hair 0) { /* * Do vertices: * 1) Make vertex array. * 2) Make vertex color array * 3) Make vertex index array (0,1,2,etc). Used to set vertex colors. */ for (x=0; x < numVertices; x++) { // Check it out: We get weird numbers mDisplayInstCache.points.append (MAYAdata.v[x].x, MAYAdata.v[x].y, MAYAdata.v[x].z); mDisplayInstCache.normals.append (MAYAdata.vn[x].x, MAYAdata.vn[x].y, MAYAdata.vn[x].z); //vertexColours.append( // MAYAdata.color[x].x, // MAYAdata.color[x].y, // MAYAdata.color[x].z, // 1.0 //); //vertexIndexArray.append (vertexIndex); //vertexIndex++; } /* * Do facecounts */ for (i = 0; i < numFaces; i++) { mDisplayInstCache.faceCounts.append( MAYAdata.face_end[i] - MAYAdata.face_start[i] ); mDisplayInstCache.faceStart.append( MAYAdata.face_start[i] ); mDisplayInstCache.faceEnd.append( MAYAdata.face_end[i] ); } for (int f=0;f < MAYAdata.totalfverts;f++) { mDisplayInstCache.faceVerts.append(MAYAdata.facelist[f]); } /* * Do face connects index is relative to one hair * so we multiply it to index into multiple hairs */ #if 0 for (int f=0;f < MAYAdata.totalfaces;f++) { for (x = MAYAdata.face_start[f]; x < MAYAdata.face_end[f]; x++) { int index; index=MAYAdata.facelist[x]; faceConnects.append ( index + totalNumVertices ); } } totalNumHairs++; totalNumVertices += numVertices; totalNumFaces += numFaces; #endif } MObject srcMesh; MPlug srcMeshPlug(thisMObject(),instanceMesh); srcMeshPlug.getValue(srcMesh); MFnMesh sourceMeshFn(srcMesh); // get the uv index mapping table MIntArray indexTable; MIntArray countTable; sourceMeshFn.getAssignedUVs(countTable, indexTable); // get the uv arrays MFloatArray refUs; MFloatArray refVs; sourceMeshFn.getUVs(refUs, refVs); if ((countTable.length() > 0) && (indexTable.length() > 0)) { // how many polys do we need to process? int instRefPolyCount = sourceMeshFn.numPolygons(); int vi, pi, rpi = 0; // outer loop interates over every poly in our output mesh for(pi = 0; pi < numFaces; pi++) { // ref poly index is outMeshPolyIndex mod ref object poly count. rpi = pi%instRefPolyCount; // inner loop iterates over current poly verts, vert count // determined via uv assignment face count table (should always // be 3) for(vi = 0; vi < countTable[rpi]; vi++) { // get, then assign the index into our uv table. //sourceMeshFn.getPolygonUVid(rpi, vi, uvi); float u, v; sourceMeshFn.getPolygonUV(rpi,vi,u,v); mDisplayInstCache.uvs.append(MFloatPoint(u,v,0.0f)); } } } unsigned int np = mDisplayInstCache.points.length(); mDisplayInstCache.center = MFloatPoint(0.0f, 0.0f, 0.0f); for(unsigned int i = 1; i < np; i++) { mDisplayInstCache.center += mDisplayInstCache.points[i]; } mDisplayInstCache.center.x /= (float)np; mDisplayInstCache.center.y /= (float)np; mDisplayInstCache.center.z /= (float)np; float rSq = 0.0f; for(unsigned int i = 1; i < np; i++) { MFloatPoint d = mDisplayInstCache.center - mDisplayInstCache.points[i]; float dsq = d.x*d.x + d.y*d.y + d.z*d.z; if(dsq > rSq) rSq = dsq; } mDisplayInstCache.radius = sqrt(rSq); mDisplayInstCacheDirty = false; RETURN(mDisplayInstCache); } void shaveHairShape::updateBlindData(MDataBlock* block, bool updateParams) { ENTER(); MFnPluginData dataFn; MObject wrapper; blindShaveData* newData; if (block) { // TODO: // // MDataHandle provides the set(MPxData*) method, which // would be easier to use, but it appears to only work // if the attribute has already been initialized with // data. Otherwise it fails. // // We may be able to get around that by first // calling asPluginData(). If it returns NULL then the // attribute has not yet been initialized and we will // need to use MFnPluginData to create a wrapper object. // If it succeeds then we can use set(MPxData*) to // set the new data value without having to create a // new wrapper object. // // The advantage of this approach is that it may allow // us to bypass some of the redundant duplication of // blind data that Maya is doing behind the scenes. // // For now, we always create a new wrapper since that is // guaranteed to work in all situations. // wrapper = dataFn.create(blindShaveData::dataTypeId); newData = (blindShaveData*)dataFn.data(); } else { newData = new blindShaveData; } if (hairnode.restMEM.size > 0) { newData->m.pos = hairnode.restMEM.pos; newData->setValue(hairnode.restMEM.data, hairnode.restMEM.size); } if (block) { MDataHandle hdl = block->outputValue(shaveBlindHair); hdl.set(wrapper); if (updateParams) { updateDatablockFromParams(*block); } } else { MObject thisNode = thisMObject(); MPlug blindPlug (thisNode, shaveBlindHair); blindPlug.setValue((MPxData*) newData); if (updateParams) { updatePlugsFromParams(); } } LEAVE(); } /* * Delete all the stat files associated with this shaveHairShape. * Called under the same condtions as MAYArefresh() and MAYAxform() */ void shaveHairShape::cleanStatFiles(MDataBlock& block) { ENTER(); // // MEL has functions for manipulating files under different platforms, // so let's pass the bulk of this work to a script. // MGlobal::executeCommand( MString("shave_removeStatFiles(\"") + nodeName() + "\")" ); doDyn = kDynamicsOff; // Turn off dynamics. // block.outputValue(runDynamics).set((int)kDynamicsOff); LEAVE(); } void shaveHairShape::updateNodeName() { ENTER(); strncpy( hairnode.shavep.name, name().asChar(), sizeof(hairnode.shavep.name) ); hairnode.shavep.name[sizeof(hairnode.shavep.name)-1] = '\0'; LEAVE(); } // Copy the shave engine's param values into their corresponding node // attributes. // // This method sets the attr values using a datablock and therefore // safe to be called during compute(). // void shaveHairShape::updateDatablockFromParams(MDataBlock& block) { MDataHandle hdl; //printf("shave plugs updated\n");fflush(stdout); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::updateDatablockFromParams", NULL); #endif ENTER(); int index = getHairGroup(&block); block.outputValue(shaveParamHaircount).set((int)hairnode.shavep.haircount[index]); block.outputValue(shaveParamPasses).set((short)hairnode.shavep.passes[index]); bool di = hairnode.shavep.dontinterpolate < 1 ? true : false; block.outputValue(shaveParamNoInterp).set(di); block.outputValue(shaveParamTotalGuides).set((short)hairnode.shavep.total_guides); di = (hairnode.shavep.collide[index] > 0) ? true : false; block.outputValue(shaveParamCollide).set(di); MPlug plug(thisMObject(), aSpecularTint); block.outputValue(plug.child(0)).set(hairnode.shavep.spec_tint.x); block.outputValue(plug.child(1)).set(hairnode.shavep.spec_tint.y); block.outputValue(plug.child(2)).set(hairnode.shavep.spec_tint.z); plug.setAttribute(aSpecularTint2); block.outputValue(plug.child(0)).set(hairnode.shavep.spec_tint2.x); block.outputValue(plug.child(1)).set(hairnode.shavep.spec_tint2.y); block.outputValue(plug.child(2)).set(hairnode.shavep.spec_tint2.z); block.outputValue(aTipFade).set(hairnode.shavep.tipfade != 0); block.outputValue(aSquirrel).set(hairnode.shavep.squirrel != 0); block.outputValue(flyawayPerc).set((int)(hairnode.shavep.flyaway_percent*100.0f)); block.outputValue(clumps).set(hairnode.shavep.clumps); #if 1 // // At the moment we only support collision method 1. However, due to // an earlier bug, there may be some old shaveHairShapes out there with // collision method 0. So let's force it back to 1. // // %%% When we add support other collision methods, this will have // to change. // block.outputValue(shaveParamCollisionMethod).set((short)1); #else block.outputValue(shaveParamCollisionMethod).set((short)hairnode.shavep.collision_method); #endif block.outputValue(shaveParamFrizzAnimDirX).set(hairnode.shavep.frizz_anim_dir[index].x); block.outputValue(shaveParamFrizzAnimDirY).set(hairnode.shavep.frizz_anim_dir[index].y); block.outputValue(shaveParamFrizzAnimDirZ).set(hairnode.shavep.frizz_anim_dir[index].z); block.outputValue(shaveParamSegs).set((short)hairnode.shavep.segs[index]); block.outputValue(shaveParamGeomShadow).set(hairnode.shavep.geom_shadow); block.outputValue(randomSeedOffset).set(hairnode.shavep.rand_seed_offset); block.outputValue(shaveParamFrizzRoot).set(hairnode.shavep.slider_val[0][index]); block.outputValue(shaveParamFrizzTip).set(hairnode.shavep.slider_val[24][index]); block.outputValue(shaveParamFrizzFreqX).set(hairnode.shavep.slider_val[1][index]); block.outputValue(shaveParamFrizzFreqY).set(hairnode.shavep.slider_val[30][index]); block.outputValue(shaveParamFrizzFreqZ).set(hairnode.shavep.slider_val[31][index]); block.outputValue(shaveParamFrizzAnim).set(hairnode.shavep.slider_val[32][index]); block.outputValue(shaveParamKinkRoot).set(hairnode.shavep.slider_val[38][index]); block.outputValue(shaveParamKinkTip).set(hairnode.shavep.slider_val[2][index]); block.outputValue(shaveParamKinkFreqX).set(hairnode.shavep.slider_val[3][index]); block.outputValue(shaveParamKinkFreqY).set(hairnode.shavep.slider_val[34][index]); block.outputValue(shaveParamKinkFreqZ).set(hairnode.shavep.slider_val[35][index]); block.outputValue(shaveParamSplayRoot).set(hairnode.shavep.slider_val[26][index]); block.outputValue(shaveParamSplayTip).set(hairnode.shavep.slider_val[27][index]); //vlad|05July2010 block.outputValue(shaveParamMultAsp).set(hairnode.shavep.slider_val[44][index]); //vlad|09July2010 block.outputValue(shaveParamOffset).set(hairnode.shavep.slider_val[45][index]); //vlad|09July2010 block.outputValue(shaveParamAspect).set(hairnode.shavep.slider_val[46][index]); block.outputValue(shaveParamMultStrand).set((short)hairnode.shavep.slider_val[25][index]); block.outputValue(shaveParamRandomizeMulti).set(hairnode.shavep.slider_val[42][index]); block.outputValue(shaveParamThickRoot).set(hairnode.shavep.slider_val[20][index]); //guide thikness, to get the value from preset, nope it updates all the time //block.outputValue(aDisplayGuideThick).set(hairnode.shavep.slider_val[20][index]); block.outputValue(shaveParamThickTip).set(hairnode.shavep.slider_val[37][index]); block.outputValue(shaveParamSpecular).set(hairnode.shavep.slider_val[4][index]); block.outputValue(shaveParamGloss).set(hairnode.shavep.slider_val[5][index]); block.outputValue(shaveParamAmbDiff).set(hairnode.shavep.slider_val[6][index]); block.outputValue(shaveParamSelfShadow).set(hairnode.shavep.slider_val[7][index]); block.outputValue(shaveParamStiffness).set(hairnode.shavep.slider_val[8][index]); block.outputValue(shaveParamRandScale).set(hairnode.shavep.slider_val[36][index]); block.outputValue(shaveParamAnimSpeed).set(hairnode.shavep.slider_val[33][index]); block.outputValue(shaveParamDisplacement).set(hairnode.shavep.slider_val[43][index]); block.outputValue(shaveParamRootRed).set((float)(hairnode.shavep.slider_val[17][index]/255.0f)); block.outputValue(shaveParamRootGreen).set((float)(hairnode.shavep.slider_val[18][index]/255.0f)); block.outputValue(shaveParamRootBlue).set((float)(hairnode.shavep.slider_val[19][index]/255.0f)); block.outputValue(shaveParamHairRed).set((float)(hairnode.shavep.slider_val[9][index]/255.0f)); block.outputValue(shaveParamHairGreen).set((float)(hairnode.shavep.slider_val[10][index]/255.0f)); block.outputValue(shaveParamHairBlue).set((float)(hairnode.shavep.slider_val[11][index]/255.0f)); block.outputValue(shaveParamMutantRed).set((float)(hairnode.shavep.slider_val[13][index]/255.0f)); block.outputValue(shaveParamMutantGreen).set((float)(hairnode.shavep.slider_val[14][index]/255.0f)); block.outputValue(shaveParamMutantBlue).set((float)(hairnode.shavep.slider_val[15][index]/255.0f)); block.outputValue(shaveParamHueVariation).set(hairnode.shavep.slider_val[12][index]); block.outputValue(shaveParamValueVariation).set(hairnode.shavep.slider_val[39][index]); block.outputValue(shaveParamMutantPercent).set(hairnode.shavep.slider_val[16][index]); block.outputValue(shaveParamDampening).set(hairnode.shavep.slider_val[40][index]); block.outputValue(shaveParamRootStiffness).set(hairnode.shavep.slider_val[21][index]); block.outputValue(shaveParamScale).set(hairnode.shavep.slider_val[41][index]); block.outputValue(flyawayStren).set(hairnode.shavep.slider_val[48][index]); block.outputValue(messStren).set(hairnode.shavep.slider_val[47][index]); block.outputValue(clumpsStren).set(hairnode.shavep.slider_val[49][index]); block.outputValue(clumpsRotStren).set(hairnode.shavep.slider_val[50][index]); block.outputValue(clumpsColStren).set(hairnode.shavep.slider_val[51][index]); block.outputValue(clumpsRotOffset).set(hairnode.shavep.slider_val[52][index]); block.outputValue(clumpsRandomize).set(hairnode.shavep.slider_val[53][index]); block.outputValue(clumpsFlatness).set(hairnode.shavep.slider_val[54][index]); block.outputValue(clumpsScruffle).set(hairnode.shavep.slider_val[55][index]); #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::updateDatablockFromParams -- done", NULL); #endif LEAVE(); } // Copy the shave engine's param values into their corresponding node // attributes. // // This method sets the attr values using plugs and therefore should not be // called during compute(). // void shaveHairShape::updatePlugsFromParams() { //printf("shave plugs updated\n");fflush(stdout); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::updatePlugsFromParams", NULL); #endif ENTER(); int index = getHairGroup(); MPlug plug(thisMObject(), shaveParamHaircount); plug.setValue((int)hairnode.shavep.haircount[index]); plug.setAttribute(shaveParamPasses); plug.setValue((short)hairnode.shavep.passes[index]); plug.setAttribute(shaveParamNoInterp); bool di = hairnode.shavep.dontinterpolate < 1 ? true : false; plug.setValue(di); plug.setAttribute(shaveParamTotalGuides); plug.setValue((short)hairnode.shavep.total_guides); plug.setAttribute(shaveParamCollide); di = (hairnode.shavep.collide[index] > 0) ? true : false; plug.setValue(di); plug.setAttribute(aSpecularTint); plug.child(0).setValue(hairnode.shavep.spec_tint.x); plug.child(1).setValue(hairnode.shavep.spec_tint.y); plug.child(2).setValue(hairnode.shavep.spec_tint.z); plug.setAttribute(aSpecularTint2); plug.child(0).setValue(hairnode.shavep.spec_tint2.x); plug.child(1).setValue(hairnode.shavep.spec_tint2.y); plug.child(2).setValue(hairnode.shavep.spec_tint2.z); plug.setAttribute(aTipFade); plug.setValue(hairnode.shavep.tipfade != 0); plug.setAttribute(aSquirrel); plug.setValue(hairnode.shavep.squirrel != 0); plug.setAttribute(flyawayPerc); plug.setValue((int)(hairnode.shavep.flyaway_percent*100.0f)); plug.setAttribute(clumps); plug.setValue(hairnode.shavep.clumps); plug.setAttribute(shaveParamCollisionMethod); #if 1 // // At the moment we only support collision method 1. However, due to // an earlier bug, there may be some old shaveHairShapes out there with // collision method 0. So let's force it back to 1. // // %%% When we add support other collision methods, this will have // to change. // plug.setValue((short)1); #else plug.setValue((short)hairnode.shavep.collision_method); #endif plug.setAttribute(shaveParamFrizzAnimDirX); plug.setValue(hairnode.shavep.frizz_anim_dir[index].x); plug.setAttribute(shaveParamFrizzAnimDirY); plug.setValue(hairnode.shavep.frizz_anim_dir[index].y); plug.setAttribute(shaveParamFrizzAnimDirZ); plug.setValue(hairnode.shavep.frizz_anim_dir[index].z); plug.setAttribute(shaveParamSegs); plug.setValue((short)hairnode.shavep.segs[index]); plug.setAttribute(shaveParamGeomShadow); plug.setValue(hairnode.shavep.geom_shadow); plug.setAttribute(randomSeedOffset); plug.setValue(hairnode.shavep.rand_seed_offset); plug.setAttribute(shaveParamFrizzRoot); plug.setValue(hairnode.shavep.slider_val[0][index]); plug.setAttribute(shaveParamFrizzTip); plug.setValue(hairnode.shavep.slider_val[24][index]); plug.setAttribute(shaveParamFrizzFreqX); plug.setValue(hairnode.shavep.slider_val[1][index]); plug.setAttribute(shaveParamFrizzFreqY); plug.setValue(hairnode.shavep.slider_val[30][index]); plug.setAttribute(shaveParamFrizzFreqZ); plug.setValue(hairnode.shavep.slider_val[31][index]); plug.setAttribute(shaveParamFrizzAnim); plug.setValue(hairnode.shavep.slider_val[32][index]); plug.setAttribute(shaveParamKinkRoot); plug.setValue(hairnode.shavep.slider_val[38][index]); plug.setAttribute(shaveParamKinkTip); plug.setValue(hairnode.shavep.slider_val[2][index]); plug.setAttribute(shaveParamKinkFreqX); plug.setValue(hairnode.shavep.slider_val[3][index]); plug.setAttribute(shaveParamKinkFreqY); plug.setValue(hairnode.shavep.slider_val[34][index]); plug.setAttribute(shaveParamKinkFreqZ); plug.setValue(hairnode.shavep.slider_val[35][index]); plug.setAttribute(shaveParamSplayRoot); plug.setValue(hairnode.shavep.slider_val[26][index]); plug.setAttribute(shaveParamSplayTip); plug.setValue(hairnode.shavep.slider_val[27][index]); plug.setAttribute(shaveParamMultAsp); //vlad|05July2010 plug.setValue(hairnode.shavep.slider_val[44][index]); plug.setAttribute(shaveParamOffset); //vlad|09July2010 plug.setValue(hairnode.shavep.slider_val[45][index]); plug.setAttribute(shaveParamAspect); //vlad|09July2010 plug.setValue(hairnode.shavep.slider_val[46][index]); plug.setAttribute(shaveParamMultStrand); plug.setValue((short)hairnode.shavep.slider_val[25][index]); plug.setAttribute(shaveParamRandomizeMulti); plug.setValue(hairnode.shavep.slider_val[42][index]); plug.setAttribute(shaveParamThickRoot); plug.setValue(hairnode.shavep.slider_val[20][index]); //guide thikness, to get the value from preset, nope it updates all the time //plug.setAttribute(aDisplayGuideThick); //plug.setValue(hairnode.shavep.slider_val[20][index]); plug.setAttribute(shaveParamThickTip); plug.setValue(hairnode.shavep.slider_val[37][index]); plug.setAttribute(shaveParamSpecular); plug.setValue(hairnode.shavep.slider_val[4][index]); plug.setAttribute(shaveParamGloss); plug.setValue(hairnode.shavep.slider_val[5][index]); plug.setAttribute(shaveParamAmbDiff); plug.setValue(hairnode.shavep.slider_val[6][index]); plug.setAttribute(shaveParamSelfShadow); plug.setValue(hairnode.shavep.slider_val[7][index]); plug.setAttribute(shaveParamStiffness); plug.setValue(hairnode.shavep.slider_val[8][index]); plug.setAttribute(shaveParamRandScale); plug.setValue(hairnode.shavep.slider_val[36][index]); plug.setAttribute(shaveParamAnimSpeed); plug.setValue(hairnode.shavep.slider_val[33][index]); plug.setAttribute(shaveParamDisplacement); plug.setValue(hairnode.shavep.slider_val[43][index]); plug.setAttribute(shaveParamRootRed); plug.setValue((float)(hairnode.shavep.slider_val[17][index]/255.0f)); plug.setAttribute(shaveParamRootGreen); plug.setValue((float)(hairnode.shavep.slider_val[18][index]/255.0f)); plug.setAttribute(shaveParamRootBlue); plug.setValue((float)(hairnode.shavep.slider_val[19][index]/255.0f)); plug.setAttribute(shaveParamHairRed); plug.setValue((float)(hairnode.shavep.slider_val[9][index]/255.0f)); plug.setAttribute(shaveParamHairGreen); plug.setValue((float)(hairnode.shavep.slider_val[10][index]/255.0f)); plug.setAttribute(shaveParamHairBlue); plug.setValue((float)(hairnode.shavep.slider_val[11][index]/255.0f)); plug.setAttribute(shaveParamMutantRed); plug.setValue((float)(hairnode.shavep.slider_val[13][index]/255.0f)); plug.setAttribute(shaveParamMutantGreen); plug.setValue((float)(hairnode.shavep.slider_val[14][index]/255.0f)); plug.setAttribute(shaveParamMutantBlue); plug.setValue((float)(hairnode.shavep.slider_val[15][index]/255.0f)); plug.setAttribute(shaveParamHueVariation); plug.setValue(hairnode.shavep.slider_val[12][index]); plug.setAttribute(shaveParamValueVariation); plug.setValue(hairnode.shavep.slider_val[39][index]); plug.setAttribute(shaveParamMutantPercent); plug.setValue(hairnode.shavep.slider_val[16][index]); plug.setAttribute(shaveParamDampening); plug.setValue(hairnode.shavep.slider_val[40][index]); plug.setAttribute(shaveParamRootStiffness); plug.setValue(hairnode.shavep.slider_val[21][index]); plug.setAttribute(shaveParamScale); plug.setValue(hairnode.shavep.slider_val[41][index]); plug.setAttribute(flyawayStren); plug.setValue(hairnode.shavep.slider_val[48][index]); plug.setAttribute(messStren); plug.setValue(hairnode.shavep.slider_val[47][index]); plug.setAttribute(clumpsStren); plug.setValue(hairnode.shavep.slider_val[49][index]); plug.setAttribute(clumpsRotStren); plug.setValue(hairnode.shavep.slider_val[50][index]); plug.setAttribute(clumpsColStren); plug.setValue(hairnode.shavep.slider_val[51][index]); plug.setAttribute(clumpsRotOffset); plug.setValue(hairnode.shavep.slider_val[52][index]); plug.setAttribute(clumpsRandomize); plug.setValue(hairnode.shavep.slider_val[53][index]); plug.setAttribute(clumpsFlatness); plug.setValue(hairnode.shavep.slider_val[54][index]); plug.setAttribute(clumpsScruffle); plug.setValue(hairnode.shavep.slider_val[55][index]); #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::updatePlugsFromParams -- done", NULL); #endif LEAVE(); } void shaveHairShape::updateParams() { ENTER(); updateParamsFromPlugs(getHairGroup()); LEAVE(); } void shaveHairShape::updateParamsFromDatablock(MDataBlock& data, int index) { //printf("shave params updated\n");fflush(stdout); ENTER(); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::updateParamsFromDatablock", NULL); #endif updateNodeName(); hairnode.shavep.slider_val[0][5] = gravityGlob; hairnode.shavep.haircount[index] = data.inputValue(shaveParamHaircount).asInt(); setShadowHaircount(index); bool di = data.inputValue (shaveParamNoInterp).asBool(); hairnode.shavep.dontinterpolate = di ? 0 : 1; hairnode.shavep.passes[index] = (int)data.inputValue(shaveParamPasses).asShort(); hairnode.shavep.total_guides = (int)data.inputValue(shaveParamTotalGuides).asShort(); di = data.inputValue (shaveParamCollide).asBool(); hairnode.shavep.collide[index] = di ? 1 : 0; hairnode.shavep.collision_method = (int)data.inputValue(shaveParamCollisionMethod).asShort(); float3& color = data.inputValue(aSpecularTint).asFloat3(); hairnode.shavep.spec_tint.x = color[0]; hairnode.shavep.spec_tint.y = color[1]; hairnode.shavep.spec_tint.z = color[2]; float3& color2 = data.inputValue(aSpecularTint2).asFloat3(); hairnode.shavep.spec_tint2.x = color2[0]; hairnode.shavep.spec_tint2.y = color2[1]; hairnode.shavep.spec_tint2.z = color2[2]; hairnode.shavep.tipfade = (data.inputValue(aTipFade).asBool() ? 1 : 0); hairnode.shavep.squirrel = (data.inputValue(aSquirrel).asBool() ? 1 : 0); hairnode.shavep.flyaway_percent = (float)data.inputValue(flyawayPerc).asInt()/100.0f; hairnode.shavep.clumps = data.inputValue(clumps).asInt(); hairnode.shavep.rand_seed_offset = data.inputValue(randomSeedOffset).asInt(); // // At the moment we only support collision method 1. However, due // to an earlier bug, there may be some old shaveHairShapes out there // with collision method 0. So let's force it back to 1. // // %%% When we add support for other collision methods, this will have // to change. // hairnode.shavep.collision_method = 1; hairnode.shavep.frizz_anim_dir[index].x = data.inputValue(shaveParamFrizzAnimDirX).asFloat(); hairnode.shavep.frizz_anim_dir[index].y = data.inputValue(shaveParamFrizzAnimDirY).asFloat(); hairnode.shavep.frizz_anim_dir[index].z = data.inputValue(shaveParamFrizzAnimDirZ).asFloat(); hairnode.shavep.segs[index] = (int)data.inputValue(shaveParamSegs).asShort(); hairnode.shavep.geom_shadow = data.inputValue(shaveParamGeomShadow).asFloat(); hairnode.shavep.slider_val[0][index] = data.inputValue(shaveParamFrizzRoot).asFloat(); hairnode.shavep.slider_val[24][index] = data.inputValue(shaveParamFrizzTip).asFloat(); hairnode.shavep.slider_val[1][index] = data.inputValue(shaveParamFrizzFreqX).asFloat(); hairnode.shavep.slider_val[30][index] = data.inputValue(shaveParamFrizzFreqY).asFloat(); hairnode.shavep.slider_val[31][index] = data.inputValue(shaveParamFrizzFreqZ).asFloat(); hairnode.shavep.slider_val[32][index] = data.inputValue(shaveParamFrizzAnim).asFloat(); hairnode.shavep.slider_val[38][index] = data.inputValue(shaveParamKinkRoot).asFloat(); hairnode.shavep.slider_val[2][index] = data.inputValue(shaveParamKinkTip).asFloat(); hairnode.shavep.slider_val[3][index] = data.inputValue(shaveParamKinkFreqX).asFloat(); hairnode.shavep.slider_val[34][index] = data.inputValue(shaveParamKinkFreqY).asFloat(); hairnode.shavep.slider_val[35][index] = data.inputValue(shaveParamKinkFreqZ).asFloat(); hairnode.shavep.slider_val[26][index] = data.inputValue(shaveParamSplayRoot).asFloat(); hairnode.shavep.slider_val[27][index] = data.inputValue(shaveParamSplayTip).asFloat(); hairnode.shavep.slider_val[44][index] = data.inputValue(shaveParamMultAsp).asFloat(); //vlad|05July2010 hairnode.shavep.slider_val[45][index] = data.inputValue(shaveParamOffset).asFloat(); //vlad|09July2010 hairnode.shavep.slider_val[46][index] = data.inputValue(shaveParamAspect).asFloat(); //vlad|09July2010 hairnode.shavep.slider_val[25][index] = (float)data.inputValue(shaveParamMultStrand).asShort(); hairnode.shavep.slider_val[42][index] = data.inputValue(shaveParamRandomizeMulti).asFloat(); hairnode.shavep.slider_val[20][index] = data.inputValue(shaveParamThickRoot).asFloat(); hairnode.shavep.slider_val[37][index] = data.inputValue(shaveParamThickTip).asFloat(); hairnode.shavep.slider_val[4][index] = data.inputValue(shaveParamSpecular).asFloat(); hairnode.shavep.slider_val[5][index] = data.inputValue(shaveParamGloss).asFloat(); hairnode.shavep.slider_val[6][index] = data.inputValue(shaveParamAmbDiff).asFloat(); hairnode.shavep.slider_val[7][index] = data.inputValue(shaveParamSelfShadow).asFloat(); hairnode.shavep.slider_val[8][index] = data.inputValue(shaveParamStiffness).asFloat(); hairnode.shavep.slider_val[36][index] = data.inputValue(shaveParamRandScale).asFloat(); hairnode.shavep.slider_val[33][index] = data.inputValue(shaveParamAnimSpeed).asFloat(); hairnode.shavep.slider_val[43][index] = data.inputValue(shaveParamDisplacement).asFloat(); hairnode.shavep.slider_val[17][index] = data.inputValue(shaveParamRootRed).asFloat()*255.0f; hairnode.shavep.slider_val[18][index] = data.inputValue(shaveParamRootGreen).asFloat()*255.0f; hairnode.shavep.slider_val[19][index] = data.inputValue(shaveParamRootBlue).asFloat()*255.0f; hairnode.shavep.slider_val[9][index] = data.inputValue(shaveParamHairRed).asFloat()*255.0f; hairnode.shavep.slider_val[10][index] = data.inputValue(shaveParamHairGreen).asFloat()*255.0f; hairnode.shavep.slider_val[11][index] = data.inputValue(shaveParamHairBlue).asFloat()*255.0f; hairnode.shavep.slider_val[13][index] = data.inputValue(shaveParamMutantRed).asFloat()*255.0f; hairnode.shavep.slider_val[14][index] = data.inputValue(shaveParamMutantGreen).asFloat()*255.0f; hairnode.shavep.slider_val[15][index] = data.inputValue(shaveParamMutantBlue).asFloat()*255.0f; hairnode.shavep.slider_val[12][index] = data.inputValue(shaveParamHueVariation).asFloat(); hairnode.shavep.slider_val[39][index] = data.inputValue(shaveParamValueVariation).asFloat(); hairnode.shavep.slider_val[16][index] = data.inputValue(shaveParamMutantPercent).asFloat(); hairnode.shavep.slider_val[40][index] = data.inputValue(shaveParamDampening).asFloat(); hairnode.shavep.slider_val[21][index] = data.inputValue(shaveParamRootStiffness).asFloat(); hairnode.shavep.slider_val[41][index] = data.inputValue(shaveParamScale).asFloat(); hairnode.shavep.slider_val[48][index] = data.inputValue(flyawayStren).asFloat(); hairnode.shavep.slider_val[47][index] = data.inputValue(messStren).asFloat(); hairnode.shavep.slider_val[49][index] = data.inputValue(clumpsStren).asFloat(); hairnode.shavep.slider_val[50][index] = data.inputValue(clumpsRotStren).asFloat(); hairnode.shavep.slider_val[51][index] = data.inputValue(clumpsColStren).asFloat(); hairnode.shavep.slider_val[52][index] = data.inputValue(clumpsRotOffset).asFloat(); hairnode.shavep.slider_val[53][index] = data.inputValue(clumpsRandomize).asFloat(); hairnode.shavep.slider_val[54][index] = data.inputValue(clumpsFlatness).asFloat(); hairnode.shavep.slider_val[55][index] = data.inputValue(clumpsScruffle).asFloat(); #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::updateParamsFromDatablock - done", NULL); #endif LEAVE(); } void shaveHairShape::updateParamsFromPlugs(int index) { //printf("shave params updated\n");fflush(stdout); ENTER(); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::updateParamsFromPlugs", NULL); #endif updateNodeName(); MFnDependencyNode nodeFn(thisMObject()); hairnode.shavep.slider_val[0][5] = gravityGlob; hairnode.shavep.haircount[index] = nodeFn.findPlug(shaveParamHaircount, true).asInt(); setShadowHaircount(index); bool di = nodeFn.findPlug(shaveParamNoInterp, true).asBool(); hairnode.shavep.dontinterpolate = di ? 0 : 1; hairnode.shavep.passes[index] = (int)nodeFn.findPlug(shaveParamPasses, true).asShort(); hairnode.shavep.total_guides = (int)nodeFn.findPlug(shaveParamTotalGuides, true).asShort(); di = nodeFn.findPlug(shaveParamCollide, true).asBool(); hairnode.shavep.collide[index] = di ? 1 : 0; hairnode.shavep.collision_method = (int)nodeFn.findPlug(shaveParamCollisionMethod, true).asShort(); hairnode.shavep.spec_tint.x = nodeFn.findPlug(aSpecularTint, true).child(0).asFloat(); hairnode.shavep.spec_tint.y = nodeFn.findPlug(aSpecularTint, true).child(1).asFloat(); hairnode.shavep.spec_tint.z = nodeFn.findPlug(aSpecularTint, true).child(2).asFloat(); hairnode.shavep.spec_tint2.x = nodeFn.findPlug(aSpecularTint2, true).child(0).asFloat(); hairnode.shavep.spec_tint2.y = nodeFn.findPlug(aSpecularTint2, true).child(1).asFloat(); hairnode.shavep.spec_tint2.z = nodeFn.findPlug(aSpecularTint2, true).child(2).asFloat(); hairnode.shavep.tipfade = (nodeFn.findPlug(aTipFade, true).asBool() ? 1 : 0); hairnode.shavep.squirrel = (nodeFn.findPlug(aSquirrel, true).asBool() ? 1 : 0); hairnode.shavep.flyaway_percent = (float)nodeFn.findPlug(flyawayPerc, true).asInt()/100.0f; hairnode.shavep.clumps = nodeFn.findPlug(clumps, true).asInt(); hairnode.shavep.rand_seed_offset = nodeFn.findPlug(randomSeedOffset, true).asInt(); // // At the moment we only support collision method 1. However, due // to an earlier bug, there may be some old shaveHairShapes out there // with collision method 0. So let's force it back to 1. // // %%% When we add support for other collision methods, this will have // to change. // hairnode.shavep.collision_method = 1; hairnode.shavep.frizz_anim_dir[index].x = nodeFn.findPlug(shaveParamFrizzAnimDirX, true).asFloat(); hairnode.shavep.frizz_anim_dir[index].y = nodeFn.findPlug(shaveParamFrizzAnimDirY, true).asFloat(); hairnode.shavep.frizz_anim_dir[index].z = nodeFn.findPlug(shaveParamFrizzAnimDirZ, true).asFloat(); hairnode.shavep.segs[index] = (int)nodeFn.findPlug(shaveParamSegs, true).asShort(); hairnode.shavep.geom_shadow = nodeFn.findPlug(shaveParamGeomShadow, true).asFloat(); hairnode.shavep.slider_val[0][index] = nodeFn.findPlug(shaveParamFrizzRoot, true).asFloat(); hairnode.shavep.slider_val[24][index] = nodeFn.findPlug(shaveParamFrizzTip, true).asFloat(); hairnode.shavep.slider_val[1][index] = nodeFn.findPlug(shaveParamFrizzFreqX, true).asFloat(); hairnode.shavep.slider_val[30][index] = nodeFn.findPlug(shaveParamFrizzFreqY, true).asFloat(); hairnode.shavep.slider_val[31][index] = nodeFn.findPlug(shaveParamFrizzFreqZ, true).asFloat(); hairnode.shavep.slider_val[32][index] = nodeFn.findPlug(shaveParamFrizzAnim, true).asFloat(); hairnode.shavep.slider_val[38][index] = nodeFn.findPlug(shaveParamKinkRoot, true).asFloat(); hairnode.shavep.slider_val[2][index] = nodeFn.findPlug(shaveParamKinkTip, true).asFloat(); hairnode.shavep.slider_val[3][index] = nodeFn.findPlug(shaveParamKinkFreqX, true).asFloat(); hairnode.shavep.slider_val[34][index] = nodeFn.findPlug(shaveParamKinkFreqY, true).asFloat(); hairnode.shavep.slider_val[35][index] = nodeFn.findPlug(shaveParamKinkFreqZ, true).asFloat(); hairnode.shavep.slider_val[26][index] = nodeFn.findPlug(shaveParamSplayRoot, true).asFloat(); hairnode.shavep.slider_val[27][index] = nodeFn.findPlug(shaveParamSplayTip, true).asFloat(); hairnode.shavep.slider_val[44][index] = nodeFn.findPlug(shaveParamMultAsp, true).asFloat(); hairnode.shavep.slider_val[45][index] = nodeFn.findPlug(shaveParamOffset, true).asFloat(); hairnode.shavep.slider_val[46][index] = nodeFn.findPlug(shaveParamAspect, true).asFloat(); hairnode.shavep.slider_val[25][index] = (float)nodeFn.findPlug(shaveParamMultStrand, true).asShort(); hairnode.shavep.slider_val[42][index] = nodeFn.findPlug(shaveParamRandomizeMulti, true).asFloat(); hairnode.shavep.slider_val[20][index] = nodeFn.findPlug(shaveParamThickRoot, true).asFloat(); hairnode.shavep.slider_val[37][index] = nodeFn.findPlug(shaveParamThickTip, true).asFloat(); hairnode.shavep.slider_val[4][index] = nodeFn.findPlug(shaveParamSpecular, true).asFloat(); hairnode.shavep.slider_val[5][index] = nodeFn.findPlug(shaveParamGloss, true).asFloat(); hairnode.shavep.slider_val[6][index] = nodeFn.findPlug(shaveParamAmbDiff, true).asFloat(); hairnode.shavep.slider_val[7][index] = nodeFn.findPlug(shaveParamSelfShadow, true).asFloat(); hairnode.shavep.slider_val[8][index] = nodeFn.findPlug(shaveParamStiffness, true).asFloat(); hairnode.shavep.slider_val[36][index] = nodeFn.findPlug(shaveParamRandScale, true).asFloat(); hairnode.shavep.slider_val[33][index] = nodeFn.findPlug(shaveParamAnimSpeed, true).asFloat(); hairnode.shavep.slider_val[43][index] = nodeFn.findPlug(shaveParamDisplacement, true).asFloat(); hairnode.shavep.slider_val[17][index] = nodeFn.findPlug(shaveParamRootRed, true).asFloat()*255.0f; hairnode.shavep.slider_val[18][index] = nodeFn.findPlug(shaveParamRootGreen, true).asFloat()*255.0f; hairnode.shavep.slider_val[19][index] = nodeFn.findPlug(shaveParamRootBlue, true).asFloat()*255.0f; hairnode.shavep.slider_val[9][index] = nodeFn.findPlug(shaveParamHairRed, true).asFloat()*255.0f; hairnode.shavep.slider_val[10][index] = nodeFn.findPlug(shaveParamHairGreen, true).asFloat()*255.0f; hairnode.shavep.slider_val[11][index] = nodeFn.findPlug(shaveParamHairBlue, true).asFloat()*255.0f; hairnode.shavep.slider_val[13][index] = nodeFn.findPlug(shaveParamMutantRed, true).asFloat()*255.0f; hairnode.shavep.slider_val[14][index] = nodeFn.findPlug(shaveParamMutantGreen, true).asFloat()*255.0f; hairnode.shavep.slider_val[15][index] = nodeFn.findPlug(shaveParamMutantBlue, true).asFloat()*255.0f; hairnode.shavep.slider_val[12][index] = nodeFn.findPlug(shaveParamHueVariation, true).asFloat(); hairnode.shavep.slider_val[39][index] = nodeFn.findPlug(shaveParamValueVariation, true).asFloat(); hairnode.shavep.slider_val[16][index] = nodeFn.findPlug(shaveParamMutantPercent, true).asFloat(); hairnode.shavep.slider_val[40][index] = nodeFn.findPlug(shaveParamDampening, true).asFloat(); hairnode.shavep.slider_val[21][index] = nodeFn.findPlug(shaveParamRootStiffness, true).asFloat(); hairnode.shavep.slider_val[41][index] = nodeFn.findPlug(shaveParamScale, true).asFloat(); hairnode.shavep.slider_val[48][index] = nodeFn.findPlug(flyawayStren, true).asFloat(); hairnode.shavep.slider_val[47][index] = nodeFn.findPlug(messStren, true).asFloat(); hairnode.shavep.slider_val[49][index] = nodeFn.findPlug(clumpsStren, true).asFloat(); hairnode.shavep.slider_val[50][index] = nodeFn.findPlug(clumpsRotStren, true).asFloat(); hairnode.shavep.slider_val[51][index] = nodeFn.findPlug(clumpsColStren, true).asFloat(); hairnode.shavep.slider_val[52][index] = nodeFn.findPlug(clumpsRotOffset, true).asFloat(); hairnode.shavep.slider_val[53][index] = nodeFn.findPlug(clumpsRandomize, true).asFloat(); hairnode.shavep.slider_val[54][index] = nodeFn.findPlug(clumpsFlatness, true).asFloat(); hairnode.shavep.slider_val[55][index] = nodeFn.findPlug(clumpsScruffle, true).asFloat(); #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::updateParamsFromPlugs - done", NULL); #endif LEAVE(); } void shaveHairShape::attrDebug(int index) { for(index = 0; index < 5; index++) { printf("shave MTL index is: %d\n", index); /* printf("%d\n", hairnode.shavep.haircount[index]); printf("%d\n", hairnode.shavep.dontinterpolate); */ printf("PASSES: %d\n", hairnode.shavep.passes[index]); /* printf("%d\n", hairnode.shavep.total_guides); printf("%d\n", hairnode.shavep.instancing_status); printf("%d\n", hairnode.shavep.collide[index]); printf("%d\n", hairnode.shavep.collision_method); printf("%f\n", hairnode.shavep.frizz_anim_dir[index].x); printf("%f\n", hairnode.shavep.frizz_anim_dir[index].y); printf("%f\n", hairnode.shavep.frizz_anim_dir[index].z); printf("%d\n", hairnode.shavep.segs[index]); printf("%f\n", hairnode.shavep.slider_val[0][index]); printf("%f\n", hairnode.shavep.slider_val[24][index]); printf("%f\n", hairnode.shavep.slider_val[1][index]); printf("%f\n", hairnode.shavep.slider_val[30][index]); printf("%f\n", hairnode.shavep.slider_val[31][index]); printf("%f\n", hairnode.shavep.slider_val[32][index]); printf("%f\n", hairnode.shavep.slider_val[38][index]); printf("%f\n", hairnode.shavep.slider_val[2][index]); printf("%f\n", hairnode.shavep.slider_val[3][index]); printf("%f\n", hairnode.shavep.slider_val[34][index]); printf("%f\n", hairnode.shavep.slider_val[35][index]); printf("%f\n", hairnode.shavep.slider_val[26][index]); printf("%f\n", hairnode.shavep.slider_val[27][index]); printf("%f\n", hairnode.shavep.slider_val[25][index]); printf("%f\n", hairnode.shavep.slider_val[20][index]); printf("%f\n", hairnode.shavep.slider_val[37][index]); printf("%f\n", hairnode.shavep.slider_val[4][index]); printf("%f\n", hairnode.shavep.slider_val[5][index]); printf("%f\n", hairnode.shavep.slider_val[6][index]); printf("%f\n", hairnode.shavep.slider_val[7][index]); printf("%f\n", hairnode.shavep.slider_val[8][index]); printf("%f\n", hairnode.shavep.slider_val[36][index]); printf("%f\n", hairnode.shavep.slider_val[33][index]); printf("%f\n", hairnode.shavep.slider_val[40][index]); printf("%f\n", hairnode.shavep.slider_val[17][index]); printf("%f\n", hairnode.shavep.slider_val[18][index]); printf("%f\n", hairnode.shavep.slider_val[19][index]); printf("%f\n", hairnode.shavep.slider_val[9][index]); printf("%f\n", hairnode.shavep.slider_val[10][index]); printf("%f\n", hairnode.shavep.slider_val[11][index]); printf("%f\n", hairnode.shavep.slider_val[13][index]); printf("%f\n", hairnode.shavep.slider_val[14][index]); printf("%f\n", hairnode.shavep.slider_val[15][index]); printf("%f\n", hairnode.shavep.slider_val[12][index]); printf("%f\n", hairnode.shavep.slider_val[39][index]); printf("%f\n", hairnode.shavep.slider_val[16][index]); */ } } MString shaveHairShape::nodeName() { ENTER(); MStringArray split; MString nodename; name().split(':', split); if(split.length() == 1) nodename = split[0]; else { nodename = split[0]; for(int i = 1; i < (int)split.length(); i++) { nodename += "_"; nodename += split[i]; } } RETURN(nodename); } MObject shaveHairShape::getDisplayShape() { ENTER(); // // The display node's shape should be connected to the // 'displayNode' attribute. // MPlug plug(thisMObject(), displayNodeAttr); MPlugArray connections; plug.connectedTo(connections, true, false); if (connections.length() == 0) { RETURN(MObject::kNullObj); } RETURN(connections[0].node()); } MObject shaveHairShape::getDisplayTransform() { ENTER(); MFnDagNode dagNodeFn(getDisplayShape()); // // Return the first parent transform (there should only be one). // RETURN(dagNodeFn.parent(0)); } MObject shaveHairShape::getShader() { ENTER(); MObject displayNode = getDisplayShape(); if (!displayNode.isNull()) { MFnDependencyNode nodeFn(displayNode); MPlug groupPlug = nodeFn.findPlug("instObjGroups"); if (groupPlug.numConnectedElements() > 0) { MPlugArray groupConnections; groupPlug .connectionByPhysicalIndex(0) .connectedTo(groupConnections, false, true); // // If we're connected to a shadingEngine node, then we're done. // if (groupConnections.length() > 0) { #if 0 MObject shader = groupConnections[0].node(); ////////// debug ///////// //MFnDependencyNode dFn(shader); //MGlobal::displayError(MString("? shader ") + dFn.name()); ////////////////////////// if (shader.hasFn(MFn::kShadingEngine)) { RETURN(shader); } #else for(unsigned int i = 0; i < groupConnections.length(); i++) { MObject shader = groupConnections[i].node(); if (shader.hasFn(MFn::kShadingEngine)) { #ifdef _DEBUG MFnDependencyNode dFn(shader); MGlobal::displayInfo(MString("shader ") + dFn.name()); #endif RETURN(shader); } } #endif } } } RETURN(MObject::kNullObj); } unsigned int shaveHairShape::numLayers() { ENTER(); int n=0; MObject displayNode = getDisplayShape(); if (!displayNode.isNull()) { MFnDependencyNode nodeFn(displayNode); MPlug groupPlug = nodeFn.findPlug("instObjGroups"); if (groupPlug.numConnectedElements() > 0) { MPlugArray groupConnections; groupPlug .connectionByPhysicalIndex(0) .connectedTo(groupConnections, false, true); for(unsigned int i = 0; i < groupConnections.length(); i++) { MObject c = groupConnections[i].node(); if(c.hasFn( MFn::kRenderLayer)) n++; } } } RETURN(n); } MObject shaveHairShape::getLayer(unsigned int idx) { ENTER(); MObject displayNode = getDisplayShape(); if (!displayNode.isNull()) { MFnDependencyNode nodeFn(displayNode); MPlug groupPlug = nodeFn.findPlug("instObjGroups"); int k = 0; if (groupPlug.numConnectedElements() > 0) { MPlugArray groupConnections; groupPlug .connectionByPhysicalIndex(0) .connectedTo(groupConnections, false, true); for(unsigned int i = 0; i < groupConnections.length(); i++) { MObject c = groupConnections[i].node(); if(c.hasFn( MFn::kRenderLayer) && idx == k) RETURN(c); k++; } } } RETURN(MObject::kNullObj); } // This is a compute-safe method which retrieves geom data from the // passed-in datablock, not from MPlugs. MStatus shaveHairShape::getGeomFromArrayAttr( MDataBlock& block, MObject arrayAttr, MObject componentGroupIDAttr, MObjectArray& geoms, MObjectArray& components ) { ENTER(); // // Step through each connected element in the array. // // 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. // MArrayDataHandle arrayHdl = block.inputArrayValue(arrayAttr); MPlug arrayPlug(thisMObject(), arrayAttr); MArrayDataHandle idArrayHdl = block.inputArrayValue(componentGroupIDAttr); unsigned int numElements = arrayPlug.evaluateNumElements(); unsigned int i; for (i = 0; i < numElements; i++) { MPlug plug = arrayPlug.elementByPhysicalIndex(i); MPlugArray srcPlugs; plug.connectedTo(srcPlugs, true, false); if (srcPlugs.length() > 0) { arrayHdl.jumpToElement(plug.logicalIndex()); MObject geomData = arrayHdl.inputValue().data(); geoms.append(geomData); // // If the caller passed us a groupID attribute, then we need to // grab the components as well. // MObject component(MObject::kNullObj); if (!componentGroupIDAttr.isNull() && geomData.hasFn(MFn::kGeometryData)) { // // Let's see if we have a group ID for this node. // MPlug idArray(thisMObject(), componentGroupIDAttr); MPlug idPlug = idArray.elementByLogicalIndex( plug.logicalIndex() ); if (idPlug.isConnected()) { idArrayHdl.jumpToElement(plug.logicalIndex()); int groupID = idArrayHdl.inputValue().asInt(); MFnGeometryData geomDataFn(geomData); if (geomDataFn.hasObjectGroup(groupID)) { // // Grab this group's component. // component = geomDataFn.objectGroupComponent(groupID); } } } components.append(component); } } RETURN(MS::kSuccess); } MStatus shaveHairShape::getSelectionListFromArrayAttr( MSelectionList& list, MObject arrayAttr, MObject componentGroupIDAttr ) { ENTER(); // // Step through each connected element in the array. // // 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. // MPlug destArrayPlug(thisMObject(), arrayAttr); unsigned int numDestElements = destArrayPlug.evaluateNumElements(); unsigned int i; for (i = 0; i < numDestElements; i++) { MPlug destPlug = destArrayPlug.elementByPhysicalIndex(i); MPlugArray srcPlugs; destPlug.connectedTo(srcPlugs, true, false); if (srcPlugs.length() > 0) { // // If the source node is not a DAG node, then just add it to // the list. // if (!srcPlugs[0].node().hasFn(MFn::kDagNode)) list.add(srcPlugs[0].node()); else { // // If the source plug is an array element then we will // assume that it is an instanced attribute and use its // index as our instance number. Otherwise we'll just take // the first instance for the node. // MDagPathArray paths; MDagPath::getAllPathsTo(srcPlugs[0].node(), paths); MDagPath instancePath = paths[0]; if (srcPlugs[0].isElement()) { unsigned instance = srcPlugs[0].logicalIndex(); if (instance < paths.length()) instancePath = paths[instance]; } // // If the caller passed us a groupID attribute, then we need to // grab the components as well. // MObject component(MObject::kNullObj); if (!componentGroupIDAttr.isNull()) { MObject geomData; destPlug.getValue(geomData); if (geomData.hasFn(MFn::kGeometryData)) { // // Let's see if we have a group ID for this node. // MPlug idArray(thisMObject(), componentGroupIDAttr); MPlug idPlug = idArray.elementByLogicalIndex( destPlug.logicalIndex() ); if (idPlug.isConnected()) { int groupID; idPlug.getValue(groupID); MFnGeometryData geomDataFn(geomData); if (geomDataFn.hasObjectGroup(groupID)) { // // Grab this group's component. // component = geomDataFn.objectGroupComponent(groupID); } } } } // // Add the node, and component if one was found, to the list. // list.add(instancePath, component); } } } RETURN(MS::kSuccess); } MStatus shaveHairShape::getCollisionGeom( MDataBlock& block, MObjectArray& geoms, MObjectArray& components ) { ENTER(); MStatus status; geoms.clear(); components.clear(); // // Having learned lessons from the growth list, the collision list is // contained in a single array attribute, which makes coding a bit // simpler. // status = getGeomFromArrayAttr( block, collisionObjectsAttr, collisionObjectsGroupIDAttr, geoms, components ); RETURN(status); } MStatus shaveHairShape::getGrowthGeom( MDataBlock& block, MObjectArray& geoms, MObjectArray& components ) { ENTER(); MStatus status; geoms.clear(); components.clear(); if (getHairGroup(&block) == 4) { // // We've got spline-based hair, so the only growth surfaces we care // about are curves. // status = getGeomFromArrayAttr( block, inputCurve, MObject::kNullObj, geoms, components ); } else { // // We've got surface-based hair. First, add the NURBS and subd // surfaces. // status = getGeomFromArrayAttr( block, inputSurf, MObject::kNullObj, geoms, components ); // // Now add the meshes. With meshes it's possible to grow from just // a subset of its faces, so we also pass down the array attribute // containing the component group IDs for the meshes. // if (status) { status = getGeomFromArrayAttr( block, inputMesh, growthObjectsGroupIDAttr, geoms, components ); } } RETURN(status); } MStatus shaveHairShape::getCollisionList(MSelectionList& list) { ENTER(); MStatus status; list.clear(); // // Having learned lessons from the growth list, the collision list is // contained in a single array attribute, which makes coding a bit // simpler. // status = getSelectionListFromArrayAttr( list, collisionObjectsAttr, collisionObjectsGroupIDAttr ); RETURN(status); } MStatus shaveHairShape::getGrowthList(MSelectionList& list) { ENTER(); MStatus status; list.clear(); if (getHairGroup() == 4) { // // We've got spline-based hair, so the only growth surfaces we care // about are curves. // status = getSelectionListFromArrayAttr(list, inputCurve); } else { // // We've got surface-based hair. First, add the NURBS and subd // surfaces. // status = getSelectionListFromArrayAttr(list, inputSurf); // // Now add the meshes. With meshes it's possible to grow from just // a subset of its faces, so we also pass down the array attribute // containing the component group IDs for the meshes. // if (status) { status = getSelectionListFromArrayAttr( list, inputMesh, growthObjectsGroupIDAttr ); } } RETURN(status); } MObject shaveHairShape::createGeomSet(const MObject& setAttr) { MStatus st; // Create the set. MDGModifier dgmod; MObject set = dgmod.createNode("objectSet", &st); MString setType = (setAttr == aGrowthSet ? "growth" : "collision"); // Rename it to something useful. MString setName = name() + "_" + setType; if (st) st = dgmod.renameNode(set, setName); // Connect its 'message' attr to the attr which was passed to us. if (st) { st = dgmod.connect( set, MPxObjectSet::message, thisMObject(), setAttr ); } // Commit the changes. if (st) st = dgmod.doIt(); if (!st || set.isNull()) { MGlobal::displayError( MString("shaveHairShape: cannot create ") + setType + " set" ); set = MObject::kNullObj; } return set; } MStatus shaveHairShape::setGeomList( const MObject& setAttr, const MSelectionList& list ) { MStatus st; MObject set = getGeomSet(setAttr); // If there's no set then create one. if (set.isNull()) { set = createGeomSet(setAttr); if (set.isNull()) return MS::kFailure; } MFnSet setFn(set, &st); setFn.clear(); // Maya displays a warning if the added list is empty. Let's avoid that. // if (list.length() > 0) { st = setFn.addMembers(list); } // Maya Bug: If we don't retrieve the new member list, the // dagSetMembers connections won't be there when we go // looking for them later. MSelectionList dummy; setFn.getMembers(dummy, false); // Update our geometry connections to reflect the new list. if (st) st = connectGeometry(setAttr, set); if (mNodeInitialized) { // If the set of surfaces to which we are attached is the same as // before, but the faces within those surfaces has changed, our // topology change detection code won't be able to detect that because // the WFTYPE has no indication of which faces within each surface are // growth faces: Shave keeps track of that info internally. So we must // force the node to do an xplant the next time it is evaluated. scheduleXplant(); } else { // Can't do an xplant yet. Let the node know that one will be // required once it's ready. // mXplantRequired = true; } return st; } // The DAG networks surrounding the object sets used to hold growth and // collision geometry sometimes get broken for reasons we've not yet // been able to identify. // // When you add an entire mesh to a set, Maya makes the following connection: // // (1a) mesh.instObjGroups[I] -> set.dagSetMembers[N] // // where 'I' is the instance number (usually 0) and N is the next available // index. // // If you only add a portion of a mesh (i.e. a subset of its components) // to a set, Maya will instead make these connection // // (1b) mesh.instObjGroups[I].objectGroups[M] -> set.dagSetMembers[N] // (2) groupIdNode.message -> set.groupNodes[N] // (3) groupIdNode.groupId -> mesh.instObjGroups[I].objectGroups[M].objectGroupId // // Where 'groupIdNode' is a groupId node that Maya creates automatically. // // Note that Maya also creates a groupParts node and makes connections to // it, but that's not of interest here. // // In either case, Shave will add the following connections: // // (4) mesh.worldMesh[I] -> hairNode.inputMesh[K] // (5) set.message -> hairNode.growthSet // // If only a portion of the mesh has been added, then Shave will make one // further connection: // // (6) mesh.instObjGroups[I].objectGroups[N].objectGroupId -> hairNode.growthObjectsGroupID[K] // // You can replace 'growth' with 'collision' when dealing with the collision // set. // // // This method attempts to fix the following problems: // // o Connection (1b) is present which means that only a subset of the // geometry's components are included in the set, but connection (6) // is missing. // // o Connection (5) is missing but there are some connection (6)s and // they all belong to the same set. // MStatus shaveHairShape::repairGeomList( const MObject& setAttr, const MObject& groupIdAttr ) { MStatus st; bool needRepair = false; MObject set = getGeomSet(setAttr); if (set.isNull()) { bool tooManySets = false; // There's no set connected to the attr. We need to repair that. // needRepair = true; // If there are any partial meshes in the set then there should be // one or more connection (6)s which we can follow back to the set. // MPlug groupIdPlug(thisMObject(), groupIdAttr); unsigned int numElements = groupIdPlug.evaluateNumElements(); for (unsigned int i = 0; (i < numElements) && !tooManySets; ++i) { // Find the source plug for this connection (6). // MPlug element = groupIdPlug.elementByPhysicalIndex(i); MPlugArray conns; element.connectedTo(conns, true, false); if (conns.length() == 0) continue; // We should now be at the objectGroupId plug on some // DAG node. Follow its connection (3) to the groupId node. // conns[0].connectedTo(conns, true, false); if (conns.length() == 0) continue; MObject groupIdNode = conns[0].node(); if (!groupIdNode.hasFn(MFn::kGroupId)) continue; // Follow the groupId node's message plug to all of its // destinations. One of them should be a connection (2) // to a set node. // MPlug msgPlug(groupIdNode, MPxNode::message); msgPlug.connectedTo(conns, false, true); unsigned int numDestinations = conns.length(); if (numDestinations == 0) continue; MObject thisGroupsSet; for (unsigned int j = 0; (j < numDestinations); ++j) { MObject destNode = conns[0].node(); if (destNode.hasFn(MFn::kSet)) { // If this is the first set that we've found for this // groupId, save it. // if (thisGroupsSet.isNull()) { thisGroupsSet = destNode; } else if (destNode != thisGroupsSet) { // This groupId is connected to multiple sets. // I don't *think* that's legal, but even if it is // there's no way for us to know which one is the // right one, so we must skip this group. // thisGroupsSet = MObject::kNullObj; break; } } } if (!thisGroupsSet.isNull()) { // If this is the first set we've found then save it. // if (set.isNull()) { set = thisGroupsSet; } else if (thisGroupsSet != set) { // There is no common set that all the partial meshes // agree on. We have to give up. // set = MObject::kNullObj; tooManySets = true; } } } // If we couldn't find an appropriate set then there's nothing more // we can do. // if (set.isNull()) { st = MS::kFailure; } else { // Connect the set's message attr to the setAttr which was passed // to us. i.e. make connection (5) // MDGModifier dgmod; st = dgmod.connect( set, MPxObjectSet::message, thisMObject(), setAttr ); if (st) { st = dgmod.doIt(); } } } else { // Check that for each connection (1b) to the set's // dagSetMembers plug, there is a corresponding connection (6) // to the groupIdAttr which was passed in to us. // MPlug dsmPlug(set, MPxObjectSet::dagSetMembers); unsigned int numElements = dsmPlug.evaluateNumElements(); for (unsigned int i = 0; (i < numElements) && !needRepair; ++i) { MPlug element = dsmPlug.elementByPhysicalIndex(i); MPlugArray conns; // Follow connection (1b) back to its source. // element.connectedTo(conns, true, false); if (conns.length() > 0) { MPlug plug = conns[0]; // If the source plug is some DAG node's objectGroups // attr, then step down to its objectGroupId child and see // if that has a connection (6) to our node's groupIdAttr. // if (plug == MPxTransform::objectGroups) { bool foundConnection = false; plug = plug.child(MPxTransform::objectGroupId); plug.connectedTo(conns, false, true); for (unsigned i = 0; (i < conns.length()) && !foundConnection; ++i) { foundConnection = (conns[i].node() == thisMObject()) &&(conns[i].attribute() == groupIdAttr); } if (!foundConnection) { needRepair = true; } } } } } // If we need repair, and nothing has gone wrong along the way, then redo // all the geometry connections. // if (needRepair && st) { st = connectGeometry(setAttr, set); } return st; } // Connect the geometry in a growth or collision set to the corresponding // attributes on the shaveHairShape. MStatus shaveHairShape::connectGeometry(MObject setAttr, MObject set) { MStatus st; // Get rid of all of our existing connections. MDGModifier* dgmod = new MDGModifier; MObject groupIDAttr; if (setAttr == aCollisionSet) { disconnectIncoming(collisionObjectsAttr, dgmod); groupIDAttr = collisionObjectsGroupIDAttr; } else if (setAttr == aGrowthSet) { bool splineLock; MPlug splineLockTrigger(thisMObject(), aSplineLockTrigger); splineLockTrigger.getValue(splineLock); if (!splineLock) disconnectIncoming(inputCurve, dgmod); disconnectIncoming(inputMesh, dgmod); disconnectIncoming(inputSurf, dgmod); groupIDAttr = growthObjectsGroupIDAttr; } else { delete dgmod; return MS::kInvalidParameter; } disconnectIncoming(groupIDAttr, dgmod); st = dgmod->doIt(); delete dgmod; dgmod = NULL; if (st && !set.isNull()) { MFnSet setFn(set, &st); // Step through all of the dagSetMembers connections and make // corresponding connections to this node. MPlugArray conns; MPlug dsmArray(set, MPxObjectSet::dagSetMembers); MPlug ourGroupArray(thisMObject(), groupIDAttr); MPlug setGroupArray(set, MPxObjectSet::groupNodes); MPlug element; unsigned int i; unsigned int nextIndex = 0; unsigned int numElements = dsmArray.evaluateNumElements(); dgmod = new MDGModifier; for (i = 0; i < numElements; ++i) { element = dsmArray.elementByPhysicalIndex(i); element.connectedTo(conns, true, false); if (conns.length() > 0) { MPlug plug = conns[0]; // Which attribute carries the shape's world geometry, // and to which attribute do we need to connect it? MObject shape = plug.node(); MObject targetAttr; MObject worldAttr; if (!getWorldGeomAttrs(shape, worldAttr, targetAttr)) continue; // For collision surfaces the target is always the // 'collisionObjects' attr. if (setAttr == aCollisionSet) targetAttr = collisionObjectsAttr; // The connection will be to either an element of the // growth node's 'objectGroups' array, or to an element of // its 'instObjGroups' array. If it's to an 'objectGroups' // element then the connection represents a subset of the // object's faces and we need to get a connection to its // corresponding groupId. if (plug == MPxTransform::objectGroups) { MPlug groupIdPlug = plug.child(MPxTransform::objectGroupId); dgmod->connect( groupIdPlug, ourGroupArray.elementByLogicalIndex(nextIndex) ); // Step up to the 'instObjGroups' element since that's // what the rest of the code is expecting. plug = plug.array().parent(); } // We should now have an 'instObjGroups' element. Its // logical index will be the instance number for the // shape. unsigned int instance = plug.logicalIndex(); // Get the world shape plug for the appropriate instance. MPlug worldPlug(shape, worldAttr); worldPlug = worldPlug.elementByLogicalIndex(instance); // Get the target plug on our hair node. MPlug targetPlug(thisMObject(), targetAttr); targetPlug = targetPlug.elementByLogicalIndex(nextIndex); // Connect them. dgmod->connect(worldPlug, targetPlug); nextIndex++; } } st = dgmod->doIt(); } return st; } MObject shaveHairShape::getGeomSet(const MObject& attr) { MObject set; MPlug plug(thisMObject(), attr); MPlugArray conns; plug.connectedTo(conns, true, false); if (conns.length() > 0) { if (conns[0].node().hasFn(MFn::kSet)) set = conns[0].node(); else { // This is not a valid connection so get rid of it. MDGModifier dgmod; dgmod.disconnect(conns[0], plug); dgmod.doIt(); } } return set; } MString shaveHairShape::getUVSet(MObject growthObject, MObject texture) { ENTER(); MString uvSetName(""); MPlug assignments(thisMObject(), uvSetAssignmentsAttr); unsigned int numAssignments = assignments.evaluateNumElements(); MPlugArray connections; unsigned int i; for (i = 0; i < numAssignments; i++) { // // Find the connection feeding into this assignment's uvSetName // plug. // assignments[i].child(uvSetNameAttr).connectedTo( connections, true, false ); // // Does it come from the growth object? // if ((connections.length() > 0) && (connections[0].node() == growthObject)) { // // Step through each of the textures for which this growth // object has an explicit UV set assignment. // MPlug textures = assignments[i].child(uvSetTexturesAttr); unsigned int numTextures = textures.evaluateNumElements(); unsigned int t; for (t = 0; t < numTextures; t++) { // // Find the texture node which is supplying this // connection. // textures[t].connectedTo(connections, true, false); // // Does it match the texture we're looking for? // if ((connections.length() > 0) && (connections[0].node() == texture)) { // // Return the UV set name for this assignment. // assignments[i].child(uvSetNameAttr).getValue(uvSetName); RETURN(uvSetName); } } } } // // No assignment was found for this combination of growth object and // texture, so return the empty string. // RETURN(uvSetName); } void shaveHairShape::disconnectIncoming(const MObject& attr, MDGModifier* dgmod) { MPlug plug(thisMObject(), attr); disconnectPlugTree(plug, true, dgmod); } void shaveHairShape::disconnectOutgoing(const MObject& attr, MDGModifier* dgmod) { MPlug plug(thisMObject(), attr); disconnectPlugTree(plug, false, dgmod); } void shaveHairShape::disconnectAll(const MObject& attr, MDGModifier* dgmod) { MPlug plug(thisMObject(), attr); disconnectPlugTree(plug, true, dgmod); disconnectPlugTree(plug, false, dgmod); } void shaveHairShape::disconnectPlugTree( MPlug& plug, bool incoming, MDGModifier* dgmod ) { bool doCommit = false; if (dgmod == NULL) { dgmod = new MDGModifier; doCommit = true; } if (plug.isLocked()) plug.setLocked(false); MPlugArray conns; plug.connectedTo(conns, incoming, !incoming); unsigned int i; for (i = 0; i < conns.length(); ++i) { if (conns[i].isLocked()) conns[i].setLocked(false); if (incoming) dgmod->disconnect(conns[i], plug); else dgmod->disconnect(plug, conns[i]); } if (plug.isArray()) { unsigned int numElements = plug.evaluateNumElements(); MPlug element; for (i = 0; i < numElements; ++i) { // For some reason, if I pass plug.elementByPhysicalIndex(i) // directly to the disconnectPlugTree call, the compiler // chooses the 'const MObject&' version rather than the // 'MPlug&' version. So let's be more explicit about this. element = plug.elementByPhysicalIndex(i); disconnectPlugTree(element, incoming, dgmod); } } else if (plug.isCompound()) { MPlug child; for (i = 0; i < plug.numChildren(); ++i) { // For some reason, if I pass plug.child(i) // directly to the disconnectPlugTree call, the compiler // chooses the 'const MObject&' version rather than the // 'MPlug&' version. So let's be more explicit about this. child = plug.child(i); disconnectPlugTree(child, incoming, dgmod); } } if (doCommit) { dgmod->doIt(); delete dgmod; } } void shaveHairShape::deleteMe(MDGModifier& dgMod) { ENTER(); MFnDependencyNode nodeFn(thisMObject()); if (nodeFn.isLocked()) nodeFn.setLocked(false); // // If the node is from a referenced file, we can't delete // it, so just make it invisible (and lock it, if possible). // if (nodeFn.isFromReferencedFile()) { // Set the visibility to false. // MPlug plug(thisMObject(), MPxSurfaceShape::visibility); if (plug.isLocked()) plug.setLocked(false); plug.setValue(false); plug.setLocked(true); // Break any connections from the output mesh. // disconnectOutgoing(outputMesh, &dgMod); // Break all incoming connections to growth and collision objects. // disconnectIncoming(inputMesh, &dgMod); disconnectIncoming(inputCurve, &dgMod); disconnectIncoming(inputSurf, &dgMod); disconnectIncoming(collisionObjectsAttr, &dgMod); // // Destroy anything else of value. // SHAVEclear_scene(); clearBlindDataAttr(shaveBlindHair); SHAVEfree_node(&hairnode); SHAVEinit_node(&hairnode, getShaveID()); nodeFn.setLocked(true); } else { dgMod.deleteNode(thisMObject()); } LEAVE(); } void shaveHairShape::clearBlindDataAttr(MObject& attr) { ENTER(); setBlindDataAttr(attr, NULL, 0); LEAVE(); } void shaveHairShape::setBlindDataAttr(MObject& attr, const void* data, long dataLen) { ENTER(); blindShaveData* blindData = new blindShaveData; blindData->setValue(data, dataLen); MPlug plug(thisMObject(), attr); bool plugWasLocked = plug.isLocked(); if (plugWasLocked) plug.setLocked(false); plug.setValue((MPxData*)blindData); if (plugWasLocked) plug.setLocked(true); LEAVE(); } SHAVENODE* shaveHairShape::getHairNode() { ENTER(); // // Grab the trigger value to ensure that the hairnode is up to date. // MPlug plug(thisMObject(), triggerAttr); float value; plug.getValue(value); RETURN(&hairnode); } void shaveHairShape::setShadowHaircount(int group) { ENTER(); int n; n = (int)(shaveShadowHairRatioGlob * (float)hairnode.shavep.haircount[group] + 0.5f); // // If the shadow/hair ratio is non-zero, then make sure that we always // get at least one shadow hair. // if ((n < 1) && (shaveShadowHairRatioGlob > 0.0001f)) n = 1; hairnode.shavep.shadowHaircount[group] = n; LEAVE(); } // // Create a mesh for the hairs, for use in a separate mesh node (i.e. does // not affect the shaveHairShape's display node). // MObject shaveHairShape::createExternalMesh(MObject parent, MStatus* st) { ENTER(); if (st) *st = MS::kSuccess; MDataBlock block = forceCache(); RETURN(makePolyMeshData(block, parent, false, false)); } // // Recomb the hair using a set of curves passed in as a WFTYPE. // void shaveHairShape::recomb(WFTYPE& curves) { SHAVEspline_recomb(&hairnode, &curves); // The contents of 'hairnode' will likely have changed, so we must // update the blind data which relies on it. // updateBlindData(); // The changes to 'hairnode' probably mean that the output mesh should // be recomputed. Let's force that. // dirtyOutputMesh(); } #if 0 static MFloatPoint interpolate( MFloatPoint p1, MFloatPoint p2, MFloatPoint p3, MFloatPoint p4, float u ) { float ret; float u3, u2; if (u >= 1.0f) RETURN(p3); if (u <= 0.0f) RETURN(p2); u3 = u * u * u; u2 = u * u; return ((-u3 + (2.0f * u2) - u) * p1 + (3.0f * u3 - 5.0f * u2 + 2.0) * p2 + (-3.0f * u3 + (4.0f * u2) + u) * p3 + (u3 + -u2) * p4) / 2.0f; } #endif // // Take the edits to spline hair guides and apply them to corresponding // curves in Maya. // void shaveHairShape::updateSplines() { ENTER(); MStatus st; // // If this isn't spline hair then there aren't any splines to update. // if (getHairGroup() != 4) LEAVE(); // // Create a fitBspline node feeding into a rebuildCurve node. // MDGModifier dgMod; #if 0 MObject rebuildNode = dgMod.createNode("rebuildCurve", &st); #else MFnDependencyNode rebuildNodeFn; MObject rebuildNode = rebuildNodeFn.create( "rebuildCurve", "shave_rebuildCurveTemp", &st ); #endif MChkErrVoid(st, "cannot create 'rebuildCurve' node."); MObject fitNode = dgMod.createNode("fitBspline", &st); MChkErrVoid(st, "cannot create 'fitBspline' node."); MFnDependencyNode fitNodeFn(fitNode); #if 0 MFnDependencyNode rebuildNodeFn(rebuildNode); #endif MPlug curveOut = fitNodeFn.findPlug("outputCurve"); MPlug curveIn = rebuildNodeFn.findPlug("inputCurve"); st = dgMod.connect(curveOut, curveIn); MChkErrVoid(st, "cannot connect 'fitBspline' node to 'rebuildCurve' node."); st = dgMod.doIt(); MChkErrVoid(st, "cannot commit updateSplines network."); curveIn = fitNodeFn.findPlug("inputCurve"); curveOut = rebuildNodeFn.findPlug("outputCurve"); MSelectionList curves; getGrowthList(curves); int curveNum = 0; MDagPath curvePath; SOFTGUIDE guide; int i; MItSelectionList iter(curves); for (iter.reset(); !iter.isDone(); iter.next()) { iter.getDagPath(curvePath); curvePath.extendToShape(); if (curvePath.hasFn(MFn::kNurbsCurve)) { #if 0 if (SHAVEfetch_guideNOISE(curveNum++, &guide) == -1) #else if (SHAVEfetch_guide(curveNum++, &guide) == -1) #endif { MGlobal::displayWarning( "Shave: spline hair has too few guides." ); break; } // // Set up the rebuildCurve node to produce the same type of // curve as we already have. // MFnNurbsCurve curveFn(curvePath); MPlug plug; plug = rebuildNodeFn.findPlug("spans"); plug.setValue(curveFn.numSpans()); plug = rebuildNodeFn.findPlug("degree"); plug.setValue(curveFn.degree()); // // Get the guide's vertices. // MPointArray guidePts(SHAVE_VERTS_PER_GUIDE); MDoubleArray knots(SHAVE_VERTS_PER_GUIDE); double paramPerKnot = 1.0 / (double)(SHAVE_VERTS_PER_GUIDE-1); for (i = 0; i < SHAVE_VERTS_PER_GUIDE; i++) { guidePts[i] = MPoint( guide.guide[i].x, guide.guide[i].y, guide.guide[i].z ); knots[i] = paramPerKnot * (double)i; } // // Make sure that the curve ends exactly on param value 1. // knots[i-1] = 1.0; // // Create a degree-1 curve. // MFnNurbsCurveData curveDataFn; MObject guideCurveData = curveDataFn.create(); MFnNurbsCurve guideCurveFn; MObject guideCurve; guideCurve = guideCurveFn.create( guidePts, knots, 1, MFnNurbsCurve::kOpen, false, true, guideCurveData, &st ); if (!st) { MRptErr(st, "cannot create degree-1 curve for guide."); break; } // // Place the curve on the fitBspline node's input. // curveIn.setValue(guideCurveData); // // Get the output curve from the rebuildCurve node. // curveOut.getValue(guideCurveData); // // Get the positions of the resulting curve's cvs. // MPointArray cvs; guideCurveFn.setObject(guideCurveData); st = guideCurveFn.getCVs(cvs); if (!st) { MRptErr(st, "cannot get cvs for rebuilt guide."); break; } // // Move the original curve's cvs to the same positions. // st = curveFn.setCVs(cvs, MSpace::kWorld); if (!st) { MRptErr(st, "cannot set new cvs positions on guide spline."); break; } curveFn.updateCurve(); } } dgMod.undoIt(); MDGModifier dgMod2; dgMod2.deleteNode(rebuildNode); dgMod2.doIt(); LEAVE(); } int shaveHairShape::getDisplayMode() const { short hairDisplayMode; MPlug plug(thisMObject(),shaveHairShape::dspyMode); plug.getValue(hairDisplayMode); return hairDisplayMode; } bool shaveHairShape::getDisplayGuides() const { shaveGlobals::getGlobals(); return displayGuidesGlob; // bool displayGuides; // MPlug plug(thisMObject(),shaveHairShape::aDisplayGuides); // plug.getValue(displayGuides); // return displayGuides; } bool shaveHairShape::getDoHairXparency() const { shaveGlobals::getGlobals(); return doHairXparencyGlob; //bool b; //MPlug plug(thisMObject(), displayHiarDoXparency); //plug.getValue(b); //return b; } float shaveHairShape::getHairXparency() const { float f; MPlug plug(thisMObject(), displayHiarXparency); plug.getValue(f); return f; } bool shaveHairShape::getDoFallback() const { return doFallbackGlob; } bool shaveHairShape::getDoTipfade() const { bool b; MPlug plug(thisMObject(), aTipFade); plug.getValue(b); return b; } float shaveHairShape::getAmbDiff() const { float v; MPlug plug(thisMObject(), shaveParamAmbDiff); plug.getValue(v); return v; } MColor shaveHairShape::getSpecularTint() const { MColor v; // create a plug to the attribute on this node MPlug plug(thisMObject(), aSpecularTint); // use the plug to get the compound attribute MObject object; plug.getValue(object); // attach a numeric attribute function set to the color MFnNumericData fn(object); // get the data from the color and wang in val fn.getData(v.r, v.g, v.b); return v; } MColor shaveHairShape::getSpecularTint2() const { MColor v; // create a plug to the attribute on this node MPlug plug(thisMObject(), aSpecularTint2); // use the plug to get the compound attribute MObject object; plug.getValue(object); // attach a numeric attribute function set to the color MFnNumericData fn(object); // get the data from the color and wang in val fn.getData(v.r, v.g, v.b); return v; } float shaveHairShape::getSpecular() const { float v; MPlug plug(thisMObject(), shaveParamSpecular); plug.getValue(v); return v; } float shaveHairShape::getGloss() const { float v; MPlug plug(thisMObject(), shaveParamGloss); plug.getValue(v); return v; } unsigned shaveHairShape::getNumPasses() const { int passes; MPlug plugp(thisMObject(), shaveParamPasses); plugp.getValue(passes); return passes; } // // Apply the display parameters to 'totalCount' and return the number of // them which should be displayed. // unsigned shaveHairShape::getDisplayCount( unsigned totalCount, bool forcefallback, MDataBlock* block ) const { ENTER(); int passes; if (block != NULL) { passes = block->inputValue(shaveParamPasses).asInt(); } else { MPlug plug(thisMObject(), shaveParamPasses); plug.getValue(passes); } shaveGlobals::getGlobals(); unsigned displayCount = (unsigned)(displayRatioGlob * (float)totalCount + 0.5); if (displayCount > (unsigned)totalCount) { displayCount = (unsigned)totalCount; } unsigned int c = passes*displayCount; #ifdef GLOBAL_FALLBACK bool fallback = doFallbackGlob; if(forcefallback || liveModeGlob || (fallback && ( dirties.BRUSH_MOUSE_DOWN || dirties.GLOBAL_MOUSE_DOWN || (dirties.BRUSH_JUST_MOVED && IsToolActive())))) #else if(forcefallback || doFallbackGlob) #endif { float fallback_ratio = fallbackRatioGlob; if (fallback_ratio < 0.0f) fallback_ratio = 0.0f; else if (fallback_ratio > 1.0f) fallback_ratio = 1.0f; unsigned displayCount = (unsigned)(fallback_ratio * (float)totalCount + 0.5); if (displayCount > (unsigned)totalCount) { displayCount = (unsigned)totalCount; } c = passes*displayCount; //c *= fallback_ratio; //c /= 10; } RETURN(c); } // // Return the number of hairs which should be displayed. // unsigned shaveHairShape::getNumDisplayHairs( bool forcefallback, MDataBlock* block ) const { ENTER(); RETURN(getDisplayCount( (unsigned)hairnode.shavep.haircount[getHairGroup(block)], forcefallback, block )); } // // Return the number of segments to use when displaying hair. // int shaveHairShape::getNumDisplaySegments() const { ENTER(); int hairGroup = getHairGroup(); //int numSegs; //MPlug plug(thisMObject(), aDisplaySegmentLimit); //plug.getValue(numSegs); shaveGlobals::getGlobals(); float p = 0.01f*displaySegmentLimitGlob; int numSegs = (int)(p*(float)hairnode.shavep.segs[hairGroup]); //if (hairnode.shavep.segs[hairGroup] < numSegs) // numSegs = hairnode.shavep.segs[hairGroup]; if (numSegs < 1) numSegs = 1; RETURN(numSegs); } void shaveHairShape::computeBoundingBox(MBoundingBox& bbox, MDataBlock& block) { ENTER(); bbox.clear(); if (mNodeInitialized) { WFTYPE hairGeom; hairGeom.totalverts = 0; init_geomWF(&hairGeom); // // Calculate the bounding box for the hair. // CURVEINFO curveInfo; unsigned hair; int hairGroup = getHairGroup(&block); unsigned numHairsToDraw = getNumDisplayHairs(&block); int v; for (hair = 0; hair < numHairsToDraw; hair++) { SHAVEmake_a_curve( 0, hairGroup, (int)hair, hairnode.shavep.segs[hairGroup], &hairGeom, &curveInfo ); if (!curveInfo.killme) { for (v = 0; v < hairGeom.totalverts; v++) { bbox.expand( MPoint(hairGeom.v[v].x,hairGeom.v[v].y,hairGeom.v[v].z) ); } } } free_geomWF(&hairGeom); } LEAVE(); } // // Unless Joe can give me a cheaper way of getting the bounding box, we'd // best not supply the standard 'boundingBox' method because it's too slow // and gets called too frequently. // // I've provided a renamed version so that the bounding box draw can use it // but Maya won't. // MBoundingBox shaveHairShape::boundingBoxTemp() const { ENTER(); MPlug plug(thisMObject(), aCachedBBoxMin); MFnNumericData dataFn; MObject dataObj; MPoint bboxMin; MPoint bboxMax; plug.getValue(dataObj); dataFn.setObject(dataObj); dataFn.getData(bboxMin.x, bboxMin.y, bboxMin.z); plug.setAttribute(aCachedBBoxMax); plug.getValue(dataObj); dataFn.setObject(dataObj); dataFn.getData(bboxMax.x, bboxMax.y, bboxMax.z); MBoundingBox bbox(bboxMin, bboxMax); RETURN(bbox); } const shaveHairShape::GuidesSnapshot& shaveHairShape::getGuides() { ENTER(); if (mNodeInitialized) { // If we're not the current hair shape, then clear all of our hidden // flags. // MDagPath currentHair = shaveUtil::getCurrentHairShape(); unsigned i; if (!currentHair.isValid() || (thisMObject() != currentHair.node())) { for (i = 0; i < mGuideCache.guides.size(); i++) mGuideCache.guides[i].hidden = false; } // If the frame has changed, save the previous cache. // if (mGuideCache.frame != mShaveFrame) { mPrevGuides = mGuideCache; } // If the cache is dirty, update it. // mGuideCache.frame = mShaveFrame; if (mGuideCacheDirty) { Guide g; SOFTGUIDE sg; int v; makeCurrent(false); g.hidden = false; g.select = 0; g.verts.setLength(SHAVE_VERTS_PER_GUIDE); for (i = 0; SHAVEfetch_guide((int)i, &sg) != -1; i++) { for (v = 0; v < SHAVE_VERTS_PER_GUIDE; v++) { // // Reuse any existing cache elements, preserving their // selection info. // if (i < mGuideCache.guides.size()) { Guide& existing = mGuideCache.guides[i]; existing.verts[v].x = sg.guide[v].x; existing.verts[v].y = sg.guide[v].y; existing.verts[v].z = sg.guide[v].z; existing.hidden = (sg.hidden != 0); } else { g.verts[v].x = sg.guide[v].x; g.verts[v].y = sg.guide[v].y; g.verts[v].z = sg.guide[v].z; g.hidden = (sg.hidden != 0); } } // // If we've run out of existing cache elements, create a new // one for this guide. // if (i >= mGuideCache.guides.size()) { mGuideCache.guides.push_back(g); mGuideSelectionsDirty = true; } } // // Remove any old guides which lie beyond the new end of the // cache. // mGuideCache.guides.erase( mGuideCache.guides.begin() + i, mGuideCache.guides.end() ); mGuideCacheDirty = false; } if (mGuideSelectionsDirty) { clearComponentSelections(); // // If we are the current hair shape then apply Maya's // selections to our cache. // if (currentHair.isValid() && (thisMObject() == currentHair.node())) { // // Set a bit for each selected vert on each guide. // MObject comp; int elem; int guideIdx; unsigned i; MSelectionList list; MDagPath path; int vertIdx; MGlobal::getActiveSelectionList(list); for (i = 0; i < list.length(); i++) { list.getDagPath(i, path, comp); if (path.isValid() && !comp.isNull() && (path.node() == thisMObject())) { if (comp.hasFn(MFn::kSingleIndexedComponent)) { MFnSingleIndexedComponent compFn(comp); for (elem = 0; elem < compFn.elementCount(); elem++) { guideIdx = compFn.element(elem); mGuideCache.guides[guideIdx].select = 0x7fff; } } else if (comp.hasFn(MFn::kDoubleIndexedComponent)) { MFnDoubleIndexedComponent compFn(comp); for (elem = 0; elem < compFn.elementCount(); elem++) { compFn.getElement(elem, guideIdx, vertIdx); // // The '| 1' at the end of the statement // below is because vert 0 is used as a // flag to indicate if *any* verts in the // guide are selected. // mGuideCache.guides[guideIdx].select |= (1 << vertIdx) | 1; } } mHaveSelections = true; } } } mGuideSelectionsDirty = false; } } RETURN(mGuideCache); } #if 0 const std::vector& shaveHairShape::getDisplayHairs() { // // If the cache is dirty, destroy it. // if (mDisplayHairCacheDirty) mDisplayHairCache.clear(); // // How many hairs should we be displaying? // unsigned numToDisplay = getNumDisplayHairs(); mDisplayHairCache.reserve(numToDisplay); // // If cache doesn't already have at least numToDisplay hairs in it, // then add more. // if (mDisplayHairCache.size() < numToDisplay) { makeCurrent(); CURVEINFO curveInfo; int hairGroup = getHairGroup(); int numSegs = hairnode.shavep.segs[hairGroup]; unsigned h; WFTYPE geom; DisplayHair emptyHair; DisplayStrand strand; geom.totalverts = 0; init_geomWF(&geom); for (h = mDisplayHairCache.size(); h < numToDisplay; h++) { SHAVEmake_a_curve(0, hairGroup, (int)h, numSegs, &geom, &curveInfo); if (!curveInfo.killme && (geom.totalverts > 0)) { // // Create a new element in the vector and get a reference to it. // Doing it this way involves less copying of memory than if we // create the entire hair first, then push it on. // mDisplayHairCache.push_back(emptyHair); DisplayHair& displayHair = mDisplayHairCache[mDisplayHairCache.size()-1]; // // Each 'face' is a strand. // int f; for (f = 0; f < geom.totalfaces; f++) { strand.verts.clear(); strand.colors.clear(); int fv; for (fv = geom.face_start[f]; fv < geom.face_end[f]; fv++) { int vertInd = geom.facelist[fv]; strand.verts.append(MVector( geom.v[vertInd].x, geom.v[vertInd].y, geom.v[vertInd].z )); strand.colors.append(MColor( geom.color[vertInd].x, geom.color[vertInd].y, geom.color[vertInd].z )); displayHair.push_back(strand); } } } } free_geomWF(&geom); } mDisplayHairCacheDirty = false; RETURN(mDisplayHairCache); } #else typedef struct { #ifdef REUSABLE_THREADS int th; //#ifdef _WIN32 // HANDLE th; //#else // pthread_t th; //#endif #endif int hairGroup; int numSegs; int startHair; int endHair; std::vector* cache; } ThreadData; #ifdef REUSABLE_THREADS #ifdef _WIN32 static DWORD APIENTRY cacheOneHairTh (void* data) #else static void* APIENTRY cacheOneHairTh (void* data) #endif { ThreadData* threadInfo = (ThreadData*)data; shaveHairShape::cacheOneHair((unsigned int)threadInfo->th, data); #ifdef _WIN32 return 0; #endif } #endif //////////////////////// //static int num_segs_dump = 0; //////////////////////// void shaveHairShape::cacheOneHair(unsigned threadID, void* data) { ENTER(); CURVEINFO curveInfo; WFTYPE geom; int i; ThreadData* threadInfo = (ThreadData*)data; geom.totalverts = 0; init_geomWF(&geom); for (i = threadInfo->startHair; i <= threadInfo->endHair; i++) { SHAVEmake_a_curveMT( 0, threadInfo->hairGroup, i, threadInfo->numSegs, &geom, &curveInfo, (int)threadID ); ////////////////////////// //if(num_segs_dump != threadInfo->numSegs) // printf("threadInfo->numSegs %i\n",threadInfo->numSegs); ///////////////////////// if (!curveInfo.killme && (geom.totalverts > 0)) { DisplayHair& displayHair = (*(threadInfo->cache))[i]; DisplayStrand strand; // // Each 'face' is a strand. // int f=0; for (f = 0; f < geom.totalfaces; f++) { strand.verts.clear(); strand.colors.clear(); int fv; strand.tiprad = curveInfo.tiprad; strand.rootrad = curveInfo.baserad; /////////////////////////// //if(num_segs_dump != threadInfo->numSegs) //{ // printf("geom.face_start[f] %i\n",geom.face_start[f]); // printf("geom.face_end[f] %i\n",geom.face_end[f]); // printf("face_end - geom.face_start %i\n",geom.face_end[f]-geom.face_start[f]); //} ////////////////////////// int k = 0; for (fv = geom.face_start[f]; fv < geom.face_end[f]; fv++) { #if 0 VERT color; float alpha; float u; int hh; u=(float)fv/(float)(geom.face_end[f]-geom.face_start[f]-1); alpha=1.0-u; alpha*=1.0/passes; color.x=m->return_hair[hh].rootcolor.x*u+m->return_hair[hh].tipcolor.x*(1.0-u); color.y=m->return_hair[hh].rootcolor.y*u+m->return_hair[hh].tipcolor.y*(1.0-u); color.z=m->return_hair[hh].rootcolor.z*u+m->return_hair[hh].tipcolor.z*(1.0-u); #endif if(k > geom.face_end[f]-geom.face_start[f]) break; k++; int vertInd = fv; /////////////////////////// //if(num_segs_dump != threadInfo->numSegs) //{ // printf("geom.facelist[fv] %i\n",vertInd); // printf("geom.v %f %f %f\n",geom.v[vertInd].x, geom.v[vertInd].y, geom.v[vertInd].z); //} ////////////////////////// strand.verts.append(MVector( geom.v[vertInd].x, geom.v[vertInd].y, geom.v[vertInd].z )); strand.colors.append(MColor( geom.color[vertInd].x, geom.color[vertInd].y, geom.color[vertInd].z, geom.alpha[vertInd] )); //for Joe: alpha is 1.0 for all the verts //printf("alpha %f\n", geom.alpha[vertInd]); } displayHair.push_back(strand); } ///////////////////// //num_segs_dump = threadInfo->numSegs; //fflush(stdout); //////////////////// } } free_geomWF(&geom); LEAVE(); } void shaveHairShape::clearDisplayHairs() { mDisplayHairCache.displayHair.clear(); mDisplayHairCacheDirty = true; } void shaveHairShape::invalidateDisplayHairs() { mDisplayHairCacheDirty = true; } //const std::vector& shaveHairShape::getDisplayHairs() const shaveHairShape::DisplayHairCache& shaveHairShape::getDisplayHairs(M3dView& view, bool cleanup) { ENTER(); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::getDisplayHairs(2)", NULL); #endif if (SHAVEis_it_loaded() && mNodeInitialized) { //goes //maybe that is not a good place for grabbing textures? //::compute is bad one as well if(cleanup) { //doFallbackGlob = false; //test it //printf("before tex update - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); updateTexLookups(); // this should be just for display counts ///////////////// //qApp->flush(); //printf("after tex update - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); //doFallbackGlob = true; ////////dbg////// //int numToDisplay = getNumDisplayHairs(false); //printf("count %i\n",numToDisplay);fflush(stdout); } #if 0 unsigned int numToDisplay = getNumDisplayHairs(false); // // If the cache is dirty, destroy it. // if ((mDisplayHairCacheDirty && cleanup) || mDisplayHairCache.displayHair.capacity() != numToDisplay) { //mDisplayHairCache.displayHair.reserve(numToDisplay); mDisplayHairCache.displayHair.resize(numToDisplay); mDisplayHairCache.displayHair.shrink_to_fit(); mDisplayHairCache.displayHair.clear(); } //////////////test/////////////// //{ // DisplayHair emptyHair; // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(emptyHair); // RETURN(mDisplayHairCache); //} ///////////////////////////// // // How many hairs should we be displaying? // unsigned int curCacheSize = mDisplayHairCache.displayHair.size(); #else // // How many hairs should we be displaying? // unsigned numToDisplay = getNumDisplayHairs(false); //setting it to zero bumps fps to 200 unsigned bunchsize = getNumDisplayHairs(true); if(bunchsize == 0) bunchsize = numToDisplay; // // If the cache is dirty, destroy it. // if (cleanup && (mDisplayHairCacheDirty || numToDisplay < mDisplayHairCache.displayHair.size())) mDisplayHairCache.displayHair.clear(); unsigned int curCacheSize = (unsigned int)mDisplayHairCache.displayHair.size(); mDisplayHairCache.displayHair.reserve(numToDisplay); ////////////// test - fake cache //////////// 200fps //if(mDisplayHairCache.displayHair.size() == 0) //{ // mDisplayHairCache.displayHair.clear(); // DisplayStrand astrand; // for(int k = 0; k<6; k++) // { // astrand.verts.append(MVector(k*5,k,k)); // astrand.colors.append(MColor(1.0,0.0,0.0)); // } // astrand.rootrad = 1.0f; // astrand.tiprad = 1.0f; // DisplayHair ahair; // ahair.push_back(astrand); // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(ahair); // // mDisplayHairCache.radius = 1.0f; // RETURN(mDisplayHairCache); //} ///////////// end test //////// #endif // // If cache doesn't already have at least numToDisplay hairs in it, // then add more. // //unsigned int upto = curCacheSize + DisplayHairCache::eBucnchSize < numToDisplay ? curCacheSize + DisplayHairCache::eBucnchSize : numToDisplay; unsigned int upto = curCacheSize + bunchsize < numToDisplay ? curCacheSize + bunchsize : numToDisplay; //unsigned int upto = numToDisplay; //printf("%i %i %i\n", upto,upto2,curCacheSize);fflush(stdout); unsigned int numh = upto - curCacheSize; //printf("curCaheSize %i upto %i \n",curCacheSize, upto); #if 0 if (curCacheSize < /*numToDisplay*/ upto ) #else if (curCacheSize < numToDisplay) #endif { /////////////////////////////////// //MFnDependencyNode dFn(thisMObject()); //printf(" update %s\n",dFn.name().asChar()); ////////////////test/////////////// //{ // DisplayHair emptyHair; // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(emptyHair); // RETURN(mDisplayHairCache); //} /////////////////////////////// if(cleanup) //only the first iteration makeCurrent(false); int hairGroup = getHairGroup(); int numSegs = getNumDisplaySegments(); unsigned h; unsigned i; DisplayHair emptyHair; #if 0 unsigned numNew = numToDisplay - curCacheSize; #else unsigned numNew = upto - curCacheSize; #endif ////////////// test - fake cache //////////// fast 200 fps //{ // mDisplayHairCache.displayHair.clear(); // DisplayStrand astrand; // for(int k = 0; k<6; k++) // { // astrand.verts.append(MVector(k*5,k,k)); // astrand.colors.append(MColor(1.0,0.0,0.0)); // } // astrand.rootrad = 1.0f; // astrand.tiprad = 1.0f; // DisplayHair ahair; // ahair.push_back(astrand); // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(ahair); // // mDisplayHairCache.radius = 1.0f; // RETURN(mDisplayHairCache); //} ///////////// end test //////// //moved down to the thread stuff //numThreads = shaveUtil::getThreadSettings(numNew, &hairsPerThread); //ThreadData* threadInfo = new ThreadData[numThreads?numThreads:1]; //void* threadGroup = SHAVEstart_thread_group(numThreads); ////////////// test - fake cache //////////// slow 17 fps //{ // mDisplayHairCache.displayHair.clear(); // DisplayStrand astrand; // for(int k = 0; k<6; k++) // { // astrand.verts.append(MVector(k*5,k,k)); // astrand.colors.append(MColor(1.0,0.0,0.0)); // } // astrand.rootrad = 1.0f; // astrand.tiprad = 1.0f; // DisplayHair ahair; // ahair.push_back(astrand); // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(ahair); // // mDisplayHairCache.radius = 1.0f; // RETURN(mDisplayHairCache); //} ///////////// end test //////// SHAVEPARMS pp; // pp= &hairnode.shavep; SHAVEfetch_parms(&pp); // SHAVEset_parms(pp); #if 0 if (hairGroup==0) pp.haircount[0]=numToDisplay; if (hairGroup==4) pp.haircount[4]=numToDisplay; #else if (hairGroup==0) pp.haircount[0]=upto; if (hairGroup==4) pp.haircount[4]=upto; #endif if (hairGroup==0) pp.segs[0]=numSegs; if (hairGroup==4) pp.segs[4]=numSegs; ////////////////test/////////////// //{ // DisplayHair emptyHair; // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(emptyHair); // RETURN(mDisplayHairCache); //} /////////////////////////////// ////////////// test - fake cache //////////// slow 17 fps //{ // mDisplayHairCache.displayHair.clear(); // DisplayStrand astrand; // for(int k = 0; k<6; k++) // { // astrand.verts.append(MVector(k*5,k,k)); // astrand.colors.append(MColor(1.0,0.0,0.0)); // } // astrand.rootrad = 1.0f; // astrand.tiprad = 1.0f; // DisplayHair ahair; // ahair.push_back(astrand); // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(ahair); // // mDisplayHairCache.radius = 1.0f; // RETURN(mDisplayHairCache); //} ///////////// end test //////// if (SHAVEis_it_loaded()) { if ( (dirties.DIRTY_DISPLAY_COUNT) || (dirties.DIRTY_DISPLAYSEGS)||(dirties.DIRTY_HAIRCOUNT)||(dirties.DIRTY_GUIDE_SEGS)||(dirties.DIRTY_PASSES)||(dirties.DIRTY_TEXTURE_JOE)||(dirties.DIRTY_CLUMPS)||(dirties.DIRTY_MULT)) { if (numToDisplay>0) { ////////////////////////////////// MDagPath camPath; if(MStatus::kSuccess == view.getCamera(camPath)) { Matrix camvm; MVector mayaUpVector, mayaDirectionVector; double rotationVector[3]; VERT shaveUpVector, shaveDirection, shavePosition; MVector unitDirectionVector(0., 0., 1.); MVector unitUpVector(0., 1., 0.); MTransformationMatrix::RotationOrder order = MTransformationMatrix::kXYZ; MTransformationMatrix camWorldMatrix = camPath.inclusiveMatrix(); MFnCamera fnCamera(camPath); // Get MAYA camera translation and rotation info MVector translation = camWorldMatrix.translation(MSpace::kWorld); camWorldMatrix.getRotation(rotationVector, order); // Set SHAVE camera position in World space shavePosition.x = (float) translation.x; shavePosition.y = (float) translation.y; shavePosition.z = (float) translation.z; // UP VECTOR mayaUpVector = unitUpVector.rotateBy(rotationVector, order); shaveUpVector.x = (float) mayaUpVector.x; shaveUpVector.y = (float) mayaUpVector.y; shaveUpVector.z = (float) mayaUpVector.z; // DIRECTION VECTOR mayaDirectionVector = unitDirectionVector.rotateBy(rotationVector, order); shaveDirection.x = (float) -mayaDirectionVector.x; shaveDirection.y = (float) -mayaDirectionVector.y; shaveDirection.z = (float) -mayaDirectionVector.z; int port_width = view.portWidth(); int port_height = view.portHeight(); // FIELD OF VIEW float pixelAspect = 1.0f; double hfovagain, vfovagain; shaveRender::portFieldOfView( port_width, port_height, pixelAspect, hfovagain, vfovagain, fnCamera ); const float rad2degrees = (180.0f / (float)M_PI); vfovagain *= rad2degrees; SHAVEmake_view_matrix( camvm, shaveUpVector, shaveDirection, shavePosition, (float)vfovagain ); SHAVEset_cameraOPEN( camvm, shavePosition, port_width, port_height, pixelAspect, (float)vfovagain, 0.01f ); } ///////////////////////////////// ////////////////test/////////////// //{ // DisplayHair emptyHair; // for (int h = 0; h < numToDisplay; h++) // mDisplayHairCache.displayHair.push_back(emptyHair); // RETURN(mDisplayHairCache); //} /////////////////////////////// int *hairlist=NULL; hairlist = (int*)malloc(sizeof(int)*numToDisplay); for(unsigned int k=0; kGet(i)->GetHandle(); threadInfo[i].cache = &mDisplayHairCache.displayHair; threadInfo[i].hairGroup = hairGroup; threadInfo[i].numSegs = numSegs; threadInfo[i].startHair = h; threadInfo[i].endHair = h + hairsPerThread - 1; #if 0 if (threadInfo[i].endHair >= (int)numToDisplay) threadInfo[i].endHair = (int)numToDisplay - 1; #else if (threadInfo[i].endHair >= (int)upto) threadInfo[i].endHair = (int)upto - 1; #endif h += hairsPerThread; } for(unsigned int i = 0; i < ncpus; i++) { ThreadData* thd = &threadInfo[i]; _lthGrp()->Start(i,/*(THPROC*)*/cacheOneHairTh,thd); } _lthGrp()->WaitToFinish(); delete [] threadInfo; #else unsigned int hairsPerThread = 0; unsigned int numThreads = shaveUtil::getThreadSettings(numNew, &hairsPerThread); ThreadData* threadInfo = new ThreadData[numThreads?numThreads:1]; void* threadGroup = SHAVEstart_thread_group(numThreads); #if 0 for (h = curCacheSize, i = 0; h < numToDisplay; h += hairsPerThread, i++) #else for (h = curCacheSize, i = 0; h < upto; h += hairsPerThread, i++) #endif { threadInfo[i].cache = &mDisplayHairCache.displayHair; threadInfo[i].hairGroup = hairGroup; threadInfo[i].numSegs = numSegs; threadInfo[i].startHair = h; threadInfo[i].endHair = h + hairsPerThread - 1; #if 0 if (threadInfo[i].endHair >= (int)numToDisplay) threadInfo[i].endHair = (int)numToDisplay - 1; #else if (threadInfo[i].endHair >= (int)upto) threadInfo[i].endHair = (int)upto - 1; #endif SHAVEstart_thread(threadGroup, cacheOneHair, &threadInfo[i]); } SHAVEend_thread_group(threadGroup); delete [] threadInfo; #endif } //maybe we need to do this only on the last iteration? if(upto == numToDisplay) { //compute extent by tips unsigned int s = (unsigned int)mDisplayHairCache.displayHair.size(); MFloatPoint c(0.0f, 0.0f, 0.0f); float lSq = 0.0f; for (i = 0; i < s; i++) { const DisplayHair& hair = mDisplayHairCache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; c.x += strand.verts[xx].x; c.y += strand.verts[xx].y; c.z += strand.verts[xx].z; } c.x /= (float)s; c.y /= (float)s; c.z /= (float)s; for (i = 0; i < s; i++) { const DisplayHair& hair = mDisplayHairCache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; MFloatPoint v(strand.verts[xx].x,strand.verts[xx].y,strand.verts[xx].z); MFloatPoint d = v-c; float lsq = d.x*d.x + d.y*d.y + d.z*d.z; if(lsq > lSq) lSq = lsq; } mDisplayHairCache.center = c; mDisplayHairCache.radius = sqrt(lSq); } } if(upto == numToDisplay || numToDisplay == 0) mDisplayHairCacheDirty = false; } #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::getDisplayHairs(2) - done ", NULL); #endif RETURN(mDisplayHairCache); } const shaveHairShape::DisplayHairCache& shaveHairShape::getDisplayHairs(M3dView& view) { ENTER(); #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::getDisplayHairs(1)", NULL); #endif if (mNodeInitialized && SHAVEis_it_loaded()) { updateTexLookups(); // this should be just for display counts // If the cache is dirty, destroy it. // if (mDisplayHairCacheDirty) mDisplayHairCache.displayHair.clear(); // // How many hairs should we be displaying? // unsigned int curCacheSize = (unsigned int)mDisplayHairCache.displayHair.size(); unsigned int numToDisplay = (unsigned int)getNumDisplayHairs(false); mDisplayHairCache.displayHair.reserve(numToDisplay); // // If cache doesn't already have at least numToDisplay hairs in it, // then add more. // if (curCacheSize < numToDisplay) { makeCurrent(); int hairGroup = getHairGroup(); int numSegs = getNumDisplaySegments(); unsigned h; unsigned i; DisplayHair emptyHair; unsigned numNew = numToDisplay - curCacheSize; unsigned hairsPerThread; unsigned numThreads; numThreads = shaveUtil::getThreadSettings(numNew, &hairsPerThread); ThreadData* threadInfo = new ThreadData[numThreads?numThreads:1]; void* threadGroup = SHAVEstart_thread_group(numThreads); SHAVEPARMS pp; // pp= &hairnode.shavep; SHAVEfetch_parms(&pp); // SHAVEset_parms(pp); if (hairGroup==0) pp.haircount[0]=numToDisplay; if (hairGroup==4) pp.haircount[4]=numToDisplay; if (hairGroup==0) pp.segs[0]=numSegs; if (hairGroup==4) pp.segs[4]=numSegs; if ((numToDisplay>0) && ( (dirties.DIRTY_DISPLAY_COUNT) || (dirties.DIRTY_DISPLAYSEGS) || (dirties.DIRTY_HAIRCOUNT) || (dirties.DIRTY_GUIDE_SEGS) || (dirties.DIRTY_PASSES) || (dirties.DIRTY_TEXTURE_JOE) || (dirties.DIRTY_CLUMPS) || (dirties.DIRTY_MULT) ) ) { ////////////////////////////////// MDagPath camPath; if(MStatus::kSuccess == view.getCamera(camPath)) { Matrix camvm; MVector mayaUpVector, mayaDirectionVector; double rotationVector[3]; VERT shaveUpVector, shaveDirection, shavePosition; MVector unitDirectionVector(0., 0., 1.); MVector unitUpVector(0., 1., 0.); MTransformationMatrix::RotationOrder order = MTransformationMatrix::kXYZ; MTransformationMatrix camWorldMatrix = camPath.inclusiveMatrix(); MFnCamera fnCamera(camPath); // Get MAYA camera translation and rotation info MVector translation = camWorldMatrix.translation(MSpace::kWorld); camWorldMatrix.getRotation(rotationVector, order); // Set SHAVE camera position in World space shavePosition.x = (float) translation.x; shavePosition.y = (float) translation.y; shavePosition.z = (float) translation.z; // UP VECTOR mayaUpVector = unitUpVector.rotateBy(rotationVector, order); shaveUpVector.x = (float) mayaUpVector.x; shaveUpVector.y = (float) mayaUpVector.y; shaveUpVector.z = (float) mayaUpVector.z; // DIRECTION VECTOR mayaDirectionVector = unitDirectionVector.rotateBy(rotationVector, order); shaveDirection.x = (float) -mayaDirectionVector.x; shaveDirection.y = (float) -mayaDirectionVector.y; shaveDirection.z = (float) -mayaDirectionVector.z; int port_width = view.portWidth(); int port_height = view.portHeight(); // FIELD OF VIEW float pixelAspect = 1.0f; double hfovagain, vfovagain; shaveRender::portFieldOfView( port_width, port_height, pixelAspect, hfovagain, vfovagain, fnCamera ); const float rad2degrees = (180.0f / (float)M_PI); vfovagain *= rad2degrees; SHAVEmake_view_matrix( camvm, shaveUpVector, shaveDirection, shavePosition, (float)vfovagain ); SHAVEset_cameraOPEN( camvm, shavePosition, port_width, port_height, pixelAspect, (float)vfovagain, 0.01f ); } ///////////////////////////////// int *hairlist=NULL; hairlist = (int*)malloc(sizeof(int)*numToDisplay); for(unsigned int k=0; k= (int)numToDisplay) threadInfo[i].endHair = (int)numToDisplay - 1; SHAVEstart_thread(threadGroup, cacheOneHair, &threadInfo[i]); } SHAVEend_thread_group(threadGroup); delete [] threadInfo; //compute extent by tips unsigned int s = (unsigned int)mDisplayHairCache.displayHair.size(); MFloatPoint c(0.0f, 0.0f, 0.0f); float lSq = 0.0f; for (i = 0; i < s; i++) { const DisplayHair& hair = mDisplayHairCache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; c.x += strand.verts[xx].x; c.y += strand.verts[xx].y; c.z += strand.verts[xx].z; } c.x /= (float)s; c.y /= (float)s; c.z /= (float)s; for (i = 0; i < s; i++) { const DisplayHair& hair = mDisplayHairCache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; MFloatPoint v(strand.verts[xx].x,strand.verts[xx].y,strand.verts[xx].z); MFloatPoint d = v-c; float lsq = d.x*d.x + d.y*d.y + d.z*d.z; if(lsq > lSq) lSq = lsq; } mDisplayHairCache.center = c; mDisplayHairCache.radius = sqrt(lSq); } mDisplayHairCacheDirty = false; } #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::getDisplayHairs(1) - done", NULL); #endif RETURN(mDisplayHairCache); } #if 0 bool shaveHairShape::getDisplayHairs(unsigned int numToDisplay, shaveHairShape::DisplayHairCache& cache) { ENTER(); if (mNodeInitialized) { cache.displayHair.clear(); cache.displayHair.reserve(numToDisplay); { makeCurrent(); int hairGroup = getHairGroup(); int numSegs = getNumDisplaySegments(); unsigned h; unsigned i; DisplayHair emptyHair; //unsigned numNew = numToDisplay/* - curCacheSize*/; unsigned hairsPerThread; unsigned numThreads; numThreads = shaveUtil::getThreadSettings(numToDisplay, &hairsPerThread); ThreadData* threadInfo = new ThreadData[numThreads?numThreads:1]; void* threadGroup = SHAVEstart_thread_group(numThreads); // // Populate the cache with empty hairs. // for (h = 0; h < numToDisplay; h++) cache.displayHair.push_back(emptyHair); // // Launch the threads. // for (h = 0, i = 0; h < numToDisplay; h += hairsPerThread, i++) { threadInfo[i].cache = &cache.displayHair; threadInfo[i].hairGroup = hairGroup; threadInfo[i].numSegs = numSegs; threadInfo[i].startHair = h; threadInfo[i].endHair = h + hairsPerThread - 1; if (threadInfo[i].endHair >= (int)numToDisplay) threadInfo[i].endHair = (int)numToDisplay - 1; SHAVEstart_thread(threadGroup, cacheOneHair, &threadInfo[i]); } SHAVEend_thread_group(threadGroup); delete [] threadInfo; //compute extent by tips unsigned int s = (unsigned int)cache.displayHair.size(); MFloatPoint c(0.0f, 0.0f, 0.0f); float lSq = 0.0f; for (i = 0; i < s; i++) { const DisplayHair& hair = cache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; c.x += strand.verts[xx].x; c.y += strand.verts[xx].y; c.z += strand.verts[xx].z; } c.x /= (float)s; c.y /= (float)s; c.z /= (float)s; for (i = 0; i < s; i++) { const DisplayHair& hair = cache.displayHair[i]; if(hair.size() == 0) continue; const DisplayStrand& strand = hair[0]; int xx = strand.verts.length()-1; MFloatPoint v(strand.verts[xx].x,strand.verts[xx].y,strand.verts[xx].z); MFloatPoint d = v-c; float lsq = d.x*d.x + d.y*d.y + d.z*d.z; if(lsq > lSq) lSq = lsq; } cache.center = c; cache.radius = sqrt(lSq); } } RETURN(true); } #endif #endif unsigned shaveHairShape::getGuideCount() { ENTER(); if (mNodeInitialized) { if (mGuideCountDirty) { SOFTGUIDE guide; int guideIndex = 0; makeCurrent(); while (SHAVEfetch_guide(guideIndex, &guide) != -1) guideIndex++; mGuideCount = (int)guideIndex; mGuideCountDirty = false; } } RETURN(mGuideCount); } //////////////////// procedrual api functions declared in shaveProceduralAPI.h ///////// #ifdef USE_PROCEDURALS void SHAVESDKadd_procedural(PROCEDURAL *params, PFPROCEDURAL callback, SHAVENODE *current_node){} HAIRTYPE* SHAVEupdate_procedural_values(PROCEDURAL *params, SHAVENODE *current_node){return NULL;} void SHAVESDKdelete_procedural(SHAVENODE *current_node){} #endif /////////////////////////////////////////////////////////////////////////////////////// MStatus shaveHairShape::setDependentsDirty( const MPlug& dirty, MPlugArray& affected ) { //////////////////////// //printf("plug diritied %s\n",dirty.name().asChar()); fflush(stdout); //MGlobal::displayInfo(MString("plug diritied ")+dirty.name()); //////////////////////// ENTER(); ////////////////// #ifdef DO_PROFILE if(!Profile::GetDiagFile()) Profile::ProfileStart(NULL); Profile::ProfileDump("shaveHairShape::setDependentsDirty", NULL); #endif if(dirty == shaveParamHaircount) { dirties.DIRTY_HAIRCOUNT = 1; dirties.DIRTY_TEXTURE = 1; //need to update cache as well } else if(dirty == shaveParamPasses) { dirties.DIRTY_PASSES = 1; dirties.DIRTY_TEXTURE = 1; //need to update cache as well } //todo: need to dirty it in shave globals //else if(dirty == displayHairMaxAttr || // dirty == displayHairRatioAttr) //{ // dirties.DIRTY_DISPLAY_COUNT = 1; // dirties.DIRTY_TEXTURE = 1; //need to update cache as well //} //else if(dirty == aDisplaySegmentLimit) // dirties.DIRTY_DISPLAYSEGS = 1; //dirty it globals else if(dirty == timeAttr) dirties.DIRTY_TIME = 1; else if(dirty == shaveParamMultStrand) dirties.DIRTY_CLUMPS = 1; else if(dirty == clumps) dirties.DIRTY_MULT = 1; //its constant SHAVE_VERTS_PER_GUIDE-1 //else if(dirty == aDisplaySegmentLimit) // dirties.DIRTY_GUIDE_SEGS = 1; //////////////////// #ifdef PER_NODE_TEXLOOKUP bool texChanged = false; if(dirty.isConnected()) { if(dirty == hairColorTexture || dirty == rootHairColorTexture || dirty == mutantHairColorTexture) { texChanged = true; } MPlug texPlug(thisMObject(),shaveHairShape::shaveTextureAttr); int nel = texPlug.numElements(); for(int k = 0; k < nel; k++) { MPlug checkPlug = texPlug.elementByLogicalIndex(k); if(checkPlug == dirty) { texChanged = true; break; } } } //not sure that it's a good place if(texChanged /* && !IsBuildingLookups()*/) { //////////////////////// //printf("textures changed %s\n",dirty.name().asChar()); fflush(stdout); //////////////////////// mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; //mDirtyTexture = true; dirties.DIRTY_TEXTURE = 1; dirties.DIRTY_TEXTURE_JOE = 1; MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); //MObjectArray shaveHairShapes; //shaveUtil::getShaveNodes(shaveHairShapes); //if (shaveHairShapes.length() > 0) //{ // shaveGlobals::Globals globals; // shaveGlobals::getGlobals(globals); //texture_is_updating = true; //initTexInfoLookup2(shaveHairShapes, "", globals.verbose,true ,thisMObject()); //texture_is_updating = false; //} } #endif //////////////////////////////////////////// if ((dirty == growthObjectsGroupIDAttr) || (dirty == inputCurve) || (dirty == inputMesh) || (dirty == inputSurf)) { mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; mGuideCacheDirty = true; mGuideCountDirty = true; ///////////////// doFallbackGlob = true; //////////////// MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); } else if ((dirty == shaveParamHaircount) || (dirty == aDisplaySegmentLimit) //it's in globals now || (dirty == aDisplayGuideThick) //it's in globals now || (dirty == aDisplayGuides) //it's in globals now || (dirty == collisionObjectsAttr) || (dirty == collisionObjectsGroupIDAttr) || (dirty == instanceMesh) || (dirty == runDynamics) || (dirty == shaveBlindHair) || (dirty == shaveParamAmbDiff) || (dirty == shaveParamAnimSpeed) || (dirty == shaveParamDampening) || (dirty == shaveParamDisplacement) || (dirty == shaveParamFrizzAnim) || (dirty == shaveParamFrizzAnimDir) || (dirty == shaveParamFrizzAnimDirX) || (dirty == shaveParamFrizzAnimDirY) || (dirty == shaveParamFrizzAnimDirZ) || (dirty == shaveParamFrizzFreqX) || (dirty == shaveParamFrizzFreqY) || (dirty == shaveParamFrizzFreqZ) || (dirty == shaveParamFrizzRoot) || (dirty == shaveParamFrizzTip) || (dirty == shaveParamGloss) || (dirty == shaveParamHairColor) || (dirty == shaveParamHueVariation) || (dirty == shaveParamInstancingStatus) || (dirty == shaveParamKinkFreqX) || (dirty == shaveParamKinkFreqY) || (dirty == shaveParamKinkFreqZ) || (dirty == shaveParamKinkRoot) || (dirty == shaveParamKinkTip) || (dirty == shaveParamMultStrand) || (dirty == shaveParamMutantColor) || (dirty == shaveParamMutantPercent) || (dirty == shaveParamNoInterp) || (dirty == shaveParamPasses) || (dirty == shaveParamRandScale) || (dirty == shaveParamRandomizeMulti) || (dirty == shaveParamRootColor) || (dirty == shaveParamRootStiffness) || (dirty == shaveParamScale) || (dirty == shaveParamSegs) || (dirty == shaveParamSelfShadow) || (dirty == shaveParamSpecular) || (dirty == shaveParamSplayRoot) || (dirty == shaveParamSplayTip) || (dirty == shaveParamMultAsp) //vlad|05July2010 || (dirty == shaveParamOffset) //vlad|09July2010 || (dirty == shaveParamAspect) //vlad|09July2010 || (dirty == shaveParamStiffness) || (dirty == shaveParamThickRoot) || (dirty == shaveParamThickTip) || (dirty == shaveParamValueVariation) || (dirty == surfTessU) || (dirty == surfTessV) || (dirty == subdTessDept) || (dirty == subdTessSamp) || (dirty == textureCacheUpdatedAttr) || (dirty == timeAttr) || (dirty == aSquirrel) || (dirty == flyawayPerc) || (dirty == flyawayStren) || (dirty == messStren) || (dirty == clumps) || (dirty == clumpsStren) || (dirty == clumpsColStren) || (dirty == clumpsRotStren) || (dirty == clumpsRotOffset) || (dirty == clumpsRandomize) || (dirty == clumpsFlatness) || (dirty == clumpsScruffle) || (dirty == randomSeedOffset) || (dirty == triggerAttr) ) { //printf("display cache dirtied\n");fflush(stdout); mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; mGuideCacheDirty = true; ///////////////// if(dirty != triggerAttr && dirty != textureCacheUpdatedAttr) { doFallbackGlob = true; } //////////////// MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); } //todo: move it to shave globals // else if( dirty == shaveParamHaircount || // dirty == displayHairMaxAttr || // dirty == displayHairRatioAttr) // // { // mDisplayInstCacheDirty = true; // MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); // } #ifdef USE_PROCEDURALS else if (dirty == procedural) { if(dirty.isConnected()) { MStatus stat; MPlugArray cons; if(dirty.connectedTo(cons,true,false,&stat)) { if(stat == MStatus::kSuccess && cons.length() > 0) { MObject procnode = cons[0].node(); MFnDependencyNode dFn(procnode); shaveProcedural* procedural = (shaveProcedural*)dFn.userNode(); PROCEDURAL* params = procedural->GetShaveParams(); PFPROCEDURAL aproc = procedural->GetShaveProc(); HAIRTYPE* sample = SHAVEupdate_procedural_values(params,&hairnode); } } } } #endif #ifdef DO_PROFILE Profile::ProfileDump("shaveHairShape::setDependentsDirty - done", NULL); #endif RETURN(MS::kSuccess); } //static bool texture_is_updating = false; MStatus shaveHairShape::connectionMade ( const MPlug& plug, const MPlug& otherPlug, bool asSrc ) { #ifdef USE_PROCEDURALS if(plug == procedural) { MObject procnode = otherPlug.node(); MFnDependencyNode dFn(procnode); shaveProcedural* procedural = (shaveProcedural*)dFn.userNode(); PROCEDURAL* params = procedural->GetShaveParams(); PFPROCEDURAL aproc = procedural->GetShaveProc(); // fires 'unsignd int' loses precicion on OSX , type cast will fix it SHAVESDKadd_procedural(params,aproc,&hairnode); } #endif #ifdef PER_NODE_TEXLOOKUP bool texConnected = false; if(plug == hairColorTexture || plug == rootHairColorTexture || plug == mutantHairColorTexture) { texConnected = true; } MPlug texPlug(thisMObject(),shaveHairShape::shaveTextureAttr); int nel = texPlug.numElements(); for(int k = 0; k < nel; k++) { MPlug checkPlug = texPlug.elementByLogicalIndex(k); if(checkPlug == plug) { texConnected = true; break; } } //not sure that it's a good place if(texConnected /*&& !IsBuildingLookups()*/) { mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; //mDirtyTexture = true; dirties.DIRTY_TEXTURE = 1; dirties.DIRTY_TEXTURE_JOE = 1; //MObjectArray shaveHairShapes; //shaveUtil::getShaveNodes(shaveHairShapes); //if (shaveHairShapes.length() > 0) //{ // shaveGlobals::Globals globals; // shaveGlobals::getGlobals(globals); // initTexInfoLookup2(shaveHairShapes, "", globals.verbose,true ,thisMObject()); //} } #endif return MPxSurfaceShape::connectionMade(plug,otherPlug,asSrc); } MStatus shaveHairShape::connectionBroken (const MPlug & plug, const MPlug &otherPlug, bool asSrc ) { #ifdef USE_PROCEDURALS if(plug == procedural) { SHAVESDKdelete_procedural(&hairnode); } #endif #ifdef PER_NODE_TEXLOOKUP bool texConnected = false; if(plug == hairColorTexture || plug == rootHairColorTexture || plug == mutantHairColorTexture) { texConnected = true; } MPlug texPlug(thisMObject(),shaveHairShape::shaveTextureAttr); int nel = texPlug.numElements(); for(int k = 0; k < nel; k++) { MPlug checkPlug = texPlug.elementByLogicalIndex(k); if(checkPlug == plug) { texConnected = true; break; } } //not sure that it's a good place if(texConnected /* && !IsBuildingLookups()*/) { mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; //mDirtyTexture = true; dirties.DIRTY_TEXTURE = 1; dirties.DIRTY_TEXTURE_JOE = 1; } #endif return MPxSurfaceShape::connectionBroken(plug,otherPlug,asSrc); } #ifdef PER_NODE_TEXLOOKUP void shaveHairShape::updateTexLookups() { if(!this->mNodeInitialized) return; if(/*mDirtyTexture*/ dirties.DIRTY_TEXTURE == 1 && !IsBuildingLookups()) { MObjectArray shaveHairShapes; shaveUtil::getShaveNodes(shaveHairShapes); if (shaveHairShapes.length() > 0) { //////////////////////// //printf("update textures\n"); fflush(stdout); //////////////////////// shaveGlobals::Globals globals; shaveGlobals::getGlobals(globals); //printf("00 - hasPendingEvetns: %s\n",QCoreApplication::hasPendingEvents()?"yes":"no");fflush(stdout); //something there is causing stacking in fallback //there is a lot of stuff happening initTexInfoLookup2(shaveHairShapes, "", globals.verbose,true ,thisMObject()); //nope, does not help and creates exra flickering //if(doFallbackGlob) //need to make sure that there are some textures //{ // //printf("fullRedrawOnIdle fired\n");fflush(stdout); // qint64 t = GetQTimer().elapsed(); // SetLastMoveTime(t); // shaveStyleCmd::redrawOnIdlDone = false; // MGlobal::executeCommandOnIdle("shaveStyle -fullRedrawOnIdle"); //} //nope, makes entire view flickering //M3dView::active3dView().refresh(true,true); } //mDirtyTexture = false; dirties.DIRTY_TEXTURE = 0; } } #endif int shaveHairShape::getHairGroup(MDataBlock* block) const { ENTER(); short hairGroup; if (block) { hairGroup = block->inputValue(aHairGroup).asShort(); } else { MPlug plug(thisMObject(), aHairGroup); plug.getValue(hairGroup); } RETURN((int)hairGroup); } #if MAYA_API_VERSION >= 20180000 bool shaveHairShape::getInternalValue(const MPlug &plug, MDataHandle &hdl) #else bool shaveHairShape::getInternalValueInContext( const MPlug& plug, MDataHandle& hdl, MDGContext& ctx ) #endif { ENTER(); if (plug == aNodeVersion) { // // If the version is zero then it has not yet been explicitly set, // so we must determine an appropriate value. // if (mNodeVersion == 0) { // // If we're in the middle of file loading, we might still be // awaiting the set command so we should continue to return 0. // Otherwise we assume that this is a newly-created node and // return the latest version. // // I used to do this in the postConstructor() but that was // causing random crashes within Maya, so we do it here now, // instead. // if (!shaveUtil::isLoadingFile()) mNodeVersion = kNodeVersion; } hdl.set(mNodeVersion); RETURN(true); } if (!plug.isArray() && ((plug == mControlPoints) || (plug.parent() == mControlPoints) || (plug == aGuide) || (plug.parent() == aGuide))) { bool isGuide = ((plug == aGuide) || (plug.parent() == aGuide)); bool isParent = ((plug == mControlPoints) || (plug == aGuide)); int plugIndex; if (isParent) plugIndex = plug.logicalIndex(); else plugIndex = plug.parent().logicalIndex(); SOFTGUIDE guide; int guideIndex; int vertIndex; if (isGuide) { guideIndex = plugIndex; // // For the position of a guide, we use its middle vertex. // vertIndex = SHAVE_VERTS_PER_GUIDE / 2; } else { guideIndex = plugIndex / SHAVE_VERTS_PER_GUIDE; vertIndex = plugIndex % SHAVE_VERTS_PER_GUIDE; } // // Make sure that this node is loaded into the Shave engine. // makeCurrent(); if (SHAVEfetch_guide(guideIndex, &guide) == -1) { if (isParent) hdl.set(0.0, 0.0, 0.0); else hdl.set(0.0); } else { if (isParent) { hdl.set( (double)guide.guide[vertIndex].x, (double)guide.guide[vertIndex].y, (double)guide.guide[vertIndex].z ); } else if (plug == plug.parent().child(0)) hdl.set((double)guide.guide[vertIndex].x); else if (plug == plug.parent().child(1)) hdl.set((double)guide.guide[vertIndex].y); else hdl.set((double)guide.guide[vertIndex].z); } RETURN(true); } if (plug == mHasHistoryOnCreate) { hdl.set(false); RETURN(true); } #if MAYA_API_VERSION >= 20180000 RETURN(MPxSurfaceShape::getInternalValue(plug, hdl)); #else RETURN(MPxSurfaceShape::getInternalValueInContext(plug, hdl, ctx)); #endif } #if MAYA_API_VERSION >= 20180000 bool shaveHairShape::setInternalValue(const MPlug &plug, const MDataHandle &hdl) #else bool shaveHairShape::setInternalValueInContext( const MPlug& plug, const MDataHandle& hdl, MDGContext& ctx ) #endif { ENTER(); if (plug == aNodeVersion) { mNodeVersion = hdl.asInt(); RETURN(true); } #if MAYA_API_VERSION >= 20180000 RETURN(MPxSurfaceShape::setInternalValue(plug, hdl)); #else RETURN(MPxSurfaceShape::setInternalValueInContext(plug, hdl, ctx)); #endif } void shaveHairShape::componentToPlugs( MObject& component, MSelectionList& list ) const { ENTER(); if (component.hasFn(MFn::kDoubleIndexedComponent)) { MFnDoubleIndexedComponent compFn(component); int guide; int i; int numComponents = compFn.elementCount(); MPlug plug(thisMObject(), mControlPoints); int vert; for (i = 0; i < numComponents; i++) { compFn.getElement(i, guide, vert); plug.selectAncestorLogicalIndex( guide * SHAVE_VERTS_PER_GUIDE + vert, mControlPoints ); list.add(plug); } } else if (component.hasFn(MFn::kSingleIndexedComponent)) { MFnSingleIndexedComponent compFn(component); int i; int numComponents = compFn.elementCount(); MPlug plug(thisMObject(), aGuide); for (i = 0; i < numComponents; i++) { plug.selectAncestorLogicalIndex(compFn.element(i), aGuide); list.add(plug); } } LEAVE(); } MObject shaveHairShape::createFullVertexGroup() const { ENTER(); shaveHairShape* nonConstThis = const_cast(this); unsigned numGuides = nonConstThis->getGuideCount(); MFnDoubleIndexedComponent compFn; MObject comp = compFn.create(kShaveGuideVertComponent); compFn.setCompleteData((int)numGuides, SHAVE_VERTS_PER_GUIDE); RETURN(comp); } MPxGeometryIterator* shaveHairShape::geometryIteratorSetup( MObjectArray& components1, MObject& components2, bool forReadOnly ) { ENTER(); shaveHairGeomIt* iter = 0; if (components2.isNull()) iter = new shaveHairGeomIt(this, components1); else iter = new shaveHairGeomIt(this, components2); RETURN(iter); } bool shaveHairShape::match( const MSelectionMask& mask, const MObjectArray& componentList ) const { ENTER(); bool result = false; if (componentList.length() == 0) { MSelectionMask shapeMask = getShapeSelectionMask(); result = mask.intersects(shapeMask); } else { for (unsigned int i = 0; i < componentList.length(); ++i) { if (((componentList[i].apiType() == MFn::kMeshEdgeComponent) && mask.intersects(MSelectionMask::kSelectMeshEdges)) || ((componentList[i].apiType() == MFn::kSurfaceCVComponent) && mask.intersects(MSelectionMask::kSelectCVs))) { result = true; break; } } } RETURN(result); } MPxSurfaceShape::MatchResult shaveHairShape::matchComponent( const MSelectionList& item, const MAttributeSpecArray& specs, MSelectionList& list ) { ENTER(); if (specs.length() == 1) { MAttributeSpec spec = specs[0]; MString attrName = spec.name(); // // We like to use 'guide' or 'gd' for guide components, but because // we're forced to use an existing component type (mesh edge) we // also have to accept Maya's 'e' name for the component. // if ((attrName == "guide") || (attrName == "gd") || (attrName == "e")) { if (spec.dimensions() != 1) RETURN(kMatchInvalidAttributeDim); MAttributeIndex attrIndex = spec[0]; int lower = 0; int upper = 0; unsigned numGuides = getGuideCount(); if (attrIndex.hasLowerBound()) attrIndex.getLower(lower); if (attrIndex.hasUpperBound()) attrIndex.getUpper(upper); if ((lower > upper) || (lower < 0) || (upper >= (int)numGuides)) RETURN(kMatchInvalidAttributeRange); MFnSingleIndexedComponent compFn; MObject comp; comp = compFn.create(kShaveGuideComponent); int guide; for (guide = lower; guide <= upper; guide++) compFn.addElement(guide); MDagPath path; item.getDagPath(0, path); list.add(path, comp); RETURN(kMatchOk); } // // We like to use 'vertex' or 'vtx' for vertex components, but // because we're forced to use an existing component type (surface // cv) we also have to accept Maya's 'cv' name for the component. // if ((attrName == "vertex") || (attrName == "vtx") || (attrName == "cv")) { if (spec.dimensions() != 2) RETURN(kMatchInvalidAttributeDim); MAttributeIndex attrIndex = spec[0]; int lowerGuide = 0; int upperGuide = 0; unsigned numGuides = getGuideCount(); if (attrIndex.hasLowerBound()) attrIndex.getLower(lowerGuide); if (attrIndex.hasUpperBound()) attrIndex.getUpper(upperGuide); if ((lowerGuide > upperGuide) || (lowerGuide < 0) || (upperGuide >= (int)numGuides)) { RETURN(kMatchInvalidAttributeRange); } attrIndex = spec[1]; int lowerVert = 0; int upperVert = 0; if (attrIndex.hasLowerBound()) attrIndex.getLower(lowerVert); if (attrIndex.hasUpperBound()) attrIndex.getUpper(upperVert); if ((lowerVert > upperVert) || (lowerVert < 0) || (upperVert >= SHAVE_VERTS_PER_GUIDE)) { RETURN(kMatchInvalidAttributeRange); } MFnDoubleIndexedComponent compFn; MObject comp; comp = compFn.create(kShaveGuideVertComponent); int guide; int vert; for (guide = lowerGuide; guide <= upperGuide; guide++) { for (vert = lowerVert; vert <= upperVert; vert++) compFn.addElement(guide, vert); } MDagPath path; item.getDagPath(0, path); list.add(path, comp); RETURN(kMatchOk); } } RETURN(MPxSurfaceShape::matchComponent(item, specs, list)); } void shaveHairShape::transformUsing( const MMatrix& mat, const MObjectArray& components ) { ENTER(); transformUsing(mat, components, kNoPointCaching, 0); LEAVE(); } // // The C++ standard does not allow locally declared types to be used as // template arguments. To get around this we have to to declare the types // globally. By using an anonymous namespace we can prevent the types from // being exported and polluting the external namespace. // namespace { struct VertRef { int vertIdx; int cacheIdx; }; typedef std::vector VertRefs; } void shaveHairShape::transformUsing( const MMatrix& mat, const MObjectArray& components, MPxSurfaceShape::MVertexCachingMode cachingMode, MPointArray* pointCache ) { ENTER(); if (pointCache == 0) cachingMode = kNoPointCaching; // // Our points are in worldSpace while the transformation matrix is // expecting local space. So modify the matrix to take us into local // space at the start, then back out to worldSpace when done. // MDagPath path; MDagPath::getAPathTo(thisMObject(), path); MMatrix m = path.inclusiveMatrixInverse()*mat*path.inclusiveMatrix(); // // Make sure that this hair shape is loaded into the shave engine. // makeCurrent(); if (components.length() == 0) { // // No components were specified, so we must apply the // transformation to every vert. // unsigned cacheIdx = 0; unsigned cacheLen = 0; SOFTGUIDE guide; int guideIdx; int i; if (cachingMode == kSavePoints) { cacheLen = getGuideCount() * SHAVE_VERTS_PER_GUIDE; pointCache->setLength(cacheLen); } else if (pointCache) cacheLen = pointCache->length(); for (guideIdx = 0; SHAVEfetch_guide(guideIdx, &guide) != -1; guideIdx++) { for (i = 0; i < SHAVE_VERTS_PER_GUIDE; i++) { if (cachingMode == kRestorePoints) { if (cacheIdx >= cacheLen) break; MPoint& p = (*pointCache)[cacheIdx]; guide.guide[i].x = (float)p.x; guide.guide[i].y = (float)p.y; guide.guide[i].z = (float)p.z; } else { MPoint p( guide.guide[i].x, guide.guide[i].y, guide.guide[i].z ); if (cachingMode == kSavePoints) (*pointCache)[cacheIdx] = p; p *= m; guide.guide[i].x = (float)p.x; guide.guide[i].y = (float)p.y; guide.guide[i].z = (float)p.z; } guide.select[i] = 1; cacheIdx++; } // // We may have exited early from the inner loop because the // pointCache was exhausted, in which case we should exit the // outer loop, too. // if (i < SHAVE_VERTS_PER_GUIDE) break; SOFTput_guide(guideIdx, &guide); } } else { // // Sort the components by guides. // std::map guideVerts; int guideIdx; unsigned i; int j; int k; struct VertRef vr; vr.cacheIdx = 0; for (i = 0; i < components.length(); i++) { // // Vertices // if (components[i].hasFn(MFn::kDoubleIndexedComponent)) { MFnDoubleIndexedComponent compFn(components[i]); for (j = 0; j < compFn.elementCount(); j++) { compFn.getElement(j, guideIdx, vr.vertIdx); guideVerts[guideIdx].push_back(vr); vr.cacheIdx++; } } // // Entire guides. // else if (components[i].hasFn(MFn::kSingleIndexedComponent)) { MFnSingleIndexedComponent compFn(components[i]); for (j = 0; j < compFn.elementCount(); j++) { guideIdx = compFn.element(j); for (k = 0; k < SHAVE_VERTS_PER_GUIDE; k++) { vr.vertIdx = k; guideVerts[guideIdx].push_back(vr); vr.cacheIdx++; } } } } unsigned cacheLen = 0; if (cachingMode == kSavePoints) { cacheLen = vr.cacheIdx; pointCache->setLength(cacheLen); } else if (pointCache) cacheLen = pointCache->length(); SOFTGUIDE guide; std::map::iterator iter; for (iter = guideVerts.begin(); iter != guideVerts.end(); iter++) { VertRefs& verts = (*iter).second; guideIdx = (*iter).first; if (SHAVEfetch_guide(guideIdx, &guide) != -1) { guide.select[0] = 1; for (j = 1; j < SHAVE_VERTS_PER_GUIDE; j++) guide.select[j] = 0; for (i = 0; i < verts.size(); i++) { int cacheIdx = verts[i].cacheIdx; int vertIdx = verts[i].vertIdx; if (cachingMode == kRestorePoints) { if (cacheIdx < (int)cacheLen) { MPoint& p = (*pointCache)[cacheIdx]; guide.guide[vertIdx].x = (float)p.x; guide.guide[vertIdx].y = (float)p.y; guide.guide[vertIdx].z = (float)p.z; } } else { MPoint p( guide.guide[vertIdx].x, guide.guide[vertIdx].y, guide.guide[vertIdx].z ); if (cachingMode == kSavePoints) (*pointCache)[cacheIdx] = p; p *= m; guide.guide[vertIdx].x = (float)p.x; guide.guide[vertIdx].y = (float)p.y; guide.guide[vertIdx].z = (float)p.z; } guide.select[vertIdx] = 1; } SOFTput_guide(guideIdx, &guide); } } } mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; mGuideCacheDirty = true; ///////////////// doFallbackGlob = true; //////////////// applyEdits(true); childChanged(); LEAVE(); } void shaveHairShape::clearComponentSelections() { ENTER(); // // Clear the selection bits. // Guides::iterator iter; for (iter = mGuideCache.guides.begin(); iter != mGuideCache.guides.end(); iter++) { (*iter).select = 0; } mHaveSelections = false; mGuideSelectionsDirty = false; LEAVE(); } void shaveHairShape::clearHiddenGuides() { ENTER(); Guides::iterator iter; for (iter = mGuideCache.guides.begin(); iter != mGuideCache.guides.end(); iter++) { (*iter).hidden = false; } LEAVE(); } void shaveHairShape::clearSelections() { ENTER(); clearComponentSelections(); clearHiddenGuides(); LEAVE(); } void shaveHairShape::updateSelections(const MSelectionList& selections) { ENTER(); MObject comp; bool haveNewSelections = false; unsigned i; MDagPath path; // // Find the first selected component from this node, if there is one. // for (i = 0; i < selections.length(); i++) { selections.getDagPath(i, path, comp); if (path.isValid() && !comp.isNull() && (path.node() == thisMObject())) { haveNewSelections = true; break; } } // // If we have any new selections to set or old ones to clear then mark // the selections as being dirty. // if (haveNewSelections || mHaveSelections) { mGuideSelectionsDirty = true; } LEAVE(); } void shaveHairShape::makeCurrent(bool trigger) { ENTER(); //printf("makeCurrent(%s)\n",trigger?"true":"false");fflush(stdout); // // Make sure that the shaveNode is up-to-date by pulling its trigger. // if(trigger) { MPlug plug(thisMObject(), triggerAttr); float value; plug.getValue(value); } // // If the node is not currently loaded into the shave engine, then load // it in now. // if (!isCurrent()) SHAVEflush_state(&hairnode); /////////////// // MFnDependencyNode dFn(thisMObject()); // MGlobal::displayInfo(MString("SHAVEflush_state ")+ dFn.name() + MString(" ") + this->getShaveID()); ////////////////// LEAVE(); } void shaveHairShape::applyEdits(bool resetRestPose) { ENTER(); if (getHairGroup() == 4) updateSplines(); else { // // The edits will be in the engine's current state. So let's fetch // that into our local store. // SHAVEfetch_node(&hairnode); if (resetRestPose) SHAVEreplace_rest(&hairnode); updateBlindData(); } // // Presumably, if something was edited, then we have changes that need // to be redrawn. // guidesChanged(); LEAVE(); } void shaveHairShape::guidesChanged() { ENTER(); // // If one or more guides have been modified then our display hair cache // is no longer valid. // mDisplayHairCacheDirty = true; mDisplayInstCacheDirty = true; mGuideCacheDirty = true; ////////////////// doFallbackGlob = true; ////////////////// childChanged(); MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); LEAVE(); } void shaveHairShape::applySelections() { ENTER(); makeCurrent(); const GuidesSnapshot& cachedGuides = getGuides(); bool componentMode = (MGlobal::selectionMode() == MGlobal::kSelectComponentMode); SOFTGUIDE guide; int g; int v; for (g = 0; SHAVEfetch_guide(g, &guide) != -1; g++) { // // If we're not in component mode, then we treat every vert as // being selected. // for (v = 1; v < SHAVE_VERTS_PER_GUIDE; v++) { if (!componentMode || (cachedGuides.guides[g].select & (1 << v))) guide.select[v] = 1; else guide.select[v] = 0; } // // If any verts on the guide were selected then select the root // vert as well. // if (!componentMode || cachedGuides.guides[g].select) guide.select[0] = 1; else guide.select[0] = 0; SOFTput_guideSELECTONLY(g, &guide); } LEAVE(); } float shaveHairShape::getGuideThinkness() const { shaveGlobals::getGlobals(); return guideThickGlob; //float n = 0.0f; //MPlug gth(thisMObject(),aDisplayGuideThick); //gth.getValue(n); //return n; } float shaveHairShape::getGuideExt() const { float extent = 0.0f; MPlug plug(thisMObject(),aDisplayGuideExt); plug.getValue(extent); return extent; } void shaveHairShape::setGuideExt(float e) { MPlug gth(thisMObject(),aDisplayGuideExt); gth.setValue(e); } void shaveHairShape::setGuideDisplayOverride(bool enabled) { ENTER(); mGuideDisplayOverride = enabled; // // If the override is off and the regular guide display flag is also // off, then clear the guide cache to free up that memory. // if (!mGuideDisplayOverride) { //MPlug plug(thisMObject(), aDisplayGuides); //plug.getValue(enabled); enabled = displayGuidesGlob; if (!enabled) { mGuideCache.guides.clear(); mGuideCacheDirty = true; } } LEAVE(); } void shaveHairShape::hideSelectedGuides() { ENTER(); // // Mark all selected guides in the cache as hidden. // unsigned i; for (i = 0; i < mGuideCache.guides.size(); i++) { if (mGuideCache.guides[i].select) mGuideCache.guides[i].hidden = true; } // // Tell Shave to hide the selected guides. // SHAVEselect_hide(); // // Select all remaining unhidden guides. // int g; SOFTGUIDE guide; for (g = 0; SHAVEfetch_guide(g, &guide) != -1; g++) { if (!guide.hidden) { for (i = 0; i < SHAVE_VERTS_PER_GUIDE; i++) guide.select[i] = 1; SOFTput_guideSELECTONLY(g, &guide); } } // // Update Maya's selection list. That will eventually result in us // being asked to update our cached selections, so there's no point in // updating them right now. // updateMayaComponentSelections(); LEAVE(); } void shaveHairShape::unhideGuides() { ENTER(); clearHiddenGuides(); SHAVEselect_unhide(); // // Let Maya know that a redisplay is required. // childChanged(); LEAVE(); } // // Set Maya's component selections to reflect the current state of guide // and vert selections inside the shave node. // // We completely replace the current selection list here, rather than // trying to preserve other selections. // // Changing the selection list will fire the selectionChanged callback // which will in turn do component conversions and update the hair shape's // cache, if necessary. // void shaveHairShape::updateMayaComponentSelections() { ENTER(); if (MGlobal::selectionMode() == MGlobal::kSelectComponentMode) { makeCurrent(); MString selMode; MGlobal::executeCommand("optionVar -q shaveBrushSelectMode", selMode); bool isGuides = (selMode == "guide"); MFnSingleIndexedComponent guideCompFn; MFnDoubleIndexedComponent vertCompFn; MObject comp; if (isGuides) { comp = guideCompFn.create(shaveHairShape::kShaveGuideComponent); } else { comp = vertCompFn.create(shaveHairShape::kShaveGuideVertComponent); } SOFTGUIDE g; int i; int j; for (i = 0; SHAVEfetch_guide(i, &g) != -1; i++) { if (isGuides) { if (g.select[0]) guideCompFn.addElement(i); } else { bool isSelected = false; for (j = 1; j < SHAVE_VERTS_PER_GUIDE; j++) { if (g.select[j]) { vertCompFn.addElement(i, j); isSelected = true; } } if (isSelected) vertCompFn.addElement(i, 0); } } MSelectionList list; MDagPath path; MDagPath::getAPathTo(thisMObject(), path); list.add(path, comp); MGlobal::setActiveSelectionList(list); } LEAVE(); } // // Add a UV set to the set of those that Shave knows about for this node. // int shaveHairShape::addShaveUVSets() { ENTER(); SHAVENODE* hairNode = getHairNode(); // 'growthCollisionUVs' contains UV values in its 'v' array, which is // what SHAVEadd_uvset() is expecting. Note that these are // per-face-vert UVs while those in memShaveObj.uv are per-vert. int setIdx = SHAVEadd_uvset(hairNode, &growthCollisionUVs); RETURN(setIdx); } void shaveHairShape::removeShaveUVSets() { ENTER(); SHAVEclear_uvsets(getHairNode()); LEAVE(); } MObjectArray shaveHairShape::activeComponents() const { ENTER(); MObjectArray comps = MPxSurfaceShape::activeComponents(); cout << "activeComponents returns " << comps.length() << " elements" << endl; RETURN(comps); } bool shaveHairShape::hasActiveComponents() const { ENTER(); bool result = MPxSurfaceShape::hasActiveComponents(); cout << "hasActiveComponents returns " << result << endl; RETURN(result); } int shaveHairShape::initNumShaveNodes() { ENTER(); MObjectArray nodes; shaveUtil::getShaveNodes(nodes); numShaveNodes = (int)nodes.length(); RETURN(numShaveNodes); } MStatus shaveHairShape::legalConnection( const MPlug& ourPlug, const MPlug& theirPlug, bool weAreSource, bool& isLegal ) { if (((ourPlug == aCollisionSet) || (ourPlug == aGrowthSet)) && !weAreSource) { // We only allow incoming connections to 'collisionSet' and // 'growthSet' from objectSet nodes. isLegal = theirPlug.node().hasFn(MFn::kSet); return MS::kSuccess; } return MPxSurfaceShape::legalConnection(ourPlug, theirPlug, weAreSource, isLegal); } void shaveHairShape::beforeDuplication(void* clientData) { if (clientData) static_cast(clientData)->beforeDuplication(); } void shaveHairShape::beforeDuplication() { // When Maya duplicates a node it insists on putting the duplicate // into all of the same sets as the original. We don't want the // duplicate to appear in the original's growth and collision sets so // we save their contents at the start of the duplication and restore // them afterward. saveGeomSet(aCollisionSet, mSavedCollisionList); saveGeomSet(aGrowthSet, mSavedGrowthList); } void shaveHairShape::afterDuplication(void* clientData) { if (clientData) static_cast(clientData)->afterDuplication(); } void shaveHairShape::afterDuplication() { restoreGeomSet(aCollisionSet, mSavedCollisionList); restoreGeomSet(aGrowthSet, mSavedGrowthList); } void shaveHairShape::saveGeomSet(const MObject& attr, MSelectionList& saveList) { MObject set = getGeomSet(attr); saveList.clear(); if (!set.isNull()) { MFnSet setFn(set); setFn.getMembers(saveList, false); } } void shaveHairShape::restoreGeomSet(const MObject& attr, MSelectionList& saveList) { MSelectionList newMembers; MObject set; set = getGeomSet(attr); if (!set.isNull()) { MFnSet setFn(set); setFn.getMembers(newMembers, false); // Remove the original members from the current list of members. newMembers.merge(saveList, MSelectionList::kRemoveFromList); // If there are any members left in the list they were added by // the duplication and must be removed from the set. if (!newMembers.isEmpty()) setFn.removeMembers(newMembers); } } bool shaveHairShape::getWorldGeomAttrs( const MObject& shape, MObject& worldGeomAttr, MObject& growthTargetAttr ) { static MObject worldCurveAttr = MObject::kNullObj; static MObject worldMeshAttr = MObject::kNullObj; static MObject worldSubdivAttr = MObject::kNullObj; static MObject worldSurfaceAttr = MObject::kNullObj; bool result = true; MFnDependencyNode shapeFn(shape); if (shape.hasFn(MFn::kMesh)) { if (worldMeshAttr.isNull()) worldMeshAttr = shapeFn.attribute("worldMesh"); worldGeomAttr = worldMeshAttr; growthTargetAttr = inputMesh; } else if (shape.hasFn(MFn::kNurbsCurve)) { if (worldCurveAttr.isNull()) worldCurveAttr = shapeFn.attribute("worldSpace"); worldGeomAttr = worldCurveAttr; growthTargetAttr = inputCurve; } else if (shape.hasFn(MFn::kNurbsSurface)) { if (worldSurfaceAttr.isNull()) worldSurfaceAttr = shapeFn.attribute("worldSpace"); worldGeomAttr = worldSurfaceAttr; growthTargetAttr = inputSurf; } else if (shape.hasFn(MFn::kSubdiv)) { if (worldSubdivAttr.isNull()) worldSubdivAttr = shapeFn.attribute("worldSubdiv"); worldGeomAttr = worldSubdivAttr; growthTargetAttr = inputSurf; } else { result = false; } return result; } MStatus shaveHairShape::setSplineLocks(const MDagPathArray& curves) { // Clear any existing splineLocks. clearSplineLocks(); // We can't use splineLocks on spline hair. if (getHairGroup() == 4) return MS::kInvalidParameter; // If 'curves' actually contains some curves, attach them to our // 'inputCurve' attr. if (curves.length() > 0) { MDGModifier dgmod; MPlug inputCurveArray(thisMObject(), inputCurve); unsigned int i; for (i = 0; i < curves.length(); ++i) { // Find the plug containing the curve's worldSpace geometry. MFnNurbsCurve curveFn(curves[i]); MPlug worldGeom = curveFn.findPlug("worldSpace"); // Get the correct instance. worldGeom = worldGeom.elementByLogicalIndex( curves[i].instanceNumber() ); // Make the connection. dgmod.connect(worldGeom, inputCurveArray.elementByLogicalIndex(i)); } dgmod.doIt(); // Set the flag to indicate that we're using splineLocks. MPlug splineLockPlug(thisMObject(), aSplineLock); splineLockPlug.setValue(true); } return MS::kSuccess; } void shaveHairShape::clearSplineLocks() { MPlug splineLockPlug(thisMObject(), aSplineLock); bool splineLockOn; SHAVENODE* hairnode = getHairNode(); splineLockPlug.getValue(splineLockOn); if (splineLockOn) { disconnectIncoming(inputCurve, NULL); splineLockPlug.setValue(false); free_geomWF(&mLockSplines); SHAVEsplinelock_clear(hairnode); } } void shaveHairShape::dirtyOutputMesh() { // The output mesh depends upon the trigger attr. // MPlug trigger(thisMObject(), triggerAttr); trigger.setValue(1.0f); } void shaveHairShape::scheduleXplant() { MPlug plug(thisMObject(), aEvalOption); // If there's any other eval option already set up (e.g. "create"), // then we shouldn't be doing an xplant as well. Only do it if // evalOption is empty. // if (plug.asString() == "") { MString cmd("doTransplant"); plug.setValue(cmd); } } MSelectionMask shaveHairShape::getComponentSelectionMask() const { MSelectionMask mask(MSelectionMask::kSelectCVs); mask.addMask(MSelectionMask::kSelectMeshEdges); return mask; } MSelectionMask shaveHairShape::getShapeSelectionMask() const { return MSelectionMask(kShapeSelectionMaskName); }