HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_BrushHairLen.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2024
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * This SOP demonstrates how to override the BrushBase SOP to paint custom
27  * attributes, and then use those attributes in the resulting geometry.
28  */
29 
30 #include "SOP_BrushHairLen.h"
31 
32 #include <SOP/SOP_Guide.h>
33 #include <GU/GU_Detail.h>
34 #include <GEO/GEO_PolyCounts.h>
35 #include <GEO/GEO_PrimPoly.h>
36 #include <GA/GA_ElementWrangler.h>
37 #include <GA/GA_Iterator.h>
38 #include <GA/GA_Range.h>
39 #include <OP/OP_AutoLockInputs.h>
40 #include <OP/OP_Operator.h>
41 #include <OP/OP_OperatorTable.h>
42 #include <PRM/PRM_Include.h>
43 #include <PRM/PRM_ChoiceList.h>
44 #include <UT/UT_DSOVersion.h>
45 
46 using namespace HDK_Sample;
47 
48 #define PRM_MENU_CHOICES (PRM_ChoiceListType)(PRM_CHOICELIST_EXCLUSIVE |\
49  PRM_CHOICELIST_REPLACE)
50 
51 // Define the new sop operator...
52 void
54 {
55  table->addOperator(new OP_Operator(
56  "proto_brushhairlen",
57  "Brush Hair Length",
60  1,
61  1));
62 }
63 
64 static PRM_Name sop_names[] = {
65  PRM_Name("group", "Group"),
66  PRM_Name("op", "Operation"),
67  PRM_Name("flen", "FL"),
68  PRM_Name("blen", "BL"),
69  PRM_Name("radius", "Radius"),
70  PRM_Name("uvradius", "UV Radius"),
71  PRM_Name(0)
72 };
73 
74 static PRM_Name sopOpMenuNames[] = {
75  PRM_Name("paint", "Paint"),
76  PRM_Name("eyedrop", "Eye Dropper"),
77  PRM_Name("smoothattrib", "Smooth"),
78  PRM_Name("callback", "Callback"),
79  PRM_Name("erase", "Erase Changes"),
80  PRM_Name(0)
81 };
82 static PRM_ChoiceList sopOpMenu(PRM_MENU_CHOICES, sopOpMenuNames);
83 
86 {
87  // Primitive group to allow painting on...
88  PRM_Template(PRM_STRING, 1, &sop_names[0], 0, &SOP_Node::primGroupMenu,
91 
92  // This is the choice of operations...
95  1, &sop_names[1], 0, &sopOpMenu),
96  // Foreground hair length (LMB)
98  1, &sop_names[2], PRMoneDefaults),
99  // Background hair length (MMB)
101  1, &sop_names[3], PRMzeroDefaults),
102  // Radius
104  1, &sop_names[4], PRMpointOneDefaults),
105  // UV Radius
107  1, &sop_names[5], PRMpointOneDefaults),
108  PRM_Template()
109 };
110 
111 OP_Node *
113 {
114  return new SOP_BrushHairLen(net, name, entry);
115 }
116 
117 
119  : SOP_BrushBase(net, name, entry)
120 {
121  // This indicates that this SOP manually manages its data IDs,
122  // so that Houdini can identify what attributes may have changed,
123  // e.g. to reduce work for the viewport, or other SOPs that
124  // check whether data IDs have changed.
125  // By default, (i.e. if this line weren't here and if not for SOP_GDT),
126  // all data IDs would be bumped after the SOP cook, to indicate that
127  // everything might have changed.
128  // If some data IDs don't get bumped properly, the viewport
129  // may not update, or SOPs that check data IDs
130  // may not cook correctly, so be *very* careful!
131  // NOTE: SOP_GDT already set this, so all subclasses have it set
132  // by default, unless they set it to false,
133  // but it's here for clarity.
135 
136  // We initialize our values to safe starting values. The most important
137  // is setting myEvent to SOP_BRUSHSTROKE_NOP as that will cause
138  // the processBrushOp to ignore most everything else.
139  myRayOrient = 0.0f;
140  myRayHit = 0.0f;
141  myRayHitU = 0.0f;
142  myRayHitV = 0.0f;
143  myRayHitW = 0.0f;
144  myRayHitPressure = 1.0f;
145  myPrimHit = -1;
146  myEvent = SOP_BRUSHSTROKE_NOP;
147  myUseFore = true;
148  myStrokeChanged = false;
149 }
150 
152 {
153 }
154 
155 
158 {
159  switch (evalInt("op", 0, 0))
160  {
161  case 0: return SOP_BRUSHOP_PAINT;
162  case 1: return SOP_BRUSHOP_EYEDROP;
163  case 2: return SOP_BRUSHOP_SMOOTHATTRIB;
164  case 3: return SOP_BRUSHOP_CALLBACK;
165  case 4: default: return SOP_BRUSHOP_ERASE;
166  }
167 }
168 
169 void
171 {
172  int iop;
173  switch (op)
174  {
175  case SOP_BRUSHOP_EYEDROP: iop = 1; break;
176  case SOP_BRUSHOP_SMOOTHATTRIB: iop = 2; break;
177  case SOP_BRUSHOP_CALLBACK: iop = 3; break;
178  case SOP_BRUSHOP_ERASE: iop = 4; break;
179  case SOP_BRUSHOP_PAINT:
180  default: iop = 0; break;
181  }
182  setInt("op", 0, 0, iop);
183 }
184 
185 void
187 {
188  // We want to do attribute erase, as we are an attribute style brush.
189  // NOTE: Both of these operations bump the affected attributes' data IDs.
191  if (myBrush.doVisualize())
193 }
194 
195 bool
197 {
198  // When do we have to apply a new sculpting operation:
199  return isParmDirty(1, t) ||
200  isParmDirty(2, t);
201 }
202 
203 const GU_Detail *
205 {
206  OP_Context context(t);
207 
208  // We always want our first input... We change our own topology,
209  // so it would be a bad thing to use ourselves.
210  SOP_Node *sop = CAST_SOPNODE(getInput(0));
211 
212  return sop->getCookedGeo(context);
213 }
214 
215 OP_ERROR
217 {
218  // We must lock our inputs before we try to access their geometry.
219  // OP_AutoLockInputs will automatically unlock our inputs when we return.
220  // NOTE: Don't call unlockInputs yourself when using this!
221  OP_AutoLockInputs inputs(this);
222  if (inputs.lock(context) >= UT_ERROR_ABORT)
223  return error();
224 
225  fpreal t = context.getTime();
226 
227  // There are two different methods here. BUILD_HAIR will create
228  // hair geometry in the gdp. This requires it to do a duplicateSource
229  // and rebuild everything every frame.
230  // The non BUILD_HAIR method merely updates the hairlen point attribute.
231  // One could then use the guide geometry to display the hair. This is
232  // more efficient, as the brush code can avoid duplicating the incoming
233  // geometry, but just rollback its changes. This method should be
234  // used if you are not doing any processing of the gdp post-processBrushOp.
235  const bool BUILD_HAIR = false;
236 
237  bool changed_input;
238  bool changed_group;
239  if (BUILD_HAIR)
240  {
241  changed_input = true;
242  changed_group = true;
243  // Duplicate the incoming source, overwriting everything as we will
244  // be messing with geometry.
245  duplicateSource(0, context);
246  }
247  else
248  {
249  changed_input = checkChangedSource(0, context);
250  changed_group = isParmDirty(SOP_GDT_GRP_IDX, context.getTime());
251 
252  if (changed_input)
253  duplicateChangedSource(0, context, 0, true);
254  }
255 
256  // Find the hairlen attribute...
257  GA_RWHandleF attrib(gdp->findFloatTuple(GA_ATTRIB_POINT, "hairlen"));
258 
259  // If it doesn't exist, create it.
260  if (attrib.isInvalid())
261  attrib = GA_RWHandleF(gdp->addFloatTuple(GA_ATTRIB_POINT, "hairlen", 1));
262 
263  // Having created the attribute, we can also create a local variable
264  // HAIRLEN which will map to it:
265  // NOTE: This bumps the data ID of the varmap attribute.
266  gdp->addVariableName("hairlen", "HAIRLEN");
267 
268  // Default to false to trigger a findFloatTuple if necessary in the callback.
269  myHairlenFound = false;
270  myTime = t;
271 
272  // Now, process any of the brush changes that may have occurred since
273  // our last cook...
274  // We inform it that we have changed both the input & group, as it
275  // should not rely on them as we have rebuilt them.
276  processBrushOp(context, changed_input, changed_group);
277 
278  // Bump the data ID for hairlen if it was modified in the callback
279  // below. Bumping the data ID once for each point might be slow,
280  // so it's done just once here.
281  if (myHairlenFound)
282  attrib.bumpDataId();
283 
284  // We now clear out our myStrokeChanged as it is no longer changed...
285  myStrokeChanged = false;
286 
287  // For each point, add a hair of the proper length, if there are any.
288  if (BUILD_HAIR && gdp->getNumPoints() > 0)
289  {
290  GA_Size n = gdp->getNumPoints();
291  GA_Offset startnewptoff = gdp->appendPointBlock(n);
292 
293  // We've added points, so all point attribute data IDs must be bumped.
295 
296  // We want to copy all standard attributes (except P) and groups
300  GA_PointWrangler ptwrangler(*gdp, filter);
301 
302  // GEO_PrimPoly::buildBlock takes an array of integers that are
303  // really offsets relative to some lower-bound offset. In this case,
304  // it's fine to just have a lower-bound of GA_Offset(0), even if
305  // that offset isn't occupied, but we could use
306  // gdp->pointOffset(GA_Index(0)) to have a tigher bound in
307  // some cases where the input wasn't defragmented.
308  // It mostly helps in cases where the span of points used by the
309  // polygons is very small compared to the total.
310  GA_Offset relativetooffset = GA_Offset(0);
311 
312  GEO_PolyCounts polygonsizes;
313  polygonsizes.append(2, n);
314  UT_IntArray polygonpointnumbers(2*n, 2*n);
315  exint i = 0;
316  for (GA_Iterator it(GA_Range(gdp->getPointMap(),GA_Offset(0),startnewptoff)); !it.atEnd(); ++it, ++i)
317  {
318  GA_Offset oldptoff = *it;
319  // appendPointBlock guarantees a contiguous block of offsets, so we can just add i.
320  GA_Offset newptoff = startnewptoff + i;
321  UT_Vector3 pos = gdp->getPos3(oldptoff);
322  // Add hair length to y value.
323  pos.y() += attrib.get(oldptoff);
324  gdp->setPos3(newptoff, pos);
325 
326  // Copy attributes (except P) and groups
327  if (ptwrangler.getNumAttributes() > 0)
328  ptwrangler.copyAttributeValues(newptoff, oldptoff);
329 
330  // Create a polygon to loft them.
331  polygonpointnumbers(2*i ) = int(oldptoff - relativetooffset);
332  polygonpointnumbers(2*i + 1) = int(newptoff - relativetooffset);
333  }
334 
335  // Build the actual polygons. This will be in parallel if there are enough.
336  // npoints just needs to be an upper bound on the maximum offset used + 1 - relative offset.
337  GEO_PrimPoly::buildBlock(gdp, relativetooffset, gdp->getNumPointOffsets() - relativetooffset, polygonsizes, polygonpointnumbers.array(), false);
338 
339  // We've added primitives and vertices, so all primitive and
340  // vertex attribute data IDs must be bumped.
343 
344  // The primitive list's data ID also needs to be bumped.
346  }
347 
348  return error();
349 }
350 
351 void
353  fpreal /*t*/,
354  GA_Offset ptoff,
355  const UT_Array<GA_Offset> * /*ptneighbour*/,
356  GA_Offset /*vtx*/,
357  const UT_Array<GA_Offset> * /*vtxneighbour*/,
358  float alpha,
359  GEO_Delta *delta,
360  const GU_Detail * /*gdp*/)
361 {
362  // Unused here is the ptneighbour and vtxneighbour. These are a list
363  // of all the points or vertices connected to this point by at least
364  // one edge. Each point will show up only once in the list, regardless
365  // of the number of times it is connected.
366 
367  // We first determine the attribute index if not already known.
368  // This is called once per point, so we want to minimize the attribute
369  // lookups, but on the other hand, we don't want to cache to early
370  // as if new attributes are created it would be invalid.
371  if (!myHairlenFound)
372  {
373  myHairlenFound = true;
374  myHairlenHandle = GA_RWHandleF(gdp->findFloatTuple(GA_ATTRIB_POINT, "hairlen"));
375  }
376 
377  // If no hairlen, do nothing.
378  if (myHairlenHandle.isInvalid())
379  return;
380 
381  // Here we actually change all our attributes. Note that we should:
382  // 1) NOT create any new attributes in here, as it will confuse the GDT.
383  // 2) Open & close the GDT for writing using beginPointAttributeChange
384  // or beginPointPositionChanged followed by endChange().
385  // 3) Use the alpha as a blend value for our effect.
386  // 4) One of point or vertex will be non-null, depending on if this
387  // is a vertex paint or point paint. Currently only point paint
388  // is supported.
389 
390  float newhair = (myUseFore ? FGR(myTime) : BGR(myTime));
391 
392  if (GAisValid(ptoff))
393  {
394  if (delta) delta->beginPointAttributeChange(*gdp, ptoff);
395 
396  // Do all our attribute tweaking here...
397  float oldhair = myHairlenHandle.get(ptoff);
398 
399  // simple alpha blending... Alpha of 1 means newhair, 0 means oldhair.
400  myHairlenHandle.set(ptoff, SYSlerp(oldhair, newhair, alpha));
401 
402  if (delta) delta->endChange();
403  }
404 }
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1189
GDT_Detail * myCurrentDelta
Definition: SOP_GDT.h:132
const GU_Detail * getCookedGeo(OP_Context &, int forced=0)
typedef int(APIENTRYP RE_PFNGLXSWAPINTERVALSGIPROC)(int)
virtual OP_ERROR error()
Iteration over a range of elements.
Definition: GA_Iterator.h:29
PRM_API const PRM_Type PRM_STRING
fpreal BGR(fpreal t) override
OP_ERROR lock(OP_Context &context)
Locks all inputs.
GU_Brush myBrush
GA_Attribute * addFloatTuple(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringHolder &name, int tuple_size, const GA_Defaults &defaults=GA_Defaults(0.0), const UT_Options *creation_args=0, const GA_AttributeOptions *attribute_options=0, GA_Storage storage=GA_STORE_REAL32, const GA_ReuseStrategy &reuse=GA_ReuseStrategy())
virtual void endChange()=0
fpreal getTime() const
Definition: OP_Context.h:62
SOP_BrushOp OP() override
bool GAisValid(GA_Size v)
Definition: GA_Types.h:649
static PRM_SpareData * getGroupSelectButton(GA_GroupType group_type, const char *group_type_parm=NULL, int input_index=0, PRM_SpareData *merge_spare_data=NULL, const char *assoc_groups=NULL, GroupSelectAsOrdered ordered=GroupSelectAsOrdered::AUTO, const char *use_name_attr=nullptr, const char *select_script=nullptr)
void brushOpCallback(fpreal t, GA_Offset pt, const UT_Array< GA_Offset > *ptneighbour, GA_Offset vtx, const UT_Array< GA_Offset > *vtxneighbour, float alpha, GEO_Delta *delta, const GU_Detail *gdp) override
This is the callback triggered when a BRUSHOP_CALLBACK is used:
OP_ERROR cookMySop(OP_Context &context) override
int64 exint
Definition: SYS_Types.h:125
PRM_API const PRM_Type PRM_ORD
GA_Attribute * getP()
Convenience method to access the P attribute.
Definition: GA_Detail.h:164
UT_ErrorSeverity
Definition: UT_Error.h:25
T * array()
Definition: UT_Array.h:819
const GU_Detail * getIsectGdp(fpreal t) override
Public methods needed by MSS:
PRM_API PRM_Default PRMpointOneDefaults[]
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
SYS_FORCE_INLINE UT_Vector3 getPos3(GA_Offset ptoff) const
The ptoff passed is the point offset.
Definition: GA_Detail.h:185
UT_Matrix2T< T > SYSlerp(const UT_Matrix2T< T > &v1, const UT_Matrix2T< T > &v2, S t)
Definition: UT_Matrix2.h:675
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:235
void applyVisualizeStencil(GU_Detail *gdp)
A range of elements in an index-map.
Definition: GA_Range.h:42
bool hasStyleChanged(fpreal t) override
GA_Size GA_Offset
Definition: GA_Types.h:641
bool atEnd() const
Definition: GA_Iterator.h:93
GLdouble n
Definition: glcorearb.h:2008
const GA_IndexMap & getPointMap() const
Definition: GA_Detail.h:741
void eraseAttributes(GEO_Delta *old, GEO_Delta *change)
#define PRM_MENU_CHOICES
OP_ERROR duplicateChangedSource(unsigned idx, OP_Context &ctx, int *changed=0, bool force=false)
Only duplicates the source if the source has changed since the last call to this method.
SYS_FORCE_INLINE GA_Offset appendPointBlock(GA_Size npoints)
Append new points, returning the first offset of the contiguous block.
Definition: GA_Detail.h:330
void bumpDataId()
Use this to mark primitives or their intrinsic data as dirty.
static OP_Node * myConstructor(OP_Network *net, const char *name, OP_Operator *entry)
void newSopOperator(OP_OperatorTable *table)
GA_AttributeSet & getAttributes()
Definition: GA_Detail.h:796
OP_Node * getInput(unsigned idx, bool mark_used=false) const
Returns the node connected to a particular input (may be null).
bool checkChangedSource(unsigned idx, OP_Context &ctx)
SYS_FORCE_INLINE GA_Offset getNumPointOffsets() const
Definition: GA_Detail.h:341
SOP_NodeFlags mySopFlags
Definition: SOP_Node.h:1625
GLfloat GLfloat GLfloat alpha
Definition: glcorearb.h:112
SOP_BrushOp
Definition: SOP_BrushBase.h:39
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:203
GLuint const GLchar * name
Definition: glcorearb.h:786
SYS_FORCE_INLINE bool isInvalid() const
Definition: GA_Handle.h:191
PRM_API const PRM_Type PRM_FLT_J
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
void setManagesDataIDs(bool onOff)
Definition: SOP_NodeFlags.h:36
GLdouble t
Definition: glad.h:2397
GU_Detail * gdp
Definition: SOP_Node.h:1622
#define SOP_GDT_GRP_IDX
Definition: SOP_GDT.h:22
const GA_Attribute * findFloatTuple(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringRef &name, int min_size=1, int max_size=-1) const
PRM_API PRM_Default PRMoneDefaults[]
static PRM_Template myTemplateList[]
SYS_FORCE_INLINE void setPos3(GA_Offset ptoff, const UT_Vector3 &pos)
Set P from a UT_Vector3.
Definition: GA_Detail.h:237
SYS_FORCE_INLINE void set(GA_Offset off, const T &val) const
Definition: GA_Handle.h:354
static GA_AttributeFilter selectStandard(const GA_Attribute *exclude=0)
Class factories.
fpreal64 fpreal
Definition: SYS_Types.h:277
void setInt(int parmi, int vectori, fpreal t, exint value)
bool doVisualize() const
Definition: GU_Brush.h:198
void append(GA_Size size, GA_Size count=1)
virtual void beginPointAttributeChange(const GEO_Detail &gdp, GA_Offset pt)=0
GA_RWHandleT< fpreal32 > GA_RWHandleF
Definition: GA_Handle.h:1355
static GA_AttributeFilter selectGroup()
const GA_PrimitiveList & getPrimitiveList() const
Definition: GA_Detail.h:792
GDT_Detail * myPermanentDelta
Definition: SOP_GDT.h:131
static GA_Offset buildBlock(GA_Detail *detail, const UT_Vector3 *points, const GA_Size npoints, const GEO_PolyCounts &polygonsizelist, const int *polygonpointnumbers, const bool closed=true)
virtual OP_ERROR processBrushOp(OP_Context &context, bool changed_input, bool changed_group)
void bumpAllDataIds(GA_AttributeOwner owner)
Bumps all data IDs of attributes of the specified owner.
exint evalInt(int pi, int vi, fpreal t) const
static GA_AttributeFilter selectOr(const GA_AttributeFilter &f0, const GA_AttributeFilter &f1, bool single_match=false)
void addVariableName(const char *attr, const char *varname)
constexpr SYS_FORCE_INLINE T & y() noexcept
Definition: UT_Vector3.h:665
PRM_API PRM_Default PRMzeroDefaults[]
SOP_BrushHairLen(OP_Network *net, const char *, OP_Operator *entry)
fpreal FGR(fpreal t) override
void setBrushOp(SOP_BrushOp op) override
SYS_FORCE_INLINE GA_Size getNumPoints() const
Return the number of points.
Definition: GA_Detail.h:334
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: glcorearb.h:1297
bool isParmDirty(int idx, fpreal t)
OP_ERROR duplicateSource(unsigned index, OP_Context &context, GU_Detail *gdp, bool clean=true)