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

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