SOP/SOP_CustomBrush.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.
 *
 *----------------------------------------------------------------------------
 */

#include "SOP_CustomBrush.h"

#include <OP/OP_OperatorTable.h>
#include <OP/OP_SaveFlags.h>
#include <PRM/PRM_Include.h>
#include <UT/UT_CPIO.h>
#include <UT/UT_DSOVersion.h>
#include <UT/UT_IStream.h>
#include <UT/UT_StreamFilter.h>
#include <UT/UT_Undo.h>
#include <UT/UT_UndoManager.h>

// undo for CustomBrush
namespace HDK_Sample {
class SOP_UndoCustomBrushData : public UT_Undo
{
public:
    SOP_UndoCustomBrushData(SOP_CustomBrush *sop,
                             int oldnumpts,
                             int numpts,
                             UT_RefArray<SOP_CustomBrushData> &olddata,
                             UT_RefArray<SOP_CustomBrushData> &data);

    virtual void undo();
    virtual void redo();

private:
    int mySopId;
    int myOldNumPts;
    int myNumPts;
    UT_RefArray<SOP_CustomBrushData> myOldData;
    UT_RefArray<SOP_CustomBrushData> myData;
};
} // End HDK_Sample namespace
using namespace HDK_Sample;

SOP_UndoCustomBrushData::SOP_UndoCustomBrushData(
    SOP_CustomBrush *sop,
    int oldnumpts,
    int numpts,
    UT_RefArray<SOP_CustomBrushData> &olddata,
    UT_RefArray<SOP_CustomBrushData> &data) :
        mySopId(sop->getUniqueId()),
        myOldNumPts(oldnumpts),
        myNumPts(numpts),
        myOldData(olddata),
        myData(data)
{
    // include the memory allocated by the UT_RefArrays to accurately compute
    // the total memory used by this undo
    addToMemoryUsage(olddata.getMemoryUsage() + data.getMemoryUsage());
}

void
SOP_UndoCustomBrushData::undo()
{
    SOP_CustomBrush *node = (SOP_CustomBrush *)OP_Node::lookupNode(mySopId);
    node->updateData(myOldNumPts, myOldData);
}

void
SOP_UndoCustomBrushData::redo()
{
    SOP_CustomBrush *node = (SOP_CustomBrush *)OP_Node::lookupNode(mySopId);
    node->updateData(myNumPts, myData);
}

void
newSopOperator(OP_OperatorTable *table)
{
    table->addOperator(new OP_Operator("proto_custombrush", "Custom Brush",
                                       SOP_CustomBrush::myConstructor,
                                       SOP_CustomBrush::myTemplateList,
                                       1, 1));
}

static PRM_Name sopOriginName("origin", "Origin");
static PRM_Name sopDirectionName("direction", "Direction");
static PRM_Name sopRadiusName("radius", "Radius");
static PRM_Name sopColorName("color", "Color");
static PRM_Name sopAlphaName("alpha", "Alpha");
static PRM_Name sopOperationName("operation", "Operation");
static PRM_Name sopEventName("event", "Event");
static PRM_Name sopClearAllName("clearall", "Clear All");

enum SOP_CustomBrushOperation
{
    SOP_CUSTOMBRUSHOPERATION_PAINT,
    SOP_CUSTOMBRUSHOPERATION_ERASE
};

static PRM_Name sopOperationMenuNames[] =
{
    PRM_Name("paint", "Paint"),
    PRM_Name("erase", "Erase"),
    PRM_Name(0)
};
static PRM_ChoiceList sopOperationMenu(PRM_CHOICELIST_SINGLE, sopOperationMenuNames);
static PRM_Default sopOperationDefault(SOP_CUSTOMBRUSHOPERATION_PAINT);

enum SOP_CustomBrushEvent
{
    SOP_CUSTOMBRUSHEVENT_BEGIN,
    SOP_CUSTOMBRUSHEVENT_ACTIVE,
    SOP_CUSTOMBRUSHEVENT_END,
    SOP_CUSTOMBRUSHEVENT_NOP
};

static PRM_Name sopEventMenuNames[] =
{
    PRM_Name("begin", "Begin Stroke"),
    PRM_Name("active", "Active Stroke"),
    PRM_Name("end", "End Stroke"),
    PRM_Name("nop", "No-op"),
    PRM_Name(0)
};
static PRM_ChoiceList sopEventMenu(PRM_CHOICELIST_SINGLE, sopEventMenuNames);
static PRM_Default sopEventDefault(SOP_CUSTOMBRUSHEVENT_NOP);

PRM_Template
SOP_CustomBrush::myTemplateList[] = {
    PRM_Template(PRM_STRING, 1, &PRMgroupName, 0, &SOP_Node::pointGroupMenu),
    PRM_Template(PRM_XYZ_J, 3, &sopOriginName),
    PRM_Template(PRM_XYZ_J, 3, &sopDirectionName),
    PRM_Template(PRM_FLT_J, 1, &sopRadiusName, PRMoneDefaults),
    PRM_Template(PRM_RGB_J, 3, &sopColorName, PRMoneDefaults),
    PRM_Template(PRM_FLT_J, 1, &sopAlphaName, PRMpointOneDefaults),
    PRM_Template(PRM_ORD, 1, &sopOperationName, &sopOperationDefault,
                          &sopOperationMenu),
    PRM_Template(PRM_ORD, 1, &sopEventName, &sopEventDefault, &sopEventMenu),
    PRM_Template(PRM_CALLBACK, 1, &sopClearAllName, 0, 0, 0, &clearAllStatic),
    PRM_Template()
};

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

SOP_CustomBrush::SOP_CustomBrush(OP_Network *net, const char *name, OP_Operator *op) :
    SOP_Node(net, name, op),
    myGroup(0),
    myNumPts(0),
    myOldNumPts(0)
{
}

SOP_CustomBrush::~SOP_CustomBrush()
{
}

OP_ERROR
SOP_CustomBrush::cookInputGroups(OP_Context &context, int alone)
{
    const GB_PointGroup *grp = 0;
    OP_ERROR err = cookInputPointGroups(context, grp,
                                        myDetailGroupPair, alone);
    if(!alone)
        myGroup = grp;

    return err;
}

OP_ERROR
SOP_CustomBrush::cookMySop(OP_Context &context)
{
    // 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)
    {
        double t = context.getTime();

        // make a copy of input 0's geometry if it different from our last
        // cook
        int changed_input;
        duplicateChangedSource(0, context, &changed_input);

        if (cookInputGroups(context) >= UT_ERROR_ABORT)
        {
            unlockInputs();
            return error();
        }

        int npts = gdp->points().entries();

        if(myData.entries() == 0)
        {
            // we have no paint yet, when we apply additional paint we will
            // require there to be 'npts' points
            myNumPts = npts;
        }
        else if(myNumPts != npts)
        {
            // we cannot apply the paint as the point numbers have changed
            addError(SOP_ERR_MISMATCH_POINT);
            unlockInputs();
            return error();
        }

        int event = getEvent(t);
        if(event == SOP_CUSTOMBRUSHEVENT_BEGIN)
        {
            // we are starting a new brush stroke
            myOldData.entries(0);
            myOldNumPts = myNumPts;
        }
        else if(event == SOP_CUSTOMBRUSHEVENT_ACTIVE)
        {
            // we are in the middle of performing a brush stroke
            UT_Vector3 origin = getOrigin(t);
            UT_Vector3 direction = getDirection(t);
            direction.normalize();
            fpreal radius = getRadius(t);
            fpreal radius2 = radius * radius;
            fpreal alpha = getAlpha(t);
            UT_Vector3 color = getColor(t);
            int operation = getOperation(t);

            // build lookup table from a point number to the paint applied to
            // that point
            UT_HashTable table;
            for(int i = 0; i < myData.entries(); ++i)
                table.addSymbol(UT_Hash_Int(myData(i).myPtNum), UT_Thing(i));
            UT_HashTable oldtable;
            for(int i = 0; i < myOldData.entries(); ++i)
                oldtable.addSymbol(UT_Hash_Int(myOldData(i).myPtNum), UT_Thing(i));

            const GEO_Point *pt;
            FOR_ALL_OPT_GROUP_POINTS(gdp, myGroup, pt)
            {
                // determine if we should apply paint to this point

                UT_Vector3 pos = pt->getPos();

                UT_Vector3 p = pos - origin;
                p.normalize();
                fpreal dot_p_dir = dot(p, direction);
                if(dot_p_dir > 0)
                {
                    UT_Vector3 par = dot_p_dir * direction;
                    UT_Vector3 perp = p - par;

                    fpreal parlen2 = dot_p_dir * dot_p_dir;
                    if(parlen2 > 0 && perp.length2() < radius2 * parlen2)
                    {
                        // we will apply paint to this point
                        int ptnum = pt->getNum();
                        UT_Hash_Int hashkey(ptnum);
                        UT_Thing thing;

                        // find the current amount of applied paint
                        if(!table.findSymbol(hashkey, &thing))
                        {
                            // no paint has been applied yet
                            thing = (long)myData.entries();
                            myData.append(SOP_CustomBrushData(ptnum, 0, 0, 0, 0));
                            table.addSymbol(hashkey, thing);
                        }
                        SOP_CustomBrushData &d = myData((long)thing);

                        if(!oldtable.findSymbol(hashkey, &thing))
                        {
                            // remember the old paint value for undos
                            thing = (long)myOldData.entries();
                            myOldData.append(d);
                            oldtable.addSymbol(hashkey, thing);
                        }

                        // update the paint value
                        fpreal one_minus_alpha = 1 - alpha;
                        switch(operation)
                        {
                            case SOP_CUSTOMBRUSHOPERATION_PAINT:
                                d.myRed = alpha * color.x() + one_minus_alpha * d.myRed;
                                d.myGreen = alpha * color.y() + one_minus_alpha * d.myGreen;
                                d.myBlue = alpha * color.z() + one_minus_alpha * d.myBlue;
                                d.myAlpha = alpha + one_minus_alpha * d.myAlpha;
                                break;

                            case SOP_CUSTOMBRUSHOPERATION_ERASE:
                                d.myRed *= one_minus_alpha;
                                d.myGreen *= one_minus_alpha;
                                d.myBlue *= one_minus_alpha;
                                d.myAlpha *= one_minus_alpha;
                                break;
                        }
                    }
                }
            }
        }
        else if(event == SOP_CUSTOMBRUSHEVENT_END)
        {
            // we have finished performing a brush stroke
            UT_UndoManager *man = UTgetUndoManager();
            if(man->willAcceptUndoAddition())
            {
                // create an undo for the entire brush stroke
                man->addToUndoBlock(new SOP_UndoCustomBrushData(this, myOldNumPts, myNumPts, myOldData, myData));
                myOldData.entries(0);
            }
        }

        // find the color attribute in the original geometry
        const GU_Detail *input0 = inputGeo(0);
        GB_AttributeRef input_offset = input0->findPointAttrib(GEO_STD_ATTRIB_DIFFUSE,
                                        3 * sizeof(float), GB_ATTRIB_FLOAT);

        // find the color attribute in the current geometry, creating one
        // if necessary
        GB_AttributeRef offset = gdp->findPointAttrib(GEO_STD_ATTRIB_DIFFUSE,
                                        3 * sizeof(float), GB_ATTRIB_FLOAT);
        if(offset.isInvalid())
        {
            offset = gdp->addPointAttrib(GEO_STD_ATTRIB_DIFFUSE,
                                3 * sizeof(float), GB_ATTRIB_FLOAT, 0);
        }
        if(offset.isValid())
        {
            // update the colour for all painted points
            for(int i = 0; i < myData.entries(); ++i)
            {
                SOP_CustomBrushData &data = myData(i);

                fpreal r = data.myRed;
                fpreal g = data.myGreen;
                fpreal b = data.myBlue;
                if(input_offset.isValid())
                {
                    fpreal one_minus_alpha = 1 - data.myAlpha;
                    const GEO_Point *pt = input0->points()(data.myPtNum);
                    UT_Vector3 f = pt->getValue<UT_Vector3>(input_offset);
                    r += one_minus_alpha * f.x();
                    g += one_minus_alpha * f.y();
                    b += one_minus_alpha * f.z();
                }

                GEO_Point *pt = gdp->points()(data.myPtNum);
                pt->setValue<UT_Vector3>(offset, UT_Vector3(r, g, b));
            }
        }

        unlockInputs();
    }
    return error();
}

OP_ERROR
SOP_CustomBrush::save(
    ostream &os,
    const OP_SaveFlags &saveflags,
    const char *path_prefix)
{
    if(SOP_Node::save(os, saveflags, path_prefix) >= UT_ERROR_ABORT)
        return error();

    // create a new packet for our paint
    UT_CPIO packet;
    UT_WorkBuffer path;
    const char *ext = saveflags.getBinary() ? "bpaint" : "paint";
    path.sprintf("%s%s.%s", path_prefix, (const char *)getName(), ext);
    packet.open(os, path.buffer());
    UT_OStreamFilter filter(os, saveflags.getBinary());

    filter.write(myNumPts);
    filter.endLine();

    int n = myData.entries();
    filter.write(n);
    filter.endLine();

    for(int i = 0; i < n; ++i)
    {
        SOP_CustomBrushData &data = myData(i);
        filter.write(data.myPtNum);
        filter.write(data.myRed);
        filter.write(data.myGreen);
        filter.write(data.myBlue);
        filter.write(data.myAlpha);
    }
    filter.endLine();

    packet.close(os);

    return error();
}

bool
SOP_CustomBrush::load(UT_IStream &is, const char *ext, const char *path)
{
    // update our paint values if this is a paint packet
    if(strcmp(ext, "bpaint") == 0 || strcmp(ext, "paint") == 0)
    {
        myNumPts = 0;
        myData.entries(0);
        myOldData.entries(0);

        if(!is.read(&myNumPts))
            return false;

        int n;
        if(!is.read(&n))
            return false;
        for(int i = 0; i < n; ++i)
        {
            int idx;
            if(!is.read(&idx))
                return false;
        
            float r;
            if(!is.read(&r))
                return false;

            float g;
            if(!is.read(&g))
                return false;

            float b;
            if(!is.read(&b))
                return false;

            float a;
            if(!is.read(&a))
                return false;

            myData.append(SOP_CustomBrushData(idx, r, g, b, a));
        }

        return true;
    }

    return SOP_Node::load(is, ext, path);
}

void
SOP_CustomBrush::updateData(int numpts, UT_RefArray<SOP_CustomBrushData> &data)
{
    myNumPts = numpts;
    if(myNumPts == 0)
    {
        myData.entries(0);

        // we make the SOP think the input geometry has changed so it will
        // duplicate the input geometry to reset the attribute values.  This
        // is necessary as our cook method only modified the color attribute
        // of points indicated by myData, which is now empty.
        resetChangedSourceFlags();
    }
    else
    {
        // build lookup table from a point number to the paint applied to
        // that point
        UT_HashTable table;
        for(int i = 0; i < myData.entries(); ++i)
            table.addSymbol(UT_Hash_Int(myData(i).myPtNum), UT_Thing(i));

        for(int i = 0; i < data.entries(); ++i)
        {
            SOP_CustomBrushData &d = data(i);
            UT_Hash_Int hashkey(d.myPtNum);

            UT_Thing thing;
            if(table.findSymbol(hashkey, &thing))
            {
                // we already have paint applied to this point, just update the
                // paint values
                myData((long)thing) = d;
            }
            else
            {
                // create a new entry for the paint
                thing = (long)myData.entries();
                myData.append(d);
                table.addSymbol(hashkey, thing);
            }
        }
    }

    // tell our SOP to re-cook as we have changed the paint values
    forceRecook();
}

int
SOP_CustomBrush::clearAllStatic(void *op, int, float time, const PRM_Template *)
{
    SOP_CustomBrush *sop = (SOP_CustomBrush *)op;
    sop->clearAll();
    return 1;
}

void
SOP_CustomBrush::clearAll()
{
    UT_AutoUndoBlock undoblock("Clear All", ANYLEVEL);

    int oldnumpts = myNumPts;
    myNumPts = 0;
    myOldData = myData;
    myData.entries(0);

    UT_UndoManager *man = UTgetUndoManager();
    if(man->willAcceptUndoAddition())
    {
        man->addToUndoBlock(new SOP_UndoCustomBrushData(this, oldnumpts, myNumPts, myOldData, myData));
        myOldData.entries(0);
    }

    // we make the SOP think the input geometry has changed so it will
    // duplicate the input geometry to reset the attribute values.  This is
    // necessary as our cook method only modified the color attribute of
    // points indicated by myData, which is now empty.
    resetChangedSourceFlags();
    forceRecook();
}

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