#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>
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;
};
}
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)
{
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)
{
if (lockInputs(context) < UT_ERROR_ABORT)
{
double t = context.getTime();
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)
{
myNumPts = npts;
}
else if(myNumPts != npts)
{
addError(SOP_ERR_MISMATCH_POINT);
unlockInputs();
return error();
}
int event = getEvent(t);
if(event == SOP_CUSTOMBRUSHEVENT_BEGIN)
{
myOldData.entries(0);
myOldNumPts = myNumPts;
}
else if(event == SOP_CUSTOMBRUSHEVENT_ACTIVE)
{
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);
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)
{
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)
{
int ptnum = pt->getNum();
UT_Hash_Int hashkey(ptnum);
UT_Thing thing;
if(!table.findSymbol(hashkey, &thing))
{
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))
{
thing = (long)myOldData.entries();
myOldData.append(d);
oldtable.addSymbol(hashkey, thing);
}
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)
{
UT_UndoManager *man = UTgetUndoManager();
if(man->willAcceptUndoAddition())
{
man->addToUndoBlock(new SOP_UndoCustomBrushData(this, myOldNumPts, myNumPts, myOldData, myData));
myOldData.entries(0);
}
}
const GU_Detail *input0 = inputGeo(0);
GB_AttributeRef input_offset = input0->findPointAttrib(GEO_STD_ATTRIB_DIFFUSE,
3 * sizeof(float), GB_ATTRIB_FLOAT);
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())
{
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();
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)
{
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);
resetChangedSourceFlags();
}
else
{
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))
{
myData((long)thing) = d;
}
else
{
thing = (long)myData.entries();
myData.append(d);
table.addSymbol(hashkey, thing);
}
}
}
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);
}
resetChangedSourceFlags();
forceRecook();
}