C++ API Reference
simpleFluidEmitter/simpleFluidEmitter.cpp
//-
// ==========================================================================
// Copyright 2015 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+
// DESCRIPTION:
//
// Produces the dependency graph node "simpleFluidEmitter".
//
// This example demonstrates how to use the new MPxFluidEmitterNode class
// to implement most of the functionality in Maya's standard fluid emitters.
//
// MEL usage:
//
// createNode simpleFluidEmitter -name simpleFluidEmitter;
// // create particle object and connect to the plugin emitter node.
// particle -name particles ;
// connectDynamic -em simpleFluidEmitter particles;
// setAttr "simpleFluidEmitter.rate" 200;
// setAttr "simpleFluidEmitter.speed" 25;
// playbackOptions -e -min 0.00 -max 60.0;
// currentTime -e 0;
// play -wait -forward true;
// // make some keyframes on emitter
// currentTime 0 ;
// select -r simpleFluidEmitter ;
// setKeyframe "simpleFluidEmitter.tx";
// setKeyframe "simpleFluidEmitter.ty";
// setKeyframe "simpleFluidEmitter.tz";
// setKeyframe "simpleFluidEmitter.rx";
// setKeyframe "simpleFluidEmitter.ry";
// setKeyframe "simpleFluidEmitter.rz";
// currentTime 30 ;
// move -r -2.011944 6.283524 -2.668834 ;
// move -r -ls -wd 0 0 12.97635 ;
// rotate -r -os 0 -75.139762 0 ;
// setKeyframe "simpleFluidEmitter.tx";
// setKeyframe "simpleFluidEmitter.ty";
// setKeyframe "simpleFluidEmitter.tz";
// setKeyframe "simpleFluidEmitter.rx";
// setKeyframe "simpleFluidEmitter.ry";
// setKeyframe "simpleFluidEmitter.rz";
// currentTime 60 ;
// move -r 0 0 -14.526107 ;
// move -r 0 -8.130523 0 ;
// rotate -r -os 0 0 78.039751 ;
// rotate -r -os 0 0 53.86918 ;
// setKeyframe "simpleFluidEmitter.tx";
// setKeyframe "simpleFluidEmitter.ty";
// setKeyframe "simpleFluidEmitter.tz";
// setKeyframe "simpleFluidEmitter.rx";
// setKeyframe "simpleFluidEmitter.ry";
// setKeyframe "simpleFluidEmitter.rz";
//
#include <maya/MIOStream.h>
#include <math.h>
#include <stdlib.h>
#include "simpleFluidEmitter.h"
#include <maya/MVectorArray.h>
#include <maya/MDoubleArray.h>
#include <maya/MIntArray.h>
#include <maya/MMatrix.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnUnitAttribute.h>
#include <maya/MFnVectorArrayData.h>
#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnArrayAttrsData.h>
#include <maya/MFnMatrixData.h>
#include <maya/MDagPath.h>
#include <maya/MMatrix.h>
#include <maya/MTransformationMatrix.h>
#include <maya/MFnDynSweptGeometryData.h>
#include <maya/MDynSweptTriangle.h>
#include <maya/MPlugArray.h>
MTypeId simpleFluidEmitter::id( 0x81020 );
simpleFluidEmitter::simpleFluidEmitter()
{
}
simpleFluidEmitter::~simpleFluidEmitter()
{
}
void *simpleFluidEmitter::creator()
{
return new simpleFluidEmitter;
}
MStatus simpleFluidEmitter::initialize()
//
// Descriptions:
// Initialize the node, create user defined attributes.
//
{
return( MS::kSuccess );
}
MStatus simpleFluidEmitter::compute(const MPlug& plug, MDataBlock& block)
//
// Description:
//
// Fluid emitters do not perform emission in their compute() method.
// Instead, each fluid to which the emitter is connected will call the
// fluidEmitter() method once per frame, to allow the emitter to emit
// directly into the fluid.
//
// It is ESSENTIAL that the compute routine return MS::kUnknownParameter
// when the "emissionFunction" attribute is being evaluated. Doing so
// will trigger the base class default compute() method, which will
// register this node's "fluidEmitter" function with the fluid. The
// mechanisms for doing this are not exposed through the API, so it is
// important to let the default code handle this case.
//
// For all other attributes, users can override the compute() method.
//
{
if( plug.attribute() == mEmissionFunction )
{
// ESSENTIAL! Let the base class default compute method handle this
//
return MS::kUnknownParameter;
}
else
{
// can add custom handling for other attributes here
//
return MS::kUnknownParameter;
}
}
simpleFluidEmitter::fluidEmitter(
const MObject& fluidObject,
const MMatrix& worldMatrix,
int plugIndex
)
//==============================================================================
//
// Description:
//
// Callback function that gets called once per frame by each fluid
// into which this emitter is emitting. Emits values directly into
// the fluid object. The MFnFluid object passed to this routine is
// not pointing to a DAG object, it is pointing to an internal fluid
// data structure that the fluid node is constructing, eventually to
// be set into the fluid's output attribute.
//
// Parameters:
//
// fluid: fluid into which we are emitting
// worldMatrix: object->world matrix for the fluid
// plugIndex: identifies which fluid connected to the emitter
// we are emitting into
//
// Returns:
//
// MS::kSuccess if the method wishes to override the default
// emitter behaviour
// MS::kUnknownParameter if the method wishes to have the default
// emitter behaviour execute after this routine
// exits.
//
// Notes:
//
// The method first does some work common to all emitter types, then
// calls one of 4 different methods to actually do the emission.
// The methods are:
//
// omniEmitter: omni-directional emitter from a point,
// or from the vertices of an owner object.
//
// volumeEmitter: emits from the surface of an exact cube, sphere,
// cone, cylinder, or torus.
//
// surfaceEmitter: emits from the surface of an owner object.
//
//==============================================================================
{
// make sure the fluid is valid. If it isn't, return MS::kSuccess, indicating
// that no work needs to be done. If we return a failure code, then the default
// fluid emitter code will try to run, which is pointless if the fluid is not
// valid.
//
MFnFluid fluid( fluidObject );
if( fluid.object() != MObject::kNullObj )
{
return MS::kSuccess;
}
// get a data block for the emitter, so we can get attribute values
//
MDataBlock block = forceCache();
// figure out the time interval for emission for the given fluid
//
double dTime = getDeltaTime( plugIndex, block ).as(MTime::kSeconds);
if( dTime == 0.0 )
{
// shouldn't happen, but if the time interval is 0, then no fluid should
// be emitted
return MS::kSuccess;
}
// if currentTime <= startTime, return. The startTime is connected to
// the target fluid object.
//
MTime cTime = getCurrentTime( block );
MTime sTime = getStartTime( plugIndex, block );
// if we are at or before the start time, reset the random number
// state to the appropriate seed value for the given fluid
//
if( cTime < sTime )
{
resetRandomState( plugIndex, block );
return MS::kSuccess;
}
// check to see if we need to emit anything into the target fluid.
// if the emission rate is 0, or if the fluid doesn't have a grid
// for one of the quantities, then we needn't do any emission
//
// emission rates
double density = fluidDensityEmission( block );
double heat = fluidHeatEmission( block );
double fuel = fluidFuelEmission( block );
bool doColor = fluidEmitColor( block );
// fluid grid settings
MFnFluid::FluidMethod densityMode, tempMode, fuelMode;
fluid.getDensityMode( densityMode, grad );
fluid.getTemperatureMode( tempMode, grad );
fluid.getFuelMode( fuelMode, grad );
fluid.getColorMode( colorMode );
fluid.getFalloffMode( falloffMode );
// see if we need to emit density, heat, fuel, or color
bool densityToEmit = (density != 0.0) && ((densityMode == MFnFluid::kDynamicGrid)||(densityMode == MFnFluid::kStaticGrid));
bool heatToEmit = (heat != 0.0) && ((tempMode == MFnFluid::kDynamicGrid)||(tempMode == MFnFluid::kStaticGrid));
bool fuelToEmit = (fuel != 0.0) && ((fuelMode == MFnFluid::kDynamicGrid)||(fuelMode == MFnFluid::kStaticGrid));
bool colorToEmit = doColor && ((colorMode == MFnFluid::kDynamicColorGrid)||(colorMode == MFnFluid::kStaticColorGrid));
bool falloffEmit = (falloffMode == MFnFluid::kStaticFalloffGrid);
// nothing to emit, do nothing
//
if( !densityToEmit && !heatToEmit && !fuelToEmit && !colorToEmit && !falloffEmit )
{
return MS::kSuccess;
}
// get the dropoff rate for the fluid
//
double dropoff = fluidDropoff( block );
// modify the dropoff rate to account for fluids that have
// been scaled in worldspace - larger scales mean slower
// falloffs and vice versa
//
MTransformationMatrix xform( worldMatrix );
double xformScale[3];
xform.getScale( xformScale, MSpace::kWorld );
double dropoffScale = sqrt( xformScale[0]*xformScale[0] +
xformScale[1]*xformScale[1] +
xformScale[2]*xformScale[2] );
if( dropoffScale > 0.1 )
{
dropoff /= dropoffScale;
}
// retrieve the current random state from the "randState" attribute, and
// store it in the member variable "randState". We will use this member
// value numerous times via the randgen() method. Once we are done emitting,
// we will set the random state back into the attribute via setRandomState().
//
getRandomState( plugIndex, block );
// conversion value used to map user input emission rates into internal
// values.
//
double conversion = 0.01;
MEmitterType emitterType = getEmitterType( block );
switch( emitterType )
{
case kOmni:
omniFluidEmitter( fluid, worldMatrix, plugIndex, block, dTime,
conversion, dropoff );
break;
case kVolume:
volumeFluidEmitter( fluid, worldMatrix, plugIndex, block, dTime,
conversion, dropoff );
break;
case kSurface:
surfaceFluidEmitter( fluid, worldMatrix, plugIndex, block, dTime,
conversion, dropoff );
break;
default:
break;
}
// store the random state back into the datablock
//
setRandomState( plugIndex, block );
return MS::kSuccess;
}
#ifdef MIN
#undef MIN
#endif
#ifdef MAX
#undef MAX
#endif
#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))
void
simpleFluidEmitter::omniFluidEmitter(
MFnFluid& fluid,
const MMatrix& fluidWorldMatrix,
int plugIndex,
MDataBlock& block,
double dt,
double conversion,
double dropoff
)
//==============================================================================
//
// Method:
//
// simpleFluidEmitter::omniFluidEmitter
//
// Description:
//
// Emits fluid from a point, or from a set of object control points.
//
// Parameters:
//
// fluid: fluid into which we are emitting
// fluidWorldMatrix: object->world matrix for the fluid
// plugIndex: identifies which fluid connected to the emitter
// we are emitting into
// block: datablock for the emitter, to retrieve attribute
// values
// dt: time delta for this frame
// conversion: mapping from UI emission rates to internal units
// dropoff: specifies how much emission rate drops off as
// we move away from each emission point.
//
// Notes:
//
// If no owner object is present for the emitter, we simply emit from
// the emitter position. If an owner object is present, then we emit
// from each control point of that object in an identical fashion.
//
// To associate an owner object with an emitter, use the
// addDynamic MEL command, e.g. "addDynamic simpleFluidEmitter1 pPlane1".
//
//==============================================================================
{
// find the positions that we need to emit from
//
MVectorArray emitterPositions;
// first, try to get them from an owner object, which will have its
// "ownerPositionData" attribute feeding into the emitter. These
// values are in worldspace
//
bool gotOwnerPositions = false;
MObject ownerShape = getOwnerShape();
if( ownerShape != MObject::kNullObj )
{
MStatus status;
MDataHandle hOwnerPos = block.inputValue( mOwnerPosData, &status );
if( status == MS::kSuccess )
{
MObject dOwnerPos = hOwnerPos.data();
MFnVectorArrayData fnOwnerPos( dOwnerPos );
MVectorArray posArray = fnOwnerPos.array( &status );
if( status == MS::kSuccess )
{
// assign vectors from block to ownerPosArray.
//
for( unsigned int i = 0; i < posArray.length(); i ++ )
{
emitterPositions.append( posArray[i] );
}
gotOwnerPositions = true;
}
}
}
// there was no owner object, so we just use the emitter position for
// emission.
//
if( !gotOwnerPositions )
{
MPoint emitterPos = getWorldPosition();
emitterPositions.append( emitterPos );
}
// get emission rates for density, fuel, heat, and emission color
//
double densityEmit = fluidDensityEmission( block );
double fuelEmit = fluidFuelEmission( block );
double heatEmit = fluidHeatEmission( block );
bool doEmitColor = fluidEmitColor( block );
MColor emitColor = fluidColor( block );
// rate modulation based on frame time, user value conversion factor, and
// standard emitter "rate" value (not actually exposed in most fluid
// emitters, but there anyway).
//
double theRate = getRate(block) * dt * conversion;
// get voxel dimensions and sizes (object space)
//
double size[3];
unsigned int res[3];
fluid.getDimensions( size[0], size[1], size[2] );
fluid.getResolution( res[0], res[1], res[2] );
// voxel sizes
double dx = size[0] / res[0];
double dy = size[1] / res[1];
double dz = size[2] / res[2];
// voxel centers
double Ox = -size[0]/2;
double Oy = -size[1]/2;
double Oz = -size[2]/2;
// emission will only happen for voxels whose centers lie within
// "minDist" and "maxDist" of an emitter position
//
double minDist = getMinDistance( block );
double maxDist = getMaxDistance( block );
// bump up the min/max distance values so that they
// are both > 0, and there is at least about a half
// voxel between the min and max values, to prevent aliasing
// artifacts caused by emitters missing most voxel centers
//
MTransformationMatrix fluidXform( fluidWorldMatrix );
double fluidScale[3];
fluidXform.getScale( fluidScale, MSpace::kWorld );
// compute smallest voxel diagonal length
double wsX = fabs(fluidScale[0]*dx);
double wsY = fabs(fluidScale[1]*dy);
double wsZ = fabs(fluidScale[2]*dz);
double wsMin = MIN( MIN( wsX, wsY), wsZ );
double wsMax = MAX( MAX( wsX, wsY), wsZ );
double wsDiag = wsMin * sqrt(3.0);
// make sure emission range is bigger than 0.5 voxels
if ( maxDist <= minDist || maxDist <= (wsDiag/2.0) ) {
if ( minDist < 0 ) minDist = 0;
maxDist = minDist + wsDiag/2.0;
dropoff = 0;
}
// Now, it's time to actually emit into the fluid:
//
// foreach emitter point
// foreach voxel
// - select some points in the voxel
// - compute a dropoff function from the emitter point
// - emit an appropriate amount of fluid into the voxel
//
// Since we've already expanded the min/max distances to cover
// the smallest voxel dimension, we should only need 1 sample per
// voxel, unless the voxels are highly non-square. We increase the
// number of samples in these cases.
//
// If the "jitter" flag is enabled, we jitter each sample position,
// using the rangen() function, which keeps track of independent
// random states for each fluid, to make sure that results are
// repeatable for multiple simulation runs.
//
// basic sample count
int numSamples = 1;
// increase samples if necessary for non-square voxels
if(wsMin >.00001)
{
numSamples = (int)(wsMax/wsMin + .5);
if(numSamples > 8)
numSamples = 8;
if(numSamples < 1)
numSamples = 1;
}
bool jitter = fluidJitter(block);
if( !jitter )
{
// I don't have a good uniform sample generator for an
// arbitrary number of samples. It would be a good idea to use
// one here. For now, just use 1 sample for the non-jittered case.
//
numSamples = 1;
}
for( unsigned int p = 0; p < emitterPositions.length(); p++ )
{
MPoint emitterWorldPos = emitterPositions[p];
// loop through all voxels, looking for ones that lie at least
// partially within the dropoff field around this emitter point
//
for( unsigned int i = 0; i < res[0]; i++ )
{
double x = Ox + i*dx;
for( unsigned int j = 0; j < res[1]; j++ )
{
double y = Oy + j*dy;
for( unsigned int k = 0; k < res[2]; k++ )
{
double z = Oz + k*dz;
int si;
for( si = 0; si < numSamples; si++ )
{
// compute sample point (fluid object space)
//
double rx, ry, rz;
if( jitter )
{
rx = x + randgen()*dx;
ry = y + randgen()*dy;
rz = z + randgen()*dz;
}
else
{
rx = x + 0.5*dx;
ry = y + 0.5*dy;
rz = z + 0.5*dz;
}
// compute distance from sample to emitter point
//
MPoint point( rx, ry, rz );
point *= fluidWorldMatrix;
MVector diff = point - emitterWorldPos;
double distSquared = diff * diff;
double dist = diff.length();
// discard if outside min/max range
//
if( (dist < minDist) || (dist > maxDist) )
{
continue;
}
// drop off the emission rate according to the falloff
// parameter, and divide to accound for multiple samples
// in the voxel
//
double distDrop = dropoff * distSquared;
double newVal = theRate * exp( -distDrop ) / (double)numSamples;
// emit density/heat/fuel/color into the current voxel
//
if( newVal != 0 )
{
fluid.emitIntoArrays( (float) newVal, i, j, k, (float)densityEmit, (float)heatEmit, (float)fuelEmit, doEmitColor, emitColor );
}
float *fArray = fluid.falloff();
if( fArray != NULL )
{
MPoint midPoint( x+0.5*dx, y+0.5*dy, z+0.5*dz );
midPoint.x *= 0.2;
midPoint.y *= 0.2;
midPoint.z *= 0.2;
float fdist = (float) sqrt( midPoint.x*midPoint.x + midPoint.y*midPoint.y + midPoint.z*midPoint.z );
fdist /= sqrtf(3.0f);
fArray[fluid.index(i,j,k)] = 1.0f-fdist;
}
}
}
}
}
}
}
void
simpleFluidEmitter::volumeFluidEmitter(
MFnFluid& fluid,
const MMatrix& fluidWorldMatrix,
int plugIndex,
MDataBlock& block,
double dt,
double conversion,
double dropoff
)
//==============================================================================
//
// Method:
//
// simpleFluidEmitter::volumeFluidEmitter
//
// Description:
//
// Emits fluid from points distributed over the surface of the
// emitter's owner object.
//
// Parameters:
//
// fluid: fluid into which we are emitting
// fluidWorldMatrix: object->world matrix for the fluid
// plugIndex: identifies which fluid connected to the emitter
// we are emitting into
// block: datablock for the emitter, to retrieve attribute
// values
// dt: time delta for this frame
// conversion: mapping from UI emission rates to internal units
// dropoff: specifies how much emission rate drops off as
// we move away from the local y-axis of the
// volume emitter shape.
//
//==============================================================================
{
// get emitter position and relevant matrices
//
MPoint emitterPos = getWorldPosition();
MMatrix emitterWorldMatrix = getWorldMatrix();
MMatrix fluidInverseWorldMatrix = fluidWorldMatrix.inverse();
// get emission rates for density, fuel, heat, and emission color
//
double densityEmit = fluidDensityEmission( block );
double fuelEmit = fluidFuelEmission( block );
double heatEmit = fluidHeatEmission( block );
bool doEmitColor = fluidEmitColor( block );
MColor emitColor = fluidColor( block );
// rate modulation based on frame time, user value conversion factor, and
// standard emitter "rate" value (not actually exposed in most fluid
// emitters, but there anyway).
//
double theRate = getRate(block) * dt * conversion;
// find the voxels that intersect the bounding box of the volume
// primitive associated with the emitter
//
if( !volumePrimitiveBoundingBox( bbox ) )
{
// shouldn't happen
//
return;
}
// transform volume primitive into fluid space
//
bbox.transformUsing( emitterWorldMatrix );
bbox.transformUsing( fluidInverseWorldMatrix );
MPoint lowCorner = bbox.min();
MPoint highCorner = bbox.max();
// see if autoresize to emitter is on, so we can resize before emitting
//
// unfortunately, we need the fluidShape itself in order to check
// we'll just look at the first connected fluid,
// walking the multi is left as an excercise for the reader
// similarly, determining whether there is start frame emission
// and resizing at the start frame, or not, is left as an excercise
// and finally, emission with autoResize will need to include the dynamicOffset
//
MObject thisObj = thisMObject();
MFnDependencyNode nodeFn(thisObj);
bool autoResize = false;
bool resizeToEmitter = false;
MPlug fnPlug = nodeFn.findPlug("emissionFunction", true);
if(fnPlug.isConnected()) {
MPlugArray connections;
fnPlug.connectedTo(connections, false, true);
if (connections.length() > 0) {
MObject sourceNode = connections[0].node();
if (sourceNode.hasFn(MFn::kFluid)) {
MFnFluid fluidFn(sourceNode);
autoResize = fluidFn.isAutoResize();
resizeToEmitter = fluidFn.isResizeToEmitter();
if(autoResize && resizeToEmitter) {
fluidFn.expandToInclude(lowCorner, highCorner);
}
}
}
}
if(autoResize && resizeToEmitter) {
fluid.updateGrid ();
}
// get voxel dimensions and sizes (object space)
//
double size[3];
unsigned int res[3];
fluid.getDimensions( size[0], size[1], size[2] );
fluid.getResolution( res[0], res[1], res[2] );
// voxel sizes
double dx = size[0] / res[0];
double dy = size[1] / res[1];
double dz = size[2] / res[2];
// voxel centers
double Ox = -size[0]/2;
double Oy = -size[1]/2;
double Oz = -size[2]/2;
// get fluid voxel coord range of bounding box
//
int3 lowCoords;
int3 highCoords;
fluid.toGridIndex( lowCorner, lowCoords );
fluid.toGridIndex( highCorner, highCoords );
int i;
for ( i = 0; i < 3; i++ )
{
if ( lowCoords[i] < 0 ) {
lowCoords[i] = 0;
} else if ( lowCoords[i] > ((int)res[i])-1 ) {
lowCoords[i] = ((int)res[i])-1;
}
if ( highCoords[i] < 0 ) {
highCoords[i] = 0;
} else if ( highCoords[i] > ((int)res[i])-1 ) {
highCoords[i] = ((int)res[i])-1;
}
}
// figure out the emitter size relative to the voxel size, and compute
// a per-voxel sampling rate that uses 1 sample/voxel for emitters that
// are >= 2 voxels big in all dimensions. For smaller emitters, use up
// to 8 samples per voxel.
//
double emitterVoxelSize[3];
emitterVoxelSize[0] = (highCorner[0]-lowCorner[0])/dx;
emitterVoxelSize[1] = (highCorner[1]-lowCorner[1])/dy;
emitterVoxelSize[2] = (highCorner[2]-lowCorner[2])/dz;
double minVoxelSize = MIN(emitterVoxelSize[0],MIN(emitterVoxelSize[1],emitterVoxelSize[2]));
if( minVoxelSize < 1.0 )
{
minVoxelSize = 1.0;
}
int maxSamples = 8;
int numSamples = (int)(8.0/(minVoxelSize*minVoxelSize*minVoxelSize) + 0.5);
if( numSamples < 1 ) numSamples = 1;
if( numSamples > maxSamples ) numSamples = maxSamples;
// non-jittered, just use one sample in the voxel center. Should replace
// with uniform sampling pattern.
//
bool jitter = fluidJitter(block);
if( !jitter )
{
numSamples = 1;
}
// for each voxel that could potentially intersect the volume emitter
// primitive, take some samples in the voxel. For those inside the
// volume, compute their dropoff relative to the primitive's local y-axis,
// and emit an appropriate amount into the voxel.
//
for( i = lowCoords[0]; i <= highCoords[0]; i++ )
{
double x = Ox + (i+0.5)*dx;
for( int j = lowCoords[1]; j < highCoords[1]; j++ )
{
double y = Oy + (j+0.5)*dy;
for( int k = lowCoords[2]; k < highCoords[2]; k++ )
{
double z = Oz + (k+0.5)*dz;
for ( int si = 0; si < numSamples; si++) {
// compute voxel sample point (object space)
//
double rx, ry, rz;
if(jitter) {
rx = x + dx*(randgen() - 0.5);
ry = y + dy*(randgen() - 0.5);
rz = z + dz*(randgen() - 0.5);
} else {
rx = x;
ry = y;
rz = z;
}
// to world space
MPoint pt( rx, ry, rz );
pt *= fluidWorldMatrix;
// test to see if point is inside volume primitive
//
if( volumePrimitivePointInside( pt, emitterWorldMatrix ) )
{
// compute dropoff
//
double dist = pt.distanceTo( emitterPos );
double distDrop = dropoff * (dist*dist);
double newVal = (theRate * exp( -distDrop )) / (double)numSamples;
// emit into arrays
//
if( newVal != 0.0 )
{
fluid.emitIntoArrays( (float) newVal, i, j, k, (float)densityEmit, (float)heatEmit, (float)fuelEmit, doEmitColor, emitColor );
}
}
}
}
}
}
}
void
simpleFluidEmitter::surfaceFluidEmitter(
MFnFluid& fluid,
const MMatrix& fluidWorldMatrix,
int plugIndex,
MDataBlock& block,
double dt,
double conversion,
double dropoff
)
//==============================================================================
//
// Method:
//
// simpleFluidEmitter::surfaceFluidEmitter
//
// Description:
//
// Emits fluid from one of a predefined set of volumes (cube, sphere,
// cylinder, cone, torus).
//
// Parameters:
//
// fluid: fluid into which we are emitting
// fluidWorldMatrix: object->world matrix for the fluid
// plugIndex: identifies which fluid connected to the emitter
// we are emitting into
// block: datablock for the emitter, to retrieve attribute
// values
// dt: time delta for this frame
// conversion: mapping from UI emission rates to internal units
// dropoff: specifies how much emission rate drops off as
// the surface points move away from the centers
// of the voxels in which they lie.
//
// Notes:
//
// To associate an owner object with an emitter, use the
// addDynamic MEL command, e.g. "addDynamic simpleFluidEmitter1 pPlane1".
//
//==============================================================================
{
// get relevant world matrices
//
MMatrix fluidInverseWorldMatrix = fluidWorldMatrix.inverse();
// get emission rates for density, fuel, heat, and emission color
//
double densityEmit = fluidDensityEmission( block );
double fuelEmit = fluidFuelEmission( block );
double heatEmit = fluidHeatEmission( block );
bool doEmitColor = fluidEmitColor( block );
MColor emitColor = fluidColor( block );
// rate modulation based on frame time, user value conversion factor, and
// standard emitter "rate" value (not actually exposed in most fluid
// emitters, but there anyway).
//
double theRate = getRate(block) * dt * conversion;
// get voxel dimensions and sizes (object space)
//
double size[3];
unsigned int res[3];
fluid.getDimensions( size[0], size[1], size[2] );
fluid.getResolution( res[0], res[1], res[2] );
// voxel sizes
double dx = size[0] / res[0];
double dy = size[1] / res[1];
double dz = size[2] / res[2];
// voxel centers
double Ox = -size[0]/2;
double Oy = -size[1]/2;
double Oz = -size[2]/2;
// get the "swept geometry" data for the emitter surface. This structure
// tracks the motion of each emitter triangle over the time interval
// for this simulation step. We just use positions on the emitter
// surface at the end of the time step to do the emission.
//
MDataHandle sweptHandle = block.inputValue( mSweptGeometry );
MObject sweptData = sweptHandle.data();
MFnDynSweptGeometryData fnSweptData( sweptData );
// for "non-jittered" sampling, just reset the random state for each
// triangle, which gives us a fixed set of samples all the time.
// Sure, they're still jittered, but they're all jittered the same,
// which makes them kinda uniform.
//
bool jitter = fluidJitter(block);
if( !jitter )
{
resetRandomState( plugIndex, block );
}
if( fnSweptData.triangleCount() > 0 )
{
// average voxel face area - use this as the canonical unit that
// receives the emission rate specified by the users. Scale the
// rate for other triangles accordingly.
//
double vfArea = pow(dx*dy*dz, 2.0/3.0);
// very rudimentary support for textured emission rate and
// textured emission color. We simply sample each texture once
// at the center of each emitter surface triangle. This will
// cause aliasing artifacts when these triangles are large.
//
MFnDependencyNode fnNode( thisMObject() );
MObject rateTextureAttr = fnNode.attribute( "textureRate" );
MObject colorTextureAttr = fnNode.attribute( "particleColor" );
bool texturedRate = hasValidEmission2dTexture( rateTextureAttr );
bool texturedColor = hasValidEmission2dTexture( colorTextureAttr );
// construct texture coordinates for each triangle center
//
MDoubleArray uCoords, vCoords;
if( texturedRate || texturedColor )
{
uCoords.setLength( fnSweptData.triangleCount() );
vCoords.setLength( fnSweptData.triangleCount() );
int t;
for( t = 0; t < fnSweptData.triangleCount(); t++ )
{
MDynSweptTriangle tri = fnSweptData.sweptTriangle( t );
MVector uv0 = tri.uvPoint(0);
MVector uv1 = tri.uvPoint(1);
MVector uv2 = tri.uvPoint(2);
MVector uvMid = (uv0+uv1+uv2)/3.0;
uCoords[t] = uvMid[0];
vCoords[t] = uvMid[1];
}
}
// evaluate textured rate and color values at the triangle centers
//
MDoubleArray texturedRateValues;
if( texturedRate )
{
texturedRateValues.setLength( uCoords.length() );
evalEmission2dTexture( rateTextureAttr, uCoords, vCoords, NULL, &texturedRateValues );
}
MVectorArray texturedColorValues;
if( texturedColor )
{
texturedColorValues.setLength( uCoords.length() );
evalEmission2dTexture( colorTextureAttr, uCoords, vCoords, &texturedColorValues, NULL );
}
for( int t = 0; t < fnSweptData.triangleCount(); t++ )
{
// calculate emission rate and color values for this triangle
//
double curTexturedRate = texturedRate ? texturedRateValues[t] : 1.0;
MColor curTexturedColor;
if( texturedColor )
{
MVector& curVec = texturedColorValues[t];
curTexturedColor.r = (float)curVec[0];
curTexturedColor.g = (float)curVec[1];
curTexturedColor.b = (float)curVec[2];
curTexturedColor.a = 1.0;
}
else
{
curTexturedColor = emitColor;
}
MDynSweptTriangle tri = fnSweptData.sweptTriangle( t );
MVector v0 = tri.vertex(0);
MVector v1 = tri.vertex(1);
MVector v2 = tri.vertex(2);
// compute number of samples for this triangle based on area,
// with large triangles receiving approximately 1 sample for
// each voxel that they intersect
//
double triArea = tri.area();
int numSamples = (int)(triArea / vfArea);
if( numSamples < 1 ) numSamples = 1;
// compute emission rate for the points on the triangle.
// Scale the canonical rate by the area ratio of this triangle
// to the average voxel size, then split it amongst all the samples.
//
double triRate = (theRate*(triArea/vfArea))/numSamples;
triRate *= curTexturedRate;
for( int j = 0; j < numSamples; j++ )
{
// generate a random point on the triangle,
// map it into fluid local space
//
double r1 = randgen();
double r2 = randgen();
if( r1 + r2 > 1 )
{
r1 = 1-r1;
r2 = 1-r2;
}
double r3 = 1 - (r1+r2);
MPoint randPoint = r1*v0 + r2*v1 + r3*v2;
randPoint *= fluidInverseWorldMatrix;
// figure out where the current point lies
//
int3 coord;
fluid.toGridIndex( randPoint, coord );
if( (coord[0]<0) || (coord[1]<0) || (coord[2]<0) ||
(coord[0]>=(int)res[0]) || (coord[1]>=(int)res[1]) || (coord[2]>=(int)res[2]) )
{
continue;
}
// do some falloff based on how far from the voxel center
// the current point lies
//
MPoint gridPoint;
gridPoint.x = Ox + (coord[0]+0.5)*dx;
gridPoint.y = Oy + (coord[1]+0.5)*dy;
gridPoint.z = Oz + (coord[2]+0.5)*dz;
MVector diff = gridPoint - randPoint;
double distSquared = diff * diff;
double distDrop = dropoff * distSquared;
double newVal = triRate * exp( -distDrop );
// emit into the voxel
//
if( newVal != 0 )
{
fluid.emitIntoArrays( (float) newVal, coord[0], coord[1], coord[2], (float)densityEmit, (float)heatEmit, (float)fuelEmit, doEmitColor, curTexturedColor );
}
}
}
}
}
MStatus initializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj, PLUGIN_COMPANY, "3.0", "Any");
status = plugin.registerNode( "simpleFluidEmitter", simpleFluidEmitter::id,
&simpleFluidEmitter::creator, &simpleFluidEmitter::initialize,
if (!status) {
status.perror("registerNode");
return status;
}
return status;
}
MStatus uninitializePlugin(MObject obj)
{
MStatus status;
MFnPlugin plugin(obj);
status = plugin.deregisterNode( simpleFluidEmitter::id );
if (!status) {
status.perror("deregisterNode");
return status;
}
return status;
}