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