SOP/SOP_BrushHairLen.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.
 *
 *----------------------------------------------------------------------------
 * This SOP demonstrates how to override the BrushBase SOP to paint custom
 * attributes, and then use those attributes in the resulting geometry.
 */

#include <UT/UT_DSOVersion.h>
#include <GU/GU_Detail.h>
#include <GU/GU_PrimPoly.h>
#include <PRM/PRM_Include.h>
#include <PRM/PRM_ChoiceList.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>

#include <SOP/SOP_Guide.h>
#include "SOP_BrushHairLen.h"

using namespace HDK_Sample;

#define PRM_MENU_CHOICES        (PRM_ChoiceListType)(PRM_CHOICELIST_EXCLUSIVE |\
                                                     PRM_CHOICELIST_REPLACE)

// Define the new sop operator...
void
newSopOperator(OP_OperatorTable *table)
{
    table->addOperator(new OP_Operator("proto_brushhairlen",
                                        "Brush Hair Length",
                                         SOP_BrushHairLen::myConstructor,
                                         SOP_BrushHairLen::myTemplateList,
                                         1,
                                         1));
}

static PRM_Name sop_names[] = {
    PRM_Name("group", "Group"),
    PRM_Name("op", "Operation"),
    PRM_Name("flen", "FL"),
    PRM_Name("blen", "BL"),
    PRM_Name("radius", "Radius"),
    PRM_Name("uvradius", "UV Radius"),
    PRM_Name(0)
};

static PRM_Name         sopOpMenuNames[] = {
    PRM_Name("paint",   "Paint"),
    PRM_Name("eyedrop", "Eye Dropper"),
    PRM_Name("smoothattrib", "Smooth"),
    PRM_Name("callback", "Callback"),
    PRM_Name("erase",   "Erase Changes"),
    PRM_Name(0)
};
static PRM_ChoiceList   sopOpMenu(PRM_MENU_CHOICES,     sopOpMenuNames);

PRM_Template
SOP_BrushHairLen::myTemplateList[]=
{
    // Primitive group to allow painting on...
    PRM_Template(PRM_STRING,    1, &sop_names[0], 0, &SOP_Node::primGroupMenu),

    // This is the choice of operations...
    PRM_Template((PRM_Type) PRM_ORD,
                    PRM_Template::PRM_EXPORT_MAX,
                    1, &sop_names[1], 0, &sopOpMenu),
    // Foreground hair length (LMB)
    PRM_Template(PRM_FLT_J, PRM_Template::PRM_EXPORT_TBX,
                    1, &sop_names[2], PRMoneDefaults),
    // Background hair length (MMB)
    PRM_Template(PRM_FLT_J, PRM_Template::PRM_EXPORT_TBX,
                    1, &sop_names[3], PRMzeroDefaults),
    // Radius
    PRM_Template(PRM_FLT_J, PRM_Template::PRM_EXPORT_TBX,
                    1, &sop_names[4], PRMpointOneDefaults),
    // UV Radius
    PRM_Template(PRM_FLT_J, PRM_Template::PRM_EXPORT_TBX,
                    1, &sop_names[5], PRMpointOneDefaults),
    PRM_Template()
};

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


SOP_BrushHairLen::SOP_BrushHairLen(OP_Network *net, const char *name, OP_Operator *entry)
        : SOP_BrushBase(net, name, entry) 
{ 
    // We initialize our values to safe starting values.  The most important
    // is setting myEvent to SOP_BRUSHSTROKE_NOP as that will cause
    // the processBrushOp to ignore most everything else.
    myRayOrient = 0.0f;
    myRayHit = 0.0f;
    myRayHitU = myRayHitV = 0.0f;
    myRayHitPressure = 1.0f;
    myPrimHit = -1;
    myEvent = SOP_BRUSHSTROKE_NOP;
    myUseFore = true;
    myStrokeChanged = false;
}

SOP_BrushHairLen::~SOP_BrushHairLen() 
{
}


unsigned
SOP_BrushHairLen::disableParms()
{
    int         changed = 0;

    return changed;
}

SOP_BrushOp
SOP_BrushHairLen::OP()
{
    switch (evalInt("op", 0, 0))
    {
        case 0: return SOP_BRUSHOP_PAINT;
        case 1: return SOP_BRUSHOP_EYEDROP;
        case 2: return SOP_BRUSHOP_SMOOTHATTRIB;
        case 3: return SOP_BRUSHOP_CALLBACK;
        case 4: default: return SOP_BRUSHOP_ERASE;
    }
}

void
SOP_BrushHairLen::setBrushOp(SOP_BrushOp op)
{
    int iop;
    switch (op)
    {
        case SOP_BRUSHOP_EYEDROP:       iop = 1; break;
        case SOP_BRUSHOP_SMOOTHATTRIB:  iop = 2; break;
        case SOP_BRUSHOP_CALLBACK:      iop = 3; break;
        case SOP_BRUSHOP_ERASE:         iop = 4; break;
        case SOP_BRUSHOP_PAINT:
        default:                        iop = 0; break;
    }
    setInt("op", 0, 0, iop);
}

void
SOP_BrushHairLen::doErase()
{
    // We want to do attribute erase, as we are an attribute style brush.
    myBrush.eraseAttributes(myPermanentDelta, myCurrentDelta);
    if (myBrush.doVisualize())
        myBrush.applyVisualizeStencil(gdp);
}

bool
SOP_BrushHairLen::hasStyleChanged(float t)
{
    // When do we have to apply a new sculpting operation:
    return      isParmDirty(1, t) ||
                isParmDirty(2, t);
}

const GU_Detail *
SOP_BrushHairLen::getIsectGdp()
{
    SOP_Node            *sop;
    OP_Context           context(0.0f);

    // We always want our first input...  We change our own topology,
    // so it would be a bad thing to use ourselves.
    sop = CAST_SOPNODE(getInput(0));

    return sop->getCookedGeo(context);
}

OP_ERROR
SOP_BrushHairLen::cookMySop(OP_Context &context)
{
    double                       t = context.getTime();
    GB_AttributeRef              aoff;
    int                          i;
    GB_Attribute                *attrib;
    static float                 zero = 0.0;            // Initializer

    if (lockInputs(context) >= UT_ERROR_ABORT) return error();

    // There are two different methods here.  BUILD_HAIR will create
    // hair geometry in the gdp.  This requires it to do a duplicateSource
    // and rebuild everything every frame.
    // The none BUILD_HAIR method merely updates the hairlen point attribute
    // One could then use the guide geometry to display the hair.  This is
    // more efficient as the brush code can avoid duplicating the incoming
    // geometry, but just rollback its changes.  This method should be
    // used if you are not doing any processing of the gdp post-processBrushOp.
#define BUILD_HAIR
#ifdef BUILD_HAIR
    // Duplicate the incoming source, overwriting everything as we will
    // be messing with geometry.
    duplicateSource(0, context);

    // Find the hairlen attribute...
    attrib = gdp->pointAttribs().find("hairlen", GB_ATTRIB_FLOAT);

    // If it doesn't exist, create it.
    if (!attrib)
    {
        // Not present, so create the point attribute:
        gdp->addPointAttrib("hairlen", sizeof(float), GB_ATTRIB_FLOAT, &zero);
        attrib = gdp->pointAttribs().find("hairlen", GB_ATTRIB_FLOAT);
    }

    // Having created the attribute, we also create a localvariable
    // HAIRLEN which will map to it:
    gdp->addVariableName("hairlen", "HAIRLEN");

    // Default to -2 to trigger a rebuild if necessary in the callback.
    myHairlenFound = false;
    myTime = t;

    // Now, process any of the brush changes that may have occurred since
    // our last cook...
    // We inform it that we have changed both the input & group, as it
    // should not rely on them as we have rebuilt them.
    processBrushOp(context, true, true);

    // We now clear out our myStrokeChanged as it is no longer changed...
    myStrokeChanged = false;
    
    aoff = gdp->pointAttribs().getOffset("hairlen", GB_ATTRIB_FLOAT);
    if (aoff.isInvalid())
    {
        // Odd... We just created this attribute!
        UT_ASSERT(!"Newly created hairlen disappeared!");
        unlockInputs();
        return error();
    }

    // For each point, add a hair of the proper length..
    int         n;

    n = gdp->points().entries();
    for (i = 0; i < n; i++)
    {
        GEO_Point               *pt, *lpt;
        GU_PrimPoly             *poly;

        pt = gdp->points()(i);
        lpt = gdp->appendPoint();
        lpt->getPos() = pt->getPos();
        // Add hair length to y value.
        lpt->getPos().y() += pt->getValue<float>(aoff);

        // Create a polygon to loft them..
        poly = GU_PrimPoly::build(gdp, 2, GU_POLY_OPEN, 0);
        poly->setVertex(0, pt);
        poly->setVertex(1, lpt);
    }
#else
    bool                        changed_input = false;
    bool                        changed_group = false;

    changed_input = checkChangedSource(0, context);
    changed_group = isParmDirty(SOP_GDT_GRP_IDX, context.getTime());

    if (changed_input)
        duplicateChangedSource(0, context, 0, true);

    // Find the hairlen attribute...
    attrib = gdp->pointAttribs().find("hairlen", GB_ATTRIB_FLOAT);

    // If it doesn't exist, create it.
    if (!attrib)
    {
        // Not present, so create the point attribute:
        gdp->addPointAttrib("hairlen", sizeof(float), GB_ATTRIB_FLOAT, &zero);
        attrib = gdp->pointAttribs().find("hairlen", GB_ATTRIB_FLOAT);
    }

    // Having created the attribute, we also create a localvariable
    // HAIRLEN which will map to it:
    gdp->addVariableName("hairlen", "HAIRLEN");

    processBrushOp(context, changed_input, changed_group);
#endif

    unlockInputs();
    return error();
}

void
SOP_BrushHairLen::brushOpCallback(
                    GEO_Point *pt, 
                    const UT_PtrArray<const GEO_Point *> * /*ptneighbour*/,
                    GEO_Vertex * /*vtx*/,
                    const UT_PtrArray<const GEO_Vertex *> * /*vtxneighbour*/,
                    GEO_Primitive * /*vtx_prim*/,
                    int /*prim_vtx_idx*/,
                    float alpha,
                    GEO_Delta *delta)
{
    // Unused here is the ptneighbour and vtxneighbour.  These are a list
    // of all the points or vertices connected to this point by at least
    // one edge.  Each point will show up only once in the list, regardless
    // of the number of times it is connected.
    
    // We first determine the attribute index if not already known.
    // This is called once per point, so we want to minimize the attribute
    // lookups, but on the other hand, we don't want to cache to early
    // as if new attributes are created it would be invalid.
    if (!myHairlenFound)
    {
        myHairlenFound = true;
        myHairlenOffset = gdp->findPointAttrib("hairlen", sizeof(float), GB_ATTRIB_FLOAT);
    }

    // If no hairlen, do nothing.
    if (myHairlenOffset.isInvalid())
        return;

    // Here we actually change all our attributes.  Note that we should:
    // 1) NOT create any new attributes in here, as it will confuse the GDT.
    // 2) Open & close the GDT for writing using beginPointAttributeChange
    //    or beginPointPositionChanged followed by endChange().
    // 3) Use the alpha as a blend value for our effect.
    // 4) One of point or vertex will be non-null, depending on if this
    //    is a vertex paint or point paint.  Currently only point paint
    //    is supported.

    float               newhair = (myUseFore ? FGR(myTime) : BGR(myTime));
    float               oldhair;

    if (pt)
    {
        if (delta) delta->beginPointAttributeChange(*pt);

        // Do all our attribute tweaking here...
        oldhair = pt->getValue<float>(myHairlenOffset);

        // simple alpha blending...  Alpha of 1 means newhair, 0 means oldhair.
        pt->setValue<float>(myHairlenOffset, SYSlerp(oldhair, newhair, alpha));

        if (delta) delta->endChange();
    }
}

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