flameShader/flameShader.cpp
 
 
 
flameShader/flameShader.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 <math.h>
#include <stdlib.h>

#include <maya/MPxNode.h>
#include <maya/MIOStream.h>
#include <maya/MString.h>
#include <maya/MTypeId.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h> 
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnMatrixAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFloatPoint.h>
#include <maya/MFnPlugin.h>

// Local functions
float Noise(float, float, float);
void  Noise_init();
static float Omega(int i, int j, int k, float t[3]);
static float omega(float);
static double turbulence(double u,double v,double w,int octaves);

#define PI                  3.14159265358979323846

#ifdef FLOOR
#undef FLOOR
#endif
#define FLOOR(x)            ((int)floorf(x))

#define TABLELEN            512
#define TLD2                256    // TABLELEN 

// Local variables
static int                  Phi[TABLELEN];
static char                 fPhi[TABLELEN];
static float                G[TABLELEN][3];


class Flame3D : public MPxNode
{
        public:
                    Flame3D();
    virtual         ~Flame3D();

    virtual MStatus compute( const MPlug&, MDataBlock& );
        virtual void    postConstructor();

    static  void *  creator();
    static  MStatus initialize();

        //  Id tag for use with binary file format
    static  MTypeId id;

        private:

        // Input attributes
    static MObject  aColorBase;
    static MObject  aColorFlame;
    static MObject  aRiseSpeed;
    static MObject  aFlickerSpeed;
    static MObject  aFlickerDeform;
    static MObject  aFlamePow;
    static MObject  aFlameFrame;
    static MObject  aRiseAxis;
    static MObject  aPlaceMat;
    static MObject  aPointWorld;

        // Output attributes
    static MObject  aOutAlpha;
    static MObject  aOutColor;
};

// Static data
MTypeId Flame3D::id(0x81016);

// Attributes
MObject  Flame3D::aColorBase;
MObject  Flame3D::aColorFlame;
MObject  Flame3D::aRiseSpeed;
MObject  Flame3D::aFlickerSpeed;
MObject  Flame3D::aFlickerDeform;
MObject  Flame3D::aFlamePow;
MObject  Flame3D::aFlameFrame;
MObject  Flame3D::aRiseAxis;
MObject  Flame3D::aPointWorld;
MObject  Flame3D::aPlaceMat;

MObject  Flame3D::aOutAlpha;
MObject  Flame3D::aOutColor;

#define MAKE_INPUT(attr)        \
    CHECK_MSTATUS(attr.setKeyable(true));               \
        CHECK_MSTATUS(attr.setStorable(true));          \
    CHECK_MSTATUS(attr.setReadable(true));              \
        CHECK_MSTATUS(attr.setWritable(true));

#define MAKE_OUTPUT(attr)       \
    CHECK_MSTATUS(attr.setKeyable(false));              \
        CHECK_MSTATUS(attr.setStorable(false));         \
    CHECK_MSTATUS(attr.setReadable(true));              \
        CHECK_MSTATUS(attr.setWritable(false));

//
// DESCRIPTION:
void Flame3D::postConstructor( )
{
        setMPSafe(true);
}

//
// DESCRIPTION:
Flame3D::Flame3D()
{
}

//
// DESCRIPTION:
Flame3D::~Flame3D()
{
}

//
// DESCRIPTION:
void * Flame3D::creator()
{
    return new Flame3D();
}

//
// DESCRIPTION:
MStatus Flame3D::initialize()
{
    MFnMatrixAttribute mAttr;
    MFnNumericAttribute nAttr; 

        // Create input attributes
    aRiseSpeed = nAttr.create( "Rise", "rs", MFnNumericData::kFloat);
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(0.1f));
    CHECK_MSTATUS(nAttr.setMin(0.0f));
    CHECK_MSTATUS(nAttr.setMax(1.0f));

    aFlickerSpeed = nAttr.create( "Speed", "s", MFnNumericData::kFloat);
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(0.1f));
    CHECK_MSTATUS(nAttr.setMin(0.0f));
    CHECK_MSTATUS(nAttr.setMax(1.0f));

    aFlickerDeform = nAttr.create( "Flicker", "f", MFnNumericData::kFloat);
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(0.5f));
    CHECK_MSTATUS(nAttr.setMin(0.0f));
    CHECK_MSTATUS(nAttr.setMax(1.0f));

    aFlamePow = nAttr.create( "Power", "pow", MFnNumericData::kFloat);
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(1.0f));
    CHECK_MSTATUS(nAttr.setMin(0.0f));
    CHECK_MSTATUS(nAttr.setMax(1.0f));

    aFlameFrame = nAttr.create( "Frame", "fr", MFnNumericData::kFloat);
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(1.0f));
    CHECK_MSTATUS(nAttr.setMin(0.0f));
    CHECK_MSTATUS(nAttr.setMax(1000.0f));

    aRiseAxis = nAttr.createPoint( "Axis", "a");
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(0., 1., 0.));

    aColorBase = nAttr.createColor("ColorBase", "cg");
        MAKE_INPUT(nAttr);

    aColorFlame = nAttr.createColor("ColorFlame", "cb");
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setDefault(1., 1., 1.));
    
    aPlaceMat = mAttr.create("placementMatrix", "pm",
                                                         MFnMatrixAttribute::kFloat);
        MAKE_INPUT(mAttr);

        // Internal shading attribute, implicitely connected.
    aPointWorld = nAttr.createPoint("pointWorld", "pw");
        MAKE_INPUT(nAttr);
    CHECK_MSTATUS(nAttr.setHidden(true));

    // Create output attributes
    aOutColor  = nAttr.createColor("outColor",  "oc");
        MAKE_OUTPUT(nAttr);

    aOutAlpha = nAttr.create( "outAlpha", "oa", MFnNumericData::kFloat);
        MAKE_OUTPUT(nAttr);

        // Add the attributes here
    CHECK_MSTATUS(addAttribute(aColorBase));
    CHECK_MSTATUS(addAttribute(aColorFlame));
    CHECK_MSTATUS(addAttribute(aRiseSpeed));
    CHECK_MSTATUS(addAttribute(aFlickerSpeed));
    CHECK_MSTATUS(addAttribute(aFlickerDeform));
    CHECK_MSTATUS(addAttribute(aFlamePow));
    CHECK_MSTATUS(addAttribute(aFlameFrame));
    CHECK_MSTATUS(addAttribute(aRiseAxis));
    CHECK_MSTATUS(addAttribute(aPointWorld));
    CHECK_MSTATUS(addAttribute(aPlaceMat));

    CHECK_MSTATUS(addAttribute(aOutAlpha));
    CHECK_MSTATUS(addAttribute(aOutColor));

    // All input affect the output color and alpha
    CHECK_MSTATUS(attributeAffects (aColorBase, aOutColor));
    CHECK_MSTATUS(attributeAffects(aColorBase, aOutAlpha));

    CHECK_MSTATUS(attributeAffects (aColorFlame, aOutColor));
    CHECK_MSTATUS(attributeAffects(aColorFlame, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aRiseSpeed, aOutColor));
    CHECK_MSTATUS(attributeAffects(aRiseSpeed, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aFlickerSpeed, aOutColor));
    CHECK_MSTATUS(attributeAffects(aFlickerSpeed, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aFlickerDeform, aOutColor));
    CHECK_MSTATUS(attributeAffects(aFlickerDeform, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aFlamePow, aOutColor));
    CHECK_MSTATUS(attributeAffects(aFlamePow, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aFlameFrame, aOutColor));
    CHECK_MSTATUS(attributeAffects(aFlameFrame, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aRiseAxis, aOutColor));
    CHECK_MSTATUS(attributeAffects(aRiseAxis, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aPointWorld, aOutColor));
    CHECK_MSTATUS(attributeAffects (aPointWorld, aOutAlpha));

    CHECK_MSTATUS(attributeAffects(aPlaceMat, aOutColor));
    CHECK_MSTATUS(attributeAffects(aPlaceMat, aOutAlpha));

    return MS::kSuccess;
}


// DESCRIPTION:
// This function gets called by Maya to evaluate the texture.
//

MStatus Flame3D::compute(const MPlug& plug, MDataBlock& block) 
{ 
        // outColor or individial R, G, B channel, or alpha
    if((plug != aOutColor) && (plug.parent() != aOutColor) && 
           (plug != aOutAlpha))
       return MS::kUnknownParameter;

        float3 & worldPos = block.inputValue( aPointWorld).asFloat3();
    const MFloatMatrix& mat = block.inputValue(aPlaceMat).asFloatMatrix();
    const MFloatVector& cBase = block.inputValue(aColorBase).asFloatVector();
    const MFloatVector& cFlame=block.inputValue(aColorFlame).asFloatVector();
    const MFloatVector& axis = block.inputValue( aRiseAxis ).asFloatVector();
    const float rise_speed = block.inputValue( aRiseSpeed ).asFloat();
    const float flicker_speed = block.inputValue( aFlickerSpeed ).asFloat();
    const float dscale = block.inputValue( aFlickerDeform ).asFloat();
    const float frame = block.inputValue( aFlameFrame ).asFloat();
    const float power = block.inputValue( aFlamePow ).asFloat();
    
        MFloatPoint q(worldPos[0], worldPos[1], worldPos[2]);
        q *= mat;                                                               // Convert into solid space

        // Offset texture coord along the RiseAxis
    float rise_distance = -1.0f * rise_speed * frame;
    float u,v,w;
    u = q.x + ( rise_distance * axis[0]);
    v = q.y + ( rise_distance * axis[1]);
    w = q.z + ( rise_distance * axis[2]);

        // Generate a displaced point by moving along the
        // displacement vector (currently the 1,1,1 vector)
        // based on flicker speed.
    float dist = flicker_speed * frame;
    float au, av, aw;
    au = u + dist;
    av = v + dist;
    aw = w + dist;

        // Calculate 3 noise values
    float ascale = Noise(au,av,aw);
    // float bscale = Noise(au,-av,aw);
    // float cscale = Noise(-au,av,-aw);

        // add this noise as a vector to the texture coordinates
        // (since we are only calculating one value, the
        // displacement will be along the 1 1 1 vector ... this
        // displacement generates the "flicker" movement as the
        // value moves around the texture coordinate

    u += ascale * dscale;
    v += ascale * dscale;
    w += ascale * dscale;

        // Calculate a turbulence value for this point

    float scalar = (float) (turbulence(u, v, w, 3) + 0.5);

        // convert scalar into a point on the color curve

    if (power != 1) scalar = powf (scalar, power);

    MFloatVector resultColor;
    if (scalar >= 1)
                resultColor = cFlame;
    else if (scalar < 0) 
                resultColor = cBase;
        else
                resultColor = ((cFlame-cBase)*scalar) + cBase;

    MDataHandle outHandle = block.outputValue( aOutColor );
    MFloatVector & outColor = outHandle.asFloatVector();
    outColor = resultColor;
    outHandle.setClean();

    outHandle = block.outputValue(aOutAlpha);
    outHandle.asFloat() = scalar;
    outHandle.setClean();

    return MS::kSuccess;
}

MStatus initializePlugin( MObject obj )
{
    const MString UserClassify( "texture/3d" );

    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any" );
    CHECK_MSTATUS( plugin.registerNode( "flame", Flame3D::id, 
            Flame3D::creator, Flame3D::initialize,
            MPxNode::kDependNode, &UserClassify) );

    Noise_init();
    
    return MS::kSuccess;
}

// DESCRIPTION:
MStatus uninitializePlugin( MObject obj )
{
    MFnPlugin plugin( obj );
    CHECK_MSTATUS( plugin.deregisterNode( Flame3D::id ) );

    return MS::kSuccess;
}

//
//  REFERENCES:
//      Perlin, K. An Image Synthesizer, Computer Graphics, 
//      Vol. 19, No. 3, July 1985.
//
//      Perlin, K., Hoffert, E.M., Hypertexture, Computer Graphics, 
//      Vol. 23, No. 3, July 1989.
//

float Noise(float u, float v, float w)
{
    int         i;
    int         j;
    int         k;
    int         ul;
    int         vl;
    int         wl;
    float       ans;
    float       t[3];

    ans = 0.0;
    ul  = FLOOR(u);
    vl  = FLOOR(v);
    wl  = FLOOR(w);

    for(i = ul + 1; i >= ul; i--)
    {  
                t[0] = u - i;
                for(j = vl + 1; j >= vl; j--)
        {   
                        t[1] = v - j;
                        for(k = wl + 1; k >= wl; k--)
            {
                                t[2] = w - k;
                                ans += Omega(i, j, k, t);
            }
        }
    }

    return ans;
}

static float Omega(int i, int j, int k, float t[3])
{
    int ct;

    ct = Phi[((i + 
         Phi[((j + 
         Phi[(k%TLD2)+TLD2]) % TLD2) + TLD2]) % TLD2) + TLD2];

    return omega(t[0]) * omega(t[1]) * omega(t[2]) *
                ( G[ct][0]*t[0] + G[ct][1]*t[1] + G[ct][2]*t[2] );
}

static float omega(float t)
{
    t  = fabsf(t);
    return (t * (t * (t * (float)2.0 - (float)3.0))) + (float)1.0;
}

void Noise_init()
{
    int i;
    float u, v, w, s, len;
    static int first_time = 1;

    if (first_time)
        first_time = 0;
    else
        return;

    (void)srand48(0l);

    for(i = 0; i < TABLELEN; i++)
        fPhi[i] = 0;

    for(i = 0; i < TABLELEN; i++) {
                Phi[i] = lrand48() % TABLELEN;

        if (fPhi[Phi[i]])
            i--;
        else
            fPhi[Phi[i]] = 1;
    }
    for(i = 0; i < TABLELEN; i++) {
                u = (float) (2.0 * drand48() - 1.0);
                v = (float) (2.0 * drand48() - 1.0);
                w = (float) (2.0 * drand48() - 1.0);
                if((s = u*u + v*v + w*w) > 1.0) 
        {   i--;
            continue;
                }
        else
                        if (s == 0.0)
                        {   i--;
                        continue;
                        }

                len = 1.0f / sqrtf(s);
                G[i][0] = u * len;
                G[i][1] = v * len;
                G[i][2] = w * len;
    }
}

static double turbulence(double u,double v,double w,int octaves)
{
        double s,t;
        s = 1.0;
        t = 0.0;

        while (octaves--) {
                t += Noise((float)u, (float)v, (float)w)*s;
                s *= 0.5;
                u*=2.0; v*=2.0; w*=2.0;
        }
        return t;
}