diff options
| author | Ben Marsh <[email protected]> | 2019-10-22 09:07:59 -0400 |
|---|---|---|
| committer | Ben Marsh <[email protected]> | 2019-10-22 09:07:59 -0400 |
| commit | bd0027e737c6512397f841c22786274ed74b927f (patch) | |
| tree | f7ffbdb8f3741bb7f24635616cc189cba5cb865c /mayaPlug/shaveWriteRib.cpp | |
| download | shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.tar.xz shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.zip | |
Adding Shave-and-a-Haircut 9.6
Diffstat (limited to 'mayaPlug/shaveWriteRib.cpp')
| -rw-r--r-- | mayaPlug/shaveWriteRib.cpp | 3307 |
1 files changed, 3307 insertions, 0 deletions
diff --git a/mayaPlug/shaveWriteRib.cpp b/mayaPlug/shaveWriteRib.cpp new file mode 100644 index 0000000..4d3bcbb --- /dev/null +++ b/mayaPlug/shaveWriteRib.cpp @@ -0,0 +1,3307 @@ +// Shave and a Haircut +// (c) 2019 Epic Games +// US Patent 6720962 + +#include "shaveIO.h" + +#include <maya/MAngle.h> +#include <maya/MArgDatabase.h> +#include <maya/MArgList.h> +#include <maya/MAnimControl.h> +#include <maya/MColor.h> +#include <maya/MColorArray.h> +#include <maya/MDagPath.h> +#include <maya/MFnCamera.h> +#include <maya/MFnDagNode.h> +#include <maya/MFnDependencyNode.h> +#include <maya/MFileObject.h> +#include <maya/MFloatVector.h> +#include <maya/MFnMesh.h> +#include <maya/MGlobal.h> +#include <maya/MIntArray.h> +#include <maya/MItMeshFaceVertex.h> +#include <maya/MObjectArray.h> +#include <maya/MPlug.h> +#include <maya/MPxCommand.h> +#include <maya/MSelectionList.h> +#include <maya/MString.h> +#include <maya/MSyntax.h> + +#include <assert.h> +#include <string.h> + +# ifdef _WIN32 + // ri.h contains a lot of float/double mismatches. Let's stop VC++ from warning us about them. +# pragma warning(push) +# pragma warning(disable : 4244 ) +# endif + +# include <ri.h> + +# ifdef _WIN32 +# pragma warning(pop) +# endif + +#include "shaveEngine.h" +#include "shaveGlobals.h" +#include "shaveHairShape.h" +#include "shaveMaya.h" +#include "shaveRender.h" +#include "shaveRenderer.h" +#include "shaveRenderman.h" +#include "shaveSDK.h" +#include "shaveTextureStore.h" +#include "shaveUtil.h" +#include "shaveWriteRib.h" + +// Helper class to load symbols from RenderMan_for_Maya mll. +// +class RfMFuncs +{ +public: + RfMFuncs(const shaveGlobals::Globals&); + ~RfMFuncs(); + + bool initialized; + +#if defined(__linux__) || defined(OSMac_) + void *rfmDLL; +#elif defined(_WIN32) + HMODULE rfmDLL; +#endif + + // Functions + void (*RiArchiveRecord)(RtToken type, char* format, ...); + void (*RiBasis)(RtBasis u, int ustep, RtBasis v, int vstep); + void (*RiBegin)(RtToken name); + void (*RiCurvesV)( + RtToken type, int ncurves, int* nvertices, RtToken wrap, + int n, RtToken nms[], RtPointer vals[]); + RtToken (*RiDeclare)(char*, char*); + void (*RiEnd)(); + void (*RiMotionBegin)(int n, ...); + void (*RiMotionEnd)(); + void (*RiOption)(RtToken, ...); + void (*RiPointsPolygonsV)( + int npolys, int* nverts, int* verts, int n, + RtToken nms[], RtPointer vals[]); + void (*RiProcDelayedReadArchive)(RtPointer data, float detail); + void (*RiProcedural)( + RtPointer data, RtBound bound, RtProcSubdivFunc sfunc, + RtProcFreeFunc ffunc); + void (*RiShadingInterpolation)(RtToken type); + void (*RiTransformBegin)(); + void (*RiTransformEnd)(); + + // Variables + RtBasis *RiCatmullRomBasis; + RtToken *RI_CS; + RtToken *RI_CUBIC; + RtToken *RI_LINEAR; + RtToken *RI_N; + RtToken *RI_NONPERIODIC; + RtToken *RI_OS; + RtToken *RI_P; + RtToken *RI_S; + RtToken *RI_SMOOTH; + RtToken *RI_T; + RtToken *RI_VERBATIM; + RtToken *RI_WIDTH; +}; + +#if defined(__linux__) || defined(OSMac_) +# include <dlfcn.h> +# define getDLLSymbol(hdl,name) dlsym(hdl,name) +#elif defined(_WIN32) +# define getDLLSymbol(hdl,name) GetProcAddress(hdl,name) +#else +# error Platform not supported. +#endif + + +RfMFuncs::RfMFuncs(const shaveGlobals::Globals &g) +: initialized(false) +{ + // If a RIB library override has been provided, use that. + // + MString dllPath = g.rib.libOverride; + + if (dllPath.length() == 0) + { + int rfmLoaded = 0; + MGlobal::executeCommand("pluginInfo -q -l RenderMan_for_Maya", rfmLoaded); + + if (rfmLoaded == 0) + { + MGlobal::displayError( + "No access to RenderMan functions. Please load the " + "RenderMan_for_Maya plugin, or provide" + ); + MGlobal::displayError( + "the path to an alternate RIB library in the shaveGlobals " + "'RIB Library Override' field." + ); + return; + } + +#if defined(__linux__) || defined(OSMac_) + MGlobal::executeCommand("pluginInfo -q -p RenderMan_for_Maya", dllPath); +#elif defined(_WIN32) + // If no path is provided Windows will look for a match based just + // on the name, which improves our chances of finding it. + // + dllPath = "RenderMan_for_Maya.mll"; +#endif + } + +#if defined(__linux__) || defined(OSMac_) + rfmDLL = dlopen(dllPath.asChar(), RTLD_LAZY); + if (rfmDLL == nullptr) +#elif defined(_WIN32) + // Make sure the path uses backslashes as directory separators. + // + dllPath.substitute("/", "\\"); + + rfmDLL = LoadLibrary(dllPath.asChar()); + + if (rfmDLL == 0) +#endif + { + MGlobal::displayError( + MString("Could not access shared library '") + dllPath + + "'. Please ensure that it exists and is readable and executable." + ); + return; + } + + // Functions + + this->RiArchiveRecord = (void (*)(RtToken, char*, ...))getDLLSymbol(rfmDLL, "RiArchiveRecord"); + if (this->RiArchiveRecord == nullptr) + { + MGlobal::displayError( + MString("Could not find the 'RiArchiveRecord' function in '") + + dllPath + "'." + ); + + if (g.rib.libOverride.length() > 0) + { + MGlobal::displayError( + "Please check that the 'RIB Library Override' field in " + "shaveGlobals is correct and gives the path to a RIB-compliant " + "shared library." + ); + } + else + { + MGlobal::displayError( + "Please check that RenderMan_for_Maya is correctly installed." + ); + } + + return; + } + + this->RiBasis = (void (*)(RtBasis, int, RtBasis, int))getDLLSymbol(rfmDLL, "RiBasis"); + assert(this->RiBasis != nullptr); + + this->RiBegin = (void (*)(RtToken name))getDLLSymbol(rfmDLL, "RiBegin"); + assert(this->RiBegin != nullptr); + + this->RiCurvesV = (void (*)(RtToken, int, int*, RtToken, int, RtToken[], RtPointer[]))getDLLSymbol(rfmDLL, "RiCurvesV"); + assert(this->RiCurvesV != nullptr); + + this->RiDeclare = (RtToken (*)(char*, char*))getDLLSymbol(rfmDLL, "RiDeclare"); + assert(this->RiDeclare != nullptr); + + this->RiEnd = (void (*)())getDLLSymbol(rfmDLL, "RiEnd"); + assert(this->RiEnd != nullptr); + + this->RiMotionBegin = (void (*)(int, ...))getDLLSymbol(rfmDLL, "RiMotionBegin"); + assert(this->RiMotionBegin != nullptr); + + this->RiMotionEnd = (void (*)())getDLLSymbol(rfmDLL, "RiMotionEnd"); + assert(this->RiMotionEnd != nullptr); + + this->RiOption = (void (*)(RtToken, ...))getDLLSymbol(rfmDLL, "RiOption"); + assert(this->RiOption != nullptr); + + this->RiPointsPolygonsV = (void (*)(int, int*, int*, int, RtToken[], RtPointer[]))getDLLSymbol(rfmDLL, "RiPointsPolygonsV"); + assert(this->RiPointsPolygonsV != nullptr); + + this->RiProcDelayedReadArchive = (void (*)(RtPointer, float))getDLLSymbol(rfmDLL, "RiProcDelayedReadArchive"); + assert(this->RiProcDelayedReadArchive != nullptr); + + this->RiProcedural = (void (*)(RtPointer, RtBound, RtProcSubdivFunc, RtProcFreeFunc))getDLLSymbol(rfmDLL, "RiProcedural"); + assert(this->RiProcedural != nullptr); + + this->RiShadingInterpolation = (void (*)(RtToken))getDLLSymbol(rfmDLL, "RiShadingInterpolation"); + assert(this->RiShadingInterpolation != nullptr); + + this->RiTransformBegin = (void (*)())getDLLSymbol(rfmDLL, "RiTransformBegin"); + assert(this->RiTransformBegin != nullptr); + + this->RiTransformEnd = (void (*)())getDLLSymbol(rfmDLL, "RiTransformEnd"); + assert(this->RiTransformEnd != nullptr); + + + // Variables + + this->RiCatmullRomBasis = (RtBasis*)getDLLSymbol(rfmDLL, "RiCatmullRomBasis"); + assert(this->RiCatmullRomBasis != nullptr); + + this->RI_CS = (RtToken*)getDLLSymbol(rfmDLL, "RI_CS"); + assert(this->RI_CS != nullptr); + + this->RI_CUBIC = (RtToken*)getDLLSymbol(rfmDLL, "RI_CUBIC"); + assert(this->RI_CUBIC != nullptr); + + this->RI_LINEAR = (RtToken*)getDLLSymbol(rfmDLL, "RI_LINEAR"); + assert(this->RI_LINEAR != nullptr); + + this->RI_NONPERIODIC = (RtToken*)getDLLSymbol(rfmDLL, "RI_NONPERIODIC"); + assert(this->RI_NONPERIODIC != nullptr); + + this->RI_OS = (RtToken*)getDLLSymbol(rfmDLL, "RI_OS"); + assert(this->RI_OS != nullptr); + + this->RI_P = (RtToken*)getDLLSymbol(rfmDLL, "RI_P"); + assert(this->RI_P != nullptr); + + this->RI_S = (RtToken*)getDLLSymbol(rfmDLL, "RI_S"); + assert(this->RI_S != nullptr); + + this->RI_SMOOTH = (RtToken*)getDLLSymbol(rfmDLL, "RI_SMOOTH"); + assert(this->RI_SMOOTH != nullptr); + + this->RI_T = (RtToken*)getDLLSymbol(rfmDLL, "RI_T"); + assert(this->RI_T != nullptr); + + this->RI_VERBATIM = (RtToken*)getDLLSymbol(rfmDLL, "RI_VERBATIM"); + assert(this->RI_VERBATIM != nullptr); + + this->RI_WIDTH = (RtToken*)getDLLSymbol(rfmDLL, "RI_WIDTH"); + assert(this->RI_WIDTH != nullptr); + + initialized = true; +} + + +RfMFuncs::~RfMFuncs() +{ +#if defined(__linux__) || defined(OSMac_) + if (rfmDLL != nullptr) dlclose(rfmDLL); +#elif defined(_WIN32) + if (rfmDLL != 0) FreeLibrary(rfmDLL); +#endif +} + +const MString shaveWriteRib::commandName("shaveWriteRib"); + +static const char* fsBinary = "-b"; +static const char* flBinary = "-binary"; +static const char* fsCleanup = "-cln"; +static const char* flCleanup = "-cleanup"; +static const char* fsFrame = "-f"; +static const char* flFrame = "-frame"; +static const char* fsFrameRelativeTime = "-frt"; +static const char* flFrameRelativeTime = "-frameRelativeTime"; +static const char* fsFullPaths = "-fp"; +static const char* flFullPaths = "-fullPaths"; +static const char* fsGzip = "-gz"; +static const char* flGzip = "-gzip"; +static const char* fsHairNode = "-hn"; +static const char* flHairNode = "-hairNode"; +static const char* fsHelp = "-h"; +static const char* flHelp = "-help"; +static const char* fsIgnoreShaveGlobals = "-isg"; +static const char* flIgnoreShaveGlobals = "-ignoreShaveGlobals"; +static const char* fsMotionBlur = "-mb"; +static const char* flMotionBlur = "-motionBlur"; +static const char* fsNormals = "-n"; +static const char* flNormals = "-normals"; +static const char* fsOpacities = "-opa"; +static const char* flOpacities = "-opacities"; +static const char* fsRestoreFrame = "-rf"; +static const char* flRestoreFrame = "-restoreFrame"; +static const char* fsRootTipColors = "-rtc"; +static const char* flRootTipColors = "-rootTipColors"; +static const char* fsRootPositions = "-rp"; +static const char* flRootPositions = "-rootPositions"; +static const char* fsShutterClose = "-sc"; +static const char* flShutterClose = "-shutterClose"; +static const char* fsShutterCloseOffset = "-sco"; +static const char* flShutterCloseOffset = "-shutterCloseOffset"; +static const char* fsShutterOpen = "-so"; +static const char* flShutterOpen = "-shutterOpen"; +static const char* fsShutterOpenOffset = "-soo"; +static const char* flShutterOpenOffset = "-shutterOpenOffset"; +static const char* fsSurfaceNormals = "-sn"; +static const char* flSurfaceNormals = "-surfaceNormals"; +static const char* fsTimeUnits = "-tu"; +static const char* flTimeUnits = "-timeUnits"; +static const char* fsUVSet = "-uv"; +static const char* flUVSet = "-uvSet"; +static const char* fsVertexColors = "-vc"; +static const char* flVertexColors = "-vertexColors"; +static const char* fsVoxels = "-vox"; +static const char* flVoxels = "-voxels"; +static const char* fsWCoords = "-wc"; +static const char* flWCoords = "-wCoords"; + + +static struct +{ + const char* shortName; + const char* longName; + bool multiUse; + MSyntax::MArgType argType; + const char* argName; + const char* description; +} kFlags[] = +{ + { + fsBinary, flBinary, false, MSyntax::kNoArg, 0, + "output file as binary rather than text." + }, + { + fsCleanup, flCleanup, false, MSyntax::kNoArg, 0, + "don't dump any RIBs but instead delete the files generated by a" + " previous dump. You must specify all the same parameters as the dump" + " you want to clean up." + }, + { + fsFrame, flFrame, false, MSyntax::kDouble, "frame", + "number of frame to output. If not specified then the current frame" + " is used." + }, + { + fsFrameRelativeTime, flFrameRelativeTime, false, MSyntax::kNoArg, 0, + "when motion blur is enabled this causes all times in motion blocks" + " to be written as being relative offsets to the frame time rather than" + " as absolute time values. This is for use with the 'offset' shutter" + " option in Renderman." + }, + { + fsFullPaths, flFullPaths, false, MSyntax::kNoArg, 0, + "use in conjunction with -voxels. If this flag is present then the" + " main RIB file will refer to the per-voxel RIB files using full" + " pathnames. If not present then just the filename will be used, with" + " no path." + }, + { + fsGzip, flGzip, false, MSyntax::kNoArg, 0, + "compress the output file using 'gzip' format. Only valid with '-b'." + }, + { + fsHairNode, flHairNode, true, MSyntax::kString, "nodeName", + "only write RIB for the specified shaveHair node." + }, + { + fsHelp, flHelp, false, MSyntax::kNoArg, 0, + "display this help message." + }, + { + fsIgnoreShaveGlobals, flIgnoreShaveGlobals, false, MSyntax::kNoArg, 0, + "ignore any defaults set in Shave Globals and only use those flags and" + " values which are explicitly specified on the command line." + }, + { + fsMotionBlur, flMotionBlur, false, MSyntax::kNoArg, 0, + "write motion blur info to the file." + }, + { + fsNormals, flNormals, false, MSyntax::kNoArg, 0, + "for instance geometry, output surface normals for each vertex of each" + " instance." + }, + { + fsOpacities, flOpacities, false, MSyntax::kNoArg, 0, + "write opacities ('Os' parameter)." + }, + { + fsRestoreFrame, flRestoreFrame, false, MSyntax::kBoolean, "yes|no", + "if this flag is set to 'no' then when motion blur is enabled the" + " current time will be left at the shutter close time rather than" + " being restored to the time at the start of the command. This is" + " useful for scripts which are processing a series of frames and wish" + " to avoid redundant frame changes. The default is 'yes'." + }, + { + fsRootPositions, flRootPositions, false, MSyntax::kNoArg, 0, + "write per-hair root positions as the 'P_srf' parameter." + }, + { + fsRootTipColors, flRootTipColors, false, MSyntax::kNoArg, 0, + "write root and tip colors." + }, + { + fsSurfaceNormals, flSurfaceNormals, false, MSyntax::kNoArg, 0, + "for each hair generate an 'N_srf' parameter which is the normal of" + " the growth surface at the root of the hair." + }, + { + fsShutterOpen, flShutterOpen, false, MSyntax::kDouble, "time", + "when motion blur is enabled, use this as the exact shutter open time." + }, + { + fsShutterOpenOffset, flShutterOpenOffset, false, MSyntax::kDouble, "offset", + "same as '-shutterOpen' except this is an offset which is added to the" + " frame time. If both this and -shutterOpen are given the latter will" + " be used." + }, + { + fsShutterClose, flShutterClose, false, MSyntax::kDouble, "time", + "when motion blur is enabled, use this as the exact shutter close time." + }, + { + fsShutterCloseOffset, flShutterCloseOffset, false, MSyntax::kDouble, "offset", + "same as '-shutterClose' except this is an offset which is added to" + " the frame time. If both this and -shutterClose are given the latter" + " will be used." + }, + { + fsTimeUnits, flTimeUnits, false, MSyntax::kString, "frames|seconds", + "the units to be used when writing times to the RIB file, for example" + " in MotionBegin statements. \"frames\" uses Maya frame numbers, which" + " is the default, \"seconds\" converts frame numbers to seconds using" + " the currently specified frame rate. Note that this does not change" + " the units used by other command flags, such as -shutterClose: those" + " continue to be in frames." + }, + { + fsUVSet, flUVSet, false, MSyntax::kString, "uvSetName", + "write texture coords for the given uv set." + }, + { + fsVertexColors, flVertexColors, false, MSyntax::kNoArg, 0, + "write vertex colors ('Cs' parameter)." + }, + { + fsVoxels, flVoxels, false, MSyntax::kNoArg, 0, + "if present, the hair for each shaveHairShape will be divided into" + " several voxels and each voxel will be written to a numbered file." + " The main output file will then load them as delayed read archives." + }, + { + fsWCoords, flWCoords, false, MSyntax::kNoArg, 0, + "write 'w' texture coords, which give the parametric length along each" + " hair." + } +}; + + +static char* ktAmbDiff = "SHAVEambdiff"; +static char* ktGloss = "SHAVEgloss"; +static char* ktOpacity = "SHAVEopacity"; +static char* ktRootColor = "rootcolor"; +static char* ktRootPosition = "P_Srf"; +static char* ktSelfShadow = "SHAVEselfshad"; +static char* ktSpecular = "SHAVEspec"; +static char* ktSquirrel = "SHAVEsquirrel"; +static char* ktSpecularColor = "SHAVEspec_color"; +static char* ktSpecularColor2 = "SHAVEspec_color2"; +static char* ktSurfaceNormal = "N_Srf"; +static char* ktTipColor = "tipcolor"; +static char* ktWCoord = "w"; +static char* ktIndex = "index"; + + +shaveWriteRib::shaveWriteRib() +: fDoBinary(false) +, fDoFullPaths(false) +, fDoGzip(false) +, fDoMotionBlur(false) +, fDoNormals(false) +, fDoOpacities(false) +, fRestoreFrame(true) +, fDoRootPositions(false) +, fDoRootTipColors(false) +, fDoSurfaceNormals(false) +, fDoUVs(false) +, fDoVertexColors(false) +, fDoVoxels(false) +, fDoWCoords(false) +#ifndef NO_PRMAN +, fFrameRelativeTime(false) +, fTimeFactor(1.0) +, fTimeUnits(shaveGlobals::kFrames) +#endif +{ +} + + +shaveWriteRib::~shaveWriteRib() {} + + +#ifndef NO_PRMAN +void shaveWriteRib::addCurve( + int firstVertIdxIdx, + int lastVertIdxIdx, + const int* vertIndices, + const VERT* verts, + const VERT* vertColours, + const VERT* rootColour, + const VERT* tipColour, + float hairU, + float hairV, + float rootRadius, + float tipRadius, + const VERT* hairNormal, + bool tipFade, + int indx +) +{ + int numHairVerts = lastVertIdxIdx - firstVertIdxIdx + 1; + + + fVertsPerHair[fCurveIdx] = numHairVerts; + + if (fUseCubicCurves) fVertsPerHair[fCurveIdx] += 2; + + // + // If per-vertex colours were not supplied then we'll have to + // interpolate them from the root and tip colours. + // + VERT colourPerVert = { 0.0f, 0.0f, 0.0f }; + float numSegs = (float)(numHairVerts - 1); + + if ((vertColours == NULL) && (rootColour != NULL) && (tipColour != NULL)) + { + colourPerVert.x = (tipColour->x - rootColour->x) / numSegs; + colourPerVert.y = (tipColour->y - rootColour->y) / numSegs; + colourPerVert.z = (tipColour->z - rootColour->z) / numSegs; + } + + // + // Set up all the per-vertex parameter values. + // + int i; + int j; + + for (i = firstVertIdxIdx; i <= lastVertIdxIdx; i++) + { + int vertIdx = vertIndices[i]; + + // + // We use a loop to double up on the first and last CVs when + // generating cubic curves. + // + for (j = 0; j < 2; j++) + { + fVert[fVertIdx][0] = verts[vertIdx].x; + fVert[fVertIdx][1] = verts[vertIdx].y; + fVert[fVertIdx][2] = verts[vertIdx].z; + + if (fDoVertexColors) + { + if (vertColours) + { + fVertexColor[fVertIdx][0] = vertColours[vertIdx].x; + fVertexColor[fVertIdx][1] = vertColours[vertIdx].y; + fVertexColor[fVertIdx][2] = vertColours[vertIdx].z; + } + else if (rootColour && tipColour) + { + float portion = (float)(i - firstVertIdxIdx); + + fVertexColor[fVertIdx][0] = + rootColour->x + colourPerVert.x * portion; + + fVertexColor[fVertIdx][1] = + rootColour->y + colourPerVert.y * portion; + + fVertexColor[fVertIdx][2] = + rootColour->z + colourPerVert.z * portion; + } + else + { + fVertexColor[fVertIdx][0] = 0.5; + fVertexColor[fVertIdx][1] = 0.5; + fVertexColor[fVertIdx][2] = 0.5; + } + } + + fVertIdx++; + + // + // If this isn't the first or last cv of a + // cubic curve then don't double up on it. + // + if (!fUseCubicCurves + || ((i != firstVertIdxIdx) && (i != lastVertIdxIdx))) + { + break; + } + } + } + + // + // We need one width, opacity and w-coord for each unique cv in the + // curve. So it doesn't matter whether we're doing linear or cubic + // curves as we output the same number of widths in both cases. + // + float radiusPerVert = (tipRadius - rootRadius) / numSegs; + float opacityPerVert = fBaseOpacity / numSegs; + + for (j = 0; j < numHairVerts; j++) + { + fWidth[fWidthIdx] = (rootRadius + radiusPerVert * (float)j) * 2.0f; + + if (fDoOpacities) + { + float opacity = fBaseOpacity; + + if (tipFade) + { + if (j == numHairVerts - 1) + opacity = 0.0f; + else + { + opacity -= opacityPerVert * (float)j; + } + } + + fOpacity[fWidthIdx][0] = opacity; + fOpacity[fWidthIdx][1] = opacity; + fOpacity[fWidthIdx][2] = opacity; + } + + if (fDoWCoords) + { + // + // It should be impossible to have just one vert as that would + // imply no segments in the curve. Still, better safe than + // sorry. + // + if (numHairVerts == 1) + fWCoord[fWidthIdx] = 0.5; + else + { + fWCoord[fWidthIdx] = + (float)j / (float)(numHairVerts - 1); + } + } + + fWidthIdx++; + } + + // + // Put the hair-specific info into their arrays. + // + + if (fDoRootPositions) + { + int firstVertIdx = vertIndices[firstVertIdxIdx]; + + fRootPosition[fCurveIdx][0] = verts[firstVertIdx].x; + fRootPosition[fCurveIdx][1] = verts[firstVertIdx].y; + fRootPosition[fCurveIdx][2] = verts[firstVertIdx].z; + } + + + if (fDoRootTipColors) + { + if (rootColour) + { + fRootColor[fCurveIdx][0] = rootColour->x; + fRootColor[fCurveIdx][1] = rootColour->y; + fRootColor[fCurveIdx][2] = rootColour->z; + } + else if (vertColours) + { + int firstVertIdx = vertIndices[firstVertIdxIdx]; + + fRootColor[fCurveIdx][0] = vertColours[firstVertIdx].x; + fRootColor[fCurveIdx][1] = vertColours[firstVertIdx].y; + fRootColor[fCurveIdx][2] = vertColours[firstVertIdx].z; + } + + if (tipColour) + { + fTipColor[fCurveIdx][0] = tipColour->x; + fTipColor[fCurveIdx][1] = tipColour->y; + fTipColor[fCurveIdx][2] = tipColour->z; + } + else + { + int lastVertIdx = vertIndices[lastVertIdxIdx]; + + fTipColor[fCurveIdx][0] = vertColours[lastVertIdx].x; + fTipColor[fCurveIdx][1] = vertColours[lastVertIdx].y; + fTipColor[fCurveIdx][2] = vertColours[lastVertIdx].z; + } + } + + if (fDoSurfaceNormals && hairNormal) + { + fSurfaceNormal[fCurveIdx][0] = hairNormal->x; + fSurfaceNormal[fCurveIdx][1] = hairNormal->y; + fSurfaceNormal[fCurveIdx][2] = hairNormal->z; + } + + if (fDoUVs) + { + // + // For curves, UVs are defined to be per-hair. + // + fSCoord[fCurveIdx] = hairU; + fTCoord[fCurveIdx] = hairV; + } + + + fIndex[fCurveIdx]=indx; + + fCurveIdx++; +} + + +void shaveWriteRib::beginFile(RfMFuncs &rfm, MString filename, MString verbatim) const +{ + // + // Set up the RIB file's formatting. + // + if (fDoBinary) + { + RtString format[1] = {"binary"}; + rfm.RiOption("rib", "format", (RtPointer)format, RI_NULL); + } + else + { + RtString format[1] = {"ascii"}; + rfm.RiOption("rib", "format", (RtPointer)format, RI_NULL); + } + + if (fDoGzip) + { + RtString str[1] = {"gzip"}; + rfm.RiOption("rib", "compression", (RtPointer)str, RI_NULL); + } + else + { + RtString str[1] = {"none"}; + rfm.RiOption("rib", "compression", (RtPointer)str, RI_NULL); + } + + // + // Begin the RIB file. + // + if (filename == "") + rfm.RiBegin(RI_NULL); + else + rfm.RiBegin ((char *)filename.asChar()); + + // + // If there is global RIB text, output it as a verbatim record. + // + if (verbatim != "") { + rfm.RiArchiveRecord(*rfm.RI_VERBATIM, "%s\n", verbatim.asChar()); + + // If the global RIB text contains the definition for our + // standard Reyes shader, then output the patterns and Bxdf + // declaration needed for our standard RIS shader as well. + // + // NOTE: Currently we only generate ShaveHairBxdf on Windows, + // but a RIB generated on Linux or OSX could be rendered + // on a Windows machine, so we want to include the Bxdf + // declarations on all platforms. + // + const MString reyesShaderDecl("Surface \"Shave\""); + + if (verbatim.indexW(reyesShaderDecl) != -1) { + rfm.RiArchiveRecord( + *rfm.RI_VERBATIM, + "Pattern \"PxrPrimvar\" \"shave_OsVar\" \"string varname\" [\"Os\"] \"string type\" [\"color\"]\n" + "Pattern \"PxrPrimvar\" \"shave_rootcolorVar\" \"string varname\" [\"rootcolor\"] \"string type\" [\"color\"]\n" + "Pattern \"PxrPrimvar\" \"shave_tipcolorVar\" \"string varname\" [\"tipcolor\"] \"string type\" [\"color\"]\n" + "Pattern \"PxrPrimvar\" \"shave_ambdiffVar\" \"string varname\" [\"SHAVEambdiff\"] \"string type\" [\"float\"]\n" + "Pattern \"PxrPrimvar\" \"shave_opacityVar\" \"string varname\" [\"SHAVEopacity\"] \"string type\" [\"float\"]\n" + "Pattern \"PxrPrimvar\" \"shave_glossVar\" \"string varname\" [\"SHAVEgloss\"] \"string type\" [\"float\"]\n" + "Pattern \"PxrPrimvar\" \"shave_selfshadVar\" \"string varname\" [\"SHAVEselfshad\"] \"string type\" [\"float\"]\n" + "Pattern \"PxrPrimvar\" \"shave_specVar\" \"string varname\" [\"SHAVEspec\"] \"string type\" [\"float\"]\n" + "Pattern \"PxrPrimvar\" \"shave_specColorVar\" \"string varname\" [\"SHAVEspec_color\"] \"string type\" [\"color\"]\n" + "Pattern \"PxrPrimvar\" \"shave_specColor2Var\" \"string varname\" [\"SHAVEspec_color2\"] \"string type\" [\"color\"]\n" + "Bxdf \"ShaveHairBxdf\" \"shaveMaterial\"\n" + " \"reference color Os\" [\"shave_OsVar:resultRGB\"]\n" + " \"reference color rootcolor\" [\"shave_rootcolorVar:resultRGB\"]\n" + " \"reference color tipcolor\" [\"shave_tipcolorVar:resultRGB\"]\n" + " \"reference float SHAVEambdiff\" [\"shave_ambdiffVar:resultF\"]\n" + " \"reference float SHAVEopacity\" [\"shave_opacityVar:resultF\"]\n" + " \"reference float SHAVEgloss\" [\"shave_glossVar:resultF\"]\n" + " \"reference float SHAVEselfshad\" [\"shave_selfshadVar:resultF\"]\n" + " \"reference float SHAVEspec\" [\"shave_specVar:resultF\"]\n" + " \"reference color SHAVEspec_color\" [\"shave_specColorVar:resultRGB\"]\n" + " \"reference color SHAVEspec_color2\" [\"shave_specColor2Var:resultRGB\"]\n" + ); + } + } + + // + // Begin the scene description. + // + rfm.RiTransformBegin(); +} + + +void shaveWriteRib::cleanup( + const MObjectArray& hairShapes, const shaveGlobals::Globals& g +) const +{ + if (fDoVoxels) + { + MString baseFilename = getBaseFilename(); + int i; + int v; + int numVoxels = g.rib.voxels.resolution + * g.rib.voxels.resolution + * g.rib.voxels.resolution; + MString voxelFile; + + for (i = 0; i < (int)hairShapes.length(); i++) + { + MFnDependencyNode nodeFn(hairShapes[i]); + + for (v = 0; v < numVoxels; v++) + { + voxelFile = getVoxelFilename(baseFilename, nodeFn.name(), v); + shaveUtil::fileDelete(voxelFile); + } + } + } + + shaveUtil::fileDelete(fFilename); +} + + +void shaveWriteRib::clearCurves() +{ + fCurveIdx = 0; + fVertIdx = 0; + fWidthIdx = 0; +} +#endif + + +void* shaveWriteRib::createCmd() +{ + return new shaveWriteRib(); +} + + +MSyntax shaveWriteRib::createSyntax() +{ + MSyntax syntax; + + syntax.enableEdit(false); + syntax.enableQuery(false); + + int numFlags = sizeof(kFlags) / sizeof(kFlags[0]); + int i; + + for (i = 0; i < numFlags; i++) + { + syntax.addFlag( + kFlags[i].shortName, kFlags[i].longName, kFlags[i].argType + ); + + if (kFlags[i].multiUse) syntax.makeFlagMultiUse(kFlags[i].shortName); + } + + syntax.setObjectType(MSyntax::kStringObjects, 0, 1); + + return syntax; +} + + +#ifdef NO_PRMAN +static RtToken (*zot)(char *name, char *decl) = nullptr; +static GLuint (*blat)(GLenum shaderType) = nullptr; + +MStatus shaveWriteRib::doIt(const MArgList& args) +{ + MGlobal::displayError( + "shaveWriteRib: command is not supported on this platform." + ); + return MS::kFailure; +} +#else +MStatus shaveWriteRib::doIt(const MArgList& args) +{ + MStatus st; + MArgDatabase argdb(syntax(), args, &st); + + if (!st) return st; + + if (argdb.isFlagSet(fsHelp)) + { + showHelp(); + return MS::kSuccess; + } + + // Are we doing cleanup of an earlier dump? + bool doCleanup = argdb.isFlagSet(fsCleanup); + + // + // Make sure that we have the default shaveGlobals node. + // + MObject shaveGlobalsNode = shaveGlobals::getDefaultNode(); + + if (shaveGlobalsNode.isNull()) + { + MGlobal::displayError( + commandName + ": Could not find default shaveGlobals node." + ); + return MS::kFailure; + } + + shaveGlobals::Globals g; + shaveGlobals::getGlobals(g); + + // This second call is still needed because some of the external + // methods we call still expect to find their shaveGlobals values in + // global variables. + shaveGlobals::getGlobals(); + +// fetch the offsets +openoffset=g.rib.blur.shutterOpenOffset; +closeoffset=g.rib.blur.shutterCloseOffset; + + + + // If we're not ignoring defaults from Shave Globals, then pull those + // in first. + bool ignoreDefaults = argdb.isFlagSet(fsIgnoreShaveGlobals); + + if (!ignoreDefaults) + { + fDoBinary = g.rib.binary; + fDoGzip = g.rib.compress; + fDoNormals = g.rib.normals; + fDoOpacities = g.rib.opacities; + fDoRootPositions = g.rib.rootPositions; + fDoRootTipColors = g.rib.rootTipColors; + fDoSurfaceNormals = g.rib.normals; + fDoUVs = g.rib.uvCoords; + fDoVertexColors = g.rib.vertexColors; + fDoWCoords = g.rib.wCoords; + fDoMotionBlur = g.rib.blur.enable; + fDoVoxels = g.rib.voxels.enable; + fDoFullPaths = g.rib.voxels.fullPaths; + fFrameRelativeTime = (g.rib.blur.timeBasis == shaveConstant::kTimeRelative); + fRestoreFrame = g.rib.blur.restoreFrame; + fTimeUnits = g.rib.timeUnits; + + // If we're inheriting settings from Renderman then see if it has + // blur enabled. If we can't find the renderManGlobals node then + // we will fall back to inheriting from Maya. + if (g.rib.blur.inheritSettings == + shaveConstant::kRibBlurInheritRenderman) + { + // Find the renderManGlobals node. + MObject globalsNode = shaveUtil::getNode("renderManGlobals"); + + if (globalsNode.isNull()) + g.rib.blur.inheritSettings = shaveConstant::kRibBlurInheritMaya; + else + { + MFnDependencyNode nodeFn(globalsNode); + + + MPlug plug = nodeFn.findPlug("rman__torattr___motionBlur"); + MPlug plug2 = nodeFn.findPlug("rman__torattr___cameraBlur"); + + + if (plug.isNull() || plug2.isNull()) + g.rib.blur.inheritSettings = shaveConstant::kRibBlurInheritMaya; + else + { + int temp, temp2; + plug.getValue(temp); + plug2.getValue(temp2); + fDoMotionBlur = (temp != 0) || (temp2 != 0); + } + } + } + + if (g.rib.blur.inheritSettings == shaveConstant::kRibBlurInheritMaya) + { + shaveMaya::getRenderGlobals(); + fDoMotionBlur = shaveMaya::doMotionBlur; + } + } + + fDoBinary |= argdb.isFlagSet(fsBinary); + fDoGzip |= argdb.isFlagSet(fsGzip); + + if (fDoGzip && !fDoBinary) + { + MGlobal::displayWarning( + commandName + ": the " + fsGzip + "/" + flGzip + + " flag is only valid with " + fsBinary + "/" + + flBinary + ); + + fDoGzip = false; + } + + fDoFullPaths |= argdb.isFlagSet(fsFullPaths); + fDoMotionBlur |= argdb.isFlagSet(fsMotionBlur); + fDoNormals |= argdb.isFlagSet(fsNormals); + fDoOpacities |= argdb.isFlagSet(fsOpacities); + fDoRootPositions |= argdb.isFlagSet(fsRootPositions); + fDoRootTipColors |= argdb.isFlagSet(fsRootTipColors); + fDoSurfaceNormals |= argdb.isFlagSet(fsSurfaceNormals); + fDoVertexColors |= argdb.isFlagSet(fsVertexColors); + fDoVoxels |= argdb.isFlagSet(fsVoxels); + fDoWCoords |= argdb.isFlagSet(fsWCoords); + fFrameRelativeTime |= argdb.isFlagSet(fsFrameRelativeTime); + + if (argdb.isFlagSet(fsRestoreFrame)) + argdb.getFlagArgument(fsRestoreFrame, 0, fRestoreFrame); + + if (argdb.isFlagSet(fsTimeUnits)) + { + MString timeUnitStr; + argdb.getFlagArgument(fsTimeUnits, 0, timeUnitStr); + + if (timeUnitStr == "frames") + fTimeUnits = shaveGlobals::kFrames; + else if (timeUnitStr == "seconds") + fTimeUnits = shaveGlobals::kSeconds; + else + { + MGlobal::displayError( + commandName + ": '" + timeUnitStr + + "' is not a valid time unit. Must be one of" + " \"frames\" or \"seconds\"." + ); + return MS::kInvalidParameter; + } + } + + if (fTimeUnits == shaveGlobals::kSeconds) + { + // Set up a time variable containing a single frame of time at + // the current frame rate. + MTime timeConv(1.0, MTime::uiUnit()); + + // Time in seconds. + fTimeFactor = timeConv.as(MTime::kSeconds); + } + + if (argdb.isFlagSet(fsUVSet)) + { + fDoUVs = true; + argdb.getFlagArgument(fsUVSet, 0, fUVSet); + } + + // + // Get the various frames times we need. + // + double curFrame = MAnimControl::currentTime().as(MTime::uiUnit()); + + getFrameTimes(argdb, g, curFrame); + + // Get the shaveHair nodes for which we're writing RIB. + MObjectArray hairNodes; + st = getHairShapes(argdb, hairNodes); + + if (!st || (hairNodes.length() == 0)) return st; + + // + // Get the name of the output file. + // + MStringArray optArgs; + argdb.getObjects(optArgs); + + if (optArgs.length() > 0) + { + fFilename = optArgs[0]; + + //check write permissions + FILE* fp = fopen(fFilename.asChar(),"wb"); + if (fp == NULL) + { + MGlobal::displayError(MString("Can't write rib '") + fFilename + "'. Directory is write protected."); + return MStatus::kFailure; + } + else + fclose(fp); +#if 0 + unsigned int flen = fFilename.length(); + if(flen > 0) + { + bool found = false; + unsigned int pos = 0; + std::string fn(fFilename.asChar()); + for(unsigned int i = flen-1; i > 0; i--) + { + if(fn[i] == '/' || fn[i] == '\\') + { + found = true; + pos = i; + break; + } + } + if(found) + { + int res = 0; + MString dir = fFilename.substring(0,pos); + + /// debug /// + //MGlobal::displayInfo(dir); + ///////////// + + + //test fails if backslash is used \\, need / + //I really do not like this approach + std::string dn(dir.asChar()); + for(unsigned int i = 0; i < dn.length(); i++) + { + if(dn[i] == '\\') + dn[i] = '/'; + } + MString dir2(dn.data()); + MGlobal::executeCommand(MString("filetest -w \"") + dir2 + MString("\""),res); + + if(res == 0) + { + MGlobal::displayError(MString("Can't write rib. Directory '") + dir + "' is write protected"); + return MStatus::kFailure; + } + } + } +#endif + if (g.verbose && !doCleanup) { +#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) + cerr << "Writing rib to file '" << fFilename.asChar() << "' ..." << endl; +#else + cerr << "Writing rib to file '" << fFilename << "' ..." << endl; +#endif + } + + } + else + fFilename = ""; + + // + // How should we represent the hairs: curves or polys? + // + fUseCubicCurves = (g.rib.primitiveType == 1); + + if (doCleanup) + { + cleanup(hairNodes, g); + } + else + { + RfMFuncs rfm(g); + + if (!rfm.initialized) return MS::kFailure; + + // + // If we're not already there, move to the frame we're writing. + // + if (fShutterOpen != curFrame) shaveUtil::setFrame((float)fShutterOpen); + + // + // Output RIB info for all of the renderable shaveHairShapes. + // + st = writeRib(rfm, hairNodes, g); + + // + // If motion blur is on we will be left at the shutter close time + // which will be between frames, so jump back to the actual frame + // time. + // + if (fDoMotionBlur && fRestoreFrame) + shaveUtil::setFrame((float)fFrameCenter); + + if (g.verbose && (fFilename != "")) + cerr << "Done writing rib file." << endl; + } +SHAVEclear_stack(); + return st; +} + + +void shaveWriteRib::endFile(RfMFuncs &rfm) const +{ + rfm.RiTransformEnd(); + rfm.RiEnd(); +} + + +void shaveWriteRib::freeBuffers(bool isInstanced) +{ + delete [] fVert; + delete [] fVertsPerHair; + + if (fDoRootTipColors) + { + delete [] fRootColor; + delete [] fTipColor; + } + if (!isInstanced) + if (fDoSurfaceNormals) delete [] fSurfaceNormal; + if (!isInstanced) + if (fDoVertexColors) + delete [] fVertexColor; + + if (fDoUVs) + { + delete [] fSCoord; + delete [] fTCoord; + } + + delete [] fIndex; + + if (isInstanced) + { + delete [] fVertIndices; + + delete [] fNormal; + delete [] fVertexColor; + } + else + { + delete [] fWidth; + + if (fDoOpacities) delete [] fOpacity; + if (!isInstanced) + if (fDoRootPositions) delete [] fRootPosition; + if (fDoWCoords) delete [] fWCoord; + } + + // + // Free up the parameter list buffers. + // + delete [] fParams; + delete [] fTokens; +} + + +MString shaveWriteRib::getBaseFilename() const +{ + MString baseFilename = fFilename; + + int lastDot = baseFilename.rindex('.'); + int lastSlash = baseFilename.rindex('/'); + int lastBSlash = baseFilename.rindex('\\'); + + if ((lastDot > lastSlash) && (lastDot > lastBSlash)) + baseFilename = baseFilename.substring(0, lastDot-1); + + return baseFilename; +} + + +void shaveWriteRib::getFrameTimes( + const MArgDatabase& argdb, const shaveGlobals::Globals& g, double curFrame +) +{ + bool ignoreGlobalDefaults = argdb.isFlagSet(fsIgnoreShaveGlobals); + + // If -frame was specified, use that as frame center, otherwise use + // the current frame. + fFrameCenter = curFrame; +{ +openoffset=g.rib.blur.shutterOpenOffset; +closeoffset=g.rib.blur.shutterCloseOffset; +} + + +if (argdb.isFlagSet(fsFrame)) + argdb.getFlagArgument(fsFrame, 0, fFrameCenter); + + // Determine the shutter open and close times, if explicitly + // specified. + bool useShaveGlobals = fDoMotionBlur + && !ignoreGlobalDefaults + && (g.rib.blur.inheritSettings == shaveConstant::kRibBlurInheritOff); + bool explicitShutterOpen = true; + + if (argdb.isFlagSet(fsShutterOpen)) + argdb.getFlagArgument(fsShutterOpen, 0, fShutterOpen); + else if (argdb.isFlagSet(fsShutterOpenOffset)) + { + double offset; + argdb.getFlagArgument(fsShutterOpenOffset, 0, offset); + fShutterOpen = fFrameCenter + offset; + } + else if (useShaveGlobals) + fShutterOpen = fFrameCenter + g.rib.blur.shutterOpenOffset; + else + explicitShutterOpen = false; + + bool explicitShutterClose = true; + + if (argdb.isFlagSet(fsShutterClose)) + argdb.getFlagArgument(fsShutterClose, 0, fShutterClose); + else if (argdb.isFlagSet(fsShutterCloseOffset)) + { + double offset; + argdb.getFlagArgument(fsShutterCloseOffset, 0, offset); + fShutterClose = fFrameCenter + offset; + } + else if (useShaveGlobals) + fShutterClose = fFrameCenter + g.rib.blur.shutterCloseOffset; + else + explicitShutterClose = false; + + if (fDoMotionBlur) + { + // Get the shutter open and close times. There are several + // possibilities here: + // + // 1) We have -shutterOpen and -shutterClose: we use those values and + // ignore -frame, if present. + // + // 2) We have -shutterOpen and -frame but no -shutterClose: we use + // -shutterOpen as the open time and treat the difference between + // it and -frame as half the blur length. + // + // 3) We have -shutterClose and -frame but no -shutterOpen: we use + // -shutterClose as the close time and treat the difference between + // it and -frame as half the blur length. + // + // 4) We have just one of -frame with no -shutterOpen or + // -shutterClose: calculate the blur delta from the camera's + // shutter angle and apply it to whichever value was provided. + if (explicitShutterOpen && explicitShutterClose) + { + // If we picked up absolute shutter open/close times from the + // command line, as opposed to relative ones, then the -frame + // value will be ignored, so warn the user. + if (argdb.isFlagSet(fsShutterOpen) + && argdb.isFlagSet(fsShutterClose) + && argdb.isFlagSet(fsFrame)) + { + displayWarning( + commandName + ": both " + flShutterOpen + " and " + + flShutterClose + " were specified so " + + flFrame + " will be ignored." + ); + } + } + else if (explicitShutterOpen) + { + fShutterClose = fFrameCenter + (fFrameCenter - fShutterOpen); + } + else if (explicitShutterClose) + { + fShutterOpen = fFrameCenter - (fShutterClose - fFrameCenter); + } + else + { + if (g.rib.blur.inheritSettings == + shaveConstant::kRibBlurInheritRenderman) + { + getRendermanShutterTimes(); + } + else + getMayaShutterTimes(); + } + } + else + { + // If we have -frame, use it. If not, then use -shutterOpen. If + // we don't have that either, then use the current frame. + if (argdb.isFlagSet(fsFrame)) + fShutterOpen = fFrameCenter; + else if (!argdb.isFlagSet(fsShutterOpen)) + fShutterOpen = curFrame; + + // -shutterClose is meaningless when motion blur is off, so warn + // the user if they specified it. + if (argdb.isFlagSet(fsShutterClose)) + { + displayWarning( + commandName + ": motion blur not enabled: ignoring " + + flShutterClose + ); + } + } +} + + +MStatus shaveWriteRib::getHairShapes( + const MArgDatabase& argdb, MObjectArray& hairShapes +) const +{ + // If shaveHair nodes were explicitly supplied in the command line, + // return those, otherwise return all renderable shaveHair nodes in + // the scene. + if (argdb.isFlagSet(fsHairNode)) + { + unsigned int numNodes = argdb.numberOfFlagUses(fsHairNode); + unsigned int i; + + for (i = 0; i < numNodes; ++i) + { + MArgList flagArgs; + argdb.getFlagArgumentList(fsHairNode, i, flagArgs); + + MString nodeName = flagArgs.asString(0); + MSelectionList list; + MDagPath path; + + list.add(nodeName); + list.getDagPath(0, path); + + if (path.hasFn(MFn::kTransform)) path.extendToShape(); + + MFnDagNode nodeFn(path); + + if (nodeFn.typeId() != shaveHairShape::id) + { + displayError( + MString("'") + nodeName + "' is not a shaveHair node." + ); + return MS::kInvalidParameter; + } + + hairShapes.append(path.node()); + } + } + else + { + shaveRenderer* renderer = shaveRender::getRenderer(); + renderer->getRenderableShaveNodes(hairShapes); + + unsigned int numShaveNodes = hairShapes.length(); + + if (numShaveNodes == 0) + { + MGlobal::displayWarning( + commandName + + ": There are no renderable shaveHairShapes in the scene." + ); + + return MS::kSuccess; + } + } + + return MS::kSuccess; +} + + +void shaveWriteRib::getMayaShutterTimes() +{ + shaveMaya::getRenderGlobals(); + + // Get the current render camera. + MDagPath cameraPath = shaveRender::getRenderCamPath(); + + if (cameraPath.isValid()) + { + // Get the camera's shutter angle. + MFnCamera cameraFn(cameraPath); + MAngle shutterAngle(cameraFn.shutterAngle(), MAngle::kRadians); + + // Calculate the amount of slider time needed for blurring. + double blurDelta = shutterAngle.asDegrees() / 360.0 + * shaveMaya::motionBlurByFrame / 2.0; + + // Calculate the shutter open/close times. + fShutterOpen = fFrameCenter - blurDelta; + fShutterClose = fFrameCenter + blurDelta; + } + else + { + // We couldn't find a valid render camera, so turn off motion + // blur. + displayWarning( + commandName + ": could not find a valid render camera." + + " Disabling motion blur." + ); + + fDoMotionBlur = false; + fShutterOpen = fFrameCenter; + } +} + + +int shaveWriteRib::getNumParams(bool isInstanced) const +{ + // Allocate the parameter list used to call Ri geometry functions. + // + int numParams = 0; + // + // Allocate buffers and fill in the parameter list. + // + int paramIdx = 0; + + // RI_P is always param 0. + + if (!isInstanced) + { + // RI_WIDTH is always param 1 for hair curves. + paramIdx = 2; + + // Opacity and the W-coord are also "varying" parameters, which + // means that there is one per vertex of the curve's linear form. + // (Or alternatively, one at the start of each segment, plus one + // at the very end of the curve.) + if (fDoOpacities) + { + // RI_OS + paramIdx++; + } + + if (fDoWCoords) + { + // ktWCoord + paramIdx++; + } + } + else + { + paramIdx = 1; + } + + if (fDoRootPositions && !isInstanced) + { + // ktRootPosition + paramIdx++; + } + + if (fDoRootTipColors) + { + // ktRootColor + paramIdx++; + + // ktTipColor + paramIdx++; + } + + // We only do vertex normals for instance geometry. + // + // TODO: Apparently we now ignore fDoNormals so we + // should get rid of it. + // + if (isInstanced) + { + // RI_N + paramIdx++; + } + + if (fDoSurfaceNormals && !isInstanced) + { + // ktSurfaceNormal + paramIdx++; + } + + // We always do vertex colors for instances, but for hairs + // only if we are asked to. + // + if (isInstanced || fDoVertexColors) + { + // RI_CS + paramIdx++; + } + + if (fDoUVs) + { + // RI_S + paramIdx++; + + // RI_T + paramIdx++; + } + + // ktIndex + paramIdx++; + + // ktAmbDiff + paramIdx++; + + // ktOpacity + paramIdx++; + + // ktGloss + paramIdx++; + + // ktSelfShadow + paramIdx++; + + // ktSpecular + paramIdx++; + + // ktSquirrel + paramIdx++; + + // ktSpecularColor + paramIdx++; + + // ktSpecularColor2 + paramIdx++; + + numParams=paramIdx; + return numParams; +} + + +void shaveWriteRib::getRendermanShutterTimes() +{ + // Find the renderManGlobals node. + MObject globalsNode = shaveUtil::getNode("renderManGlobals"); + + if (!globalsNode.isNull()) + { + MFnDependencyNode nodeFn(globalsNode); + MPlug plug; + + plug = nodeFn.findPlug("rman__toropt___motionBlurType"); + + if (!plug.isNull()) + { + float blurDelta = 1.0; + MString blurType; + plug.getValue(blurType); + + // If 'Motion Blur Type' is 'Frame' then the blur delta is + // always one full frame, regardless of camera angle. + // + // If 'Motion Blur Type' is 'Subframe' then the blur delta is + // the camera angle divided by 360. + if (blurType == "frame") + { + blurDelta = 1.0; + } + else if (blurType == "subframe") + { + float angle; + + plug = nodeFn.findPlug("rman__toropt___shutterAngle"); + plug.getValue(angle); + + blurDelta = angle / 360.0f; + } + else + { + displayWarning( + MString("shaveWriteRib: unrecognized renderManGlobals") + + " Motion Blur Type, '" + blurType + "'. Assuming 'frame'." + ); + blurDelta = 1.0f; + } + + MString shutterTiming; + plug = nodeFn.findPlug("rman__toropt___shutterTiming"); + plug.getValue(shutterTiming); + + //new params - 29Jun2012 + bool doBlur = false; + int prmanMoBlur; + plug = nodeFn.findPlug("rman__torattr___motionBlur"); + plug.getValue(prmanMoBlur); + + int prmanCamBlur; + plug = nodeFn.findPlug("rman__torattr___cameraBlur"); + plug.getValue(prmanCamBlur); + + doBlur = (prmanMoBlur != 0) || (prmanCamBlur != 0); + + float prmanSutterOpen; + plug = nodeFn.findPlug("rman__riopt__Camera_shutteropening0"); + plug.getValue(prmanSutterOpen); + + float prmanSutterClose; + plug = nodeFn.findPlug("rman__riopt__Camera_shutteropening1"); + plug.getValue(prmanSutterClose); + + /////// dump //////////// + printf(" --------------- prman ---------------\n"); + printf("prman: Motion Blur: %i\n",prmanMoBlur); + printf("prman: Camera Blur: %i\n",prmanCamBlur); + printf("prman: Sutter Angle/360: %f\n",blurDelta); + printf("prman: Sutter Open: %f\n",prmanSutterOpen); + printf("prman: Sutter Close: %f\n",prmanSutterClose); + printf("prman: Sutter Timing: %s\n",shutterTiming.asChar()); + printf("prman: Blur Type: %s\n",blurType.asChar()); + fflush(stdout); + ///////////////////////// + + // If 'Shutter Timing' is 'Frame Open' then the open offset is 0 + // and the close offset is the blur delta. + // + // If 'Shutter Timing' is 'Frame Center' then the open offset is + // negative half the blur delta and the close offset is + // positive half the blur delta. + // + // If 'Shutter Timing' is 'Frame Close' then the open offset is + // negative the blur delta and the close offset is 0. + // + // The actual shutter open/close times are simply the frame + // time with those offsets applied. + if (shutterTiming == "frameOpen") + { + fShutterOpen = fFrameCenter; + fShutterClose = fFrameCenter + blurDelta; + } + else if (shutterTiming == "frameCenter") + { + fShutterOpen = fFrameCenter - blurDelta / 2.0f; + fShutterClose = fFrameCenter + blurDelta / 2.0f; + } + else if (shutterTiming == "frameClose") + { + fShutterOpen = fFrameCenter - blurDelta; + fShutterClose = fFrameCenter; + } + else + { + displayWarning( + MString("shaveWriteRib: unrecognized renderManGlobals") + + " Shutter Timing, '" + shutterTiming + + "'. Assuming 'frameOpen'." + ); + + fShutterOpen = fFrameCenter; + fShutterClose = fFrameCenter + blurDelta; + } + + return; + } + } + + // We couldn't find the necessary renderManGlobals settings, so print + // a warning and use Maya's instead. + displayWarning( + MString("shaveWriteRib: renderMan blur defaults requested but") + + " renderManGlobals are unavailable. Using Maya defaults instead." + ); + + getMayaShutterTimes(); +} + + +MString shaveWriteRib::getVoxelFilename( + const MString& baseFilename, const MString& nodeName, int voxel +) const +{ + // The node name may contain a ':' which is bad in a filename, so + // let's convert those all to '_'. + MString modifiedNodeName = shaveUtil::substituteAll(nodeName, ':', '_'); + + MString voxelFile = baseFilename + "_" + modifiedNodeName + "_"; + voxelFile += voxel; + voxelFile += ".rib"; + + return voxelFile; +} + + +void shaveWriteRib::initBuffers( + RfMFuncs &rfm, + MFnDependencyNode &nodeFn, + int numHairs, + int numVerts, + int numFaceVerts, + float baseOpacity, + bool isInstanced +) +{ + // + // Allocate the parameter list used to call Ri geometry functions. + // + int numParams = getNumParams(isInstanced); + + fParams = new RtPointer[numParams]; + fTokens = new RtToken[numParams]; + + // + // Allocate buffers and fill in the parameter list. + // + int paramIdx = 0; + + if (!isInstanced) + { + int numWidths = numVerts; + + fWidth = new RtFloat[numWidths]; + + fTokens[1] = *rfm.RI_WIDTH; + fParams[1] = fWidth; + + paramIdx = 2; + + // Opacity and the W-coord are also "varying" parameters, which + // means that there is one per vertex of the curve's linear form. + // (Or alternatively, one at the start of each segment, plus one + // at the very end of the curve.) + if (fDoOpacities) + { + fOpacity = new RtColor[numWidths]; + fTokens[paramIdx] = *rfm.RI_OS; + fParams[paramIdx] = fOpacity; + paramIdx++; + } + + if (fDoWCoords) + { + fWCoord = new RtFloat[numWidths]; + + fTokens[paramIdx] = ktWCoord; + fParams[paramIdx] = fWCoord; + paramIdx++; + } + + // + // If we are creating cubic curves, then we need to double up on the + // start and end cvs to anchor the hair's endpoints. + // + // Note that we only supply widths for each unique cv, so we do not + // want to increment the width count to include these extra cvs. + // + if (fUseCubicCurves) numVerts += 2 * numHairs; + + // + // For curves, the number of face-verts and the number of verts + // should be the same. + // + numFaceVerts = numVerts; + } + else + { + fVertIndices = new RtInt[numFaceVerts]; + paramIdx = 1; + } + + fVert = new RtPoint[numVerts]; + + fTokens[0] = *rfm.RI_P; + fParams[0] = fVert; + + fVertsPerHair = new RtInt[numHairs]; + + if (fDoRootPositions && !isInstanced) + { + fRootPosition = new RtPoint[numHairs]; + fTokens[paramIdx] = ktRootPosition; + fParams[paramIdx] = fRootPosition; + paramIdx++; + } + + if (fDoRootTipColors) + { + fRootColor = new RtColor[numHairs]; + fTipColor = new RtColor[numHairs]; + + fTokens[paramIdx] = ktRootColor; + fParams[paramIdx] = fRootColor; + paramIdx++; + + fTokens[paramIdx] = ktTipColor; + fParams[paramIdx] = fTipColor; + paramIdx++; + } + + + // We only do vertex normals for instance geometry. + // + if (isInstanced) + { + fNormal = new RtNormal[numVerts]; + fTokens[paramIdx] = *rfm.RI_N; + fParams[paramIdx] = fNormal; + paramIdx++; + } + + if (fDoSurfaceNormals && !isInstanced) + { + fSurfaceNormal = new RtNormal[numHairs]; + fTokens[paramIdx] = ktSurfaceNormal; + fParams[paramIdx] = fSurfaceNormal; + paramIdx++; + } + + if (fDoVertexColors || isInstanced) + { + fVertexColor = new RtColor[numVerts]; + fTokens[paramIdx] = *rfm.RI_CS; + fParams[paramIdx] = fVertexColor; + paramIdx++; + } + + if (fDoUVs) + { + // For instance geometry we output the per face-vert UVs from the + // original geometry. + // + // For hair curves the entire curve acquires the UV of the + // point on the growth surface to which it is rooted, so the UVs + // are per-curve. + // + if (isInstanced) + { + fSCoord = new RtFloat[numFaceVerts]; + fTCoord = new RtFloat[numFaceVerts]; + } + else + { + fSCoord = new RtFloat[numHairs]; + fTCoord = new RtFloat[numHairs]; + } + + fTokens[paramIdx] = *rfm.RI_S; + fParams[paramIdx] = fSCoord; + paramIdx++; + + fTokens[paramIdx] = *rfm.RI_T; + fParams[paramIdx] = fTCoord; + paramIdx++; + } + + + fIndex = new RtInt[numHairs]; + fTokens[paramIdx] = ktIndex; + fParams[paramIdx] = fIndex; + paramIdx++; + + + // Set the Shave material constants + MPlug plug; + + fTokens[paramIdx] = ktAmbDiff; + plug = nodeFn.findPlug("amb/diff"); + plug.getValue(fAmbDiff); + fParams[paramIdx] = &fAmbDiff; + paramIdx++; + + fTokens[paramIdx] = ktOpacity; + fBaseOpacity = baseOpacity; + fParams[paramIdx] = &fBaseOpacity; + paramIdx++; + + fTokens[paramIdx] = ktGloss; + plug = nodeFn.findPlug("gloss"); + plug.getValue(fGloss); + fParams[paramIdx] = &fGloss; + paramIdx++; + + fTokens[paramIdx] = ktSelfShadow; + plug = nodeFn.findPlug("selfShadow"); + plug.getValue(fSelfShadow); + fParams[paramIdx] = &fSelfShadow; + paramIdx++; + + + + fTokens[paramIdx] = ktSpecular; + plug = nodeFn.findPlug("specular"); + plug.getValue(fSpecular); + fParams[paramIdx] = &fSpecular; + paramIdx++; + + fTokens[paramIdx] = ktSquirrel; + plug = nodeFn.findPlug("squirrel"); + plug.getValue(fSquirrel); + fParams[paramIdx] = &fSquirrel; + paramIdx++; + + + fTokens[paramIdx] = ktSpecularColor; + plug = nodeFn.findPlug("specularTint"); + plug.child(0).getValue(fSpecularColor[0]); + plug.child(1).getValue(fSpecularColor[1]); + plug.child(2).getValue(fSpecularColor[2]); + fParams[paramIdx] = &fSpecularColor; + paramIdx++; + + fTokens[paramIdx] = ktSpecularColor2; + plug = nodeFn.findPlug("specularTint2"); + plug.child(0).getValue(fSpecularColor2[0]); + plug.child(1).getValue(fSpecularColor2[1]); + plug.child(2).getValue(fSpecularColor2[2]); + fParams[paramIdx] = &fSpecularColor2; + paramIdx++; + + clearCurves(); +} + + +static void myFree(void* ptr) +{ +} + + +void shaveWriteRib::outputCurves(RfMFuncs &rfm, int numCurves, bool isShutterOpen) +{ + int numParams = getNumParams(false); + + if (fDoMotionBlur && isShutterOpen) + outputMotionBegin(rfm); + + rfm.RiCurvesV( + (fUseCubicCurves ? *rfm.RI_CUBIC : *rfm.RI_LINEAR), + numCurves, + fVertsPerHair, + *rfm.RI_NONPERIODIC, + numParams, + fTokens, + fParams + ); + + if (fDoMotionBlur && !isShutterOpen) + rfm.RiMotionEnd(); +} + + +void shaveWriteRib::outputInstanceGeom( + RfMFuncs& rfm, + MFnDependencyNode& nodeFn, + bool isShutterOpen, + MObject instanceMesh, + int numFaces, + int numVerts, + int numFaceVerts, + const int* faceList, + const int* faceStarts, + const int* faceEnds, + const VERT* verts, + const VERT* vertColors, + const VERT* vertNormals, + const VERT* growthSurfNormal, + int index +) +{ + MStatus st; + + MFnMesh meshFn(instanceMesh); + int facesPerInst = meshFn.numPolygons(); + int faceVertsPerInst = meshFn.numFaceVertices(); + int numInsts = numFaces / facesPerInst; + + // + // We can only do those parameters for which we have been passed + // appropriate data. + // + bool savedDoNormals = fDoNormals; + bool savedDoRootTipColors = fDoRootTipColors; + bool savedDoSurfaceNormals = fDoSurfaceNormals; + bool savedDoUVs = fDoUVs; + bool savedDoVertexColors = fDoVertexColors; + + fDoNormals = fDoNormals && (vertNormals != NULL); + + fDoRootTipColors = false; + + fDoSurfaceNormals = fDoSurfaceNormals && (growthSurfNormal != NULL); + + fDoUVs = fDoUVs && !instanceMesh.isNull(); + + fDoVertexColors = fDoVertexColors && + (!instanceMesh.isNull() || (vertColors != NULL)); + + // + // Now that we've adjusted some of the settings, get the number of + // parameters which we'll be dumping. + // + int numParams = getNumParams(true); + + initBuffers(rfm, nodeFn, numFaces, numVerts, numFaceVerts, 1.0f, true); + + int i; + + for (i = 0; i < numFaces; i++) + { + fVertsPerHair[i] = faceEnds[i] + - faceStarts[i]; + } + + for (i = 0; i < numFaceVerts; i++) + fVertIndices[i] = faceList[i]; + + for (i = 0; i < numVerts; i++) + { + fVert[i][0] = verts[i].x; + fVert[i][1] = verts[i].y; + fVert[i][2] = verts[i].z; + } + +// if (fDoNormals) + { + for (i = 0; i < numVerts; i++) + { + fNormal[i][0] = vertNormals[i].x; + fNormal[i][1] = vertNormals[i].y; + fNormal[i][2] = vertNormals[i].z; +MFloatVector v(vertNormals[i].x, vertNormals[i].y, vertNormals[i].z); +float mag = v.length(); +if ((mag < 0.995f) || (mag > 1.005f)) +{ +(std::cerr << "--- normal " << i << " has mag " << mag << std::endl).flush(); +} + } + } +if (0==1) + if (fDoSurfaceNormals) + { + // + // At the moment, we only ever get passed one surface normal which + // must then be applied to all the faces (which will generally be + // multiple strands of the same hair). + // + for (i = 0; i < numFaces; i++) + { + fSurfaceNormal[i][0] = growthSurfNormal[i].x; + fSurfaceNormal[i][1] = growthSurfNormal[i].y; + fSurfaceNormal[i][2] = growthSurfNormal[i].z; + } + } + + { +// if (vertColors) + { +// for (i = 0; i < numFaceVerts; i++) + for (i = 0; i < numVerts; i++) + { +// fVertexColor[i][0] = vertColors[faceList[i]].x; +// fVertexColor[i][1] = vertColors[faceList[i]].y; +// fVertexColor[i][2] = vertColors[faceList[i]].z; + fVertexColor[i][0] = vertColors[i].x; + fVertexColor[i][1] = vertColors[i].y; + fVertexColor[i][2] = vertColors[i].z; + } + } + } + + if (fDoUVs) + { + // + // Get the uvs from the original instance mesh. + // + MString* uvSet = NULL; + + if (fUVSet != "") uvSet = &fUVSet; + + int firstInstFVIndex = 0; + int instNum; + MItMeshFaceVertex iter(instanceMesh); + float2 uv; + bool uvSetWarningGiven = false; + + for (iter.reset(); !iter.isDone(); iter.next()) + { + int thisInstFVIndex = firstInstFVIndex; + + st = iter.getUV(uv, uvSet); + + if (!st && (uvSet != NULL)) + { + MGlobal::displayWarning( + commandName + ": '" + nodeFn.name() + + "'s instance mesh does not have a UV set named '" + + *uvSet + "'. Using the default UV set instead." + ); + + uvSet = NULL; + + st = iter.getUV(uv, uvSet); + } + + if (!st && !uvSetWarningGiven) + { + MGlobal::displayWarning( + commandName + ": '" + nodeFn.name() + + "'s instance mesh does not have a default UV set." + + " All UVs for this mesh will be zero." + ); + + uvSetWarningGiven = true; + } + + for (instNum = 0; instNum < numInsts; instNum++) + { + if (st) + { + fSCoord[thisInstFVIndex] = uv[0]; + fTCoord[thisInstFVIndex] = uv[1]; + } + else + { + fSCoord[thisInstFVIndex] = 0.0f; + fTCoord[thisInstFVIndex] = 0.0f; + } + + thisInstFVIndex += faceVertsPerInst; + } + + firstInstFVIndex++; + } + } + int xx; + for (xx = 0; xx < numFaces; xx++) + { +// fprintf (stdout,"xx = %d\n",xx);fflush(stdout); + fIndex[xx]=index; + } + + + if (fDoMotionBlur && isShutterOpen) + outputMotionBegin(rfm); + + rfm.RiPointsPolygonsV( + numFaces, + fVertsPerHair, + fVertIndices, + numParams, + fTokens, + fParams + ); + + if (fDoMotionBlur && !isShutterOpen) + rfm.RiMotionEnd(); + + freeBuffers(true); + + fDoNormals = savedDoNormals; + fDoRootTipColors = savedDoRootTipColors; + fDoSurfaceNormals = savedDoSurfaceNormals; + fDoUVs = savedDoUVs; + fDoVertexColors = savedDoVertexColors; +} + + +// +// Output those declarations which will be the same for all objects, +// regardless of whether they are hairs or instances. +// +void shaveWriteRib::outputGlobalDecls(RfMFuncs &rfm) const +{ + // + // Output declarations. + // + if (fUseCubicCurves) + rfm.RiBasis(*rfm.RiCatmullRomBasis, 1, *rfm.RiCatmullRomBasis, 1); + + rfm.RiDeclare(*rfm.RI_WIDTH, "varying float"); + + rfm.RiDeclare(*rfm.RI_OS, "varying color"); + + +// if (!isInstanced) + if (fDoRootPositions) + rfm.RiDeclare(ktRootPosition, "uniform point"); + + if (fDoRootTipColors) + { + rfm.RiDeclare(ktRootColor, "uniform color"); + rfm.RiDeclare(ktTipColor, "uniform color"); + } + + if (fDoSurfaceNormals) + rfm.RiDeclare(ktSurfaceNormal, "uniform normal"); + + // + // The w texture coord runs up the length of the hair, so it's + // different for each vertex along the hair. + // + if (fDoWCoords) + rfm.RiDeclare(ktWCoord, "varying float"); + + rfm.RiDeclare(ktIndex, "uniform integer"); + + // Basic Shave texture params + rfm.RiDeclare(ktAmbDiff, "constant float"); + rfm.RiDeclare(ktOpacity, "constant float"); + rfm.RiDeclare(ktGloss, "constant float"); + rfm.RiDeclare(ktSelfShadow, "constant float"); + rfm.RiDeclare(ktSpecular, "constant float"); + rfm.RiDeclare(ktSquirrel, "constant integer"); + rfm.RiDeclare(ktSpecularColor, "constant color"); + rfm.RiDeclare(ktSpecularColor2, "constant color"); +} + + +void shaveWriteRib::outputMotionBegin(RfMFuncs &rfm) const +{ + float openTime = 0.0f; + float closeTime = 0.0f; + if (fFrameRelativeTime) + { +// we put this in because someone was having problems with negative motion blocks +// openTime = 0.0; +// closeTime = (fShutterClose - fShutterOpen) * fTimeFactor; + + openTime = openoffset;//*fTimeFactor; + closeTime = closeoffset;//*fTimeFactor; +// openTime = (-(fShutterClose - fShutterOpen)/2.0f) * fTimeFactor; +// closeTime = ((fShutterClose - fShutterOpen)/2.0f) * fTimeFactor; + } + else + { + openTime = (float)(fShutterOpen * fTimeFactor); + closeTime = (float)(fShutterClose * fTimeFactor); + } + + rfm.RiMotionBegin(2, openTime, closeTime); +} + + +void shaveWriteRib::outputObjSpecificDecls(RfMFuncs &rfm, bool isInstanced) const +{ +#if 0 + // Opacity ("Os") is a standard geometric parameter so we don't need + // to declare it. + if (fDoOpacities && !isInstanced) + { + rfm.RiDeclare(*rfm.RI_OS, "vertex color"); + } +#endif + + rfm.RiShadingInterpolation(*rfm.RI_SMOOTH); + + if (fDoVertexColors) + { + rfm.RiDeclare(*rfm.RI_CS, "vertex color"); + } + + if (fDoUVs) + { + // + // For instance geometry we output the per face-vert UVs from the + // original geometry. + // + // For hair curves the entire curve acquires the UV of the point on + // the growth surface to which it is rooted, so the UVs are per + // curve. + // + if (isInstanced) + { + rfm.RiDeclare(*rfm.RI_S, "facevarying float"); + rfm.RiDeclare(*rfm.RI_T, "facevarying float"); + } + else + { + rfm.RiDeclare(*rfm.RI_S, "uniform float"); + rfm.RiDeclare(*rfm.RI_T, "uniform float"); + } + } +} + + +void shaveWriteRib::showHelp() const +{ + // Use 'indent1' if you're including the hypen after the indent (e.g. + // when the flag name overran where the description should start so + // the description starts on the second line). + // + // Use 'indent2' when just continuing a description. + MString indent1 = " "; + MString indent2 = indent1 + " "; + + MGlobal::displayInfo( + MString("Usage: ") + commandName + " [flags] [\"ribFileName\"]" + ); + + MGlobal::displayInfo(""); + MGlobal::displayInfo("where [flags] are:"); + + unsigned int numFlags = sizeof(kFlags) / sizeof(kFlags[0]); + unsigned int i; + const MString indent = " "; + unsigned int indentLen = indent.length(); + MString line; + const unsigned int kMaxLen = 78; + + for (i = 0; i < numFlags; i++) + { + line = MString(" ") + kFlags[i].shortName + "/" + kFlags[i].longName; + + if (kFlags[i].argName) line += MString(" ") + kFlags[i].argName; + + if (line.length() == indentLen - 2) + line += "- "; + else if (line.length() < indentLen - 2) + line += indent.substring(0, indentLen - line.length() - 3) + "- "; + else + { + MGlobal::displayInfo(line); + line = indent.substring(0, indentLen-3) + "- "; + } + + line += kFlags[i].description; + + if (kFlags[i].multiUse) line += " (multi-use)"; + + while (line.length() > kMaxLen) + { + MString temp = line.substring(0, kMaxLen-1); + int wordBreak = temp.rindex(' '); + + temp = temp.substring(0, wordBreak-1); + MGlobal::displayInfo(temp); + line = indent + line.substring(wordBreak+1, line.length()+1); + } + + if (line.length() > indent.length()) MGlobal::displayInfo(line); + } + + MGlobal::displayInfo(""); + + MGlobal::displayInfo( + " ribFileName - file to send output to. Must be in quotes." + ); + MGlobal::displayInfo(indent2 + "If not specified then output will be written"); + MGlobal::displayInfo(indent2 + "to the console."); + + MGlobal::displayInfo(""); + + MGlobal::displayInfo( + "Note that most settings will default to those specified in Shave Globals" + ); + MGlobal::displayInfo( + "unless the -isg/-ignoreShaveGlobals flag is used." + ); +} + + +// +// Write out a RIB file for each shaveHairShape's voxels, and have the main file +// include them all as delayed read archives. +// +MStatus shaveWriteRib::writeMultipleRibs( + RfMFuncs &rfm, MObjectArray shaveHairShapes, const shaveGlobals::Globals& g +) +{ + //vlad|16jan2013: you can get visibility flags from there + //g.cameraVis + //g.lightVis + //g.giVis + + MStatus st; + + // + // Create a name for the temporary Shave archive file we'll be using + // by stripping the extension from the name of the RIB file and adding + // "_shaveTemp.dat". + // + // %%% If the filename is empty, need to find the temp dir and create + // a default name there. + // + MString baseFilename = getBaseFilename(); + MString archiveFile = baseFilename + "_shaveTemp.dat"; + + // + // Create the Shave archive file. + // + fprintf (stdout, "Creating archive file..\n");fflush(stdout); + st = shaveRender::renderToDRAFile( + kShaveHostPRMAN, + archiveFile, + (int)g.rib.voxels.resolution, + shaveHairShapes, + fDoMotionBlur, + (float)fShutterOpen, + (float)fShutterClose, + true + ); + fprintf (stdout, "Done Creating archive file..\n");fflush(stdout); + + // + // Initialize Shave's PRIM library from the archive. + // + PRIMlogin(); + + int numVoxels = PRIMinit_hairstack((char*)archiveFile.asChar()); + + // + // Process each shaveHairShape separately. + // + std::vector<NodeInfo> nodes; + const unsigned int numBlurPasses = (fDoMotionBlur ? 2 : 1); + unsigned int sn; + fprintf (stdout, "Fetching Voxels..\n");fflush(stdout); + + for (sn = 0; sn < shaveHairShapes.length(); sn++) + { + MFnDependencyNode nodeFn(shaveHairShapes[sn]); + shaveHairShape* nodePtr = (shaveHairShape*)nodeFn.userNode(); + MFnMesh instanceMeshFn; + MObject instanceMesh; + bool isInstanced = nodePtr->isInstanced(); + NodeInfo nodeInfo; + MString nodeName = nodeFn.name(); + MString nodePreamble; + MPlug plug = nodeFn.findPlug(shaveHairShape::ribInsert); + bool tipFade = false; + + plug.getValue(nodePreamble); + + plug = nodeFn.findPlug(shaveHairShape::aTipFade); + plug.getValue(tipFade); + + // + // If the node is instanced then we'll need its instance mesh. + // + if (isInstanced) + { + plug = nodeFn.findPlug(shaveHairShape::instanceMesh); + plug.getValue(instanceMesh); + instanceMeshFn.setObject(instanceMesh); + } + + // + // Step through each voxel for this shaveHairShape and output its hair + // into a separate RIB file. + // + int i; + NodeInfo::VoxelInfo voxel; + + for (i = 0; i < numVoxels; i++) + { + // + // Get the hair which this shaveHairShape has within this voxel. + // + HAIRTYPE hair; + UVSETS uvSets; + UVSETS* pUVSets = (fDoUVs ? &uvSets : 0); + + PRIMfetch_voxel_by_name2( + i, (char*)nodeName.asChar(), &hair, pUVSets, false + ); + + if (hair.totalfaces > 0) + { + bool haveUVs = (fDoUVs && (uvSets.totalUVSets > 0)); + + // + // Create the name for this voxel's RIB file and save it + // for later output to the main RIB file. + // + voxel.fFilename = getVoxelFilename(baseFilename, nodeName, i); + + // + // Get the voxel's bounding box and save it for later + // output to the main RIB file. + // + VERT bbMin; + VERT bbMax; + + PRIMfetch_bbox(i, &bbMin, &bbMax); + + voxel.fBBox[0] = bbMin.x; + voxel.fBBox[1] = bbMax.x; + voxel.fBBox[2] = bbMin.y; + voxel.fBBox[3] = bbMax.y; + voxel.fBBox[4] = bbMin.z; + voxel.fBBox[5] = bbMax.z; + + // + // Add this voxel's info to the shaveHairShape's array. + // + nodeInfo.fVoxels.push_back(voxel); + + // + // Start the RIB file for this voxel. + // + beginFile(rfm, (char *)voxel.fFilename.asChar(), nodePreamble); + outputGlobalDecls(rfm); + outputObjSpecificDecls(rfm, isInstanced); + + if (!isInstanced) + { + initBuffers( + rfm, + nodeFn, + hair.totalfaces, + hair.totalverts, + hair.totalfverts, + sqrt(hair.opacity[0]), // Opacity is const per-node + false + ); + } + + // + // If motion blur is on, we need to create separate motion + // blocks for shutter open and close. We do this by making + // two passes over the data. + // + unsigned int blurPass; + int j; + + for (blurPass = 0; blurPass < numBlurPasses; blurPass++) + { + // + // If this is the second pass for motion blur, add the + // velocities to the vertex positions to get their + // positions at shutter close. + // + if (blurPass == 1) + { + for (j = 0; j < hair.totalverts; j++) + { + VERT& vert = hair.v[j]; + VERT& vel = hair.velocity[j]; + + vert.x += vel.x; + vert.y += vel.y; + vert.z += vel.z; + } + } + + // + // At this point, curves and instances must part + // company as they are handled quite differently. + // + if (isInstanced) + { +#ifndef PRIM_BUG_FIXED + // + // %%% There is a bug in the PRIM functions such that + // they don't return the correct face-vertex counts + // or lists for instances, so we have to do those + // ourselves. + // + int facesPerInst = instanceMeshFn.numPolygons(); + int faceVertsPerInst = instanceMeshFn.numFaceVertices(); + int numInsts = hair.totalfaces / facesPerInst; + int numFaceVerts = numInsts * faceVertsPerInst; + int vertsPerInst = instanceMeshFn.numVertices(); + + if (hair.totalfverts < numFaceVerts) + { + if (hair.facelist) free(hair.facelist); + if (hair.face_start) free(hair.face_start); + if (hair.face_end) free(hair.face_end); + + hair.totalfverts = numFaceVerts; + hair.facelist = (int*)malloc(sizeof(int)*numFaceVerts); + hair.face_start = (int*)malloc(sizeof(int)*hair.totalfaces+1); + hair.face_end = (int*)malloc(sizeof(int)*hair.totalfaces+1); + + int faceStart = 0; + int faceEnd = 0; + int faceNum; + int instNum; + + for (faceNum = 0; faceNum < facesPerInst; faceNum++) + { + MIntArray vertIds; + + instanceMeshFn.getPolygonVertices( + faceNum, vertIds + ); + + faceEnd = faceStart + (int)vertIds.length(); + + for (instNum = 0; instNum < numInsts; instNum++) + { + int vertOffset = instNum * vertsPerInst; + int faceVertOffset = instNum * faceVertsPerInst; + int fv; + + for (fv = 0; fv < (int)vertIds.length(); fv++) + { + hair.facelist[ + faceVertOffset + faceStart + fv + ] = vertOffset + vertIds[fv]; + } + + hair.face_start[instNum*facesPerInst + faceNum] + = faceVertOffset + faceStart; + hair.face_end[instNum*facesPerInst + faceNum] + = faceVertOffset + faceEnd; + } + + faceStart = faceEnd; + } + + hair.face_start[hair.totalfaces] = hair.face_end[hair.totalfaces-1]; + hair.face_end[hair.totalfaces] = hair.face_end[hair.totalfaces-1]; + } +#endif + + outputInstanceGeom( + rfm, + nodeFn, + (blurPass == 0), + instanceMesh, + hair.totalfaces, + hair.totalverts, + hair.totalfverts, + hair.facelist, + hair.face_start, + hair.face_end, + hair.v, + NULL, + hair.surfNorm, + NULL, + hair.index[0] + ); + } + else + { + // + // Clear out any curve data from previous passes. + // + clearCurves(); + + // + // Fill in the arrays for the hairs. + // + int hairIdx; + int uvIdx = 0; + + for (hairIdx = 0; hairIdx < hair.totalfaces; hairIdx++) + { + int faceStart = hair.face_start[hairIdx]; + int faceEnd = hair.face_end[hairIdx] - 1; + + addCurve( + faceStart, + faceEnd, + hair.facelist, + hair.v, + NULL, + &hair.colorroot[hairIdx], + &hair.colortip[hairIdx], + (haveUVs ? uvSets.uvRoot[uvIdx].x : 0.0f), + (haveUVs ? uvSets.uvRoot[uvIdx].y : 0.0f), + hair.radiusroot[hairIdx], + hair.radiustip[hairIdx], + &hair.surfNorm[hairIdx], + tipFade, + hair.index[hairIdx] + + ); + + if (haveUVs) uvIdx += uvSets.totalUVSets; + } + + outputCurves(rfm, hair.totalfaces, (blurPass == 0)); + } + } + + endFile(rfm); + + if (!isInstanced) + freeBuffers(false); + } + + if (fDoUVs) SHAVEfree_UV(pUVSets); + + PRIMfree_hairtype(&hair); + } + + nodes.push_back(nodeInfo); + } + + // + // Create the main RIB file. + // + beginFile(rfm, fFilename, g.rib.filePreamble); + appendToResult(fFilename); + + for (sn = 0; sn < nodes.size(); sn++) + { + NodeInfo& node = nodes[sn]; + + // + // Output delayed read archives for each of the node's voxel + // files. + // this stuff will need to be inserted at the node level for prman export + // come back here for pixar .. + + RtString args[1]; + unsigned int v; + + for (v = 0; v < node.fVoxels.size(); v++) + { + NodeInfo::VoxelInfo& voxel = node.fVoxels[v]; + MString filename = voxel.fFilename; + + appendToResult(filename); + + // + // If the -fullPaths option was not specified, then strip the + // path from the filename. + // + if (!fDoFullPaths) + { + MFileObject fileObj; + + fileObj.setFullName(filename); + filename = fileObj.name(); + } + +#ifdef RiProcedural_BROKEN + rfm.RiArchiveRecord( + *rfm.RI_VERBATIM, + "Procedural \"DelayedReadArchive\" [\"%s\"] [%f %f %f %f %f %f]\n", + filename.asChar(), + voxel.fBBox[0], + voxel.fBBox[1], + voxel.fBBox[2], + voxel.fBBox[3], + voxel.fBBox[4], + voxel.fBBox[5] + ); +#else + args[0] = (char*)filename.asChar(); + + rfm.RiProcedural( + (RtPointer)args, voxel.fBBox, rfm.RiProcDelayedReadArchive, myFree + ); +#endif + } + } + + endFile(rfm); + + PRIMlogout(); + + shaveUtil::fileDelete(archiveFile); + + return MS::kSuccess; +} + + +MStatus shaveWriteRib::writeRib( + RfMFuncs &rfm, const MObjectArray& shaveHairShapes, const shaveGlobals::Globals& g +) +{ + MStatus st; + + unsigned int numShaveNodes = shaveHairShapes.length(); + + if (numShaveNodes == 0) + { + MGlobal::displayWarning( + commandName + + ": There are no renderable shaveHairShapes in the scene." + ); + + return MS::kSuccess; + } + + // + // If we're not doing motion blur, then we can get the texture info for + // all of the shaveHairShapes at once. + // + if (!fDoMotionBlur) +#ifdef PER_NODE_TEXLOOKUP + initTexInfoLookup2(shaveHairShapes, fUVSet, g.verbose); +#else + initTexInfoLookup(shaveHairShapes, fUVSet, g.verbose); +#endif + + // + // Generate the rib file(s). + // + if (fDoVoxels) + st = writeMultipleRibs(rfm, shaveHairShapes, g); + else + st = writeSingleRib(rfm, shaveHairShapes, g); + + return st; +} + +MStatus shaveWriteRib::writeSingleRib( + RfMFuncs &rfm, MObjectArray shaveHairShapes, const shaveGlobals::Globals& g +) +{ + //vlad|16jan2013: you can get visibility flags from there + //g.cameraVis + //g.lightVis + //g.giVis + + MStatus st; + + // + // Start the file. + // + beginFile(rfm, fFilename, g.rib.filePreamble); + outputGlobalDecls(rfm); + + // We always want the return type to be an array of strings, so even + // though we only have one filename in this case, let's put it into an + // array. + MStringArray fileNames; + fileNames.append(fFilename); + setResult(fileNames); + + // + // Output the hair for each shave node. + // + unsigned int sn; + unsigned int blurPass; + + for (sn = 0; sn < shaveHairShapes.length(); sn++) + { + MFnDependencyNode nodeFn(shaveHairShapes[sn]); + shaveHairShape* theShaveNode = (shaveHairShape*)nodeFn.userNode(); + bool isInstanced = theShaveNode->isInstanced(); + bool savedDoMotionBlur = fDoMotionBlur; + + // Motion blur is not currently supported when doing geometry + // output in non-voxel mode, because the overhead is simply too + // great: we would either have to store all of the instance + // geometry between shutter open and close, thereby defeating the + // point of the hair-at-a-time approach, which is to keep memory + // overhead low; or we would have to switch frames for every + // instance which would be horribly slow. +// if (isInstanced) fDoMotionBlur = false; + + theShaveNode->makeCurrent(); + outputObjSpecificDecls(rfm, isInstanced); + + if (fDoMotionBlur) + { + // + // If motion blur is on, and we have multiple shaveHairShapes, + // then the previous shaveHairShape will have left us at the + // shutterClose time, in which case we need to move back to + // shutterOpen. + // + if (sn > 0) shaveUtil::setFrame((float)fShutterOpen); + + // + // Generate this shaveHairShape's texture info. + // + // (When motion blur is off, texture info for all of the nodes + // is generated at once, back near the start of this method.) + // + MObjectArray shaveHairShapeAsAnArray; + + shaveHairShapeAsAnArray.append(shaveHairShapes[sn]); +#ifdef PER_NODE_TEXLOOKUP + initTexInfoLookup2(shaveHairShapeAsAnArray, fUVSet, g.verbose); +#else + initTexInfoLookup(shaveHairShapeAsAnArray, fUVSet, g.verbose); +#endif + } + + // + // If this shaveHairShape has RIB text, output it as a verbatim + // record. + // + MPlug plug = nodeFn.findPlug(shaveHairShape::ribInsert); + MString nodePreamble; + + plug.getValue(nodePreamble); + + if (nodePreamble != "") + rfm.RiArchiveRecord(*rfm.RI_VERBATIM, "%s\n", nodePreamble.asChar()); + + bool tipFade = false; + plug = nodeFn.findPlug(shaveHairShape::aTipFade); + plug.getValue(tipFade); + + SHAVENODE* hairNode = theShaveNode->getHairNode(); + int hairGroup = theShaveNode->getHairGroup(); + int numHairs = hairNode->shavep.haircount[hairGroup]; + int numPasses = hairNode->shavep.passes[hairGroup]; + int numSegs = hairNode->shavep.segs[hairGroup]; + int pass; + WFTYPE wf; + + init_geomWF(&wf); + + // + // If we're generating the hair as curves, we need to figure out + // how big our arrays have to be. + // + CURVEINFO curveInfo; + MObject instanceMesh; + unsigned int totalCurves = 0; + unsigned int totalCVs = 0; + + if (isInstanced) + { + // + // Get the original instance mesh. + // + plug = nodeFn.findPlug(shaveHairShape::instanceMesh); + plug.getValue(instanceMesh); + } + else + { +// for (pass = 0; pass < numPasses; pass++) + pass=0; + { + int hair; + + for (hair = 0; hair < numHairs*(numPasses+1); hair++) + { + // For speed, we call SHAVEmake_a_curveROOT rather than + // SHAVEmake_a_curve. Note, however, that this means + // our counts will include some curves which may + // eventually be killed. + SHAVEmake_a_curveROOT( + pass, hairGroup, hair, &wf, &curveInfo + ); + + // + // Each face is effectively a separate hair. + // + totalCurves += wf.totalfaces; + } + } + + // + // If we ended up with no curves, then skip this shaveHairShape. + // + if (totalCurves == 0) continue; + + totalCVs = totalCurves * (numSegs + 1); + float np=(float)numPasses; +// np=(np)/2; + if (np<1.0f) np=1.0f; + float opacity = 1.0f / np; + opacity=sqrt(opacity); +//opacity*=opacity; + + initBuffers( + rfm, nodeFn, totalCurves, totalCVs, totalCVs, opacity, false + ); + } + + const unsigned int numBlurPasses = (fDoMotionBlur ? 2 : 1); + + for (blurPass = 0; blurPass < numBlurPasses; blurPass++) + { + // + // If this is the second pass for motion blur, move to the + // shutter close time. + // + if (blurPass == 1) + { + shaveUtil::setFrame((float)fShutterClose); + + // + // Changing time causes all shaveHairShapes to reload + // themselves, which means that whichever shaveHairShape was + // last is the one which will now be loaded into Shave. So + // let's make sure that *this* shaveHairShape is the one that's + // loaded. + // + theShaveNode->makeCurrent(); + } + + // + // At this point, curves and polys must part company as they are + // handled quite differently. + // + if (isInstanced) + { + int pass=0; + int np; + np=numHairs; + if (np<1) np=1; +// np*=numPasses; + // + // Generate each instance as a poly. + // + pass=0; +// for (pass = 0; pass < numPasses; pass++) + { + int hair; + int ff; + + VERT *snorm; + + SHAVEmake_a_hair(pass, hairGroup, 1, 14, &wf); +if (numPasses<=0) numPasses=1; +if (wf.totalverts>0) + snorm=(VERT *) malloc((np*numPasses+1)*wf.totalverts*sizeof(VERT)); +ff=wf.totalverts; +free_geomWF(&wf); + for (hair = 0; hair < np*numPasses; hair++) + { + +/// SHAVEmake_a_curve( +/// pass, hairGroup, hair, 1, &wf, &curveInfo +// ); + + SHAVEmake_a_curveROOT( + pass, hairGroup, hair, &wf, &curveInfo + ); + +// if (curveInfo.killme) continue; +//if (wf.totalfaces>0) +// SHAVEmake_a_hair(pass, hairGroup, hair, numSegs, &wf); +int qq; +for (qq=0;qq<wf.totalverts;qq++) + snorm[qq+ff*hair]=curveInfo.norm; +free_geomWF(&wf); + } +int list_total=0; +int c; +int *inlist; +inlist=(int *) malloc (np*numPasses*sizeof(int)); +// list_total=0; + for (c=0;c<np*numPasses;c++) + { + inlist[c]=c; + list_total++; + } + + MTbunch_of_hairs(list_total, inlist,hairGroup,0, &wf,&curveInfo); +free(inlist); + + // + // Note that currently motion blur is not supported + // when doing geometry output in non-voxel mode, + // because the overhead is simply too great. + // + // So the second arg in the call below is + // meaningless. Which is a good thing because + // otherwise we would end up generating a whole + // bunch of RiMotionBegin's followed by a bunch of + // RiMotionEnd's, rather than having them be + // properly paired up. + // + if (wf.totalfaces>0) + outputInstanceGeom( + rfm, + nodeFn, + (blurPass == 0), + instanceMesh, + wf.totalfaces, + wf.totalverts, + wf.totalfverts, + wf.facelist, + wf.face_start, + wf.face_end, + wf.v, + wf.color, + wf.vn, + snorm, + curveInfo.hairID + ); +if (wf.totalfaces>0) + free(snorm); +if (wf.totalfaces>0) +free_geomWF(&wf); + } + } + else + { + // + // Clear out any curve data from previous passes. + // + clearCurves(); + + // Now we get to find out how many curves we *really* + // have. + totalCurves = 0; + + // + // Fill in the arrays for the hairs. + // + pass=0; +// for (pass = 0; pass < numPasses; pass++) + { +// int hair; + int nh; + int b=0,c=0; + int chunk=0; + int np; + np=numPasses; + if (np<1) np=1; + nh=numHairs*np;//*(np); + +// for (hair = 0; hair < numHairs; hair++) + while (b<nh) + { + int inlist[1001]; + int list_total=0; + chunk=1; + if (nh>1000) + { + chunk=1000; + } + if (chunk>nh-b) + chunk=nh-b; + +// SHAVEmake_a_curve( +// pass, hairGroup, hair, numSegs, &wf, &curveInfo +// ); + list_total=0; + for (c=b;c<b+chunk;c++) + { + inlist[list_total]=c; + list_total++; + } + + MTbunch_of_hairs(list_total, inlist,hairGroup,pass, &wf,&curveInfo); + + int face; + + for (face = 0; face < wf.totalfaces; face++) + { + // + // If this is spline hair, then the first two + // weights in the barycentric coords in + // curveInfo are the UV coords we want. + // + float u; + float v; + +// if (theShaveNode->getHairGroup() == 4) +// { + u = wf.uv[wf.facelist[wf.face_start[face]]].x; + v = wf.uv[wf.facelist[wf.face_start[face]]].y; +// u = curveInfo.wgt[0]; +// v = curveInfo.wgt[1]; +// } +// else +// { +// u = curveInfo.u; +// v = curveInfo.v; +// } + + addCurve( + wf.face_start[face], + wf.face_end[face] - 1, + wf.facelist, + wf.v, + wf.color, + NULL, + NULL, + u, + v, +// curveInfo.baserad, +// curveInfo.tiprad, + wf.r1[face], + wf.r2[face], + &wf.vn[wf.face_start[face]], + tipFade, + wf.index[face] + ); + } + // + // Each face is effectively a separate hair and + // will thus require its own curve. + // + totalCurves += wf.totalfaces; + b+=chunk; + free_geomWF(&wf); + + } + } + + outputCurves(rfm, totalCurves, (blurPass == 0)); + } // end of curves vs. polys split + } // end of blur pass loop + + // + // If we were generating instances then restore the motion blur + // setting, otherwise clean up the buffers used to create curves. + // + if (isInstanced) + fDoMotionBlur = savedDoMotionBlur; + else + freeBuffers(false); + + } // end of shaveHairShape loop + + endFile(rfm); + + return st; +} +#endif |