HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_CentroidDivide.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2022
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  * The HDK Centroid Divide
27  */
28 
29 
30 #include "SOP_CentroidDivide.h"
31 
32 // This is an automatically generated header file based on theDsFile, below,
33 // to provide SOP_CentroidDivideParms, an easy way to access parameter values from
34 // SOP_CentroidDivideVerb::cook with the correct type.
35 #include "SOP_CentroidDivide.proto.h"
36 
37 #include <GA/GA_ElementWrangler.h>
38 #include <GA/GA_SplittableRange.h>
39 #include <GU/GU_Detail.h>
41 #include <SYS/SYS_Math.h>
42 #include <UT/UT_DSOVersion.h>
43 
44 using namespace UT::Literal;
45 using namespace HDK_Sample;
46 
47 /// This is the internal name of the SOP type.
48 /// It isn't allowed to be the same as any other SOP's type name.
49 const UT_StringHolder SOP_CentroidDivide::theSOPTypeName("hdk_centroiddivide"_sh);
50 
51 void
53 {
54  OP_Operator *op =
55  new OP_Operator(
56  SOP_CentroidDivide::theSOPTypeName, // Internal name
57  "HDK Centroid Divide", // GUI name
58  SOP_CentroidDivide::myConstructor, // Op Constructr
59  SOP_CentroidDivide::buildTemplates(), // Parameter Definition
60  1, // Min # of Inputs
61  1, // Max # of Inputs
62  nullptr, // Local variables
63  0, // flags
64  0, // custom input labels. See SOP_CentroidDivide::inputLabels instead.
65  1, // 1 output
66  "HDK Examples" // Set the tab menu category to "HDK Examples"
67  );
68 
69  table->addOperator(op);
70 }
71 
72 /// This is a multi-line raw string specifying the parameter interface
73 /// for this SOP.
74 
75 // For our group parameter we will customize the string input parameter
76 // to more easily input the group.
77 // This group is on the second input. In SOP_Expand::buildTemplates
78 // we bind a drop down menu to the group field.
79 // Next, to get a selector button we use the 'script_action' parmtag to
80 // specify the script to execute when the button is pressed, the
81 // 'script_action_help' parmtag to create the hover tooltip, and the
82 // 'script_action_icon' parmtag to set the icon.
83 // All group selector buttons use the `soputils.selectGroupParm(kwargs)`
84 // function, and can use the kwargs['geometrytype'] to set the allowed
85 // geometry types, and the kwargs['indexindex'] to set the input
86 // to select on.
87 static const char *theDsFile = R"THEDSFILE(
88 {
89  name hdk_centroiddivide
90 
91  parm {
92  name "group"
93  label "Group"
94  type string
95  default { "" }
96  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
97  parmtag { "script_action_help" "Select primitives from an available viewport." }
98  parmtag { "script_action_icon" "BUTTONS_reselect" }
99  }
100 }
101 )THEDSFILE";
102 
103 PRM_Template *
104 SOP_CentroidDivide::buildTemplates()
105 {
106  static PRM_TemplateBuilder templ("SOP_CentroidDivide.C"_sh, theDsFile);
107  if (templ.justBuilt())
108  {
109  // Add drop down menu to the attribute string field.
110  // The menu type is a PRM_ChoiceList and SOP_Node provides a
111  // number of nice pre-built menus that you can use.
112 
113  // In this case we want to list all the primitive groups on the
114  // second input. We can use SOP_Node::primGroupMenu to generate the
115  // menu for us which will list all primitive groups.
116  templ.setChoiceListPtr("group", &SOP_Node::primGroupMenu);
117  }
118  return templ.templates();
119 }
120 
121 OP_Node *
122 SOP_CentroidDivide::myConstructor(
123  OP_Network *net, const char *name, OP_Operator *entry)
124 {
125  return new SOP_CentroidDivide(net, name, entry);
126 }
127 
128 SOP_CentroidDivide::SOP_CentroidDivide(
129  OP_Network *net, const char *name, OP_Operator *entry)
130  : SOP_Node(net, name, entry)
131 {
132 }
133 
135 {
136 }
137 
138 OP_ERROR
140 {
141  return cookMyselfAsVerb(context);
142 }
143 
144 // Set custom input labels for the source and destination inputs.
145 const char *
147 {
148  switch (idx)
149  {
150  case 0:
151  return "Input Geometry";
152  default:
153  return "Invalid Source";
154  }
155 }
156 
157 // Define the verb for this SOP Node.
159 {
160 public:
163 
164  SOP_NodeParms *allocParms() const override { return new SOP_CentroidDivideParms(); }
165 
167 
168  // There are a few choices for the cookMode.
169  // For SOPs with no inputs that only generate geometry use COOK_GENERATOR
170  // For SOPs that modify the input, COOK_DUPLICATE or COOK_INPLACE is usually
171  // the best option, with COOK_DUPLICATE creating a full copy of the input detail
172  // and COOK_INPLACE working on the input detail directly.
173  // The final option COOK_GENERIC allows any number of inputs, but the detail you
174  // get in ::cook will be empty by default. This mode is useful for SOPs like Sweep
175  // that combine two separate geometries in an interesting way.
176  CookMode cookMode(const SOP_NodeParms *parms) const override { return COOK_INPLACE; }
177 
178  void cook(const CookParms &cookparms) const override;
179 };
180 
181 // Register a verb for our SOP
182 static SOP_NodeVerb::Register<SOP_CentroidDivideVerb> theSOPCentroidDivideVerb;
183 
184 const SOP_NodeVerb *
186 {
187  return theSOPCentroidDivideVerb.get();
188 }
189 
190 void
192 {
193  auto &&sopparms = cookparms.parms<SOP_CentroidDivideParms>();
194  GU_Detail *detail = cookparms.gdh().gdpNC();
195 
196  // Get input parameters.
197  UT_StringHolder groupname = sopparms.getGroup();
198 
199  // Parse our group.
200  GOP_Manager gop;
201  const GA_PrimitiveGroup *group = nullptr;
202  if (groupname.isstring())
203  {
204  // NOTE: Use a detached group here.
205  group = gop.parsePrimitiveGroups(
206  /*pat=*/groupname,
207  /*creator=*/GOP_Manager::GroupCreator(/*detail=*/detail, /*detached=*/true));
208  }
209 
210  // Make a group we will use to collect all the closed polygons in our
211  // group that we will divide and then delete.
212 
213  // Collect the polygons that will be removed in an offset list.
214  GA_OffsetList rm_polys;
215 
216  // One triangle added per edge.
217  exint num_new_prims = 0;
218  for (GA_Offset prim_off : detail->getPrimitiveRange(group))
219  {
220  // Only handle closed polygons
221  if (detail->getPrimitiveTypeId(prim_off) != GA_PRIMPOLY
222  || !detail->getPrimitiveClosedFlag(prim_off))
223  continue;
224 
225  // Add this prim to our list to divide and then remove.
226  rm_polys.append(prim_off);
227  // The polygon is closed so we have one edge per vertex.
228  // And we are going to create one triangle per edge.
229  num_new_prims += detail->getPrimitiveVertexCount(prim_off);
230  }
231 
232  // For copying/interpolating attributes from the source geometry
233  // onto the newly created geometry.
234  GA_PointWrangler pt_wrangler(*detail, GA_PointWrangler::INCLUDE_P);
235  GA_PrimitiveWrangler prim_wrangler(*detail);
236  GA_VertexWrangler vtx_wrangler(*detail);
237 
238  // Add a block of new points to the detail. These are the
239  // new points at the centroids of each polygon.
240  GA_Size num_new_points = rm_polys.entries();
241  GA_Offset point_block_start = detail->appendPointBlock(/*npoints=*/num_new_points);
242 
243  // Add a block of polygons each with 3 vertices.
244  // This also adds a block of 3 * num_new_prims vertices.
245  GA_Offset vertex_block_start;
246  GA_Offset prim_block_start = detail->appendPrimitivesAndVertices(
247  /*type=*/GA_PRIMPOLY,
248  /*nprimitives=*/num_new_prims,
249  /*nvertices_each=*/3,
250  /*vertex_block_start=*/vertex_block_start,
251  /*closed_flag=*/true);
252 
253  // Use these to keep track of which new point/vertex/prim we are on.
254  GA_Offset new_pt = point_block_start;
255  GA_Offset new_prim_vtx0 = vertex_block_start;
256  GA_Offset new_prim = prim_block_start;
257  for (GA_Offset source_prim : rm_polys)
258  {
259  int nvtx = detail->getPrimitiveVertexCount(source_prim);
260 
261  // Loop over the edges.
262  for (int vi = 0; vi < nvtx; ++vi)
263  {
264  int vi_next = (vi + 1) % nvtx;
265 
266  GA_Offset source_vtx1 = detail->getPrimitiveVertexOffset(source_prim, vi);
267  GA_Offset source_vtx2 = detail->getPrimitiveVertexOffset(source_prim, vi_next);
268  GA_Offset source_pt1 = detail->vertexPoint(source_vtx1);
269  GA_Offset source_pt2 = detail->vertexPoint(source_vtx2);
270 
271  // Our three vertices on the new triangle.
272  // The one at the center
273  GA_Offset new_vtx0 = new_prim_vtx0 + 3 * vi;
274  // first on edge
275  GA_Offset new_vtx1 = new_vtx0 + 1;
276  // second on edge
277  GA_Offset new_vtx2 = new_vtx0 + 2;
278 
279  // Wire the third vertex to the point at the center.
280  detail->setVertexPoint(new_vtx0, new_pt);
281 
282  // Directly copy point wirings over from the source
283  // vertices for the first two vertices on the triangle.
284  detail->setVertexPoint(new_vtx1, source_pt1);
285  detail->setVertexPoint(new_vtx2, source_pt2);
286 
287  // Directly copy for vertices around the source poly
288  vtx_wrangler.copyAttributeValues(new_vtx1, source_vtx1);
289  vtx_wrangler.copyAttributeValues(new_vtx2, source_vtx2);
290 
291  // Copy the attribute from the first vertex/point.
292  // This will correctly handle the case of non-numerical
293  // attributes which we just want to copy and numerical
294  // attributes where the default value is not 0.
295 
296  // At the end we will scale it by 1.0/nvtx to
297  // compute the average for any numerical attributes.
298 
299  // NOTE: We're going to interpolate vertex values and write to
300  // new_prim_vtx0 first and then copy to all other vertices
301  // at the centroid.
302  if (vi)
303  {
304  vtx_wrangler.addAttributeValues(new_prim_vtx0, source_vtx1);
305  pt_wrangler.addAttributeValues(new_pt, source_pt1);
306  }
307  else
308  {
309  vtx_wrangler.copyAttributeValues(new_prim_vtx0, source_vtx1);
310  pt_wrangler.copyAttributeValues(new_pt, source_pt1);
311  }
312 
313  // Copy new triangle attributes from source prim.
314  prim_wrangler.copyAttributeValues(new_prim, source_prim);
315 
316  // One new prim per edge. Step to next new prim when done
317  // with this one.
318  ++new_prim;
319  }
320 
321  // Numerical attributes got all added together so now we need to
322  // scale by 1.0/nvtx to compute the average of each one.
323  vtx_wrangler.scaleAttributeValues(new_prim_vtx0, 1.0/nvtx);
324  pt_wrangler.scaleAttributeValues(new_pt, 1.0/nvtx);
325 
326  // We interpolated all the vertex attributes onto the first
327  // one at the centroid but now we need to copy the attributes
328  // from that vertex to the others at the centroid.
329  // Note how vi starts at 1 to skip copying *to* new_prim_vtx0.
330  for (int vi = 1; vi < nvtx; ++vi)
331  {
332  GA_Offset new_vtx0 = new_prim_vtx0 + 3 * vi;
333  vtx_wrangler.copyAttributeValues(new_vtx0, new_prim_vtx0);
334  }
335 
336  // new_prim_vtx0 is the first vertex in the block of vertices added
337  // for this current source primitive. To advance to the next source
338  // prim, we advance by the number of vertices added which is
339  // num vertices per added prim (3) * num added prims (nvtx)
340  new_prim_vtx0 += 3 * nvtx;
341 
342  // Each source prim has a single new point, step to the next point
343  // when done with the primitive.
344  ++new_pt;
345  }
346 
347  // Destroy the existing polygons we just replaced with triangles.
348 
349  // Internally, the detail holds a map of GA_Offsets -> GA_Index.
350  // As a user you only see the indices, however in C++ we access
351  // elements through their offsets (which we can map to indices
352  // with something like GA_Detail::primitiveIndex(offset))
353  // When deleting elements, like with
354  // GA_Detail::destroyPrimitiveOffsets, it will internally mark the
355  // offset as unused, removing the index, but will not shift
356  // around offsets for any primitive. Later, after this SOP
357  // finishes cooking, something may choose to defragment the
358  // index map, removing these unused offsets, but until this
359  // happens, we can rely on primitive offsets staying the same.
360  detail->destroyPrimitiveOffsets(GA_Range(detail->getPrimitiveMap(), rm_polys), true);
361 
362  if (cookparms.selectionEnabled())
363  {
364  // Since we know all offsets in
365  // [prim_block_start, prim_block_start+num_new_prims-1]
366  // are newly added prims, we can select these as follows.
367  GA_Range prim_range(detail->getPrimitiveMap(),
368  prim_block_start,
369  prim_block_start+num_new_prims);
370 
371  // Set the cook selection as the final step after we have removed
372  // the old primitives.
373  cookparms.select(prim_range);
374  }
375 }
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1193
void scaleAttributeValues(GA_Offset dest, fpreal scale)
void addAttributeValues(GA_Offset dest, GA_Offset src, fpreal scale, IncludeP add_p)
void copyAttributeValues(GA_Offset dest, GA_Offset src)
FromType append(ToType value)
Add a single entry (may grow array)
void addAttributeValues(GA_Offset dest, GA_Offset src, fpreal scale=1)
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
int OP_InputIdx
Definition: OP_DataTypes.h:184
int64 exint
Definition: SYS_Types.h:125
UT_ErrorSeverity
Definition: UT_Error.h:25
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
SOP_NodeParms * allocParms() const override
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:236
A range of elements in an index-map.
Definition: GA_Range.h:42
const char * inputLabel(OP_InputIdx idx) const overridefinal
GA_Size GA_Offset
Definition: GA_Types.h:646
const T & parms() const
Definition: SOP_NodeVerb.h:423
void cook(const CookParms &cookparms) const override
Compute the output geometry.
Constructs a PRM_Template list from an embedded .ds file or an istream.
static const UT_StringHolder theSOPTypeName
PRM_Template * templates() const
const GA_PrimitiveGroup * parsePrimitiveGroups(const char *pat, const GroupCreator &creator, bool numok=true, bool ordered=false, bool strict=false, GA_Index prim_offset=GA_Index(0), ParseInfo *info=0)
CookMode cookMode(const SOP_NodeParms *parms) const override
OP_ERROR cookMyselfAsVerb(OP_Context &context)
GLuint const GLchar * name
Definition: glcorearb.h:786
const SOP_NodeVerb * cookVerb() const overridefinal
UT_StringHolder name() const override
void select(GA_GroupType gtype=GA_GROUP_PRIMITIVE) const
Definition: SOP_NodeVerb.h:539
GU_Detail * gdpNC()
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
GA_API const UT_StringHolder parms
OP_ERROR cookMySop(OP_Context &context) overridefinal
void scaleAttributeValues(GA_Offset dest, fpreal scale, IncludeP scale_p)
bool selectionEnabled() const
Definition: SOP_NodeVerb.h:610
void newSopOperator(OP_OperatorTable *table)
void copyAttributeValues(GA_Offset dest, GA_Offset src, IncludeP copy_p)
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
SYS_FORCE_INLINE bool isstring() const
SYS_FORCE_INLINE FromType entries() const
Returns the number of used elements in the list (always <= capacity())