diff options
Diffstat (limited to 'mayaPlug/shaveSDKCALLBACKS.cpp')
| -rw-r--r-- | mayaPlug/shaveSDKCALLBACKS.cpp | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/mayaPlug/shaveSDKCALLBACKS.cpp b/mayaPlug/shaveSDKCALLBACKS.cpp new file mode 100644 index 0000000..070ed80 --- /dev/null +++ b/mayaPlug/shaveSDKCALLBACKS.cpp @@ -0,0 +1,601 @@ +// Shave and a Haircut +// (c) 2019 Epic Games +// US Patent 6720962 + +#include <maya/MDagPath.h> +#include <maya/MFloatMatrix.h> +#include <maya/MFloatPoint.h> +#include <maya/MFloatPointArray.h> +#include <maya/MFloatVector.h> +#include <maya/MFloatVectorArray.h> +#include <maya/MFnField.h> +#include <maya/MFnNonAmbientLight.h> +#include <maya/MGlobal.h> +#include <maya/MItSelectionList.h> +#include <maya/MMatrix.h> +#include <maya/MPoint.h> +#include <maya/MPointArray.h> +#include <maya/MRenderUtil.h> +#include <maya/MVector.h> +#include <maya/MVectorArray.h> + +#include "shaveGlobals.h" +#include "shaveHairShape.h" +#include "shaveRender.h" +#include "shaveRenderCallback.h" +#include "shaveSDKTYPES.h" +#include "shaveTextureStore.h" +#include "shaveUtil.h" + + +extern "C" +{ +#include "shaveSDKCALLBACKS.h" + +VERT zeroVert = { 0.0f, 0.0f, 0.0f }; + +// Callback functions (shave calls you) + +#ifndef _DEBUG +VERT SHAVEapply_GI(VERT v,CURVEINFO *cc) +{ + return (zeroVert); +} +#endif + +void SHAVEapply_inst_color( + WFTYPE *instance_geom, int hairID, int slgID, unsigned long shaveINSTID +) +{} + + + +VERT SHAVEdisplace_root(VERT *root,CURVEINFO *ci,int nodeID) +{ + NODETEXINFO* nodeTexInfo = getTexInfoLookup((unsigned)nodeID); + VERT ret = zeroVert; + + if ((nodeTexInfo != NULL) + && (nodeTexInfo->maxPasses > 0) + && (ci->hairID < nodeTexInfo->count) + && (nodeTexInfo->displacement)) + { + // + // See SHAVEapply_texture() for an explanation of why we're doing + // this modulus. + // + int pass = ci->depthpass % nodeTexInfo->maxPasses; + + ret = nodeTexInfo->displacement[pass][ci->hairID]; + } + + return ret; +} + + +static int shadowTicksLeft = 0; +static bool progressBarActive = false; + +// +// This function gets called every 100 hairs when rendering so that you can +// make a progress bar. Estimated total is the number of times this +// function should get called during a render. If you want to cancel the +// render you should return 1 for the rest of the calls to this function +// until the render releases control. +// +int SHAVEprogress(int actual, int estimated_total) +{ + // + // If actual == -1, or if the render has been cancelled, then kill off + // the progress bar. + // + if (progressBarActive && ((actual == -1) || shaveRenderCancelled)) + { + MGlobal::executeCommand("shave_closeProgressBar"); + progressBarActive = false; + } + + if (shadowRender) + { + if ((actual > 0) && (--shadowTicksLeft > 0)) return 0; + + shadowTicksLeft = 400; + } + + if (shaveRenderCancelled) return 1; + + // + // We don't need a progress bar in batch mode. + // + if (MGlobal::mayaState() != MGlobal::kInteractive) return 0; + + int killit = 0; + + if (shaveEnableProgressBar) + { + if (actual > estimated_total) + actual = estimated_total; + + if (actual == 0) + { + if (!progressBarActive) + { + MString cmd = MString("shave_progressBarInit ") + + (double)estimated_total + + " \"" + (shadowRender?"Shadow":"Hair") + "\""; + + MGlobal::executeCommand(cmd); + progressBarActive = true; + } + } + else + { + MGlobal::executeCommand("shave_progressBarQuery()", killit); + + if (!killit) + { + MGlobal::executeCommand( + MString("shave_progressBarStep ") + (double)actual + ); + } + else + { + shaveRenderCancelled = true; + MGlobal::executeCommand("shave_closeProgressBar"); + progressBarActive = false; + } + } + + if (shaveRender::getFrameGlobals().verbose) + { + float pct = ((float)actual/(float)estimated_total)*100; + fprintf(stderr, "Done %f percent.\n",pct); + } + } + + return killit; +} + + +void SHAVEcoord_convertTOSHAVE(VERT *in) +{} + + +void SHAVEcoord_convertFROMSHAVE(VERT *in) +{} +#ifdef _DEBUG +VERT +SHAVEapply_GI( VERT vv, CURVEINFO * ci ) +{ + VERT t; + + t.x = 0.0f; + t.y = 0.0f; + t.z = 0.0f; + return ( t ); +} +extern float +SHAVEapply_falloff( int lightNUM, VERT pos, float cone ) +{ + return ( cone ); +} +#endif + +float SHAVEapply_texture( + CURVEINFO* ci, + VERT rest_root_worldpos, + unsigned long shaveINSTID, + int parm, + float inbound_value +) +{ + NODETEXINFO* nodeTexInfo = getTexInfoLookup(shaveINSTID); + if ((nodeTexInfo != NULL) + && (nodeTexInfo->maxPasses > 0) + && (ci->hairID < nodeTexInfo->count)) + { + // + // If there are multiple shaveHairShapes in the scene, then Shave + // will use the same number of passes for all of them. For + // example, if one shaveHairShape specifies 2 passes and another + // specifies 5, then both shaveHairShapes will be called for 5 + // passes. + // + // So it is possible that we are being called with a pass number + // which is larger than the number of passes specified for this + // particular shaveHairShape. In that case, we use a modulus to + // wrap around to the values used in earlier passes. + // + // No, this isn't a horrible kludge, this is really what Shave is + // expecting. :-) + // + int passToUse = ci->depthpass % nodeTexInfo->maxPasses; +passToUse=0; + // + // Copy the UV values for this hair into the CURVEINFO so that + // later callbacks have access to them. + // + ci->u = nodeTexInfo->u[passToUse][ci->hairID]; + ci->v = nodeTexInfo->v[passToUse][ci->hairID]; + + if (((int)ci->hairID < nodeTexInfo->maxIndex[parm]) + && nodeTexInfo->textured[parm]) + { + float texVal = (float)(nodeTexInfo->textureLookup[passToUse][parm][ci->hairID]); + + //printf("tex %i hair %i value %f\n",parm,ci->hairID,texVal );fflush(stdout); + + return (texVal); // * inbound_value); + } + } + + return inbound_value; +} + + +void MAYAexternal_forces(VERT *lastpos, VERT *velocity, int y) +{ + const MDagPathArray& fieldList = shaveUtil::getFields(); + + unsigned int fieldCount = fieldList.length(); + + if (fieldCount) + { + MStatus stat; + MDagPath dagPath; + MFnField field; + MPoint p(lastpos->x, lastpos->y, lastpos->z); + MVector v(velocity->x, velocity->y, velocity->z); + MPointArray pointArray; + pointArray.append(p); + MVectorArray forces; + MVectorArray velocityArray; + velocityArray.append(v); + MDoubleArray massArray; + unsigned int i; + + for(i = 0; i < fieldCount; i++) + { + // shaveUtil::getFields() only updates the field list when a + // new one is added, not when one is deleted, so we have to + // check the field to make sure that it still exists. + if (fieldList[i].isValid()) + { + field.setObject(fieldList[i]); + stat = field.getForceAtPoint( + pointArray, velocityArray, massArray, forces + ); + } + } + + velocity->x+=((float)forces[0].x)*((float)0.01); + velocity->y+=((float)forces[0].y)*((float)0.01); + velocity->z+=((float)forces[0].z)*((float)0.01); + } +} + + +// These methods must be used when running multiple threads. +// +// Call MAYAcache_forces(0) from Maya's main thread (or its proxy) to create +// the cache for *all* of the current node's guides. +// +// From within individual threads you can then call MAYAapply_cached_forces(..) +// for each guide vertex you want forces applied to. +// +// When done, call MAYAcache_forces(1) from Maya's main thread (or its proxy) +// to free up the cache. +// +MVectorArray forcesCache; + +void MAYAcache_forces(int clearCache) +{ + forcesCache.clear(); + + if (clearCache == 0) + { + const MDagPathArray& fieldList = shaveUtil::getFields(); + + unsigned int fieldCount = fieldList.length(); + + if (fieldCount > 0) + { + shaveHairShape* hairShape = shaveUtil::getLoadedHairShape(); + + // TODO: + // + // We want the new guide positions here, but they're not yet + // available. The call to this function is part of the process + // of calculating those new positions. + // + // For now we'll work around the problem by calculating the + // forces using the old positions. If we call getGuides() at + // this point it will still return the old positions because + // they haven't been updated yet, but it will also mark the + // guide cache as clean, which messes things up later down the + // line. So instead we call getDirtyGuides() which will return + // the current guide cache without attempting to update it. + // + const shaveHairShape::GuidesSnapshot& curGuides = hairShape->getDirtyGuides(); + const shaveHairShape::GuidesSnapshot& prevGuides = hairShape->getPrevGuides(); + MVectorArray points; + MVectorArray velocities; + MDoubleArray masses; + + points.setLength( + static_cast<unsigned int>(curGuides.guides.size() * SHAVE_VERTS_PER_GUIDE) + ); + + velocities.setLength( + static_cast<unsigned int>(curGuides.guides.size() * SHAVE_VERTS_PER_GUIDE) + ); + + // If the previous guide snapshot has the same time as the + // current one, or a different number of guides, then we won't + // be able to use it to calculate velocities and will have to + // assume zero velocity. + // + float deltaT = curGuides.frame - prevGuides.frame; + bool haveVelocity = ( + (fabs(deltaT) > 0.000001f) + && (prevGuides.guides.size() == curGuides.guides.size()) + ); + + unsigned int vertIdx = 0; + + for (size_t g = 0; g < curGuides.guides.size(); ++g) + { + const shaveHairShape::Guide& guide = curGuides.guides[g]; + + for (unsigned int v = 0; v < SHAVE_VERTS_PER_GUIDE; ++v) + { + points[vertIdx] = static_cast<MVector>(guide.verts[v]); + + if (haveVelocity) + { + velocities[vertIdx] = static_cast<MVector>( + (guide.verts[v] - prevGuides.guides[g].verts[v]) / deltaT + ); + } + else + { + velocities[vertIdx] = MVector::zero; + } + + ++vertIdx; + } + } + + for (unsigned int f = 0; f < fieldCount; ++f) + { + // shaveUtil::getFields() only updates the field list when a + // new one is added, not when one is deleted, so we have to + // check the field to make sure that it still exists. + // + if (fieldList[f].isValid()) + { + MFnField fieldFn(fieldList[f]); + + fieldFn.getForceAtPoint( + points, velocities, masses, forcesCache + ); + } + } + } + } +} + +// Forces are added to whatever value is already in velocity. +// +void MAYAapply_cached_forces(int guideNum, int vertNum, VERT* velocity) +{ + if ((guideNum >= 0) && (guideNum < static_cast<int>(forcesCache.length())) + && (vertNum >= 0) && (vertNum < SHAVE_VERTS_PER_GUIDE)) + { + MVector& delta = forcesCache[guideNum * SHAVE_VERTS_PER_GUIDE + vertNum]; + velocity->x += static_cast<float>(delta.x * 0.01); + velocity->y += static_cast<float>(delta.y * 0.01); + velocity->z += static_cast<float>(delta.z * 0.01); + } +} + +VERT SHAVEapply_illumination(int LIGHTID, VERT wpos,VERT vector, VERT color) +{ + return color; +} + +#ifndef _DEBUG +float SHAVEapply_falloff(int lightID, VERT p, float intensity) +{ + MStatus st; + + // + // When doing native illumination, the light samples taken in + // SHAVEapply_illuminationWF() will already have taken decay into + // account, so we don't want to further apply it here. + // + if (!shaveRender::getFrameGlobals().useNativeIllumination + && (intensity > 0.0f)) + { + int lightIndex = shaveUtil::getLightIndexFromID(lightID); + + if (lightIndex >= 0) + { + MDagPath lightDag = shaveUtil::globalLightList[lightIndex].path; + + // + // Only non-ambient lights have decay. + // + MFnNonAmbientLight lightFn(lightDag, &st); + + if (st) + { + short decay = lightFn.decayRate(); + + if (decay != 0) + { + // + // How far is the sample point from the light? + // + MTransformationMatrix lightWorldMatrix; + lightWorldMatrix = lightDag.inclusiveMatrix(); + + MVector lightPos = lightWorldMatrix.translation(MSpace::kWorld); + MVector samplePos((double)p.x, (double)p.y, (double)p.z); + float dist = (float)(samplePos - lightPos).length(); + + // + // Maya's lights don't decay within the first unit of + // distance. + // + if (dist > 1.0) + { + switch (decay) + { + case 1: // Linear + intensity = intensity / dist; + break; + + case 2: // Quadratic + intensity = intensity / (dist * dist); + break; + + case 3: // Cubic + intensity = intensity / (dist * dist * dist); + break; + + default: + break; + } + } + } + } + } + } + + return intensity; +} +#endif + +void SHAVEapply_illuminationWF(int LIGHTID, WFTYPE* samples) +{ + // ranges are 0.0 - 1.0 (return isn't clipped until after shading) + // modify or replace 'samples->color' - it contains light info for + // current test before shave shadows are applied + // samples->v is the position + // samples->totalverts is the total number of points + + if (shaveRender::getFrameGlobals().useNativeIllumination + && (samples != NULL) + && (samples->totalverts > 0)) + { + int lightIndex = shaveUtil::getLightIndexFromID(LIGHTID); + + if (lightIndex >= 0) + { + MStatus status; + MTransformationMatrix lightWorldMatrix; + MDagPath lightDag = shaveUtil::globalLightList[lightIndex].path; + + lightWorldMatrix = lightDag.inclusiveMatrix(); + MVector lightTranslation = lightWorldMatrix.translation(MSpace::kWorld ); + + MFloatVectorArray normals; + MFloatPointArray pointArray; + unsigned int i; + + for (i = 0; i < (unsigned int)samples->totalverts; i++) + { + pointArray.append( + samples->v[i].x, samples->v[i].y, samples->v[i].z + ); + + MFloatVector sampleAsVec( + samples->v[i].x, + samples->v[i].y, + samples->v[i].z + ); + + MFloatVector vec(lightTranslation - sampleAsVec); + vec.normalize(); + normals.append(vec); + } + + MFloatVectorArray vertColorArray; + MFloatVectorArray vertTranspArray; + MFloatMatrix camMatrix; + MString attrName = lightDag.fullPathName() + ".lightIntensity"; + status = MRenderUtil::sampleShadingNetwork( + attrName, //attribute to sample + (int)pointArray.length(), //samples + false, //shadows + false, //reuse shad maps + camMatrix, //camMatrix + &pointArray, //points + NULL, //u coords + NULL, //v coords + &normals, //normals + NULL, //ref points + NULL, //u tan + NULL, //v tan + NULL, //filter size + vertColorArray, //out color + vertTranspArray //out transp + ); + + if (status) + { + for (i = 0; i < vertColorArray.length(); i++) + { + samples->color[i].x = vertColorArray[i].x; + samples->color[i].y = vertColorArray[i].y; + samples->color[i].z = vertColorArray[i].z; + } + } + } + } +} + + +// this is a callback for applying atmospherics/depth cueing +VERT SHAVEapply_atmosphere(VERT wpos,VERT inbound_color ) +{ + // range = 0.0 - 1.0 (shave will clip out of bound returns) + // do your tint here based on wpos + // here's how: + // fogval=your_compute_func_for_fog(wpos); + // if (fog_val>1.0) fog_val=1.0; + // if (fog_val<0.0) fog_val=0.0; + // inbound_color.x=inbound_color.x*(1.0-fog_val)+fog_color.x*fog_val; + // inbound_color.y=inbound_color.y*(1.0-fog_val)+fog_color.y*fog_val; + // inbound_color.z=inbound_color.z*(1.0-fog_val)+fog_color.z*fog_val; + return inbound_color; +} + + +// +// This function callback gives you the opportunity to apply external +// vertpaint to any/every channel in shave. Normally the inbound_value +// will be 1.0 (unless you've painted a map inside shave) the return, +// should contain a value who's range is (float) 0-1. +// +float SHAVEapply_VMAP(long SHAVEINSTID,int VERTID,int chan, float inbound_value) +{ + return applyVertTexValue(SHAVEINSTID, VERTID, chan, inbound_value); +} + + +// This is called whenever Shave finishes rendering a tile into the pixel +// buffer. +void SHAVEdraw_tile_callback(VERT* min, VERT* max) +{ + shaveRenderCallback::tileRendered( + (unsigned int)min->x, + (unsigned int)max->x, + (unsigned int)min->y, + (unsigned int)max->y + ); +} + +} + |