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/shaveRender.cpp | |
| download | archived-shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.tar.xz archived-shave-and-a-haircut-bd0027e737c6512397f841c22786274ed74b927f.zip | |
Adding Shave-and-a-Haircut 9.6
Diffstat (limited to 'mayaPlug/shaveRender.cpp')
| -rw-r--r-- | mayaPlug/shaveRender.cpp | 2504 |
1 files changed, 2504 insertions, 0 deletions
diff --git a/mayaPlug/shaveRender.cpp b/mayaPlug/shaveRender.cpp new file mode 100644 index 0000000..a005cf2 --- /dev/null +++ b/mayaPlug/shaveRender.cpp @@ -0,0 +1,2504 @@ +// Shave and a Haircut +// (c) 2019 Epic Games +// US Patent 6720962 + +#include <maya/MAngle.h> +#include <maya/MAnimControl.h> +#include <maya/MColorArray.h> +#include <maya/MDagPath.h> +#include <maya/MDGMessage.h> +#include <maya/MFileIO.h> +#include <maya/MFnCamera.h> +#include <maya/MFnDagNode.h> +#include <maya/MFnLight.h> +#include <maya/MFnMesh.h> +#include <maya/MFnSpotLight.h> +#include <maya/MFnTypedAttribute.h> +#include <maya/MIntArray.h> +#include <maya/MItDag.h> +#include <maya/MItDependencyNodes.h> +#include <maya/MMatrix.h> +#include <maya/MPlug.h> +#include <maya/MPlugArray.h> +#include <maya/MPoint.h> +#include <maya/MSelectionList.h> +#include <maya/MString.h> +#include <maya/MStringArray.h> +#include <maya/MVector.h> + +#include "shaveCheckObjectVisibility.h" +#include "shaveDebug.h" +#include "shaveGlobals.h" +#include "shaveHairShape.h" +#include "shaveIO.h" +#include "shaveMaya.h" +#include "shaveMayaRenderer.h" +#include "shaveObjExporter.h" +#include "shaveRender.h" +#include "shaveRenderCallback.h" +#include "shaveRenderer.h" +#include "shaveSDK.h" +#include "shaveTextureStore.h" +#include "shaveUtil.h" +#include "shaveVertexShader.h" +#include "shaveVrayRenderer.h" +#include "shaveXPM.h" + + +#ifndef M_PI +#define M_PI 3.14159926535 +#endif + +bool shaveRenderCancelled = false; +bool shaveEnableProgressBar = true; + + +// +// Static class members. +// +bool shaveRender::mCallbacksSet = false; +bool shaveRender::mExportActive = false; +unsigned shaveRender::mExportFileCompression = 0; +unsigned shaveRender::mExportFileFramePadding = 0; +MString shaveRender::mExportFileName = ""; +bool shaveRender::mExportFilePerFrame = false; + +shaveConstant::FileNameFormat + shaveRender::mExportFileNameFormat = shaveConstant::kNoFileNameFormat; + +shaveGlobals::Globals shaveRender::mFrameGlobals; + +shaveConstant::RenderMode shaveRender::mHairRenderMode = shaveConstant::kNoRender; +MObjectArray shaveRender::mHiddenNodes; +bool shaveRender::mRenderInstances = true; + +bool shaveRender::mNeedVertexColours = false; +shaveConstant::ShadowSource + shaveRender::mNormalShadows = shaveConstant::kNoShadows; + +shaveRenderer* shaveRender::mRenderer = NULL; +shaveRender::RenderState shaveRender::mRenderState = shaveRender::kRenderNone; +shaveRender::SceneInfo shaveRender::mSceneInfo; +MObjectArray shaveRender::mTemplatedNodes; + +MCallbackId shaveRender::mAfterFrameRenderCallback; +MCallbackId shaveRender::mAfterRenderCallback; +MCallbackId shaveRender::mBeforeFrameRenderCallback; +MCallbackId shaveRender::mBeforeRenderCallback; +MCallbackId shaveRender::mRenderInterruptedCallback; +MCallbackId shaveRender::mTimeChangeCallback; +shaveRenderCallback* shaveRender::mShaveCallback = NULL; + +// +// Normally, 'mFirstFrame' will be set true in renderStart() and false in +// frameEnd(). This ensures that it will be true during the first frame +// and false during subsequent frames within the same render. +// +// However, Shave API calls don't go through renderStart() and frameEnd(). +// Since the Shave API currently just does a single frame at a time, +// 'mFirstFrame' should always be true. +// +bool shaveRender::mFirstFrame = true; + + +void shaveRender::initSceneInfo(float currentFrame, bool allocateBuffers) +{ + if (mFrameGlobals.verbose) cerr << "setting up render." << endl; + + static int MBAAQ[] = {1,3,8,15,23}; + + shaveRenderCallback::setGeomRender(false); + + mSceneInfo.shaveRenderPixels = NULL; + mSceneInfo.shaveZBuffer = NULL; + + if (allocateBuffers) + { + mSceneInfo.shaveRenderPixels = (Pixel*)malloc( + (shaveMaya::imageWidth+1) * + (shaveMaya::imageHeight+1) * + sizeof(Pixel) + ); + + if (!mSceneInfo.shaveRenderPixels) + { + MGlobal::displayError( + "Shave Render: not enough memory available for pixel buffer." + ); + + shaveRenderCancelled = true; + + return; + } + + mSceneInfo.shaveZBuffer = (float *)malloc( + (shaveMaya::imageWidth+1) * + (shaveMaya::imageHeight+1) *\ + sizeof(float) + ); + + if (!mSceneInfo.shaveZBuffer) + { + MGlobal::displayError( + "Shave Render: not enough memory available for depth buffer." + ); + + shaveRenderCancelled = true; + + return; + } + } + + mSceneInfo.aa = MBAAQ[renderQualityGlob]; + mSceneInfo.currentFrame = currentFrame; + mSceneInfo.haveTracedLights = false; + mSceneInfo.height = shaveMaya::imageHeight; + mSceneInfo.shaveTraceInitialized= false; + mSceneInfo.width = shaveMaya::imageWidth; + + return; +} + + +void shaveRender::doShadows() +{ + // + // Note that here we check doHairShadowsGlob rather than checking the + // hair and instance shadow sources. This is because even if all the + // shadow sources are set to kNoShadows, if doHairShadowsGlob is true + // then we still want self-shadowing. + // + if (doHairShadowsGlob) + { + int renderReturnVal = 0; + + shadowRender = true; + + if (nativeIlluminationGlob) + { + // + // With native illumination, Maya will be rendering the shadows + // for those objects which are actually in the scene, so Shave + // doesn't have to. + // + // The only use that SHAVErender_shadows() makes of the WFTYPEs + // that we pass down to it is to render shadows for the + // geometry that they contain. So by passing down empty + // WFTYPEs, we can prevent it from redoing the shadows which + // Maya has already done. + // + WFTYPE empty; + + init_geomWF(&empty); + + renderReturnVal = SHAVErender_shadows(&empty, &empty, 1, 1); + } + else + { + renderReturnVal = SHAVErender_shadows( + &mSceneInfo.shutterOpenShadowScene, + (shaveMaya::doMotionBlur ? + &mSceneInfo.shutterCloseShadowScene : + &mSceneInfo.shutterOpenShadowScene), + 1, + 1 + ); + } + + // + // If there were any traced lights, then the shadow render will have + // called SHAVEtrace_init. It's an expensive operation, so let's + // set a flag so that we know not to do it in other places, such as + // where we set up for reflections and refractions. + // + mSceneInfo.shaveTraceInitialized |= mSceneInfo.haveTracedLights; + + if (shaveRenderCancelled) + { + MGlobal::displayInfo("Shave render cancelled by user."); + } + else if (renderReturnVal != 0) + { + cerr << "Shave light render failed, return value is " + << renderReturnVal << endl; + + shaveRenderCancelled = true; + } + else if (mFrameGlobals.verbose) + { + cerr << "Done with Shave light render." << endl; + } + + shadowRender = false; + + MGlobal::executeCommand("shave_closeProgressBar()"); + } + + return; +} + + +void shaveRender::bufferRender( + shaveConstant::ShutterState shutter, + const MDagPath& cameraPath, + float frame, + bool needBuffers, + bool doShadowRender, + bool doCameraRender, + bool doOcclusions +) +{ + // + // Do the initialization on shutter open so that everything's there for + // the rest of the render. + // + if (((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + && !shaveRenderCancelled) + { + initSceneInfo(frame, needBuffers); + } + + // + // If this is shutter close, then the actual frame time will be the + // average of the current frame and what was saved into + // mSceneInfo.currentFrame on shutter open. So calculate the real frame + // time. + // + if (shutter == shaveConstant::kShutterClose) + mSceneInfo.currentFrame = (mSceneInfo.currentFrame + frame) / 2.0f; + + if (!shaveRenderCancelled + && (doCameraRender || doShadowRender || doOcclusions)) + { + buildOcclusionLists(shutter); + } + + if (!shaveRenderCancelled) doCamera(cameraPath, shutter, mSceneInfo); + + // + // Lights are not motion-blurred, so we only do them on shutter open. + // + if (((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + && !shaveRenderCancelled) + { + doLights(); + } + + // + // Shadows & rendering are done on shutter close. + // + if ((shutter == shaveConstant::kShutterClose) + || (shutter == shaveConstant::kShutterBoth)) + { + if (!shaveRenderCancelled && doShadowRender) + doShadows(); + + if (!shaveRenderCancelled && doCameraRender) + { + if (renderCameraView(cameraPath) != 0) + shaveRenderCancelled = true; + } + } + + // + // If something went wrong, clean up. Otherwise cleanup will be done + // by shaveRenderCallback::renderCallback(), once it's through with the + // compositing. + // + if (shaveRenderCancelled) + { + bufferRenderCleanup(); + } + + return; +} + + +void shaveRender::bufferRenderCleanup() +{ + mSceneInfo.bufferValid = false; + + if (mSceneInfo.shaveRenderPixels) + { + free(mSceneInfo.shaveRenderPixels); + mSceneInfo.shaveRenderPixels = NULL; + } + + if (mSceneInfo.shaveZBuffer) + { + free(mSceneInfo.shaveZBuffer); + mSceneInfo.shaveZBuffer = NULL; + } + + SHAVEclear_stack(); + + return; +} + + +void shaveRender::geomRender(MObjectArray& shaveHairShapes) +{ + // + // Let shaveRenderCallback know that this is a geom render. + // + shaveRenderCallback::setGeomRender(true); + + // + // This code used to only set up texture lookups if there were vertex + // shaders in the scene, the logic being that they would need updated + // vertex colours. However, we still need to evaluate things such as + // cutlength and density which might be textured. So we need to do the + // lookups *every* time. + // +#ifdef PER_NODE_TEXLOOKUP + initTexInfoLookup2(shaveHairShapes, "", mFrameGlobals.verbose); +#else + initTexInfoLookup(shaveHairShapes, "", mFrameGlobals.verbose); +#endif + // + // Force each shaveHairShape to update its outputMesh. This is needed for + // two reasons: + // + // 1) When motion blur is off and we're rendering just a single frame, + // the last time change will have occurred just prior to + // renderStart(). So the outputMeshes won't have been updated since + // renderStart() set the mRenderAllNormalHairs/mRenderAllInstanceHairs + // flags, which will affect the output meshes. + // + // 2) We just now built the proper texture lookup for this frame, so + // the meshes need to pick up new values from that. + // + // %%% We could optimize this by only doing the loop below if motion + // blur is off and this is the first frame to be rendered, or if + // mNeedVertexColours is true. + // + unsigned int i; + unsigned int numShaveNodes = shaveHairShapes.length(); + + for (i = 0; i < numShaveNodes; i++) + { + MFnDependencyNode nodeFn(shaveHairShapes[i]); + shaveHairShape* theShaveNode = (shaveHairShape*)nodeFn.userNode(); + + // + // Simply grabbing the hairNode will cause the output mesh to + // recompute. + // + theShaveNode->getHairNode(); + } + + // + // NOTE: We no longer need the texture lookup tables at this point + // because any info we needed from them is now embedded in the + // output meshes built by the getHairNode() calls in the loop + // above. So it's not a problem if someone else now calls + // initTexInfoLookup() with a different set of shaveHairShapes. + // + + return; +} + + +// +// Called at the start of each frame render. Note that for normal Maya +// renders we get called from a callback, but some renderers might call +// us via the shaveRender MEL command. +// +void shaveRender::frameStart(void * clientData) +{ + // + // We don't support IPR. + // + if (isIPRActive()) return; + + mRenderState = kRenderingFrame; + mSceneInfo.bufferValid = false; + shaveRenderCancelled = false; + + // We are transitioning from using a bunch of global variables to + // using a shaveGlobals::Globals instance to pass around the + // shaveGlobals values for the frame. During the transition period we + // have to continue to support both. + shaveGlobals::getGlobals(); + saveFrameGlobals(); + + shaveMaya::getRenderGlobals(); + + SHAVEset_verbose(mFrameGlobals.verbose ? 1 : 0); + SHAVEclear_stack(); + + // + // Do renderer-specific setup. + // + if (mRenderer) mRenderer->frameStart(mFrameGlobals); + + return; +} + + +// +// Called at the end of each frame render. Note that for normal Maya +// renders we get called from a callback but some renderers might call +// us via the shaveRender MEL command. +// +void shaveRender::frameEnd(void * clientData) +{ + // + // We don't support IPR. + // + if (isIPRActive()) return; + + // + // Do renderer-specific cleanup. + // + if (mRenderer) mRenderer->frameEnd(mFrameGlobals); + + if (shaveHairShape::getNumShaveNodes() > 0) + { + SHAVEclear_stack(); + } + + mRenderState = kRendering; + mFirstFrame = false; + + return; +} + + + + +/************************************************************************* + * LIGHTS + *************************************************************************/ +void shaveRender::doLights() +{ + MStatus st; + + MDagPath lightPath; + MTransformationMatrix lightWorldMatrix; + MVector lightTranslation; + double rot[3]; + VERT shaveDirection; + + MTransformationMatrix::RotationOrder order = MTransformationMatrix::kXYZ; + + // + // Get our lights + // + shaveUtil::buildLightList(); + + // + // Loop through lights, register them with Shave + // + unsigned i; + unsigned length = (unsigned int)shaveUtil::globalLightList.size(); + + for (i = 0; i < length; i++) + { + lightPath = shaveUtil::globalLightList[i].path; + + MFnLight lightFn(lightPath); + MColor lightColor = lightFn.color (&st); + float lightIntensity = lightFn.intensity(&st); + + if (nativeIlluminationGlob) + { + lightColor = MColor(1,1,1); + lightIntensity = 1; + } + + // + // Look for the per-light shadow parameters. Use defaults if + // this light doesn't have the parameters. + // + MPlug plug; + float shadowFuzz = 8.0f; + short shadowQuality = shaveGlobals::kHighShadowQuality; + int shadowRes = 450; + bool shadowTrace = false; + + if (lightPath.hasFn(MFn::kSpotLight)) shadowRes = 600; + + plug = lightFn.findPlug("shaveShadowFuzz", &st); + if (st) plug.getValue(shadowFuzz); + + plug = lightFn.findPlug("shaveShadowResolution", &st); + if (st) plug.getValue(shadowRes); + + plug = lightFn.findPlug("shaveShadowTrace", &st); + if (st) plug.getValue(shadowTrace); + + // + // Get the position of the light, in World space. + // + lightWorldMatrix = lightPath.inclusiveMatrix(); + lightTranslation = lightWorldMatrix.translation(MSpace::kWorld); + + VERT shaveLightPosition; + shaveLightPosition.x = (float)lightTranslation.x; + shaveLightPosition.y = (float)lightTranslation.y; + shaveLightPosition.z = (float)lightTranslation.z; + + MColor shadowColour = lightFn.shadowColor(); + + // + // Handle spotlights. + // + if (lightPath.hasFn(MFn::kSpotLight)) + { + // + // DIRECTION VECTOR + // + lightWorldMatrix.getRotation(rot, order); + + MVector mayaDirectionVector = MVector::zAxis.rotateBy(rot, order); + + shaveDirection.x = (float) -mayaDirectionVector.x; + shaveDirection.y = (float) -mayaDirectionVector.y; + shaveDirection.z = (float) -mayaDirectionVector.z; + + // + // UP VECTOR + // + MVector mayaUpVector = MVector::yAxis.rotateBy(rot, order); + + VERT shaveUpVector; + shaveUpVector.x = (float) mayaUpVector.x; + shaveUpVector.y = (float) mayaUpVector.y; + shaveUpVector.z = (float) mayaUpVector.z; + + // + // Cone Angle + // + const float kRad2degrees = (float)(180.0 / M_PI); + MFnSpotLight spotFn(lightPath); + + float lightConeAngle = (float) + (spotFn.coneAngle() + spotFn.penumbraAngle() * 2.0); + lightConeAngle = ((float)lightConeAngle) * kRad2degrees; + + Matrix shaveLightMatrix; + + SHAVEmake_view_matrix( + shaveLightMatrix, + shaveUpVector, + shaveDirection, + shaveLightPosition, + lightConeAngle + ); + + shaveUtil::globalLightList[i].minId = + SHAVEadd_light( + lightColor.r*lightIntensity, + lightColor.g*lightIntensity, + lightColor.b*lightIntensity, + shaveLightMatrix, + shaveLightPosition, + shadowRes, + shadowRes, + 1.0, + lightConeAngle, + shadowFuzz, + shadowQuality, + (float)shadowColour.r, + (float)shadowColour.g, + (float)shadowColour.b, + (shadowTrace ? 1 : 0), + 0.01f + ); + shaveUtil::globalLightList[i].maxId = shaveUtil::globalLightList[i].minId; + } + // + // Handle all other light types. + // + else + { + shaveUtil::globalLightList[i].minId = + SHAVEadd_point_light( + lightColor.r*lightIntensity, + lightColor.g*lightIntensity, + lightColor.b*lightIntensity, + shaveLightPosition, + shadowRes, + shadowRes, + shadowFuzz, + shadowQuality, + (float)shadowColour.r, + (float)shadowColour.g, + (float)shadowColour.b, + (shadowTrace ? 1 : 0), + 0.01f + ); + + // + // Internally, Shave generates 6 lights for each non-spotlight + // we register using the above function. + // + shaveUtil::globalLightList[i].maxId = shaveUtil::globalLightList[i].minId + 5; + } + + // + // Keep track of if we have any traced lights. + // + if (shadowTrace) mSceneInfo.haveTracedLights = true; + } + + return; +} + + +/************************************************************************* + * CAMERA + * + * A *LOT* of voodoo here. Maya Z and Shave Z are opposites. So we flip + * it on our end. This affects the handed-ness of rotations along + * X and Y (but not Z). Maya's FOV is horizontal, Shave's camera is vertical. + * Maya has the concept of a camera's viewing fustrum (aspect, FOV) being + * different than the rendering (globals) fustrum. This is because Maya looks + * at the FILM aperture (width & height) for calculating its camera FOV and + * aspect, while Shave uses the rendering globals. Can't we all just get along? + *************************************************************************/ + + + +/************************************************************************* + * SCENE GEOMETRY + *************************************************************************/ +void shaveRender::buildOcclusionLists(shaveConstant::ShutterState shutter) +{ + static bool firstTime = true; + + if (firstTime) + { + init_geomWF(&mSceneInfo.shutterOpenCamScene); + init_geomWF(&mSceneInfo.shutterCloseCamScene); + init_geomWF(&mSceneInfo.shutterOpenShadowScene); + init_geomWF(&mSceneInfo.shutterCloseShadowScene); + firstTime = false; + } + + static MDagPathArray camOcclusions; + static MDagPathArray shadowOcclusions; + shaveObjTranslator x; + + if ((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + { + camOcclusions.clear(); + shadowOcclusions.clear(); + + // + // When doing 2D compositing, we ignore transparency: the user must + // switch to 3D if zie wants to see hair through glass. + // + bool ignoreTransparency = doShaveComp2dGlob; + unsigned int i; + MStringArray occlusionNames; + MDagPath occlusionPath; + MSelectionList tmpSel; + + if (shaveSceneObjectsGlob == "all") + { + MGlobal::executeCommand("ls -g", occlusionNames); + + for (i = 0; i < occlusionNames.length(); i++) + { + tmpSel.clear(); + tmpSel.add(occlusionNames[i]); + tmpSel.getDagPath(0, occlusionPath); + + // + // If native illumination is on, then we only do + // occlusions for the cam pass, not the shadow pass. + // + if (nativeIlluminationGlob) + { + if (areObjectAndParentsVisible( + occlusionPath, true, false, ignoreTransparency)) + { + camOcclusions.append(occlusionPath); + } + } + else + { + // + // For shadows, we ignore transparency and primary + // visibility. + // + if (areObjectAndParentsVisible( + occlusionPath, true, true, true)) + { + shadowOcclusions.append(occlusionPath); + + // + // For the cam pass, we don't want objects whose + // primary visibility is off or which have any + // transparency to be treated as occlusions. + // + if (areObjectAndParentsVisible( + occlusionPath, true, false, ignoreTransparency)) + { + camOcclusions.append(occlusionPath); + } + } + } + } + } + else + { + shaveSceneObjectsGlob.split(' ', occlusionNames); + + for (i = 0; i < occlusionNames.length(); i++) + { + tmpSel.clear(); + tmpSel.add(occlusionNames[i]); + tmpSel.getDagPath(0, occlusionPath); + + camOcclusions.append(occlusionPath); + + if (!nativeIlluminationGlob) + shadowOcclusions.append(occlusionPath); + } + } + + if (mFrameGlobals.verbose) cerr << "built occlusion list." << endl; + + x.exportOcclusion(camOcclusions, &mSceneInfo.shutterOpenCamScene); + + if (!nativeIlluminationGlob) + { + x.exportOcclusion( + shadowOcclusions, &mSceneInfo.shutterOpenShadowScene + ); + } + } + + // + // If there's no motion blur then shutter open and close are identical + // and we can just use the same data for both. So we only need to + // gather occlusions for shutter close if motion blur is on. + // + if ((shaveMaya::doMotionBlur) + && ((shutter == shaveConstant::kShutterClose) + || (shutter == shaveConstant::kShutterBoth))) + { + x.exportOcclusion(camOcclusions, &mSceneInfo.shutterCloseCamScene); + + if (!nativeIlluminationGlob) + { + x.exportOcclusion( + shadowOcclusions, &mSceneInfo.shutterCloseShadowScene + ); + } + } + + return; +} + + +/* + * Modify our color projection map (for hair-to-object shadows) with the + * color of the light. I thought intensity would also need to be + * multiplied in, but I was wrong. + */ +void shaveRender::doColorCorrection (MColor colorMod, short mapWidth, short mapHeight, unsigned char* mapBuffer) +{ + + for (int x=0; x < mapWidth*mapHeight*4; x += 4) + { + // RED + mapBuffer[x+0] = (unsigned char) ((float) mapBuffer[x+0] * colorMod.r ); + //GREEN + mapBuffer[x+1] = (unsigned char) ((float) mapBuffer[x+1] * colorMod.g ); + //BLUE + mapBuffer[x+2] = (unsigned char) ((float) mapBuffer[x+2] * colorMod.b ); + } + +} + +/* + * Correct black levels, to control hair-on-skin shadow density. + */ +void shaveRender::doDensityCorrection (float density, short mapWidth, short mapHeight, unsigned char* mapBuffer) +{ + /* + * Tint values towards white. + * Density range is 0.0-1.0 + */ + for (int x=0; x < mapWidth*mapHeight*4; x += 4) + { + mapBuffer[x+0] = (unsigned char) + ((float)mapBuffer[x+0] * density + 255.*(1.0-density)); + + mapBuffer[x+1] = (unsigned char) + ((float)mapBuffer[x+1] * density + 255.*(1.0-density)); + + mapBuffer[x+2] = (unsigned char) + ((float)mapBuffer[x+2] * density + 255.*(1.0-density)); + } +} + + +// +// Called at the start of a render of a group of one or more frames. Note +// that for normal Maya renders we get called from a callback but some +// renderers may call us via the shaveRender MEL command. +// +void shaveRender::renderStart(void* clientData) +{ + // + // We don't support IPR. + // + if (isIPRActive()) return; + + // + // If a render crashes (e.g. due to insufficient memory) Maya won't + // send us the renderEnd event. So let's make sure that there isn't + // anything lying around from a previous render. + // + bufferRenderCleanup(); + + mRenderState = kRendering; + mFirstFrame = true; + + // + // Make sure we start out with a fresh renderer instance. + // + if (mRenderer != NULL) + { + delete mRenderer; + mRenderer = NULL; + } + + getRenderer(); + + // + // Make sure that the display nodes are all in the same render layer as + // at least on of their growth surfaces. + // + MGlobal::executeCommand("shave_assignRenderLayers"); + + + //******************************************************************* + // + // Get The Shave Nodes + // + //******************************************************************* + + shaveGlobals::getGlobals(); + + MObjectArray shaveHairShapes; + shaveUtil::getShaveNodes(shaveHairShapes); + + unsigned int numShaveNodes = shaveHairShapes.length(); + + + //******************************************************************* + // + // Get The Render Modes + // + //******************************************************************* + + mHairRenderMode = mRenderer->getFilteredRenderMode( + shaveHairShapes, &mRenderInstances + ); + + // + // Go no further if there's nothing to render. + // + if ((mHairRenderMode == shaveConstant::kNoRender) && !mRenderInstances) + { + delete mRenderer; + mRenderer = NULL; + + return; + } + + + //******************************************************************* + // + // Determine Shadow Sources + // + //******************************************************************* + + // + // Let's figure out where our shadows are coming from. + // + mNormalShadows = mRenderer->getShadowSource(); + + + //******************************************************************* + // + // Set Templating & Visibility + // + //******************************************************************* + + // + // We may not want display geometry showing up in the render, or we + // might only want it showing up in secondary effects, such as shadows, + // so ask the renderer what we should do with it. + // + // Note that we need to do it now, before the first call to frameStart, + // because we want the full geometry available at the start of the + // render, when the renderer does its automatic clipping plane + // calculations. Otherwise it might end up clipping some of our + // geometry later on. + // + bool hairPrimaryVisibility; + bool hairSecondaryVisibility; + bool instancePrimaryVisibility; + bool instanceSecondaryVisibility; + + mRenderer->getGeomVisibility( + hairPrimaryVisibility, + hairSecondaryVisibility, + instancePrimaryVisibility, + instanceSecondaryVisibility + ); + + unsigned int i; + bool shouldBeTemplated; + bool shouldBeVisible; + + mHiddenNodes.clear(); + mTemplatedNodes.clear(); + + for (i = 0; i < numShaveNodes; i++) + { + // + // Find the shaveHairShape's display node. + // + MFnDependencyNode nodeFn(shaveHairShapes[i]); + shaveHairShape* nodePtr = (shaveHairShape*)nodeFn.userNode(); + + MObject displayNode = nodePtr->getDisplayShape(); + + if (!displayNode.isNull()) + { + // + // Set the display node's templating, as appropriate. + // + nodeFn.setObject(displayNode); + + if (nodePtr->isInstanced()) + { + shouldBeVisible = instancePrimaryVisibility; + shouldBeTemplated = (!instancePrimaryVisibility + && !instanceSecondaryVisibility); + } + else + { + shouldBeVisible = hairPrimaryVisibility; + shouldBeTemplated = (!hairPrimaryVisibility + && !hairSecondaryVisibility); + } + + bool isTemplated = false; + bool isVisible = false; + MPlug plug; + + plug = nodeFn.findPlug("template"); + plug.getValue(isTemplated); + + if (isTemplated != shouldBeTemplated) + { + plug.setValue(shouldBeTemplated); + mTemplatedNodes.append(displayNode); + } + + // + // If the display node is not templated we may still need to + // adjust its primary visibility. + // + if (!shouldBeTemplated) + { + plug = nodeFn.findPlug("primaryVisibility"); + plug.getValue(isVisible); + + if (isVisible != shouldBeVisible) + { + plug.setValue(shouldBeVisible); + mHiddenNodes.append(displayNode); + } + } + } + + // + // Let the shaveHairShape know about the new render state. + // + MPlug renderStatePlug( + shaveHairShapes[i], shaveHairShape::renderStateAttr + ); + + renderStatePlug.setValue(mRenderState); + } + + + //******************************************************************* + // + // Set Up Vertex Shaders + // + //******************************************************************* + + // + // If we're using geometry for hair, for shadows, or for instances, + // then we'll need to create/update vertex shaders for those shaveHairShapes + // with their geom shader override turned on. + // + if ((mHairRenderMode == shaveConstant::kGeometryRender) + || (mNormalShadows == shaveConstant::kMayaGeomShadows) + || mRenderInstances) + { + MGlobal::executeCommand("shave_prepareVertexShaders"); + } + + // + // Do we need to set up vertex colours? + // + mNeedVertexColours = mRenderer->needVertexColours(); + + + //******************************************************************* + // + // Turn Off Automatic Clipping Planes + // + //******************************************************************* + + // + // Automatic clipping planes cause us all kinds of problems because + // they're evaluated before frameStart() gets called, which means that + // any geometry created on a per-frame basis (e.g. the camera's render + // cone) may end up getting clipped. + // + // So let's just turn it off. We turn it off on all cameras just in + // case the render camera changes in mid-sequence. + // + mSceneInfo.autoClipCameras.clear(); + + if (numShaveNodes > 0) + { + MItDag dagIter(MItDag::kDepthFirst, MFn::kCamera); + AutoClipInfo camInfo; + + camInfo.nearClipChanged = false; + camInfo.farClipChanged = false; + + for (; !dagIter.isDone(); dagIter.next()) + { + dagIter.getPath(camInfo.cameraPath); + + MFnCamera cameraFn(camInfo.cameraPath); + MPlug plug = cameraFn.findPlug("bestFitClippingPlanes"); + bool autoClipOn; + + plug.getValue(autoClipOn); + + if (autoClipOn) + { + plug.setValue(false); + + camInfo.origNearClip = cameraFn.nearClippingPlane(); + camInfo.origFarClip = cameraFn.farClippingPlane(); + + // + // We shouldn't be doing this here since we don't know + // the render cam yet, but Joe doesn't want to pay to do + // it properly, so we're stuck with issuing spurious + // warnings. + // + if (!cameraFn.isOrtho()) + { + float diff; + + diff = (float)(camInfo.origFarClip - camInfo.origNearClip); + + if (diff == camInfo.origFarClip) + { + MGlobal::displayWarning( + "Shave does not support autoclip. The clipping" + " range is larger than Maya can support. Your" + " surfaces may appear black as a result." + ); + } + } + + mSceneInfo.autoClipCameras.push_front(camInfo); + } + } + } + + // + // Do any renderer-specific setup. + // + mRenderer->renderStart(); + + // + // Setup up a callback for time changes. + // + mTimeChangeCallback = MDGMessage::addForceUpdateCallback(timeChanged); + + return; +} + + +// +// Called at the end of a render of a group of one or more frames. Note +// that for normal Maya renders we get called from a callback but some +// renderers may call us via the shaveRender MEL command. +// +void shaveRender::renderEnd(void* clientData) +{ + // + // We don't support IPR. + // + if (isIPRActive()) return; + + mRenderState = kRenderNone; + + // + // If there's nothing to render, then there's nothing to undo. + // + if ((mHairRenderMode == shaveConstant::kNoRender) && !mRenderInstances) + { + return; + } + + MDGMessage::removeCallback(mTimeChangeCallback); + + // + // Do renderer-specific cleanup. + // + if (mRenderer) mRenderer->renderEnd(); + + // + // Restore automatic clipping planes to those cameras which had them + // turned on. + // + MPlug plug; + AutoClipListIter iter; + + for (iter = mSceneInfo.autoClipCameras.begin(); + iter != mSceneInfo.autoClipCameras.end(); + iter++) + { + MFnCamera cameraFn(iter->cameraPath); + + if (iter->nearClipChanged) + cameraFn.setNearClippingPlane(iter->origNearClip); + + if (iter->farClipChanged) + cameraFn.setFarClippingPlane(iter->origFarClip); + + // + // If we turn the camera's auto clipping back on right now, either + // by setting its plug or executing a 'setAttr' command, then for + // some reason Maya will reset both of the camera's clipping planes + // to 0.1. + // + // But if we do it as a deferred command, then it retains the + // clipping planes. + // + // Just more of the magic of Maya... + // + MGlobal::executeCommand( + MString("evalDeferred \"setAttr ") + + iter->cameraPath.fullPathName() + + ".bestFitClippingPlanes true \"" + ); + } + + mSceneInfo.autoClipCameras.clear(); + + shaveGlobals::getGlobals(); + + // + // Remove any vertex shaders we set up. + // + if ((mHairRenderMode == shaveConstant::kGeometryRender) + || (mNormalShadows == shaveConstant::kMayaGeomShadows) + || mRenderInstances) + { + MGlobal::executeCommand("shave_destroyVertexShaders"); + } + + // + // Toggle the templating of any display nodes which we changed, + // back to their original settings. + // + unsigned int i; + unsigned int numTemplatedNodes = mTemplatedNodes.length(); + + for (i = 0; i < numTemplatedNodes; i++) + { + MFnDependencyNode nodeFn(mTemplatedNodes[i]); + MPlug plug = nodeFn.findPlug("template"); + bool isTemplated; + + plug.getValue(isTemplated); + plug.setValue(!isTemplated); + } + + mTemplatedNodes.clear(); + + // + // Toggle the primary visibility of any display nodes which we changed, + // back to their original settings. + // + unsigned int numHiddenNodes = mHiddenNodes.length(); + + for (i = 0; i < numHiddenNodes; i++) + { + MFnDependencyNode nodeFn(mHiddenNodes[i]); + MPlug plug = nodeFn.findPlug("primaryVisibility"); + bool isHidden; + + plug.getValue(isHidden); + plug.setValue(!isHidden); + } + + mHiddenNodes.clear(); + + // + // Reset the first frame flag to true for the sake of Shave API calls. + // + // See the initialization of 'mFirstFrame' near the top of this file + // for more details on why we do this here. + // + mFirstFrame = true; + + // + // Let the shaveHairShapes know about the new render state. + // + MObjectArray shaveHairShapes; + shaveUtil::getShaveNodes(shaveHairShapes); + + unsigned int numShaveNodes = shaveHairShapes.length(); + + for (i = 0; i < numShaveNodes; i++) + { + MPlug plug(shaveHairShapes[i], shaveHairShape::renderStateAttr); + + plug.setValue(mRenderState); + } + + // + // Get rid of the renderer instance. + // + delete mRenderer; + mRenderer = NULL; + + return; +} + + +void shaveRender::renderInterrupted(void* clientData) +{ + // + // We don't support IPR. + // + if (isIPRActive()) return; + + if (mRenderer) mRenderer->renderInterrupted(); + + if (mRenderState == kRendering) + renderEnd(NULL); + else if (mRenderState != kRenderNone) + { + frameEnd(NULL); + renderEnd(NULL); + } + + return; +} + + +void shaveRender::setupCallbacks() +{ + if (!mCallbacksSet) + { + mBeforeRenderCallback = + MSceneMessage::addCallback( + MSceneMessage::kBeforeSoftwareRender, + renderStart + ); + + mAfterRenderCallback = + MSceneMessage::addCallback( + MSceneMessage::kAfterSoftwareRender, + renderEnd + ); + + mBeforeFrameRenderCallback = + MSceneMessage::addCallback( + MSceneMessage::kBeforeSoftwareFrameRender, + frameStart + ); + + mAfterFrameRenderCallback = + MSceneMessage::addCallback( + MSceneMessage::kAfterSoftwareFrameRender, + frameEnd + ); + + mRenderInterruptedCallback = + MSceneMessage::addCallback( + MSceneMessage::kSoftwareRenderInterrupted, + renderInterrupted + ); + + mCallbacksSet = true; + } +} + + +void shaveRender::cleanupCallbacks() +{ + if (mCallbacksSet) + { + MSceneMessage::removeCallback(mBeforeRenderCallback); + MSceneMessage::removeCallback(mAfterRenderCallback); + MSceneMessage::removeCallback(mBeforeFrameRenderCallback); + MSceneMessage::removeCallback(mAfterFrameRenderCallback); + MSceneMessage::removeCallback(mRenderInterruptedCallback); + + mCallbacksSet = false; + } +} + + +// vlad|15Apr2010 +// +bool shaveRender::rendererIsVray() +{ + MStatus status; + int isVray = false; + + status = MGlobal::executeCommand("shave_rendererIsVray", isVray); + + return (status && isVray); +} + +bool shaveRender::rendererIsPrMan() +{ + MStatus status; + int isPrMan = 0; + status = MGlobal::executeCommand("shave_curRendererIsPrMan", isPrMan); + + return (status && isPrMan); +} + + +int shaveRender::renderCameraView(const MDagPath& cameraPath) +{ + int renderReturnVal = 0; + + shaveGlobals::getGlobals(); + + SceneInfo* shaveData = getSceneInfo(); + + if (doShaveCompsGlob || keepHairRenderFilesGlob) + { + shadowRender = false; + + SHAVEcalibrate(0.0f, -.05f); +// SHAVEcalibrate(-.25f,0.0f); + + if (shaveMaya::doMotionBlur) + { + renderReturnVal = SHAVErender_cam( + &mSceneInfo.shutterOpenCamScene, + &mSceneInfo.shutterCloseCamScene, + mSceneInfo.aa, + (unsigned char*)mSceneInfo.shaveRenderPixels, + mSceneInfo.shaveZBuffer, + 0, + 0, + mSceneInfo.width-1, + mSceneInfo.height-1, + mSceneInfo.width, + mSceneInfo.height, + 3 + ); + } + else + { + renderReturnVal = SHAVErender_camNOBLUR( + &mSceneInfo.shutterOpenCamScene, + &mSceneInfo.shutterOpenCamScene, + mSceneInfo.aa, + (unsigned char*)mSceneInfo.shaveRenderPixels, + mSceneInfo.shaveZBuffer, + 0, + 0, + mSceneInfo.width-1, + mSceneInfo.height-1, + mSceneInfo.width, + mSceneInfo.height, + 3 + ); + } + + if (mFrameGlobals.verbose) + cerr << "Pixel render returned: " << renderReturnVal << endl; + + // + // Did this camera have automatic clipping planes on? + // + AutoClipListIter iter; + + for (iter = mSceneInfo.autoClipCameras.begin(); + iter != mSceneInfo.autoClipCameras.end(); + iter++) + { + if (iter->cameraPath == cameraPath) break; + } + + if (iter != mSceneInfo.autoClipCameras.end()) + { + // + // Find the min and max depth values in the image. + // + bool gotDepth = false; + int i; + int numPixels = mSceneInfo.height * mSceneInfo.width; + float depth; + float minDepth = 0.0f; + float maxDepth = 0.0f; + + for (i = 0; i < numPixels; i++) + { + depth = mSceneInfo.shaveZBuffer[i]; + + if (depth != SHAVE_FAR_CLIP) + { + if (!gotDepth) + { + minDepth = maxDepth = depth; + gotDepth = true; + } + else if (depth < minDepth) + minDepth = depth; + else if (depth > maxDepth) + maxDepth = depth; + } + } + + if (gotDepth) + { + // + // We want to make sure that the camera's clipping planes + // catch all of our generated pixels. So if a pixel lies + // outside of the volume bounded by the near and far + // clipping planes, then we will need to move them so that + // the pixel is included. + // + // Shave's depths represent linear distance from the + // camera while Maya's clip values are the Z coordinates of + // the clipping planes in camera space. + // + // Now let's look at two points in camera space: [0, 0, 5] + // and [4, 2, 4]. The first point is 5 units away from the + // camera while the second point is 6 units away from the + // camera. So the first point is closer to the camera. + // + // However, if we set Maya's near clipping plane to a value + // of 5, it will still clip the second point. That's + // because while the second point is farther from the + // camera than the first, it is still closer along the + // camera's Z-axis, and that's what counts when clipping. + // The camera does not clip at a uniform distance but + // rather at a uniform Z coordinate, which means that the + // clipping distance increases the further you get from the + // camera's Z-axis. + // + // To do the job properly, we should convert each non-empty + // Shave pixel to a camera-space position, then find the + // one with the lowest Z coord. However, that's a bit + // expensive in terms of computation, so instead we'll + // cheat a bit. + // + // The points on the clipping plane which are most distant + // from the camera are at the far corners of the plane. If + // we pull the near clipping plane in so that even those + // corner points are as close as the minimum depth in + // Shave's depth buffer, then we'll be guaranteed that our + // nearest pixel doesn't get clipped. + // + // For the far clipping plane, our job is easier. We want + // to push the far clipping plane out until its point + // nearest the camera is at least as far away as Shave's + // largest depth value. The point nearest the camera on + // the far clipping plane is directly along the Z-axis. So + // we can just move the far clipping plane to Shave's max + // depth value and that will work. + // + // I call all of this "cheating" because we'll frequently + // end up pulling the near clipping plane in closer to the + // camera, and pushing the far plane farther from the + // camera, than we really need to. But it's unlikely that + // anyone will care. If they do, then we can switch to the + // full-blown approach. + // + + // + // The first step is to get the camera's near and far + // clipping planes. + // + MFnCamera cameraFn(cameraPath); + double nearClip = cameraFn.nearClippingPlane(); + double farClip = cameraFn.farClippingPlane(); + + // + // If Shave's max depth is greater than the camera's far + // clip, then reset the clip. + // + if (maxDepth > farClip) + { + cameraFn.setFarClippingPlane((double)maxDepth); + iter->farClipChanged = true; + } + + // + // For the near clipping plane, find a corner of the + // frustum. + // + MPoint frustumCorners[4]; + + cameraFn.getFilmFrustum(1.0, frustumCorners); + + // + // Turn it into a normalized vector. + // + MVector vec( + frustumCorners[0].x, + frustumCorners[0].y, + frustumCorners[0].z + ); + + vec.normalize(); + + // + // Multiply it by our minimum depth value. This will give + // us the position vector for a corner point at the minimum + // depth. + // + vec *= minDepth; + + // + // If the resulting z-coord is smaller than the near clip + // value, then set the near clip to the z-coord value. + // + if (vec.z < nearClip) + { + cameraFn.setNearClippingPlane((double)vec.z); + iter->nearClipChanged = true; + } + } + } + + shaveData->bufferValid = true; + } + + MGlobal::executeCommand("shave_closeProgressBar()"); + + if (keepHairRenderFilesGlob) + { + MString outFilename; + int intFrame; + + if (mSceneInfo.currentFrame < 0) + intFrame = (int)(mSceneInfo.currentFrame - 0.5f); + else + intFrame = (int)(mSceneInfo.currentFrame + 0.5f); + + outFilename = hairFileNameGlob + "."; + outFilename += intFrame; + outFilename += ".tga"; + + if (mFrameGlobals.verbose) { +#if defined(OSMac_) && (MAYA_API_VERSION >= 201600) && (MAYA_API_VERSION < 201700) + cerr << "Outputting to " << outFilename.asChar() << endl; +#else + cerr << "Outputting to " << outFilename << endl; +#endif + } + + SHAVEwrite_targa( + (char *)outFilename.asChar(), + (short)mSceneInfo.width, + (short)mSceneInfo.height, + (unsigned char*)mSceneInfo.shaveRenderPixels + ); + } + + return renderReturnVal; +} + + +void shaveRender::doCamera( + const MDagPath& camPath, + shaveConstant::ShutterState shutter, + shaveRender::SceneInfo& shaveData +) +{ + 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; + + // + // FIELD OF VIEW + // + float pixelAspect = mRenderer ? mRenderer->getPixelAspect() : 1.0f; + double hfovagain, vfovagain; + + portFieldOfView( + shaveMaya::imageWidth, + shaveMaya::imageHeight, + pixelAspect, + hfovagain, + vfovagain, + fnCamera + ); + + const float rad2degrees = (180.0f / (float)M_PI); + + vfovagain *= rad2degrees; + + if (mFrameGlobals.verbose) + { +#if 0 +#ifdef OSMac_ + // + // Panther broke cerr somehow so that it crashes if you use it on + // non-zero float or double values. So let's try our old pal + // stderr, instead. + // + if (stderr) fprintf(stderr, "pixel aspect is: %f\n", pixelAspect); +#else + cerr << "pixel aspect is: " << pixelAspect << endl; +#endif +#endif + } + + SHAVEmake_view_matrix( + camvm, shaveUpVector, shaveDirection, shavePosition, (float)vfovagain + ); + + + /* + * Register the camera with Shave + */ + if ((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + { + SHAVEset_cameraOPEN( + camvm, + shavePosition, + shaveData.width, + shaveData.height, + pixelAspect, + (float)vfovagain, + 0.01f + ); + } + + if ((shutter == shaveConstant::kShutterClose) + || (shutter == shaveConstant::kShutterBoth)) + { + SHAVEset_cameraCLOSE(camvm, shavePosition); + } + + return; +} + + +void shaveRender::computeViewingFrustum( + double image_aspect, + double& left, + double& right, + double& bottom, + double& top, + MFnCamera& cam +) +{ + double film_aspect = cam.aspectRatio(); + double aperture_x = cam.horizontalFilmAperture(); + double aperture_y = cam.verticalFilmAperture(); + double offset_x = cam.horizontalFilmOffset(); + double offset_y = cam.verticalFilmOffset(); + double focal_to_near = cam.nearClippingPlane() / + (cam.focalLength() * MM_TO_INCH); +// 88360 + + focal_to_near *= cam.cameraScale(); + + double scale_x = 1.0; + double scale_y = 1.0; + double translate_x = 0.0; + double translate_y = 0.0; + + switch (cam.filmFit()) { + case MFnCamera::kFillFilmFit: + // In Fill mode we want to keep the entire image within the + // viewing frustum. If the image's aspect ratio is greater + // than that of the film then we'll do a horizontal fit, + // otherwise we'll do a vertical fit. + if (image_aspect > film_aspect) + scale_y = film_aspect / image_aspect; + else + scale_x = image_aspect / film_aspect; + break; + + case MFnCamera::kHorizontalFilmFit: + scale_y = film_aspect / image_aspect; + + if (scale_y > 1.0) + { + translate_y = cam.filmFitOffset() * + (aperture_y - (aperture_y * scale_y)) / 2.0; + } + break; + + case MFnCamera::kVerticalFilmFit: + scale_x = image_aspect / film_aspect; + + if (scale_x > 1.0) + { + translate_x = cam.filmFitOffset() * + (aperture_x - (aperture_x * scale_x)) / 2.0; + } + break; + + case MFnCamera::kOverscanFilmFit: + // In Overscan mode we allow the edges of the image to extend + // beyond the viewing frustum. If the image's aspect ratio is + // greater than that of the film then we'll do a vertical fit, + // allowing the left and right edges to extend beyond the + // frustum. Otherwise we'll do a horizontal fit, allowing the + // top and bottom edges to extend beyond the frustum. + if (image_aspect > film_aspect) + scale_x = image_aspect / film_aspect; + else + scale_y = film_aspect / image_aspect; + break; + + case MFnCamera::kInvalid: + break; + } + + + left = focal_to_near * (-.5*aperture_x*scale_x+offset_x+translate_x); + right = focal_to_near * ( .5*aperture_x*scale_x+offset_x+translate_x); + bottom = focal_to_near * (-.5*aperture_y*scale_y+offset_y+translate_y); + top = focal_to_near * ( .5*aperture_y*scale_y+offset_y+translate_y); +} + + +void shaveRender::portFieldOfView( + int port_width, + int port_height, + double pixelAspect, + double& horizontal, + double& vertical, + MFnCamera& fnCamera +) +{ + double left, right, bottom, top; + double aspect = pixelAspect * (double)port_width / (double)port_height; + computeViewingFrustum(aspect,left,right,bottom,top,fnCamera); + + double neardb = fnCamera.nearClippingPlane(); + horizontal = atan(((right - left) * 0.5) / neardb) * 2.0; + vertical = atan(((top - bottom) * 0.5) / neardb) * 2.0; +} + + +void shaveRender::buildHairStack( + MObjectArray& shaveHairShapes, shaveConstant::ShutterState shutter +) +{ + unsigned int i; + unsigned int numShaveNodes = shaveHairShapes.length(); + + // + // Register the hair nodes. + // + if ((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + SHAVEclear_stack(); + + for (i = 0; i < numShaveNodes; i++) + { + MFnDependencyNode nodeFn(shaveHairShapes[i]); + shaveHairShape* hairShape = (shaveHairShape*)nodeFn.userNode(); + SHAVENODE* hairNode = hairShape->getHairNode(); + + hairShape->setStackIndex(i); + + if ((shutter == shaveConstant::kShutterOpen) + || (shutter == shaveConstant::kShutterBoth)) + { + // + // Remove any old UV sets and add the current ones. + // Note that this *must* be done on the add_hairOPEN call: + // add_hairCLOSE is too late. + // + hairShape->removeShaveUVSets(); + hairShape->addShaveUVSets(); + SHAVEadd_hairOPEN2(hairNode, (int)i); + } + + if ((shutter == shaveConstant::kShutterClose) + || (shutter == shaveConstant::kShutterBoth)) + { + SHAVEadd_hairCLOSE2(hairNode, (int)i); + } + } + + SHAVEset_stack_max(numShaveNodes); + + return; +} + + +void shaveRender::clearHairStack(MObjectArray& shaveHairShapes) +{ + unsigned int i; + unsigned int numShaveNodes = shaveHairShapes.length(); + + // + // Remove the UV sets from the nodes, so that they don't waste mem. + // + for (i = 0; i < numShaveNodes; i++) + { + MFnDependencyNode nodeFn(shaveHairShapes[i]); + shaveHairShape* hairShape = (shaveHairShape*)nodeFn.userNode(); + + hairShape->removeShaveUVSets(); + } + + SHAVEclear_stack(); + + return; +} + + +void shaveRender::timeChanged(MTime& newTime, void* clientData) +{ + if (mRenderer) mRenderer->timeChange(newTime); + + return; +} + + +MStatus shaveRender::renderSwatch( + MObject shaveHairShapeObj, unsigned int res, MString fileName +) +{ + unsigned char* image = new unsigned char[res*res*4]; + float* zbuff = new float[res*res]; + + MFnDependencyNode nodeFn(shaveHairShapeObj); + shaveHairShape* theShaveNode = (shaveHairShape*)nodeFn.userNode(); + SHAVENODE* hairNode = theShaveNode->getHairNode(); + + theShaveNode->makeCurrent(); + + // + // Let's not have the progress bar for such a short render. + // + shaveRenderCancelled = false; + shaveEnableProgressBar = false; + SHAVErender_swatch(image, zbuff, res, &hairNode->shavep); + shaveEnableProgressBar = true; + + delete [] zbuff; + + bool success = shaveXPM::write(res, res, image, fileName); + + delete [] image; + + if (!success) return MS::kFailure; + + return MS::kSuccess; +} + + +MStatus shaveRender::getShutterTimes(float& shutterOpen, float& shutterClose) +{ + // + // Get the current render camera. + // + MDagPath cameraPath = getRenderCamPath(); + + if (!cameraPath.isValid()) return MS::kUnknownParameter; + + // + // Get the camera's shutter angle. + // + MFnCamera cameraFn(cameraPath); + MAngle shutterAngle(cameraFn.shutterAngle(), MAngle::kRadians); + + // + // Calculate the amount of slider time needed for blurring. + // + float frameDelta = (float) + (shutterAngle.asDegrees()/360.0 * shaveMaya::motionBlurByFrame/2.0); + + float midFrame = 0.0f; + //if(rendererIsVray()) + //{ + // double open = 0.0; + // double close = 0.0; + // MGlobal::executeCommand("eval(\"shaveVrayShader -q -shutterOpen\")",open); + // MGlobal::executeCommand("eval(\"shaveVrayShader -q -shutterClose\")",close); + // shutterOpen = open; + // shutterClose= close; + //} + //else + //{ + midFrame = (float)MAnimControl::currentTime().value(); + shutterOpen = midFrame - frameDelta; + shutterClose = midFrame + frameDelta; + //} + MGlobal::displayInfo(MString(" sutterOpen ") + shutterOpen + MString(" shutterClose ") + shutterClose); + + return MS::kSuccess; +} + + +MStatus shaveRender::renderToDRAFile( + SHAVE_RENDER_HOST renderHost, + const MString& fileName, + int voxelResolution, + bool includeOcclusions +) +{ + MStatus st; + bool mustFreeRenderer = false; + MObjectArray shaveHairShapes; + float shutterOpen = 0.0f; + float shutterClose = 0.0f; + + shaveGlobals::getGlobals(); + + if (mRenderer == NULL) + { + getRenderer(); + mustFreeRenderer = true; + } + + mRenderer->getRenderableShaveNodesByRenderMode(NULL, &shaveHairShapes); + + bool doMotionBlur = shaveMaya::doMotionBlur; + + if (doMotionBlur) + { + st = getShutterTimes(shutterOpen, shutterClose); + if (!st) doMotionBlur = false; + } + + MGlobal::displayInfo(MString("blur ") + doMotionBlur + MString("sutterOpen ") + shutterOpen + MString(" shutterClose ") + shutterClose); + + st = renderToDRAFile( + renderHost, + fileName, + voxelResolution, + shaveHairShapes, + doMotionBlur, + shutterOpen, + shutterClose, + true, + includeOcclusions + ); + + if (mustFreeRenderer) + { + delete mRenderer; + mRenderer = NULL; + } + + return st; +} + + +MStatus shaveRender::renderToDRAFile( + SHAVE_RENDER_HOST renderHost, + const MString& fileName, + int voxelResolution, + MObjectArray shaveHairShapes, + bool doMotionBlur, + float shutterOpen, + float shutterClose, + bool updateTextureCache, + bool includeOcclusions +) +{ + bool mustFreeRenderer = false; + MStatus st; + + if (shaveHairShapes.length() == 0) return MS::kNotFound; + + saveFrameGlobals(); + shaveMaya::getRenderGlobals(); + + SHAVEset_verbose(mFrameGlobals.verbose ? 1 : 0); + SHAVEclear_stack(); + + SHAVEset_tile_limit( + mFrameGlobals.tileMemoryLimit, mFrameGlobals.transparencyDepth + ); + + if (mRenderer == NULL) + { + getRenderer(); + mustFreeRenderer = true; + } + + mHairRenderMode = mRenderer->getFilteredRenderMode( + shaveHairShapes, &mRenderInstances + ); + mNormalShadows = mRenderer->getShadowSource(); + + // + // Build the texture lookup tables. + // + if (updateTextureCache) +#ifdef PER_NODE_TEXLOOKUP + initTexInfoLookup2(shaveHairShapes, "", mFrameGlobals.verbose); +#else + initTexInfoLookup(shaveHairShapes, "", mFrameGlobals.verbose); +#endif + + // + // Do everything except the camera renders, as we won't be needing the + // rendered images. + // + shaveRenderCancelled = false; + mSceneInfo.bufferValid = false; + + MDagPath cameraPath = getRenderCamPath(); + + + + if (doMotionBlur) + { + //if(rendererIsVray()) //vlad|15Apr2010 + //{ + // shutterOpen += 0.5; + // shutterClose += 0.5; + //} + + float curFrame = (float)MAnimControl::currentTime().as(MTime::uiUnit()); + + if (curFrame != shutterOpen) shaveUtil::setFrame(shutterOpen); + + buildHairStack(shaveHairShapes, shaveConstant::kShutterOpen); + + bufferRender( + shaveConstant::kShutterOpen, + cameraPath, + shutterOpen, + false, + false, + false, + includeOcclusions + ); + + shaveUtil::setFrame(shutterClose); + buildHairStack(shaveHairShapes, shaveConstant::kShutterClose); + + bufferRender( + shaveConstant::kShutterClose, + cameraPath, + shutterClose, + false, + false, + false, + includeOcclusions + ); + } + else + { + //MTime ct = MAnimControl::currentTime(); + //if(rendererIsVray()) //vlad|15Apr2010 + //{ + // shaveUtil::setFrame((ct + 1.0).value()); + //} + buildHairStack(shaveHairShapes, shaveConstant::kShutterBoth); + + bufferRender( + shaveConstant::kShutterBoth, + cameraPath, + shutterClose, + false, + false, + false, + includeOcclusions + ); + //if(rendererIsVray()) //vlad|15Apr2010 + //{ + // shaveUtil::setFrame(ct.value()); + //} + } + +#if OCCLUSIONS_IN_DRA + if (includeOcclusions) + { + shaveUtil::setWFTYPEVelocities( + &mSceneInfo.shutterOpenCamScene, &mSceneInfo.shutterCloseCamScene + ); + + SHAVEadd_occlusions(&mSceneInfo.shutterOpenCamScene); + } +#endif + + // + // Export the current Shave state to a DRA file. + // + SHAVEset_render_host(renderHost); + SHAVEmult_vox_size(mFrameGlobals.voxelScaling); + SHAVEexport_archive((char*)fileName.asChar(), voxelResolution); + +#if OCCLUSIONS_IN_DRA + if (includeOcclusions) + { + free_geomWF(&mSceneInfo.shutterOpenCamScene); + free_geomWF(&mSceneInfo.shutterCloseCamScene); + } +#endif + + clearHairStack(shaveHairShapes); + + if (mustFreeRenderer) + { + delete mRenderer; + mRenderer = NULL; + } + + return MS::kSuccess; +} + + +MDagPath shaveRender::getRenderCamPath() +{ + // + // We have a dandy MEL proc which determines the current render camera + // for us, so let's use it. + // + MString cameraName; + MGlobal::executeCommand("shave_getRenderCamera()", cameraName); + + MDagPath cameraPath; + MSelectionList list; + + list.add(cameraName); + list.getDagPath(0, cameraPath); + + return cameraPath; +} + + +MString shaveRender::getRenderModeName(shaveConstant::RenderMode mode) +{ + // + // Yes, it would be nice to switch to some kind of dynamic registration + // system rather than these hard-coded strings, but that's not likely + // to happen unless we start supporting a new renderer, or I have a lot + // of idle time on my hands. + // + switch (mode) + { + case shaveConstant::kNoRender: + return "None"; + + case shaveConstant::kBufferRender: + return "Buffer"; + + case shaveConstant::kGeometryRender: + return "Geometry"; + + default: + break; + } + + return ""; +} + + +// +// The caller must not free the returned renderer. +// +shaveRenderer* shaveRender::getRenderer() +{ + // + // If we're in the middle of a render, then return the existing + // renderer (if there is one), otherwise return a new renderer. + // + if (mRenderState == kRenderNone) + { + if (mRenderer != NULL) + { + delete mRenderer; + mRenderer = NULL; + } + } + + if (mRenderer == NULL) + { +#ifdef USE_VRAY + if(rendererIsVray()) //vlad|16Apr2010 + mRenderer = new shaveVrayRenderer; + else +#endif + mRenderer = new shaveMayaRenderer; + + ///I do not see tha rpman case is trapped here -- vald|19Jan2012 + //int isPrMan = 0; + //status = MGlobal::executeCommand("shave_curRendererIsPrMan", isPrMan); + //if(isPrMan) + //{ + //} + } + + return mRenderer; +} + + +bool shaveRender::isIPRActive() +{ + bool isActive = false; + + MGlobal::executeCommand("shave_isIPRActive", isActive); + + return isActive; +} + + +void shaveRender::exportEnd() +{ + mExportActive = false; + + // + // Let the MEL scripts know that the export is over. + // + MGlobal::executeCommand("shave_exportEnd"); + + // + // Clear out the export filename info. + // + mExportFileFramePadding = 0; + mExportFileNameFormat = shaveConstant::kNoFileNameFormat; + mExportFileName = ""; + mExportFilePerFrame = false; + mExportFileCompression = 0; +} + + +void shaveRender::exportStart() +{ + // + // The caller didn't give us a name for the export file, so let's see + // if Maya can. + // + exportStart(MFileIO::beforeExportFilename()); +} + + +void shaveRender::exportStart(MString fileName) +{ + // These parameters were used for special handling of Mental Ray. We + // no longer support Mental Ray and none of the renderers we do + // support have need of these, so we just set them to default values + // for now. + // + bool filePerFrame = false; + shaveConstant::FileNameFormat nameFormat = shaveConstant::kNoFileNameFormat; + unsigned framePadding = 0; + unsigned compression = 0; + + exportStart(fileName, filePerFrame, nameFormat, framePadding, compression); +} + + +void shaveRender::exportStart( + MString exportFile, + bool filePerFrame, + shaveConstant::FileNameFormat nameFormat, + unsigned framePadding, + unsigned compression +) +{ + mExportFileFramePadding = framePadding; + mExportFileName = exportFile; + mExportFileNameFormat = nameFormat; + mExportFilePerFrame = filePerFrame; + mExportFileCompression = compression; + + mExportActive = true; + + // + // Let the MEL scripts know that an export has started. + // + MGlobal::executeCommand("shave_exportStart"); +} + + +void shaveRender::getExportFileInfo( + MString& exportFile, + bool& filePerFrame, + shaveConstant::FileNameFormat& nameFormat, + unsigned& framePadding, + unsigned& compression +) +{ + exportFile = mExportFileName; + filePerFrame = mExportFilePerFrame; + nameFormat = mExportFileNameFormat; + framePadding = mExportFileFramePadding; + compression = mExportFileCompression; + + return; +} + +void shaveRender::vrayKeyFrame() +{ + if (mRenderer) + { +#ifdef USE_VRAY + if(rendererIsVray()) + { + shaveVrayRenderer* vr = static_cast<shaveVrayRenderer*>( mRenderer ); + vr->vrayKeyframeCallback(); + } +#endif + } +} + +/////////////////////// +//void shaveRender::shutterOpen() +//{ +// shaveGlobals::getGlobals(); +// saveFrameGlobals(); +// +// shaveMaya::getRenderGlobals(); +// +// SHAVEset_verbose(mFrameGlobals.verbose ? 1 : 0); +// SHAVEclear_stack(); +// +// MTime curTime = MAnimControl::currentTime(); +// float frame = (float)curTime.as(MTime::uiUnit()); +// MGlobal::displayInfo(MString("shutter open: ") + frame); +// +// bool mustFreeRenderer = false; +// if (mRenderer == NULL) +// { +// getRenderer(); +// mustFreeRenderer = true; +// } +// +// MObjectArray shaveHairShapes; +// mRenderer->getRenderableShaveNodes(shaveHairShapes); +// MGlobal::displayInfo(MString("num nodes: ") + shaveHairShapes.length() ); +// buildHairStack(shaveHairShapes, shaveConstant::kShutterOpen); +// +// //if(mustFreeRenderer) +// //{ +// // delete mRenderer; +// // mRenderer = NULL; +// //} +// MGlobal::displayInfo("shaveRender::shutterOpen - OK"); +// +//} +//void shaveRender::shutterClose() +//{ +// shaveGlobals::getGlobals(); +// saveFrameGlobals(); +// +// shaveMaya::getRenderGlobals(); +// +// MTime curTime = MAnimControl::currentTime(); +// float frame = (float)curTime.as(MTime::uiUnit()); +// MGlobal::displayInfo(MString("shutter close: ") + frame); +// +// +// //bool mustFreeRenderer = false; +// //if (mRenderer == NULL) +// //{ +// // getRenderer(); +// // mustFreeRenderer = true; +// //} +// MObjectArray shaveHairShapes; +// mRenderer->getRenderableShaveNodes(shaveHairShapes); +// buildHairStack(shaveHairShapes, shaveConstant::kShutterClose); +// +// //if(mustFreeRenderer) +// //{ +// // delete mRenderer; +// // mRenderer = NULL; +// //} +//} +//void shaveRender::shutterExport(MString fileName) +//{ +// SHAVEmult_vox_size(mFrameGlobals.voxelScaling); +// SHAVEexport_archive((char*)fileName.asChar(), voxelResolutionGlob); +// +// //bool mustFreeRenderer = false; +// //if (mRenderer == NULL) +// //{ +// // getRenderer(); +// // mustFreeRenderer = true; +// //} +// MObjectArray shaveHairShapes; +// mRenderer->getRenderableShaveNodes(shaveHairShapes); +// clearHairStack(shaveHairShapes); +// +// //if(mustFreeRenderer) +// //{ +// if(mRenderer) +// { +// delete mRenderer; +// mRenderer = NULL; +// } +// //} +//} |