aboutsummaryrefslogtreecommitdiff
path: root/mayaPlug/shaveHairShape.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mayaPlug/shaveHairShape.cpp')
-rw-r--r--mayaPlug/shaveHairShape.cpp8379
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);
+}