SOP/SOP_SParticle.C

/*
 * Copyright (c) 2013
 *      Side Effects Software Inc.  All rights reserved.
 *
 * Redistribution and use of Houdini Development Kit samples in source and
 * binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. The name of Side Effects Software may not be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------
 * Simple Particle demonstration code.  Includes sample collision detection...
 */

#include <GB/GB_AttributeRef.h>
#include <UT/UT_DSOVersion.h>

#include <UT/UT_Math.h>
#include <UT/UT_Interrupt.h>
#include <UT/UT_Vector3.h>
#include <UT/UT_Vector4.h>

#include <GEO/GEO_PrimPart.h>

#include <GU/GU_Detail.h>
#include <GU/GU_RayIntersect.h>

#include <PRM/PRM_Include.h>

#include <OP/OP_Director.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>

#include "SOP_SParticle.h"

using namespace HDK_Sample;

void
newSopOperator(OP_OperatorTable *table)
{
     table->addOperator(new OP_Operator("hdk_sparticle",
                                        "Simple Particle",
                                         SOP_SParticle::myConstructor,
                                         SOP_SParticle::myTemplateList,
                                         1,     // Min required sources
                                         2,     // Maximum sources
                                         0));
}

// The names here have to match the inline evaluation functions
static PRM_Name        names[] = {
    PRM_Name("reset",   "Reset Frame"),
    PRM_Name("birth",   "Birth Rate"),
    PRM_Name("force",   "Force"),
};

static PRM_Default      birthRate(10);

PRM_Template
SOP_SParticle::myTemplateList[] = {
    PRM_Template(PRM_INT,       1, &names[0], PRMoneDefaults),
    PRM_Template(PRM_INT_J,     1, &names[1], &birthRate),
    PRM_Template(PRM_XYZ_J,     3, &names[2]),
    PRM_Template(),
};

int *
SOP_SParticle::myOffsets = 0;

OP_Node *
SOP_SParticle::myConstructor(OP_Network *net, const char *name, OP_Operator *op)
{
    return new SOP_SParticle(net, name, op);
}

SOP_SParticle::SOP_SParticle(OP_Network *net, const char *name, OP_Operator *op)
        : SOP_Node(net, name, op)
{
    // Make sure that our offsets are allocated.  Here we allow up to 32
    // parameters, no harm in over allocating.  The definition for this
    // function is in OP/OP_Parameters.h
    if (!myOffsets)
        myOffsets = allocIndirect(32);

    // Now, flag that nothing has been built yet...
    GBclearAttributeRef(myVelocity);
}

SOP_SParticle::~SOP_SParticle() {}

unsigned
SOP_SParticle::disableParms()
{
    return 0;
}

void
SOP_SParticle::birthParticle()
{
    const GEO_Point     *src;
    GEO_Point           *ppt;
    GEO_ParticleVertex  *pvtx;
    float               *life;

    src = 0;
    pvtx = mySystem->giveBirth();
    if (mySource)
    {
        if (mySourceNum >= mySource->points().entries())
            mySourceNum = 0;
        if (mySource->points().entries() > 0)   // No points in the input
            src = mySource->points()(mySourceNum);
        mySourceNum++;          // Move on to the next source point...
    }
    if (src)
    {
        ppt = pvtx->getPt();
        ppt->getPos() = src->getPos();
        if (GBisAttributeRefValid(mySourceVel))
        {
            ppt->setValue<UT_Vector3>(myVelocity,
                        src->getValue<UT_Vector3>(mySourceVel));
        }
        else
            ppt->setValue<UT_Vector3>(myVelocity, UT_Vector3(0, 0, 0));
    }
    else
    {
        ppt = pvtx->getPt();
        ppt->getPos().assign(drand48()-.5, drand48()-.5, drand48()-.5, 1);
        ppt->setValue<UT_Vector3>(myVelocity, UT_Vector3(0, 0, 0));
    }
    // First index of the life variable represents how long the particle has
    // been alive (set to 0).
    ppt->setValue<float>(myLife, 0, 0);
    // The second index of the life variable represents how long the particle
    // will live (in frames)
    ppt->setValue<float>(myLife, 30+30*drand48(), 1);
}

int
SOP_SParticle::moveParticle(GEO_ParticleVertex *pvtx, const UT_Vector3 &force)
{
    float       life, death;
    float       tinc;
    UT_Vector3  vel, dir;

    life = pvtx->getPt()->getValue<float>(myLife, 0);
    death = pvtx->getPt()->getValue<float>(myLife, 1);
    life += 1;
    pvtx->getPt()->setValue<float>(myLife, life, 0);    // Store back in point
    if (life >= death)
        return 0;               // The particle should die!

    tinc = 1./30.;              // Hardwire 1/30 of a second time inc...

    // Adjust the velocity (based on the force) - of course, the multiplies
    // can be pulled out of the loop...
    vel = pvtx->getPt()->getValue<UT_Vector3>(myVelocity);
    vel.x() += tinc*force.x();
    vel.y() += tinc*force.y();
    vel.z() += tinc*force.z();
    pvtx->getPt()->setValue<UT_Vector3>(myVelocity, vel);

    // Now adjust the point positions

    if (myCollision)
    {
        dir = vel * tinc;

        // here, we only allow hits within the length of the velocity vector
        GU_RayInfo      info(dir.normalize());
        UT_Vector3      start;

        start = pvtx->getPos();
        if (myCollision->sendRay(start, dir, info) > 0)
            return 0;   // We hit something, so kill the particle
    }

    pvtx->getPos().x() += tinc*vel.x();
    pvtx->getPos().y() += tinc*vel.y();
    pvtx->getPos().z() += tinc*vel.z();

    return 1;
}

void
SOP_SParticle::timeStep(float now)
{
    int                  i, nbirth;
    GEO_ParticleVertex  *pvtx, *next = 0;
    UT_Vector3           force;

    force.assign(FX(now), FY(now), FZ(now));
    nbirth = BIRTH(now);

    if (error() >= UT_ERROR_ABORT)
        return;

    for (i = nbirth; i >= 0; i--)
        birthParticle();

    for (pvtx = mySystem->iterateInit(); pvtx; pvtx = next)
    {
        next = mySystem->iterateFastNext(pvtx);
        if (!moveParticle(pvtx, force))
            mySystem->deadParticle(pvtx);
    }
}

void
SOP_SParticle::initSystem()
{
    if (!gdp) gdp = new GU_Detail;

    // Check to see if we really need to reset everything
    if (gdp->points().entries() > 0 || GBisAttributeRefInvalid(myVelocity))
    {
        mySourceNum = 0;
        gdp->clearAndDestroy();
        mySystem = (GEO_PrimParticle *)gdp->appendPrimitive(GEOPRIMPART);
        mySystem->clearAndDestroy();

        // A vector attribute will be transformed correctly by following
        //      SOPs.  Use float types for stuff like color...
        myVelocity = gdp->addPointAttrib("v", sizeof(UT_Vector3),
                                         GB_ATTRIB_VECTOR, 0);
        myLife = gdp->addPointAttrib("life", sizeof(float)*2,
                                         GB_ATTRIB_FLOAT, 0);
    }
}

OP_ERROR
SOP_SParticle::cookMySop(OP_Context &context)
{
    double               reset, currframe;
    CH_Manager          *chman;         // Channel manager has time info for us
    const GU_Detail     *collision;

    // Before we do anything, we must lock our inputs.  Before returning,
    //  we have to make sure that the inputs get unlocked.
    if (lockInputs(context) >= UT_ERROR_ABORT)
        return error();

    // Now, indicate that we are time dependent (i.e. have to cook every
    //  time the current frame changes).
    OP_Node::flags().timeDep = 1;

    chman = OPgetDirector()->getChannelManager();

    // This is the frame that we're cooking at...
    currframe = chman->getSample(context.getTime());
    reset = RESET();            // Find our reset frame...

    if (currframe <= reset)
    {
        myLastCookTime = reset;
        initSystem();
    }
    else
    {
        // Set up the collision detection object
        collision = inputGeo(1, context);
        if (collision)
        {
            myCollision = new GU_RayIntersect;
            myCollision->init(collision);
        }
        else myCollision = 0;

        // Set up our source information...
        mySource = inputGeo(0, context);
        if (mySource)
        {
            mySourceVel = mySource->findPointAttrib("v", sizeof(UT_Vector3),
                                    GB_ATTRIB_VECTOR);

            // If there's no velocity, pick up the velocity from the normal
            if (GBisAttributeRefInvalid(mySourceVel))
                mySourceVel = mySource->findPointAttrib("N", sizeof(UT_Vector3),
                                    GB_ATTRIB_VECTOR);
        }

        // This is where we notify our handles (if any) if the inputs have
        // changed.  Since there is no input group, "0" is passed as the
        // fourth argument.
        checkInputChanged(0, -1, myDetailGroupPair, (GU_Detail *)mySource, 0);

        // Now cook the geometry up to our current time
        // Here, we could actually re-cook the source input to get a moving
        //      source...  But this is just an example ;-)

        currframe += 0.05;      // Add a bit to avoid floating point error
        while (myLastCookTime < currframe)
        {
            // Here we have to convert our frame number to the actual time.
            timeStep(chman->getTime(myLastCookTime));
            myLastCookTime += 1;
        }

        if (myCollision) delete myCollision;

        // Highlight all the birthed particles.  This call also clears the
        // selection at the beginning.  The argument "GU_SPoint" indicates
        // that we want a point selection.
        select(GU_SPoint);
    }

    unlockInputs();
    return error();
}

const char *
SOP_SParticle::inputLabel(unsigned inum) const
{
    switch (inum)
    {
        case 0: return "Particle Source Geometry";
        case 1: return "Collision Object";
    }
    return "Unknown source";
}

Generated on Mon Jan 28 00:45:45 2013 for HDK by  doxygen 1.5.9