diff options
Diffstat (limited to 'mayaPlug/shaveHairShape.cpp')
| -rw-r--r-- | mayaPlug/shaveHairShape.cpp | 8379 |
1 files changed, 8379 insertions, 0 deletions
diff --git a/mayaPlug/shaveHairShape.cpp b/mayaPlug/shaveHairShape.cpp new file mode 100644 index 0000000..54988d3 --- /dev/null +++ b/mayaPlug/shaveHairShape.cpp @@ -0,0 +1,8379 @@ +// 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 <QtCore/QEvent> +# include <QtCore/QElapsedTimer> +# include <QtGui/QMouseEvent> +# include <QtGui/QTabletEvent> + +#if QT_VERSION < 0x050000 +# include <QtGui/QApplication> +# include <QtGui/QWidget> +#else +# include <QtWidgets/QApplication> +# include <QtWidgets/QWidget> +#endif + +#include <map> +#include <math.h> +#include <stdlib.h> +#include <time.h> +#include <vector> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "shaveIO.h" + +#include <maya/M3dView.h> +#include <maya/MAttributeIndex.h> +#include <maya/MAttributeSpec.h> +#include <maya/MAttributeSpecArray.h> +#include <maya/MDagPath.h> +#include <maya/MDGContext.h> +#include <maya/MDGModifier.h> +#include <maya/MDoubleArray.h> +#include <maya/MFileIO.h> +#include <maya/MFloatMatrix.h> +#include <maya/MFloatPointArray.h> +#include <maya/MFnAttribute.h> +#include <maya/MFnCamera.h> +#include <maya/MFnDependencyNode.h> +#include <maya/MFnDoubleIndexedComponent.h> +#include <maya/MFnField.h> +#include <maya/MFnLight.h> +#include <maya/MFnMeshData.h> +#include <maya/MFnNonExtendedLight.h> +#include <maya/MFnNurbsCurve.h> +#include <maya/MFnNurbsCurveData.h> +#include <maya/MFnPluginData.h> +#include <maya/MFnSet.h> +#include <maya/MFnSingleIndexedComponent.h> +#include <maya/MGlobal.h> +#include <maya/MIntArray.h> +#include <maya/MItDependencyGraph.h> +#include <maya/MItSelectionList.h> +#include <maya/MMatrix.h> +#include <maya/MModelMessage.h> +#include <maya/MObjectArray.h> +#include <maya/MPlug.h> +#include <maya/MPoint.h> +#include <maya/MPointArray.h> +#include <maya/MPxObjectSet.h> +#include <maya/MPxTransform.h> +#include <maya/MTime.h> +#include <maya/MTransformationMatrix.h> +#include <maya/MRenderUtil.h> +#include <maya/MImage.h> +#include <maya/MAnimControl.h> +#include <maya/MFileObject.h> + +#ifdef _WIN32 +# include <process.h> +# include <stdio.h> +# define unlink _unlink +#else +# ifdef OSMac_ +# include <sys/types.h> +# include <sys/sysctl.h> +# endif +# include <unistd.h> +#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 <maya/MHWGeometryUtilities.h> + +#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<numHairs*hairnode.shavep.passes[hairGroup];hair++) list[hair]=hair; + MTbunch_of_hairs(numHairs*hairnode.shavep.passes[hairGroup], list,hairGroup,0, &MAYAdata,&cinfo); + free(list); +#endif + + numVertices = MAYAdata.totalverts; + numFaces = MAYAdata.totalfaces; + + if (numVertices > 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<numHairs*hairnode.shavep.passes[hairGroup];hair++) list[hair]=hair; + + MTbunch_of_hairs(numHairs*hairnode.shavep.passes[hairGroup], list,hairGroup,0, &MAYAdata,&cinfo); + free(list); + + numVertices = MAYAdata.totalverts; + numFaces = MAYAdata.totalfaces; + + if (numVertices > 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::DisplayHair>& 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<shaveHairShape::DisplayHair>* 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::DisplayHair>& 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; k<numToDisplay; k++) + hairlist[k] = k; + int hairlist_size; + hairlist_size=numToDisplay; + + //if(upto == numToDisplay) //do it only on the last iteration + { + dirties.DIRTY_DISPLAY_COUNT=0; + dirties.DIRTY_DISPLAYSEGS=0; + dirties.DIRTY_DISPLAY_COUNT=0; + dirties.DIRTY_DISPLAYSEGS=0; + dirties.DIRTY_HAIRCOUNT=0; + dirties.DIRTY_GUIDE_SEGS=0; + dirties.DIRTY_PASSES=0; + dirties.DIRTY_TEXTURE_JOE=0; + dirties.DIRTY_CLUMPS=0; + dirties.DIRTY_MULT=0; + } + } + } + } + + // + // Populate the cache with empty hairs. + // +#if 0 + for (h = curCacheSize; h < numToDisplay; h++) + mDisplayHairCache.displayHair.push_back(emptyHair); +#else + for (h = curCacheSize; h < upto; h++) + mDisplayHairCache.displayHair.push_back(emptyHair); +#endif + + if(upto < 160) + { + // + // single thread + // + ThreadData threadInfo; + threadInfo.cache = &mDisplayHairCache.displayHair; + threadInfo.hairGroup = hairGroup; + threadInfo.numSegs = numSegs; + threadInfo.startHair = 0; + threadInfo.endHair = upto - 1; + + cacheOneHair(0,&threadInfo); + } + else + { + // + // Launch the threads. + // +#ifdef REUSABLE_THREADS + unsigned int ncpus = LThreadGroup::GetNumCPUs(); + unsigned int hairsPerThread = numNew/ncpus; + ThreadData* threadInfo = new ThreadData[ncpus]; + + h = curCacheSize; + for (i = 0; i < ncpus; i++) + { + threadInfo[i].th = i; //lthGrp()->Get(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<numToDisplay; k++) + hairlist[k] = k; + int hairlist_size; + hairlist_size=numToDisplay; + + dirties.DIRTY_DISPLAY_COUNT=0; + dirties.DIRTY_DISPLAYSEGS=0; + dirties.DIRTY_DISPLAY_COUNT=0; + dirties.DIRTY_DISPLAYSEGS=0; + dirties.DIRTY_HAIRCOUNT=0; + dirties.DIRTY_GUIDE_SEGS=0; + dirties.DIRTY_PASSES=0; + dirties.DIRTY_TEXTURE_JOE=0; + dirties.DIRTY_CLUMPS=0; + dirties.DIRTY_MULT=0; + } + + // + // Populate the cache with empty hairs. + // + for (h = curCacheSize; h < numToDisplay; h++) + mDisplayHairCache.displayHair.push_back(emptyHair); + + // + // Launch the threads. + // + for (h = curCacheSize, i = 0; h < numToDisplay; h += hairsPerThread, i++) + { + threadInfo[i].cache = &mDisplayHairCache.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)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<shaveHairShape*>(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<struct VertRef> 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<int, VertRefs> 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<int, VertRefs>::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<shaveHairShape*>(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<shaveHairShape*>(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); +} |