HDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
GEO_SplitPoints.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  * Definitions of functions for splitting points based on vertices or primitives
27  */
28 
29 #include "GEO_SplitPoints.h"
30 
31 #include <GEO/GEO_Detail.h>
32 #include <GA/GA_ATINumeric.h>
33 #include <GA/GA_Attribute.h>
34 #include <GA/GA_AttributeFilter.h>
35 #include <GA/GA_AIFCompare.h>
36 #include <GA/GA_ElementGroup.h>
37 #include <GA/GA_ElementWrangler.h>
38 #include <GA/GA_Handle.h>
39 #include <GA/GA_Range.h>
40 #include <GA/GA_Types.h>
41 #include <UT/UT_Interrupt.h>
42 #include <UT/UT_SmallArray.h>
43 #include <UT/UT_UniquePtr.h>
44 #include <SYS/SYS_Types.h>
45 #include <SYS/SYS_Math.h>
46 
47 using namespace UT::Literal;
48 
49 namespace HDK_Sample {
50 
51 GA_Size
53  GEO_Detail *detail,
54  const GA_ElementGroup *group)
55 {
56  if (group && group->isEmpty())
57  return 0;
58 
59  UT_AutoInterrupt boss("Making Unique Points");
60  if (boss.wasInterrupted())
61  return 0;
62 
63  // Behaviour is as if it's a point group if no group is present.
64  GA_GroupType grouptype = GA_GROUP_POINT;
65  const GA_PointGroup *point_group = nullptr;
66  GA_PointGroupUPtr point_group_deleter;
67  if (group)
68  {
69  grouptype = group->classType();
70  if (grouptype == GA_GROUP_POINT)
71  point_group = static_cast<const GA_PointGroup *>(group);
72  else if (grouptype == GA_GROUP_VERTEX || grouptype == GA_GROUP_PRIMITIVE)
73  {
74  GA_PointGroup *ptgroup = new GA_PointGroup(*detail);
75  point_group_deleter.reset(ptgroup);
76  ptgroup->combine(group);
77  point_group = ptgroup;
78  }
79  }
80 
81  GA_Size numpointsadded = 0;
82 
83  char bcnt = 0;
84 
85  UT_UniquePtr<GA_PointWrangler> ptwrangler(nullptr);
86 
88  GA_Offset points_end = detail->getPointMap().lastOffset() + 1;
90  GA_Offset end;
91  for (GA_Iterator it(detail->getPointRange(point_group)); it.blockAdvance(start, end); )
92  {
93  // Prevent going beyond the original points; new points don't need to be split.
94  if (start >= points_end)
95  break;
96  if (end > points_end)
97  end = points_end;
98 
99  for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
100  {
101  vtxoffs.setSize(0);
102 
103  // Make vertex order consistent, regardless of linked list order,
104  // by sorting in vertex offset order
105  bool has_other = false;
106  if (grouptype == GA_GROUP_POINT)
107  {
108  for (GA_Offset vtxoff = detail->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = detail->vertexToNextVertex(vtxoff))
109  vtxoffs.append(vtxoff);
110  }
111  else
112  {
113  for (GA_Offset vtxoff = detail->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = detail->vertexToNextVertex(vtxoff))
114  {
115  GA_Offset group_offset = vtxoff;
116  if (grouptype == GA_GROUP_PRIMITIVE)
117  group_offset = detail->vertexPrimitive(vtxoff);
118  if (group->contains(group_offset))
119  vtxoffs.append(vtxoff);
120  else
121  has_other = true;
122  }
123  }
124 
125  // If there's only one vertex in the group and none outside, nothing to split.
126  // If there are no vertices in the group, also nothing to split.
127  // Unique all but first vertex if all vertices of the point are in vtxoffs.
128  GA_Size nvtx = vtxoffs.size();
129  GA_Size points_to_add = nvtx - GA_Size(!has_other);
130  if (points_to_add <= 0)
131  continue;
132 
133  numpointsadded += points_to_add;
134 
135  if (!ptwrangler)
136  ptwrangler.reset(new GA_PointWrangler(*detail, GA_AttributeFilter::selectPublic()));
137 
138  // TODO: This is not perfect; we should be sorting by primitive order
139  // and then vertex order within a primitive, so that results
140  // are the same whether the input was loaded from a file or
141  // computed.
142  vtxoffs.sort();
143 
144  bool skip_first = !has_other;
145  GA_Offset newptoff = detail->appendPointBlock(points_to_add);
146  for (exint j = exint(skip_first); j < nvtx; ++j, ++newptoff)
147  {
148  if (!bcnt++ && boss.wasInterrupted())
149  return numpointsadded;
150 
151  ptwrangler->copyAttributeValues(newptoff, ptoff);
152  detail->setVertexPoint(vtxoffs(j), newptoff);
153  }
154  }
155  }
156  if (numpointsadded > 0)
157  {
158  // If we'd only added/removed points, we could use
159  // detail->bumpDataIdsForAddOrRemove(true, false, false),
160  // but we also rewired vertices, so we need to bump the
161  // linked-list topology attributes.
163  GA_ATITopology *topo = detail->getTopology().getPointRef();
164  if (topo)
165  topo->bumpDataId();
166  topo = detail->getTopology().getVertexNextRef();
167  if (topo)
168  topo->bumpDataId();
169  topo = detail->getTopology().getVertexPrevRef();
170  if (topo)
171  topo->bumpDataId();
172  // Edge groups might also be affected, if any edges
173  // were on points that were split, so we bump their
174  // data IDs, just in case.
175  detail->edgeGroups().bumpAllDataIds();
176  }
177  return numpointsadded;
178 }
179 
180 namespace {
181 template<typename T>
182 bool
183 compareVertices(
184  GA_Offset v1,
185  GA_Offset v2,
186  const GA_ROHandleT<T> &attrib,
187  int tuplesize,
188  fpreal tol)
189 {
190  for (int i = 0; i < tuplesize; i++)
191  {
192  if (SYSabs(attrib.get(v1, i) - attrib.get(v2, i)) > tol)
193  return false;
194  }
195  return true;
196 }
197 
198 GA_Size
199 geoSplitPointsByAttrib(
200  GEO_Detail *detail,
201  const GA_Range &points,
202  const GA_ElementGroup *group,
203  const GA_Attribute *attrib,
204  fpreal tolerance)
205 {
206  // Point groups should be weeded out by the wrappers below.
207  UT_ASSERT(!group || group->classType() == GA_GROUP_PRIMITIVE || group->classType() == GA_GROUP_VERTEX);
208  bool is_primgroup = group && group->classType() == GA_GROUP_PRIMITIVE;
209 
210  GA_Size numpointsadded = 0;
211 
212  UT_ASSERT(attrib);
213  if (!attrib)
214  return numpointsadded;
215 
216  GA_AttributeOwner owner = attrib->getOwner();
217  if (owner == GA_ATTRIB_POINT || owner == GA_ATTRIB_DETAIL)
218  return numpointsadded;
219 
220  const GA_AIFCompare *compare = NULL;
221  GA_ROHandleT<int64> attribi;
222  GA_ROHandleR attribf;
223  int tuplesize;
224  const GA_ATINumeric *numeric = dynamic_cast<const GA_ATINumeric *>(attrib);
225  if (numeric)
226  {
227  GA_StorageClass storeclass = attrib->getStorageClass();
228  if (storeclass == GA_STORECLASS_INT)
229  {
230  attribi = attrib;
231  UT_ASSERT(attribi.isValid());
232  tuplesize = attribi.getTupleSize();
233  }
234  else if (storeclass == GA_STORECLASS_FLOAT)
235  {
236  attribf = attrib;
237  UT_ASSERT(attribf.isValid());
238  tuplesize = attribf.getTupleSize();
239  }
240  else
241  {
242  UT_ASSERT_MSG(0, "Why does a GA_ATINumeric have a storage class other than int or float?");
243  compare = attrib->getAIFCompare();
244  }
245  }
246  else
247  {
248  compare = attrib->getAIFCompare();
249  if (compare == NULL)
250  {
251  UT_ASSERT_MSG(0, "Missing an implementation of GA_AIFCompare!");
252  return numpointsadded;
253  }
254  }
255 
256  UT_UniquePtr<GA_PointWrangler> ptwrangler(nullptr);
257 
258  UT_SmallArray<GA_Offset> vtxlist;
259  GA_Size initnumpts = detail->getNumPoints();
260  for (GA_Iterator it(points); !it.atEnd(); ++it)
261  {
262  GA_Offset ptoff = *it;
263  GA_Index ptidx = detail->pointIndex(ptoff);
264  if (ptidx >= initnumpts)
265  break; // New points are already split
266 
267  bool splitfound;
268  do
269  {
270  GA_Offset vtxoff = detail->pointVertex(ptoff);
271  if (!GAisValid(vtxoff))
272  break; // Point doesn't belong to anyone
273 
274  // To avoid the results being dependent on the order of the
275  // linked-list topology attribute, find the highest vertex offset
276  // in the primitive of highest offset to be the base vertex.
277  // Lowest would make more sense, but highest is more compatible with
278  // the previous code.
279  GA_Offset primoff = detail->vertexPrimitive(vtxoff);
280  GA_Offset basevtxoff = GA_INVALID_OFFSET;
281  GA_Offset baseprimoff = GA_INVALID_OFFSET;
282  if (!group || group->contains(is_primgroup ? primoff : vtxoff))
283  {
284  basevtxoff = vtxoff;
285  baseprimoff = primoff;
286  }
287  vtxoff = detail->vertexToNextVertex(vtxoff);
288  while (GAisValid(vtxoff))
289  {
290  primoff = detail->vertexPrimitive(vtxoff);
291 
292  if (!group || group->contains(is_primgroup ? primoff : vtxoff))
293  {
294  if (primoff > baseprimoff || (primoff == baseprimoff && vtxoff > basevtxoff))
295  {
296  basevtxoff = vtxoff;
297  baseprimoff = primoff;
298  }
299  }
300  vtxoff = detail->vertexToNextVertex(vtxoff);
301  }
302 
303  if (!GAisValid(basevtxoff))
304  break; // No vertices in group on current point
305 
306  // We have a vertex on the point that's in the group.
307  // If any vertices mismatch, we make a new point.
308 
309  GA_Offset baseoffset = (owner == GA_ATTRIB_VERTEX) ? basevtxoff : baseprimoff;
310  vtxlist.setSize(1);
311  vtxlist(0) = basevtxoff;
312  splitfound = false;
313  for (vtxoff = detail->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = detail->vertexToNextVertex(vtxoff))
314  {
315  if (vtxoff == basevtxoff)
316  continue;
317  GA_Offset offset = (owner == GA_ATTRIB_VERTEX) ? vtxoff : detail->vertexPrimitive(vtxoff);
318  bool match;
319  if (attribi.isValid())
320  match = compareVertices<int64>(baseoffset, offset, attribi, tuplesize, tolerance);
321  else if (attribf.isValid())
322  match = compareVertices<fpreal>(baseoffset, offset, attribf, tuplesize, tolerance);
323  else
324  {
325  bool success = compare->isEqual(match, *attrib, baseoffset, *attrib, offset);
326  if (!success)
327  match = true;
328  }
329  if (match)
330  {
331  // Only append vertices in the group
332  if (!group || group->contains(is_primgroup ? detail->vertexPrimitive(vtxoff) : vtxoff))
333  {
334  vtxlist.append(vtxoff);
335  }
336  }
337  else
338  {
339  // Split regardless of whether the unmatched vertex is in the group.
340  // This may or may not be the behaviour people expect,
341  // but it's ambiguous as to what people would expect.
342  // At least, if all vertices in the group have one value
343  // and all vertices out of the group have a different value,
344  // they probably want a new point, and that's accomplished
345  // with this condition.
346  splitfound = true;
347  }
348  }
349 
350  if (splitfound)
351  {
352  // A split has been found! Create a new point
353  // using all the entries of vtxlist.
354  GA_Offset newptoff = detail->appendPointOffset();
355  ++numpointsadded;
356  if (!ptwrangler)
357  ptwrangler.reset(new GA_PointWrangler(*detail, GA_AttributeFilter::selectPublic()));
358  ptwrangler->copyAttributeValues(newptoff, ptoff);
359  for (exint i = 0; i < vtxlist.size(); i++)
360  detail->setVertexPoint(vtxlist(i), newptoff);
361  }
362  } while (splitfound);
363  }
364 
365  if (numpointsadded > 0)
366  {
367  // If we'd only added/removed points, we could use
368  // detail->bumpDataIdsForAddOrRemove(true, false, false),
369  // but we also rewired vertices, so we need to bump the
370  // linked-list topology attributes.
372  GA_ATITopology *topo = detail->getTopology().getPointRef();
373  if (topo)
374  topo->bumpDataId();
375  topo = detail->getTopology().getVertexNextRef();
376  if (topo)
377  topo->bumpDataId();
378  topo = detail->getTopology().getVertexPrevRef();
379  if (topo)
380  topo->bumpDataId();
381  // Edge groups might also be affected, if any edges
382  // were on points that were split, so we bump their
383  // data IDs, just in case.
384  detail->edgeGroups().bumpAllDataIds();
385  }
386  return numpointsadded;
387 }
388 }
389 
390 GA_Size
392  GEO_Detail *detail,
393  const GA_ElementGroup *group,
394  const GA_Attribute *attrib,
395  fpreal tolerance)
396 {
397  bool isptgroup = (group == nullptr || group->getOwner() == GA_ATTRIB_POINT);
398  return geoSplitPointsByAttrib(
399  detail,
400  detail->getPointRange(isptgroup ? UTverify_cast<const GA_PointGroup *>(group) : nullptr),
401  isptgroup ? nullptr : group,
402  attrib,
403  tolerance);
404 }
405 
406 GA_Size
408  GEO_Detail *detail,
409  const GA_Range &points,
410  const GA_Attribute *attrib,
411  fpreal tolerance)
412 {
413  return geoSplitPointsByAttrib(
414  detail,
415  points,
416  nullptr,
417  attrib,
418  tolerance);
419 }
420 
421 } // End of HDK_Sample namespace
SYS_FORCE_INLINE void bumpDataId()
Definition: GA_Attribute.h:306
Definition of a geometry attribute.
Definition: GA_Attribute.h:198
GLdouble GLdouble GLint GLint const GLdouble * points
Definition: glad.h:2676
Iteration over a range of elements.
Definition: GA_Iterator.h:29
GA_StorageClass
Definition: GA_Types.h:73
static GA_AttributeFilter selectPublic(bool include_noninternal_groups=true)
Select public scope attributes and non-internal groups.
bool blockAdvance(GA_Offset &start, GA_Offset &end)
GLuint start
Definition: glcorearb.h:475
int64 exint
Definition: SYS_Types.h:125
#define SYSabs(a)
Definition: SYS_Math.h:1572
GA_EdgeGroupTable & edgeGroups()
Definition: GA_Detail.h:1191
GLfloat GLfloat GLfloat v2
Definition: glcorearb.h:818
void setVertexPoint(GA_Offset vertex, GA_Offset ptoff)
Given a vertex, set the corresponding point offset.
Definition: GA_Detail.h:536
SYS_FORCE_INLINE bool GAisValid(GA_Size v)
Definition: GA_Types.h:655
exint size() const
Definition: UT_Array.h:646
void setSize(exint newsize)
Definition: UT_Array.h:666
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:236
#define GA_INVALID_OFFSET
Definition: GA_Types.h:687
A range of elements in an index-map.
Definition: GA_Range.h:42
std::unique_ptr< T, Deleter > UT_UniquePtr
A smart pointer for unique ownership of dynamically allocated objects.
Definition: UT_UniquePtr.h:39
virtual const GA_AIFCompare * getAIFCompare() const
Return the attribute's comparison interface or NULL.
bool combine(const GA_Group *src) overridefinal
#define UT_ASSERT_MSG(ZZ,...)
Definition: UT_Assert.h:159
GA_Size GA_Offset
Definition: GA_Types.h:646
bool atEnd() const
Definition: GA_Iterator.h:93
CompareResults OIIO_API compare(const ImageBuf &A, const ImageBuf &B, float failthresh, float warnthresh, ROI roi={}, int nthreads=0)
GA_Range getPointRange(const GA_PointGroup *group=0) const
Get a range of all points in the detail.
Definition: GA_Detail.h:1738
const GA_IndexMap & getPointMap() const
Definition: GA_Detail.h:744
GLintptr offset
Definition: glcorearb.h:665
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 sort(ComparatorBool is_less={})
Sort using std::sort with bool comparator. Defaults to operator<().
Definition: UT_Array.h:456
GA_StorageClass getStorageClass() const
Returns the approximate type of the attribute.
SYS_FORCE_INLINE const GA_ATITopology * getVertexNextRef() const
Definition: GA_Topology.h:229
GLuint GLuint end
Definition: glcorearb.h:475
GA_AttributeSet & getAttributes()
Definition: GA_Detail.h:799
SYS_FORCE_INLINE GA_Offset pointVertex(GA_Offset point) const
Definition: GA_Detail.h:559
Attribute Interface class to perform comparisons on attributes.
Definition: GA_AIFCompare.h:27
GA_Size GEOsplitPointsByAttrib(GEO_Detail *detail, const GA_Range &points, const GA_Attribute *attrib, fpreal tolerance)
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:203
SYS_FORCE_INLINE GA_Offset lastOffset() const
Definition: GA_IndexMap.h:179
GA_Size GA_Index
Define the strictness of GA_Offset/GA_Index.
Definition: GA_Types.h:640
exint append()
Definition: UT_Array.h:142
SYS_FORCE_INLINE GA_Offset vertexToNextVertex(GA_Offset vtx) const
Definition: GA_Detail.h:571
GA_GroupType classType() const
Definition: GA_Group.h:54
GA_Topology & getTopology()
Definition: GA_Detail.h:801
SYS_FORCE_INLINE bool isValid() const
Definition: GA_Handle.h:187
SYS_FORCE_INLINE GA_Index pointIndex(GA_Offset offset) const
Given a point's data offset, return its index.
Definition: GA_Detail.h:349
GLint j
Definition: glad.h:2733
SYS_FORCE_INLINE GA_Offset vertexPrimitive(GA_Offset vertex) const
Definition: GA_Detail.h:545
SYS_FORCE_INLINE const GA_ATITopology * getVertexPrevRef() const
Definition: GA_Topology.h:227
SYS_FORCE_INLINE GA_Offset appendPointOffset()
Definition: GEO_Detail.h:1143
GA_AttributeOwner
Definition: GA_Types.h:35
virtual bool isEqual(bool &result, const GA_Attribute &a, GA_Offset ai, const GA_Attribute &b, GA_Offset bi) const =0
fpreal64 fpreal
Definition: SYS_Types.h:277
GA_GroupType
An ordinal enum for the different types of groups in GA.
Definition: GA_Types.h:161
GLfloat GLfloat v1
Definition: glcorearb.h:817
SYS_FORCE_INLINE GA_AttributeOwner getOwner() const
Definition: GA_Attribute.h:210
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
GA_Size GEO_API GEOsplitPoints(GEO_Detail *detail, const GA_ElementGroup *group=nullptr)
void bumpAllDataIds(GA_AttributeOwner owner)
Bumps all data IDs of attributes of the specified owner.
UT_UniquePtr< GA_PointGroup > GA_PointGroupUPtr
SYS_FORCE_INLINE bool isEmpty() const
Query whether the group is empty of primary elements.
SYS_FORCE_INLINE const GA_ATITopology * getPointRef() const
Definition: GA_Topology.h:221
SYS_FORCE_INLINE bool contains(GA_Offset offset) const
SYS_FORCE_INLINE GA_Size getNumPoints() const
Return the number of points.
Definition: GA_Detail.h:334