- Main Page
- Related Pages
- Modules
- Classes
- Examples

# simpleFluidEmitter.cpp

    //-
    // ==========================================================================
    // Copyright 1995,2006,2008 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.
    // ==========================================================================
    //+

    #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>

    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;
        }
    }

    MStatus
    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;
        MFnFluid::ColorMethod colorMode;
        MFnFluid::FluidGradient grad;
        MFnFluid::FalloffMethod falloffMode;
        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;
    }

    #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;

        //  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;

        //  find the voxels that intersect the bounding box of the volume
        //  primitive associated with the emitter
        //
        MBoundingBox bbox;
        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();

        //  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,
                                MPxNode::kFluidEmitterNode );
        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;
    }