particleAttrNode.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.
// ==========================================================================
//+

//
//  Class:  particleAttrNode
//
//  Author: Lonnie Li
//
//  Description:
//
//      particleAttrNode is an example node that extends the MPxParticleAttributeMapperNode
//  type. These nodes allow us to define custom nodes to map per-particle attribute data
//  to a particle shape.
//
//      In this particular example, we have defined two operations:
//
//      - compute2DTexture()
//      - computeMesh()
//
//      compute2DTexture() replicates the internal behaviour of Maya's internal 'arrayMapper'
//  node at the API level. Given an input texture node and a U/V coord per particle input,
//  this node will evaluate the texture at the given coordinates and map the result back
//  to the outColorPP or outValuePP attributes. See the method description for compute2DTexture()
//  for details on how to setup a proper attribute mapping graph.
//
//      computeMesh() is a user-defined behaviour. It is called when the 'computeNode' attribute
//  is connected to a polygonal mesh. From there, given a particle count, it will map the
//  object space vertex positions of the mesh to a user-defined 'outPositions' vector attribute.
//  This 'outPositions' attribute can then be connected to a vector typed, per-particle attribute
//  on the shape to drive. In our particular example we drive the particle shape's 'rampPosition'
//  attribute. For further details, see the method description for computeMesh() to setup
//  this example.
//

#include <particleAttrNode.h>

#include <maya/MStatus.h>
#include <maya/MObject.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MPlug.h>
#include <maya/MVector.h>
#include <maya/MDoubleArray.h>
#include <maya/MVectorArray.h>
#include <maya/MPlugArray.h>
#include <maya/MPointArray.h>

#include <maya/MFnDoubleArrayData.h>
#include <maya/MFnVectorArrayData.h>
#include <maya/MFnMesh.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>

#include <maya/MDynamicsUtil.h>

MTypeId particleAttrNode::id = 0x81036;
MObject particleAttrNode::outPositionPP;
MObject particleAttrNode::particleCount;

particleAttrNode::particleAttrNode()
{}

particleAttrNode::~particleAttrNode()
{}

void* particleAttrNode::creator()
{
    return new particleAttrNode();
}

MStatus particleAttrNode::initialize()
{
    MStatus status = MS::kSuccess;

    // In addition to the standard arrayMapper attributes,
    // create an additional vector attribute.
    //
    MFnTypedAttribute typedAttrFn;
    MVectorArray defaultVectArray;
    MFnVectorArrayData vectArrayDataFn;
    vectArrayDataFn.create( defaultVectArray );
    typedAttrFn.create( "outPosition",
                        "opos",
                        MFnData::kVectorArray,
                        vectArrayDataFn.object(),
                        &status );
    // Transient output attribute. Not writable nor storable.
    //
    typedAttrFn.setWritable(false);
    typedAttrFn.setStorable(false);
    outPositionPP = typedAttrFn.object();
    addAttribute( outPositionPP );

    MFnNumericAttribute numAttrFn;
    numAttrFn.create( "particleCount", "pc", MFnNumericData::kInt );
    particleCount = numAttrFn.object();
    addAttribute( particleCount );

    attributeAffects( computeNode, outPositionPP );
    attributeAffects( particleCount, outPositionPP );

    return status;
}

MStatus particleAttrNode::compute( const MPlug& plug, MDataBlock& block )
{
    MStatus status = MS::kUnknownParameter;
    if( (plug.attribute() == outColorPP) || (plug.attribute() == outValuePP) )
    {
        status = compute2DTexture( plug, block );
    }
    else if( plug.attribute() == outPositionPP )
    {
        status = computeMesh( plug, block );
    }
    return status;
}

MStatus particleAttrNode::compute2DTexture( const MPlug& plug, MDataBlock& block )
//
//  Description:
//
//      This computes an outputValue/Color based on input U and V coordinates.
//  The code contained herein replicates the compute code of the internal Maya
//  'arrayMapper' node.
//
//  How to use:
//
//      1) Create a ramp
//      2) Create a particleAttr node
//      3) Create an emitter with a particleShape
//      4) Create a per particle color attribute (rgbPP) on the particleShape via the AE.
//      5) connectAttr ramp1.outColor particleAttr1.computeNode
//      6) connectAttr particleShape1.age particleAttr1.vCoordPP
//      7) connectAttr particleAttr1.outColorPP particleShape1.rgbPP
//
{
    MStatus status = MS::kSuccess;

    //
    // We request the computeNodeColor attributes here to make sure that
    // they get marked clean as the arrayMapper's output attribute(s) get
    // computed.
    //
    /* MDataHandle hComputeNodeColor = */ block.inputValue( computeNodeColor );
    /* MDataHandle hComputeNodeColorR = */ block.inputValue( computeNodeColorR );
    /* MDataHandle hComputeNodeColorG = */ block.inputValue( computeNodeColorG );
    /* MDataHandle hComputeNodeColorB = */ block.inputValue( computeNodeColorB );

    bool doUcoord = false;
    bool doVcoord = false;
    bool doOutColor = ( plug.attribute() == outColorPP );
    bool doOutValue = ( plug.attribute() == outValuePP );

    // Get pointers to the source attributes: aUCoordPP, aVCoordPP.
    //
    MFnDoubleArrayData dataDoubleArrayFn;
    MObject uCoordD = block.inputValue( uCoordPP ).data();
    status = dataDoubleArrayFn.setObject( uCoordD );
    MDoubleArray uAry;
    if( status == MS::kSuccess )
    {
        uAry = dataDoubleArrayFn.array();
        if (uAry.length())
        {
            doUcoord = true;
        }
    }

    status = MS::kSuccess;
    MObject vCoordD = block.inputValue( vCoordPP ).data();
    status = dataDoubleArrayFn.setObject( vCoordD );
    MDoubleArray vAry;
    if( status == MS::kSuccess )
    {
        vAry = dataDoubleArrayFn.array();
        if (vAry.length())
        {
            doVcoord = true;
        }
    }

    // Get pointers to destination attributes: outColorPP, outValuePP.
    //
    status = MS::kSuccess;
    MFnVectorArrayData dataVectorArrayFn;
    MVectorArray outColorAry;
    if( doOutColor )
    {
        MObject colorD = block.outputValue( outColorPP ).data();
        status = dataVectorArrayFn.setObject( colorD );

        if( status == MS::kSuccess )
        {
            outColorAry = dataVectorArrayFn.array();
        }
    }

    status = MS::kSuccess;
    MDoubleArray outValueAry;
    if( doOutValue )
    {
        MObject valueD = block.outputValue( outValuePP ).data();
        status = dataDoubleArrayFn.setObject( valueD );

        if( status == MS::kSuccess )
        {
            outValueAry = dataDoubleArrayFn.array();
        }
    }

    // Setup loop counter.
    //
    unsigned int uCount = ( doUcoord ? uAry.length() : 0 );
    unsigned int vCount = ( doVcoord ? vAry.length() : 0 );
    unsigned int count  = ( (uCount) > (vCount) ? (uCount) : (vCount) );

    // Resize the output arrays to match the max size
    // of the input arrays.
    //
    if( doOutColor ) outColorAry.setLength( count );
    if( doOutValue ) outValueAry.setLength( count );

    // Check for a texture node
    //
    bool hasTextureNode =
        MDynamicsUtil::hasValidDynamics2dTexture( thisMObject(),
                                                computeNode );

    // Call the texture node to compute color values for
    // the input u,v coordinate arrays.
    //
    if( hasTextureNode )
    {
        MDynamicsUtil::evalDynamics2dTexture( thisMObject(),
                                            computeNode,
                                            uAry,
                                            vAry,
                                            &outColorAry,
                                            &outValueAry );

        // Scale the output value and/or color to the min/max range.
        //
        const double& minValue = block.inputValue( outMinValue ).asDouble();
        const double& maxValue = block.inputValue( outMaxValue ).asDouble();

        if( (minValue != 0.0) || (maxValue != 1.0) )
        {
            double r = maxValue - minValue;
            if( doOutValue )
            {
                for( unsigned int i = 0; i < count; i++ )
                {
                    outValueAry[i] = (r * outValueAry[i]) + minValue;
                }
            }

            if( doOutColor )
            {
                MVector minVector( minValue, minValue, minValue );
                for( unsigned int i = 0; i < count; i++ )
                {
                    outColorAry[i] = (r * outColorAry[i]) + minVector;
                }
            }
        }
    }
    else
    {
        // Not connected to texture node, so simply set output
        // arrays to default values.
        //
        for( unsigned int i = 0; i < count; i++ )
        {
            MVector clr( 0.0, 0.0, 0.0 );
            if( doOutColor )
            {
                outColorAry[i] = clr;
            }
            if( doOutValue )
            {
                outValueAry[i] = clr.x;
            }
        }
    }
    dataVectorArrayFn.set( outColorAry );
    dataDoubleArrayFn.set( outValueAry );
    return MS::kSuccess;
}

MStatus particleAttrNode::computeMesh( const MPlug& plug, MDataBlock& block )
//
//  Description:
//
//      To illustrate a custom use of the arrayMapper node, we'll define this
//  alternative compute that will map particles to mesh vertex positions. If
//  array lengths do not match, we'll wrap around the arrays.
//
//  How to use:
//
//      1) Create a poly surface.
//      2) connectAttr polyShape1.outMesh particleAttr1.computeNode
//      3) connectAttr particleShape1.count particleAttr1.particleCount
//      4) connectAttr particleAttr1.outPosition particleShape1.rampPosition (*)
//
//      * You may choose to drive any vector based per particle attribute.
//        rampPosition was selected here as an example.
//
{
    MStatus status = MS::kSuccess;

    // Verify that computeNode has a mesh connected to computeNode
    //
    MPlug compPlug( thisMObject(), computeNode );
    MPlugArray conns;
    compPlug.connectedTo( conns, true, false, &status );
    if( conns.length() <= 0 )
    {
        return MS::kFailure;
    }
    MPlug conn = conns[0];
    MObject compNode = conn.node();
    MFnMesh meshFn( compNode, &status );
    if( status != MS::kSuccess )
    {
        return MS::kFailure;
    }

    // Retrieve the mesh vertices
    //
    MPointArray points;
    meshFn.getPoints( points );
    unsigned int nPoints = points.length();

    // Retrieve the current particle count.
    //
    // NOTE: Due to the order in which the particle system requests
    // various pieces of data, some attributes are requested prior
    // to the actual emission of particles (eg. rampPosition), whereas
    // other attributes are requested after particles have been emitted.
    //
    // If the driven PP attribute on the particleShape is requested prior
    // to the emission of particles, this compute() method will be called
    // before any particles have been emitted. As a result, the effect
    // will be lagged by one frame.
    //
    unsigned int nParticles = 0;
    int nSignedPart = block.inputValue( particleCount ).asInt();
    if( nSignedPart > 0 )
    {
        nParticles = nSignedPart;
    }

    // Get pointer to destination attribute: outPositionPP
    //
    MFnVectorArrayData dataVectorArrayFn;
    MVectorArray outPosArray;
    MObject posD = block.outputValue( outPositionPP ).data();
    //const char* typeStr = posD.apiTypeStr();
    status = dataVectorArrayFn.setObject( posD );
    if( status == MS::kSuccess )
    {
        outPosArray = dataVectorArrayFn.array();
    }

    outPosArray.setLength( nParticles );
    for( unsigned int i = 0; i < nParticles; i++ )
    {
        unsigned int index = i;
        if( nParticles > nPoints )
        {
            index = i % nPoints;
        }
        MPoint point = points[index];
        MVector pos( point.x, point.y, point.z );
        outPosArray[i] = pos;
    }
    dataVectorArrayFn.set( outPosArray );
    return MS::kSuccess;
}