HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_SweepHDK.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2025
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 creates grid surfaces around curves in the first input, optionally
27  * using cross sections from the second input. If only the second input is
28  * connected, it will surface the cross sections where they're already located.
29  */
30 
31 // A .proto.h file is an automatically generated header file based on theDsFile,
32 // below, to provide SOP_SweepHDKParms, an easy way to access parameter
33 // values from SOP_SweepHDKVerb::cook with the correct type, and
34 // SOP_SweepHDKEnums, a namespace containing enum types for any ordinal
35 // menu parameters.
36 #include "SOP_SweepHDK.proto.h"
37 
38 #include "GU_Grid.h"
39 #include "GU_GridImpl.h"
40 
41 #include "../SOP_OrientAlongCurve/GU_CurveFrame.h"
42 #include "../SOP_CopyToPoints/GEO_BuildPrimitives.h"
43 #include "../SOP_CopyToPoints/GU_Copy2.h"
44 
45 #include <SOP/SOP_Node.h>
46 #include <SOP/SOP_NodeVerb.h>
47 #include <GU/GU_Detail.h>
48 #include <GOP/GOP_Manager.h>
49 #include <GEO/GEO_Curve.h>
50 #include <GEO/GEO_Hull.h>
52 #include <GEO/GEO_PrimPoly.h>
53 #include <GEO/GEO_PrimPolySoup.h>
54 #include <GEO/GEO_TPSurf.h>
55 #include <GA/GA_AttributeFilter.h>
57 #include <GA/GA_BezBasis.h>
58 #include <GA/GA_DataBitArray.h>
59 #include <GA/GA_Handle.h>
60 #include <GA/GA_Iterator.h>
61 #include <GA/GA_Names.h>
62 #include <GA/GA_NUBBasis.h>
63 #include <GA/GA_OffsetList.h>
64 #include <GA/GA_PolyCounts.h>
65 #include <GA/GA_SplittableRange.h>
66 #include <GA/GA_Types.h>
67 #include <OP/OP_AutoLockInputs.h>
68 #include <OP/OP_Operator.h>
69 #include <OP/OP_OperatorTable.h>
71 #include <UT/UT_BitArray.h>
72 #include <UT/UT_BoundingCircle.h>
73 #include <UT/UT_DSOVersion.h>
74 #include <UT/UT_Interrupt.h>
75 #include <UT/UT_PageArray.h>
76 #include <UT/UT_PageArrayImpl.h>
77 #include <UT/UT_ParallelUtil.h>
78 #include <UT/UT_StringHolder.h>
79 #include <UT/UT_UniquePtr.h>
80 #include <UT/UT_Vector3.h>
81 #include <SYS/SYS_Math.h>
82 
83 #include <algorithm> // For std::make_heap, push_heap, pop_heap
84 
85 using namespace UT::Literal;
86 
87 namespace HDK_Sample {
88 
89 //******************************************************************************
90 //* Setup *
91 //******************************************************************************
92 
93 constexpr static int theUComponent = 0;
94 constexpr static int theVComponent = 1;
95 
96 constexpr static int theCurveInput = 0;
97 constexpr static int theCrossSectionInput = 1;
98 
99 namespace {
100 struct CrossSectionAttribMatchData
101 {
102  /// Handle for reading int64 attribute on curve input.
103  /// If valid, it could be a point, vertex, primitive, or detail attribute.
104  /// If invalid, myCurveStrAttrib might be valid.
106 
107  /// Handle for reading string attribute on curve input.
108  /// If valid, it could be a point, vertex, primitive, or detail attribute.
109  /// This can only be valid if myCurveIntAttrib is invalid.
111 
112  /// Map from integer to primitive offset in the cross section input.
113  /// This won't be used at the same time as myStrToPrimOff.
114  /// NOTE: UT::ArrayMap<exint,...> doesn't support 0x8000000000000000LL
115  /// as a key, which is probably safe to exclude.
116  /// If you need a map without any excluded values, UT_Map works.
118 
119  /// Map from string to primitive offset in the cross section input.
120  /// This won't be used at the same time as myIntToPrimOff.
121  /// NOTE: UT_ArrayStringMap doesn't support "" as a key, so any
122  /// primitives with name "" won't be matched, which is probably
123  /// what'd be expected anyway.
124  /// If you need a map without any excluded values, UT_Map works.
126 
127  /// This is just a cache of the owner of myCurveIntAttrib or
128  /// myCurveStrAttrib, whichever is valid, or GA_ATTRIB_INVALID if
129  /// neither is valid.
131 
132  /// If myCurveIntAttrib is valid and the cross section geometry doesn't
133  /// have a matching attribute, it will use the primitive index, instead
134  /// of a map.
135  bool myIsUsingMap = false;
136 };
137 
138 
139 struct sop_SweepGrid
140 {
141  sop_SweepGrid()
142  : mySingleCrossSection(true)
143  {}
144  ~sop_SweepGrid()
145  {
147  delete myCrossSectionPrimOffs;
148  }
149 
150  // Copy constructor, for handling myCrossSectionPrimOffs union.
151  sop_SweepGrid(const sop_SweepGrid &that)
152  : myStartPtOff(that.myStartPtOff)
164  , myUEndPoles(that.myUEndPoles)
165  , myVEndPoles(that.myVEndPoles)
170  {
171  if (that.mySingleCrossSection)
172  myCrossSectionPrimOff = that.myCrossSectionPrimOff;
173  else
174  myCrossSectionPrimOffs = new GA_OffsetList(*that.myCrossSectionPrimOffs);
175  }
176 
177  // Move constructor, for handling myCrossSectionPrimOffs union.
178  sop_SweepGrid(sop_SweepGrid &&that)
179  : myStartPtOff(that.myStartPtOff)
191  , myUEndPoles(that.myUEndPoles)
192  , myVEndPoles(that.myVEndPoles)
197  {
198  if (that.mySingleCrossSection)
199  myCrossSectionPrimOff = that.myCrossSectionPrimOff;
200  else
201  {
202  myCrossSectionPrimOffs = that.myCrossSectionPrimOffs;
203  that.mySingleCrossSection = true;
204  }
205  }
206 
207  // Copy assignment operator, for handling myCrossSectionPrimOffs union.
208  sop_SweepGrid &operator=(const sop_SweepGrid &that)
209  {
210  if (this == &that)
211  return *this;
212 
213  myStartPtOff = that.myStartPtOff;
214  myStartPrimOff = that.myStartPrimOff;
215  myStartVtxOff = that.myStartVtxOff;
216  myCurveNEdges = that.myCurveNEdges;
217  myCrossSectionNEdges = that.myCrossSectionNEdges;
218  myCurvePrimOff = that.myCurvePrimOff;
220  delete myCrossSectionPrimOffs;
221  if (that.mySingleCrossSection)
222  myCrossSectionPrimOff = that.myCrossSectionPrimOff;
223  else
224  myCrossSectionPrimOffs = new GA_OffsetList(*that.myCrossSectionPrimOffs);
225  myCurveClosed = that.myCurveClosed;
226  myCurveUnrolled = that.myCurveUnrolled;
227  myCrossSectionClosed = that.myCrossSectionClosed;
228  myCrossSectionUnrolled = that.myCrossSectionUnrolled;
229  mySingleCrossSection = that.mySingleCrossSection;
230  myAllEqualNEdges = that.myAllEqualNEdges;
231  myUEndPoles = that.myUEndPoles;
232  myVEndPoles = that.myVEndPoles;
233  myHasPolygonCaps = that.myHasPolygonCaps;
234  myPrimitiveType = that.myPrimitiveType;
235  myBasisOrderCurve = that.myBasisOrderCurve;
236  myBasisOrderCrossSection = that.myBasisOrderCrossSection;
237  return *this;
238  }
239 
240  // Move assignment operator, for handling myCrossSectionPrimOffs union.
241  sop_SweepGrid &operator=(sop_SweepGrid &&that)
242  {
243  if (this == &that)
244  return *this;
245 
246  myStartPtOff = that.myStartPtOff;
247  myStartPrimOff = that.myStartPrimOff;
248  myStartVtxOff = that.myStartVtxOff;
249  myCurveNEdges = that.myCurveNEdges;
250  myCrossSectionNEdges = that.myCrossSectionNEdges;
251  myCurvePrimOff = that.myCurvePrimOff;
253  delete myCrossSectionPrimOffs;
254  if (that.mySingleCrossSection)
255  myCrossSectionPrimOff = that.myCrossSectionPrimOff;
256  else
257  {
258  myCrossSectionPrimOffs = that.myCrossSectionPrimOffs;
259  that.mySingleCrossSection = true;
260  }
261  myCurveClosed = that.myCurveClosed;
262  myCurveUnrolled = that.myCurveUnrolled;
263  myCrossSectionClosed = that.myCrossSectionClosed;
264  myCrossSectionUnrolled = that.myCrossSectionUnrolled;
265  mySingleCrossSection = that.mySingleCrossSection;
266  myAllEqualNEdges = that.myAllEqualNEdges;
267  myUEndPoles = that.myUEndPoles;
268  myVEndPoles = that.myVEndPoles;
269  myHasPolygonCaps = that.myHasPolygonCaps;
270  myPrimitiveType = that.myPrimitiveType;
271  myBasisOrderCurve = that.myBasisOrderCurve;
272  myBasisOrderCrossSection = that.myBasisOrderCrossSection;
273  return *this;
274  }
275 
282  union {
283  /// NOTE: This is exint instead of GA_Offset just because unions need
284  /// all POD types, and GA_Offset isn't POD in strict types builds.
287  };
294  bool myUEndPoles:1;
295  bool myVEndPoles:1;
297  using PrimitiveType = GU_GridT<GA_Offset>::PrimitiveType;
298  PrimitiveType myPrimitiveType;
299  unsigned char myBasisOrderCurve:4;
300  unsigned char myBasisOrderCrossSection:4;
301 };
302 }
303 
305 {
306 public:
308  myPrevCurvePrimListDataID(-1),
309  myPrevCurveTopologyDataID(-1),
310  myPrevOutputDetailID(-1),
311  myPrevCopyOrder(SOP_SweepHDKEnums::CopyOrder::CYCLEVTX),
312  myPrevSurfaceType(SOP_SweepHDKEnums::SurfaceType::POINTS),
313  myPrevPrimType(SOP_SweepHDKEnums::PrimType::AUTO),
314  myPrevSurfaceShape(SOP_SweepHDKEnums::SurfaceShape::INPUT),
315  myPrevCols(-1),
316  myPrevEndCapType(SOP_SweepHDKEnums::EndCapType::NONE),
317  myPrevEndCapDivs(-1),
318  myPrevCrossSectionPrimListDataID(-1),
319  myPrevCrossSectionTopologyDataID(-1),
320  myPrevUnrollClosedRowCol(false),
321  myPrevCloseIfNoCurveInput(false),
322  myPrevTriangularPoles(false),
323  myPrevSwapRowCol(false),
324  myPrevCrossSectCurveAttribDataId(-1),
325  myPrevCrossSectPrimAttribDataId(-1)
326  {}
327  ~SOP_SweepHDKCache() override {}
328 
336 
340  /// We'll use NONE to represent using the cross section input.
351  CrossSectionAttribMatchData myCrossSectionAttribMatchData;
352 
355 
356  // These are indices into the transform arrays for the start of each grid.
358 };
359 
361 {
363 
364  void init(const SOP_SweepHDKCache &cache, exint gridi)
365  {
366  exint offset = cache.myGridTransformStarts[gridi];
367  myTranslated = cache.myTransformTranslates3D ? (cache.myTransformTranslates3D.get() + offset) : nullptr;
368  myMatrix3d = cache.myTransformMatrices3D ? (cache.myTransformMatrices3D.get() + offset) : nullptr;
369  myInverse3d = cache.myTransformInverse3D ? (cache.myTransformInverse3D.get() + offset) : nullptr;
370  myQuaterniond = cache.myTransformQuaternionsD ? (cache.myTransformQuaternionsD.get() + offset) : nullptr;
371  myTranslatef = cache.myTransformTranslates3F ? (cache.myTransformTranslates3F.get() + offset) : nullptr;
372  myMatrix3f = cache.myTransformMatrices3F ? (cache.myTransformMatrices3F.get() + offset) : nullptr;
373  myInverse3f = cache.myTransformInverse3F ? (cache.myTransformInverse3F.get() + offset) : nullptr;
374  myQuaternionf = cache.myTransformQuaternionsF ? (cache.myTransformQuaternionsF.get() + offset) : nullptr;
375  }
376 
377  template<typename T>
379  {
380  SYS_STATIC_ASSERT((SYSisSame<T,double>()) || (SYSisSame<T,float>()));
381 
382  // NOTE: The reinterpret_cast's are just needed to get this to compile;
383  // the returned value should not change type.
384  return SYSisSame<T,double>()
385  ? reinterpret_cast<const UT_Vector3T<T> *>(myTranslated)
386  : reinterpret_cast<const UT_Vector3T<T> *>(myTranslatef);
387  }
388 
389  template<typename T>
391  {
392  SYS_STATIC_ASSERT((SYSisSame<T,double>()) || (SYSisSame<T,float>()));
393 
394  // NOTE: The reinterpret_cast's are just needed to get this to compile;
395  // the returned value should not change type.
396  return SYSisSame<T,double>()
397  ? reinterpret_cast<const UT_Matrix3T<T> *>(myInverse3d)
398  : reinterpret_cast<const UT_Matrix3T<T> *>(myInverse3f);
399  }
400 
401  template<typename T>
403  {
404  SYS_STATIC_ASSERT((SYSisSame<T,double>()) || (SYSisSame<T,float>()));
405 
406  // NOTE: The reinterpret_cast's are just needed to get this to compile;
407  // the returned value should not change type.
408  return SYSisSame<T,double>()
409  ? reinterpret_cast<const UT_Matrix3T<T> *>(myMatrix3d)
410  : reinterpret_cast<const UT_Matrix3T<T> *>(myMatrix3f);
411  }
412 
413  template<typename T>
415  {
416  SYS_STATIC_ASSERT((SYSisSame<T,double>()) || (SYSisSame<T,float>()));
417 
418  // NOTE: The reinterpret_cast's are just needed to get this to compile;
419  // the returned value should not change type.
420  return SYSisSame<T,double>()
421  ? reinterpret_cast<const UT_QuaternionT<T> *>(myQuaterniond)
422  : reinterpret_cast<const UT_QuaternionT<T> *>(myQuaternionf);
423  }
424 
433 };
434 
435 
437 {
438 public:
439  SOP_NodeParms *allocParms() const override { return new SOP_SweepHDKParms(); }
440  SOP_NodeCache *allocCache() const override { return new SOP_SweepHDKCache(); }
441  UT_StringHolder name() const override { return theSOPTypeName; }
442 
443  CookMode cookMode(const SOP_NodeParms *parms) const override { return COOK_GENERIC; }
444 
445  void cook(const CookParms &cookparms) const override;
446 
447  /// This is the internal name of the SOP type.
448  /// It isn't allowed to be the same as any other SOP's type name.
450 
451  /// This static data member automatically registers
452  /// this verb class at library load time.
454 
455  /// This is the parameter interface string, below.
456  static const char *const theDsFile;
457 };
458 
459 // The static member variable definitions have to be outside the class definition.
460 // The declarations are inside the class.
461 const UT_StringHolder SOP_SweepHDKVerb::theSOPTypeName("hdk_sweep"_sh);
462 const SOP_NodeVerb::Register<SOP_SweepHDKVerb> SOP_SweepHDKVerb::theVerb;
463 
464 /// This is the SOP class definition.
465 class SOP_SweepHDK : public SOP_Node
466 {
467 public:
468  static PRM_Template *buildTemplates();
469 
470  static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op)
471  {
472  return new SOP_SweepHDK(net, name, op);
473  }
474 
475 protected:
476  const SOP_NodeVerb *cookVerb() const override;
477 
479  : SOP_Node(net, name, op)
480  {
481  // All verb SOPs must manage data IDs, to track what's changed
482  // from cook to cook.
483  mySopFlags.setManagesDataIDs(true);
484  }
485 
486  ~SOP_SweepHDK() override {}
487 
488  OP_ERROR cookInputGroups(OP_Context &context, int alone) override;
489 
490  /// Since this SOP implements a verb, cookMySop just delegates to the verb.
491  OP_ERROR cookMySop(OP_Context &context) override
492  {
493  return cookMyselfAsVerb(context);
494  }
495 
496  /// These are the labels that appear when hovering over the inputs.
497  const char *inputLabel(OP_InputIdx idx) const override
498  {
499  UT_ASSERT(idx >= 0);
500  switch (idx)
501  {
502  case 0: return "Backbone Curves";
503  case 1: return "Cross Section(s)";
504  default: return "Invalid Source";
505  }
506  }
507 
508  /// This just indicates whether an input wire gets drawn with a dotted line
509  /// in the network editor. If something is usually copied directly
510  /// into the output, a solid line (false) is used, but this SOP very often
511  /// doesn't do that for either input.
512  int isRefInput(OP_InputIdx i) const override
513  {
514  UT_ASSERT(i >= 0);
515  // First or second input both use dotted lines
516  return (i == 0 || i == 1);
517  }
518 };
519 
520 PRM_Template *SOP_SweepHDK::buildTemplates()
521 {
522  static PRM_TemplateBuilder templ("SOP_SweepHDK.C"_sh, SOP_SweepHDKVerb::theDsFile);
523  if (templ.justBuilt())
524  {
525  templ.setChoiceListPtr("curvegroup", &SOP_Node::primGroupMenu);
526  // FIXME: This needs to select primitive groups from input 1, not input 0!!!
527  templ.setChoiceListPtr("crosssectiongroup", &SOP_Node::primGroupMenu);
528  }
529  return templ.templates();
530 }
531 
532 const SOP_NodeVerb *SOP_SweepHDK::cookVerb() const
533 {
534  return SOP_SweepHDKVerb::theVerb.get();
535 }
536 
537 } // End of HDK_Sample namespace
538 
539 /// newSopOperator is the hook that Houdini grabs from this dll
540 /// and invokes to register the SOP. In this case, we add ourselves
541 /// to the specified operator table.
543 {
544  // NOTE: Even though this SOP requires at least one of the two
545  // inputs to be connected, if we specified the minimum number
546  // of sources as 1, the node would error out when providing only
547  // the second (cross section) input, which isn't what we want,
548  // so instead, we specify 0 as the minimum number of sources,
549  // and explicitly error in the cook function if neither input is
550  // present.
551  table->addOperator(new OP_Operator(
553  "HDK Sweep", // UI name
554  HDK_Sample::SOP_SweepHDK::myConstructor, // How to build the SOP
555  HDK_Sample::SOP_SweepHDK::buildTemplates(), // My parameters
556  0, // Min # of sources
557  2, // Max # of sources
558  nullptr,// Custom local variables (none)
559  0)); // No flags: not a generator, no merge input, not an output
560 }
561 
562 namespace HDK_Sample {
563 
564 OP_ERROR
565 SOP_SweepHDK::cookInputGroups(OP_Context &context, int alone)
566 {
567  UT_ASSERT(alone);
568  OP_AutoLockInputs inputs(this);
569  if (inputs.lock(context) >= UT_ERROR_ABORT)
570  return error();
571 
572  const GU_Detail *curve_input = inputGeo(theCurveInput);
573  const GA_PrimitiveGroup *curve_group;
574  OP_ERROR ret = cookInputPrimitiveGroups(context, curve_group, alone, true, 0, -1, true, false, GroupCreator(curve_input));
575  if (ret >= UT_ERROR_ABORT)
576  return ret;
577 
578  const GU_Detail *cross_section_input = inputGeo(theCrossSectionInput);
579  const GA_PrimitiveGroup *cross_section_group;
580  ret = cookInputPrimitiveGroups(context, cross_section_group, alone, true, 1, -1, true, false, GroupCreator(cross_section_input));
581  return ret;
582 }
583 
584 //******************************************************************************
585 //* Parameters *
586 //******************************************************************************
587 
588 /// This is a multi-line raw string specifying the parameter interface for this SOP.
589 const char *const SOP_SweepHDKVerb::theDsFile = R"THEDSFILE(
590 {
591  name parameters
592  parm {
593  name "curvegroup"
594  cppname "CurveGroup"
595  label "Backbone Curve Group"
596  type string
597  default { "" }
598  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
599  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
600  parmtag { "script_action_icon" "BUTTONS_reselect" }
601  }
602  parm {
603  name "crosssectiongroup"
604  cppname "CrossSectionGroup"
605  label "Cross Section Group"
606  type string
607  default { "" }
608  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 1\nsoputils.selectGroupParm(kwargs)" }
609  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
610  parmtag { "script_action_icon" "BUTTONS_reselect" }
611  }
612  parm {
613  name "sepparm"
614  label ""
615  type separator
616  default { "" }
617  }
618  group {
619  name "surface_folder"
620  label "Surface"
621  parm {
622  name "surfaceshape"
623  cppname "SurfaceShape"
624  label "Surface Shape"
625  type ordinal
626  default { "0" } // Default to first entry in menu, "input"
627  menu {
628  "input" "Second Input Cross Sections"
629  "tube" "Round Tube"
630  "square" "Square Tube"
631  "ribbon" "Ribbon"
632  }
633  }
634  parm {
635  name "surfacetype"
636  cppname "SurfaceType"
637  label "Surface Type"
638  type ordinal
639  default { "5" } // Default to menu entry "quads"
640  menu {
641  "points" "Points"
642  "rows" "Rows"
643  "cols" "Columns"
644  "rowcol" "Rows and Columns"
645  "tris" "Triangles"
646  "quads" "Quadrilaterals"
647  "alttris" "Alternating Triangles"
648  "revtris" "Reverse Triangles"
649  }
650  }
651  parm {
652  name "scale"
653  label "Scale Cross Sections"
654  type float
655  default { "1" }
656  range { 0 4 }
657  disablewhen "{ surfaceshape != input }"
658  hidewhen "{ surfaceshape != input }"
659  }
660  parm {
661  name "cols"
662  label "Columns"
663  type integer
664  default { "8" }
665  range { 1! 24 }
666  disablewhen "{ surfaceshape == input }"
667  hidewhen "{ surfaceshape == input }"
668  }
669  parm {
670  name "radius"
671  label "Radius"
672  type float
673  default { "0.1" }
674  range { 0 1 }
675  parmtag { "units" "m1" }
676  disablewhen "{ surfaceshape != tube }"
677  hidewhen "{ surfaceshape != tube }"
678  }
679  parm {
680  name "width"
681  label "Width"
682  type float
683  default { "0.2" }
684  range { 0 1 }
685  parmtag { "units" "m1" }
686  disablewhen "{ surfaceshape != ribbon surfaceshape != square }"
687  hidewhen "{ surfaceshape != ribbon surfaceshape != square }"
688  }
689  parm {
690  name "reversecrosssections"
691  cppname "ReverseCrossSections"
692  label "Reverse Cross Sections"
693  type toggle
694  default { "0" }
695  }
696  parm {
697  name "stretcharoundturns"
698  cppname "StretchAroundTurns"
699  label "Stretch Around Turns"
700  type toggle
701  default { "1" }
702  }
703  parm {
704  name "maxstretcharoundturns"
705  cppname "MaxStretchAroundTurns"
706  label "Max Stretch"
707  type log
708  default { "10" }
709  range { 1! 100 }
710  disablewhen "{ stretcharoundturns == 0 }"
711  }
712  groupsimple {
713  name "endcaps_folder"
714  label "End Caps"
715  parm {
716  name "endcaptype"
717  cppname "EndCapType"
718  label "End Cap Type"
719  type ordinal
720  default { "0" } // Default to menu entry "none"
721  menu {
722  "none" "None"
723  "single" "Single Polygon"
724  "grid" "Grid"
725  "sidesingle" "Side Single Polygon"
726  }
727  }
728  parm {
729  name "capdivs"
730  cppname "CapDivs"
731  label "Cap Divisions"
732  type integer
733  default { "3" }
734  range { 1! 10 }
735  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
736  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
737  joinnext
738  }
739  parm {
740  name "triangularpoles"
741  cppname "TriangularPoles"
742  label "Triangular Poles"
743  type toggle
744  default { "0" }
745  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
746  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
747  }
748  parm {
749  name "capscale"
750  cppname "CapScale"
751  label "End Cap Scale"
752  type float
753  default { "1" }
754  range { 0 1 }
755  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
756  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
757  }
758  parm {
759  name "caproundness"
760  cppname "CapRoundness"
761  label "End Cap Roundness"
762  type float
763  default { "1" }
764  range { 0 1 }
765  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
766  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
767  }
768  parm {
769  name "addendcapsgroup"
770  cppname "AddEndCapsGroup"
771  label "Add End Caps Group"
772  type toggle
773  default { "0" }
774  nolabel
775  joinnext
776  }
777  parm {
778  name "endcapsgroup"
779  cppname "EndCapsGroup"
780  label "End Caps Group"
781  type string
782  default { "endcaps" }
783  disablewhen "{ addendcapsgroup == 0 }"
784  }
785  }
786  groupsimple {
787  name "scale_folder"
788  label "Scale"
789  parm {
790  name "applyscale"
791  cppname "ApplyScale"
792  label "Apply Scale Along Curve"
793  type toggle
794  default { "0" }
795  }
796  parm {
797  name "scaleramp"
798  cppname "ScaleRamp"
799  label "Scale Ramp"
800  type ramp_flt
801  default { "2" }
802  range { 0! 10 }
803  disablewhen "{ applyscale == 0 }"
804  parmtag { "rampfloatdefault" "1pos ( 0 ) 1value ( 1 ) 1interp ( linear ) 2pos ( 1 ) 2value ( 1 ) 2interp ( linear )" }
805  }
806  }
807  groupsimple {
808  name "rotation_folder"
809  label "Rotation"
810  parm {
811  name "rOrd"
812  cppname "ROrd"
813  label "Rotate Order"
814  type ordinal
815  // NOTE: The default rotation order X,Y,Z is semi-arbitrary, but Z
816  // should probably be last, since it always needs to twist
817  // around the curve tangent. The X and Y rotations may have
818  // just been to reorient a cross-section before copying.
819  default { "xyz" }
820  menu {
821  "xyz" "Pitch, Yaw, Roll"
822  "xzy" "Pitch, Roll, Yaw"
823  "yxz" "Yaw, Pitch, Roll"
824  "yzx" "Yaw, Roll, Pitch"
825  "zxy" "Roll, Pitch, Yaw"
826  "zyx" "Roll, Yaw, Pitch"
827  }
828  }
829  parm {
830  name "applyroll"
831  cppname "ApplyRoll"
832  label "Apply Roll or Twist"
833  type toggle
834  default { "1" }
835  }
836  parm {
837  name "roll"
838  label "Roll"
839  type float
840  default { "0" }
841  range { -180 180 }
842  hidewhen "{ applyroll == 0 }"
843  }
844  parm {
845  name "fulltwists"
846  cppname "FullTwists"
847  label "Full Twists"
848  type integer
849  default { "0" }
850  range { -10 10 }
851  hidewhen "{ applyroll == 0 }"
852  }
853  parm {
854  name "incroll"
855  cppname "IncRoll"
856  label "Partial Twist"
857  type float
858  default { "0" }
859  range { -180 180 }
860  hidewhen "{ applyroll == 0 }"
861  joinnext
862  }
863  parm {
864  name "rollper"
865  cppname "RollPer"
866  label "Twist Per"
867  type ordinal
868  default { "4" } // Default to "fulldistance" entry in menu
869  menu {
870  "edge" "Per Edge"
871  "distance" "Per Unit Distance"
872  "attrib" "Scale by Attribute"
873  "fulledges" "Per Full Curve by Edges"
874  "fulldistance" "Per Full Curve by Distance"
875  }
876  hidewhen "{ applyroll == 0 }"
877  }
878  parm {
879  name "rollattrib"
880  cppname "RollAttrib"
881  label "Twist Ramp Attribute"
882  type string
883  default { "roll" }
884  disablewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
885  hidewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
886  }
887  parm {
888  name "sepparmroll"
889  label ""
890  type separator
891  default { "" }
892  hidewhen "{ applyroll == 0 }"
893  }
894  parm {
895  name "applyyaw"
896  cppname "ApplyYaw"
897  label "Apply Yaw"
898  type toggle
899  default { "0" }
900  }
901  parm {
902  name "yaw"
903  label "Yaw"
904  type float
905  default { "0" }
906  range { -180 180 }
907  hidewhen "{ applyyaw == 0 }"
908  }
909  parm {
910  name "incyaw"
911  cppname "IncYaw"
912  label "Incremental Yaw"
913  type float
914  default { "0" }
915  range { -180 180 }
916  hidewhen "{ applyyaw == 0 }"
917  joinnext
918  }
919  parm {
920  name "yawper"
921  cppname "YawPer"
922  label "Yaw Per"
923  type ordinal
924  default { "4" } // Default to "fulldistance" entry in menu
925  menu {
926  "edge" "Per Edge"
927  "distance" "Per Unit Distance"
928  "attrib" "Scale By Attribute"
929  "fulledges" "Per Full Curve by Edges"
930  "fulldistance" "Per Full Curve by Distance"
931  }
932  hidewhen "{ applyyaw == 0 }"
933  }
934  parm {
935  name "yawattrib"
936  cppname "YawAttrib"
937  label "Yaw Ramp Attribute"
938  type string
939  default { "yaw" }
940  disablewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
941  hidewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
942  }
943  parm {
944  name "sepparmyaw"
945  label ""
946  type separator
947  default { "" }
948  hidewhen "{ applyyaw == 0 }"
949  }
950  parm {
951  name "applypitch"
952  cppname "ApplyPitch"
953  label "Apply Pitch"
954  type toggle
955  default { "0" }
956  }
957  parm {
958  name "pitch"
959  label "Pitch"
960  type float
961  default { "0" }
962  range { -180 180 }
963  hidewhen "{ applypitch == 0 }"
964  }
965  parm {
966  name "incpitch"
967  cppname "IncPitch"
968  label "Incremental Pitch"
969  type float
970  default { "0" }
971  range { -180 180 }
972  hidewhen "{ applypitch == 0 }"
973  joinnext
974  }
975  parm {
976  name "pitchper"
977  cppname "PitchPer"
978  label "Pitch Per"
979  type ordinal
980  default { "4" } // Default to "fulldistance" entry in menu
981  menu {
982  "edge" "Per Edge"
983  "distance" "Per Unit Distance"
984  "attrib" "Scale By Attribute"
985  "fulledges" "Per Full Curve by Edges"
986  "fulldistance" "Per Full Curve by Distance"
987  }
988  hidewhen "{ applypitch == 0 }"
989  }
990  parm {
991  name "pitchattrib"
992  cppname "PitchAttrib"
993  label "Pitch Ramp Attribute"
994  type string
995  default { "pitch" }
996  disablewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
997  hidewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
998  }
999  }
1000  }
1001 )THEDSFILE"
1002 // ==== This is necessary because MSVC++ has a limit of 16380 character per
1003 // ==== string literal
1004 R"THEDSFILE(
1005  group {
1006  name "construction_folder"
1007  label "Construction"
1008  groupsimple {
1009  name "cross_sections_folder"
1010  label "Cross Sections"
1011  parm {
1012  name "copyorder"
1013  cppname "CopyOrder"
1014  label "Cross Section Order"
1015  type ordinal
1016  default { "1" } // Default to third entry in menu, "each"
1017  menu {
1018  "all" "All Cross Sections At Each Curve Vertex"
1019  "each" "Each Cross Section At All Curve Vertices"
1020  "cyclevtx" "Cycle Through Cross Section Primitives per Vertex"
1021  "cyclepr" "Cycle Through Cross Section Primitives per Curve"
1022  "attrib" "Choose Cross Section Primitives by Attribute"
1023  }
1024  disablewhen "{ surfaceshape != input }"
1025  }
1026  parm {
1027  name "crosssectionattrib"
1028  cppname "CrossSectionAttrib"
1029  label "Cross Section Attribute"
1030  type string
1031  default { "variant" }
1032  disablewhen "{ surfaceshape != input } { copyorder != attrib }"
1033  hidewhen "{ surfaceshape != input } { copyorder != attrib }"
1034  }
1035  parm {
1036  name "primtype"
1037  cppname "PrimType"
1038  label "Primitive Type"
1039  type ordinal
1040  default { "0" } // Default to menu entry "auto"
1041  menu {
1042  "auto" "Automatic"
1043  "poly" "Polygons"
1044  "mesh" "Bilinear Mesh"
1045  "nurbs" "NURBS Surface"
1046  "bezier" "Bezier Surface"
1047  "polysoup" "Polygon Soup"
1048  }
1049  disablewhen "{ surfacetype == points }"
1050  }
1051  parm {
1052  name "unrollclosedrowcol"
1053  cppname "UnrollClosedRowCol"
1054  label "Ensure Unique Seam Vertices"
1055  type toggle
1056  default { "0" }
1057  disablewhen "{ surfacetype == points }"
1058  }
1059  parm {
1060  name "swaprowcol"
1061  cppname "SwapRowCol"
1062  label "Swap Rows and Columns"
1063  type toggle
1064  default { "0" }
1065  }
1066  parm {
1067  name "closeifnocurveinput"
1068  cppname "CloseIfNoCurveInput"
1069  label "Close Implicit Backbone Curve if No Curve Input"
1070  type toggle
1071  default { "0" }
1072  disablewhen "{ surfacetype == points } { surfacetype == rows } { hasinput(0) != 0 }"
1073  }
1074  //parm {
1075  // name "segdivs"
1076  // cppname "SegDivs"
1077  // label "Segment Divisions"
1078  // type integer
1079  // default { "1" }
1080  // range { 1! 10 }
1081  //}
1082  }
1083  groupsimple {
1084  name "up_folder"
1085  label "Up Vectors"
1086  parm {
1087  name "upvectortype"
1088  cppname "UpVectorType"
1089  label "Target Up Vector"
1090  type ordinal
1091  default { "0" } // Default to first entry in menu, "normal"
1092  menu {
1093  "normal" "Curve Normal"
1094  "x" "X Axis"
1095  "y" "Y Axis"
1096  "z" "Z Axis"
1097  "attrib" "Attribute"
1098  "custom" "Custom"
1099  }
1100  disablewhen "{ tangenttype == none }"
1101  }
1102  //parm {
1103  // name "usenormalup"
1104  // cppname "UseNormalUp"
1105  // label "Use Curve Normal as Up Vector (When Valid)"
1106  // type toggle
1107  // default { "1" }
1108  // disablewhen "{ tangenttype == none }"
1109  //}
1110  parm {
1111  name "upvectoratstart"
1112  cppname "UpVectorAtStart"
1113  label "Target Up Vector at Start (else Average)"
1114  type toggle
1115  default { "1" }
1116  disablewhen "{ tangenttype == none }"
1117  }
1118  parm {
1119  name "useendupvector"
1120  cppname "UseEndUpVector"
1121  label "Use Target End Up Vector"
1122  type toggle
1123  default { "0" }
1124  disablewhen "{ tangenttype == none } { upvectoratstart == 0 }"
1125  }
1126  parm {
1127  name "upvectorattrib"
1128  cppname "UpVectorAttrib"
1129  label "Start Up Attribute"
1130  type string
1131  default { "start_up" }
1132  disablewhen "{ tangenttype == none } { upvectortype != attrib }"
1133  hidewhen "{ tangenttype == none } { upvectortype != attrib }"
1134  }
1135  parm {
1136  name "endupvectorattrib"
1137  cppname "EndUpVectorAttrib"
1138  label "End Up Attribute"
1139  type string
1140  default { "end_up" }
1141  disablewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
1142  hidewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
1143  }
1144  parm {
1145  name "upvector"
1146  cppname "UpVector"
1147  label "Start Up Vector"
1148  type vector
1149  size 3
1150  default { "0" "1" "0" }
1151  disablewhen "{ tangenttype == none } { upvectortype != custom }"
1152  hidewhen "{ tangenttype == none } { upvectortype != custom }"
1153  }
1154  parm {
1155  name "endupvector"
1156  cppname "EndUpVector"
1157  label "End Up Vector"
1158  type vector
1159  size 3
1160  default { "0" "1" "0" }
1161  disablewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
1162  hidewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
1163  }
1164  }
1165  groupsimple {
1166  name "tangents_folder"
1167  label "Tangents"
1168  parm {
1169  name "tangenttype"
1170  cppname "TangentType"
1171  label "Tangent Type"
1172  type ordinal
1173  default { "0" } // Default to first entry in menu, "avgdir"
1174  menu {
1175  "avgdir" "Average of Edge Directions"
1176  "diff" "Central Difference"
1177  "prev" "Previous Edge"
1178  "next" "Next Edge"
1179  "none" "Z Axis (Ignore Curve)"
1180  }
1181  }
1182  parm {
1183  name "continuousclosed"
1184  cppname "ContinuousClosed"
1185  label "Make Closed Curve Orientations Continuous"
1186  type toggle
1187  default { "1" }
1188  disablewhen "{ tangenttype == none }"
1189  }
1190  parm {
1191  name "extrapolateendtangents"
1192  cppname "ExtrapolateEndTangents"
1193  label "Extrapolate End Tangents"
1194  type toggle
1195  default { "0" }
1196  disablewhen "{ tangenttype == none }"
1197  }
1198  parm {
1199  name "transformbyattribs"
1200  cppname "TransformByAttribs"
1201  label "Transform Using Curve Point Attributes"
1202  type toggle
1203  default { "1" }
1204  }
1205  }
1206  }
1207 )THEDSFILE"
1208 // ==== This is necessary because MSVC++ has a limit of 16380 character per
1209 // ==== string literal
1210 R"THEDSFILE(
1211  group {
1212  name "uvs_folder"
1213  label "UVs and Attributes"
1214  groupsimple {
1215  name "uv_folder"
1216  label "UV Coordinates"
1217  parm {
1218  name "computeuvs"
1219  cppname "ComputeUVs"
1220  label "Compute UVs"
1221  type toggle
1222  default { "0" }
1223  }
1224  parm {
1225  name "overrideexistinguvs"
1226  cppname "OverrideExistingUVs"
1227  label "Override Any Existing UVs"
1228  type toggle
1229  default { "0" }
1230  disablewhen "{ computeuvs == 0 }"
1231  }
1232  parm {
1233  name "lengthweighteduvs"
1234  cppname "LengthWeightedUVs"
1235  label "Length-Weighted UVs"
1236  type toggle
1237  default { "1" }
1238  disablewhen "{ computeuvs == 0 }"
1239  }
1240  parm {
1241  name "normalizeu"
1242  cppname "NormalizeU"
1243  label "Normalize Computed Us"
1244  type toggle
1245  default { "1" }
1246  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1247  }
1248  parm {
1249  name "normalizev"
1250  cppname "NormalizeV"
1251  label "Normalize Computed Vs"
1252  type toggle
1253  default { "0" }
1254  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1255  }
1256  parm {
1257  name "flipu"
1258  cppname "FlipU"
1259  label "Flip Computed Us"
1260  type toggle
1261  default { "1" }
1262  disablewhen "{ computeuvs == 0 }"
1263  }
1264  groupcollapsible {
1265  name "uvscale_folder"
1266  label "UV Scale"
1267  grouptag { "group_type" "collapsible" }
1268  parmtag { "group_default" "0" }
1269  parm {
1270  name "uvscale"
1271  cppname "UVScale"
1272  label "UV Scale"
1273  type float
1274  size 2
1275  default { "1" "1" }
1276  disablewhen "{ computeuvs == 0 }"
1277  }
1278  parm {
1279  name "usemeshedgelengths"
1280  cppname "UseMeshEdgeLengths"
1281  label "Use Mesh Edge Lengths Instead of Curve Edge Lengths"
1282  type toggle
1283  default { "1" }
1284  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1285  }
1286  parm {
1287  name "propscalepercurve"
1288  cppname "PropScalePerCurve"
1289  label "Use Max Cross Section Length per Curve for Proportional Scale"
1290  type toggle
1291  default { "1" }
1292  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizeu != 1 } { normalizev != 0 }"
1293  }
1294  }
1295  groupcollapsible {
1296  name "uvseams_folder"
1297  label "UV Seams"
1298  grouptag { "group_type" "collapsible" }
1299  parmtag { "group_default" "0" }
1300  parm {
1301  name "wrapu"
1302  cppname "WrapU"
1303  label "Snap U to Nearest Tile Boundary"
1304  type toggle
1305  default { "1" }
1306  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizeu == 1 }"
1307  }
1308  parm {
1309  name "wrapv"
1310  cppname "WrapV"
1311  label "Snap V to Nearest Tile Boundary"
1312  type toggle
1313  default { "1" }
1314  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizev == 1 }"
1315  }
1316  }
1317  }
1318  groupcollapsible {
1319  name "attributes_folder"
1320  label "Attributes"
1321  grouptag { "group_type" "collapsible" }
1322  parmtag { "group_default" "0" }
1323  groupsimple {
1324  name "input_folder"
1325  label "Input"
1326  parm {
1327  name "attribsfrombackbone"
1328  cppname "AttribsFromBackbone"
1329  label "From Backbone Curves"
1330  type string
1331  default { "* ^P ^N ^up ^pscale ^scale ^orient ^rot ^pivot ^trans ^transform" }
1332  }
1333  parm {
1334  name "attribsfromcrosssection"
1335  cppname "AttribsFromCrossSection"
1336  label "From Cross Sections"
1337  type string
1338  default { "*" }
1339  }
1340  }
1341  groupsimple {
1342  name "output_folder"
1343  label "Output"
1344  parm {
1345  name "addptrow"
1346  cppname "AddPointRow"
1347  label "Add Point Row Attribute"
1348  type toggle
1349  default { "0" }
1350  nolabel
1351  joinnext
1352  }
1353  parm {
1354  name "ptrowattrib"
1355  cppname "PtRowAttrib"
1356  label "Point Row Attribute"
1357  type string
1358  default { "ptrow" }
1359  disablewhen "{ addptrow == 0 }"
1360  }
1361  parm {
1362  name "addptcol"
1363  cppname "AddPointCol"
1364  label "Add Point Col Attribute"
1365  type toggle
1366  default { "0" }
1367  nolabel
1368  joinnext
1369  }
1370  parm {
1371  name "ptcolattrib"
1372  cppname "PtColAttrib"
1373  label "Point Col Attribute"
1374  type string
1375  default { "ptcol" }
1376  disablewhen "{ addptcol == 0 }"
1377  }
1378  parm {
1379  name "addprimrow"
1380  cppname "AddPrimRow"
1381  label "Add Prim Row Attribute"
1382  type toggle
1383  default { "0" }
1384  nolabel
1385  joinnext
1386  }
1387  parm {
1388  name "primrowattrib"
1389  cppname "PrimRowAttrib"
1390  label "Prim Row Attribute"
1391  type string
1392  default { "primrow" }
1393  disablewhen "{ addprimrow == 0 }"
1394  }
1395  parm {
1396  name "addprimcol"
1397  cppname "AddPrimCol"
1398  label "Add Prim Col Attribute"
1399  type toggle
1400  default { "0" }
1401  nolabel
1402  joinnext
1403  }
1404  parm {
1405  name "primcolattrib"
1406  cppname "PrimColAttrib"
1407  label "Prim Col Attribute"
1408  type string
1409  default { "primcol" }
1410  disablewhen "{ addprimcol == 0 }"
1411  }
1412  parm {
1413  name "addcrosssectionnum"
1414  cppname "AddCrossSectionNum"
1415  label "Add Cross Section Num Attribute"
1416  type toggle
1417  default { "0" }
1418  nolabel
1419  joinnext
1420  }
1421  parm {
1422  name "crosssectionnumattrib"
1423  cppname "CrossSectionNumAttrib"
1424  label "Cross Section Num Attribute"
1425  type string
1426  default { "crossnum" }
1427  disablewhen "{ addcrosssectionnum == 0 }"
1428  }
1429  parm {
1430  name "addcurvenum"
1431  cppname "AddCurveNum"
1432  label "Add Curve Num Attribute"
1433  type toggle
1434  default { "0" }
1435  nolabel
1436  joinnext
1437  }
1438  parm {
1439  name "curvenumattrib"
1440  cppname "CurveNumAttrib"
1441  label "Curve Num Attribute"
1442  type string
1443  default { "curvenum" }
1444  disablewhen "{ addcurvenum == 0 }"
1445  }
1446  // TODO: Add option to compute vertex normals with cusp angle.
1447  }
1448  }
1449  }
1450 }
1451 )THEDSFILE";
1452 
1453 //******************************************************************************
1454 //* Cooking *
1455 //******************************************************************************
1456 
1457 using namespace SOP_SweepHDKEnums;
1458 using namespace GU_CurveFrame;
1459 
1460 #if 0
1461 /// This function takes edge lengths and a number of points to insert,
1462 /// and determines the number of those points that should go in each edge
1463 /// in order to minimize the maximum final interval length.
1464 template<typename T>
1465 static void
1466 insertIntoIntervals(
1467  const T *const edge_lengths,
1468  const T total_length,
1469  const exint nedges,
1470  const exint ninsertions,
1471  exint *const ninsertions_per_edge)
1472 {
1473  UT_ASSERT(nedges >= 1 && ninsertions >= 0);
1474  exint ninsertions_so_far = 0;
1475  for (exint i = 0; i < nedges; ++i)
1476  {
1477  T portion = (total_length > 0) ? (edge_lengths[i]/total_length) : (T(1)/T(nedges));
1478  // In exact arithmetic, the number of insertions in a given edge would
1479  // be at least floor(ninsertions*portion), but roundoff error could
1480  // cause problems, so subtract 1 to reduce the risk of the initial
1481  // number of insertions exceeding ninsertions.
1482  exint ki = SYSmax(exint(0), exint(SYSfloor(ninsertions*portion))-1);
1483  // Clamp it, just in case something weird happens.
1484  ki = SYSmin(ki, ninsertions-ninsertions_so_far);
1485  ninsertions_per_edge[i] = ki;
1486  ninsertions_so_far += ki;
1487  }
1488  if (ninsertions_so_far == ninsertions)
1489  return;
1490 
1491  struct IntervalComparator {
1492  IntervalComparator(const T *const edge_lengths, const exint *const ninsertions_per_edge) :
1493  myEdgeLengths(edge_lengths),
1494  myNInsertionsPerEdge(ninsertions_per_edge)
1495  {}
1496  bool operator()(const exint a, const exint b) const {
1497  // Compare the current lengths of the intervals along edge a vs those along edge b.
1498  // Multiply by both denomonators, instead of dividing, for better performance.
1499  const T nadb = myEdgeLengths[a]*(myNInsertionsPerEdge[b]+1);
1500  const T nbda = myEdgeLengths[b]*(myNInsertionsPerEdge[a]+1);
1501 
1502  // std::priority_queue is a max heap, and we want that, but the
1503  // comparator should act like std::less and return true if the
1504  // value for a is less than the value for b.
1505  // We also want a stable tie-breaker to avoid any problems with
1506  // different behaviour on different platforms.
1507  return nadb < nbda || (nadb == nbda && a < b);
1508  }
1509  const T *const myEdgeLengths;
1510  const exint *const myNInsertionsPerEdge;
1511  };
1512  IntervalComparator comparator(edge_lengths, ninsertions_per_edge);
1513 
1514  // Use n^2 approach for small nedges or small number of remaining
1515  // insertions, to avoid building a heap.
1516  if (nedges < 20 || (ninsertions-ninsertions_so_far) < 20) {
1517  do {
1518  exint edge_with_largest_intervals = 0;
1519  for (exint i = 1; i < nedges; ++i) {
1520  if (comparator(edge_with_largest_intervals, i)) {
1521  edge_with_largest_intervals = i;
1522  }
1523  }
1524  ++ninsertions_per_edge[edge_with_largest_intervals];
1525  ++ninsertions_so_far;
1526  } while (ninsertions_so_far < ninsertions);
1527 
1528  return;
1529  }
1530 
1531  // Make a binary heap of edge indices such that the edge with
1532  // longest current intervals will be at the top.
1533  UT_Array<exint> heap;
1534  heap.setSizeNoInit(nedges);
1535  for (exint i = 0; i < nedges; ++i)
1536  heap(i) = i;
1537  exint *const heap_begin = heap.array();
1538  exint *const heap_end = heap_begin+nedges;
1539  std::make_heap(heap_begin, heap_end, comparator);
1540 
1541  while (true)
1542  {
1543  // Insert another point in the edge with the largest intervals.
1544  exint edge_with_largest_intervals = heap_begin[0];
1545  ++ninsertions_per_edge[edge_with_largest_intervals];
1546  ++ninsertions_so_far;
1547  if (ninsertions_so_far == ninsertions)
1548  return;
1549 
1550  // Update the heap
1551  std::pop_heap(heap_begin, heap_end, comparator);
1552  heap_end[-1] = edge_with_largest_intervals;
1553  std::push_heap(heap_begin, heap_end, comparator);
1554  }
1555 }
1556 #endif
1557 
1558 static int
1559 sopParmToPrimType(const SOP_SweepHDKEnums::PrimType sop_primtype)
1560 {
1562  switch (sop_primtype)
1563  {
1564  case PrimType::AUTO: return GA_PRIMNONE;
1565  case PrimType::POLY: return GA_PRIMPOLY;
1566  case PrimType::POLYSOUP:return GA_PRIMPOLYSOUP;
1567  case PrimType::MESH: return GA_PRIMMESH;
1568  case PrimType::BEZIER: return GA_PRIMBEZSURF;
1569  case PrimType::NURBS: return GA_PRIMNURBSURF;
1570  }
1571  return GA_PRIMNONE;
1572 }
1573 
1574 static SYS_FORCE_INLINE exint
1575 reverseVtx(exint i, exint n, bool closed)
1576 {
1577  if (!closed)
1578  return (n-i-1);
1579  return (i==0) ? 0 : (n-i);
1580 }
1581 
1582 static void
1583 computeCurveUVs(
1584  const GEO_Detail *detail,
1585  const GA_PrimitiveGroup *prim_group,
1586  const GA_RWHandleV3 &uv_attrib,
1587  bool by_length,
1588  bool reverse)
1589 {
1590  // We're modifying this attribute, and it'll be in the output detail,
1591  // so we need to bump its data ID.
1592  uv_attrib->bumpDataId();
1593 
1594  // Because we're not parallelizing over vertex pages and we're writing
1595  // to a vertex attribute, we must harden all pages of the attribute in advance.
1596  uv_attrib->hardenAllPages();
1597 
1598  UT_AutoInterrupt interrupt("Computing curve UVs");
1599  if (interrupt.wasInterrupted())
1600  return;
1601 
1602  //const float fnprimitives = float(prim_group ? prim_group->entries() : detail->getNumPrimitives());
1603 
1604  // Loop over primitives in parallel, using a splittable range and a lambda functor.
1605  UTparallelFor(GA_SplittableRange(detail->getPrimitiveRange(prim_group)),
1606  [detail,&uv_attrib,&interrupt,by_length,reverse](const GA_SplittableRange &r)
1607  {
1608  // Inside the functor, we have a sub-range of primitives, so loop over that range.
1609  // We use blockAdvance, instead of !it.atEnd() and ++it, for less looping overhead.
1611  for (GA_Iterator it(r); it.blockAdvance(start,end); )
1612  {
1613  // We probably don't need to check for interruption on every curve,
1614  // (unless people put in a curve with millions of vertices), so we
1615  // can check it once for every contiguous block of up to GA_PAGE_SIZE
1616  // primitive offsets.
1617  if (interrupt.wasInterrupted())
1618  return;
1619 
1620  // Loop over all primitives in this contiguous block of offsets.
1621  for (GA_Offset primoff = start; primoff < end; ++primoff)
1622  {
1623  const GA_OffsetListRef vertices = detail->getPrimitiveVertexList(primoff);
1624  const GA_Size n = vertices.size();
1625 
1626  float v = 0;
1627  //if (separate_us)
1628  // v = float(GA_Size(detail->primitiveIndex(primoff))) / fnprimitives;
1629 
1630  // Nothing to do if no vertices; special case if 1 vertex
1631  if (n <= 1)
1632  {
1633  if (n == 1)
1634  uv_attrib.set(vertices(0), UT_Vector3(0,v,0));
1635  continue;
1636  }
1637 
1638  const bool closed = vertices.getExtraFlag();
1639 
1640  // Reversed closed polygons keep vertex 0 in the same place
1641  GA_Offset vtxoff0;
1642  if (!reverse || closed)
1643  vtxoff0 = vertices(0);
1644  else
1645  vtxoff0 = vertices(n-1);
1646 
1647  bool local_by_length = by_length;
1648  if (local_by_length)
1649  {
1650  // First, compute the full length of the curve
1651  float length = 0;
1652  const UT_Vector3 pos0 = detail->getPos3(detail->vertexPoint(vtxoff0));
1653  UT_Vector3 prev_pos = pos0;
1654  for (GA_Size i = 1; i < n; ++i)
1655  {
1656  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1657  const UT_Vector3 cur_pos = detail->getPos3(detail->vertexPoint(vertices(vtxi)));
1658  length += distance3d(prev_pos, cur_pos);
1659  prev_pos = cur_pos;
1660  }
1661  if (closed)
1662  {
1663  // One last edge if closed
1664  length += distance3d(prev_pos, pos0);
1665  }
1666  if (length == 0)
1667  {
1668  // Fall back to uniform weighting if total length is zero
1669  local_by_length = false;
1670  }
1671  else
1672  {
1673  // Compute the u values as cur_length/length
1674  uv_attrib.set(vtxoff0, UT_Vector3(0,v,0));
1675  float cur_length = 0;
1676  prev_pos = detail->getPos3(detail->vertexPoint(vtxoff0));
1677  for (GA_Size i = 1; i < n; ++i)
1678  {
1679  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1680  GA_Offset vtxoff = vertices(vtxi);
1681  const UT_Vector3 cur_pos = detail->getPos3(detail->vertexPoint(vtxoff));
1682  cur_length += distance3d(prev_pos, cur_pos);
1683  const float u = cur_length/length;
1684  uv_attrib.set(vtxoff, UT_Vector3(u,v,0));
1685  prev_pos = cur_pos;
1686  }
1687  }
1688  }
1689  if (!local_by_length)
1690  {
1691  // Uniform uv step on each edge, so each u value is vertex/nedges
1692  GA_Size nedges = closed ? n : (n-1);
1693  uv_attrib.set(vtxoff0, UT_Vector3(0,v,0));
1694  for (GA_Size i = 1; i < n; ++i)
1695  {
1696  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1697  const float u = i/float(nedges);
1698  uv_attrib.set(vertices(vtxi), UT_Vector3(u,v,0));
1699  }
1700  }
1701 
1702  // This is a silly way to do it, but to try to get UV wrapping
1703  // correct when reversing a closed cross section, the first U value
1704  // needs to be 1, instead of 0. This is because of how code below
1705  // handles wrapping and closed cross sections from the cross section input.
1706  if (reverse && closed)
1707  {
1708  uv_attrib.set(vtxoff0, UT_Vector3(1,v,0));
1709  }
1710  }
1711  }
1712  });
1713 }
1714 
1715 static GA_Offset lookupCrossSectionFromAttrib(
1716  const CrossSectionAttribMatchData *copy_order_attrib_data,
1717  GA_Offset curve_offset,
1718  const GEO_Detail *cross_section_input,
1719  const GA_PrimitiveGroup *cross_section_group)
1720 {
1721  if (copy_order_attrib_data->myCurveIntAttrib.isValid())
1722  {
1723  // Integer attribute case
1724  const exint id = copy_order_attrib_data->myCurveIntAttrib.get(curve_offset);
1725  if (copy_order_attrib_data->myIsUsingMap) {
1726  // Using map (both inputs had integer attribute)
1728  // No cross section, since invalid key for map type.
1729  return GA_INVALID_OFFSET;
1730  }
1731  auto &&it = copy_order_attrib_data->myIntToPrimOff.find(id);
1732  if (it == copy_order_attrib_data->myIntToPrimOff.end()) {
1733  // No cross section, since key not in map.
1734  return GA_INVALID_OFFSET;
1735  }
1736  return it->second;
1737  }
1738  if (id < 0 || id >= cross_section_input->getNumPrimitives()) {
1739  // No cross section, since primitive index out of range.
1740  return GA_INVALID_OFFSET;
1741  }
1742  const GA_Offset cross_section_primoff = cross_section_input->primitiveOffset(id);
1743  if (cross_section_group && !cross_section_group->contains(cross_section_primoff)) {
1744  // No cross section, since primitive not in group.
1745  return GA_INVALID_OFFSET;
1746  }
1747  return cross_section_primoff;
1748  }
1749 
1750  // String attribute case
1751  UT_ASSERT_P(copy_order_attrib_data->myCurveStrAttrib.isValid());
1752  UT_ASSERT_P(copy_order_attrib_data->myIsUsingMap);
1753 
1754  const UT_StringHolder &name = copy_order_attrib_data->myCurveStrAttrib.get(curve_offset);
1755  if (!name.isstring()) {
1756  // No cross section, since invalid key for map type.
1757  return GA_INVALID_OFFSET;
1758  }
1759  auto &&it = copy_order_attrib_data->myStrToPrimOff.find(name);
1760  if (it == copy_order_attrib_data->myStrToPrimOff.end()) {
1761  // No cross section, since key not in map.
1762  return GA_INVALID_OFFSET;
1763  }
1764  return it->second;
1765 }
1766 
1767 static int
1768 updateAutoPrimType(
1769  const GEO_Detail *const cross_section_input,
1770  const GA_Offset cross_section_primoff,
1771  int &primitive_type,
1772  unsigned char &fallback_orderu,
1773  const GA_Basis **const pbasisu)
1774 {
1775  UT_ASSERT_P(cross_section_input != nullptr);
1776  int primtype = cross_section_input->getPrimitiveTypeId(cross_section_primoff);
1777  // Only change if cross section is NURBS or Bezier curve.
1778  if (primtype != GA_PRIMNURBCURVE && primtype != GA_PRIMBEZCURVE)
1779  return primtype;
1780 
1781  // NURBS takes precedence, so don't modify if already NURBS
1782  if (primitive_type != GA_PRIMNURBSURF)
1783  {
1784  if (primtype == GA_PRIMNURBCURVE)
1785  primitive_type = GA_PRIMNURBSURF;
1786  else //if (primtype == GA_PRIMBEZCURVE)
1787  primitive_type = GA_PRIMBEZSURF;
1788  }
1789 
1790  const GEO_Curve *curve = UTverify_cast<const GEO_Curve *>(cross_section_input->getPrimitive(cross_section_primoff));
1791  UT_ASSERT_P(curve->getBasis() != nullptr);
1792  fallback_orderu = SYSmax(fallback_orderu, curve->getBasis()->getOrder());
1793  if (pbasisu != nullptr)
1794  *pbasisu = curve->getBasis();
1795 
1796  return primtype;
1797 }
1798 
1799 static bool
1800 computeCombinedCrossSectionProperties(
1801  const GEO_Detail *const cross_section_input,
1802  const GA_PrimitiveGroup *const cross_section_group,
1803  GA_Iterator &cross_section_it,
1804  exint num_cross_sections,
1805  exint &cross_section_nedges,
1806  bool &cross_section_closed,
1807  bool &cross_section_unrolled,
1808  bool &varying_nedges,
1809  GA_OffsetList &cross_section_primoffs,
1810  const bool is_primtype_auto,
1811  int &primitive_type,
1812  unsigned char &fallback_orderu,
1813  const GA_Basis **const pbasisu)
1814 {
1815  if (num_cross_sections == 0)
1816  return false;
1817 
1818  UT_ASSERT(cross_section_it.isValid());
1819  UT_ASSERT(!cross_section_it.atEnd());
1820 
1821  // If the final values of closed, unrolled, or primtype differ from
1822  // the values for the U basis, the U basis must be thrown out.
1823  bool basis_cross_section_closed;
1824  bool basis_cross_section_unrolled;
1825  int basis_primtype;
1826 
1827  cross_section_nedges = -1;
1828  varying_nedges = false;
1829  //bool varying_cross_section_topology = false;
1830  for (exint cross_sectioni = 0; cross_sectioni < num_cross_sections; ++cross_sectioni)
1831  {
1832  GA_Offset cross_section_primoff = *cross_section_it;
1833  ++cross_section_it;
1834  if (cross_section_it.atEnd())
1835  {
1836  // Wrap around the cross sections
1837  cross_section_it.rewind();
1838  UT_ASSERT(!cross_section_it.atEnd());
1839  }
1840 
1841  cross_section_primoffs.append(cross_section_primoff);
1842  const GA_OffsetListRef cross_section_vertices = cross_section_input->getPrimitiveVertexList(cross_section_primoff);
1843  exint local_cross_section_nedges;
1844  bool local_cross_section_closed;
1845  bool local_cross_section_unrolled;
1846  bool nonempty = getPolyProperties(cross_section_input, cross_section_vertices, local_cross_section_nedges, local_cross_section_closed, local_cross_section_unrolled);
1847 
1848  // If any cross section has no vertices, don't generate this grid at all.
1849  if (!nonempty)
1850  return false;
1851 
1852  if (cross_section_nedges == -1)
1853  {
1854  // First iteration, so copy value.
1855  cross_section_closed = local_cross_section_closed;
1856  cross_section_unrolled = local_cross_section_unrolled;
1857  }
1858  else if (!local_cross_section_closed)
1859  {
1860  //varying_cross_section_topology |= cross_section_closed || cross_section_unrolled;
1861  // If any cross section is open, we go with open.
1862  cross_section_closed = false;
1863  cross_section_unrolled = false;
1864  }
1865  else if (local_cross_section_unrolled && cross_section_closed && !cross_section_unrolled)
1866  {
1867  // If we're closed and not yet unrolled and we encounter
1868  // an unrolled cross section, go with unrolled.
1869  cross_section_unrolled = true;
1870  //varying_cross_section_topology = true;
1871  }
1872 
1873  // Independent of that, we choose the number of edges to be the max number of edges.
1874  bool local_varying_nedges = (cross_section_nedges != -1 && local_cross_section_nedges != cross_section_nedges);
1875  //varying_cross_section_topology |= local_varying_nedges;
1876  varying_nedges |= local_varying_nedges;
1877 
1878  int local_primtype;
1879  if (is_primtype_auto)
1880  {
1881  local_primtype = updateAutoPrimType(cross_section_input, cross_section_primoff, primitive_type, fallback_orderu, nullptr);
1882  }
1883 
1884  // Pick the maximum cross_section_nedges.
1885  if (local_cross_section_nedges > cross_section_nedges)
1886  {
1887  cross_section_nedges = local_cross_section_nedges;
1888 
1889  if (is_primtype_auto && pbasisu != nullptr)
1890  {
1891  // Choose the basis with the maximum nedges, since it's the most likely to be valid.
1892  if (local_primtype == GA_PRIMNURBCURVE || local_primtype == GA_PRIMBEZCURVE)
1893  {
1894  basis_primtype = (local_primtype == GA_PRIMNURBCURVE) ? GA_PRIMNURBSURF : GA_PRIMBEZSURF;
1895  basis_cross_section_closed = local_cross_section_closed;
1896  basis_cross_section_unrolled = local_cross_section_unrolled;
1897 
1898  const GEO_Curve *curve = UTverify_cast<const GEO_Curve*>(cross_section_input->getPrimitive(cross_section_primoff));
1899  UT_ASSERT_P(curve->getBasis() != nullptr);
1900  *pbasisu = curve->getBasis();
1901  }
1902  else
1903  {
1904  // Not a NURBS or Bezier curve, so clear the basis.
1905  *pbasisu = nullptr;
1906  }
1907  }
1908  }
1909  }
1910 
1911  if (pbasisu != nullptr && *pbasisu != nullptr)
1912  {
1913  // NOTE: This basis not be valid with the final
1914  // cross_section_closed or cross_section_unrolled or primitive_type,
1915  // so clear it if it's not valid.
1916  if (basis_primtype != primitive_type ||
1917  basis_cross_section_closed != cross_section_closed ||
1918  basis_cross_section_unrolled != cross_section_unrolled)
1919  {
1920  *pbasisu = nullptr;
1921  }
1922  }
1923 
1924  UT_ASSERT(cross_section_primoffs.size() > 0);
1925 
1926  return true;
1927 }
1928 
1929 // This is primarily the same as computeCombinedCrossSectionProperties,
1930 // except selecting cross sections using copy_order_attrib_data,
1931 // instead of getting sequential ones with a GA_Iterator.
1932 static bool
1933 computeCombinedCrossSectionPropertiesAttrib(
1934  const GEO_Detail *const cross_section_input,
1935  const GA_PrimitiveGroup *const cross_section_group,
1936  const GEO_Detail *const curve_input,
1937  const GA_OffsetListRef &curve_vertices,
1938  const CrossSectionAttribMatchData *const copy_order_attrib_data,
1939  exint &cross_section_nedges,
1940  bool &cross_section_closed,
1941  bool &cross_section_unrolled,
1942  bool &varying_nedges,
1943  GA_OffsetList &cross_section_primoffs,
1944  const bool is_primtype_auto,
1945  int &primitive_type,
1946  unsigned char &fallback_orderu,
1947  const GA_Basis **const pbasisu)
1948 {
1949  exint num_vertices = curve_vertices.size();
1950  if (num_vertices == 0)
1951  return false;
1952 
1953  const GA_AttributeOwner curve_owner = copy_order_attrib_data->myCurveAttribOwner;
1954  UT_ASSERT(curve_owner == GA_ATTRIB_POINT || curve_owner == GA_ATTRIB_VERTEX);
1955 
1956  // If the final values of closed, unrolled, or primtype differ from
1957  // the values for the U basis, the U basis must be thrown out.
1958  bool basis_cross_section_closed;
1959  bool basis_cross_section_unrolled;
1960  int basis_primtype;
1961 
1962  cross_section_nedges = -1;
1963  varying_nedges = false;
1964  //bool varying_cross_section_topology = false;
1965  for (exint vtxi = 0; vtxi < num_vertices; ++vtxi)
1966  {
1967  GA_Offset curve_offset = curve_vertices[vtxi];
1968  if (curve_owner == GA_ATTRIB_POINT)
1969  curve_offset = curve_input->vertexPoint(curve_offset);
1970  GA_Offset cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data, curve_offset, cross_section_input, cross_section_group);
1971 
1972  // If any cross sections don't work out, give up on the grid,
1973  // for simplicity when copying curve attributes later.
1974  if (!GAisValid(cross_section_primoff))
1975  return false;
1976 
1977  cross_section_primoffs.append(cross_section_primoff);
1978  const GA_OffsetListRef cross_section_vertices = cross_section_input->getPrimitiveVertexList(cross_section_primoff);
1979  exint local_cross_section_nedges;
1980  bool local_cross_section_closed;
1981  bool local_cross_section_unrolled;
1982  bool nonempty = getPolyProperties(cross_section_input, cross_section_vertices, local_cross_section_nedges, local_cross_section_closed, local_cross_section_unrolled);
1983 
1984  // If any cross section has no vertices, don't generate this grid at all.
1985  if (!nonempty)
1986  return false;
1987 
1988  if (cross_section_nedges == -1)
1989  {
1990  // First iteration, so copy value.
1991  cross_section_closed = local_cross_section_closed;
1992  cross_section_unrolled = local_cross_section_unrolled;
1993  }
1994  else if (!local_cross_section_closed)
1995  {
1996  //varying_cross_section_topology |= cross_section_closed || cross_section_unrolled;
1997  // If any cross section is open, we go with open.
1998  cross_section_closed = false;
1999  cross_section_unrolled = false;
2000  }
2001  else if (local_cross_section_unrolled && cross_section_closed && !cross_section_unrolled)
2002  {
2003  // If we're closed and not yet unrolled and we encounter
2004  // an unrolled cross section, go with unrolled.
2005  cross_section_unrolled = true;
2006  //varying_cross_section_topology = true;
2007  }
2008 
2009  // Independent of that, we choose the number of edges to be the max number of edges.
2010  bool local_varying_nedges = (cross_section_nedges != -1 && local_cross_section_nedges != cross_section_nedges);
2011  //varying_cross_section_topology |= local_varying_nedges;
2012  varying_nedges |= local_varying_nedges;
2013 
2014  int local_primtype;
2015  if (is_primtype_auto)
2016  {
2017  local_primtype = updateAutoPrimType(cross_section_input, cross_section_primoff, primitive_type, fallback_orderu, nullptr);
2018  }
2019 
2020  // Pick the maximum cross_section_nedges.
2021  if (local_cross_section_nedges > cross_section_nedges)
2022  {
2023  cross_section_nedges = local_cross_section_nedges;
2024 
2025  if (is_primtype_auto && pbasisu != nullptr)
2026  {
2027  // Choose the basis with the maximum nedges, since it's the most likely to be valid.
2028  if (local_primtype == GA_PRIMNURBCURVE || local_primtype == GA_PRIMBEZCURVE)
2029  {
2030  basis_primtype = (local_primtype == GA_PRIMNURBCURVE) ? GA_PRIMNURBSURF : GA_PRIMBEZSURF;
2031  basis_cross_section_closed = local_cross_section_closed;
2032  basis_cross_section_unrolled = local_cross_section_unrolled;
2033 
2034  const GEO_Curve *curve = UTverify_cast<const GEO_Curve*>(cross_section_input->getPrimitive(cross_section_primoff));
2035  UT_ASSERT_P(curve->getBasis() != nullptr);
2036  *pbasisu = curve->getBasis();
2037  }
2038  else
2039  {
2040  // Not a NURBS or Bezier curve, so clear the basis.
2041  *pbasisu = nullptr;
2042  }
2043  }
2044  }
2045  }
2046 
2047  if (pbasisu != nullptr && *pbasisu != nullptr)
2048  {
2049  // NOTE: This basis not be valid with the final
2050  // cross_section_closed or cross_section_unrolled or primitive_type,
2051  // so clear it if it's not valid.
2052  if (basis_primtype != primitive_type ||
2053  basis_cross_section_closed != cross_section_closed ||
2054  basis_cross_section_unrolled != cross_section_unrolled)
2055  {
2056  *pbasisu = nullptr;
2057  }
2058  }
2059 
2060  UT_ASSERT(cross_section_primoffs.size() > 0);
2061 
2062  return true;
2063 }
2064 
2065 static void
2066 initPrimType(
2067  const bool output_points_only,
2068  const bool is_primtype_auto,
2069  int &primitive_type,
2070  unsigned char &fallback_orderv,
2071  const GA_Basis **const pbasisv,
2072  const GEO_Detail *const curve_input,
2073  const GA_Offset curve_primoff)
2074 {
2075  // When using automatic primitive type mode, we take the highest order NURBS,
2076  // or if the cross section with the most edges is Bezier, Bezier of that order;
2077  // 0 indicates that we haven't encountered any NURBS or Bezier curves yet.
2078  // NOTE: If primitive_type becomes GA_PRIMNURBSURF or GA_PRIMBEZSURF below,
2079  // fallback_orderv should be set to something between 2 and GA_MAXORDER.
2080  fallback_orderv = 0;
2081  if (!is_primtype_auto)
2082  return;
2083 
2084  // Default guess is polygon
2085  primitive_type = GA_PRIMPOLY;
2086  if (curve_input == nullptr)
2087  {
2088  // If no curve, and cross sections end up having NURBS or Bezier,
2089  // default to order 4, instead of assuming implicit curve should be a polygon (order 2).
2090  fallback_orderv = 4;
2091  if (pbasisv)
2092  *pbasisv = nullptr;
2093  return;
2094  }
2095 
2096  const int curve_primtype = curve_input->getPrimitiveTypeId(curve_primoff);
2097  bool is_curve = false;
2098  if (curve_primtype == GA_PRIMBEZCURVE)
2099  {
2100  // If curve primitive is a Bezier curve, default guess is Bezier surface.
2101  primitive_type = GA_PRIMBEZSURF;
2102  is_curve = true;
2103  }
2104  else if (curve_primtype == GA_PRIMNURBCURVE)
2105  {
2106  // If curve primitive is a NURBS curve, always use NURBS surface
2107  // primitive if automatic mode.
2108  primitive_type = GA_PRIMNURBSURF;
2109  is_curve = true;
2110  }
2111  if (is_curve)
2112  {
2113  const GEO_Curve *const curve = UTverify_cast<const GEO_Curve *>(curve_input->getPrimitive(curve_primoff));
2114  if (pbasisv)
2115  *pbasisv = curve->getBasis();
2116  UT_ASSERT_P(curve->getBasis());
2117  fallback_orderv = curve->getBasis()->getOrder();
2118  }
2119 }
2120 
2121 // This fills in grid_info based on the input data.
2122 // This function does *not* handle the copy cases; it only handles the grid cases.
2123 // It returns false if the grid is invalid; the caller should not rely on grid_info or num_points in that case.
2124 // NOTE: It does *not* fill in grid_info.myStartPtOff, grid_info.myStartVtxOff, or grid_info.myStartPrimOff.
2125 static bool
2126 computeSingleGridSetup(
2127  // Cross section parameters
2128  const GEO_Detail *const cross_section_input,
2129  const GA_PrimitiveGroup *const cross_section_group,
2130  const bool single_cross_section,
2131  GA_Iterator &cross_section_it,
2132  GA_Offset single_cross_section_primoff,
2133  exint cross_section_nedges,
2134  bool cross_section_closed,
2135  bool cross_section_unrolled,
2136  const CopyOrder copy_order,
2137  const CrossSectionAttribMatchData *const copy_order_attrib_data,
2138  bool varying_nedges_all_case,
2139  const GA_OffsetList *cross_section_primoffs_all_case,
2140 
2141  // Backbone curve parameters
2142  const GEO_Detail *const curve_input,
2143  const GA_Offset curve_primoff,
2144  const bool closed_if_no_curve_input,
2145 
2146  // Surface parameters
2147  const GEO_SurfaceType surface_type,
2148  const bool output_points_only,
2149  const bool unroll_closed_row_col,
2150  const bool is_primtype_auto,
2151  int primitive_type,
2152  unsigned char fallback_orderu,
2153  unsigned char fallback_orderv,
2154  const GA_Basis **const pbasisu,
2155  EndCapType end_cap_type,
2156  exint cap_divisions,
2157 
2158  // Output parameters
2159  sop_SweepGrid &grid_info,
2160  exint &num_points)
2161 {
2162  const bool has_col_surface_type =
2163  surface_type == GEO_PATCH_COLS ||
2164  surface_type == GEO_PATCH_ROWCOL;
2165  const bool has_row_surface_type =
2166  surface_type == GEO_PATCH_ROWS ||
2167  surface_type == GEO_PATCH_ROWCOL;
2168  const bool has_rowcol_surface_type =
2169  has_col_surface_type || has_row_surface_type;
2170 
2171  const bool is_polygon_type = (!output_points_only && primitive_type == GA_PRIMPOLY);
2172  const bool could_add_row_seam_vertex = (has_col_surface_type || !is_polygon_type) && unroll_closed_row_col;
2173  const bool could_add_col_seam_vertex = (has_row_surface_type || !is_polygon_type) && unroll_closed_row_col;
2174 
2175  if (curve_input == nullptr)
2176  {
2177  // Just input cross sections, no input curves, so only this one grid.
2178  UT_ASSERT(cross_section_input != nullptr);
2179  grid_info.myCurvePrimOff = GA_INVALID_OFFSET;
2180  grid_info.myCurveClosed = closed_if_no_curve_input;
2181  grid_info.myCurveUnrolled = (grid_info.myCurveClosed && could_add_row_seam_vertex);
2182  exint num_cross_sections = (cross_section_group ? cross_section_group->entries() : cross_section_input->getNumPrimitives());
2183  grid_info.myCurveNEdges = num_cross_sections - !grid_info.myCurveClosed;
2184 
2185  bool varying_nedges;
2186  GA_OffsetList cross_section_primoffs;
2187  bool is_valid_grid = computeCombinedCrossSectionProperties(
2188  cross_section_input,
2189  cross_section_group,
2190  cross_section_it,
2191  num_cross_sections,
2192  cross_section_nedges,
2193  cross_section_closed,
2194  cross_section_unrolled,
2195  varying_nedges,
2196  cross_section_primoffs,
2197  is_primtype_auto,
2198  primitive_type,
2199  fallback_orderu,
2200  pbasisu);
2201 
2202  if (!is_valid_grid)
2203  return false;
2204 
2205  grid_info.myCrossSectionNEdges = cross_section_nedges;
2206  grid_info.myCrossSectionClosed = cross_section_closed;
2207  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2208  grid_info.myAllEqualNEdges = !varying_nedges;
2209  grid_info.mySingleCrossSection = (cross_section_primoffs.size() == 1);
2210  if (grid_info.mySingleCrossSection)
2211  grid_info.myCrossSectionPrimOff = cross_section_primoffs(0);
2212  else
2213  grid_info.myCrossSectionPrimOffs = new GA_OffsetList(std::move(cross_section_primoffs));
2214  }
2215  else
2216  {
2217  // Input curve
2218  grid_info.myCurvePrimOff = curve_primoff;
2219 
2220  exint curve_nedges;
2221  bool curve_closed;
2222  bool curve_unrolled;
2223  const GA_OffsetListRef curve_vertices = curve_input->getPrimitiveVertexList(curve_primoff);
2224  bool nonempty = getPolyProperties(curve_input, curve_vertices, curve_nedges, curve_closed, curve_unrolled);
2225 
2226  // Skip curves with no vertices
2227  if (!nonempty)
2228  return false;
2229 
2230  grid_info.myCurveClosed = curve_closed;
2231  grid_info.myCurveUnrolled = curve_unrolled || (curve_closed && could_add_row_seam_vertex);
2232  grid_info.myCurveNEdges = curve_nedges;
2233 
2234  if (single_cross_section)
2235  {
2236  UT_ASSERT_MSG(copy_order != CopyOrder::ATTRIB, "This branch doesn't handle the possibility of invalid cross section selection in attribute.");
2237  grid_info.myCrossSectionNEdges = cross_section_nedges;
2238  grid_info.myCrossSectionClosed = cross_section_closed;
2239  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2240  grid_info.myAllEqualNEdges = true;
2241  grid_info.mySingleCrossSection = true;
2242  grid_info.myCrossSectionPrimOff = single_cross_section_primoff;
2243  }
2244  else
2245  {
2246  UT_ASSERT_MSG(copy_order == CopyOrder::CYCLEVTX || copy_order == CopyOrder::ALL || copy_order == CopyOrder::ATTRIB,
2247  "Other cases should have resolved to single cross section grids.");
2248 
2249  bool varying_nedges = varying_nedges_all_case;
2250  GA_OffsetList cross_section_primoffs;
2251  if (copy_order == CopyOrder::CYCLEVTX)
2252  {
2253  exint num_cross_sections = curve_nedges + !curve_closed;
2254  bool is_valid_grid = computeCombinedCrossSectionProperties(
2255  cross_section_input,
2256  cross_section_group,
2257  cross_section_it,
2258  num_cross_sections,
2259  cross_section_nedges,
2260  cross_section_closed,
2261  cross_section_unrolled,
2262  varying_nedges,
2263  cross_section_primoffs,
2264  is_primtype_auto,
2265  primitive_type,
2266  fallback_orderu,
2267  pbasisu);
2268 
2269  if (!is_valid_grid)
2270  return false;
2271  }
2272  else if (copy_order == CopyOrder::ATTRIB)
2273  {
2274  bool is_valid_grid = computeCombinedCrossSectionPropertiesAttrib(
2275  cross_section_input,
2276  cross_section_group,
2277  curve_input,
2278  curve_vertices,
2279  copy_order_attrib_data,
2280  cross_section_nedges,
2281  cross_section_closed,
2282  cross_section_unrolled,
2283  varying_nedges,
2284  cross_section_primoffs,
2285  is_primtype_auto,
2286  primitive_type,
2287  fallback_orderu,
2288  pbasisu);
2289 
2290  if (!is_valid_grid)
2291  return false;
2292  }
2293 
2294  grid_info.myCrossSectionNEdges = cross_section_nedges;
2295  grid_info.myCrossSectionClosed = cross_section_closed;
2296  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2297  grid_info.myAllEqualNEdges = !varying_nedges;
2298  grid_info.mySingleCrossSection = false;
2299  if (copy_order == CopyOrder::ALL)
2300  {
2301  exint ncopies = grid_info.myCurveNEdges + !grid_info.myCurveClosed;
2302  grid_info.myCurveNEdges = (ncopies * cross_section_primoffs_all_case->size()) - !grid_info.myCurveClosed;
2303  grid_info.myCrossSectionPrimOffs = new GA_OffsetList();
2304  for (exint copyi = 0; copyi < ncopies; ++copyi)
2305  {
2306  grid_info.myCrossSectionPrimOffs->append(*cross_section_primoffs_all_case);
2307  }
2308  }
2309  else
2310  {
2311  grid_info.myCrossSectionPrimOffs = new GA_OffsetList(std::move(cross_section_primoffs));
2312  }
2313  }
2314  }
2315 
2316  grid_info.myHasPolygonCaps =
2317  !output_points_only &&
2318  (
2319  (end_cap_type == EndCapType::SINGLE &&
2320  (!grid_info.myCurveClosed && grid_info.myCrossSectionClosed && grid_info.myCrossSectionNEdges > 1)) ||
2321  (end_cap_type == EndCapType::SIDESINGLE &&
2322  (!grid_info.myCrossSectionClosed && grid_info.myCurveClosed && grid_info.myCurveNEdges > 1))
2323  ) &&
2324  !has_rowcol_surface_type;
2325 
2326  // FIXME: Because we haven't sorted out how to represent transforms for U end caps yet, disable them for now!!!
2327  grid_info.myUEndPoles = false;
2328 #if 0
2329  grid_info.myUEndPoles =
2330  !output_points_only &&
2331  (end_cap_type != EndCapType::NONE && end_cap_type != EndCapType::SINGLE && end_cap_type != EndCapType::SIDESINGLE) &&
2332  (!grid_info.myCrossSectionClosed && grid_info.myCurveClosed) &&
2333  cap_divisions > 0;
2334 #endif
2335 
2336  // NOTE: Even non-closed cross sections can yield end "caps", so that
2337  // it's possible to do rounded and pointed ends on ribbons.
2338  // Even the flat case can be useful if the end cross section is not a straight line.
2339  grid_info.myVEndPoles =
2340  !output_points_only &&
2341  (end_cap_type != EndCapType::NONE && end_cap_type != EndCapType::SINGLE && end_cap_type != EndCapType::SIDESINGLE) &&
2342  (!grid_info.myCurveClosed) &&
2343  cap_divisions > 0;
2344 
2345  if (grid_info.myUEndPoles)
2346  {
2347  // Add in the divisions for the first and last col cap
2348  grid_info.myCrossSectionNEdges += 2*cap_divisions;
2349 
2350  // The first and last columns are collapsed to single points.
2351  UT_ASSERT(grid_info.myCurveNEdges >= 1);
2352  }
2353  else if (grid_info.myVEndPoles)
2354  {
2355  // Add in the divisions for the first and last row cap
2356  grid_info.myCurveNEdges += 2*cap_divisions;
2357 
2358  if (!grid_info.mySingleCrossSection)
2359  {
2360  // Add duplicates of first and last cross section primoffs for simplicity later.
2361  GA_OffsetList new_cross_section_primoffs;
2362  GA_OffsetList &primoffs = *grid_info.myCrossSectionPrimOffs;
2363  for (exint i = 0; i < cap_divisions; ++i)
2364  new_cross_section_primoffs.append(primoffs(0));
2365  new_cross_section_primoffs.append(primoffs);
2366  for (exint i = 0; i < cap_divisions; ++i)
2367  new_cross_section_primoffs.append(primoffs.last());
2368  primoffs = std::move(new_cross_section_primoffs);
2369  }
2370 
2371  // The first and last rows are collapsed to single points.
2372  UT_ASSERT(grid_info.myCrossSectionNEdges >= 1);
2373  }
2374 
2375  using PrimitiveType = sop_SweepGrid::PrimitiveType;
2376  grid_info.myPrimitiveType = PrimitiveType::POINTS;
2377  if (!output_points_only)
2378  {
2379  grid_info.myPrimitiveType = PrimitiveType::POLYGON;
2380  if (primitive_type == GA_PRIMPOLYSOUP)
2381  grid_info.myPrimitiveType = PrimitiveType::POLYSOUP;
2382  else if (primitive_type == GA_PRIMMESH)
2383  grid_info.myPrimitiveType = PrimitiveType::MESH;
2384  else if (primitive_type == GA_PRIMNURBSURF)
2385  grid_info.myPrimitiveType = PrimitiveType::NURBS;
2386  else if (primitive_type == GA_PRIMBEZSURF)
2387  grid_info.myPrimitiveType = PrimitiveType::BEZIER;
2388 
2389  // Fall back to polygon if number of rows or cols is too small.
2390  // TODO: Consider supporting outputting a single NURBS/Bezier curve.
2391  if (grid_info.myPrimitiveType != PrimitiveType::POLYGON &&
2392  (grid_info.myCurveNEdges <= 0 || grid_info.myCrossSectionNEdges <= 0))
2393  {
2394  grid_info.myPrimitiveType = PrimitiveType::POLYGON;
2395  }
2396  }
2397  if (grid_info.myPrimitiveType == PrimitiveType::NURBS)
2398  {
2399  if (fallback_orderv == 0)
2400  {
2401  // Either not auto, or curve was polygon but one of the cross sections was NURBS/Bezier.
2402  // Use order 2 if auto, since that's the closest match to polygon,
2403  // but use order 4 if not auto, since the user specifically chose NURBS.
2404  fallback_orderv = (is_primtype_auto && curve_input != nullptr) ? 2 : 4;
2405  }
2406  exint curve_nvertices = grid_info.myCurveNEdges + (!grid_info.myCurveClosed || grid_info.myCurveUnrolled);
2407  if (curve_nvertices < fallback_orderv)
2408  {
2409  // Too few vertices for fallback_orderv
2410  // Clamp just in case we made a mistake elsewhere with a closed 1-vertex curve having 1 edge.
2411  fallback_orderv = SYSclamp(int(curve_nvertices), 2, 3);
2412  }
2413  if (fallback_orderu == 0)
2414  {
2415  // Either not auto, or curve was NURBS but all of the cross sections were polygon.
2416  fallback_orderu = (is_primtype_auto && cross_section_input != nullptr) ? 2 : 4;
2417  }
2418  exint cross_section_nvertices = grid_info.myCrossSectionNEdges + (!grid_info.myCrossSectionClosed || grid_info.myCrossSectionUnrolled);
2419  if (cross_section_nvertices < fallback_orderu)
2420  {
2421  // Too few vertices for fallback_orderu
2422  // Clamp just in case we made a mistake elsewhere with a closed 1-vertex curve having 1 edge.
2423  fallback_orderu = SYSclamp(int(cross_section_nvertices), 2, 3);
2424  }
2425  }
2426  else if (grid_info.myPrimitiveType == PrimitiveType::BEZIER)
2427  {
2428  if (fallback_orderv == 0)
2429  {
2430  // Either not auto, or curve was polygon but one of the cross sections was Bezier.
2431  // Use order 2 if auto, since that's the closest match to polygon,
2432  // but use order 4 if not auto, since the user specifically chose Bezier.
2433  fallback_orderv = is_primtype_auto ? 2 : 4;
2434  }
2435  if (grid_info.myCurveNEdges % (fallback_orderv-1) != 0)
2436  {
2437  // Fall back to order 2, since number of edges isn't correct.
2438  fallback_orderv = 2;
2439  }
2440  if (fallback_orderu == 0)
2441  {
2442  // Either not auto, or curve was Bezier but all of the cross sections were polygon.
2443  fallback_orderu = is_primtype_auto ? 2 : 4;
2444  }
2445  if (grid_info.myCrossSectionNEdges % (fallback_orderu-1) != 0)
2446  {
2447  // Fall back to order 2, since number of edges isn't correct.
2448  fallback_orderu = 2;
2449  }
2450  }
2451  grid_info.myBasisOrderCrossSection = fallback_orderu;
2452  grid_info.myBasisOrderCurve = fallback_orderv;
2453 
2454  // Number of points, taking into account rows/cols that are collapsed into single points.
2455  num_points = (grid_info.myCurveNEdges + !grid_info.myCurveClosed - 2*exint(grid_info.myVEndPoles))
2456  * (grid_info.myCrossSectionNEdges + !grid_info.myCrossSectionClosed - 2*exint(grid_info.myUEndPoles))
2457  + 2*exint(grid_info.myVEndPoles) + 2*exint(grid_info.myUEndPoles);
2458 
2459  return true;
2460 }
2461 
2462 template<typename T>
2463 static void
2464 initGUGrid(
2465  const sop_SweepGrid &grid_info,
2466  const GEO_SurfaceType surface_type,
2467  const bool output_points_only,
2468  const bool triangular_poles,
2469  const bool swap_row_col,
2470  const T grid_start_ptnum,
2471 
2472  GU_GridT<T> &grid)
2473 {
2474  grid.myTriangularPoles = triangular_poles;
2475  // TODO: Add support to GU_GridT for separate unrolled rows vs. unrolled cols.
2476  grid.myUnrollCurves = grid_info.myCurveUnrolled || grid_info.myCrossSectionUnrolled;
2477  grid.mySurfaceType = surface_type;
2478  using PrimitiveType = typename GU_GridT<T>::PrimitiveType;
2479  bool is_single_grid_prim = false;
2480  if (output_points_only)
2481  grid.myPrimitiveType = PrimitiveType::POINTS;
2482  else
2483  {
2484  grid.myPrimitiveType = PrimitiveType(int(grid_info.myPrimitiveType));
2485  is_single_grid_prim = (grid.myPrimitiveType != PrimitiveType::POLYGON);
2486  }
2487 
2488  grid.myFirstColIfNotWrapped = true;
2489  grid.myLastColIfNotWrapped = true;
2490  grid.myFirstRowIfNotWrapped = true;
2491  grid.myLastRowIfNotWrapped = true;
2492  grid.myCurvesIfBasisRowCol = true;
2493 
2494  const exint nedgerows = swap_row_col ? grid_info.myCrossSectionNEdges : grid_info.myCurveNEdges;
2495  const exint nedgecols = swap_row_col ? grid_info.myCurveNEdges : grid_info.myCrossSectionNEdges;
2496  bool vclosed = swap_row_col ? grid_info.myCrossSectionClosed : grid_info.myCurveClosed;
2497  bool uclosed = swap_row_col ? grid_info.myCurveClosed : grid_info.myCrossSectionClosed;
2498  bool uendpoles = swap_row_col ? grid_info.myVEndPoles : grid_info.myUEndPoles;
2499  bool vendpoles = swap_row_col ? grid_info.myUEndPoles : grid_info.myVEndPoles;
2500  if (is_single_grid_prim)
2501  {
2502  const exint nvtxrows = nedgerows + (!vclosed || grid.myUnrollCurves);
2503  const exint nvtxcols = nedgecols + (!uclosed || grid.myUnrollCurves);
2504  if (nvtxrows < 2 || nvtxcols < 2)
2505  {
2506  is_single_grid_prim = false;
2507  grid.myPrimitiveType = PrimitiveType::POLYGON;
2508  }
2509  else
2510  {
2511  unsigned char orderv = swap_row_col ? grid_info.myBasisOrderCrossSection : grid_info.myBasisOrderCurve;
2512  unsigned char orderu = swap_row_col ? grid_info.myBasisOrderCurve : grid_info.myBasisOrderCrossSection;
2513  grid.myBasisOrderV = orderv;
2514  grid.myBasisOrderU = orderu;
2515  }
2516  }
2517  if (vclosed)
2518  {
2519  if (uclosed)
2520  grid.initTorus(nedgerows, nedgecols, grid_start_ptnum);
2521  else if (!uendpoles)
2522  grid.initRowTube(nedgerows, nedgecols, grid_start_ptnum);
2523  else
2524  {
2525  grid.myFirstColIfNotWrapped = false;
2526  grid.myLastColIfNotWrapped = false;
2527  const exint nmid_points = nedgerows*(nedgecols - 1);
2528  grid.initRowSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2529  }
2530  }
2531  else
2532  {
2533  if (uclosed)
2534  {
2535  if (!vendpoles)
2536  grid.initColTube(nedgerows, nedgecols, grid_start_ptnum);
2537  else
2538  {
2539  grid.myFirstRowIfNotWrapped = false;
2540  grid.myLastRowIfNotWrapped = false;
2541  const exint nmid_points = (nedgerows - 1)*nedgecols;
2542  grid.initColSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2543  }
2544  }
2545  else
2546  {
2547  if (!vendpoles)
2548  grid.initSingleGrid(nedgerows, nedgecols, grid_start_ptnum);
2549  else
2550  {
2551  grid.myFirstRowIfNotWrapped = false;
2552  grid.myLastRowIfNotWrapped = false;
2553  const exint nmid_points = (nedgerows - 1)*(nedgecols+1);
2554  grid.initSplitColSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2555  }
2556  }
2557  }
2558 }
2559 
2560 static void
2561 appendPrimTypeCountPair(
2562  UT_Array<std::pair<int,exint>> &prim_type_count_pairs,
2563  int primtype,
2564  exint count)
2565 {
2566  if (prim_type_count_pairs.isEmpty() || prim_type_count_pairs.last().first != primtype)
2567  {
2568  prim_type_count_pairs.append(std::pair<int,exint>(primtype, count));
2569  }
2570  else
2571  {
2572  prim_type_count_pairs.last().second += count;
2573  }
2574 }
2575 
2576 // NOTE: closed_span_lengths must be non-empty. It can have an entry with value 0 instead.
2577 // createGrids always ensures that closed_span_lengths is not empty.
2578 static void
2579 appendClosedSpans(
2580  UT_Array<exint> &closed_span_lengths,
2581  bool closed)
2582 {
2583  // Odd sizes (even indices) are for open polygon counts.
2584  // Even sizes (odd indices) are for closed polygon counts.
2585  if (!(closed_span_lengths.size() & 1) == closed)
2586  ++closed_span_lengths.last();
2587  else
2588  closed_span_lengths.append(1);
2589 }
2590 
2591 // appends to GEObuildPrimitives arrays, the info for building
2592 // a single grid based on the data in grid_info.
2593 // NOTE: num_grid_prims and num_grid_verts include caps.
2594 static void
2595 appendSingleGridTopology(
2596  const sop_SweepGrid &grid_info,
2597  const GEO_SurfaceType surface_type,
2598  const bool output_points_only,
2599  const bool triangular_poles,
2600  const bool single_polygon_caps,
2601  const bool swap_row_col,
2602 
2603  UT_Array<std::pair<int,exint>> &prim_type_count_pairs,
2604  //GA_Size &npoints, // npoints should already have been computed for grid_info
2605  GA_PolyCounts &vertexlistsizelist,
2606  UT_Array<exint> &vertexpointnumbers,
2607  UT_Array<exint> &closed_span_lengths,
2608  GA_Size &num_grid_prims,
2609  GA_Size &num_grid_verts)
2610 {
2611  // Before creating the geometry, myStartPtOff is relative,
2612  // so just an exint, not a true GA_Offset.
2613  exint grid_start_ptnum = exint(grid_info.myStartPtOff);
2614  GU_GridT<exint> grid;
2615  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_start_ptnum, grid);
2616 
2617  num_grid_prims = grid.myNumPrimitives;
2618  num_grid_verts = grid.myNumVertices;
2619 
2620  if (num_grid_prims == 0)
2621  return;
2622 
2623  bool current_has_polygon_caps = grid_info.myHasPolygonCaps;
2624  bool is_row_cap = current_has_polygon_caps && grid.myNoWrapV;
2625  bool is_cross_section_cap = !grid_info.myCrossSectionClosed;
2626  exint cap_count = current_has_polygon_caps ? 2 : 0;
2627 
2628  int primtype = GA_PRIMPOLY;
2629  int cap_primtype = GA_PRIMPOLY;
2630  using PrimitiveType = GU_GridT<exint>::PrimitiveType;
2631  if (grid.myPrimitiveType == PrimitiveType::NURBS)
2632  {
2633  // FIXME: Take into account grid.myCurvesIfBasisRowCol when implemented!!!
2634  primtype = GA_PRIMNURBSURF;
2635  cap_primtype = GA_PRIMNURBCURVE;
2636  }
2637  else if (grid.myPrimitiveType == PrimitiveType::BEZIER)
2638  {
2639  // FIXME: Take into account grid.myCurvesIfBasisRowCol when implemented!!!
2640  primtype = GA_PRIMBEZSURF;
2641  cap_primtype = GA_PRIMBEZCURVE;
2642  }
2643  else if (grid.myPrimitiveType == PrimitiveType::MESH || grid.myPrimitiveType == PrimitiveType::POLYSOUP)
2644  {
2645  primtype = (grid.myPrimitiveType == PrimitiveType::MESH) ? GA_PRIMMESH : GA_PRIMPOLYSOUP;
2646  cap_primtype = GA_PRIMPOLY;
2647  }
2648 
2649  if (primtype == GA_PRIMPOLY || cap_count == 0)
2650  {
2651  appendPrimTypeCountPair(prim_type_count_pairs, primtype, num_grid_prims + cap_count);
2652  }
2653  else
2654  {
2655  appendPrimTypeCountPair(prim_type_count_pairs, cap_primtype, 1);
2656  appendPrimTypeCountPair(prim_type_count_pairs, primtype, num_grid_prims);
2657  appendPrimTypeCountPair(prim_type_count_pairs, cap_primtype, 1);
2658  }
2659 
2660  exint cap_vertex_count = 0;
2661  if (current_has_polygon_caps)
2662  cap_vertex_count = is_row_cap ? grid.myNumEdgeCols : grid.myNumEdgeRows;
2663  auto &&add_polygon_cap_functor = [is_row_cap,cap_vertex_count,swap_row_col,is_cross_section_cap,&grid,&vertexlistsizelist,&closed_span_lengths,&vertexpointnumbers](bool last_cap)
2664  {
2665  vertexlistsizelist.append(cap_vertex_count);
2666 
2667  // Single polygon caps are closed.
2668  appendClosedSpans(closed_span_lengths, true);
2669 
2670  const exint old_size = vertexpointnumbers.size();
2671  vertexpointnumbers.bumpSize(old_size + cap_vertex_count);
2672  exint *vertexpointnumber_start = vertexpointnumbers.getArray() + old_size;
2673  if (is_row_cap)
2674  {
2675  // First or last row
2676  exint row = last_cap ? grid.myNumEdgeRows : 0;
2677  for (exint col = 0; col < cap_vertex_count; ++col)
2678  {
2679  exint point_col = ((last_cap != swap_row_col) != is_cross_section_cap) ? col : reverseVtx(col, cap_vertex_count, true);
2680  vertexpointnumber_start[col] = grid.getPoint(row, point_col);
2681  }
2682  }
2683  else
2684  {
2685  // First or last column
2686  exint col = last_cap ? grid.myNumEdgeCols : 0;
2687  for (exint row = 0; row < cap_vertex_count; ++row)
2688  {
2689  exint point_row = ((last_cap != swap_row_col) != is_cross_section_cap) ? row : reverseVtx(row, cap_vertex_count, true);
2690  vertexpointnumber_start[row] = grid.getPoint(point_row, col);
2691  }
2692  }
2693  };
2694  if (current_has_polygon_caps)
2695  {
2696  // Start cap comes before main grid
2697  add_polygon_cap_functor(false);
2698  }
2699 
2700  // Add main grid primitives
2702  [&vertexlistsizelist,&closed_span_lengths
2704  ,num_grid_prims
2705 #endif
2706  ](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
2707  {
2708  UT_ASSERT_P(primnum >= 0 && primnum < num_grid_prims);
2709  vertexlistsizelist.append(primvtxcount);
2710 
2711  appendClosedSpans(closed_span_lengths, closed);
2712  });
2713 
2714  const exint old_size = vertexpointnumbers.size();
2715  vertexpointnumbers.bumpSize(old_size + grid.myNumVertices);
2716  exint *vertexpointnumber_start = vertexpointnumbers.getArray() + old_size;
2717  GUiterateGridVertices(grid,
2718  [vertexpointnumber_start,&grid](exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
2719  {
2720  UT_ASSERT_P(vtxnum >= 0 && vtxnum < grid.myNumVertices);
2721  vertexpointnumber_start[vtxnum] = grid.getPoint(row+exint(isrowend),col+exint(iscolend));
2722  });
2723 
2724  if (current_has_polygon_caps)
2725  {
2726  // End cap comes after main grid
2727  add_polygon_cap_functor(true);
2728  }
2729 
2730  // Make sure that caps are counted in num_grid_prims and num_grid_verts,
2731  // so that the caller knows the total for this grid (including caps).
2732  num_grid_prims += cap_count;
2733  num_grid_verts += 2*cap_vertex_count;
2734 }
2735 
2736 static void
2737 initNonPolyGridPrims(
2738  const sop_SweepGrid &grid_info,
2739  const GU_GridT<GA_Offset> &grid,
2740  const bool swap_row_col,
2741  const GA_Basis *source_basisu,
2742  const GA_Basis *source_basisv,
2743  GEO_Detail *output_geo)
2744 {
2745  using PrimitiveType = GU_GridT<GA_Offset>::PrimitiveType;
2746  if (grid.myPrimitiveType == PrimitiveType::POLYGON ||
2747  grid.myPrimitiveType == PrimitiveType::POINTS)
2748  return;
2749 
2750  GA_Offset primoff = grid_info.myStartPrimOff;
2751  if (grid_info.myHasPolygonCaps)
2752  ++primoff;
2753 
2754  GA_Primitive *prim = output_geo->getPrimitive(primoff);
2755  const exint nedgerows = grid.myNumEdgeRows;
2756  const exint nedgecols = grid.myNumEdgeCols;
2757  const bool hull_wrapv = grid.myAllWrapV && !grid.myUnrollCurves;
2758  const bool hull_wrapu = grid.myAllWrapU && !grid.myUnrollCurves;
2759  const exint nvtxrows = nedgerows + exint(!hull_wrapv);
2760  const exint nvtxcols = nedgecols + exint(!hull_wrapu);
2761 
2762  if (grid.myPrimitiveType == PrimitiveType::POLYSOUP)
2763  {
2764  GEO_PrimPolySoup *polysoup = UTverify_cast<GEO_PrimPolySoup *>(prim);
2765 
2766  // Iterate over the internal polygons
2767  sop_SweepGrid polygrid_info(grid_info);
2768  polygrid_info.myPrimitiveType = sop_SweepGrid::PrimitiveType::POLYGON;
2769  GU_GridT<GA_Offset> polygrid;
2770  initGUGrid(polygrid_info, grid.mySurfaceType, false, grid.myTriangularPoles, swap_row_col, polygrid_info.myStartPtOff, polygrid);
2771 
2772  GA_PolyCounts polygonsizes;
2773  GUiterateGridPrimitives(polygrid,
2774  [&polygonsizes](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
2775  {
2776  polygonsizes.append(primvtxcount, 1);
2777  });
2778 
2779  // Preallocate memory for polygonvertexlist,
2780  // since it's unlikely to be trivial-compressed anyway.
2781  GA_OffsetList polygonvertexlist;
2782  polygonvertexlist.harden(polygrid.myNumVertices);
2783 
2784  // The following comment and two bool definitions were copied from GUiterateGridVertices.
2785  // "Unroll" in this case allows for UV seams.
2786  // FIXME: Does this need to take into account myFirstRowIfNotWrapped,
2787  // myLastRowIfNotWrapped, myFirstColIfNotWrapped, myLastColIfNotWrapped
2788  // if surface_type is rows or cols or rowcol?
2789  const bool has_endrow = grid.myNoWrapV || grid.myUnrollCurves;
2790  const bool has_endcol = grid.myNoWrapU || grid.myUnrollCurves;
2791  const exint nvtxcols = grid.myNumEdgeCols + exint(has_endcol);
2792 
2793  // Remember that the polygon soup primitive's vertices start after the first cap.
2794  exint cap_vertex_count = 0;
2795  if (grid_info.myHasPolygonCaps)
2796  {
2797  cap_vertex_count = !grid_info.myCurveClosed ? grid_info.myCrossSectionNEdges : grid_info.myCurveNEdges;
2798  }
2799  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
2800  GUiterateGridVertices(polygrid,
2801  [&polygonvertexlist,start_vtxoff,&grid,has_endrow,has_endcol,nvtxcols](exint vtxnum, exint row, exint col,
2802  bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
2803  {
2804  // NOTE: vtxnum is the vtxnum if it were polygons, but we need the vtxnum within the soup.
2805  if (isrowend)
2806  {
2807  if (!has_endrow && row == grid.myNumEdgeRows-1)
2808  row = 0;
2809  else
2810  ++row;
2811  }
2812  if (iscolend)
2813  {
2814  if (!has_endcol && col == grid.myNumEdgeCols-1)
2815  col = 0;
2816  else
2817  ++col;
2818  }
2819  exint soupvtxnum = row*nvtxcols + col;
2820  polygonvertexlist.append(start_vtxoff + soupvtxnum);
2821  });
2822 
2823  // This initializes GEO_PrimPolySoup::myPolygonSizes and myPolygonVertexList
2824  polysoup->appendPolygons(polygonsizes, polygonvertexlist);
2825 
2826  return;
2827  }
2828 
2829  // Bilinear mesh, NURBS surface, or Bezier surface
2830  GEO_Hull *hull = UTverify_cast<GEO_Hull *>(prim);
2831  hull->initHullData(nvtxrows, nvtxcols, hull_wrapv, hull_wrapu);
2832  hull->setSurfaceType(grid.mySurfaceType);
2833 
2834  const bool is_nurbs = (grid.myPrimitiveType == PrimitiveType::NURBS);
2835  const bool is_bezier = (grid.myPrimitiveType == PrimitiveType::BEZIER);
2836  if (!is_nurbs && !is_bezier)
2837  return;
2838 
2839  GEO_TPSurf *tpsurf = UTverify_cast<GEO_TPSurf *>(hull);
2840 
2841  GA_Basis *basisu = nullptr;
2842  GA_Basis *basisv = nullptr;
2843 
2844  GA_BASIS_TYPE basis_type = is_nurbs ? GA_NURBS_BASIS : GA_BEZIER_BASIS;
2845  if (source_basisu != nullptr && source_basisu->getType() != basis_type)
2846  source_basisu = nullptr;
2847  if (source_basisv != nullptr && source_basisv->getType() != basis_type)
2848  source_basisv = nullptr;
2849 
2850  // Copy any source basis
2851  if (source_basisu != nullptr)
2852  {
2853  basisu = GA_Basis::newSpecies(basis_type);
2854  basisu->copyFrom(*source_basisu);
2855 
2856  tpsurf->setUBasis(basisu);
2857  // NOTE: The "Ensure Unique Seam Vertices" option might mean
2858  // the source basis isn't valid for the target.
2859  if (!tpsurf->getUBasis())
2860  {
2861  delete basisu;
2862  basisu = nullptr;
2863  }
2864  }
2865  if (source_basisv != nullptr)
2866  {
2867  basisv = GA_Basis::newSpecies(basis_type);
2868  basisv->copyFrom(*source_basisv);
2869 
2870  tpsurf->setVBasis(basisv);
2871  // NOTE: The "Ensure Unique Seam Vertices" option might mean
2872  // the source basis isn't valid for the target.
2873  if (!tpsurf->getVBasis())
2874  {
2875  delete basisv;
2876  basisv = nullptr;
2877  }
2878  }
2879 
2880  if (is_nurbs)
2881  {
2882  auto &&make_nurbs_basis = [](exint nvertices, int order, bool closed) -> GA_NUBBasis*
2883  {
2884  // For now, we never end-interpolate closed cases.
2885  bool interpolate_ends = !closed;
2886  int extra_knots = order + (closed ? (interpolate_ends ? 1 : (order - 1)) : 0);
2887  // Number of knots = cvs+order.
2888  // However, if closed we need to add yet a few more knots:
2889  // 1 if interpEnds and order-1 otherwise
2890  return new GA_NUBBasis(0, 1,
2891  (nvertices + extra_knots),
2892  order,
2893  interpolate_ends);
2894  };
2895 
2896  if (basisu == nullptr)
2897  {
2898  basisu = make_nurbs_basis(nvtxcols, grid.myBasisOrderU, hull_wrapu);
2899  tpsurf->setUBasis(basisu);
2900  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2901  }
2902  if (basisv == nullptr)
2903  {
2904  basisv = make_nurbs_basis(nvtxrows, grid.myBasisOrderV, hull_wrapv);
2905  tpsurf->setVBasis(basisv);
2906  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2907  }
2908  }
2909  else
2910  {
2911  UT_ASSERT(is_bezier);
2912 
2913  auto &&make_bez_basis = [](exint nvertices, int order, bool closed) -> GA_BezBasis*
2914  {
2915  int extra = ((nvertices > 2 && order > 2) || (order <= 2 && closed));
2916  return new GA_BezBasis(0, 1,
2917  extra + (nvertices / (order - 1)),
2918  order);
2919  };
2920  if (basisu == nullptr)
2921  {
2922  basisu = make_bez_basis(nvtxcols, grid.myBasisOrderU, hull_wrapu);
2923  tpsurf->setUBasis(basisu);
2924  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2925  }
2926  if (basisv == nullptr)
2927  {
2928  basisv = make_bez_basis(nvtxrows, grid.myBasisOrderV, hull_wrapv);
2929  tpsurf->setVBasis(basisv);
2930  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2931  }
2932  }
2933 
2934  // setUBasis and setVBasis should always have succeeded,
2935  // because we created the bases correctly.
2936  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2937  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2938 
2939  if (grid_info.myHasPolygonCaps)
2940  {
2941  const bool side_caps = !grid_info.myCrossSectionClosed;
2942  const GA_Basis *cap_source_basis = (swap_row_col != side_caps) ? basisv : basisu;
2943  // Copy the U basis for each of the cap curves.
2944  GA_Basis *cap0_basis = GA_Basis::newSpecies(cap_source_basis->getType());
2945  GA_Basis *cap1_basis = GA_Basis::newSpecies(cap_source_basis->getType());
2946  cap0_basis->copyFrom(*cap_source_basis);
2947  cap1_basis->copyFrom(*cap_source_basis);
2948  // The caps are just before and just after the surface primitive.
2949  GEO_Curve *cap0 = UTverify_cast<GEO_Curve *>(output_geo->getPrimitive(primoff-1));
2950  GEO_Curve *cap1 = UTverify_cast<GEO_Curve *>(output_geo->getPrimitive(primoff+1));
2951  cap0->setBasis(cap0_basis);
2952  cap1->setBasis(cap1_basis);
2953  }
2954 }
2955 
2956 static bool
2957 createGrids(
2958  const GEO_Detail *cross_section_input,
2959  const GA_PrimitiveGroup *cross_section_group,
2960  SurfaceShape cross_section_shape,
2961  exint cross_section_nedges,
2962  const CopyOrder copy_order,
2963  const CrossSectionAttribMatchData *const copy_order_attrib_data,
2964 
2965  const GEO_Detail *curve_input,
2966  const GA_PrimitiveGroup *curve_group,
2967  const bool closed_if_no_curve_input,
2968 
2969  const GEO_SurfaceType surface_type,
2970  const bool output_points_only,
2971  const bool unroll_closed_row_col,
2972  const int primitive_type,
2973  const EndCapType end_cap_type,
2974  const exint cap_divisions,
2975  const bool triangular_poles,
2976  const bool swap_row_col,
2977 
2978  GEO_Detail *output_geo,
2979  UT_Array<sop_SweepGrid> &grids)
2980 {
2981  // Clear the array of grids.
2982  grids.setCapacity(0);
2983 
2984  // Destroy whatever was in the previous detail.
2985  output_geo->clearAndDestroy();
2986 
2987  UT_AutoInterrupt interrupt("Computing surface topology");
2988  if (interrupt.wasInterrupted())
2989  return false;
2990 
2991  // These arrays keep a pointer to the U or V basis of each grid,
2992  // if there are NURBS/Bezier curves or cross sections.
2993  UT_Array<const GA_Basis*> grid_basisus;
2994  UT_Array<const GA_Basis*> grid_basisvs;
2995  if (cross_section_input != nullptr && (
2996  cross_section_input->countPrimitiveType(GA_PRIMNURBCURVE) != 0 ||
2997  cross_section_input->countPrimitiveType(GA_PRIMBEZCURVE) != 0))
2998  {
2999  // Some grids may be invalid, so this will sometimes overallocate, but should be fine.
3000  exint max_num_grids = 1;
3001  if (curve_input != nullptr)
3002  max_num_grids = curve_group ? curve_group->entries() : curve_input->getNumPrimitives();
3003  grid_basisus.setSizeNoInit(max_num_grids);
3004  grid_basisus.constant(nullptr);
3005  }
3006  if (curve_input != nullptr && (
3007  curve_input->countPrimitiveType(GA_PRIMNURBCURVE) != 0 ||
3008  curve_input->countPrimitiveType(GA_PRIMBEZCURVE) != 0))
3009  {
3010  // Some grids may be invalid, so this will sometimes overallocate, but should be fine.
3011  const exint max_num_grids = curve_group ? curve_group->entries() : curve_input->getNumPrimitives();
3012  grid_basisvs.setSizeNoInit(max_num_grids);
3013  grid_basisvs.constant(nullptr);
3014  }
3015 
3016  bool single_cross_section = true;
3017  GA_Offset single_cross_section_primoff = GA_INVALID_OFFSET;
3018  bool cross_section_closed = (cross_section_shape == SurfaceShape::TUBE || cross_section_shape == SurfaceShape::SQUARE);
3019  bool cross_section_unrolled = false;
3020  GA_Range cross_section_range;
3021  if (cross_section_input != nullptr)
3022  {
3023  // If cross_section_group is null, this will be the whole range of primitives.
3024  cross_section_range = cross_section_input->getPrimitiveRange(cross_section_group);
3025  GA_Size ncross_sections = cross_section_range.getEntries();
3026  if (ncross_sections == 0)
3027  {
3028  // Nothing to do if no cross sections.
3029  // We must exit early, because code below assumes that there's
3030  // at least one cross section.
3031  return true;
3032  }
3033  // If selecting cross sections by attribute, there may be some invalid selections
3034  // that must be handled, even if there's only one valid selection.
3035  single_cross_section = (cross_section_range.getEntries() == 1 && copy_order != CopyOrder::ATTRIB);
3036  if (single_cross_section)
3037  {
3038  // Special case for a single cross section, since
3039  // it's a common case, and can be faster.
3040  GA_Iterator it(cross_section_range);
3041  UT_ASSERT(!it.atEnd());
3042  single_cross_section_primoff = *it;
3043  bool nonempty = getPolyProperties(cross_section_input, single_cross_section_primoff,
3044  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3045  if (!nonempty)
3046  return true;
3047  }
3048  }
3049 
3050  GA_Iterator cross_section_it;
3051  if (!single_cross_section || curve_input == nullptr)
3052  cross_section_it = GA_Iterator(cross_section_range);
3053 
3054  const bool is_primtype_auto = !output_points_only && (primitive_type == GA_PRIMNONE);
3055 
3056  UT_SmallArray<std::pair<int,exint>,sizeof(std::pair<int,exint>)> prim_type_count_pairs;
3057  GA_PolyCounts vertexlistsizelist;
3058  UT_Array<exint> vertexpointnumbers;
3059  UT_SmallArray<exint,2*sizeof(exint)> closed_span_lengths;
3060  // Code in appendSingleGridTopology requires at least one initial entry in closed_span_lengths.
3061  closed_span_lengths.append(0);
3062 
3063  // TODO: If it's worthwhile for the no shared points case,
3064  // take into account unrolling to determine hassharedpoints accurately.
3065  bool hassharedpoints = true;//(!output_points_only && surface_type != GEO_PATCH_COLS && surface_type != GEO_PATCH_ROWS);
3066  exint total_num_points = 0;
3067  exint total_num_verts = 0;
3068  exint total_num_prims = 0;
3069  if (curve_input == nullptr)
3070  {
3071  // No curve input, so just one grid of all cross sections.
3072  UT_ASSERT(cross_section_input != nullptr);
3073 
3074  UT_ASSERT(grid_basisvs.size() == 0);
3075 
3076  int local_primitive_type = primitive_type;
3077  unsigned char fallback_orderu = 0;
3078  unsigned char fallback_orderv;
3079  initPrimType(
3080  output_points_only,
3081  is_primtype_auto,
3082  local_primitive_type,
3083  fallback_orderv,
3084  nullptr,
3085  nullptr, GA_INVALID_OFFSET); // curve
3086 
3087  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[0];
3088 
3089  sop_SweepGrid grid_info;
3090  exint num_points;
3091  bool valid_grid = computeSingleGridSetup(
3092  cross_section_input,
3093  cross_section_group,
3094  false, // single_cross_section
3095  cross_section_it,
3097  0, false, false, CopyOrder::CYCLEVTX, nullptr, false, nullptr, // unused
3098  nullptr, GA_INVALID_OFFSET, // curve
3099  closed_if_no_curve_input,
3100  surface_type, output_points_only,
3101  unroll_closed_row_col,
3102  is_primtype_auto,
3103  local_primitive_type,
3104  fallback_orderu, fallback_orderv,
3105  pbasisu,
3106  end_cap_type,
3107  cap_divisions,
3108  grid_info, num_points);
3109 
3110  if (!valid_grid)
3111  return true;
3112 
3113  grid_info.myStartPtOff = GA_Offset(0);
3114  total_num_points = num_points;
3115 
3116  exint num_grid_prims;
3117  exint num_grid_verts;
3118  appendSingleGridTopology(
3119  grid_info, surface_type, output_points_only, triangular_poles,
3120  grid_info.myHasPolygonCaps, swap_row_col,
3121  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3122  closed_span_lengths, num_grid_prims, num_grid_verts);
3123 
3124  grid_info.myStartVtxOff = GA_Offset(0);
3125  total_num_verts = num_grid_verts;
3126  grid_info.myStartPrimOff = GA_Offset(0);
3127  total_num_prims = num_grid_prims;
3128  grids.append(std::move(grid_info));
3129  }
3130  else if (!single_cross_section && copy_order == CopyOrder::EACH)
3131  {
3132  // For each cross section, loop over all curves.
3133  // FIXME: Parallelize this!!!
3134  for (; !cross_section_it.atEnd(); ++cross_section_it)
3135  {
3136  GA_Offset cross_section_primoff = *cross_section_it;
3137 
3138  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3139  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3140  if (!nonempty)
3141  continue;
3142 
3143  GA_Offset start;
3144  GA_Offset end;
3145  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3146  {
3147  if (interrupt.wasInterrupted())
3148  return false;
3149 
3150  GA_Iterator invalid_it;
3151 
3152  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3153  {
3154  const exint gridi = grids.size();
3155  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3156  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3157 
3158  int local_primitive_type = primitive_type;
3159  unsigned char fallback_orderu = 0;
3160  unsigned char fallback_orderv;
3161  initPrimType(
3162  output_points_only,
3163  is_primtype_auto,
3164  local_primitive_type,
3165  fallback_orderv,
3166  pbasisv,
3167  curve_input,
3168  curve_primoff);
3169 
3170  // Single cross section, so primitive type must be handled in advance.
3171  if (is_primtype_auto && cross_section_input != nullptr)
3172  updateAutoPrimType(cross_section_input, cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3173 
3174  sop_SweepGrid grid_info;
3175  exint num_points;
3176  bool valid_grid = computeSingleGridSetup(
3177  nullptr, nullptr, // cross section resolved above
3178  true, // single_cross_section for this grid
3179  invalid_it,
3180  cross_section_primoff,
3181  cross_section_nedges,
3182  cross_section_closed,
3183  cross_section_unrolled,
3184  copy_order,
3185  nullptr,// copy_order_attrib_data unused
3186  false, // varying_nedges_all_case unused
3187  nullptr,// cross_section_primoffs_all_case unused
3188  curve_input, curve_primoff,
3189  false, // closed_if_no_curve_input unused
3190  surface_type,
3191  output_points_only,
3192  unroll_closed_row_col,
3193  is_primtype_auto,
3194  local_primitive_type,
3195  fallback_orderu, fallback_orderv,
3196  pbasisu,
3197  end_cap_type,
3198  cap_divisions,
3199  grid_info,
3200  num_points);
3201 
3202  if (!valid_grid)
3203  continue;
3204 
3205  grid_info.myStartPtOff = GA_Offset(total_num_points);
3206  total_num_points += num_points;
3207 
3208  exint num_grid_prims;
3209  exint num_grid_verts;
3210  appendSingleGridTopology(
3211  grid_info, surface_type, output_points_only, triangular_poles,
3212  grid_info.myHasPolygonCaps, swap_row_col,
3213  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3214  closed_span_lengths, num_grid_prims, num_grid_verts);
3215 
3216  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3217  total_num_verts += num_grid_verts;
3218  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3219  total_num_prims += num_grid_prims;
3220  grids.append(std::move(grid_info));
3221  }
3222  }
3223  }
3224  }
3225  else if (!single_cross_section && (copy_order == CopyOrder::CYCLEPR ||
3226  (copy_order == CopyOrder::ATTRIB &&
3227  (copy_order_attrib_data->myCurveAttribOwner == GA_ATTRIB_DETAIL ||
3228  copy_order_attrib_data->myCurveAttribOwner == GA_ATTRIB_PRIMITIVE))))
3229  {
3230  // CopyOrder::CYCLEPR means "For each curve, pick the next cross section."
3231  // CopyOrder::ATTRIB has primitive or detail attribute on curve input selecting the cross section primitives in this case.
3232  GA_AttributeOwner curve_attrib_owner = (copy_order == CopyOrder::ATTRIB) ? copy_order_attrib_data->myCurveAttribOwner : GA_ATTRIB_INVALID;
3233  GA_Offset cross_section_primoff;
3234  if (curve_attrib_owner == GA_ATTRIB_DETAIL)
3235  {
3236  cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data,
3237  GA_DETAIL_OFFSET, cross_section_input, cross_section_group);
3238  // If no cross section for this detail, skip
3239  if (!GAisValid(cross_section_primoff))
3240  return true;
3241  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3242  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3243  if (!nonempty)
3244  return true;
3245  }
3246 
3247  GA_Offset start;
3248  GA_Offset end;
3249  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3250  {
3251  if (interrupt.wasInterrupted())
3252  return false;
3253 
3254  GA_Iterator invalid_it;
3255 
3256  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3257  {
3258  if (copy_order == CopyOrder::CYCLEPR)
3259  {
3260  cross_section_primoff = *cross_section_it;
3261  ++cross_section_it;
3262  if (cross_section_it.atEnd())
3263  {
3264  cross_section_it.rewind();
3265  UT_ASSERT(!cross_section_it.atEnd());
3266  }
3267  }
3268  else if (curve_attrib_owner == GA_ATTRIB_PRIMITIVE)
3269  {
3270  cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data,
3271  curve_primoff, cross_section_input, cross_section_group);
3272  // If no cross section for this curve, skip
3273  if (!GAisValid(cross_section_primoff))
3274  continue;
3275  }
3276 
3277  if (curve_attrib_owner != GA_ATTRIB_DETAIL)
3278  {
3279  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3280  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3281  if (!nonempty)
3282  continue;
3283  }
3284 
3285  const exint gridi = grids.size();
3286  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3287  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3288 
3289  int local_primitive_type = primitive_type;
3290  unsigned char fallback_orderu = 0;
3291  unsigned char fallback_orderv;
3292  initPrimType(
3293  output_points_only,
3294  is_primtype_auto,
3295  local_primitive_type,
3296  fallback_orderv,
3297  pbasisv,
3298  curve_input,
3299  curve_primoff);
3300 
3301  // Single cross section, so primitive type must be handled in advance.
3302  if (is_primtype_auto && cross_section_input != nullptr)
3303  updateAutoPrimType(cross_section_input, cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3304 
3305  sop_SweepGrid grid_info;
3306  exint num_points;
3307  bool valid_grid = computeSingleGridSetup(
3308  nullptr, nullptr, // cross section resolved above
3309  true, // single_cross_section for this grid
3310  invalid_it,
3311  cross_section_primoff,
3312  cross_section_nedges,
3313  cross_section_closed,
3314  cross_section_unrolled,
3315  copy_order,
3316  nullptr,// copy_order_attrib_data unused
3317  false, // varying_nedges_all_case unused
3318  nullptr,// cross_section_primoffs_all_case unused
3319  curve_input, curve_primoff,
3320  false, // closed_if_no_curve_input unused
3321  surface_type, output_points_only,
3322  unroll_closed_row_col,
3323  is_primtype_auto,
3324  local_primitive_type,
3325  fallback_orderu, fallback_orderv,
3326  pbasisu,
3327  end_cap_type,
3328  cap_divisions,
3329  grid_info, num_points);
3330 
3331  if (!valid_grid)
3332  continue;
3333 
3334  grid_info.myStartPtOff = GA_Offset(total_num_points);
3335  total_num_points += num_points;
3336 
3337  exint num_grid_prims;
3338  exint num_grid_verts;
3339  appendSingleGridTopology(
3340  grid_info, surface_type, output_points_only, triangular_poles,
3341  grid_info.myHasPolygonCaps, swap_row_col,
3342  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3343  closed_span_lengths, num_grid_prims, num_grid_verts);
3344 
3345  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3346  total_num_verts += num_grid_verts;
3347  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3348  total_num_prims += num_grid_prims;
3349  grids.append(std::move(grid_info));
3350  }
3351  }
3352  }
3353  else
3354  {
3355  unsigned char fallback_orderu = 0;
3356  int cross_section_primitive_type = is_primtype_auto ? GA_PRIMPOLY : primitive_type;
3357  bool varying_nedges_all_case = false;
3358  GA_OffsetList cross_section_primoffs_all_case;
3359  const bool all_case = (!single_cross_section && copy_order == CopyOrder::ALL);
3360  const GA_Basis *all_case_grid_basisu = nullptr;
3361  if (all_case)
3362  {
3363  // At each vertex, we'll want all cross sections.
3364  // To avoid having to recompute the cross section properties for every vertex,
3365  // iterate through all cross sections in advance.
3366  exint num_cross_sections = (cross_section_group ? cross_section_group->entries() : cross_section_input->getNumPrimitives());
3367 
3368  bool is_valid_grid = computeCombinedCrossSectionProperties(
3369  cross_section_input,
3370  cross_section_group,
3371  cross_section_it,
3372  num_cross_sections,
3373  cross_section_nedges,
3374  cross_section_closed,
3375  cross_section_unrolled,
3376  varying_nedges_all_case,
3377  cross_section_primoffs_all_case,
3378  is_primtype_auto,
3379  cross_section_primitive_type,
3380  fallback_orderu,
3381  &all_case_grid_basisu);
3382 
3383  if (!is_valid_grid)
3384  return true;
3385  }
3386 
3387  // FIXME: Parallelize this!!!
3388  GA_Offset start;
3389  GA_Offset end;
3390  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3391  {
3392  if (interrupt.wasInterrupted())
3393  return false;
3394 
3395  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3396  {
3397  const exint gridi = grids.size();
3398  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3399  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3400 
3401  int local_primitive_type = primitive_type;
3402  unsigned char fallback_orderv;
3403  initPrimType(
3404  output_points_only,
3405  is_primtype_auto,
3406  local_primitive_type,
3407  fallback_orderv,
3408  pbasisv,
3409  curve_input,
3410  curve_primoff);
3411 
3412  // If single cross section, primitive type must be handled in advance.
3413  if (single_cross_section && is_primtype_auto && cross_section_input != nullptr)
3414  updateAutoPrimType(cross_section_input, single_cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3415  else if (all_case && local_primitive_type != GA_PRIMNURBSURF)
3416  {
3417  if (cross_section_primitive_type == GA_PRIMNURBSURF || cross_section_primitive_type == GA_PRIMBEZSURF)
3418  local_primitive_type = cross_section_primitive_type;
3419  }
3420 
3421  if (all_case && pbasisu != nullptr)
3422  {
3423  // In the all cross sections at each vertex case,
3424  // all grids have the same U basis.
3425  *pbasisu = all_case_grid_basisu;
3426  }
3427 
3428  sop_SweepGrid grid_info;
3429  exint num_points;
3430  bool valid_grid = computeSingleGridSetup(
3431  cross_section_input,
3432  cross_section_group,
3433  single_cross_section,
3434  cross_section_it,
3435  single_cross_section_primoff,
3436  cross_section_nedges,
3437  cross_section_closed,
3438  cross_section_unrolled,
3439  copy_order,
3440  copy_order_attrib_data,
3441  varying_nedges_all_case,
3442  (cross_section_primoffs_all_case.size() == 0) ? nullptr : &cross_section_primoffs_all_case,
3443  curve_input, curve_primoff,
3444  closed_if_no_curve_input,
3445  surface_type, output_points_only,
3446  unroll_closed_row_col,
3447  is_primtype_auto,
3448  local_primitive_type,
3449  fallback_orderu, fallback_orderv,
3450  pbasisu,
3451  end_cap_type,
3452  cap_divisions,
3453  grid_info, num_points);
3454 
3455  if (!valid_grid)
3456  continue;
3457 
3458  grid_info.myStartPtOff = GA_Offset(total_num_points);
3459  total_num_points += num_points;
3460 
3461  exint num_grid_prims;
3462  exint num_grid_verts;
3463  appendSingleGridTopology(
3464  grid_info, surface_type, output_points_only, triangular_poles,
3465  grid_info.myHasPolygonCaps, swap_row_col,
3466  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3467  closed_span_lengths, num_grid_prims, num_grid_verts);
3468 
3469  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3470  total_num_verts += num_grid_verts;
3471  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3472  total_num_prims += num_grid_prims;
3473  grids.append(std::move(grid_info));
3474  }
3475  }
3476  }
3477 
3478  if (interrupt.wasInterrupted())
3479  return false;
3480 
3481  // Create the geometry in output_geo
3482  GA_Offset start_ptoff = output_geo->appendPointBlock(total_num_points);
3483  GA_Offset start_vtxoff = GA_INVALID_OFFSET;
3484  GA_Offset start_primoff = GA_INVALID_OFFSET;
3485  if (output_points_only)
3486  output_geo->bumpDataIdsForAddOrRemove(true, false, false);
3487  else
3488  {
3489  start_primoff = GEObuildPrimitives(
3490  output_geo,
3491  prim_type_count_pairs.getArray(),
3492  start_ptoff, total_num_points,
3493  vertexlistsizelist, vertexpointnumbers.getArray(),
3494  hassharedpoints,
3495  closed_span_lengths.getArray());
3496  if (output_geo->getNumVertices() != 0)
3497  start_vtxoff = output_geo->vertexOffset(GA_Index(0));
3498 
3499  output_geo->bumpDataIdsForAddOrRemove(true, true, true);
3500  }
3501 
3502  // NOTE: Since there was never anything else in the detail, start_ptoff,
3503  // start_primoff, and start_vtxoff should all be GA_Offset(0),
3504  // but just in case we ever have anything before it, we'll write
3505  // the code as if they could be anything.
3506 
3507  // Adjust all myStartPtOff, myStartVtxOff, and myStartPrimOff if starts aren't zero.
3508  if (start_ptoff != GA_Offset(0))
3509  {
3510  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3511  {
3512  grids[gridi].myStartPtOff += start_ptoff;
3513  }
3514  }
3515  if (start_vtxoff != GA_INVALID_OFFSET && start_vtxoff != GA_Offset(0))
3516  {
3517  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3518  {
3519  grids[gridi].myStartVtxOff += start_vtxoff;
3520  }
3521  }
3522  if (start_primoff != GA_INVALID_OFFSET && start_primoff != GA_Offset(0))
3523  {
3524  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3525  {
3526  grids[gridi].myStartPrimOff += start_primoff;
3527  }
3528  }
3529 
3530  if (output_geo->getNumPrimitives() != output_geo->countPrimitiveType(GA_PRIMPOLY))
3531  {
3532  // There are non-polygon primitives, so we must initialize
3533  // primitive member data.
3534  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3535  {
3536  const sop_SweepGrid &grid_info = grids[gridi];
3537  GU_GridT<GA_Offset> grid;
3538  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3539  const GA_Basis *basisu = grid_basisus.isEmpty() ? nullptr : grid_basisus[gridi];
3540  const GA_Basis *basisv = grid_basisvs.isEmpty() ? nullptr : grid_basisvs[gridi];
3542  {
3543  initNonPolyGridPrims(grid_info, grid, swap_row_col, basisu, basisv, output_geo);
3544  }
3545  }
3546  }
3547 
3548  return true;
3549 }
3550 
3551 template<typename FUNCTOR>
3552 static void
3553 copyVertexCrossSectionWrapper(
3554  const GU_GridT<GA_Offset> &grid,
3555  const sop_SweepGrid &grid_info,
3556  const FUNCTOR &functor)
3557 {
3558  // FIXME: Is this function producing correct results when swap_row_col is true???!!!
3559 
3560  exint cap_vertex_count = 0;
3561  if (grid_info.myHasPolygonCaps)
3562  {
3563  if (grid.myNoWrapV)
3564  {
3565  // First row cap
3566  cap_vertex_count = grid.myNumEdgeCols;
3567 
3568  for (exint col = 0; col < cap_vertex_count; ++col)
3569  {
3570  // First cap is reversed
3571  functor(grid_info.myStartVtxOff + col, 0, reverseVtx(col, cap_vertex_count, true));
3572  }
3573  }
3574  else
3575  {
3576  // First side col cap
3577  cap_vertex_count = grid.myNumEdgeRows;
3578 
3579  for (exint row = 0; row < cap_vertex_count; ++row)
3580  {
3581  // First cap is reversed
3582  functor(grid_info.myStartVtxOff + row, reverseVtx(row, cap_vertex_count, true), 0);
3583  }
3584  }
3585  }
3586  // Main grid vertices
3587  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
3588  auto &&functor_wrapper = [&functor,start_vtxoff](exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
3589  {
3590  GA_Offset output_vtxoff = start_vtxoff + vtxnum;
3591  functor(output_vtxoff, row + exint(isrowend), col + exint(iscolend));
3592  };
3593  GUiterateGridVertices(grid, functor_wrapper);
3594  if (grid_info.myHasPolygonCaps)
3595  {
3596  // Last cap starts after main grid
3597  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
3598  if (grid.myNoWrapV)
3599  {
3600  // Last row cap
3601  for (exint col = 0; col < cap_vertex_count; ++col)
3602  {
3603  functor(cap_start_vtxoff + col, grid.myNumEdgeRows, col);
3604  }
3605  }
3606  else
3607  {
3608  // Last side col cap
3609  for (exint row = 0; row < cap_vertex_count; ++row)
3610  {
3611  functor(cap_start_vtxoff + row, row, grid.myNumEdgeCols);
3612  }
3613  }
3614  }
3615 }
3616 
3617 template<typename TRANSFORM_T,GA_AttributeOwner output_attrib_owner,bool wrap_to_invalid=false,typename OUTPUT_ATTRIB_T,typename INPUT_ATTRIB_T,typename GET_TRANSFORM_FUNCTOR,typename TRANSFORM_FUNCTOR,typename INTERP_FUNCTOR>
3618 static void
3619 copyCrossSectionAttributeWrapper2(
3620  const GU_GridT<GA_Offset> &grid,
3621  const OUTPUT_ATTRIB_T &outputh,
3622  const INPUT_ATTRIB_T &cross_sectionh,
3623  const GA_AttributeOwner cross_section_owner,
3624  const GEO_Detail *cross_section_input,
3625  const sop_SweepGridTransformWrapper *transforms,
3626  const sop_SweepGrid &grid_info,
3627  const bool reverse_cross_sections,
3628  const bool swap_row_col,
3629  const GET_TRANSFORM_FUNCTOR &get_transform_functor,
3630  const TRANSFORM_FUNCTOR &transform_and_copy_functor,
3631  const INTERP_FUNCTOR &transform_and_interp_functor)
3632 {
3633  UT_ASSERT(cross_section_owner != GA_ATTRIB_INVALID);
3634  UT_ASSERT(cross_section_owner == output_attrib_owner ||
3635  (cross_section_owner == GA_ATTRIB_POINT && output_attrib_owner == GA_ATTRIB_VERTEX) ||
3636  (cross_section_owner == GA_ATTRIB_VERTEX && output_attrib_owner == GA_ATTRIB_POINT));
3637 
3638  exint prev_curve_vtxi = -1;
3639  GA_Offset source_cross_section_primoff;
3640  GA_OffsetListRef cross_section_vertices;
3641  exint current_cross_section_nedges;
3642  bool current_cross_section_closed;
3643  bool current_cross_section_unrolled;
3644  const TRANSFORM_T *transform;
3645  auto &&functor = [&grid_info,&outputh,&cross_sectionh,
3646  cross_section_owner,
3647  &get_transform_functor,&transform_and_copy_functor,
3648  &transform_and_interp_functor,
3649  cross_section_input,
3650  transforms,
3651  reverse_cross_sections,
3652  swap_row_col,
3653  &prev_curve_vtxi,
3654  &source_cross_section_primoff,
3655  &cross_section_vertices,
3656  &current_cross_section_nedges,
3657  &current_cross_section_closed,
3658  &current_cross_section_unrolled,
3659  &transform](GA_Offset output_offset, exint row, exint col)
3660  {
3661  exint num_curve_edges = grid_info.myCurveNEdges;
3662  bool is_curve_closed = grid_info.myCurveClosed;
3663  exint curve_vtxi = swap_row_col ? col : row;
3664  exint num_cross_section_edges = grid_info.myCrossSectionNEdges;
3665  //bool is_cross_section_closed = grid_info.myCrossSectionClosed;
3666  exint cross_section_vtxi = swap_row_col ? row : col;
3667 
3668  if (curve_vtxi != prev_curve_vtxi)
3669  {
3670  // NOTE: This function is only for copying cross section attributes,
3671  // so wrap_to_invalid only applies to column wrapping.
3672  // Rows wrap normally when closed in this case.
3673  if (output_attrib_owner == GA_ATTRIB_VERTEX && is_curve_closed && curve_vtxi == num_curve_edges)
3674  curve_vtxi = 0;
3675  transform = get_transform_functor(transforms, curve_vtxi, grid_info);
3676  source_cross_section_primoff =
3677  grid_info.mySingleCrossSection ?
3678  GA_Offset(grid_info.myCrossSectionPrimOff) :
3679  (*grid_info.myCrossSectionPrimOffs)[curve_vtxi];
3680  UT_ASSERT_P(GAisValid(source_cross_section_primoff));
3681  if (output_attrib_owner != GA_ATTRIB_PRIMITIVE)
3682  {
3683  cross_section_vertices = cross_section_input->getPrimitiveVertexList(source_cross_section_primoff);
3684  getPolyProperties(cross_section_input, cross_section_vertices, current_cross_section_nedges, current_cross_section_closed, current_cross_section_unrolled);
3685  }
3686  prev_curve_vtxi = curve_vtxi;
3687  }
3688 
3689  if (output_attrib_owner == GA_ATTRIB_PRIMITIVE)
3690  {
3691  transform_and_copy_functor(cross_sectionh, source_cross_section_primoff, transform, outputh, output_offset);
3692  return;
3693  }
3694 
3695  constexpr bool is_vertex_attrib = (output_attrib_owner == GA_ATTRIB_VERTEX);
3696 
3697  if (current_cross_section_nedges == num_cross_section_edges)
3698  {
3699  // Copy from a single point.
3700 
3701  if (reverse_cross_sections)
3702  {
3703  // We handle wrapping below, so we reverse like an open polygon for all vertex attributes.
3704  cross_section_vtxi = reverseVtx(cross_section_vtxi, current_cross_section_nedges + exint(is_vertex_attrib || !current_cross_section_closed),
3705  !is_vertex_attrib && current_cross_section_closed);
3706  }
3707 
3708  // NOTE: cross_section_vtxi may not be in-bounds, if this cross section is closed and some are open,
3709  // since in that case, the grid will be open in that direction,
3710  // so two output points will need to copy from vertex 0 of this cross section.
3711  GA_Offset vtxoff;
3712  if (cross_section_vtxi == cross_section_vertices.size())
3713  {
3714  if (wrap_to_invalid)
3715  {
3716  // vertex UV attribute wraps to 1.0 value, not first value.
3717  UT_ASSERT(is_vertex_attrib);
3718  vtxoff = GA_INVALID_OFFSET;
3719  }
3720  else
3721  vtxoff = cross_section_vertices[0];
3722  }
3723  else
3724  vtxoff = cross_section_vertices[cross_section_vtxi];
3725  GA_Offset cross_section_offset = (cross_section_owner == GA_ATTRIB_VERTEX || vtxoff == GA_INVALID_OFFSET) ? vtxoff : cross_section_input->vertexPoint(vtxoff);
3726  transform_and_copy_functor(cross_sectionh, cross_section_offset, transform, outputh, output_offset);
3727  }
3728  else
3729  {
3730  // Possibly interpolate between points.
3731 
3732  // The grid should have at least as many columns as the number of edges in each cross section.
3733  UT_ASSERT_P(num_cross_section_edges > current_cross_section_nedges);
3734 
3735  // Distribute the points evenly, but then snap the first point on a current edge
3736  // to the beginning of the edge and redistribute the points along that edge.
3737  // The division/modulus math is a bit hairy, but hopefully this works correctly.
3738  exint fine_fractions = cross_section_vtxi*current_cross_section_nedges;
3739  if (reverse_cross_sections)
3740  fine_fractions = num_cross_section_edges*current_cross_section_nedges - fine_fractions;
3741  exint current_cross_section_edge = fine_fractions / num_cross_section_edges;
3742  exint current_cross_section_fractions = fine_fractions % num_cross_section_edges;
3743  exint current_cross_section_pt = current_cross_section_fractions / current_cross_section_nedges;
3744  if (current_cross_section_pt == 0)
3745  {
3746  // First point on this edge: no need to interpolate, can just copy.
3747  GA_Offset vtxoff;
3748  if (current_cross_section_edge == cross_section_vertices.size())
3749  {
3750  if (wrap_to_invalid)
3751  {
3752  // vertex UV attribute wraps to 1.0 value, not first value.
3753  UT_ASSERT(is_vertex_attrib);
3754  vtxoff = GA_INVALID_OFFSET;
3755  }
3756  else
3757  vtxoff = cross_section_vertices[0];
3758  }
3759  else
3760  vtxoff = cross_section_vertices[current_cross_section_edge];
3761  GA_Offset cross_section_offset = (cross_section_owner == GA_ATTRIB_VERTEX || vtxoff == GA_INVALID_OFFSET) ? vtxoff : cross_section_input->vertexPoint(vtxoff);
3762  transform_and_copy_functor(cross_sectionh, cross_section_offset, transform, outputh, output_offset);
3763  }
3764  else
3765  {
3766  // Partway along an edge: interpolate.
3767  // current_cross_section_edge_start is the smallest value of cross_section_vtxi that would yield current_cross_section_edge
3768  // current_cross_section_edge_end is the smallest value of cross_section_vtxi that would yield current_cross_section_edge+1
3769  // The difference is the number of points that will yield current_cross_section_edge
3770  exint current_cross_section_edge_start = (current_cross_section_edge*num_cross_section_edges + current_cross_section_nedges-1) / current_cross_section_nedges;
3771  exint current_cross_section_edge_end = ((current_cross_section_edge+1)*num_cross_section_edges + current_cross_section_nedges-1) / current_cross_section_nedges;
3772  exint current_cross_section_edge_npts = current_cross_section_edge_end - current_cross_section_edge_start;
3773  double u = double(current_cross_section_pt)/double(current_cross_section_edge_npts);
3774 
3775  // If current_cross_section_edge == current_cross_section_nedges,
3776  // the branch above to copy should have been taken.
3777  UT_ASSERT_P(current_cross_section_edge != current_cross_section_nedges);
3778  exint vtxi0 = current_cross_section_edge;
3779  GA_Offset vtxoff0 = cross_section_vertices[vtxi0];
3780 
3781  GA_Offset vtxoff1;
3782  if (current_cross_section_edge+1 == cross_section_vertices.size())
3783  {
3784  if (wrap_to_invalid)
3785  {
3786  // vertex UV attribute wraps to 1.0 value, not first value.
3787  UT_ASSERT(is_vertex_attrib);
3788  vtxoff1 = GA_INVALID_OFFSET;
3789  }
3790  else
3791  vtxoff1 = cross_section_vertices[0];
3792  }
3793  else
3794  vtxoff1 = cross_section_vertices[current_cross_section_edge+1];
3795 
3796  GA_Offset cross_section_offset0;
3797  GA_Offset cross_section_offset1;
3798  if (cross_section_owner == GA_ATTRIB_VERTEX)
3799  {
3800  cross_section_offset0 = vtxoff0;
3801  cross_section_offset1 = vtxoff1;
3802  }
3803  else
3804  {
3805  cross_section_offset0 = cross_section_input->vertexPoint(vtxoff0);
3806  if (vtxoff1 != GA_INVALID_OFFSET)
3807  cross_section_offset1 = cross_section_input->vertexPoint(vtxoff1);
3808  else
3809  cross_section_offset1 = vtxoff1;
3810  }
3811  transform_and_interp_functor(cross_sectionh, cross_section_offset0, cross_section_offset1, u, transform, outputh, output_offset);
3812  }
3813  }
3814  };
3815 
3816  SYS_STATIC_ASSERT(output_attrib_owner != GA_ATTRIB_DETAIL);
3817 
3818  if (output_attrib_owner == GA_ATTRIB_VERTEX)
3819  {
3820  copyVertexCrossSectionWrapper(grid, grid_info, functor);
3821  }
3822  else if (output_attrib_owner == GA_ATTRIB_POINT)
3823  GUiterateGridPoints(grid, functor);
3824  else
3825  {
3826  UT_ASSERT_P(output_attrib_owner == GA_ATTRIB_PRIMITIVE);
3827  if (grid_info.myHasPolygonCaps)
3828  {
3829  // First row/col cap
3830  functor(grid_info.myStartPrimOff, 0, 0);
3831  }
3832  // Main grid primitives
3833  const GA_Offset start_primoff = grid_info.myStartPrimOff + exint(grid_info.myHasPolygonCaps);
3834  auto &&functor_wrapper = [&functor,start_primoff](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
3835  {
3836  GA_Offset output_primoff = start_primoff + primnum;
3837  functor(output_primoff, row, col);
3838  };
3839  GUiterateGridPrimitives(grid, functor_wrapper);
3840  if (grid_info.myHasPolygonCaps)
3841  {
3842  const GA_Offset cap_start_primoff = start_primoff + grid.myNumPrimitives;
3843  if (grid.myNoWrapV)
3844  {
3845  // Last row cap
3846  functor(cap_start_primoff, grid.myNumEdgeRows, 0);
3847  }
3848  else
3849  {
3850  // Last side col cap
3851  functor(cap_start_primoff, 0, grid.myNumEdgeCols);
3852  }
3853  }
3854  }
3855 }
3856 
3857 template<typename VALUE_T,typename TRANSFORM_T,GA_AttributeOwner attrib_owner,typename GET_TRANSFORM_FUNCTOR,typename TRANSFORM_FUNCTOR,typename INTERP_FUNCTOR>
3858 static void
3859 copyCrossSectionAttributeWrapper(
3860  GU_GridT<GA_Offset> &grid,
3861  GA_ATINumeric *output_attrib,
3862  const GA_ATINumeric *cross_section_attrib,
3863  const GEO_Detail *cross_section_input,
3864  const sop_SweepGridTransformWrapper *transforms,
3865  const sop_SweepGrid &grid_info,
3866  const bool reverse_cross_sections,
3867  const bool swap_row_col,
3868  const GET_TRANSFORM_FUNCTOR &get_transform_functor,
3869  const TRANSFORM_FUNCTOR &transform_and_copy_functor,
3870  const INTERP_FUNCTOR &transform_and_interp_functor)
3871 {
3872  GA_RWHandleT<VALUE_T> outputh(output_attrib);
3873  GA_ROHandleT<VALUE_T> cross_sectionh(cross_section_attrib);
3874  UT_ASSERT_P(outputh.isValid());
3875  UT_ASSERT_P(cross_sectionh.isValid());
3876  UT_ASSERT(attrib_owner == cross_section_attrib->getOwner());
3877  copyCrossSectionAttributeWrapper2<TRANSFORM_T,attrib_owner>(
3878  grid, outputh, cross_sectionh, attrib_owner,
3879  cross_section_input, transforms, grid_info,
3880  reverse_cross_sections,
3881  swap_row_col,
3882  get_transform_functor,
3883  transform_and_copy_functor,
3884  transform_and_interp_functor);
3885 }
3886 
3887 template<typename T,GA_AttributeOwner attrib_owner>
3888 static void
3889 copyIntegerSingleGrid(
3890  GA_ATINumeric *output_attrib,
3891  const GA_ATINumeric *cross_section_attrib,
3892  const GEO_Detail *cross_section_input,
3893  const sop_SweepGridTransformWrapper *transforms,
3894  const sop_SweepGrid &grid_info,
3895  const GEO_SurfaceType surface_type,
3896  const bool output_points_only,
3897  const bool triangular_poles,
3898  const bool reverse_cross_sections,
3899  const bool swap_row_col)
3900 {
3901  UT_ASSERT_P(cross_section_attrib != nullptr);
3902  UT_ASSERT_P(cross_section_input != nullptr);
3903 
3905 
3906  GU_GridT<GA_Offset> grid;
3907  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3908 
3909  const int tuple_size = output_attrib->getTupleSize();
3910 
3911  // Non-transforming cases follow
3912  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
3913  {
3914  return nullptr;
3915  };
3916 
3917  if (tuple_size == 1)
3918  {
3919  // Copy single-component numeric attribute
3920  auto &&transform_and_copy = [](
3921  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
3922  const void *transform,
3923  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3924  {
3925  T value = cross_sectionh.get(cross_section_offset);
3926  outputh.set(output_offset, value);
3927  };
3928  auto &&transform_and_interp = [](
3929  const GA_ROHandleT<T> &cross_sectionh,
3930  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
3931  const void *transform,
3932  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3933  {
3934  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
3935  T value = cross_sectionh.get(cross_section_offset);
3936  outputh.set(output_offset, value);
3937  };
3938  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
3939  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
3940  reverse_cross_sections, swap_row_col,
3941  get_transform, transform_and_copy, transform_and_interp);
3942  return;
3943  }
3944 
3945  // Copy arbitrary-component numeric attribute
3946  auto &&transform_and_copy = [tuple_size](
3947  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
3948  const void *transform,
3949  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3950  {
3951  for (exint component = 0; component < tuple_size; ++component)
3952  {
3953  T value = cross_sectionh.get(cross_section_offset, component);
3954  outputh.set(output_offset, component, value);
3955  }
3956  };
3957  auto &&transform_and_interp = [tuple_size](
3958  const GA_ROHandleT<T> &cross_sectionh,
3959  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
3960  const void *transform,
3961  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3962  {
3963  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
3964  for (exint component = 0; component < tuple_size; ++component)
3965  {
3966  T value = cross_sectionh.get(cross_section_offset, component);
3967  outputh.set(output_offset, component, value);
3968  }
3969  };
3970  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
3971  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
3972  reverse_cross_sections, swap_row_col,
3973  get_transform, transform_and_copy, transform_and_interp);
3974 }
3975 
3976 template<typename T,GA_TypeInfo transform_type,GA_AttributeOwner attrib_owner>
3977 static void
3978 copyFloatSingleGrid(
3979  GA_ATINumeric *output_attrib,
3980  const GA_ATINumeric *cross_section_attrib,
3981  const GEO_Detail *cross_section_input,
3982  const sop_SweepGridTransformWrapper *transforms,
3983  const sop_SweepGrid &grid_info,
3984  const exint cross_sections_per_vertex,
3985  const exint cap_divisions,
3986  const GEO_SurfaceType surface_type,
3987  const bool output_points_only,
3988  const bool triangular_poles,
3989  const bool reverse_cross_sections,
3990  const bool swap_row_col)
3991 {
3992  UT_ASSERT_P(cross_section_attrib != nullptr);
3993  UT_ASSERT_P(cross_section_input != nullptr);
3994 
3996 
3997  GU_GridT<GA_Offset> grid;
3998  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3999 
4000  const exint cap_rows = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
4001  exint last_cap_start_row = -1;
4002  exint last_cap_start_transform = -1;
4003  if (cross_sections_per_vertex != 1 && cap_rows > 0)
4004  {
4005  last_cap_start_row = grid_info.myCurveNEdges - cap_rows;
4006  last_cap_start_transform = (last_cap_start_row - cap_rows)/cross_sections_per_vertex + cap_rows;
4007  }
4008 
4009  auto &&fix_transformi_for_all_case = [cap_rows,cross_sections_per_vertex,last_cap_start_row,last_cap_start_transform](exint &transformi)
4010  {
4011  // We have multiple cross sections per vertex, except in the caps,
4012  // when CopyOrder::ALL, but only one transform, so we need to
4013  // adjust transformi.
4014  if (cap_rows == 0)
4015  transformi /= cross_sections_per_vertex;
4016  else if (transformi > cap_rows)
4017  {
4018  if (transformi < last_cap_start_row)
4019  transformi = (transformi - cap_rows)/cross_sections_per_vertex + cap_rows;
4020  else
4021  transformi = last_cap_start_transform + (transformi - last_cap_start_row);
4022  }
4023  };
4024 
4025  const int tuple_size = output_attrib->getTupleSize();
4026 
4027  if (transforms)
4028  {
4029  if (transform_type == GA_TYPE_POINT)
4030  {
4031  // Transform position attribute
4033  transform.identity();
4034  UT_ASSERT(tuple_size == 3);
4035  auto &&get_transform = [&transform,cross_sections_per_vertex,&fix_transformi_for_all_case]
4036  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix4T<T>*
4037  {
4038  exint transformi = row;
4039  if (cross_sections_per_vertex != 1)
4040  fix_transformi_for_all_case(transformi);
4041  transform = UT_Matrix4T<T>(transforms->getMatrix3s<T>()[transformi]);
4042  transform.setTranslates(transforms->getTranslates<T>()[transformi]);
4043  return &transform;
4044  };
4045  auto &&transform_and_copy = [](
4046  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4047  const UT_Matrix4T<T> *transform,
4048  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4049  {
4050  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4051  value *= *transform;
4052  outputh.set(output_offset, value);
4053  };
4054  auto &&transform_and_interp = [](
4055  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4056  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4057  const UT_Matrix4T<T> *transform,
4058  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4059  {
4060  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4061  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4062  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4063  value *= *transform;
4064  outputh.set(output_offset, value);
4065  };
4066  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix4T<T>, attrib_owner>(grid,
4067  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4068  reverse_cross_sections, swap_row_col,
4069  get_transform, transform_and_copy, transform_and_interp);
4070  return;
4071  }
4072  if (transform_type == GA_TYPE_NORMAL)
4073  {
4074  // Transform normal attribute
4075  UT_ASSERT(tuple_size == 3);
4076  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4077  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4078  {
4079  exint transformi = row;
4080  if (cross_sections_per_vertex != 1)
4081  fix_transformi_for_all_case(transformi);
4082  return &transforms->getInverse3s<T>()[transformi];
4083  };
4084  auto &&transform_and_copy = [](
4085  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4086  const UT_Matrix3T<T> *transform,
4087  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4088  {
4089  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4090  value.colVecMult(*transform);
4091  outputh.set(output_offset, value);
4092  };
4093  auto &&transform_and_interp = [](
4094  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4095  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4096  const UT_Matrix3T<T> *transform,
4097  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4098  {
4099  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4100  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4101 
4102  // FIXME: slerp, instead of lerping and normalizing!!!
4103  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4104  value.normalize();
4105 
4106  T orig_length2 = value.length2();
4107  value.colVecMult(*transform);
4108  T new_length2 = value.length2();
4109  // Preserve normal length
4110  if (new_length2 != 0)
4111  value *= SYSsqrt(orig_length2/new_length2);
4112  outputh.set(output_offset, value);
4113  };
4114  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4115  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4116  reverse_cross_sections, swap_row_col,
4117  get_transform, transform_and_copy, transform_and_interp);
4118  return;
4119  }
4120  if (transform_type == GA_TYPE_VECTOR)
4121  {
4122  // Transform vector attribute
4123  UT_ASSERT(tuple_size == 3);
4124  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4125  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4126  {
4127  exint transformi = row;
4128  if (cross_sections_per_vertex != 1)
4129  fix_transformi_for_all_case(transformi);
4130  return &transforms->getMatrix3s<T>()[transformi];
4131  };
4132  auto &&transform_and_copy = [](
4133  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4134  const UT_Matrix3T<T> *transform,
4135  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4136  {
4137  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4138  value *= *transform;
4139  outputh.set(output_offset, value);
4140  };
4141  auto &&transform_and_interp = [](
4142  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4143  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4144  const UT_Matrix3T<T> *transform,
4145  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4146  {
4147  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4148  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4149  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4150  value *= *transform;
4151  outputh.set(output_offset, value);
4152  };
4153  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4154  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4155  reverse_cross_sections, swap_row_col,
4156  get_transform, transform_and_copy, transform_and_interp);
4157  return;
4158  }
4159  if (transform_type == GA_TYPE_QUATERNION)
4160  {
4161  // Transform vector attribute
4162  UT_ASSERT(tuple_size == 4);
4163  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4164  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_QuaternionT<T>*
4165  {
4166  exint transformi = row;
4167  if (cross_sections_per_vertex != 1)
4168  fix_transformi_for_all_case(transformi);
4169  return &transforms->getQuaternions<T>()[transformi];
4170  };
4171  auto &&transform_and_copy = [](
4172  const GA_ROHandleT<UT_QuaternionT<T>> &cross_sectionh, GA_Offset cross_section_offset,
4174  const GA_RWHandleT<UT_QuaternionT<T>> &outputh, GA_Offset output_offset)
4175  {
4176  UT_QuaternionT<T> value = cross_sectionh.get(cross_section_offset);
4177  value *= *transform;
4178  outputh.set(output_offset, value);
4179  };
4180  auto &&transform_and_interp = [](
4181  const GA_ROHandleT<UT_QuaternionT<T>> &cross_sectionh,
4182  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4184  const GA_RWHandleT<UT_QuaternionT<T>> &outputh, GA_Offset output_offset)
4185  {
4186  const UT_QuaternionT<T> value0 = cross_sectionh.get(cross_section_offset0);
4187  const UT_QuaternionT<T> value1 = cross_sectionh.get(cross_section_offset1);
4188  UT_QuaternionT<T> value = value0.interpolate(value1, t);
4189  value = *transform * value;
4190  outputh.set(output_offset, value);
4191  };
4192  copyCrossSectionAttributeWrapper<UT_QuaternionT<T>, UT_QuaternionT<T>, attrib_owner>(grid,
4193  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4194  reverse_cross_sections, swap_row_col,
4195  get_transform, transform_and_copy, transform_and_interp);
4196  return;
4197  }
4198  if (transform_type == GA_TYPE_TRANSFORM)
4199  {
4200  if (tuple_size == 9)
4201  {
4202  // Transform 3x3 matrix attribute
4203  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4204  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4205  {
4206  exint transformi = row;
4207  if (cross_sections_per_vertex != 1)
4208  fix_transformi_for_all_case(transformi);
4209  return &transforms->getMatrix3s<T>()[transformi];
4210  };
4211  auto &&transform_and_copy = [](
4212  const GA_ROHandleT<UT_Matrix3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4213  const UT_Matrix3T<T> *transform,
4214  const GA_RWHandleT<UT_Matrix3T<T>> &outputh, GA_Offset output_offset)
4215  {
4216  UT_Matrix3T<T> value = cross_sectionh.get(cross_section_offset);
4217  value *= *transform;
4218  outputh.set(output_offset, value);
4219  };
4220  auto &&transform_and_interp = [](
4221  const GA_ROHandleT<UT_Matrix3T<T>> &cross_sectionh,
4222  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4223  const UT_Matrix3T<T> *transform,
4224  const GA_RWHandleT<UT_Matrix3T<T>> &outputh, GA_Offset output_offset)
4225  {
4226  const UT_Matrix3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4227  const UT_Matrix3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4228  UT_Matrix3T<T> value = SYSlerp(value0, value1, t);
4229  value *= *transform;
4230  outputh.set(output_offset, value);
4231  };
4232  copyCrossSectionAttributeWrapper<UT_Matrix3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4233  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4234  reverse_cross_sections, swap_row_col,
4235  get_transform, transform_and_copy, transform_and_interp);
4236  return;
4237  }
4238  if (tuple_size == 16)
4239  {
4240  // Transform 4x4 matrix attribute
4242  transform.identity();
4243  auto &&get_transform = [&transform,cross_sections_per_vertex,&fix_transformi_for_all_case]
4244  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix4T<T>*
4245  {
4246  exint transformi = row;
4247  if (cross_sections_per_vertex != 1)
4248  fix_transformi_for_all_case(transformi);
4249  transform = UT_Matrix4T<T>(transforms->getMatrix3s<T>()[transformi]);
4250  transform.setTranslates(transforms->getTranslates<T>()[transformi]);
4251  return &transform;
4252  };
4253  auto &&transform_and_copy = [](
4254  const GA_ROHandleT<UT_Matrix4T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4255  const UT_Matrix4T<T> *transform,
4256  const GA_RWHandleT<UT_Matrix4T<T>> &outputh, GA_Offset output_offset)
4257  {
4258  UT_Matrix4T<T> value = cross_sectionh.get(cross_section_offset);
4259  value *= *transform;
4260  outputh.set(output_offset, value);
4261  };
4262  auto &&transform_and_interp = [](
4263  const GA_ROHandleT<UT_Matrix4T<T>> &cross_sectionh,
4264  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4265  const UT_Matrix4T<T> *transform,
4266  const GA_RWHandleT<UT_Matrix4T<T>> &outputh, GA_Offset output_offset)
4267  {
4268  const UT_Matrix4T<T> value0 = cross_sectionh.get(cross_section_offset0);
4269  const UT_Matrix4T<T> value1 = cross_sectionh.get(cross_section_offset1);
4270  UT_Matrix4T<T> value = SYSlerp(value0, value1, t);
4271  value *= *transform;
4272  outputh.set(output_offset, value);
4273  };
4274  copyCrossSectionAttributeWrapper<UT_Matrix4T<T>, UT_Matrix4T<T>, attrib_owner>(grid,
4275  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4276  reverse_cross_sections, swap_row_col,
4277  get_transform, transform_and_copy, transform_and_interp);
4278  return;
4279  }
4280  }
4281  }
4282 
4283  // Non-transforming cases follow
4284  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
4285  {
4286  return nullptr;
4287  };
4288 
4289  bool is_integer_type = std::is_integral<T>::value;
4290  if (tuple_size == 1)
4291  {
4292  // Copy single-component numeric attribute
4293  auto &&transform_and_copy = [](
4294  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
4295  const void *transform,
4296  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4297  {
4298  T value = cross_sectionh.get(cross_section_offset);
4299  outputh.set(output_offset, value);
4300  };
4301  if (is_integer_type)
4302  {
4303  auto &&transform_and_interp = [](
4304  const GA_ROHandleT<T> &cross_sectionh,
4305  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4306  const void *transform,
4307  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4308  {
4309  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4310  T value = cross_sectionh.get(cross_section_offset);
4311  outputh.set(output_offset, value);
4312  };
4313  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4314  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4315  reverse_cross_sections, swap_row_col,
4316  get_transform, transform_and_copy, transform_and_interp);
4317  }
4318  else
4319  {
4320  auto &&transform_and_interp = [](
4321  const GA_ROHandleT<T> &cross_sectionh,
4322  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4323  const void *transform,
4324  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4325  {
4326  const T value0 = cross_sectionh.get(cross_section_offset0);
4327  const T value1 = cross_sectionh.get(cross_section_offset1);
4328  T value = SYSlerp(value0, value1, t);
4329  outputh.set(output_offset, value);
4330  };
4331  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4332  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4333  reverse_cross_sections, swap_row_col,
4334  get_transform, transform_and_copy, transform_and_interp);
4335  }
4336  return;
4337  }
4338  if (tuple_size == 2 && !is_integer_type)
4339  {
4340  // Copy 2-component numeric attribute
4341  auto &&transform_and_copy = [](
4342  const GA_ROHandleT<UT_Vector2T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4343  const void *transform,
4344  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
4345  {
4346  UT_Vector2T<T> value = cross_sectionh.get(cross_section_offset);
4347  outputh.set(output_offset, value);
4348  };
4349  auto &&transform_and_interp = [](
4350  const GA_ROHandleT<UT_Vector2T<T>> &cross_sectionh,
4351  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4352  const void *transform,
4353  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
4354  {
4355  const UT_Vector2T<T> value0 = cross_sectionh.get(cross_section_offset0);
4356  const UT_Vector2T<T> value1 = cross_sectionh.get(cross_section_offset1);
4357  UT_Vector2T<T> value = SYSlerp(value0, value1, t);
4358  outputh.set(output_offset, value);
4359  };
4360  copyCrossSectionAttributeWrapper<UT_Vector2T<T>, void, attrib_owner>(grid,
4361  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4362  reverse_cross_sections, swap_row_col,
4363  get_transform, transform_and_copy, transform_and_interp);
4364  return;
4365  }
4366  if (tuple_size == 3 && !is_integer_type)
4367  {
4368  // Copy 3-component numeric attribute
4369  auto &&transform_and_copy = [](
4370  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4371  const void *transform,
4372  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4373  {
4374  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4375  outputh.set(output_offset, value);
4376  };
4377  auto &&transform_and_interp = [](
4378  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4379  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4380  const void *transform,
4381  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4382  {
4383  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4384  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4385  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4386  outputh.set(output_offset, value);
4387  };
4388  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, void, attrib_owner>(grid,
4389  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4390  reverse_cross_sections, swap_row_col,
4391  get_transform, transform_and_copy, transform_and_interp);
4392  return;
4393  }
4394 
4395  // Copy arbitrary-component numeric attribute
4396  auto &&transform_and_copy = [tuple_size](
4397  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
4398  const void *transform,
4399  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4400  {
4401  for (exint component = 0; component < tuple_size; ++component)
4402  {
4403  T value = cross_sectionh.get(cross_section_offset, component);
4404  outputh.set(output_offset, component, value);
4405  }
4406  };
4407  if (is_integer_type)
4408  {
4409  auto &&transform_and_interp = [tuple_size](
4410  const GA_ROHandleT<T> &cross_sectionh,
4411  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4412  const void *transform,
4413  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4414  {
4415  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4416  for (exint component = 0; component < tuple_size; ++component)
4417  {
4418  T value = cross_sectionh.get(cross_section_offset, component);
4419  outputh.set(output_offset, component, value);
4420  }
4421  };
4422  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4423  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4424  reverse_cross_sections, swap_row_col,
4425  get_transform, transform_and_copy, transform_and_interp);
4426  }
4427  else
4428  {
4429  auto &&transform_and_interp = [tuple_size](
4430  const GA_ROHandleT<T> &cross_sectionh,
4431  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4432  const void *transform,
4433  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4434  {
4435  for (exint component = 0; component < tuple_size; ++component)
4436  {
4437  const T value0 = cross_sectionh.get(cross_section_offset0, component);
4438  const T value1 = cross_sectionh.get(cross_section_offset1, component);
4439  T value = SYSlerp(value0, value1, t);
4440  outputh.set(output_offset, component, value);
4441  }
4442  };
4443  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4444  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4445  reverse_cross_sections, swap_row_col,
4446  get_transform, transform_and_copy, transform_and_interp);
4447  }
4448 }
4449 
4450 template<GA_AttributeOwner attrib_owner>
4451 static void
4452 copyAttribSingleGrid(
4453  GA_Attribute *output_attrib,
4454  const GA_Attribute *cross_section_attrib,
4455  const GEO_Detail *cross_section_input,
4456  const sop_SweepGrid &grid_info,
4457  const GEO_SurfaceType surface_type,
4458  const bool output_points_only,
4459  const bool triangular_poles,
4460  const bool reverse_cross_sections,
4461  const bool swap_row_col)
4462 {
4463  // Copy non-numeric attribute
4464 
4465  GU_GridT<GA_Offset> grid;
4466  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
4467 
4468  // Non-transforming
4469  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
4470  {
4471  return nullptr;
4472  };
4473 
4474  auto &&transform_and_copy = [](
4475  const GA_Attribute *cross_section_attrib, GA_Offset cross_section_offset,
4476  const void *transform,
4477  GA_Attribute *output_attrib, GA_Offset output_offset)
4478  {
4479  output_attrib->copy(output_offset, *cross_section_attrib, cross_section_offset);
4480  };
4481  auto &&transform_and_interp = [](
4482  const GA_Attribute *cross_section_attrib,
4483  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4484  const void *transform,
4485  GA_Attribute *output_attrib, GA_Offset output_offset)
4486  {
4487  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4488  output_attrib->copy(output_offset, *cross_section_attrib, cross_section_offset);
4489  };
4490 
4491  UT_ASSERT(attrib_owner == cross_section_attrib->getOwner());
4492  copyCrossSectionAttributeWrapper2<void, attrib_owner>(grid,
4493  output_attrib, cross_section_attrib, attrib_owner, cross_section_input, nullptr, grid_info,
4494  reverse_cross_sections, swap_row_col,
4495  get_transform, transform_and_copy, transform_and_interp);
4496 }
4497 
4498 static const sop_SweepGridTransformWrapper *
4499 gridOffsetTransforms(
4500  const SOP_SweepHDKCache *const transform_cache,
4501  sop_SweepGridTransformWrapper &grid_transforms,
4502  const exint gridi)
4503 {
4504  if (transform_cache == nullptr)
4505  return nullptr;
4506 
4507  grid_transforms.init(*transform_cache, gridi);
4508 
4509  return &grid_transforms;
4510 }
4511 
4512 template<GA_AttributeOwner attrib_owner>
4513 static void
4514 copyCrossSectionAttrib2(
4515  GA_Attribute *output_attrib,
4516  const GA_Attribute *cross_section_attrib,
4517  const GEO_Detail *cross_section_input,
4518  const SOP_SweepHDKCache *transform_cache,
4519  const sop_SweepGrid *grids,
4520  exint ngrids,
4521  const exint cross_sections_per_vertex,
4522  const exint cap_divisions,
4523  const GEO_SurfaceType surface_type,
4524  const bool output_points_only,
4525  const bool triangular_poles,
4526  const bool reverse_cross_sections,
4527  const bool swap_row_col)
4528 {
4529  UT_ASSERT(output_attrib->getOwner() == attrib_owner);
4530  UT_ASSERT(cross_section_attrib->getOwner() == attrib_owner);
4531 
4532  const exint PARALLEL_THRESHOLD = 2048;
4533  const bool parallel = (ngrids > 1 &&
4534  output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD);
4535  const UT_BlockedRange<exint> grid_range(0, ngrids);
4536 
4537  GA_ATINumeric *const output_numeric = GA_ATINumeric::cast(output_attrib);
4538  if (!output_numeric)
4539  {
4540  output_attrib->bumpDataId();
4541  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4542  {
4543  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4544  {
4545  copyAttribSingleGrid<attrib_owner>(
4546  output_attrib, cross_section_attrib, cross_section_input,
4547  grids[gridi],
4548  surface_type, output_points_only, triangular_poles,
4549  reverse_cross_sections, swap_row_col);
4550  }
4551  };
4552  if (parallel)
4553  {
4554  // Must harden all pages before multi-threading.
4555  output_attrib->hardenAllPages();
4556  UTparallelFor(grid_range, functor);
4557  }
4558  else
4559  {
4560  functor(grid_range);
4561  }
4562  return;
4563  }
4564 
4565  const GA_ATINumeric *const cross_section_numeric = GA_ATINumeric::cast(cross_section_attrib);
4566  UT_ASSERT(cross_section_numeric);
4567  if (!cross_section_numeric)
4568  return;
4569  const int tuple_size = output_numeric->getTupleSize();
4570  UT_ASSERT(tuple_size == cross_section_numeric->getTupleSize());
4571  if (tuple_size != cross_section_numeric->getTupleSize())
4572  return;
4573 
4574  output_attrib->bumpDataId();
4575 
4576  if (parallel)
4577  {
4578  // Must harden all pages before multi-threading.
4579  output_attrib->hardenAllPages();
4580  }
4581 
4582  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4583  {
4584  const GA_TypeInfo type_info = cross_section_numeric->getTypeInfo();
4585  const GA_Storage storage_type = output_numeric->getStorage();
4586  sop_SweepGridTransformWrapper local_grid_transforms;
4587  if (tuple_size == 3)
4588  {
4589  if (type_info == GA_TYPE_POINT)
4590  {
4591  if (storage_type == GA_STORE_REAL64)
4592  {
4593  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4594  {
4595  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4596  copyFloatSingleGrid<fpreal64,GA_TYPE_POINT,attrib_owner>(
4597  output_numeric, cross_section_numeric, cross_section_input,
4598  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4599  surface_type, output_points_only, triangular_poles,
4600  reverse_cross_sections, swap_row_col);
4601  }
4602  }
4603  else
4604  {
4605  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4606  {
4607  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4608  copyFloatSingleGrid<fpreal32,GA_TYPE_POINT,attrib_owner>(
4609  output_numeric, cross_section_numeric, cross_section_input,
4610  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4611  surface_type, output_points_only, triangular_poles,
4612  reverse_cross_sections, swap_row_col);
4613  }
4614  }
4615  return;
4616  }
4617  if (type_info == GA_TYPE_NORMAL)
4618  {
4619  if (storage_type == GA_STORE_REAL64)
4620  {
4621  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4622  {
4623  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4624  copyFloatSingleGrid<fpreal64,GA_TYPE_NORMAL,attrib_owner>(
4625  output_numeric, cross_section_numeric, cross_section_input,
4626  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4627  surface_type, output_points_only, triangular_poles,
4628  reverse_cross_sections, swap_row_col);
4629  }
4630  }
4631  else
4632  {
4633  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4634  {
4635  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4636  copyFloatSingleGrid<fpreal32,GA_TYPE_NORMAL,attrib_owner>(
4637  output_numeric, cross_section_numeric, cross_section_input,
4638  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4639  surface_type, output_points_only, triangular_poles,
4640  reverse_cross_sections, swap_row_col);
4641  }
4642  }
4643  return;
4644  }
4645  if (type_info == GA_TYPE_VECTOR)
4646  {
4647  if (storage_type == GA_STORE_REAL64)
4648  {
4649  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4650  {
4651  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4652  copyFloatSingleGrid<fpreal64,GA_TYPE_VECTOR,attrib_owner>(
4653  output_numeric, cross_section_numeric, cross_section_input,
4654  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4655  surface_type, output_points_only, triangular_poles,
4656  reverse_cross_sections, swap_row_col);
4657  }
4658  }
4659  else
4660  {
4661  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4662  {
4663  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4664  copyFloatSingleGrid<fpreal32,GA_TYPE_VECTOR,attrib_owner>(
4665  output_numeric, cross_section_numeric, cross_section_input,
4666  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4667  surface_type, output_points_only, triangular_poles,
4668  reverse_cross_sections, swap_row_col);
4669  }
4670  }
4671  return;
4672  }
4673  }
4674  else if (tuple_size == 4)
4675  {
4676  if (type_info == GA_TYPE_QUATERNION)
4677  {
4678  if (storage_type == GA_STORE_REAL64)
4679  {
4680  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4681  {
4682  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4683  copyFloatSingleGrid<fpreal64,GA_TYPE_QUATERNION,attrib_owner>(
4684  output_numeric, cross_section_numeric, cross_section_input,
4685  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4686  surface_type, output_points_only, triangular_poles,
4687  reverse_cross_sections, swap_row_col);
4688  }
4689  }
4690  else
4691  {
4692  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4693  {
4694  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4695  copyFloatSingleGrid<fpreal32,GA_TYPE_QUATERNION,attrib_owner>(
4696  output_numeric, cross_section_numeric, cross_section_input,
4697  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4698  surface_type, output_points_only, triangular_poles,
4699  reverse_cross_sections, swap_row_col);
4700  }
4701  }
4702  return;
4703  }
4704  }
4705  else if (tuple_size == 9 || tuple_size == 16)
4706  {
4707  if (type_info == GA_TYPE_TRANSFORM)
4708  {
4709  if (storage_type == GA_STORE_REAL64)
4710  {
4711  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4712  {
4713  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4714  copyFloatSingleGrid<fpreal64,GA_TYPE_TRANSFORM,attrib_owner>(
4715  output_numeric, cross_section_numeric, cross_section_input,
4716  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4717  surface_type, output_points_only, triangular_poles,
4718  reverse_cross_sections, swap_row_col);
4719  }
4720  }
4721  else
4722  {
4723  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4724  {
4725  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4726  copyFloatSingleGrid<fpreal32,GA_TYPE_TRANSFORM,attrib_owner>(
4727  output_numeric, cross_section_numeric, cross_section_input,
4728  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4729  surface_type, output_points_only, triangular_poles,
4730  reverse_cross_sections, swap_row_col);
4731  }
4732  }
4733  return;
4734  }
4735  }
4736 
4737  if (storage_type == GA_STORE_REAL64)
4738  {
4739  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4740  {
4741  copyFloatSingleGrid<fpreal64,GA_TYPE_VOID,attrib_owner>(
4742  output_numeric, cross_section_numeric, cross_section_input,
4743  nullptr, grids[gridi], cross_sections_per_vertex, cap_divisions,
4744  surface_type, output_points_only, triangular_poles,
4745  reverse_cross_sections, swap_row_col);
4746  }
4747  }
4748  else if (storage_type == GA_STORE_REAL32 || storage_type == GA_STORE_REAL16)
4749  {
4750  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4751  {
4752  copyFloatSingleGrid<fpreal32,GA_TYPE_VOID,attrib_owner>(
4753  output_numeric, cross_section_numeric, cross_section_input,
4754  nullptr, grids[gridi], cross_sections_per_vertex, cap_divisions,
4755  surface_type, output_points_only, triangular_poles,
4756  reverse_cross_sections, swap_row_col);
4757  }
4758  }
4759  else if (storage_type == GA_STORE_INT64)
4760  {
4761  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4762  {
4763  copyIntegerSingleGrid<int64,attrib_owner>(
4764  output_numeric, cross_section_numeric, cross_section_input,
4765  nullptr, grids[gridi],
4766  surface_type, output_points_only, triangular_poles,
4767  reverse_cross_sections, swap_row_col);
4768  }
4769  }
4770  else if (storage_type == GA_STORE_INT32 || storage_type == GA_STORE_INT16 || storage_type == GA_STORE_INT8)
4771  {
4772  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4773  {
4774  copyIntegerSingleGrid<int32,attrib_owner>(
4775  output_numeric, cross_section_numeric, cross_section_input,
4776  nullptr, grids[gridi],
4777  surface_type, output_points_only, triangular_poles,
4778  reverse_cross_sections, swap_row_col);
4779  }
4780  }
4781  };
4782 
4783  if (parallel)
4784  UTparallelFor(grid_range, functor);
4785  else
4786  functor(grid_range);
4787 }
4788 
4789 static void
4790 copyCrossSectionAttrib(
4791  GA_Attribute *output_attrib,
4792  const GA_Attribute *cross_section_attrib,
4793  const GEO_Detail *cross_section_input,
4794  const SOP_SweepHDKCache *transform_cache,
4795  const sop_SweepGrid *grids,
4796  exint ngrids,
4797  const exint cross_sections_per_vertex,
4798  const exint cap_divisions,
4799  const GEO_SurfaceType surface_type,
4800  const bool output_points_only,
4801  const bool triangular_poles,
4802  const bool reverse_cross_sections,
4803  const bool swap_row_col)
4804 {
4805  UT_ASSERT(output_attrib->getOwner() == cross_section_attrib->getOwner());
4806 
4807  GA_AttributeOwner owner = output_attrib->getOwner();
4808  if (owner == GA_ATTRIB_POINT)
4809  {
4810  copyCrossSectionAttrib2<GA_ATTRIB_POINT>(
4811  output_attrib, cross_section_attrib, cross_section_input,
4812  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4813  surface_type, output_points_only, triangular_poles,
4814  reverse_cross_sections, swap_row_col);
4815  }
4816  else if (owner == GA_ATTRIB_VERTEX)
4817  {
4818  copyCrossSectionAttrib2<GA_ATTRIB_VERTEX>(
4819  output_attrib, cross_section_attrib, cross_section_input,
4820  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4821  surface_type, output_points_only, triangular_poles,
4822  reverse_cross_sections, swap_row_col);
4823  }
4824  else if (owner == GA_ATTRIB_PRIMITIVE)
4825  {
4826  copyCrossSectionAttrib2<GA_ATTRIB_PRIMITIVE>(
4827  output_attrib, cross_section_attrib, cross_section_input,
4828  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4829  surface_type, output_points_only, triangular_poles,
4830  reverse_cross_sections, swap_row_col);
4831  }
4832  else if (owner == GA_ATTRIB_DETAIL)
4833  {
4834  output_attrib->replace(*cross_section_attrib);
4835  }
4836 }
4837 
4838 template<bool wrap_to_invalid,typename FUNCTOR>
4839 static void
4840 copyVertexCurveAttribWrapper(
4841  const sop_SweepGrid &grid_info,
4842  const GU_GridT<GA_Offset> &grid,
4843  exint num_vertices,
4844  exint cap_divisions,
4845  exint cross_sections_per_vertex,
4846  const bool swap_row_col,
4847  const FUNCTOR &copy_functor)
4848 {
4849  const exint curve_nedges = grid_info.myCurveNEdges;
4850  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
4851 
4852  const exint cap_divs = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
4853  exint prev_vtxi = -1;
4854  exint curve_vtxi;
4855  exint cap_vertex_count = 0;
4856  if (grid_info.myHasPolygonCaps)
4857  {
4858  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
4859  {
4860  // First row cap
4861  cap_vertex_count = cross_section_nedges;
4862 
4863  curve_vtxi = 0;
4864  prev_vtxi = 0;
4865 
4866  for (exint col = 0; col < cap_vertex_count; ++col)
4867  {
4868  copy_functor(grid_info.myStartVtxOff + col, 0);
4869  }
4870  }
4871  else
4872  {
4873  // First side col cap
4874  cap_vertex_count = curve_nedges;
4875 
4876  for (exint row = 0; row < cap_vertex_count; ++row)
4877  {
4878  copy_functor(grid_info.myStartVtxOff + row, row);
4879  }
4880  }
4881  }
4882  // Copy to main grid vertices
4883  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
4884  const bool closed_curve = grid_info.myCurveClosed;
4885  GUiterateGridVertices(grid, [cap_divs, &prev_vtxi, &curve_vtxi, num_vertices,
4886  start_vtxoff, cross_sections_per_vertex, swap_row_col, closed_curve, &copy_functor]
4887  (exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
4888  {
4889  exint vtxi = swap_row_col ? col : row;
4890  vtxi += exint(swap_row_col ? iscolend : isrowend);
4891  if (vtxi != prev_vtxi)
4892  {
4893  curve_vtxi = vtxi - cap_divs;
4894  if (cross_sections_per_vertex != 1)
4895  curve_vtxi /= cross_sections_per_vertex;
4896  if (curve_vtxi < 0)
4897  curve_vtxi = 0;
4898  else if (curve_vtxi >= num_vertices)
4899  {
4900  if (!closed_curve)
4901  curve_vtxi = num_vertices-1;
4902  else if (wrap_to_invalid)
4903  curve_vtxi = -1;
4904  else
4905  curve_vtxi = 0;
4906  }
4907  prev_vtxi = vtxi;
4908  }
4909  copy_functor(start_vtxoff + vtxnum, curve_vtxi);
4910  });
4911  if (grid_info.myHasPolygonCaps)
4912  {
4913  // Last cap starts after main grid
4914  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
4915  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
4916  {
4917  // Last row cap
4918  curve_vtxi = num_vertices-1;
4919 
4920  for (exint col = 0; col < cap_vertex_count; ++col)
4921  {
4922  copy_functor(cap_start_vtxoff + col, curve_vtxi);
4923  }
4924  }
4925  else
4926  {
4927  // Last side col cap
4928  for (exint row = 0; row < cap_vertex_count; ++row)
4929  {
4930  copy_functor(cap_start_vtxoff + row, row);
4931  }
4932  }
4933  }
4934 }
4935 
4936 template<GA_AttributeOwner attrib_owner,bool wrap_to_invalid=false,typename COPY_FUNCTOR>
4937 static void
4938 copyCurveAttrib2(
4939  GA_Attribute *output_attrib,
4940  const GEO_Detail *curve_input,
4941  const sop_SweepGrid *grids,
4942  exint ngrids,
4943  const exint cross_sections_per_vertex,
4944  const GEO_SurfaceType surface_type,
4945  const bool output_points_only,
4946  const bool triangular_poles,
4947  const exint cap_divisions,
4948  const bool swap_row_col,
4949  const COPY_FUNCTOR &copy_functor)
4950 {
4951  UT_ASSERT(output_attrib->getOwner() == attrib_owner);
4952  SYS_STATIC_ASSERT(attrib_owner == GA_ATTRIB_POINT || attrib_owner == GA_ATTRIB_VERTEX);
4953 
4954  output_attrib->bumpDataId();
4955 
4956  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4957  {
4958  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4959  {
4960  const sop_SweepGrid &grid_info = grids[gridi];
4961  GU_GridT<GA_Offset> grid;
4962  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
4963 
4964  const GA_OffsetListRef vertices = curve_input->getPrimitiveVertexList(grid_info.myCurvePrimOff);
4965  if (attrib_owner == GA_ATTRIB_VERTEX)
4966  {
4967  copyVertexCurveAttribWrapper<wrap_to_invalid>(grid_info, grid, vertices.size(), cap_divisions, cross_sections_per_vertex, swap_row_col,
4968  [&copy_functor,&vertices](GA_Offset output_vtxoff, exint curve_vtxi)
4969  {
4970  GA_Offset curve_vtxoff = (wrap_to_invalid && curve_vtxi < 0) ? GA_INVALID_OFFSET : vertices(curve_vtxi);
4971  copy_functor(output_vtxoff, curve_vtxoff);
4972  });
4973  }
4974  else
4975  {
4976  UT_ASSERT(attrib_owner == GA_ATTRIB_POINT);
4977  const exint cap_divs = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
4978  exint prev_vtxi = -1;
4979  GA_Offset curve_ptoff;
4980  GUiterateGridPoints(grid, [cap_divs, &vertices, &prev_vtxi,
4981  &curve_ptoff, curve_input, cross_sections_per_vertex, swap_row_col, &copy_functor]
4982  (GA_Offset output_ptoff, exint row, exint col)
4983  {
4984  exint vtxi = swap_row_col ? col : row;
4985  if (vtxi != prev_vtxi)
4986  {
4987  exint curve_vtxi = vtxi - cap_divs;
4988  if (cross_sections_per_vertex != 1)
4989  curve_vtxi /= cross_sections_per_vertex;
4990  if (curve_vtxi < 0)
4991  curve_vtxi = 0;
4992  else if (curve_vtxi >= vertices.size())
4993  curve_vtxi = vertices.size()-1;
4994  GA_Offset curve_vtxoff = vertices(curve_vtxi);
4995  curve_ptoff = curve_input->vertexPoint(curve_vtxoff);
4996  prev_vtxi = vtxi;
4997  }
4998  copy_functor(output_ptoff, curve_ptoff);
4999  });
5000  }
5001  }
5002  };
5003 
5004  const exint PARALLEL_THRESHOLD = 2048;
5005  if (ngrids > 1 && output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD)
5006  {
5007  // Must harden all pages before multi-threading.
5008  output_attrib->hardenAllPages();
5009 
5010  UTparallelFor(UT_BlockedRange<exint>(0, ngrids), functor);
5011  }
5012  else
5013  {
5014  functor(UT_BlockedRange<exint>(0, ngrids));
5015  }
5016 }
5017 
5018 static void
5019 copyCurveAttrib(
5020  GA_Attribute *output_attrib,
5021  const GA_Attribute *curve_attrib,
5022  const GEO_Detail *curve_input,
5023  const sop_SweepGrid *grids,
5024  exint ngrids,
5025  const exint cross_sections_per_vertex,
5026  const GEO_SurfaceType surface_type,
5027  const bool output_points_only,
5028  const bool triangular_poles,
5029  const exint cap_divisions,
5030  const bool swap_row_col)
5031 {
5032  UT_ASSERT(output_attrib->getOwner() == curve_attrib->getOwner());
5033 
5034  const GA_AttributeOwner owner = output_attrib->getOwner();
5035  if (owner == GA_ATTRIB_POINT)
5036  {
5037  auto &&copy_functor = [output_attrib,curve_attrib](GA_Offset output_off, GA_Offset curve_off)
5038  {
5039  output_attrib->copy(output_off, *curve_attrib, curve_off);
5040  };
5041  copyCurveAttrib2<GA_ATTRIB_POINT>(
5042  output_attrib, curve_input,
5043  grids, ngrids, cross_sections_per_vertex,
5044  surface_type, output_points_only, triangular_poles, cap_divisions,
5045  swap_row_col, copy_functor);
5046  }
5047  else if (owner == GA_ATTRIB_VERTEX)
5048  {
5049  if (!output_points_only)
5050  {
5051  auto &&copy_functor = [output_attrib,curve_attrib](GA_Offset output_off, GA_Offset curve_off)
5052  {
5053  output_attrib->copy(output_off, *curve_attrib, curve_off);
5054  };
5055  copyCurveAttrib2<GA_ATTRIB_VERTEX>(
5056  output_attrib, curve_input,
5057  grids, ngrids, cross_sections_per_vertex,
5058  surface_type, output_points_only, triangular_poles, cap_divisions,
5059  swap_row_col, copy_functor);
5060  }
5061  }
5062  else if (owner == GA_ATTRIB_PRIMITIVE)
5063  {
5064  if (!output_points_only)
5065  {
5066  UT_ASSERT(curve_attrib->getOwner() == GA_ATTRIB_PRIMITIVE);
5067 
5068  output_attrib->bumpDataId();
5069 
5070  auto&& functor = [&](const UT_BlockedRange<exint>& r)
5071  {
5072  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
5073  {
5074  const sop_SweepGrid &grid_info = grids[gridi];
5075  GU_GridT<GA_Offset> grid;
5076  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
5077 
5078  // Copy same value to all primitives in grid
5079  const GA_Offset startprimoff = grid_info.myStartPrimOff;
5080  const GA_Offset endprimoff = startprimoff + grid.myNumPrimitives + (grid_info.myHasPolygonCaps ? 2 : 0);
5081  output_attrib->fill(GA_Range(output_attrib->getIndexMap(), startprimoff, endprimoff), *curve_attrib, grid_info.myCurvePrimOff);
5082  }
5083  };
5084 
5085  const exint PARALLEL_THRESHOLD = 2048;
5086  if (ngrids > 1 && output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD)
5087  {
5088  // Must harden all pages before multi-threading.
5089  output_attrib->hardenAllPages();
5090 
5091  UTparallelFor(UT_BlockedRange<exint>(0, ngrids), functor);
5092  }
5093  else
5094  {
5095  functor(UT_BlockedRange<exint>(0, ngrids));
5096  }
5097  }
5098  }
5099  else if (owner == GA_ATTRIB_DETAIL)
5100  {
5101  output_attrib->replace(*curve_attrib);
5102  }
5103 }
5104 
5105 enum class UVStyle {
5106  NORMALIZED,
5107  ROUNDED,
5108  FULL
5109 };
5110 
5111 // This is for copying a single component from the cross section attribute to the grid.
5112 // NOTE: UVStyle should already be folded into uscale by the caller!
5113 template<typename T,GA_AttributeOwner output_owner>
5114 static void
5115 copyCrossSectionUVSingleGrid(
5116  GA_ATINumeric *output_attrib,
5117  const GA_ATINumeric *cross_section_attrib,
5118  const GA_AttributeOwner cross_section_owner,
5119  const GEO_Detail *cross_section_input,
5120  const sop_SweepGrid &grid_info,
5121  const GU_GridT<GA_Offset> &grid,
5122  const bool is_cross_section_uv_computed,
5123  const bool reverse_cross_sections,
5124  const bool swap_row_col,
5125  const bool flipu,
5126  const T uscale,
5127  const UT_Array<double> *missing_input_us = nullptr)
5128 {
5129  UT_ASSERT_P((cross_section_attrib != nullptr && cross_section_input != nullptr && cross_section_owner != GA_ATTRIB_INVALID)
5130  || (cross_section_owner == GA_ATTRIB_INVALID && missing_input_us != nullptr));
5131 
5133 
5134  const int tuple_size = output_attrib->getTupleSize();
5135 
5136  // Non-transforming
5137  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
5138  {
5139  return nullptr;
5140  };
5141 
5142  if (cross_section_owner == GA_ATTRIB_INVALID)
5143  {
5144  // Single cross section with implicit U value already computed.
5145  UT_ASSERT(missing_input_us != nullptr);
5146  UT_ASSERT(output_owner == GA_ATTRIB_VERTEX || output_owner == GA_ATTRIB_POINT);
5147  if (tuple_size == 2)
5148  {
5149  const GA_RWHandleT<UT_Vector2T<T>> outputh(output_attrib);
5150  UT_ASSERT_P(outputh.isValid());
5151  auto &&copy_functor = [missing_input_us,&outputh,flipu,swap_row_col,uscale](
5152  GA_Offset output_offset, exint row, exint col)
5153  {
5154  const exint cross_section_vtxi = swap_row_col ? row : col;
5155  T value = (cross_section_vtxi == missing_input_us->size()) ? T(1.0) : (*missing_input_us)[cross_section_vtxi];
5156  if (flipu)
5157  value = T(1.0)-value;
5158  UT_Vector2T<T> output_value = outputh.get(output_offset);
5159  output_value[theUComponent] = uscale*value;
5160  outputh.set(output_offset, output_value);
5161  };
5162  if (output_owner == GA_ATTRIB_VERTEX)
5163  copyVertexCrossSectionWrapper(grid, grid_info, copy_functor);
5164  else
5165  GUiterateGridPoints(grid, copy_functor);
5166  }
5167  else if (tuple_size == 3)
5168  {
5169  const GA_RWHandleT<UT_Vector3T<T>> outputh(output_attrib);
5170  UT_ASSERT_P(outputh.isValid());
5171  auto &&copy_functor = [missing_input_us,&outputh,flipu,swap_row_col,uscale](
5172  GA_Offset output_offset, exint row, exint col)
5173  {
5174  const exint cross_section_vtxi = swap_row_col ? row : col;
5175  T value = (cross_section_vtxi == missing_input_us->size()) ? T(1.0) : (*missing_input_us)[cross_section_vtxi];
5176  if (flipu)
5177  value = T(1.0)-value;
5178  UT_Vector3T<T> output_value = outputh.get(output_offset);
5179  output_value[theUComponent] = uscale*value;
5180  outputh.set(output_offset, output_value);
5181  };
5182  if (output_owner == GA_ATTRIB_VERTEX)
5183  copyVertexCrossSectionWrapper(grid, grid_info, copy_functor);
5184  else
5185  GUiterateGridPoints(grid, copy_functor);
5186  }
5187  return;
5188  }
5189 
5190  if (tuple_size == 2)
5191  {
5192  // Copy u component of 2-component numeric attribute
5193  auto &&transform_and_copy = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5194  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
5195  const void *transform,
5196  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
5197  {
5198  T value;
5199  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset == GA_INVALID_OFFSET)
5200  value = reverse_cross_sections ? T(0.0) : T(1.0);
5201  else
5202  value = cross_sectionh.get(cross_section_offset);
5203 
5204  if (flipu)
5205  value = T(1.0)-value;
5206  if (is_cross_section_uv_computed)
5207  value *= uscale;
5208  UT_Vector2T<T> output_value = outputh.get(output_offset);
5209  output_value[theUComponent] = value;
5210  outputh.set(output_offset, output_value);
5211  };
5212  auto &&transform_and_interp = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5213  const GA_ROHandleT<T> &cross_sectionh,
5214  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
5215  const void *transform,
5216  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
5217  {
5218  UT_ASSERT_P(GAisValid(cross_section_offset0));
5219  const T value0 = cross_sectionh.get(cross_section_offset0);
5220  // cross_section_offset1 might be invalid here if we have closed, non-unrolled
5221  // cross sections with different numbers of vertices and we're on the last edge.
5222  T value1;
5223  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset1 == GA_INVALID_OFFSET)
5224  value1 = reverse_cross_sections ? T(0.0) : T(1.0);
5225  else
5226  value1 = cross_sectionh.get(cross_section_offset1);
5227 
5228  T value = SYSlerp(value0, value1, t);
5229  if (flipu)
5230  value = T(1.0)-value;
5231  if (is_cross_section_uv_computed)
5232  value *= uscale;
5233  UT_Vector2T<T> output_value = outputh.get(output_offset);
5234  output_value[theUComponent] = value;
5235  outputh.set(output_offset, output_value);
5236  };
5237  GA_ROHandleT<T> cross_sectionh(cross_section_attrib);
5238  GA_RWHandleT<UT_Vector2T<T>> outputh(output_attrib);
5239  // true indicates to specify GA_INVALID_OFFSET, instead of the first vertex,
5240  // when wrapping, so that 1.0 can be written.
5241  constexpr bool wrap_to_invalid = output_owner == GA_ATTRIB_VERTEX;
5242  copyCrossSectionAttributeWrapper2<void, output_owner, wrap_to_invalid>(grid,
5243  outputh, cross_sectionh, cross_section_owner, cross_section_input, nullptr, grid_info,
5244  reverse_cross_sections, swap_row_col,
5245  get_transform, transform_and_copy, transform_and_interp);
5246  return;
5247  }
5248  if (tuple_size == 3)
5249  {
5250  // Copy u component of 3-component numeric attribute
5251  auto &&transform_and_copy = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5252  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
5253  const void *transform,
5254  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
5255  {
5256  T value;
5257  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset == GA_INVALID_OFFSET)
5258  value = reverse_cross_sections ? T(0.0) : T(1.0);
5259  else
5260  value = cross_sectionh.get(cross_section_offset);
5261 
5262  if (flipu)
5263  value = T(1.0)-value;
5264  if (is_cross_section_uv_computed)
5265  value *= uscale;
5266  UT_Vector3T<T> output_value = outputh.get(output_offset);
5267  output_value[theUComponent] = value;
5268  outputh.set(output_offset, output_value);
5269  };
5270  auto &&transform_and_interp = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5271  const GA_ROHandleT<T> &cross_sectionh,
5272  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
5273  const void *transform,
5274  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
5275  {
5276  UT_ASSERT_P(GAisValid(cross_section_offset0));
5277  const T value0 = cross_sectionh.get(cross_section_offset0);
5278  // cross_section_offset1 might be invalid here if we have closed, non-unrolled
5279  // cross sections with different numbers of vertices and we're on the last edge.
5280  T value1;
5281  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset1 == GA_INVALID_OFFSET)
5282  value1 = reverse_cross_sections ? T(0.0) : T(1.0);
5283  else
5284  value1 = cross_sectionh.get(cross_section_offset1);
5285 
5286  T value = SYSlerp(value0, value1, t);
5287  if (flipu)
5288  value = T(1.0)-value;
5289  if (is_cross_section_uv_computed)
5290  value *= uscale;
5291  UT_Vector3T<T> output_value = outputh.get(output_offset);
5292  output_value[theUComponent] = value;
5293  outputh.set(output_offset, output_value);
5294  };
5295  GA_ROHandleT<T> cross_sectionh(cross_section_attrib);
5296  GA_RWHandleT<UT_Vector3T<T>> outputh(output_attrib);
5297  // true indicates to specify GA_INVALID_OFFSET, instead of the first vertex,
5298  // when wrapping, so that 1.0 can be written.
5299  constexpr bool wrap_to_invalid = output_owner == GA_ATTRIB_VERTEX;
5300  copyCrossSectionAttributeWrapper2<void, output_owner, wrap_to_invalid>(grid,
5301  outputh, cross_sectionh, cross_section_owner, cross_section_input, nullptr, grid_info,
5302  reverse_cross_sections, swap_row_col,
5303  get_transform, transform_and_copy, transform_and_interp);
5304  return;
5305  }
5306 }
5307 
5308 template<typename T>
5309 static UT_Vector3D crossSectionCenter(
5310  bool swap_row_col,
5311  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5312  const GU_GridT<GA_Offset> &grid,
5313  const exint cross_section_nedges,
5314  const exint curve_row)
5315 {
5316  UT_Vector3D sum(0,0,0);
5317  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_nedges; ++cross_section_vtxi)
5318  {
5319  exint row = curve_row;;
5320  exint col = cross_section_vtxi;
5321  if (swap_row_col)
5322  UTswap(row, col);
5323  const UT_Vector3T<T> curr_pos = pos.get(grid.getPoint(row, col));
5324  sum += curr_pos;
5325  }
5326 
5327  return sum / cross_section_nedges;
5328 }
5329 
5330 // Computes the total "length" of a single cross section in
5331 // the given grid. If dir0 is null, positions are treated as is.
5332 // If dir0 is non-null and dir1 is null, dir0 will be projected
5333 // out of the positions before length is computed.
5334 // If dir0 and dir1 are both non-null, the average of the two projections
5335 // will be computed. Each must be normalized if non-null.
5336 template<typename T>
5337 static double crossSectionLength(
5338  bool swap_row_col,
5339  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5340  const GU_GridT<GA_Offset> &grid,
5341  const exint cross_section_nedges,
5342  const exint curve_row,
5343  const UT_Vector3T<T> *dir0,
5344  const UT_Vector3T<T> *dir1)
5345 {
5346  double cross_section_length = 0;
5347  exint row = curve_row;
5348  exint col = 0;
5349  if (swap_row_col)
5350  UTswap(row, col);
5351  UT_Vector3T<T> prevpos = pos.get(grid.getPoint(row, col));
5352  UT_Vector3T<T> prevpos0;
5353  UT_Vector3T<T> prevpos1;
5354  if (dir0 != nullptr)
5355  {
5356  T d = prevpos.dot(*dir0);
5357  prevpos0 = prevpos - d*(*dir0);
5358  if (dir1 != nullptr)
5359  {
5360  d = prevpos.dot(*dir1);
5361  prevpos1 = prevpos - d*(*dir1);
5362  }
5363  }
5364  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_nedges; ++cross_section_vtxi)
5365  {
5366  exint row = curve_row;
5367  exint col = cross_section_vtxi+1;
5368  if (swap_row_col)
5369  UTswap(row, col);
5370  const UT_Vector3T<T> nextpos = pos.get(grid.getPoint(row, col));
5371  UT_Vector3T<T> nextpos0;
5372  UT_Vector3T<T> nextpos1;
5373  if (dir0 != nullptr)
5374  {
5375  T d = nextpos.dot(*dir0);
5376  nextpos0 = nextpos - d*(*dir0);
5377  T dist0 = prevpos0.distance(nextpos0);
5378  if (dir1 != nullptr)
5379  {
5380  d = nextpos.dot(*dir1);
5381  nextpos1 = nextpos - d*(*dir1);
5382 
5383  cross_section_length += 0.5f*(dist0 + prevpos1.distance(nextpos1));
5384  }
5385  else
5386  {
5387  cross_section_length += dist0;
5388  }
5389  }
5390  else
5391  {
5392  cross_section_length += prevpos.distance(nextpos);
5393  }
5394  prevpos = nextpos;
5395  if (dir0 != nullptr)
5396  {
5397  prevpos0 = nextpos0;
5398  if (dir1 != nullptr)
5399  prevpos1 = nextpos1;
5400  }
5401  }
5402  return cross_section_length;
5403 }
5404 
5405 template<typename T, typename FUNCTOR>
5406 static void iterateCurveEdgeLengths(
5407  const exint curve_nedges,
5408  const bool use_mesh_edge_lengths,
5409 
5410  const exint cross_section_npts,
5411  const bool swap_row_col,
5412  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5413  const GU_GridT<GA_Offset> &grid,
5414 
5415  const sop_SweepGrid &grid_info,
5416  const exint cap_divisions,
5417  const GA_ROHandleT<UT_Vector3T<T>> &curve_pos,
5418  const GEO_Detail *curve_input,
5419  const GA_OffsetListRef &curve_vertices,
5420 
5421  FUNCTOR&& functor)
5422 {
5423  for (exint curve_vtxi = 0; curve_vtxi < curve_nedges; ++curve_vtxi)
5424  {
5425  double length_sum = 0;
5426  exint length_sum_count;
5427  if (use_mesh_edge_lengths)
5428  {
5429  length_sum_count = cross_section_npts;
5430  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_npts; ++cross_section_vtxi)
5431  {
5432  exint col0 = swap_row_col ? curve_vtxi : cross_section_vtxi;
5433  exint col1 = swap_row_col ? curve_vtxi+1 : cross_section_vtxi;
5434  exint row0 = swap_row_col ? cross_section_vtxi : curve_vtxi;
5435  exint row1 = swap_row_col ? cross_section_vtxi : curve_vtxi+1;
5436  const UT_Vector3T<T> p0 = pos.get(grid.getPoint(row0, col0));
5437  const UT_Vector3T<T> p1 = pos.get(grid.getPoint(row1, col1));
5438  length_sum += p0.distance(p1);
5439  }
5440  }
5441  else
5442  {
5443  length_sum_count = 1;
5444  exint orig_curve_vtxi = curve_vtxi;
5445  if (grid_info.myVEndPoles)
5446  {
5447  // Caps all correspond with the curve end positions
5448  orig_curve_vtxi -= cap_divisions;
5449  orig_curve_vtxi = SYSclamp(orig_curve_vtxi, GA_Size(0), curve_vertices.size()-1);
5450  }
5451  const UT_Vector3T<T> p0 = curve_pos.get(curve_input->vertexPoint(curve_vertices(orig_curve_vtxi)));
5452  exint orig_curve_vtxi1 = (curve_vertices.getExtraFlag() && curve_vtxi+1 == curve_nedges) ? 0 : curve_vtxi+1;
5453  if (grid_info.myVEndPoles)
5454  {
5455  // Same shift and clamping as with the previous vertex.
5456  // This can end up with both at the same vertex, so a length of zero for this edge.
5457  orig_curve_vtxi1 -= cap_divisions;
5458  orig_curve_vtxi1 = SYSclamp(orig_curve_vtxi1, GA_Size(0), curve_vertices.size()-1);
5459  }
5460  const UT_Vector3T<T> p1 = curve_pos.get(curve_input->vertexPoint(curve_vertices(orig_curve_vtxi1)));
5461  length_sum = p0.distance(p1);
5462  }
5463 
5464  functor(curve_vtxi, length_sum, length_sum_count);
5465  }
5466 }
5467 
5468 // NOTE: pos is double-precision to match computeGridUVScales
5469 template<GA_AttributeOwner output_owner, typename T>
5470 static void
5471 generateLengthWeightedV(
5472  const sop_SweepGrid &grid_info,
5473  const GU_GridT<GA_Offset> &grid,
5474  const exint cross_sections_per_vertex,
5475  const GA_RWHandleT<T> &outputh,
5476  const GA_ROHandleV3D &pos,
5477  UVStyle ustyle,
5478  UVStyle vstyle,
5479  bool scalevasu_usingmax,
5480  double vscale,
5481  double orig_vscale,
5482  bool swap_row_col,
5483  bool use_mesh_edge_lengths,
5484  const GEO_Detail *curve_input)
5485 {
5486  //const exint nedgerows = grid.myNumEdgeRows;
5487  //const exint nedgecols = grid.myNumEdgeCols;
5488  const exint curve_nedges = grid_info.myCurveNEdges;
5489  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
5490 
5491  GA_OffsetListRef curve_vertices;
5492  // NOTE: curve_pos is double-precision to match computeGridUVScales
5493  GA_ROHandleV3D curve_pos;
5494  exint cap_divisions = 0;
5495  if (curve_input != nullptr)
5496  {
5497  curve_vertices = curve_input->getPrimitiveVertexList(grid_info.myCurvePrimOff);
5498  if (grid_info.myVEndPoles)
5499  {
5500  UT_ASSERT_MSG(!curve_vertices.getExtraFlag(), "Curve should be open, not closed");
5501  exint orig_curve_nedges = curve_vertices.size() - 1;
5502  cap_divisions = (grid_info.myCurveNEdges - orig_curve_nedges)/2;
5503  UT_ASSERT(cap_divisions >= 1);
5504  }
5505  curve_pos.bind(curve_input->getP());
5506  }
5507 
5508  bool norm_u_unnorm_v = (ustyle == UVStyle::NORMALIZED && vstyle != UVStyle::NORMALIZED);
5509  bool special_case_u_division = (!scalevasu_usingmax && norm_u_unnorm_v);
5510  //bool need_max_cross_section_length = (scalevasu_usingmax && norm_u_unnorm_v);
5511 
5512  UT_Vector3D prevPos;
5513  bool prevPosValid = false;
5514  UT_Vector3D currPos;
5515  bool currPosValid = false;
5516  UT_Vector3D nextPos;
5517  UT_Vector3D dir0;
5518  UT_Vector3D dir1;
5519  double length0 = 0;
5520  double length1 = 0;
5521  UT_Vector3D *pdir0 = nullptr;
5522  UT_Vector3D *pdir1 = nullptr;
5523  double prev_cross_section_length = 0;
5524  if (special_case_u_division)
5525  {
5526  if (curve_input != nullptr)
5527  {
5528  UT_ASSERT_P(curve_pos.isValid());
5529  UT_ASSERT(curve_vertices.size() > 0);
5530  if (curve_vertices.size() >= 2)
5531  {
5532  currPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[0]));
5533  nextPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[1]));
5534  currPosValid = true;
5535  if (grid_info.myCurveClosed)
5536  {
5537  // Closed grid in curve direction, possibly open curve with shared point (unrolled)
5538  bool unrolled = !curve_vertices.getExtraFlag();
5539  exint last_vtxi = curve_vertices.size() - (unrolled ? 2 : 1);
5540  prevPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[last_vtxi]));
5541  prevPosValid = true;
5542  }
5543  }
5544  }
5545  else if (curve_nedges >= 1 + (grid_info.myVEndPoles ? 2*cap_divisions : 0))
5546  {
5547  // If there's no curve, we use the cross section centers to
5548  // determine the implicit curve egde directions.
5549  exint start = grid_info.myVEndPoles ? cap_divisions : 0;
5550  currPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start);
5551  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start+1);
5552  currPosValid = true;
5553  if (grid_info.myCurveClosed)
5554  {
5555  prevPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, curve_nedges-1);
5556  prevPosValid = true;
5557  }
5558  }
5559 
5560  if (prevPosValid)
5561  {
5562  dir0 = (currPos - prevPos);
5563  dir1 = (nextPos - currPos);
5564  length0 = dir0.normalize();
5565  length1 = dir1.normalize();
5566  if (length0 != 0 && length1 != 0)
5567  {
5568  pdir0 = &dir0;
5569  pdir1 = &dir1;
5570  }
5571  else if (length0 != 0 || length1 != 0)
5572  pdir0 = (length0 != 0) ? &dir0 : &dir1;
5573  }
5574  else if (currPosValid)
5575  {
5576  dir1 = (nextPos - currPos);
5577  length1 = dir1.normalize();
5578  if (length1 != 0)
5579  pdir0 = &dir1;
5580  }
5581 
5582  prev_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, 0, pdir0, pdir1);
5583  if (currPosValid)
5584  {
5585  currPos = nextPos;
5586  dir0 = dir1;
5587  length0 = length1;
5588  }
5589  }
5590 #if 0
5591  double max_cross_section_length = 0;
5592  if (need_max_cross_section_length)
5593  {
5594  for (exint curve_vtxi = 0; curve_vtxi <= curve_nedges; ++curve_vtxi)
5595  {
5596  double cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, curve_vtxi, dir0, dir1);
5597  max_cross_section_length = SYSmax(cross_section_length, max_cross_section_length);
5598  }
5599  }
5600 #endif
5601 
5602  UT_SmallArray<double> length_sums;
5603  length_sums.setSizeNoInit(curve_nedges+1);
5604  length_sums[0] = 0;
5605  double total_length_sum = 0;
5606  exint curve_npts = curve_nedges + !grid_info.myCurveClosed;
5607  exint cross_section_npts = cross_section_nedges + !grid_info.myCrossSectionClosed;
5608  iterateCurveEdgeLengths(
5609  curve_nedges,
5610  use_mesh_edge_lengths,
5611  cross_section_npts,
5612  swap_row_col,
5613  pos,
5614  grid,
5615  grid_info,
5616  cap_divisions,
5617  curve_pos,
5618  curve_input,
5619  curve_vertices,
5620  [&](exint curve_vtxi, double length_sum, exint length_sum_count)
5621  {
5622  if (special_case_u_division)
5623  {
5624  bool nextPosValid = false;
5625  // FIXME: Double-check these bounds!!!
5626  if (!grid_info.myVEndPoles || (curve_vtxi >= cap_divisions && curve_vtxi < curve_npts-cap_divisions))
5627  {
5628  nextPosValid = true;
5629  if (cross_section_nedges >= 1)
5630  {
5631  exint nextPosVtxi = curve_vtxi+2;
5632  exint threshold = curve_npts;
5633  if (grid_info.myVEndPoles)
5634  {
5635  nextPosVtxi -= cap_divisions;
5636  threshold -= 2*cap_divisions;
5637  }
5638  if (nextPosVtxi >= threshold)
5639  {
5640  if (grid_info.myCurveClosed)
5641  nextPosVtxi -= curve_npts;
5642  else
5643  nextPosValid = false;
5644  }
5645  if (nextPosValid)
5646  {
5647  if (curve_input != nullptr)
5648  {
5649  UT_ASSERT_P(curve_pos.isValid());
5650  exint next_curve_vtxi = nextPosVtxi;
5651  if (cross_sections_per_vertex != 1)
5652  next_curve_vtxi /= cross_sections_per_vertex;
5653  if (next_curve_vtxi < 0)
5654  next_curve_vtxi = 0;
5655  else if (next_curve_vtxi >= curve_vertices.size())
5656  next_curve_vtxi = curve_vertices.size()-1;
5657  GA_Offset curve_vtxoff = curve_vertices[next_curve_vtxi];
5658  GA_Offset curve_ptoff = curve_input->vertexPoint(curve_vtxoff);
5659  nextPos = curve_pos.get(curve_ptoff);
5660  }
5661  else
5662  {
5663  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, nextPosVtxi);
5664  }
5665  }
5666  }
5667  pdir0 = nullptr;
5668  pdir1 = nullptr;
5669  if (nextPosValid)
5670  {
5671  dir1 = (nextPos - currPos);
5672  length1 = dir1.normalize();
5673  if (length0 != 0 && length1 != 0)
5674  {
5675  pdir0 = &dir0;
5676  pdir1 = &dir1;
5677  }
5678  else if (length0 != 0 || length1 != 0)
5679  pdir0 = (length0 != 0) ? &dir0 : &dir1;
5680  }
5681  else if (length0 != 0)
5682  {
5683  pdir0 = &dir0;
5684  }
5685  }
5686  double next_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, curve_vtxi+1, pdir0, pdir1);
5687  if (nextPosValid)
5688  {
5689  currPos = nextPos;
5690  dir0 = dir1;
5691  length0 = length1;
5692  }
5693  // Use adjacent cross section lengths
5694  double curr_cross_section_length_avg = 0.5*(prev_cross_section_length + next_cross_section_length);
5695  if (curr_cross_section_length_avg == 0)
5696  length_sum = 0;
5697  else
5698  {
5699  // Also normalize length_sum to an average at the same time
5700  length_sum /= (length_sum_count*curr_cross_section_length_avg);
5701  }
5702 
5703  prev_cross_section_length = next_cross_section_length;
5704  }
5705  else
5706  {
5707  // Normalize length_sum to an average
5708  length_sum /= length_sum_count;
5709  }
5710 
5711  total_length_sum += length_sum;
5712  length_sums[curve_vtxi+1] = vscale*total_length_sum;
5713  });
5714 
5715  UT_ASSERT_MSG(total_length_sum != 0 || (vstyle != UVStyle::NORMALIZED), "computeGridUVScales should have fallen back to v being uniform");
5716 
5717  if (vstyle == UVStyle::NORMALIZED)
5718  {
5719  // Ensure that the end is exactly 1 (or the original V scale), regardless of any roundoff error
5720  length_sums[curve_nedges] = orig_vscale;
5721  // If any earlier values are greater than 1, make them exactly 1.
5722  for (exint curve_vtxi = curve_nedges-1; curve_vtxi >= 0 && length_sums[curve_vtxi] > orig_vscale; ++curve_vtxi)
5723  {
5724  length_sums[curve_vtxi] = orig_vscale;
5725  }
5726  }
5727  else if (vstyle == UVStyle::ROUNDED)
5728  {
5729  // Ensure that the end is exactly an integer >= 1, regardless of any roundoff error
5730  double &rounded = length_sums[curve_nedges];
5731  rounded = SYSrint(rounded);
5732  if (rounded < 1)
5733  rounded = 1;
5734  length_sums[curve_nedges] = rounded;
5735  // If any earlier values are greater than rounded, make them exactly rounded.
5736  for (exint curve_vtxi = curve_nedges-1; curve_vtxi >= 0 && length_sums[curve_vtxi] > rounded; ++curve_vtxi)
5737  {
5738  length_sums[curve_vtxi] = rounded;
5739  }
5740  }
5741 
5742  // length_sums array should be ready for use, at this point.
5743 
5744 
5745  exint cap_vertex_count = 0;
5746  if (grid_info.myHasPolygonCaps && output_owner == GA_ATTRIB_VERTEX)
5747  {
5748  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
5749  {
5750  // First row cap
5751  cap_vertex_count = cross_section_nedges;
5752  for (exint col = 0; col < cap_vertex_count; ++col)
5753  {
5754  outputh.set(grid_info.myStartVtxOff + col, theVComponent, 0);
5755  }
5756  }
5757  else
5758  {
5759  // First side col cap
5760  cap_vertex_count = curve_nedges;
5761  // TODO: This won't work.
5762  }
5763  }
5764  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
5765  if (output_owner == GA_ATTRIB_VERTEX)
5766  {
5767  // Copy to main grid vertices
5768  GUiterateGridVertices(grid, [&outputh, start_vtxoff, swap_row_col, &length_sums]
5769  (exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
5770  {
5771  exint curve_vtxi = swap_row_col ? col : row;
5772  curve_vtxi += exint(swap_row_col ? iscolend : isrowend);
5773  outputh.set(start_vtxoff + vtxnum, theVComponent, length_sums[curve_vtxi]);
5774  });
5775  }
5776  else
5777  {
5778  // Copy to grid points
5779  const GA_Offset start_ptoff = grid_info.myStartPtOff;
5780  GUiterateGridPoints(grid, [&outputh, start_ptoff, swap_row_col, &length_sums]
5781  (exint ptnum, exint row, exint col)
5782  {
5783  exint curve_vtxi = swap_row_col ? col : row;
5784  outputh.set(start_ptoff + ptnum, theVComponent, length_sums[curve_vtxi]);
5785  });
5786  }
5787  if (grid_info.myHasPolygonCaps && output_owner == GA_ATTRIB_VERTEX)
5788  {
5789  // Last cap starts after main grid
5790  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
5791  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
5792  {
5793  // Last row cap
5794  for (exint col = 0; col < cap_vertex_count; ++col)
5795  {
5796  outputh.set(cap_start_vtxoff + col, theVComponent, length_sums[curve_nedges]);
5797  }
5798  }
5799  else
5800  {
5801  // Last side col cap
5802  // TODO: This won't work.
5803  }
5804  }
5805 }
5806 
5807 template<GA_AttributeOwner output_owner, typename T>
5808 static void
5809 generateUniformV(
5810  const sop_SweepGrid &grid_info,
5811  const GU_GridT<GA_Offset> &grid,
5812  const GA_RWHandleT<T> &outputh,
5813  bool swap_row_col,
5814  const T vscale)
5815 {
5816  const exint curve_nedges = grid_info.myCurveNEdges;
5817  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
5818 
5819  // Uniform V coordinate along grid
5820  exint cap_vertex_count = 0;
5821  if (grid_info.myHasPolygonCaps && output_owner == GA_ATTRIB_VERTEX)
5822  {
5823  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
5824  {
5825  // First row cap
5826  cap_vertex_count = cross_section_nedges;
5827  for (exint col = 0; col < cap_vertex_count; ++col)
5828  {
5829  outputh.set(grid_info.myStartVtxOff + col, theVComponent, 0);
5830  }
5831  }
5832  else
5833  {
5834  // First side col cap
5835  cap_vertex_count = curve_nedges;
5836  // TODO: This won't work.
5837  }
5838  }
5839 
5840  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
5841  const T recip_curve_nedges = (curve_nedges == 0) ? T(0.0) : (vscale/curve_nedges);
5842  if (output_owner == GA_ATTRIB_VERTEX)
5843  {
5844  // Copy to main grid vertices
5845  GUiterateGridVertices(grid, [&outputh, recip_curve_nedges, start_vtxoff, swap_row_col]
5846  (exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
5847  {
5848  exint curve_vtxi = swap_row_col ? col : row;
5849  curve_vtxi += exint(swap_row_col ? iscolend : isrowend);
5850  outputh.set(start_vtxoff + vtxnum, theVComponent, T(curve_vtxi)*recip_curve_nedges);
5851  });
5852  }
5853  else
5854  {
5855  // Copy to grid points
5856  const GA_Offset start_ptoff = grid_info.myStartPtOff;
5857  GUiterateGridPoints(grid, [&outputh, recip_curve_nedges, start_ptoff, swap_row_col]
5858  (exint ptnum, exint row, exint col)
5859  {
5860  exint curve_vtxi = swap_row_col ? col : row;
5861  outputh.set(start_ptoff + ptnum, theVComponent, T(curve_vtxi)*recip_curve_nedges);
5862  });
5863  }
5864  if (grid_info.myHasPolygonCaps && output_owner == GA_ATTRIB_VERTEX)
5865  {
5866  // Last cap starts after main grid
5867  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
5868  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
5869  {
5870  // Last row cap
5871  for (exint col = 0; col < cap_vertex_count; ++col)
5872  {
5873  outputh.set(cap_start_vtxoff + col, theVComponent, vscale);
5874  }
5875  }
5876  else
5877  {
5878  // Last side col cap
5879  // TODO: This won't work.
5880  }
5881  }
5882 }
5883 
5884 static UT_Vector2D computeGridUVScales(
5885  const GA_Detail *output_detail,
5886  const GEO_Detail *cross_section_input,
5887  const GEO_Detail *curve_input,
5888  const sop_SweepGrid &grid_info,
5889  GU_GridT<GA_Offset> grid,
5890  const bool swap_row_col,
5891  const exint cross_sections_per_vertex,
5892  const exint cap_divisions,
5893  const UVStyle ustyle,
5894  const UVStyle vstyle,
5895  const bool use_mesh_edge_lengths,
5896  const bool scalevasu_usingmax,
5897  UT_Vector2D uvscale,
5898  bool &fallback_to_uniform_v)
5899 {
5900  fallback_to_uniform_v = false;
5901 
5902  const exint curve_nedges = grid_info.myCurveNEdges;
5903  const exint curve_npts = curve_nedges + !grid_info.myCurveClosed;
5904  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
5905  exint cross_section_npts = cross_section_nedges + !grid_info.myCrossSectionClosed;
5906 
5907  // For simplicity, for now, always compute everything.
5908  // TODO: Only compute what's needed.
5909  const GA_ROHandleV3D pos(output_detail->getP());
5910  GA_ROHandleV3D curve_pos;
5911  GA_OffsetListRef curve_vertices;
5912  UT_Vector3D prevPos;
5913  bool prevPosValid = false;
5914  UT_Vector3D currPos;
5915  bool currPosValid = false;
5916  UT_Vector3D nextPos;
5917  if (curve_input != nullptr)
5918  {
5919  curve_pos.bind(curve_input->getP());
5920  UT_ASSERT_P(curve_pos.isValid());
5921  curve_vertices = curve_input->getPrimitiveVertexList(grid_info.myCurvePrimOff);
5922  UT_ASSERT(curve_vertices.size() > 0);
5923  if (curve_vertices.size() >= 2)
5924  {
5925  currPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[0]));
5926  nextPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[1]));
5927  currPosValid = true;
5928  if (grid_info.myCurveClosed)
5929  {
5930  // Closed grid in curve direction, possibly open curve with shared point (unrolled)
5931  bool unrolled = !curve_vertices.getExtraFlag();
5932  exint last_vtxi = curve_vertices.size() - (unrolled ? 2 : 1);
5933  prevPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[last_vtxi]));
5934  prevPosValid = true;
5935  }
5936  }
5937  }
5938  else if (curve_nedges >= 1 + (grid_info.myVEndPoles ? 2*cap_divisions : 0))
5939  {
5940  // If there's no curve, we use the cross section centers to
5941  // determine the implicit curve egde directions.
5942  exint start = grid_info.myVEndPoles ? cap_divisions : 0;
5943  currPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start);
5944  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start+1);
5945  currPosValid = true;
5946  if (grid_info.myCurveClosed)
5947  {
5948  prevPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, curve_nedges-1);
5949  prevPosValid = true;
5950  }
5951  }
5952 
5953  UT_Vector3D dir0;
5954  UT_Vector3D dir1;
5955  double length0 = 0;
5956  double length1 = 0;
5957  UT_Vector3D *pdir0 = nullptr;
5958  UT_Vector3D *pdir1 = nullptr;
5959  if (prevPosValid)
5960  {
5961  dir0 = (currPos - prevPos);
5962  dir1 = (nextPos - currPos);
5963  length0 = dir0.normalize();
5964  length1 = dir1.normalize();
5965  if (length0 != 0 && length1 != 0)
5966  {
5967  pdir0 = &dir0;
5968  pdir1 = &dir1;
5969  }
5970  else if (length0 != 0 || length1 != 0)
5971  pdir0 = (length0 != 0) ? &dir0 : &dir1;
5972  }
5973  else if (currPosValid)
5974  {
5975  dir1 = (nextPos - currPos);
5976  length1 = dir1.normalize();
5977  if (length1 != 0)
5978  pdir0 = &dir1;
5979  }
5980 
5981  double prev_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, 0, pdir0, pdir1);
5982  if (currPosValid)
5983  {
5984  currPos = nextPos;
5985  dir0 = dir1;
5986  length0 = length1;
5987  }
5988  double curve_length = 0;
5989  double curve_length_per_cross_section_length_sum = 0;
5990  double cross_section_length_sum = prev_cross_section_length;
5991  exint cross_section_length_count = 1;
5992  double cross_section_length_max = prev_cross_section_length;
5993  iterateCurveEdgeLengths(
5994  curve_nedges,
5995  use_mesh_edge_lengths,
5996  cross_section_npts,
5997  swap_row_col,
5998  pos,
5999  grid,
6000  grid_info,
6001  cap_divisions,
6002  curve_pos,
6003  curve_input,
6004  curve_vertices,
6005  [&](exint curve_vtxi, double length_sum, exint length_sum_count)
6006  {
6007  double curr_curve_length = (length_sum/length_sum_count);
6008  curve_length += curr_curve_length;
6009 
6010  bool nextPosValid = false;
6011  // FIXME: Double-check these bounds!!!
6012  if (!grid_info.myVEndPoles || (curve_vtxi >= cap_divisions && curve_vtxi < curve_npts-cap_divisions))
6013  {
6014  nextPosValid = true;
6015  if (cross_section_nedges >= 1)
6016  {
6017  exint nextPosVtxi = curve_vtxi+2;
6018  exint threshold = curve_npts;
6019  if (grid_info.myVEndPoles)
6020  {
6021  nextPosVtxi -= cap_divisions;
6022  threshold -= 2*cap_divisions;
6023  }
6024  if (nextPosVtxi >= threshold)
6025  {
6026  if (grid_info.myCurveClosed)
6027  nextPosVtxi -= curve_npts;
6028  else
6029  nextPosValid = false;
6030  }
6031  if (nextPosValid)
6032  {
6033  if (curve_input != nullptr)
6034  {
6035  UT_ASSERT_P(curve_pos.isValid());
6036  exint next_curve_vtxi = nextPosVtxi;
6037  if (cross_sections_per_vertex != 1)
6038  next_curve_vtxi /= cross_sections_per_vertex;
6039  if (next_curve_vtxi < 0)
6040  next_curve_vtxi = 0;
6041  else if (next_curve_vtxi >= curve_vertices.size())
6042  next_curve_vtxi = curve_vertices.size()-1;
6043  GA_Offset curve_vtxoff = curve_vertices[next_curve_vtxi];
6044  GA_Offset curve_ptoff = curve_input->vertexPoint(curve_vtxoff);
6045  nextPos = curve_pos.get(curve_ptoff);
6046  }
6047  else
6048  {
6049  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, nextPosVtxi);
6050  }
6051  }
6052  }
6053  pdir0 = nullptr;
6054  pdir1 = nullptr;
6055  if (nextPosValid)
6056  {
6057  dir1 = (nextPos - currPos);
6058  length1 = dir1.normalize();
6059  if (length0 != 0 && length1 != 0)
6060  {
6061  pdir0 = &dir0;
6062  pdir1 = &dir1;
6063  }
6064  else if (length0 != 0 || length1 != 0)
6065  pdir0 = (length0 != 0) ? &dir0 : &dir1;
6066  }
6067  else if (length0 != 0)
6068  {
6069  pdir0 = &dir0;
6070  }
6071  }
6072  double next_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, curve_vtxi+1, pdir0, pdir1);
6073  // Don't overwrite dir0 and length0 if we might need them for end caps.
6074  if (nextPosValid)
6075  {
6076  currPos = nextPos;
6077  dir0 = dir1;
6078  length0 = length1;
6079  }
6080 
6081  double curr_cross_section_length_avg = 0.5*(prev_cross_section_length + next_cross_section_length);
6082  if (curr_cross_section_length_avg != 0)
6083  {
6084  // Use adjacent cross section lengths
6085  // NOTE: Code using this scale elsewhere will need to accumulate multiplying by
6086  // Vlen[i]/avg(Ulen[i], Ulen[i+1]), instead of just Vlen[i].
6087  curve_length_per_cross_section_length_sum += (curr_curve_length/curr_cross_section_length_avg);
6088  }
6089  // Don't double-count first cross section if wrapped
6090  if (curve_vtxi+1 != curve_nedges || !grid_info.myCurveClosed)
6091  {
6092  cross_section_length_max = SYSmax(next_cross_section_length, cross_section_length_max);
6093  cross_section_length_sum += next_cross_section_length;
6094  ++cross_section_length_count;
6095  prev_cross_section_length = next_cross_section_length;
6096  }
6097  });
6098  double cross_section_length_avg = (cross_section_length_sum/cross_section_length_count);
6099 
6100  // NOTE: U coordinates from missing_input_us and cross_section_attrib are already normalized.
6101 
6102  if (ustyle == UVStyle::NORMALIZED)
6103  {
6104  // NOTE: No need to re-normalize U.
6105 
6106  if (vstyle == UVStyle::NORMALIZED)
6107  {
6108  // Both normalized: scale V down by curve length.
6109  if (curve_length == 0)
6110  fallback_to_uniform_v = true;
6111  else
6112  uvscale[1] /= curve_length;
6113  }
6114  else
6115  {
6116  // Just U normalized: scale V down by U length to keep them consistent.
6117 
6118  if ((scalevasu_usingmax && cross_section_length_max == 0) || (!scalevasu_usingmax && curve_length_per_cross_section_length_sum == 0))
6119  {
6120  uvscale[1] = 1;
6121  fallback_to_uniform_v = true;
6122  }
6123  else if (vstyle == UVStyle::FULL)
6124  {
6125  if (scalevasu_usingmax)
6126  uvscale[1] /= cross_section_length_max;
6127  // NOTE: When !scalevasu_usingmax, the code using the scale elsewhere
6128  // will automatically apply the scale, since it must
6129  // accumulate multiplying by Vlen[i]/avg(Ulen[i], Ulen[i+1]),
6130  // instead of just Vlen[i].
6131  }
6132  else
6133  {
6134  UT_ASSERT_P(vstyle == UVStyle::ROUNDED);
6135 
6136  if (scalevasu_usingmax)
6137  {
6138  if (curve_length == 0)
6139  {
6140  uvscale[1] = 1;
6141  fallback_to_uniform_v = true;
6142  }
6143  else
6144  {
6145  double rounded = SYSrint(uvscale[1]*curve_length/cross_section_length_max);
6146  if (rounded < 1)
6147  rounded = 1;
6148  uvscale[1] = rounded/curve_length;
6149  }
6150  }
6151  else
6152  {
6153  UT_ASSERT(curve_length_per_cross_section_length_sum != 0);
6154  double rounded = SYSrint(uvscale[1]*curve_length_per_cross_section_length_sum);
6155  if (rounded < 1)
6156  rounded = 1;
6157  uvscale[1] = rounded/curve_length_per_cross_section_length_sum;
6158  }
6159  }
6160  }
6161  }
6162  else if (ustyle == UVStyle::FULL)
6163  {
6164  // Because U is already normalized, we need to re-introduce
6165  // the average cross section length scale to U.
6166  uvscale[0] *= cross_section_length_avg;
6167 
6168  if (vstyle == UVStyle::NORMALIZED)
6169  {
6170  // Need to divide uvscale[0] and uvscale[1] by curve length
6171  if (curve_length == 0)
6172  {
6173  fallback_to_uniform_v = true;
6174  }
6175  else
6176  {
6177  uvscale[0] /= curve_length;
6178  uvscale[1] /= curve_length;
6179  }
6180  }
6181  else if (vstyle == UVStyle::FULL)
6182  {
6183  // No change needed
6184  }
6185  else
6186  {
6187  UT_ASSERT_P(vstyle == UVStyle::ROUNDED);
6188 
6189  // Need to compute curve length
6190  if (curve_length == 0)
6191  {
6192  uvscale[1] = 1;
6193  fallback_to_uniform_v = true;
6194  }
6195  else
6196  {
6197  double old_vscale = uvscale[1];
6198  double rounded = SYSrint(old_vscale*curve_length);
6199  if (rounded < 1)
6200  rounded = 1;
6201  uvscale[1] = rounded/curve_length;
6202  uvscale[0] *= (uvscale[1]/old_vscale);
6203  }
6204  }
6205  }
6206  else
6207  {
6208  UT_ASSERT_P(ustyle == UVStyle::ROUNDED);
6209 
6210  // NOTE: Since Us are already divided by cross section length,
6211  // uvscale[0] doesn't need to be divided by cross section length at the end.
6212  // However, the average cross section length is still needed for the
6213  // rounded U max.
6214 
6215  if (vstyle == UVStyle::NORMALIZED)
6216  {
6217  if (curve_length == 0)
6218  {
6219  double rounded = SYSrint(cross_section_length_avg*uvscale[0]);
6220  if (rounded < 1)
6221  rounded = 1;
6222  uvscale[0] = rounded;
6223  fallback_to_uniform_v = true;
6224  }
6225  else
6226  {
6227  double rounded = SYSrint(cross_section_length_avg*uvscale[0]/curve_length);
6228  if (rounded < 1)
6229  rounded = 1;
6230  uvscale[0] = rounded;
6231  uvscale[1] /= curve_length;
6232  }
6233  }
6234  else
6235  {
6236  const double old_uscale = cross_section_length_avg*uvscale[0];
6237  uvscale[0] = SYSrint(old_uscale);
6238  if (uvscale[0] < 1)
6239  uvscale[0] = 1;
6240 
6241  if (vstyle == UVStyle::FULL)
6242  {
6243  if (old_uscale != 0)
6244  uvscale[1] *= (uvscale[0]/old_uscale);
6245  }
6246  else
6247  {
6248  UT_ASSERT_P(vstyle == UVStyle::ROUNDED);
6249  if (curve_length == 0)
6250  {
6251  uvscale[1] = 1;
6252  fallback_to_uniform_v = true;
6253  }
6254  else
6255  {
6256  double rounded = SYSrint(curve_length*uvscale[1]);
6257  if (rounded < 1)
6258  rounded = 1;
6259  uvscale[1] = rounded/curve_length;
6260  }
6261  }
6262  }
6263  }
6264 
6265  return uvscale;
6266 }
6267 
6268 template<GA_AttributeOwner output_owner>
6269 static void
6270 copyUVAttribPairPtOrVtx(
6271  GA_ATINumeric *output_attrib,
6272  const GA_ATINumeric *cross_section_attrib,
6273  const GEO_Detail *cross_section_input,
6274  const GA_ATINumeric *curve_attrib,
6275  const GEO_Detail *curve_input,
6276  const sop_SweepGrid *grids,
6277  const exint ngrids,
6278  const exint cross_sections_per_vertex,
6279  const GEO_SurfaceType surface_type,
6280  const bool output_points_only,
6281  const bool triangular_poles,
6282  const exint cap_divisions,
6283  const bool reverse_cross_sections,
6284  const bool swap_row_col,
6285  const UT_Array<double> *missing_input_us,
6286  const bool length_weighted_uvs,
6287  const UVStyle ustyle,
6288  const UVStyle vstyle,
6289  const bool use_mesh_edge_lengths,
6290  const bool scalevasu_usingmax,
6291  const bool flipu,
6292  const UT_Vector2D &uvscale)
6293 {
6294  const exint PARALLEL_THRESHOLD = 2048;
6295  const bool parallel = (ngrids > 1 &&
6296  output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD);
6297  if (parallel)
6298  {
6299  // Must harden all pages before multi-threading.
6300  output_attrib->hardenAllPages();
6301  }
6302 
6303  const GA_Storage storage = output_attrib->getStorage();
6304 
6305  const bool is_cross_section_uv_computed = cross_section_attrib == nullptr || cross_section_attrib->isDetached();
6306  GA_AttributeOwner cross_section_owner = cross_section_attrib ? cross_section_attrib->getOwner() : GA_ATTRIB_INVALID;
6307  GA_AttributeOwner curve_owner = curve_attrib ? curve_attrib->getOwner() : GA_ATTRIB_VERTEX;
6308  UT_ASSERT((cross_section_attrib != nullptr) != (missing_input_us != nullptr));
6309  UT_ASSERT(!curve_attrib || (curve_owner == GA_ATTRIB_VERTEX || curve_owner == GA_ATTRIB_POINT));
6310  UT_ASSERT(!cross_section_attrib || (cross_section_owner == GA_ATTRIB_VERTEX || cross_section_owner == GA_ATTRIB_POINT));
6311 
6312  auto&& functor = [&](const UT_BlockedRange<exint>& r)
6313  {
6314  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
6315  {
6316  const sop_SweepGrid &grid_info = grids[gridi];
6317  GU_GridT<GA_Offset> grid;
6318  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
6319 
6320  bool fallback_to_uniform_v = false;
6321  UT_Vector2D grid_uvscale = uvscale;
6322  if (length_weighted_uvs && (is_cross_section_uv_computed || curve_attrib == nullptr))
6323  {
6324  GA_Detail *output_detail = &output_attrib->getDetail();
6325  grid_uvscale = computeGridUVScales(
6326  output_detail,
6327  cross_section_input,
6328  curve_input,
6329  grid_info, grid,
6330  swap_row_col,
6331  cross_sections_per_vertex,
6332  cap_divisions,
6333  ustyle, vstyle,
6334  use_mesh_edge_lengths,
6335  scalevasu_usingmax,
6336  uvscale,
6337  fallback_to_uniform_v);
6338  }
6339 
6340  UT_ASSERT((cross_section_attrib != nullptr) != (missing_input_us != nullptr));
6341  if (storage == GA_STORE_REAL64)
6342  {
6343  copyCrossSectionUVSingleGrid<fpreal64,output_owner>(
6344  output_attrib, cross_section_attrib, cross_section_owner, cross_section_input,
6345  grid_info, grid, is_cross_section_uv_computed,
6346  reverse_cross_sections, swap_row_col, flipu, grid_uvscale[0],
6347  missing_input_us);
6348  }
6349  else
6350  {
6351  copyCrossSectionUVSingleGrid<fpreal32,output_owner>(
6352  output_attrib, cross_section_attrib, cross_section_owner, cross_section_input,
6353  grid_info, grid, is_cross_section_uv_computed,
6354  reverse_cross_sections, swap_row_col, flipu, grid_uvscale[0],
6355  missing_input_us);
6356  }
6357 
6358  if (curve_attrib == nullptr)
6359  {
6360  // Use computed implicit backbone curve U values (for V).
6361 
6362  if (storage == GA_STORE_REAL64)
6363  {
6364  GA_RWHandleD outputh(output_attrib);
6365  GA_ROHandleV3D pos(output_attrib->getDetail().getP());
6366 
6367  if (length_weighted_uvs && !fallback_to_uniform_v)
6368  generateLengthWeightedV<output_owner>(grid_info, grid, cross_sections_per_vertex, outputh, pos, ustyle, vstyle,
6369  scalevasu_usingmax, grid_uvscale[1], uvscale[1], swap_row_col, use_mesh_edge_lengths, curve_input);
6370  else
6371  generateUniformV<output_owner>(grid_info, grid, outputh, swap_row_col, grid_uvscale[1]);
6372  }
6373  else
6374  {
6375  GA_RWHandleF outputh(output_attrib);
6376  GA_ROHandleV3D pos(output_attrib->getDetail().getP());
6377  if (length_weighted_uvs && !fallback_to_uniform_v)
6378  generateLengthWeightedV<output_owner>(grid_info, grid, cross_sections_per_vertex, outputh, pos, ustyle, vstyle,
6379  scalevasu_usingmax, float(grid_uvscale[1]), float(uvscale[1]), swap_row_col, use_mesh_edge_lengths, curve_input);
6380  else
6381  generateUniformV<output_owner>(grid_info, grid, outputh, swap_row_col, float(grid_uvscale[1]));
6382  }
6383  }
6384  }
6385  };
6386 
6387  const UT_BlockedRange<exint> grid_range(0, ngrids);
6388  if (parallel)
6389  UTparallelFor(grid_range, functor);
6390  else
6391  functor(grid_range);
6392 
6393  if (curve_attrib != nullptr && curve_owner == GA_ATTRIB_VERTEX)
6394  {
6395  if (storage == GA_STORE_REAL64)
6396  {
6397  GA_RWHandleD outputh(output_attrib);
6398  GA_ROHandleD curveh(curve_attrib);
6399  auto &&copy_functor = [&outputh,&curveh](GA_Offset output_off, GA_Offset curve_off)
6400  {
6401  outputh.set(output_off, theVComponent, GAisValid(curve_off) ? curveh.get(curve_off) : 1.0);
6402  };
6403  copyCurveAttrib2<output_owner,true>(
6404  output_attrib, curve_input,
6405  grids, ngrids, cross_sections_per_vertex,
6406  surface_type, output_points_only, triangular_poles, cap_divisions,
6407  swap_row_col, copy_functor);
6408  }
6409  else
6410  {
6411  GA_RWHandleF outputh(output_attrib);
6412  GA_ROHandleF curveh(curve_attrib);
6413  auto &&copy_functor = [&outputh,&curveh](GA_Offset output_off, GA_Offset curve_off)
6414  {
6415  outputh.set(output_off, theVComponent, GAisValid(curve_off) ? curveh.get(curve_off) : 1.0f);
6416  };
6417  copyCurveAttrib2<output_owner,true>(
6418  output_attrib, curve_input,
6419  grids, ngrids, cross_sections_per_vertex,
6420  surface_type, output_points_only, triangular_poles, cap_divisions,
6421  swap_row_col, copy_functor);
6422  }
6423  }
6424  else if (curve_attrib != nullptr && curve_owner == GA_ATTRIB_POINT)
6425  {
6426  // We do support a point curve owner with a vertex output owner.
6427 
6428  if (storage == GA_STORE_REAL64)
6429  {
6430  GA_RWHandleD outputh(output_attrib);
6431  GA_ROHandleD curveh(curve_attrib);
6432  auto &&copy_functor = [&outputh,&curveh,curve_input](GA_Offset output_off, GA_Offset curve_vtxoff)
6433  {
6434  double value;
6435  if (GAisValid(curve_vtxoff))
6436  {
6437  GA_Offset curve_off = curve_input->vertexPoint(curve_vtxoff);
6438  value = curveh.get(curve_off);
6439  }
6440  else
6441  value = 1.0;
6442  outputh.set(output_off, theVComponent, value);
6443  };
6444  copyCurveAttrib2<output_owner,true>(
6445  output_attrib, curve_input,
6446  grids, ngrids, cross_sections_per_vertex,
6447  surface_type, output_points_only, triangular_poles, cap_divisions,
6448  swap_row_col, copy_functor);
6449  }
6450  else
6451  {
6452  GA_RWHandleF outputh(output_attrib);
6453  GA_ROHandleF curveh(curve_attrib);
6454  auto &&copy_functor = [&outputh,&curveh,curve_input](GA_Offset output_off, GA_Offset curve_vtxoff)
6455  {
6456  float value;
6457  if (GAisValid(curve_vtxoff))
6458  {
6459  GA_Offset curve_off = curve_input->vertexPoint(curve_vtxoff);
6460  value = curveh.get(curve_off);
6461  }
6462  else
6463  value = 1.0f;
6464  outputh.set(output_off, theVComponent, value);
6465  };
6466  copyCurveAttrib2<output_owner,true>(
6467  output_attrib, curve_input,
6468  grids, ngrids, cross_sections_per_vertex,
6469  surface_type, output_points_only, triangular_poles, cap_divisions,
6470  swap_row_col, copy_functor);
6471  }
6472  }
6473 }
6474 
6475 static void
6476 copyUVAttribPair(
6477  GA_ATINumeric *output_attrib,
6478  const GA_ATINumeric *cross_section_attrib,
6479  const GEO_Detail *cross_section_input,
6480  const GA_ATINumeric *curve_attrib,
6481  const GEO_Detail *curve_input,
6482  const sop_SweepGrid *grids,
6483  const exint ngrids,
6484  const exint cross_sections_per_vertex,
6485  const GEO_SurfaceType surface_type,
6486  const bool output_points_only,
6487  const bool triangular_poles,
6488  const exint cap_divisions,
6489  const bool reverse_cross_sections,
6490  const bool swap_row_col,
6491  const UT_Array<double> *missing_input_us,
6492  const bool length_weighted_uvs,
6493  const UVStyle ustyle,
6494  const UVStyle vstyle,
6495  const bool use_mesh_edge_lengths,
6496  const bool scalevasu_usingmax,
6497  const bool flipu,
6498  const UT_Vector2D &uvscale)
6499 {
6500  output_attrib->bumpDataId();
6501 
6502  // NOTE: If UV generation is disabled and one of the two input attributes is missing,
6503  // this function shouldn't be called; the existing attribute should be copied as a normal attribute.
6504 
6505  const GA_AttributeOwner output_owner = output_attrib->getOwner();
6506  if (output_owner == GA_ATTRIB_DETAIL)
6507  {
6508  UT_ASSERT(curve_attrib);
6509  UT_ASSERT(cross_section_attrib);
6510  UT_ASSERT(curve_attrib->getOwner() == GA_ATTRIB_DETAIL);
6511  UT_ASSERT(cross_section_attrib->getOwner() == GA_ATTRIB_DETAIL);
6512 
6513  GA_RWHandleV2D outputh(output_attrib);
6514  GA_ROHandleD cross_sectionh(cross_section_attrib);
6515  GA_ROHandleD curveh(curve_attrib);
6516  double u = cross_sectionh.get(GA_DETAIL_OFFSET);
6517  double v = curveh.get(GA_DETAIL_OFFSET);
6518  outputh.set(GA_DETAIL_OFFSET, UT_Vector2D(u,v));
6519  return;
6520  }
6521  if (output_owner == GA_ATTRIB_POINT)
6522  {
6523  copyUVAttribPairPtOrVtx<GA_ATTRIB_POINT>(
6524  output_attrib,
6525  cross_section_attrib,
6526  cross_section_input,
6527  curve_attrib,
6528  curve_input,
6529  grids, ngrids,
6530  cross_sections_per_vertex,
6531  surface_type,
6532  output_points_only,
6533  triangular_poles,
6534  cap_divisions,
6535  reverse_cross_sections,
6536  swap_row_col,
6537  missing_input_us,
6538  length_weighted_uvs,
6539  ustyle, vstyle,
6540  use_mesh_edge_lengths,
6541  scalevasu_usingmax,
6542  flipu,
6543  uvscale);
6544 
6545  return;
6546  }
6547  if (output_points_only)
6548  return;
6549  if (output_owner == GA_ATTRIB_VERTEX)
6550  {
6551  if (output_attrib->getDetail().getNumVertices() == 0)
6552  return;
6553 
6554  copyUVAttribPairPtOrVtx<GA_ATTRIB_VERTEX>(
6555  output_attrib,
6556  cross_section_attrib,
6557  cross_section_input,
6558  curve_attrib,
6559  curve_input,
6560  grids, ngrids,
6561  cross_sections_per_vertex,
6562  surface_type,
6563  output_points_only,
6564  triangular_poles,
6565  cap_divisions,
6566  reverse_cross_sections,
6567  swap_row_col,
6568  missing_input_us,
6569  length_weighted_uvs,
6570  ustyle, vstyle,
6571  use_mesh_edge_lengths,
6572  scalevasu_usingmax,
6573  flipu,
6574  uvscale);
6575 
6576  return;
6577  }
6578  if (output_owner == GA_ATTRIB_PRIMITIVE)
6579  {
6580  if (output_attrib->getDetail().getNumPrimitives() == 0)
6581  return;
6582 
6583  const exint PARALLEL_THRESHOLD = 2048;
6584  const bool parallel = (ngrids > 1 &&
6585  output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD);
6586  if (parallel)
6587  {
6588  // Must harden all pages before multi-threading.
6589  output_attrib->hardenAllPages();
6590  }
6591 
6592  UT_ASSERT(curve_attrib);
6593  UT_ASSERT(cross_section_attrib);
6594  UT_ASSERT(curve_attrib->getOwner() == GA_ATTRIB_PRIMITIVE);
6595  UT_ASSERT(cross_section_attrib->getOwner() == GA_ATTRIB_PRIMITIVE);
6596 
6597  const GA_Storage storage = output_attrib->getStorage();
6598  auto&& functor = [&](const UT_BlockedRange<exint>& r)
6599  {
6600  if (storage == GA_STORE_REAL64)
6601  {
6602  GA_ROHandleD curveh(curve_attrib);
6603  GA_RWHandleD outputh(output_attrib);
6604  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
6605  {
6606  const sop_SweepGrid &grid_info = grids[gridi];
6607  GU_GridT<GA_Offset> grid;
6608  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
6609 
6610  copyCrossSectionUVSingleGrid<fpreal64,GA_ATTRIB_PRIMITIVE>(
6611  output_attrib, cross_section_attrib, GA_ATTRIB_PRIMITIVE, cross_section_input,
6612  grid_info, grid, false,
6613  reverse_cross_sections, swap_row_col, flipu, uvscale[0]);
6614 
6615  if (curveh.isValid() && outputh.isValid() && outputh.getTupleSize() >= 2)
6616  {
6617  const double v = curveh.get(grid_info.myCurvePrimOff);
6618 
6620  [&grid_info,&outputh,v](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
6621  {
6622  outputh.set(grid_info.myStartPrimOff + primnum, theVComponent, v);
6623  });
6624  }
6625  }
6626  }
6627  else
6628  {
6629  GA_ROHandleF curveh(curve_attrib);
6630  GA_RWHandleF outputh(output_attrib);
6631  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
6632  {
6633  const sop_SweepGrid &grid_info = grids[gridi];
6634  GU_GridT<GA_Offset> grid;
6635  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
6636 
6637  copyCrossSectionUVSingleGrid<fpreal32,GA_ATTRIB_PRIMITIVE>(
6638  output_attrib, cross_section_attrib, GA_ATTRIB_PRIMITIVE, cross_section_input,
6639  grid_info, grid, false,
6640  reverse_cross_sections, swap_row_col, flipu, uvscale[0]);
6641 
6642  if (curveh.isValid() && outputh.isValid() && outputh.getTupleSize() >= 2)
6643  {
6644  const float v = curveh.get(grid_info.myCurvePrimOff);
6645 
6647  [&grid_info,&outputh,v](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
6648  {
6649  outputh.set(grid_info.myStartPrimOff + primnum, theVComponent, v);
6650  });
6651  }
6652  }
6653  }
6654  };
6655 
6656  const UT_BlockedRange<exint> grid_range(0, ngrids);
6657  if (parallel)
6658  UTparallelFor(grid_range, functor);
6659  else
6660  functor(grid_range);
6661  return;
6662  }
6663 }
6664 
6665 static GEO_SurfaceType sweepSurfaceToGeoSurface(const SurfaceType src_type)
6666 {
6667  switch (src_type)
6668  {
6669  case SurfaceType::ROWS:
6670  return GEO_PATCH_ROWS;
6671  case SurfaceType::COLS:
6672  return GEO_PATCH_COLS;
6673  case SurfaceType::ROWCOL:
6674  return GEO_PATCH_ROWCOL;
6675  case SurfaceType::QUADS:
6676  return GEO_PATCH_QUADS;
6677  case SurfaceType::TRIS:
6678  return GEO_PATCH_TRIANGLE;
6679  case SurfaceType::REVTRIS:
6680  return GEO_PATCH_REVTRIANGLE;
6681  case SurfaceType::ALTTRIS:
6682  return GEO_PATCH_ALTTRIANGLE;
6683 
6684  case SurfaceType::POINTS:
6685  break;
6686  }
6687  return GEO_PATCH_QUADS;
6688 }
6689 
6690 static bool
6691 copySurfaceAttributes(
6692  GEO_Detail *output_geo,
6693  const GEO_Detail *curve_input,
6694  const GEO_Detail *cross_section_input,
6695  const UT_Array<sop_SweepGrid> &grids,
6696  const exint cross_sections_per_vertex,
6697  const UT_Array<std::pair<const GA_Attribute*,GA_Attribute*>> &cross_section_attribs,
6698  const UT_Array<std::pair<const GA_Attribute*,GA_Attribute*>> &curve_attribs,
6699  const UT_Array<std::pair<std::pair<const GA_ATINumeric*,const GA_ATINumeric*>,GA_ATINumeric*>> &uv_attribs,
6700  const SOP_SweepHDKCache *const transform_cache,
6701  const GEO_SurfaceType surface_type,
6702  const bool output_points_only,
6703  const bool triangular_poles,
6704  const exint cap_divisions,
6705  const bool reverse_cross_sections,
6706  const bool swap_row_col,
6707  const exint uv_attrib_index,
6708  const UT_Array<double> *missing_input_us,
6709  const bool length_weighted_uvs,
6710  const UVStyle ustyle,
6711  const UVStyle vstyle,
6712  const bool use_mesh_edge_lengths,
6713  const bool scalevasu_usingmax,
6714  const bool flipu,
6715  const UT_Vector2D &uvscale)
6716 {
6717  UT_AutoInterrupt interrupt("Copying surface attributes");
6718  if (interrupt.wasInterrupted())
6719  return false;
6720 
6721  // Copy/transform cross section attributes
6722  for (exint attribi = 0; attribi < cross_section_attribs.size(); ++attribi)
6723  {
6724  if (interrupt.wasInterrupted())
6725  return false;
6726 
6727  const GA_Attribute *cross_section_attrib = cross_section_attribs[attribi].first;
6728  GA_Attribute *output_attrib = cross_section_attribs[attribi].second;
6729  copyCrossSectionAttrib(
6730  output_attrib,
6731  cross_section_attrib,
6732  cross_section_input,
6733  transform_cache,
6734  grids.getArray(), grids.size(),
6735  cross_sections_per_vertex, cap_divisions,
6736  surface_type, output_points_only, triangular_poles,
6737  reverse_cross_sections, swap_row_col);
6738  }
6739 
6740  // Copy curve attributes
6741  for (exint attribi = 0; attribi < curve_attribs.size(); ++attribi)
6742  {
6743  if (interrupt.wasInterrupted())
6744  return false;
6745 
6746  const GA_Attribute *curve_attrib = curve_attribs[attribi].first;
6747  GA_Attribute *output_attrib = curve_attribs[attribi].second;
6748  copyCurveAttrib(
6749  output_attrib,
6750  curve_attrib,
6751  curve_input,
6752  grids.getArray(), grids.size(),
6753  cross_sections_per_vertex,
6754  surface_type, output_points_only, triangular_poles,
6755  cap_divisions, swap_row_col);
6756  }
6757 
6758  // Copy UV attributes
6759  for (exint attribi = 0; attribi < uv_attribs.size(); ++attribi)
6760  {
6761  if (interrupt.wasInterrupted())
6762  return false;
6763 
6764  const GA_ATINumeric *cross_section_attrib = uv_attribs[attribi].first.first;
6765  const GA_ATINumeric *curve_attrib = uv_attribs[attribi].first.second;
6766  GA_ATINumeric *output_attrib = uv_attribs[attribi].second;
6767  copyUVAttribPair(
6768  output_attrib,
6769  cross_section_attrib,
6770  cross_section_input,
6771  curve_attrib,
6772  curve_input,
6773  grids.getArray(), grids.size(),
6774  cross_sections_per_vertex,
6775  surface_type, output_points_only, triangular_poles,
6776  cap_divisions,
6777  reverse_cross_sections, swap_row_col,
6778  (uv_attrib_index == attribi) ? missing_input_us : nullptr,
6779  length_weighted_uvs,
6780  ustyle, vstyle,
6781  use_mesh_edge_lengths,
6782  scalevasu_usingmax,
6783  (uv_attrib_index == attribi) && flipu,
6784  uvscale);
6785  }
6786 
6787  return true;
6788 }
6789 
6790 static void setupCrossSectionAttribs(
6791  SOP_SweepHDKCache *sopcache,
6792  const SOP_NodeVerb::CookParms &cookparms,
6793  const SOP_SweepHDKParms &sopparms,
6794  bool &same_cross_section_data,
6795  CopyOrder &copy_order,
6796  const GEO_Detail *const cross_section_input,
6797  const GEO_Detail *const curve_input,
6798  const GA_PrimitiveGroup *cross_section_group)
6799 {
6800  UT_ASSERT_MSG(cross_section_input && curve_input, "Copy order only makes sense if both inputs are present.");
6801 
6802  CrossSectionAttribMatchData &copy_order_attrib_data = sopcache->myCrossSectionAttribMatchData;
6803 
6804  // Handle data for case where an attribute on the curves
6805  // selects which cross section primitive to use.
6806 
6807  const UT_StringHolder &cross_section_attrib_name = sopparms.getCrossSectionAttrib();
6808 
6809  // First, find attribute with the given name (of any type or owner) on the curves.
6810  const GA_AttributeOwner attrib_owners[4] = {
6815  };
6816  const GA_Attribute *curve_attrib = curve_input->findAttribute(cross_section_attrib_name, attrib_owners, 4);
6817 
6818  bool string_missing_error_case = false;
6819  if (curve_attrib) {
6820  // We found one.
6821  const int64 new_curve_data_id = curve_attrib->getDataId();
6822  same_cross_section_data &= (sopcache->myPrevCrossSectCurveAttribDataId == new_curve_data_id);
6823  sopcache->myPrevCrossSectCurveAttribDataId = new_curve_data_id;
6824 
6825  copy_order_attrib_data.myCurveAttribOwner = curve_attrib->getOwner();
6826 
6827  // Check if it can be read as an integer attribute. (Handle will
6828  // automatically convert float attribute values as they're read.)
6829  copy_order_attrib_data.myCurveIntAttrib.bind(curve_attrib);
6830  if (!copy_order_attrib_data.myCurveIntAttrib.isValid()) {
6831  copy_order_attrib_data.myCurveStrAttrib.bind(curve_attrib);
6832  if (!copy_order_attrib_data.myCurveStrAttrib.isValid()) {
6833  // Invalid: Must have integer or string attribute.
6834  curve_attrib = nullptr;
6835  }
6836  else {
6837  GA_ROHandleS cross_section_name_attrib(cross_section_input->findPrimitiveAttribute(cross_section_attrib_name));
6838  if (!cross_section_name_attrib.isValid()) {
6839  // Invalid: Must have matching primitive string attribute.
6840  curve_attrib = nullptr;
6841  copy_order_attrib_data.myCurveStrAttrib.clear();
6842  string_missing_error_case = true;
6843  }
6844  else {
6845  // Valid: String attribute value matching.
6846  const int64 new_cross_section_data_id = cross_section_name_attrib->getDataId();
6847  const bool same_map = (sopcache->myPrevCrossSectPrimAttribDataId == new_cross_section_data_id);
6848  same_cross_section_data &= same_map;
6849  sopcache->myPrevCrossSectPrimAttribDataId = new_cross_section_data_id;
6850  copy_order_attrib_data.myIsUsingMap = true;
6851 
6852  // Avoid recomputing the mapping if same as last cook.
6853  if (!same_map) {
6854  // Destroy any previous map.
6855  copy_order_attrib_data.myIntToPrimOff.destroy();
6856  copy_order_attrib_data.myStrToPrimOff.destroy();
6857 
6858  // Record the mapping from name to cross section primitive offset
6859  GA_Offset start;
6860  GA_Offset end;
6861  for (GA_Iterator it(cross_section_input->getPrimitiveRange(cross_section_group)); it.blockAdvance(start, end); ) {
6862  for (GA_Offset primoff = start; primoff < end; ++primoff) {
6863  const UT_StringHolder &name = cross_section_name_attrib.get(primoff);
6864  if (!name.isstring()) {
6865  // UT_ArrayStringMap doesn't support "" as a key, and if
6866  // "" appears in the matching attribute, the user is
6867  // probably indicating that they don't want to copy
6868  // anything there, so it should be okay.
6869  continue;
6870  }
6871 
6872  // NOTE: This will not overwrite if another primitive with the same name has already been inserted,
6873  // so it only keeps the first match.
6874  copy_order_attrib_data.myStrToPrimOff.insert(name, primoff);
6875  }
6876  }
6877  }
6878  }
6879  }
6880  }
6881  else {
6882  GA_ROHandleID cross_section_id_attrib(cross_section_input->findPrimitiveAttribute(cross_section_attrib_name));
6883  if (!cross_section_id_attrib.isValid()) {
6884  // Valid: Looking up primitives by index.
6885  copy_order_attrib_data.myIsUsingMap = false;
6886  same_cross_section_data &= (sopcache->myPrevCrossSectPrimAttribDataId == -1);
6887  sopcache->myPrevCrossSectPrimAttribDataId = -1;
6888 
6889  // Destroy any previous map.
6890  copy_order_attrib_data.myIntToPrimOff.destroy();
6891  copy_order_attrib_data.myStrToPrimOff.destroy();
6892  }
6893  else {
6894  // Valid: Integer attribute value matching.
6895  copy_order_attrib_data.myIsUsingMap = true;
6896  const int64 new_cross_section_data_id = cross_section_id_attrib->getDataId();
6897  const bool same_map = (sopcache->myPrevCrossSectPrimAttribDataId == new_cross_section_data_id);
6898  same_cross_section_data &= same_map;
6899  sopcache->myPrevCrossSectPrimAttribDataId = new_cross_section_data_id;
6900 
6901  // Avoid recomputing the mapping if same as last cook.
6902  if (!same_map) {
6903  // Destroy any previous map.
6904  copy_order_attrib_data.myIntToPrimOff.destroy();
6905  copy_order_attrib_data.myStrToPrimOff.destroy();
6906 
6907  // Record the mapping from ID to cross section primitive offset
6908  GA_Offset start;
6909  GA_Offset end;
6910  for (GA_Iterator it(cross_section_input->getPrimitiveRange(cross_section_group)); it.blockAdvance(start, end); ) {
6911  for (GA_Offset primoff = start; primoff < end; ++primoff) {
6912  exint id = cross_section_id_attrib.get(primoff);
6914  // UT::ArrayMap<exint,...> doesn't support
6915  // 0x8000000000000000LL as a key. Hopefully that's okay.
6916  continue;
6917  }
6918 
6919  // NOTE: This will not overwrite if another primitive with the same ID has already been inserted,
6920  // so it only keeps the first match.
6921  copy_order_attrib_data.myIntToPrimOff.insert(id, primoff);
6922  }
6923  }
6924  }
6925  }
6926  }
6927  }
6928  if (!curve_attrib) {
6929  // Something went wrong, so warn.
6931  if (!string_missing_error_case)
6932  buf.sprintf("Curve input geometry doesn't have an integer or string attribute named \"%s\" for selecting cross section primitives.", cross_section_attrib_name.c_str());
6933  else
6934  buf.sprintf("Cross section doesn't have a primitive string attribute named \"%s\" for selecting cross section primitives,", cross_section_attrib_name.c_str());
6935  cookparms.sopAddWarning(SOP_MESSAGE, buf.buffer());
6936 
6937  // Fall back to the default copy order mode.
6938  copy_order = CopyOrder::CYCLEVTX;
6939  same_cross_section_data = (sopcache->myPrevCopyOrder == copy_order);
6940  sopcache->myPrevCrossSectCurveAttribDataId = -1;
6941  sopcache->myPrevCrossSectPrimAttribDataId = -1;
6942  copy_order_attrib_data.myIntToPrimOff.destroy();
6943  copy_order_attrib_data.myStrToPrimOff.destroy();
6944  copy_order_attrib_data.myCurveAttribOwner = GA_ATTRIB_INVALID;
6945  }
6946 }
6947 
6948 static void removeUnnecessarySweepAttribs(
6949  GEO_Detail *output_geo)
6950 {
6951  // For simplicity, we delete all nonessential attributes and groups
6952  // from the output detail.
6953  // TODO: Only delete the ones that won't be in the output this cook
6954  // or that don't match the correct type / data type / tuple size.
6955  UT_SmallArray<GA_Attribute *> attribs_to_delete;
6956  output_geo->pointAttribs().forEachAttribute([output_geo,&attribs_to_delete](GA_Attribute *attrib)
6957  {
6958  if (attrib != output_geo->getP() && !GA_ATITopology::isType(attrib))
6959  attribs_to_delete.append(attrib);
6960  });
6961  for (exint i = 0, n = attribs_to_delete.size(); i < n; ++i)
6962  {
6963  const UT_StringHolder &name = attribs_to_delete[i]->getName();
6964  GA_AttributeScope scope = attribs_to_delete[i]->getScope();
6965  if (scope != GA_SCOPE_GROUP)
6966  output_geo->destroyAttribute(GA_ATTRIB_POINT, scope, name);
6967  else
6968  output_geo->destroyPointGroup(name);
6969  }
6970  attribs_to_delete.clear();
6971  output_geo->vertexAttribs().forEachAttribute([&attribs_to_delete](GA_Attribute *attrib)
6972  {
6973  if (!GA_ATITopology::isType(attrib))
6974  attribs_to_delete.append(attrib);
6975  });
6976  for (exint i = 0, n = attribs_to_delete.size(); i < n; ++i)
6977  {
6978  const UT_StringHolder &name = attribs_to_delete[i]->getName();
6979  GA_AttributeScope scope = attribs_to_delete[i]->getScope();
6980  if (scope != GA_SCOPE_GROUP)
6981  output_geo->destroyAttribute(GA_ATTRIB_VERTEX, scope, name);
6982  else
6983  output_geo->destroyVertexGroup(name);
6984  }
6985  attribs_to_delete.clear();
6986  output_geo->primitiveAttribs().forEachAttribute([&attribs_to_delete](GA_Attribute *attrib)
6987  {
6988  attribs_to_delete.append(attrib);
6989  });
6990  for (exint i = 0, n = attribs_to_delete.size(); i < n; ++i)
6991  {
6992  const UT_StringHolder &name = attribs_to_delete[i]->getName();
6993  GA_AttributeScope scope = attribs_to_delete[i]->getScope();
6994  if (scope != GA_SCOPE_GROUP)
6995  output_geo->destroyAttribute(GA_ATTRIB_PRIMITIVE, scope, name);
6996  else
6997  output_geo->destroyPrimitiveGroup(name);
6998  }
6999  attribs_to_delete.clear();
7000  output_geo->attribs().forEachAttribute([&attribs_to_delete](GA_Attribute *attrib)
7001  {
7002  attribs_to_delete.append(attrib);
7003  });
7004  for (exint i = 0, n = attribs_to_delete.size(); i < n; ++i)
7005  output_geo->destroyAttribute(GA_ATTRIB_DETAIL, attribs_to_delete[i]->getScope(), attribs_to_delete[i]->getName());
7006  attribs_to_delete.setCapacity(0);
7007 
7008  UT_SmallArray<GA_EdgeGroup *> edge_groups_to_delete;
7009  for (auto it = output_geo->edgeGroups().beginTraverse(); !it.atEnd(); ++it)
7010  {
7011  edge_groups_to_delete.append(it.group());
7012  }
7013  for (exint i = 0, n = edge_groups_to_delete.size(); i < n; ++i)
7014  {
7015  output_geo->destroyEdgeGroup(edge_groups_to_delete[i]);
7016  }
7017 }
7018 
7019 static void setupSweepTransferAttribs(
7020  GEO_Detail *output_geo,
7021  const GEO_Detail *cross_section_input,
7022  const UT_StringHolder &cross_section_attrib_pattern,
7023  const GEO_Detail *curve_input,
7024  const UT_StringHolder &curve_attrib_pattern,
7025  UT_Array<std::pair<const GA_Attribute*,GA_Attribute*>> &cross_section_attribs,
7026  UT_Array<std::pair<const GA_Attribute*,GA_Attribute*>> &curve_attribs,
7027  UT_Array<std::pair<std::pair<const GA_ATINumeric*,const GA_ATINumeric*>,GA_ATINumeric*>> &uv_attribs,
7028  exint &uv_attrib_index,
7029  const bool compute_uvs,
7030  const bool override_existing_uvs)
7031 {
7032  const GA_AttributeFilter curve_filter(
7035  GA_AttributeFilter::selectByPattern(curve_attrib_pattern)));
7036 
7037  UT_ASSERT(compute_uvs || !override_existing_uvs);
7038  uv_attrib_index = -1;
7039 
7040  // If matching texture coordinate attributes exist on both inputs, add the pair to uv_attribs.
7041  // If UV generation is on and "uv" specifically is on only one input or neither input, add it to uv_attribs, with nullptr for the missing one/two.
7042  // If texture coordinate attribute, but not "uv" specifically and no match exists, add to cross_section_attribs or curve_attribs to copy.
7043 
7044  if (cross_section_input)
7045  {
7046  const GA_AttributeFilter filter(
7049  GA_AttributeFilter::selectByPattern(cross_section_attrib_pattern)));
7050  auto &&clone_attrib_functor = [&cross_section_attribs,&uv_attribs,&uv_attrib_index,&filter,output_geo,
7051  curve_input,&curve_filter,override_existing_uvs,compute_uvs](const GA_Attribute *attrib)
7052  {
7053  if (!filter.match(attrib))
7054  return;
7055 
7056  GA_AttributeOwner owner = attrib->getOwner();
7057 
7058  // If we're overriding uv, don't transfer it.
7059  const bool point_or_vertex = (owner == GA_ATTRIB_POINT || owner == GA_ATTRIB_VERTEX);
7060  const bool isuv = point_or_vertex &&
7061  (attrib->getScope() == GA_SCOPE_PUBLIC) &&
7062  (attrib->getName() == GA_Names::uv);
7063  if ((compute_uvs && override_existing_uvs) && isuv)
7064  return;
7065 
7066  if (point_or_vertex && attrib->shouldInterpretAsTexCoord())
7067  {
7068  UT_ASSERT_MSG(GA_ATINumeric::isType(attrib), "This should be guaranteed by shouldInterpretAsTexCoord()");
7069  if (curve_input)
7070  {
7071  const GA_Attribute *other_attrib = curve_input->findAttribute(GA_ATTRIB_VERTEX, attrib->getName());
7072  if (!other_attrib)
7073  other_attrib = curve_input->findAttribute(GA_ATTRIB_POINT, attrib->getName());
7074  if (other_attrib && curve_filter.match(other_attrib) && other_attrib->shouldInterpretAsTexCoord())
7075  {
7076  UT_ASSERT_MSG(GA_ATINumeric::isType(other_attrib), "This should be guaranteed by shouldInterpretAsTexCoord()");
7077  GA_AttributeOwner output_owner = (owner == GA_ATTRIB_VERTEX || other_attrib->getOwner() == GA_ATTRIB_VERTEX) ? GA_ATTRIB_VERTEX : GA_ATTRIB_POINT;
7078 
7079  GA_Attribute *new_attribute = output_geo->getAttributes().cloneAttribute(
7080  output_owner, attrib->getName(),
7082  *attrib, true);
7083 
7084  uv_attribs.append(std::make_pair(
7085  std::make_pair(
7086  GA_ATINumeric::cast(attrib),
7087  GA_ATINumeric::cast(other_attrib)),
7088  GA_ATINumeric::cast(new_attribute)));
7089  return;
7090  }
7091  }
7092  if (isuv && compute_uvs)
7093  {
7094  // No matching uv attribute, but we're generating UVs, so still put it in uv_attribs.
7095  // Generated UVs always result in a vertex attribute, for safety.
7096  GA_Attribute *new_attribute = output_geo->getAttributes().cloneAttribute(
7097  GA_ATTRIB_VERTEX, attrib->getName(),
7099  *attrib, true);
7100 
7101  uv_attrib_index = uv_attribs.size();
7102  uv_attribs.append(std::make_pair(
7103  std::make_pair(
7104  GA_ATINumeric::cast(attrib), nullptr),
7105  GA_ATINumeric::cast(new_attribute)));
7106  return;
7107  }
7108  }
7109 
7110  // NOTE: We even clone P, to match the storage type and metadata
7111  GA_Attribute *new_attribute;
7112  if (attrib->getScope() != GA_SCOPE_GROUP)
7113  {
7114  new_attribute = output_geo->getAttributes().cloneAttribute(
7115  owner, attrib->getName(),
7117  *attrib, true);
7118  }
7119  else
7120  {
7121  new_attribute = UTverify_cast<GA_ElementGroup*>(
7122  output_geo->getElementGroupTable(owner).newGroup(attrib->getName()));
7123  }
7124 
7125  if (owner != GA_ATTRIB_DETAIL)
7126  cross_section_attribs.append(std::make_pair(attrib, new_attribute));
7127  else
7128  {
7129  // Just copy detail attribute values
7130  new_attribute->copy(GA_DETAIL_OFFSET, *attrib, GA_DETAIL_OFFSET);
7131  }
7132  };
7133  for (int owneri = 0; owneri < GA_ATTRIB_OWNER_N; ++owneri)
7134  {
7135  cross_section_input->getAttributes().getDict(GA_AttributeOwner(owneri))
7136  .forEachAttribute(clone_attrib_functor);
7137  }
7138  }
7139 
7140  if (curve_input)
7141  {
7142  auto &&clone_attrib_functor = [&curve_attribs,&uv_attribs,&uv_attrib_index,&curve_filter,output_geo,
7143  cross_section_input,override_existing_uvs,compute_uvs](const GA_Attribute *attrib)
7144  {
7145  if (!curve_filter.match(attrib))
7146  return;
7147 
7148  GA_AttributeOwner owner = attrib->getOwner();
7149 
7150  // Don't copy attributes that were copied from cross_section_input,
7151  // unless it's a uv-like attribute, (since we can combine those).
7152  // TODO: Handle combining things like v from both inputs.
7153  // NOTE: This way of checking automatically excludes P if cross_section_input is non-null.
7154  if (cross_section_input)
7155  {
7156  GA_Attribute *old_attrib =
7157  output_geo->findAttribute(owner, attrib->getScope(), attrib->getName());
7158 
7159  if (old_attrib)
7160  {
7161  // NOTE: If they're being combined as texture coordinate attributes, they were added to uv_attribs above.
7162  return;
7163  }
7164  if (owner == GA_ATTRIB_POINT || owner == GA_ATTRIB_VERTEX)
7165  {
7166  // Avoid point-vs-vertex name conflicts, too.
7168  old_attrib = output_geo->findAttribute(
7169  other_owner, attrib->getScope(), attrib->getName());
7170  if (old_attrib)
7171  return;
7172  }
7173  }
7174 
7175  // If we're overriding uv, don't transfer it.
7176  const bool point_or_vertex = (owner == GA_ATTRIB_POINT || owner == GA_ATTRIB_VERTEX);
7177  const bool isuv = point_or_vertex &&
7178  (attrib->getScope() == GA_SCOPE_PUBLIC) &&
7179  (attrib->getName() == GA_Names::uv);
7180  if ((compute_uvs && override_existing_uvs) && isuv)
7181  return;
7182 
7183  if (point_or_vertex && attrib->shouldInterpretAsTexCoord())
7184  {
7185  UT_ASSERT_MSG(GA_ATINumeric::isType(attrib), "This should be guaranteed by shouldInterpretAsTexCoord()");
7186 
7187  // NOTE: Don't need to worry about case of conflict with cross_section_input, since that's handled above.
7188  if (isuv && compute_uvs)
7189  {
7190  // No matching uv attribute, but we're generating UVs, so still put it in uv_attribs.
7191  // Generated UVs always result in a vertex attribute, for safety.
7192  GA_Attribute *new_attribute = output_geo->getAttributes().cloneAttribute(
7193  GA_ATTRIB_VERTEX, attrib->getName(),
7195  *attrib, true);
7196 
7197  uv_attrib_index = uv_attribs.size();
7198  uv_attribs.append(std::make_pair(
7199  std::make_pair(
7200  nullptr, GA_ATINumeric::cast(attrib)),
7201  GA_ATINumeric::cast(new_attribute)));
7202  return;
7203  }
7204  }
7205 
7206  GA_Attribute *new_attribute;
7207  if (attrib->getScope() != GA_SCOPE_GROUP)
7208  {
7209  new_attribute = output_geo->getAttributes().cloneAttribute(
7210  owner, attrib->getName(),
7212  *attrib, true);
7213  }
7214  else
7215  {
7216  new_attribute = UTverify_cast<GA_ElementGroup*>(
7217  output_geo->getElementGroupTable(owner).newGroup(attrib->getName()));
7218  }
7219 
7220  if (owner != GA_ATTRIB_DETAIL)
7221  curve_attribs.append(std::make_pair(attrib, new_attribute));
7222  else
7223  {
7224  // Just copy detail attribute values
7225  new_attribute->copy(GA_DETAIL_OFFSET, *attrib, GA_DETAIL_OFFSET);
7226  }
7227  };
7228  for (int owneri = 0; owneri < GA_ATTRIB_OWNER_N; ++owneri)
7229  {
7230  curve_input->getAttributes().getDict(GA_AttributeOwner(owneri))
7231  .forEachAttribute(clone_attrib_functor);
7232  }
7233  }
7234 }
7235 
7236 static void
7237 sopBoundingTube(
7238  const GEO_Detail *cross_section_input,
7239  GA_Offset primoff,
7240  double &radius,
7241  UT_Vector3D &centre,
7242  UT_Vector3D *out_normal)
7243 {
7244  const GA_OffsetListRef vertices = cross_section_input->getPrimitiveVertexList(primoff);
7245  const exint n = vertices.size();
7246 
7247  if (n <= 1)
7248  {
7249  radius = 0;
7250  centre = UT_Vector3D(0,0,0);
7251  return;
7252  }
7253 
7254  const GA_ROHandleV3D pos(cross_section_input->getP());
7255 
7256  UT_Vector3D p0 = pos.get(cross_section_input->vertexPoint(vertices(0)));
7257  UT_Vector3D p1 = pos.get(cross_section_input->vertexPoint(vertices(1)));
7258  if (n == 2)
7259  {
7260  radius = 0.5*p0.distance(p1);
7261  centre = 0.5*(p0+p1);
7262  if (out_normal)
7263  {
7264  // out_normal starts as effective curve edge direction.
7265  // Subtract component parallel to p1-p0
7266  UT_Vector3D diff = p1-p0;
7267  double length = diff.normalize();
7268  if (length != 0)
7269  {
7270  UT_Vector3D new_normal = *out_normal - dot(*out_normal, diff)*diff;
7271  length = new_normal.normalize();
7272  if (length != 0)
7273  *out_normal = new_normal;
7274  }
7275  }
7276  return;
7277  }
7278 
7279  // Check if all points approximately in a straight line segment.
7280  // First, compute average position of points.
7281  UT_Vector3D pos_sum = p0+p1;
7282  for (exint i = 2; i < n; ++i)
7283  {
7284  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7285  pos_sum += p;
7286  }
7287  const UT_Vector3D pos_avg = pos_sum / n;
7288 
7289  // Compute covariance matrix
7290  UT_Matrix3D covariance_matrix;
7291  covariance_matrix.zero();
7292  UT_Vector3D pdiff = p0 - pos_avg;
7293  covariance_matrix.outerproductUpdate(1.0, pdiff, pdiff);
7294  pdiff = p1 - pos_avg;
7295  covariance_matrix.outerproductUpdate(1.0, pdiff, pdiff);
7296  for (exint i = 2; i < n; ++i)
7297  {
7298  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7299  pdiff = p - pos_avg;
7300  covariance_matrix.outerproductUpdate(1.0, pdiff, pdiff);
7301  }
7302  const double row_length2s[] = {
7303  covariance_matrix[0].length2(),
7304  covariance_matrix[1].length2(),
7305  covariance_matrix[2].length2()
7306  };
7307  const int maxrow = SYSargmax(
7308  row_length2s[0],
7309  row_length2s[1],
7310  row_length2s[2]);
7311  if (row_length2s[maxrow] == 0)
7312  {
7313  // All a single point
7314  centre = pos_avg;
7315  radius = 0;
7316  return;
7317  }
7318 
7319  UT_Vector3D maxrow_vec = covariance_matrix[maxrow];
7320  maxrow_vec.normalize();
7321  UT_Vector3D other_dir0;
7322  other_dir0.arbitraryPerp(maxrow_vec);
7323  UT_Vector3D other_dir1 = cross(maxrow_vec, other_dir0);
7324  double maxrow_vec_eigen2 = (maxrow_vec*covariance_matrix).length2();
7325  double other_dir0_eigen2 = (other_dir0*covariance_matrix).length2();
7326  double other_dir1_eigen2 = (other_dir1*covariance_matrix).length2();
7327  // If max eigenvalue is more than 10000x larger than middle eigenvalue,
7328  // it's effectively a straight line.
7329  if (other_dir0_eigen2+other_dir1_eigen2 < 1e-8*maxrow_vec_eigen2)
7330  {
7331  pdiff = p0 - pos_avg;
7332  double min_from_avg = pdiff.dot(maxrow_vec);
7333  double max_from_avg = min_from_avg;
7334  pdiff = p1 - pos_avg;
7335  double d = pdiff.dot(maxrow_vec);
7336  min_from_avg = SYSmin(min_from_avg, d);
7337  max_from_avg = SYSmax(max_from_avg, d);
7338  for (exint i = 2; i < n; ++i)
7339  {
7340  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7341  pdiff = p - pos_avg;
7342  d = pdiff.dot(maxrow_vec);
7343  min_from_avg = SYSmin(min_from_avg, d);
7344  max_from_avg = SYSmax(max_from_avg, d);
7345  }
7346 
7347  radius = 0.5*(max_from_avg - min_from_avg);
7348  centre = pos_avg + 0.5*(min_from_avg + max_from_avg);
7349  if (out_normal)
7350  {
7351  UT_Vector3D new_normal = *out_normal - dot(*out_normal, maxrow_vec)*maxrow_vec;
7352  double length = new_normal.normalize();
7353  if (length != 0)
7354  *out_normal = new_normal;
7355  }
7356  return;
7357  }
7358 
7359  // Compute normal
7360  UT_Vector3D normal(0,0,0);
7361  UT_Vector3D pprev = p1;
7362  for (exint i = 2; i < n; ++i)
7363  {
7364  UT_Vector3D pnext = pos.get(cross_section_input->vertexPoint(vertices(i)));
7365  normal += cross(pprev-p0, pnext-p0);
7366  pprev = pnext;
7367  }
7368  normal.normalize();
7369 
7370  if (normal.length2() == 0)
7371  {
7372  // No reliable normal, so use point centre and radius.
7373  centre = pos_avg;
7374 
7375  double max_distance2 = distance2(centre, p0);
7376  for (exint i = 1; i < n; ++i)
7377  {
7378  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7379  max_distance2 = SYSmax(max_distance2, distance2(centre, p));
7380  }
7381  radius = SYSsqrt(max_distance2);
7382  return;
7383  }
7384 
7385  // Compute min & max along direction of normal
7386  double min_normal_dot = 0;
7387  double max_normal_dot = 0;
7388  for (exint i = 1; i < n; ++i)
7389  {
7390  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7391  double dot = (p-p0).dot(normal);
7392  min_normal_dot = SYSmin(dot, min_normal_dot);
7393  max_normal_dot = SYSmax(dot, max_normal_dot);
7394  }
7395 
7396  // Take middle of min and max along normal as the centre for that axis
7397  double mid_normal_dot = 0.5*(min_normal_dot+max_normal_dot);
7398 
7399  // Create arbitrary frame perpendicular to normal
7400  UT_Vector3D tangent0;
7401  UT_Vector3D tangent1;
7402  tangent0.arbitraryPerp(normal);
7403  tangent1 = cross(normal, tangent0);
7404 
7405  // Compute minimum bounding circle in perpendicular frame
7406  UT_Array<UT_Vector2D> pos2d;
7407  pos2d.setSizeNoInit(n);
7408  pos2d[0] = UT_Vector2D(0,0);
7409  for (exint i = 1; i < n; ++i)
7410  {
7411  UT_Vector3D p = pos.get(cross_section_input->vertexPoint(vertices(i)));
7412  UT_Vector3D diff = p-p0;
7413  pos2d[i] = UT_Vector2D(diff.dot(tangent0), diff.dot(tangent1));
7414  }
7415  UT_Vector2D centre2d;
7416  double radius2 = UTboundingCircle(pos2d, &centre2d);
7417  radius = SYSsqrt(radius2);
7418 
7419  // Take circle centre as centre for remaining directions
7420  // and circle radius as the cross section radius
7421  centre = p0 + (normal*mid_normal_dot + tangent0*centre2d[0] + tangent1*centre2d[1]);
7422  if (out_normal)
7423  {
7424  // Ensure that the direction is consistent with the input direction,
7425  // since the direction doesn't need to be consistent with the
7426  // cross section winding.
7427  if (out_normal->dot(normal) < 0)
7428  *out_normal = -normal;
7429  else
7430  *out_normal = normal;
7431  }
7432 }
7433 
7434 static UT_Matrix4D
7435 sopPrescaleTranslate(
7436  const double scale,
7437  const UT_Vector3D &centre,
7438  const UT_Vector3D ztranslate,
7439  const UT_Matrix4D &transform0)
7440 {
7441  // Adjustment for scaling around the centre, instead of around the origin.
7442  UT_Vector3D pret = (1.0-scale)*centre;
7443  //pret[2] += ztranslate;
7444  pret += ztranslate;
7445 
7446  UT_Vector3D t(
7447  pret[0]*transform0[0][0] + pret[1]*transform0[1][0] + pret[2]*transform0[2][0] + transform0[3][0],
7448  pret[0]*transform0[0][1] + pret[1]*transform0[1][1] + pret[2]*transform0[2][1] + transform0[3][1],
7449  pret[0]*transform0[0][2] + pret[1]*transform0[1][2] + pret[2]*transform0[2][2] + transform0[3][2]
7450  );
7451 
7452  return UT_Matrix4D(
7453  scale*transform0[0][0], scale*transform0[0][1], scale*transform0[0][2], scale*transform0[0][3],
7454  scale*transform0[1][0], scale*transform0[1][1], scale*transform0[1][2], scale*transform0[1][3],
7455  scale*transform0[2][0], scale*transform0[2][1], scale*transform0[2][2], scale*transform0[2][3],
7456  t[0], t[1], t[2], 1.0
7457  );
7458 }
7459 
7460 static UT_Matrix4D
7461 sopEndCapTransform(
7462  const exint capi,
7463  const exint cap_divisions,
7464  const UT_Vector3D &centre,
7465  UT_Vector3D scaled_z_dir,
7466  double roundness,
7467  const UT_Matrix4D &transform0)
7468 {
7469  // zscale is negated, because the first end cap goes backward
7470  // from the first cross section.
7471  scaled_z_dir = -scaled_z_dir;
7472 
7473  double scale;
7474  UT_Vector3D ztranslate;
7475  if (capi == 0)
7476  {
7477  // The pole
7478  // NOTE: scale must be exactly zero here, since code checks for zero
7479  // to identify special case for inversion.
7480  scale = 0;
7481  ztranslate = scaled_z_dir;
7482  }
7483  else
7484  {
7485  const double scale_portion = double(capi)/double(cap_divisions);
7486  const double ztranslate_portion = 1.0 - scale_portion;
7487 
7488  // Round portion:
7489  // Scale by cos((pi/2)*(i-n)/n) (for first cap) about the centre
7490  // and translate along the z axis by zscale*sin((pi/2)*(i-n)/n),
7491  // as a pretransform on the cross section at each end.
7492  const double theta = M_PI_2*ztranslate_portion;
7493  const double scale_round = SYScos(theta);
7494  const double ztranslate_round = SYSsin(theta);
7495 
7496  // Pointed portion:
7497  // Scale by (i/n) (for first cap) about the centre
7498  // as a pretransform on the cross section at each end.
7499  // Pretranslate along the z axis by zscale*(i-n)/n,
7500  // for a pointed shape.
7501  const double scale_pointed = scale_portion;
7502  const double ztranslate_pointed = ztranslate_portion;
7503 
7504  // Interpolate from pointed to round
7505  double ztranslate_scalar;
7506  if (roundness > 0)
7507  {
7508  scale = SYSlerp(scale_pointed, scale_round, roundness);
7509  ztranslate_scalar = SYSlerp(ztranslate_pointed, ztranslate_round, roundness);
7510  }
7511  else if (roundness == 0)
7512  {
7513  scale = scale_pointed;
7514  ztranslate_scalar = ztranslate_pointed;
7515  }
7516  else
7517  {
7518  // Lerp toward the inverted round point, which is
7519  // not quite the same as the round point reflected through
7520  // the pointed point, even though the circle reflected through
7521  // the straight line is the inverted circle, due to the
7522  // uniform speed along the line and circle not matching that
7523  // the circle projected onto the line has nonuniform speed.
7524  scale = SYSlerp(scale_pointed, 1.0-ztranslate_round, -roundness);
7525  ztranslate_scalar = SYSlerp(ztranslate_pointed, 1.0-scale_round, -roundness);
7526  }
7527  ztranslate = ztranslate_scalar*scaled_z_dir;
7528  }
7529  return sopPrescaleTranslate(scale, centre, ztranslate, transform0);
7530 }
7531 
7532 static void
7533 sopAllEndCapTransforms(
7534  const sop_SweepGrid &grid,
7535  const SOP_SweepHDKParms &sopparms,
7536  const GEO_Detail *cross_section_input,
7537  const UT_Matrix4D &transform0,
7538  const UT_Matrix4D &transform1,
7539  const exint num_vertices,
7540  UT_Matrix3D *grid_matrix3ds,
7541  UT_Vector3D *grid_translate3ds,
7542  bool use_cross_section_normal_for_z)
7543 {
7544  const exint cap_divisions = sopparms.getCapDivs();
7545 
7546  // Compute normals of the end cross sections.
7547  // Compute minimum bounding circles projected into the axis perpendicular to the normals.
7548  // Use circle radii and centres to form end cap scale pivots and translations.
7549  double radii[2];
7550  UT_Vector3D centres[2];
7551  UT_Vector3D normals[2] = {
7552  UT_Vector3D(0,0,1),
7553  UT_Vector3D(0,0,1)
7554  };
7555  if (grid.mySingleCrossSection && !GAisValid(grid.myCrossSectionPrimOff))
7556  {
7557  // NOTE: Don't use sopparms.getRadius(), since that's already integrated into the transforms.
7558  radii[0] = 1;
7559  radii[1] = 1;
7560  centres[0] = UT_Vector3D(0,0,0);
7561  centres[1] = UT_Vector3D(0,0,0);
7562  }
7563  else if (grid.mySingleCrossSection)
7564  {
7565  GA_Offset primoff = GA_Offset(grid.myCrossSectionPrimOff);
7566 
7567  double radius;
7568  UT_Vector3D centre;
7569  UT_Vector3D normal;
7570  sopBoundingTube(cross_section_input, primoff, radius, centre,
7571  use_cross_section_normal_for_z ? &normal : nullptr);
7572  radii[0] = radius;
7573  radii[1] = radius;
7574  centres[0] = centre;
7575  centres[1] = centre;
7576  if (use_cross_section_normal_for_z)
7577  {
7578  normals[0] = normal;
7579  normals[1] = normal;
7580  }
7581  }
7582  else
7583  {
7584  GA_Offset primoff0 = (*grid.myCrossSectionPrimOffs)[0];
7585  GA_Offset primoff1 = grid.myCrossSectionPrimOffs->last();
7586 
7587  if (use_cross_section_normal_for_z && grid.myCrossSectionPrimOffs->size() >= 2*cap_divisions+1)
7588  {
7589  double radius;
7590  UT_Vector3D centreA;
7591  UT_Vector3D centreB;
7592  sopBoundingTube(cross_section_input, primoff0, radius, centreA, nullptr);
7593  sopBoundingTube(cross_section_input, (*grid.myCrossSectionPrimOffs)[cap_divisions+1], radius, centreB, nullptr);
7594  normals[0] = centreA-centreB;
7595  normals[0].normalize();
7596  sopBoundingTube(cross_section_input, primoff1, radius, centreA, nullptr);
7597  sopBoundingTube(cross_section_input, (*grid.myCrossSectionPrimOffs)[grid.myCrossSectionPrimOffs->size()-2-cap_divisions], radius, centreB, nullptr);
7598  normals[1] = centreB-centreA;
7599  normals[1].normalize();
7600  }
7601  sopBoundingTube(cross_section_input, primoff0, radii[0], centres[0],
7602  use_cross_section_normal_for_z ? &normals[0] : nullptr);
7603  sopBoundingTube(cross_section_input, primoff1, radii[1], centres[1],
7604  use_cross_section_normal_for_z ? &normals[1] : nullptr);
7605  }
7606  if (use_cross_section_normal_for_z)
7607  {
7608  normals[0].negate();
7609  normals[1].negate();
7610  }
7611 
7612  // FIXME: For inverses on the final (singular) transform, specify an orthogonal matrix, instead of a NaN matrix!!!
7613  // TODO: Maybe normals should be handled differently for end caps, since translating and uniform scaling won't
7614  // change them at all, and for regular surfaces at least, they should match the surface normal.
7615 
7616  const double cap_scale = sopparms.getCapScale();
7617  const double cap_roundness = sopparms.getCapRoundness();
7618 
7619  // First cap
7620  for (exint i = 0; i < cap_divisions; ++i)
7621  {
7622  const exint row = i;
7623  const UT_Matrix4D transform = sopEndCapTransform(
7624  i, cap_divisions,
7625  centres[0], (cap_scale*radii[0])*normals[0],
7626  cap_roundness, transform0);
7627 
7628  grid_matrix3ds[row] = UT_Matrix3D(transform);
7629  transform.getTranslates(grid_translate3ds[row]);
7630  }
7631 
7632  // Last cap
7633  for (exint i = 0; i < cap_divisions; ++i)
7634  {
7635  // Negate the radius, so that translation is in the positive
7636  // z direction, instead of the negative z direction.
7637  const exint row = cap_divisions+num_vertices+i;
7638  const UT_Matrix4D transform = sopEndCapTransform(
7639  cap_divisions-1-i, cap_divisions,
7640  centres[1], (-cap_scale*radii[1])*normals[1],
7641  cap_roundness, transform1);
7642 
7643  grid_matrix3ds[row] = UT_Matrix3D(transform);
7644  transform.getTranslates(grid_translate3ds[row]);
7645  }
7646 }
7647 
7648 /// This is the function that does the actual work.
7649 void SOP_SweepHDKVerb::cook(const CookParms &cookparms) const
7650 {
7651  // Overall behaviour:
7652  // - Handle invalid input
7653  // - Parse groups
7654  // - Delete any output attributes that should no longer exist or
7655  // that mismatch what they should be.
7656  // - Determine whether output topology needs to be different from previous cook
7657  // - If so,
7658  // - Compute a simple representation of the topology to cache
7659  // - Construct primitives
7660  // - Bump topology and primitive list data IDs
7661  // - Compute input backbone curve UVs, if needed
7662  // - Compute transforms for each vertex
7663  // - Set up missing output attributes from source and target attributes
7664  // - For each output attribute,
7665  // - For each curve,
7666  // -
7667 
7668  using namespace GU_Copy;
7669 
7670  ///////////////////////////////////////////////////////////////////
7671  // //
7672  // 0. PREP //
7673  // //
7674  ///////////////////////////////////////////////////////////////////
7675 
7676  // This gives easy access to all of the current parameter values
7677  auto &&sopparms = cookparms.parms<SOP_SweepHDKParms>();
7678  auto sopcache = (SOP_SweepHDKCache *)cookparms.cache();
7679 
7680  // The output detail
7681  GU_Detail *output_geo = cookparms.gdh().gdpNC();
7682 
7683  // Only use the cross_section_input if
7684  const GU_Detail *const cross_section_input =
7685  (sopparms.getSurfaceShape() == SurfaceShape::INPUT)
7686  ? cookparms.inputGeo(theCrossSectionInput)
7687  : nullptr;
7688 
7689  const GU_Detail *const curve_input = cookparms.inputGeo(theCurveInput);
7690 
7691  if (!cookparms.inputGeo(theCrossSectionInput) && !curve_input)
7692  {
7693  cookparms.sopAddError(SOP_MESSAGE, "At least one input is required.");
7694  output_geo->clearAndDestroy();
7695  return;
7696  }
7697 
7698  if (!cross_section_input && (sopparms.getSurfaceShape() == SurfaceShape::INPUT))
7699  {
7700  cookparms.sopAddError(SOP_MESSAGE, "No cross section geometry provided.");
7701  output_geo->clearAndDestroy();
7702  return;
7703  }
7704  if (!curve_input && (sopparms.getSurfaceShape() != SurfaceShape::INPUT))
7705  {
7706  cookparms.sopAddError(SOP_MESSAGE, "No backbone curve geometry provided.");
7707  output_geo->clearAndDestroy();
7708  return;
7709  }
7710 
7711  // GOP_Manager will own any temporary groups it creates
7712  // and automatically destroy them when it goes out of scope.
7713  GOP_Manager group_manager;
7714  const GA_PrimitiveGroup *curve_group = nullptr;
7715  GA_OffsetList curve_group_list;
7716  if (curve_input)
7717  {
7718  if (sopparms.getCurveGroup().isstring())
7719  {
7720  curve_group = group_manager.parsePrimitiveGroups(
7721  sopparms.getCurveGroup(),
7722  GOP_Manager::GroupCreator(curve_input));
7723  }
7724  if (curve_group == nullptr)
7725  {
7726  curve_group_list = curve_input->getPrimitiveMap().getOffsetFromIndexList();
7727  }
7728  else
7729  {
7730  GA_Offset start;
7731  GA_Offset end;
7732  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.fullBlockAdvance(start, end); )
7733  {
7734  curve_group_list.setTrivialRange(curve_group_list.size(), start, end-start);
7735  }
7736  }
7737  }
7738  const GA_PrimitiveGroup *cross_section_group = nullptr;
7739  GA_OffsetList cross_section_group_list;
7740  if (cross_section_input)
7741  {
7742  if (sopparms.getCrossSectionGroup().isstring())
7743  {
7744  cross_section_group = group_manager.parsePrimitiveGroups(
7745  sopparms.getCrossSectionGroup(),
7746  GOP_Manager::GroupCreator(cross_section_input));
7747  }
7748  if (cross_section_group == nullptr)
7749  {
7750  cross_section_group_list = cross_section_input->getPrimitiveMap().getOffsetFromIndexList();
7751  }
7752  else
7753  {
7754  GA_Offset start;
7755  GA_Offset end;
7756  for (GA_Iterator it(cross_section_input->getPrimitiveRange(cross_section_group)); it.fullBlockAdvance(start, end); )
7757  {
7758  cross_section_group_list.setTrivialRange(cross_section_group_list.size(), start, end-start);
7759  }
7760  }
7761  }
7762 
7763  //GUremoveUnnecessaryAttribs(output_geo, cross_section_input, curve_input, sopcache, target_attrib_info, target_group_info);
7764 
7765  ///////////////////////////////////////////////////////////////////
7766  // //
7767  // 1. SURFACE TOPOLOGY //
7768  // //
7769  ///////////////////////////////////////////////////////////////////
7770 
7771  CopyOrder copy_order = (cross_section_input && curve_input) ? sopparms.getCopyOrder() : CopyOrder::CYCLEVTX;
7772  bool same_cross_section_data = (sopcache->myPrevCopyOrder == copy_order);
7773  if (copy_order == CopyOrder::ATTRIB)
7774  {
7775  // NOTE: If the attribute matching doesn't work out, this switches copy_order to CopyOrder::CYCLEVTX.
7776  setupCrossSectionAttribs(
7777  sopcache, cookparms, sopparms,
7778  same_cross_section_data, copy_order,
7779  cross_section_input, curve_input,
7780  cross_section_group);
7781  }
7782 
7783  // Remove unnecessary attributes here
7784  // Add necessary attributes after createGrids, since it calls output_geo->clearAndDestroy()
7785 
7786  removeUnnecessarySweepAttribs(output_geo);
7787 
7788  // We might not have to rebuild the output surface if we still have the previous
7789  // cook's output surface and it would be regenerated with the same topology.
7790  // It's a very common case that the surface doesn't need to be rebuilt,
7791  // but a lot of things affect the output topology, so it's difficult to do,
7792  // but it could be a big payoff for the common case.
7793  bool need_to_build_surface = true;
7794 
7795  const bool same_output_geo_as_last_cook =
7796  (output_geo->getUniqueId() == sopcache->myPrevOutputDetailID);
7797  int64 new_curve_prim_list_data_id = (curve_input ? curve_input->getPrimitiveList().getDataId() : -1);
7798  int64 new_curve_topology_data_id = (curve_input ? curve_input->getTopology().getDataId() : -1);
7799  if (same_output_geo_as_last_cook)
7800  {
7801  const bool same_input_curves =
7802  (new_curve_prim_list_data_id == sopcache->myPrevCurvePrimListDataID) &&
7803  (new_curve_topology_data_id == sopcache->myPrevCurveTopologyDataID) &&
7804  (curve_group_list.size() == sopcache->myPrevCurveGroup.size()) &&
7805  (curve_group_list.isEqual(sopcache->myPrevCurveGroup, 0, curve_group_list.size()));
7806  // NOTE: Reversing cross sections does not change the topology of the output grids.
7807  bool same_surface_parms =
7808  (sopparms.getSurfaceType() == sopcache->myPrevSurfaceType) &&
7809  (sopparms.getUnrollClosedRowCol() == sopcache->myPrevUnrollClosedRowCol) &&
7810  (sopparms.getPrimType() == sopcache->myPrevPrimType) &&
7811  (sopparms.getEndCapType() == sopcache->myPrevEndCapType) &&
7812  (sopparms.getCapDivs() == sopcache->myPrevEndCapDivs) &&
7813  (sopparms.getTriangularPoles() == sopcache->myPrevTriangularPoles) &&
7814  (sopparms.getSwapRowCol() == sopcache->myPrevSwapRowCol);
7815  if (same_surface_parms)
7816  {
7817  if (cross_section_input && sopcache->myPrevSurfaceShape == SurfaceShape::INPUT)
7818  {
7819  same_surface_parms =
7820  (sopcache->myPrevSurfaceShape == SurfaceShape::INPUT) &&
7821  (sopcache->myPrevCloseIfNoCurveInput == sopparms.getCloseIfNoCurveInput()) &&
7822  same_cross_section_data &&
7823  (cross_section_input->getPrimitiveList().getDataId() == sopcache->myPrevCrossSectionPrimListDataID) &&
7824  (cross_section_input->getTopology().getDataId() == sopcache->myPrevCrossSectionTopologyDataID) &&
7825  (cross_section_group_list.size() == sopcache->myPrevCrossSectionGroup.size()) &&
7826  (cross_section_group_list.isEqual(sopcache->myPrevCrossSectionGroup, 0, cross_section_group_list.size()));
7827  }
7828  else
7829  {
7830  same_surface_parms =
7831  (sopparms.getSurfaceShape() == sopcache->myPrevSurfaceShape) &&
7832  (sopparms.getCols() == sopcache->myPrevCols);
7833  }
7834  }
7835 
7836  // If the output is cached from the last cook, and the input curve
7837  // topology is the same, and the output surface type is the same,
7838  // we only need to move point positions.
7839  if (same_input_curves && same_surface_parms)
7840  need_to_build_surface = false;
7841  }
7842 
7843  sopcache->myPrevOutputDetailID = output_geo->getUniqueId();
7844  sopcache->myPrevCurvePrimListDataID = new_curve_prim_list_data_id;
7845  sopcache->myPrevCurveTopologyDataID = new_curve_topology_data_id;
7846  sopcache->myPrevCurveGroup = std::move(curve_group_list);
7847  sopcache->myPrevSurfaceType = sopparms.getSurfaceType();
7848  sopcache->myPrevUnrollClosedRowCol = sopparms.getUnrollClosedRowCol();
7849  sopcache->myPrevPrimType = sopparms.getPrimType();
7850  sopcache->myPrevCloseIfNoCurveInput = sopparms.getCloseIfNoCurveInput();
7851  sopcache->myPrevSurfaceShape = sopparms.getSurfaceShape();
7852  sopcache->myPrevCols = (cross_section_input && sopcache->myPrevSurfaceShape == SurfaceShape::INPUT) ? -1 : sopparms.getCols();
7853  sopcache->myPrevEndCapType = sopparms.getEndCapType();
7854  sopcache->myPrevEndCapDivs = sopparms.getCapDivs();
7855  sopcache->myPrevTriangularPoles = sopparms.getTriangularPoles();
7856  sopcache->myPrevSwapRowCol = sopparms.getSwapRowCol();
7857  sopcache->myPrevCrossSectionPrimListDataID = cross_section_input ? cross_section_input->getPrimitiveList().getDataId() : -1;
7858  sopcache->myPrevCrossSectionTopologyDataID = cross_section_input ? cross_section_input->getTopology().getDataId() : -1;
7859  sopcache->myPrevCrossSectionGroup = std::move(cross_section_group_list);
7860  sopcache->myPrevCopyOrder = copy_order;
7861 
7862  UT_Array<sop_SweepGrid> &grids = sopcache->myGrids;
7863  const CrossSectionAttribMatchData *copy_order_attrib_data = (copy_order == CopyOrder::ATTRIB) ? &sopcache->myCrossSectionAttribMatchData : nullptr;
7864 
7865  GEO_SurfaceType surface_type = sweepSurfaceToGeoSurface(sopparms.getSurfaceType());
7866  bool output_points_only = (sopparms.getSurfaceType() == SurfaceType::POINTS);
7867  exint cap_divisions =
7868  (sopparms.getEndCapType() == EndCapType::NONE || sopparms.getEndCapType() == EndCapType::SINGLE || sopparms.getEndCapType() == EndCapType::SIDESINGLE) ? 0 : sopparms.getCapDivs();
7869 
7870  if (need_to_build_surface)
7871  {
7872  bool finished = createGrids(
7873  cross_section_input, cross_section_group,
7874  sopparms.getSurfaceShape(),
7875  sopparms.getCols() * ((sopparms.getSurfaceShape() == SurfaceShape::SQUARE) ? 4 : 1),
7876  copy_order, copy_order_attrib_data,
7877  curve_input, curve_group,
7878  sopparms.getCloseIfNoCurveInput(),
7879  surface_type, output_points_only, sopparms.getUnrollClosedRowCol(),
7880  sopParmToPrimType(sopparms.getPrimType()),
7881  sopparms.getEndCapType(), cap_divisions,
7882  sopparms.getTriangularPoles(),
7883  sopparms.getSwapRowCol(),
7884  output_geo, grids);
7885 
7886  if (!finished)
7887  return; // Interrupted, so return.
7888  }
7889 
7890  // We compute surface point positions, uvs, metadata, and normals,
7891  // and copy other attributes after computing transforms.
7892  GA_AttributeUPtr detached_cross_section_uv;
7893 
7897  // If non-negative, this is the index into uv_attribs containing "uv" itself.
7898  exint uv_attrib_index = -1;
7899 
7900  setupSweepTransferAttribs(output_geo,
7901  cross_section_input, sopparms.getAttribsFromCrossSection(),
7902  curve_input, sopparms.getAttribsFromBackbone(),
7903  cross_section_attribs,
7904  curve_attribs,
7905  uv_attribs,
7906  uv_attrib_index,
7907  sopparms.getComputeUVs(),
7908  sopparms.getComputeUVs() && sopparms.getOverrideExistingUVs());
7909 
7910  ///////////////////////////////////////////////////////////////////
7911  // //
7912  // 2. UV //
7913  // //
7914  ///////////////////////////////////////////////////////////////////
7915 
7916  // Optionally compute UVs.
7917  // If we create a detached uv attribute, it will need to be deleted before we exit.
7918  //GA_AttributeUPtr curve_uv_deleter;
7919  UT_Array<double> missing_input_us;
7920  if (sopparms.getComputeUVs())
7921  {
7922  if (cross_section_input)
7923  {
7924  GA_ROHandleV3 input_uv_attrib;
7925  if (!sopparms.getOverrideExistingUVs())
7926  {
7927  input_uv_attrib.bind(cross_section_input->findAttribute(GA_ATTRIB_VERTEX, GA_Names::uv));
7928  if (!input_uv_attrib.isValid())
7929  input_uv_attrib.bind(cross_section_input->findAttribute(GA_ATTRIB_POINT, GA_Names::uv));
7930  }
7931 
7932  GA_RWHandleV3 rw_uv_attrib;
7933  if (!input_uv_attrib.isValid() || sopparms.getOverrideExistingUVs())
7934  {
7935  // We can't modify the input detail to add an attribute, so it must
7936  // be a "detached" attribute that we're responsible for deleting.
7937  detached_cross_section_uv =
7938  cross_section_input->createDetachedTupleAttribute(
7940  rw_uv_attrib.bind(detached_cross_section_uv.get());
7941  }
7942 
7943  if (rw_uv_attrib.isValid())
7944  {
7945  UT_ASSERT(rw_uv_attrib->getOwner() == GA_ATTRIB_VERTEX);
7946  computeCurveUVs(cross_section_input, cross_section_group, rw_uv_attrib, sopparms.getLengthWeightedUVs(), sopparms.getReverseCrossSections());
7947 
7948  if (uv_attrib_index >= 0)
7949  {
7950  UT_ASSERT(uv_attribs[uv_attrib_index].first.first == nullptr);
7951  uv_attribs[uv_attrib_index].first.first = rw_uv_attrib.getAttribute();
7952  }
7953  else
7954  {
7955  GA_AttributeOwner output_uv_owner = output_points_only ? GA_ATTRIB_POINT : GA_ATTRIB_VERTEX;
7956  GA_ATINumeric *output_attrib = UTverify_cast<GA_ATINumeric*>(output_geo->createTupleAttribute(output_uv_owner, GA_Names::uv, GA_STORE_REAL32, 3));
7957  output_attrib->setTypeInfo(GA_TYPE_TEXTURE_COORD);
7958  uv_attrib_index = uv_attribs.size();
7959  uv_attribs.append(std::make_pair(std::make_pair(rw_uv_attrib.getAttribute(), (const GA_ATINumeric*)nullptr), output_attrib));
7960  }
7961  }
7962  }
7963  else
7964  {
7965  // Compute automatic cross section UVs
7966  exint nedgecols = sopparms.getCols();
7967  if (sopparms.getSurfaceShape() == SurfaceShape::SQUARE)
7968  nedgecols *= 4;
7969  bool closed = (sopparms.getSurfaceShape() == SurfaceShape::TUBE || sopparms.getSurfaceShape() == SurfaceShape::SQUARE);
7970  exint nvertices = nedgecols + !closed;
7971  missing_input_us.setSizeNoInit(nvertices);
7972  for (exint i = 0; i < nvertices; ++i)
7973  {
7974  missing_input_us[i] = double(i)/double(nedgecols);
7975  }
7976 
7977  if (uv_attrib_index < 0)
7978  {
7979  // No UV attribute was added before, so we need to add one here.
7980  // Curve UVs will be generated later if both source attributes are null.
7981  GA_AttributeOwner output_uv_owner = output_points_only ? GA_ATTRIB_POINT : GA_ATTRIB_VERTEX;
7982  GA_ATINumeric *output_attrib = UTverify_cast<GA_ATINumeric*>(output_geo->createTupleAttribute(output_uv_owner, GA_Names::uv, GA_STORE_REAL32, 3));
7983  output_attrib->setTypeInfo(GA_TYPE_TEXTURE_COORD);
7984  uv_attrib_index = uv_attribs.size();
7985  uv_attribs.append(std::make_pair(std::make_pair((const GA_ATINumeric*)nullptr, (const GA_ATINumeric*)nullptr), output_attrib));
7986  }
7987  }
7988 
7989  // NOTE: Backbone curve UVs need to be handled differently, since
7990  // need V values for end caps, which don't have their own vertices.
7991 #if 0
7992  if (curve_input)
7993  {
7994  GA_ROHandleV3 input_uv_attrib;
7995  if (!sopparms.getOverrideExistingUVs())
7996  {
7997  input_uv_attrib.bind(curve_input->findAttribute(GA_ATTRIB_VERTEX, GA_Names::uv));
7998  if (!input_uv_attrib.isValid())
7999  input_uv_attrib.bind(curve_input->findAttribute(GA_ATTRIB_POINT, GA_Names::uv));
8000  }
8001 
8002  GA_RWHandleV3 rw_uv_attrib;
8003  if (!input_uv_attrib.isValid() || sopparms.getOverrideExistingUVs())
8004  {
8005  // We can't modify the input detail to add an attribute, so it must
8006  // be a "detached" attribute that we're responsible for deleting.
8007  GA_Attribute *detached_uv_attrib = curve_input->createDetachedTupleAttribute(GA_ATTRIB_VERTEX, GA_STORE_REAL32, 3);
8008  curve_uv_deleter.reset(detached_uv_attrib);
8009  rw_uv_attrib.bind(detached_uv_attrib);
8010  }
8011 
8012  if (rw_uv_attrib.isValid())
8013  {
8014  UT_ASSERT(rw_uv_attrib->getOwner() == GA_ATTRIB_VERTEX);
8015  computeCurveUVs(curve_input, curve_group, rw_uv_attrib, sopparms.getLengthWeightedUVs());
8016 
8017  if (uv_attrib_index >= 0)
8018  {
8019  UT_ASSERT(uv_attribs[uv_attrib_index].first.second == nullptr);
8020  uv_attribs[uv_attrib_index].first.second = rw_uv_attrib.getAttribute();
8021  }
8022  else
8023  {
8024  GA_ATINumeric *output_attrib = UTverify_cast<GA_ATINumeric*>(output_geo->createTupleAttribute(GA_ATTRIB_VERTEX, GA_Names::uv, GA_STORE_REAL32, 3));
8025  output_attrib->setTypeInfo(GA_TYPE_TEXTURE_COORD);
8026  uv_attrib_index = uv_attribs.size();
8027  uv_attribs.append(std::make_pair(std::make_pair((const GA_ATINumeric*)nullptr, rw_uv_attrib.getAttribute()), output_attrib));
8028  }
8029  }
8030  }
8031  else
8032  {
8033  // Compute UVs for the implicit backbone curve when no curve input.
8034  exint num_cross_sections = cross_section_group ? cross_section_group->entries() : cross_section_input->getNumPrimitives();
8035  bool closed = sopparms.getCloseIfNoCurveInput();
8036  exint nedgecols = num_cross_sections - !closed;
8037  missing_input_us.setSizeNoInit(num_cross_sections);
8038  bool length_weighted_uvs = sopparms.getLengthWeightedUVs();
8039  if (length_weighted_uvs)
8040  {
8041  // Approximate distance when weighting UVs by length.
8042  // Compute cross section centroids and distances between them.
8043  GA_Iterator cross_section_it(cross_section_input->getPrimitiveRange(cross_section_group));
8044  GA_ROHandleV3D pos_attrib(cross_section_input->getP());
8045  UT_Vector3D first_centroid;
8046  UT_Vector3D prev_centroid;
8047  exint cross_section_i = 0;
8048  double total_distance = 0;
8049  for (; !cross_section_it.atEnd(); ++cross_section_it)
8050  {
8051  GA_Offset primoff = *cross_section_it;
8052  const GA_OffsetListRef vertices = cross_section_input->getPrimitiveVertexList(primoff);
8053  UT_Vector3D centroid(0,0,0);
8054  exint n = vertices.size();
8055  for (exint i = 0; i < n; ++i)
8056  {
8057  GA_Offset ptoff = cross_section_input->vertexPoint(vertices(i));
8058  centroid += pos_attrib.get(ptoff);
8059  }
8060  if (n != 0)
8061  centroid /= n;
8062 
8063  if (cross_section_i == 0)
8064  {
8065  first_centroid = centroid;
8066  }
8067  else
8068  {
8069  double distance = centroid.distance(prev_centroid);
8070  total_distance += distance;
8071  }
8072  missing_input_us[cross_section_i] = total_distance;
8073  ++cross_section_i;
8074 
8075  prev_centroid = centroid;
8076  }
8077 
8078  if (closed)
8079  {
8080  double distance = first_centroid.distance(prev_centroid);
8081  total_distance += distance;
8082  }
8083 
8084  if (total_distance == 0)
8085  {
8086  // Fall back to uniform UVs if the distance is zero.
8087  length_weighted_uvs = false;
8088  }
8089  else
8090  {
8091  for (exint i = 0, n = missing_input_us.size(); i < n; ++i)
8092  {
8093  missing_input_us[i] /= total_distance;
8094  }
8095  }
8096  }
8097 
8098  // NOTE: Not an "else", since length-weighted UVs case might fall back to uniform UVs.
8099  if (!length_weighted_uvs)
8100  {
8101  // Uniform UVs
8102  for (exint i = 0; i < num_cross_sections; ++i)
8103  {
8104  missing_input_us[i] = double(i)/double(nedgecols);
8105  }
8106  }
8107  }
8108 #endif
8109  }
8110 
8111  const bool computing_uvs = (uv_attrib_index >= 0);
8112 
8113  ///////////////////////////////////////////////////////////////////
8114  // //
8115  // 3. TRANSFORMS //
8116  // //
8117  ///////////////////////////////////////////////////////////////////
8118 
8119  exint total_transforms = 0;
8120  if (curve_input != nullptr)
8121  {
8122  // FIXME: Don't recompute transforms if nothing affecting transforms has changed!!!
8123 
8124  // Compute transforms on the curves in a detached attribute,
8125  // since we won't be keeping it, and the index map won't be changing
8126  // while it's alive, and we can't change curve_input.
8127  GA_AttributeUPtr detached_transform_attrib =
8128  curve_input->createDetachedTupleAttribute(
8130  GA_RWHandleM4D transform_attrib(detached_transform_attrib.get());
8131 
8133 
8134  parms.myTangentType = GU_CurveFrame::TangentType(int(sopparms.getTangentType()));
8135  parms.myExtrapolateEndTangents = sopparms.getExtrapolateEndTangents();
8136  parms.myTransformByInstanceAttribs = sopparms.getTransformByAttribs();
8137 
8138  parms.myRotationOrder = OP_Node::getRotOrder(int(sopparms.getROrd()));
8139 
8140  const bool applypitch = sopparms.getApplyPitch();
8141  const bool applyyaw = sopparms.getApplyYaw();
8142  const bool applyroll = sopparms.getApplyRoll();
8143  const UT_Vector3D angles(
8144  applypitch ? SYSdegToRad(sopparms.getPitch()) : 0.0,
8145  applyyaw ? SYSdegToRad(sopparms.getYaw()) : 0.0,
8146  applyroll ? SYSdegToRad(sopparms.getRoll()) : 0.0);
8147  parms.myAngles = angles;
8148  const UT_Vector3D incangles(
8149  applypitch ? SYSdegToRad(sopparms.getIncPitch()) : 0.0,
8150  applyyaw ? SYSdegToRad(sopparms.getIncYaw()) : 0.0,
8151  applyroll ? SYSdegToRad(sopparms.getIncRoll() + 360.0*sopparms.getFullTwists()) : 0.0);
8152  parms.myIncAngles = incangles;
8153  parms.myIncAnglePer[0] = GU_CurveFrame::RotationPer(int(sopparms.getPitchPer()));
8154  parms.myIncAnglePer[1] = GU_CurveFrame::RotationPer(int(sopparms.getYawPer()));
8155  parms.myIncAnglePer[2] = GU_CurveFrame::RotationPer(int(sopparms.getRollPer()));
8156 
8157  const GA_AttributeOwner search_order[4] = {
8162  };
8163  if (parms.myIncAngles[0] != 0.0 && parms.myIncAnglePer[0] == GU_CurveFrame::RotationPer::ATTRIB)
8164  parms.myRotAttribs[0].bind(curve_input->findAttribute(sopparms.getPitchAttrib(), search_order, 4));
8165  if (parms.myIncAngles[1] != 0.0 && parms.myIncAnglePer[1] == GU_CurveFrame::RotationPer::ATTRIB)
8166  parms.myRotAttribs[1].bind(curve_input->findAttribute(sopparms.getYawAttrib(), search_order, 4));
8167  if (parms.myIncAngles[2] != 0.0 && parms.myIncAnglePer[2] == GU_CurveFrame::RotationPer::ATTRIB)
8168  parms.myRotAttribs[2].bind(curve_input->findAttribute(sopparms.getRollAttrib(), search_order, 4));
8169 
8171 
8172  switch (sopparms.getUpVectorType())
8173  {
8174  case UpVectorType::X: parms.myTargetUpVector = UT_Vector3D(1,0,0); break;
8175  case UpVectorType::NORMAL: SYS_FALLTHROUGH; // Fallback up vector for curve normal case is Y Axis.
8176  case UpVectorType::Y: parms.myTargetUpVector = UT_Vector3D(0,1,0); break;
8177  case UpVectorType::Z: parms.myTargetUpVector = UT_Vector3D(0,0,1); break;
8178  case UpVectorType::ATTRIB:
8179  {
8180  parms.myTargetUpVectorAttrib.bind(curve_input, GA_ATTRIB_PRIMITIVE, sopparms.getUpVectorAttrib());
8181  if (!parms.myTargetUpVectorAttrib.isValid())
8182  {
8183  parms.myTargetUpVectorAttrib.bind(curve_input, GA_ATTRIB_DETAIL, sopparms.getUpVectorAttrib());
8184  }
8185  if (parms.myTargetUpVectorAttrib.isValid() && parms.myTargetUpVectorAttrib->getOwner() == GA_ATTRIB_DETAIL)
8186  {
8187  // If it's a detail attribute, just read the value here.
8188  parms.myTargetUpVector = parms.myTargetUpVectorAttrib.get(GA_DETAIL_OFFSET);
8189  parms.myTargetUpVectorAttrib.clear();
8190  }
8191  else
8192  {
8193  if (!parms.myTargetUpVectorAttrib.isValid())
8194  {
8195  cookparms.sopAddWarning(SOP_MESSAGE, "Target up vector attribute not found; defaulting to y axis.");
8196  }
8197  parms.myTargetUpVector = UT_Vector3D(0,1,0);
8198  }
8199  break;
8200  }
8201  case UpVectorType::CUSTOM: parms.myTargetUpVector = sopparms.getUpVector(); break;
8202  }
8203  parms.myUseCurveNormalAsTargetUp = (sopparms.getUpVectorType() == UpVectorType::NORMAL);
8204  parms.myTargetUpVectorAtStart = sopparms.getUpVectorAtStart();
8205  parms.myContinuousClosedCurves = sopparms.getContinuousClosed();
8206 
8207  if (parms.myTargetUpVectorAtStart && sopparms.getUseEndUpVector())
8208  {
8209  parms.myUseEndTargetUpVector = true;
8210  if (sopparms.getUpVectorType() == UpVectorType::ATTRIB)
8211  {
8212  parms.myEndTargetUpVectorAttrib.bind(curve_input, GA_ATTRIB_PRIMITIVE, sopparms.getEndUpVectorAttrib());
8213  if (!parms.myEndTargetUpVectorAttrib.isValid())
8214  {
8215  parms.myEndTargetUpVectorAttrib.bind(curve_input, GA_ATTRIB_DETAIL, sopparms.getEndUpVectorAttrib());
8216  }
8217  if (parms.myEndTargetUpVectorAttrib.isValid() && parms.myEndTargetUpVectorAttrib->getOwner() == GA_ATTRIB_DETAIL)
8218  {
8219  // If it's a detail attribute, just read the value here.
8220  parms.myEndTargetUpVector = parms.myEndTargetUpVectorAttrib.get(GA_DETAIL_OFFSET);
8221  parms.myEndTargetUpVectorAttrib.clear();
8222  }
8223  else
8224  {
8225  if (!parms.myEndTargetUpVectorAttrib.isValid())
8226  {
8227  cookparms.sopAddWarning(SOP_MESSAGE, "End target up vector attribute not found; defaulting to no end target up vector.");
8228  parms.myUseEndTargetUpVector = false;
8229  }
8230  parms.myEndTargetUpVector = UT_Vector3D(0,1,0);
8231  }
8232  }
8233  else if (sopparms.getUpVectorType() == UpVectorType::CUSTOM)
8234  {
8235  parms.myEndTargetUpVector = sopparms.getEndUpVector();
8236  }
8237  else
8238  {
8239  parms.myEndTargetUpVector = parms.myTargetUpVector;
8240  }
8241  }
8242 
8243  parms.myNormalizeScales = false;//sopparms.getNormalize();
8244  //parms.myMakeOrthogonal = sopparms.getOrthogonal();
8245  if (sopparms.getSurfaceShape() == SurfaceShape::INPUT)
8246  parms.myUniformScale = sopparms.getScale();
8247  else if (sopparms.getSurfaceShape() == SurfaceShape::TUBE)
8248  parms.myUniformScale = sopparms.getRadius();
8249  else
8250  parms.myUniformScale = 0.5*sopparms.getWidth();
8251 
8252  if(sopparms.getApplyScale())
8253  parms.myScaleRamp = sopparms.getScaleRamp().get();
8254 
8255  parms.myStretchAroundTurns = sopparms.getStretchAroundTurns();
8256  parms.myMaxStretchScale = sopparms.getMaxStretchAroundTurns();
8257 
8258  computeCurveTransforms(curve_input, curve_group, transform_attrib, parms);
8259 
8260  // Compute grid_transform_starts
8261  total_transforms = 0;
8262  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8263  {
8264  const sop_SweepGrid &grid = grids[gridi];
8265  total_transforms += grid.myCurveNEdges + !grid.myCurveClosed;
8266  }
8267  sopcache->myGridTransformStarts.reset(new exint[grids.size()]);
8268  sopcache->clearTransformArrays();
8269  sopcache->myTransformMatrices3D.reset(new UT_Matrix3D[total_transforms]);
8270  sopcache->myTransformTranslates3D.reset(new UT_Vector3D[total_transforms]);
8271  sopcache->myTransformCacheSize = total_transforms;
8272  exint *grid_transform_starts = sopcache->myGridTransformStarts.get();
8273  UT_Matrix3D *transform_matrix3ds = sopcache->myTransformMatrices3D.get();
8274  UT_Vector3D *transform_translate3ds = sopcache->myTransformTranslates3D.get();
8275  exint current_transform_start = 0;
8276  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8277  {
8278  const sop_SweepGrid &grid = grids[gridi];
8279  grid_transform_starts[gridi] = current_transform_start;
8280  current_transform_start += grid.myCurveNEdges + !grid.myCurveClosed;
8281  }
8282 
8283  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8284  {
8285  const sop_SweepGrid &grid = grids[gridi];
8286 
8287  const GA_Offset curve_primoff = grid.myCurvePrimOff;
8288  const GA_OffsetListRef vertices = curve_input->getPrimitiveVertexList(curve_primoff);
8289  // NOTE: Don't use grid.myCurveUnrolled, since if "Unroll Closed Row or Column Curves" is on,
8290  // that doesn't reflect whether we need to skip computing a transform for
8291  // the final input vertex.
8292  bool curve_unrolled = !vertices.getExtraFlag() &&
8293  (curve_input->vertexPoint(vertices(0)) == curve_input->vertexPoint(vertices.last()));
8294  exint num_vertices = vertices.size() - exint(curve_unrolled);
8295  if (vertices.size() == 1)
8296  {
8297  // Special case for single vertex, else it'll effectively
8298  // get incorrectly treated as having zero vertices.
8299  curve_unrolled = false;
8300  num_vertices = 1;
8301  }
8302  // FIXME: This assert condition is wrong for CopyOrder::ALL!!!
8303  //UT_ASSERT(num_vertices == grid.myCurveNEdges + !grid.myCurveClosed - (grid.myVEndPoles ? 2*cap_divisions : 0));
8304 
8305  const exint grid_transform_start = grid_transform_starts[gridi];
8306  UT_Matrix3D *grid_matrix3ds = transform_matrix3ds + grid_transform_start;
8307  UT_Vector3D *grid_translate3ds = transform_translate3ds + grid_transform_start;
8308 
8309  // Fill in the transform cache from the vertex matrix4 attribute.
8310  const exint row_start = grid.myVEndPoles ? cap_divisions : 0;
8311  for (exint i = 0; i < num_vertices; ++i)
8312  {
8313  const UT_Matrix4D transform = transform_attrib.get(vertices(i));
8314  UT_ASSERT(grid_transform_start+row_start+i < total_transforms);
8315  grid_matrix3ds[row_start+i] = UT_Matrix3D(transform);
8316  transform.getTranslates(grid_translate3ds[row_start+i]);
8317  }
8318 
8319  if (grid.myVEndPoles)
8320  {
8321  const GA_Offset vtxoff0 = vertices(0);
8322  const GA_Offset vtxoff1 = vertices.last();
8323  const UT_Matrix4D transform0 = transform_attrib.get(vtxoff0);
8324  const UT_Matrix4D transform1 = transform_attrib.get(vtxoff1);
8325 
8326  sopAllEndCapTransforms(
8327  grid, sopparms, cross_section_input,
8328  transform0, transform1, num_vertices,
8329  grid_matrix3ds, grid_translate3ds,
8330  false);
8331  }
8332  }
8333  }
8334  else if (grids.size() == 1 && grids[0].myVEndPoles)
8335  {
8336  // Single grid skinning the cross section input and end caps
8337  // The end caps need transforms; identity for the rest.
8338  const sop_SweepGrid &grid = grids[0];
8339  const UT_Matrix4D identity(1.0);
8340 
8341  total_transforms = grid.myCurveNEdges + !grid.myCurveClosed;
8342  exint num_vertices = total_transforms - 2*cap_divisions;
8343 
8344  sopcache->myGridTransformStarts.reset(new exint[1]);
8345  sopcache->clearTransformArrays();
8346  sopcache->myTransformMatrices3D.reset(new UT_Matrix3D[total_transforms]);
8347  sopcache->myTransformTranslates3D.reset(new UT_Vector3D[total_transforms]);
8348  sopcache->myTransformCacheSize = total_transforms;
8349  exint *grid_transform_starts = sopcache->myGridTransformStarts.get();
8350  UT_Matrix3D *grid_matrix3ds = sopcache->myTransformMatrices3D.get();
8351  UT_Vector3D *grid_translate3ds = sopcache->myTransformTranslates3D.get();
8352  grid_transform_starts[0] = 0;
8353 
8354  // Transforms in the middle are all identity
8355  for (exint i = 0; i < num_vertices; ++i)
8356  {
8357  UT_ASSERT(cap_divisions+i < total_transforms);
8358  grid_matrix3ds[cap_divisions+i].identity();
8359  grid_translate3ds[cap_divisions+i] = UT_Vector3D(0,0,0);
8360  }
8361 
8362  // Transforms for the end cap rows aren't identity.
8363  sopAllEndCapTransforms(
8364  grid, sopparms, cross_section_input,
8365  identity, identity, num_vertices,
8366  grid_matrix3ds, grid_translate3ds,
8367  true);
8368  }
8369  else
8370  {
8371  sopcache->clearTransformArrays();
8372  }
8373 
8374  // Determine which transform caches are needed
8375  bool needed_transforms[NeededTransforms::num_needed_transforms];
8376  for (exint i = 0; i < NeededTransforms::num_needed_transforms; ++i)
8377  {
8378  needed_transforms[i] = false;
8379  }
8380  for (exint attribi = 0, nattribs = cross_section_attribs.size(); attribi < nattribs; ++attribi)
8381  {
8382  GA_Attribute *output_attrib = cross_section_attribs[attribi].second;
8383  GA_ATINumeric *output_numeric = GA_ATINumeric::cast(output_attrib);
8384  if (output_numeric == nullptr)
8385  continue;
8386 
8387  GA_TypeInfo transform_type = output_numeric->getTypeInfo();
8388  if (transform_type == GA_TYPE_VOID)
8389  continue;
8390 
8391  using namespace NeededTransforms;
8392  GA_Storage storage = output_numeric->getStorage();
8393  bool double_precision = (storage == GA_STORE_REAL64);
8394  if (transform_type == GA_TYPE_POINT || transform_type == GA_TYPE_HPOINT)
8395  {
8396  needed_transforms[matrix3f] |= !double_precision;
8397  needed_transforms[translate3f] |= !double_precision;
8398  }
8399  else if (transform_type == GA_TYPE_VECTOR)
8400  {
8401  needed_transforms[matrix3f] |= !double_precision;
8402  }
8403  else if (transform_type == GA_TYPE_NORMAL)
8404  {
8405  needed_transforms[inverse3d] |= double_precision;
8406  needed_transforms[inverse3f] |= !double_precision;
8407  }
8408  else if (transform_type == GA_TYPE_QUATERNION)
8409  {
8410  needed_transforms[quaterniond] |= double_precision;
8411  needed_transforms[quaternionf] |= !double_precision;
8412  }
8413  else if (transform_type == GA_TYPE_TRANSFORM)
8414  {
8415  needed_transforms[matrix3f] |= !double_precision;
8416  needed_transforms[translate3f] |= !double_precision && (output_numeric->getTupleSize() == 16);
8417  }
8418  }
8419 
8421  sopcache,
8422  total_transforms,
8423  true,//transforms_changed,
8424  needed_transforms);
8425 
8426  const SOP_SweepHDKCache *transform_cache = (sopcache->myTransformCacheSize != 0) ? sopcache : nullptr;
8427 
8428  ///////////////////////////////////////////////////////////////////
8429  // //
8430  // 4. ATTRIBUTES //
8431  // //
8432  ///////////////////////////////////////////////////////////////////
8433 
8434  //exint num_source_attribs[3];
8435  //GUaddAttributesFromSourceOrTarget(output_geo, cross_section_input, num_source_attribs, has_transform_matrices, needed_transforms, curve_input, );
8436 
8437  if (!cross_section_input)
8438  {
8439  // We're using the implicit circle (tube) or line (ribbon) cross section, so we need to compute P specially.
8440 
8441  output_geo->getP()->bumpDataId();
8442  const GA_RWHandleV3D outputP(output_geo->getP());
8443  UT_ASSERT(outputP.isValid());
8444 
8445  UT_Array<UT_Vector3D> cross_section_positions;
8446  const exint cross_section_nedges = sopparms.getCols();
8447  if (sopparms.getSurfaceShape() == SurfaceShape::TUBE)
8448  {
8449  cross_section_positions.setSize(cross_section_nedges);
8450  if (cross_section_nedges == 1)
8451  {
8452  cross_section_positions[0].assign(1,0,0);
8453  }
8454  else
8455  {
8456  cross_section_positions[0].assign(1,0,0);
8457  for (exint cross_sectioni = 1; cross_sectioni < cross_section_nedges; ++cross_sectioni)
8458  {
8459  exint tcross_sectioni = cross_sectioni;
8460  if (sopparms.getReverseCrossSections())
8461  tcross_sectioni = reverseVtx(cross_sectioni, cross_section_nedges, true);
8462  double t = double(tcross_sectioni)/double(cross_section_nedges);
8463  UT_Vector3D v;
8464  if (t == 0.25)
8465  v = UT_Vector3D(0,1,0);
8466  else if (t == 0.5)
8467  v = UT_Vector3D(-1,0,0);
8468  else if (t == 0.75)
8469  v = UT_Vector3D(0,-1,0);
8470  else
8471  {
8472  double theta = (2.0*M_PI)*t;
8473  double c = SYScos(theta);
8474  double s = SYSsin(theta);
8475  v = UT_Vector3D(c,s,0);
8476  }
8477  // NOTE: Normals are reversed in Houdini, so cross section
8478  // orientation must be flipped to have outward facing normals.
8479  v[1] = -v[1];
8480  cross_section_positions[cross_sectioni] = v;
8481  }
8482  }
8483  }
8484  else if (sopparms.getSurfaceShape() == SurfaceShape::RIBBON)
8485  {
8486  cross_section_positions.setSize(cross_section_nedges+1);
8487  // NOTE: Normals are reversed in Houdini, so cross section
8488  // orientation must be flipped to have normals matching up vectors.
8489  double x0 = (sopparms.getReverseCrossSections() ? 1 : -1);
8490  double x1 = -x0;
8491  cross_section_positions[0].assign(x0, 0, 0);
8492  for (exint cross_sectioni = 1; cross_sectioni < cross_section_nedges; ++cross_sectioni)
8493  {
8494  double t = double(cross_sectioni)/double(cross_section_nedges);
8495  double x = SYSlerp(x0, x1, t);
8496  cross_section_positions[cross_sectioni] = UT_Vector3D(x,0,0);
8497  }
8498  cross_section_positions.last().assign(x1,0,0);
8499  }
8500  else
8501  {
8502  UT_ASSERT(sopparms.getSurfaceShape() == SurfaceShape::SQUARE);
8503 
8504  cross_section_positions.setSize(4*cross_section_nedges);
8505  UT_Vector2D corners[4] = {
8506  UT_Vector2D(-1, -1),
8507  UT_Vector2D( 1, -1),
8508  UT_Vector2D( 1, 1),
8509  UT_Vector2D(-1, 1)
8510  };
8511  // NOTE: Normals are reversed in Houdini, so cross section
8512  // orientation must be flipped to have outward facing normals.
8513  if (!sopparms.getReverseCrossSections())
8514  UTswap(corners[1], corners[3]);
8515 
8516  exint arrayi = 0;
8517  for (exint big_edgei = 0; big_edgei < 4; ++big_edgei)
8518  {
8519  // Exact for first position on each big edge
8520  cross_section_positions[arrayi].assign(corners[big_edgei][0], corners[big_edgei][1], 0);
8521  ++arrayi;
8522 
8523  UT_Vector2D prev = corners[big_edgei];
8524  UT_Vector2D next = corners[(big_edgei==3) ? 0 : (big_edgei+1)];
8525  for (exint small_edgei = 1; small_edgei < cross_section_nedges; ++small_edgei, ++arrayi)
8526  {
8527  double t = double(small_edgei)/double(cross_section_nedges);
8528  UT_Vector2D pos2d = SYSlerp(prev, next, t);
8529  cross_section_positions[arrayi].assign(pos2d[0], pos2d[1], 0);
8530  }
8531  }
8532  }
8533 
8534  UT_ASSERT_P(curve_input != nullptr);
8535 
8536  // Transform position attribute
8537  const exint ngrids = grids.size();
8538  auto&& functor = [&](const UT_BlockedRange<exint>& r)
8539  {
8541  sop_SweepGridTransformWrapper local_grid_transforms;
8542  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
8543  {
8544  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
8545  const sop_SweepGrid &grid_info = grids[gridi];
8546  GU_GridT<GA_Offset> grid;
8547  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8548 
8549  const bool swap_row_col = sopparms.getSwapRowCol();
8550  exint prev_vtxi = -1;
8551  auto&& functor = [&prev_vtxi,&cross_section_positions,swap_row_col,grid_transforms,&transform,&outputP]
8552  (GA_Offset output_offset, exint row, exint col)
8553  {
8554  const exint curve_vtxi = swap_row_col ? col : row;
8555  if (curve_vtxi != prev_vtxi)
8556  {
8557  transform = UT_Matrix4D(grid_transforms->getMatrix3s<double>()[curve_vtxi]);
8558  transform.setTranslates(grid_transforms->getTranslates<double>()[curve_vtxi]);
8559  prev_vtxi = curve_vtxi;
8560  }
8561 
8562  // Transform a single position.
8563  // NOTE: col should be in-bounds in this case, since all cross sections have the same topology.
8564  const exint cross_section_vtxi = swap_row_col ? row : col;
8565  const UT_Vector3D &cross_section_pos = cross_section_positions[cross_section_vtxi];
8566  outputP.set(output_offset, (cross_section_pos * transform));
8567  };
8568 
8569  GUiterateGridPoints(grid, functor);
8570  }
8571  };
8572 
8573  const exint PARALLEL_THRESHOLD = 2048;
8574  if (ngrids > 1 && output_geo->getNumPoints() >= PARALLEL_THRESHOLD)
8575  {
8576  // Must harden all pages before multi-threading.
8577  outputP->hardenAllPages();
8578 
8579  UTparallelFor(UT_BlockedRange<exint>(0, ngrids), functor);
8580  }
8581  else
8582  {
8583  functor(UT_BlockedRange<exint>(0, ngrids));
8584  }
8585  }
8586 
8587  exint num_cross_sections;
8588  if (cross_section_group != nullptr)
8589  {
8590  num_cross_sections = cross_section_group->entries();
8591  }
8592  else if (cross_section_input != nullptr)
8593  {
8594  num_cross_sections = cross_section_input->getNumPrimitives();
8595  }
8596  else
8597  {
8598  num_cross_sections = 1;
8599  }
8600 
8601  const UVStyle ustyle = (!computing_uvs || !sopparms.getLengthWeightedUVs() || sopparms.getNormalizeU()) ?
8602  UVStyle::NORMALIZED :
8603  (sopparms.getWrapU() ? UVStyle::ROUNDED : UVStyle::FULL);
8604  const UVStyle vstyle = (!computing_uvs || !sopparms.getLengthWeightedUVs() || sopparms.getNormalizeV()) ?
8605  UVStyle::NORMALIZED :
8606  (sopparms.getWrapV() ? UVStyle::ROUNDED : UVStyle::FULL);
8607 
8608  bool finished = copySurfaceAttributes(
8609  output_geo, curve_input, cross_section_input,
8610  grids, (copy_order == CopyOrder::ALL) ? num_cross_sections : 1,
8611  cross_section_attribs,
8612  curve_attribs, uv_attribs,
8613  transform_cache,
8614  surface_type, output_points_only, sopparms.getTriangularPoles(), cap_divisions,
8615  sopparms.getReverseCrossSections(), sopparms.getSwapRowCol(),
8616  uv_attrib_index,
8617  (!missing_input_us.isEmpty()) ? &missing_input_us : nullptr,
8618  sopparms.getLengthWeightedUVs(),
8619  ustyle, vstyle,
8620  (curve_input == nullptr) || sopparms.getUseMeshEdgeLengths(),
8621  sopparms.getPropScalePerCurve(),
8622  computing_uvs && sopparms.getFlipU(),
8623  sopparms.getUVScale());
8624 
8625  if (!finished)
8626  return; // Interrupted, so return.
8627 
8628  ///////////////////////////////////////////////////////////////////
8629  // //
8630  // 5. METADATA //
8631  // //
8632  ///////////////////////////////////////////////////////////////////
8633 
8634 
8635  if (sopparms.getAddPointRow() && sopparms.getPtRowAttrib().isstring())
8636  {
8637  // Create point row attribute
8638  const UT_StringHolder &attribname = sopparms.getPtRowAttrib();
8639  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_POINT, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8640  if (attrib.isValid())
8641  {
8642  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8643  {
8644  const sop_SweepGrid &grid_info = grids[gridi];
8645  GU_GridT<GA_Offset> grid;
8646  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8647 
8648  GUiterateGridPoints(grid, [&attrib](GA_Offset output_ptoff, exint ptrow, exint ptcol)
8649  {
8650  attrib.set(output_ptoff, ptrow);
8651  });
8652  }
8653  }
8654  }
8655  if (sopparms.getAddPointCol() && sopparms.getPtColAttrib().isstring())
8656  {
8657  // Create point col attribute
8658  const UT_StringHolder &attribname = sopparms.getPtColAttrib();
8659  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_POINT, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8660  if (attrib.isValid())
8661  {
8662  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8663  {
8664  const sop_SweepGrid &grid_info = grids[gridi];
8665  GU_GridT<GA_Offset> grid;
8666  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8667 
8668  GUiterateGridPoints(grid, [&attrib](GA_Offset output_ptoff, exint ptrow, exint ptcol)
8669  {
8670  attrib.set(output_ptoff, ptcol);
8671  });
8672  }
8673  }
8674  }
8675  if (sopparms.getAddPrimRow() && sopparms.getPrimRowAttrib().isstring())
8676  {
8677  // Create primitive row attribute
8678  const UT_StringHolder &attribname = sopparms.getPrimRowAttrib();
8679  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_PRIMITIVE, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8680  if (attrib.isValid())
8681  {
8682  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8683  {
8684  const sop_SweepGrid &grid_info = grids[gridi];
8685  GU_GridT<GA_Offset> grid;
8686  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8687 
8688  GA_Offset grid_start_primoff = grid_info.myStartPrimOff;
8689  if (grid_info.myHasPolygonCaps)
8690  {
8691  // Row zero for first single polygon cap.
8692  attrib.set(grid_start_primoff, 0);
8693  ++grid_start_primoff;
8694  }
8695 
8696  GUiterateGridPrimitives(grid, [&attrib,grid_start_primoff]
8697  (exint primnum, exint primrow, exint primcol, exint primvtxcount, bool closed)
8698  {
8699  attrib.set(grid_start_primoff + primnum, primrow);
8700  });
8701 
8702  if (grid_info.myHasPolygonCaps)
8703  {
8704  // Last row for last single polygon cap.
8705  attrib.set(grid_start_primoff + grid.myNumPrimitives, grid_info.myCurveNEdges);
8706  }
8707  }
8708  }
8709  }
8710  if (sopparms.getAddPrimCol() && sopparms.getPrimColAttrib().isstring())
8711  {
8712  // Create primitive col attribute
8713  const UT_StringHolder &attribname = sopparms.getPrimColAttrib();
8714  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_PRIMITIVE, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8715  if (attrib.isValid())
8716  {
8717  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8718  {
8719  const sop_SweepGrid &grid_info = grids[gridi];
8720  GU_GridT<GA_Offset> grid;
8721  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8722 
8723  GA_Offset grid_start_primoff = grid_info.myStartPrimOff;
8724  if (grid_info.myHasPolygonCaps)
8725  {
8726  // No meaningful column for single polygon cap.
8727  attrib.set(grid_start_primoff, 0);
8728  ++grid_start_primoff;
8729  }
8730 
8731  GUiterateGridPrimitives(grid, [&attrib,grid_start_primoff]
8732  (exint primnum, exint primrow, exint primcol, exint primvtxcount, bool closed)
8733  {
8734  attrib.set(grid_start_primoff + primnum, primcol);
8735  });
8736 
8737  if (grid_info.myHasPolygonCaps)
8738  {
8739  // No meaningful column for single polygon cap.
8740  attrib.set(grid_start_primoff + grid.myNumPrimitives, 0);
8741  }
8742  }
8743  }
8744  }
8745  if (sopparms.getAddCurveNum() && sopparms.getCurveNumAttrib().isstring())
8746  {
8747  // Create input curve number attribute
8748  const UT_StringHolder &attribname = sopparms.getCurveNumAttrib();
8749  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_POINT, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8750  if (attrib.isValid())
8751  {
8752  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8753  {
8754  const sop_SweepGrid &grid_info = grids[gridi];
8755  GU_GridT<GA_Offset> grid;
8756  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8757 
8758  // If no curve input, there's just the one implicit curve.
8759  exint curve_number = (curve_input != nullptr) ? exint(curve_input->primitiveIndex(grid_info.myCurvePrimOff)) : 0;
8760 
8761  GUiterateGridPoints(grid, [&attrib,curve_number](GA_Offset output_ptoff, exint ptrow, exint ptcol)
8762  {
8763  attrib.set(output_ptoff, curve_number);
8764  });
8765  }
8766  }
8767  }
8768  if (sopparms.getAddCrossSectionNum() && sopparms.getCrossSectionNumAttrib().isstring())
8769  {
8770  // Create input cross section number attribute
8771  const UT_StringHolder &attribname = sopparms.getCrossSectionNumAttrib();
8772  GA_RWHandleI attrib(output_geo->createTupleAttribute(GA_ATTRIB_POINT, attribname, GA_STORE_INT32, 1, GA_Defaults(-1)));
8773  if (attrib.isValid())
8774  {
8775  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8776  {
8777  const sop_SweepGrid &grid_info = grids[gridi];
8778  GU_GridT<GA_Offset> grid;
8779  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8780 
8781  if (cross_section_input != nullptr)
8782  {
8783  GUiterateGridPoints(grid, [&attrib,&grid_info,cross_section_input](GA_Offset output_ptoff, exint ptrow, exint ptcol)
8784  {
8785  GA_Offset source_cross_section_primoff =
8786  grid_info.mySingleCrossSection ?
8787  GA_Offset(grid_info.myCrossSectionPrimOff) :
8788  (*grid_info.myCrossSectionPrimOffs)[ptrow];
8789  UT_ASSERT_P(GAisValid(source_cross_section_primoff));
8790  exint cross_section_number = cross_section_input->primitiveIndex(source_cross_section_primoff);
8791 
8792  attrib.set(output_ptoff, cross_section_number);
8793  });
8794  }
8795  else
8796  {
8797  GUiterateGridPoints(grid, [&attrib](GA_Offset output_ptoff, exint ptrow, exint ptcol)
8798  {
8799  // Only the one cross section if no cross section input.
8800  attrib.set(output_ptoff, 0);
8801  });
8802  }
8803  }
8804  }
8805  }
8806  if (sopparms.getAddEndCapsGroup() && sopparms.getEndCapsGroup().isstring())
8807  {
8808  // Create end caps primitive group
8809  const UT_StringHolder &groupname = sopparms.getEndCapsGroup();
8810  GA_PrimitiveGroup *group = output_geo->newPrimitiveGroup(groupname);
8811  if (group != nullptr && sopparms.getEndCapType() != EndCapType::NONE)
8812  {
8813  for (exint gridi = 0, ngrids = grids.size(); gridi < ngrids; ++gridi)
8814  {
8815  const sop_SweepGrid &grid_info = grids[gridi];
8816  GU_GridT<GA_Offset> grid;
8817  initGUGrid(grid_info, surface_type, output_points_only, sopparms.getTriangularPoles(), sopparms.getSwapRowCol(), grid_info.myStartPtOff, grid);
8818 
8819  GA_Offset grid_start_primoff = grid_info.myStartPrimOff;
8820  if (grid_info.myHasPolygonCaps)
8821  {
8822  // Only the first and last primitive if single-polygon end caps
8823  group->addOffset(grid_start_primoff);
8824  group->addOffset(grid_start_primoff+1+grid.myNumPrimitives);
8825  }
8826  else if (grid_info.myVEndPoles)
8827  {
8828  // All primitives with primrow < cap_divisions or >= last_cap_start_row are end in caps.
8829  exint last_cap_start_vtxi = grid_info.myCurveNEdges - cap_divisions;
8830  const bool swap_row_col = sopparms.getSwapRowCol();
8831  GUiterateGridPrimitives(grid, [group,cap_divisions,grid_start_primoff,last_cap_start_vtxi,swap_row_col]
8832  (exint primnum, exint primrow, exint primcol, exint primvtxcount, bool closed)
8833  {
8834  exint curve_vtxi = swap_row_col ? primcol : primrow;
8835  if (curve_vtxi < cap_divisions || curve_vtxi >= last_cap_start_vtxi)
8836  {
8837  group->addOffset(grid_start_primoff + primnum);
8838  }
8839  });
8840  }
8841  }
8842  }
8843  }
8844 
8845 
8846  // FIXME: Separate Us in UV, even if didn't compute UVs!!!
8847 
8848  // Since we're recomputing transforms on every cook for now, we might as well clear the transform cache.
8849  // FIXME: Reuse the transforms between cooks if they haven't changed!!!
8850  sopcache->clearTransformArrays();
8851 
8852 
8853 }
8854 
8855 } // End of HDK_Sample namespace
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1193
void destroyPrimitiveGroup(GA_PrimitiveGroup *g)
Definition: GEO_Detail.h:2176
void initColTube(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0))
Definition: GU_GridImpl.h:1184
const UT_Matrix3T< T > * getInverse3s() const
Definition: SOP_SweepHDK.C:390
constexpr SYS_FORCE_INLINE T length2() const noexcept
Definition: UT_Vector3.h:356
#define SYSmax(a, b)
Definition: SYS_Math.h:1582
T & last()
Definition: UT_Array.h:823
GLint first
Definition: glcorearb.h:405
SYS_FORCE_INLINE void bumpDataId()
Definition: GA_Attribute.h:306
SYS_FORCE_INLINE const GA_Detail & getDetail() const
Definition: GA_Attribute.h:208
SYS_FORCE_INLINE void forEachAttribute(FUNCTOR &&functor) const
GA_Offset myCurvePrimOff
Definition: SOP_SweepHDK.C:281
static SYS_FORCE_INLINE bool isType(const GA_Attribute *attrib)
Definition of a geometry attribute.
Definition: GA_Attribute.h:198
UT_Matrix4T< fpreal64 > UT_Matrix4D
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition: glcorearb.h:2540
void UTparallelFor(const Range &range, const Body &body, const int subscribe_ratio=2, const int min_grain_size=1, const bool force_use_task_scope=true)
constexpr SYS_FORCE_INLINE T dot(const UT_Vector3T &b) const noexcept
Definition: UT_Vector3.h:529
Data has no numeric representation.
Definition: GA_Types.h:104
UT_UniquePtr< UT_Vector3D[]> myTransformTranslates3D
Definition: GU_Copy2.h:95
#define SYS_STATIC_ASSERT(expr)
int setUBasis(GA_Basis *ub)
Definition: GEO_TPSurf.h:366
SYS_FORCE_INLINE GA_Primitive * getPrimitive(GA_Offset prim_off)
Definition: GA_Detail.h:429
SYS_FORCE_INLINE const GA_AttributeDict & pointAttribs() const
Definition: GEO_Detail.h:1959
FromType append(ToType value)
Add a single entry (may grow array)
const GA_IndexMap & getPrimitiveMap() const
Definition: GA_Detail.h:746
const UT_QuaternionT< T > * getQuaternions() const
Definition: SOP_SweepHDK.C:414
GA_DataId getDataId() const
unsigned char myBasisOrderU
Definition: GU_Grid.h:92
const UT_Matrix3T< T > * getMatrix3s() const
Definition: SOP_SweepHDK.C:402
Iteration over a range of elements.
Definition: GA_Iterator.h:29
static SYS_FORCE_INLINE bool isType(const GA_Attribute *attrib)
Definition: GA_ATINumeric.h:60
int setVBasis(GA_Basis *vb)
Definition: GEO_TPSurf.h:375
Y
Definition: ImathEuler.h:184
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
UT_ErrorSeverity sopAddWarning(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:509
GOP_GroupParse::GroupCreator GroupCreator
Definition: GOP_Manager.h:35
static GA_AttributeFilter selectAnd(const GA_AttributeFilter &f0, const GA_AttributeFilter &f1, bool single_match=false)
void newSopOperator(OP_OperatorTable *table)
Definition: SOP_SweepHDK.C:542
void UTswap(T &a, T &b)
Definition: UT_Swap.h:35
SYS_FORCE_INLINE int getPrimitiveTypeId(GA_Offset primoff) const
Definition: GA_Primitive.h:916
Class which stores the default values for a GA_Attribute.
Definition: GA_Defaults.h:35
T distance3d(const UT_Vector3T< T > &p1, const UT_Vector3T< T > &p2)
Compute the distance between two points.
Definition: UT_Vector3.h:1116
void
Definition: png.h:1083
OP_ERROR lock(OP_Context &context)
Locks all inputs.
getFileOption("OpenEXR:storage") storage
Definition: HDK_Image.dox:276
bool mySingleCrossSection
Definition: SOP_SweepHDK.C:292
bool isValid() const
Test to see whether the iterator is valid.
Definition: GA_Iterator.h:41
GA_API const UT_StringHolder uv
bool myNoWrapU
Definition: GU_Grid.h:143
bool myFirstRowIfNotWrapped
Definition: GU_Grid.h:105
const GLdouble * v
Definition: glcorearb.h:837
static GA_AttributeFilter selectPublic(bool include_noninternal_groups=true)
Select public scope attributes and non-internal groups.
SYS_FORCE_INLINE const GA_IndexMap & getIndexMap() const
Definition: GA_Attribute.h:207
SYS_FORCE_INLINE void colVecMult(const UT_Matrix3F &m)
Definition: UT_Matrix3.h:1557
GA_OffsetListType< GA_Size > GA_OffsetList
GA_OffsetList is a map from index to offset.
UT_Vector2T< fpreal64 > UT_Vector2D
#define M_PI
Definition: fmath.h:98
#define UT_ASSERT_LEVEL_PARANOID
Definition: UT_Assert.h:13
bool blockAdvance(GA_Offset &start, GA_Offset &end)
GLuint start
Definition: glcorearb.h:475
GLsizei const GLfloat * value
Definition: glcorearb.h:824
GA_Size entries() const overridefinal
Will return the number of primary elements.
void setSizeNoInit(exint newsize)
Definition: UT_Array.h:702
void clearAndDestroy()
Clear all the points/primitives out of this detail.
Definition: GEO_Detail.h:268
#define M_PI_2
Definition: fmath.h:101
int getTupleSize() const
UT_Vector3T< float > UT_Vector3
int OP_InputIdx
Definition: OP_DataTypes.h:184
SYS_FORCE_INLINE bool getExtraFlag() const
Synonym for isClosed()
fpreal64 distance2(const UT_VectorD &v1, const UT_VectorD &v2)
Distance squared (L2) aka quadrance.
Definition: UT_Vector.h:399
GA_DataId getDataId() const
Return the data ID for the topology attributes.
int64 exint
Definition: SYS_Types.h:125
A soup of polygons.
SYS_FORCE_INLINE void initHullData(int rows, int cols, bool wrapv, bool wrapu)
Definition: GEO_Hull.h:390
GA_Attribute * getP()
Convenience method to access the P attribute.
Definition: GA_Detail.h:164
SYS_FORCE_INLINE const char * buffer() const
GLboolean GLboolean GLboolean GLboolean a
Definition: glcorearb.h:1222
bool myCurvesIfBasisRowCol
Definition: GU_Grid.h:133
GLdouble s
Definition: glad.h:3009
PUGI__FN void reverse(I begin, I end)
Definition: pugixml.cpp:7458
UT_ErrorSeverity
Definition: UT_Error.h:25
SYS_FORCE_INLINE GA_AttributeScope getScope() const
Definition: GA_Attribute.h:212
bool myVEndPoles
Definition: SOP_SweepHDK.C:295
T * array()
Definition: UT_Array.h:846
GLuint GLsizei GLsizei * length
Definition: glcorearb.h:795
X
Definition: ImathEuler.h:183
bool myCurveClosed
Definition: SOP_SweepHDK.C:288
void setCapacity(exint new_capacity)
void destroyVertexGroup(GA_VertexGroup *g)
Definition: GEO_Detail.h:2178
enditerator end() const
Definition: GA_Range.h:62
GA_EdgeGroupTable & edgeGroups()
Definition: GA_Detail.h:1202
SYS_FORCE_INLINE TO_T UTverify_cast(FROM_T from)
Definition: UT_Assert.h:229
bool myFirstColIfNotWrapped
Definition: GU_Grid.h:115
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
Standard user attribute level.
Definition: GA_Types.h:149
SYS_FORCE_INLINE UT_Vector3 getPos3(GA_Offset ptoff) const
The ptoff passed is the point offset.
Definition: GA_Detail.h:185
void arbitraryPerp(const UT_Vector3T< T > &v)
Finds an arbitrary perpendicular to v, and sets this to it.
UT_Matrix2T< T > SYSlerp(const UT_Matrix2T< T > &v1, const UT_Matrix2T< T > &v2, S t)
Definition: UT_Matrix2.h:675
UT_UniquePtr< exint[]> myGridTransformStarts
Definition: SOP_SweepHDK.C:357
GA_ROHandleS myCurveStrAttrib
Definition: SOP_SweepHDK.C:110
3D Vector class.
SOP_SweepHDKEnums::SurfaceShape myPrevSurfaceShape
We'll use NONE to represent using the cross section input.
Definition: SOP_SweepHDK.C:341
2D Vector class.
Definition: UT_Vector2.h:159
exint myNumEdgeCols
Definition: GU_Grid.h:46
bool shouldInterpretAsTexCoord(bool allow_float2=false) const
void appendPolygons(const GA_PolyCounts &sizes, const GA_OffsetList &vertices)
SYS_FORCE_INLINE bool GAisValid(GA_Size v)
Definition: GA_Types.h:655
CrossSectionAttribMatchData myCrossSectionAttribMatchData
Definition: SOP_SweepHDK.C:351
exint size() const
Definition: UT_Array.h:653
void setSize(exint newsize)
Definition: UT_Array.h:673
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:236
GA_ATINumeric * getAttribute() const
Definition: GA_Handle.h:347
This is the SOP class definition.
Definition: SOP_SweepHDK.C:465
#define GA_INVALID_OFFSET
Definition: GA_Types.h:687
void bumpSize(exint newsize)
Definition: UT_Array.h:636
GA_Size countPrimitiveType(const GA_PrimitiveTypeId &type) const
Definition: GA_Detail.h:2311
UT_ErrorSeverity sopAddError(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:516
GA_GroupTable::iterator< GA_EdgeGroup > beginTraverse() const
A range of elements in an index-map.
Definition: GA_Range.h:42
bool myCrossSectionClosed
Definition: SOP_SweepHDK.C:290
std::unique_ptr< T, Deleter > UT_UniquePtr
A smart pointer for unique ownership of dynamically allocated objects.
Definition: UT_UniquePtr.h:39
SYS_FORCE_INLINE const UT_StringHolder & getName() const
Definition: GA_Attribute.h:283
< returns > If no error
Definition: snippets.dox:2
#define UT_ASSERT_MSG(ZZ,...)
Definition: UT_Assert.h:159
UT_UniquePtr< UT_Matrix3D[]> myTransformMatrices3D
Definition: GU_Copy2.h:93
GA_Size GA_Offset
Definition: GA_Types.h:646
bool atEnd() const
Definition: GA_Iterator.h:93
virtual bool fill(const GA_Range &destrange, GA_Offset srci)
Definition: GA_Attribute.h:834
virtual bool copyFrom(const GA_Basis &b, bool compatible=false)
SYS_FORCE_INLINE void outerproductUpdate(T b, const UT_Vector3F &v1, const UT_Vector3F &v2)
Definition: UT_Matrix3.h:428
GA_API const UT_StringHolder scale
GA_AttributeScope
Definition: GA_Types.h:143
const T & parms() const
Definition: SOP_NodeVerb.h:423
exint myCrossSectionPrimOff
Definition: SOP_SweepHDK.C:285
GLdouble n
Definition: glcorearb.h:2008
UT_UniquePtr< UT_Vector3F[]> myTransformTranslates3F
Definition: GU_Copy2.h:94
UT_Array< sop_SweepGrid > myGrids
Definition: SOP_SweepHDK.C:354
SYS_FORCE_INLINE GA_Index primitiveIndex(GA_Offset offset) const
Given a primitive's data offset, return its index.
Definition: GA_Detail.h:423
GA_ROHandleID myCurveIntAttrib
Definition: SOP_SweepHDK.C:105
GLintptr offset
Definition: glcorearb.h:665
Constructs a PRM_Template list from an embedded .ds file or an istream.
bool myTriangularPoles
Definition: GU_Grid.h:127
void identity()
Set the matrix to identity.
Definition: UT_Matrix3.h:1128
bool myHasPolygonCaps
Definition: SOP_SweepHDK.C:296
void bind(const GA_Detail *gdp, GA_AttributeOwner owner, const UT_StringRef &name, int minsize=1)
GA_Iterator begin() const
SOP_SweepHDKEnums::CopyOrder myPrevCopyOrder
Definition: SOP_SweepHDK.C:337
void GUiterateGridPrimitives(const GU_GridT< INT_TYPE > &grid, FUNCTOR &&functor)
Definition: GU_GridImpl.h:77
exint myCrossSectionNEdges
Definition: SOP_SweepHDK.C:280
virtual void replace(const GA_Attribute &src)=0
GA_OffsetList * myCrossSectionPrimOffs
Definition: SOP_SweepHDK.C:286
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
constexpr SYS_FORCE_INLINE void negate() noexcept
Definition: UT_Vector3.h:351
PRM_Template * templates() const
UT_ArrayStringMap< GA_Offset > myStrToPrimOff
Definition: SOP_SweepHDK.C:125
#define UT_ASSERT_P(ZZ)
Definition: UT_Assert.h:155
static SYS_FORCE_INLINE GA_ATINumeric * cast(GA_Attribute *attrib)
Definition: GA_ATINumeric.h:65
SOP_SweepHDKEnums::PrimType myPrevPrimType
Definition: SOP_SweepHDK.C:339
SYS_FORCE_INLINE GA_OffsetListRef getPrimitiveVertexList(GA_Offset primoff) const
Definition: GA_Primitive.h:895
virtual GA_BASIS_TYPE getType() const =0
Return the type of the basis.
fpreal64 dot(const CE_VectorT< T > &a, const CE_VectorT< T > &b)
Definition: CE_Vector.h:137
bool myAllEqualNEdges
Definition: SOP_SweepHDK.C:293
#define SYS_FALLTHROUGH
Definition: SYS_Compiler.h:68
GLuint GLuint end
Definition: glcorearb.h:475
GEO_API GA_Offset GEObuildPrimitives(GEO_Detail *detail, const std::pair< int, exint > *primtype_count_pairs, const GA_Offset init_startpt, const GA_Size npoints_per_copy, const GA_PolyCounts &vertexlistsizelist, const INT_T *vertexpointnumbers, const bool hassharedpoints, const exint *closed_span_lengths, const exint ncopies=1)
#define SYS_FORCE_INLINE
Definition: SYS_Inline.h:45
bool myAllWrapU
Definition: GU_Grid.h:141
UT_Vector3T< T > SYSclamp(const UT_Vector3T< T > &v, const UT_Vector3T< T > &min, const UT_Vector3T< T > &max)
Definition: UT_Vector3.h:1057
#define UT_ASSERT_LEVEL
Definition: UT_Assert.h:17
void GUiterateGridVertices(const GU_GridT< INT_TYPE > &grid, FUNCTOR &&functor)
Definition: GU_GridImpl.h:497
GA_AttributeSet & getAttributes()
Definition: GA_Detail.h:799
bool myUnrollCurves
Definition: GU_Grid.h:100
SOP_NodeCache * allocCache() const override
Definition: SOP_SweepHDK.C:440
void rewind()
Bezier or NURBS basis classes which maintain knot vectors.
Definition: GA_Basis.h:49
NURBS basis classes which maintain knot vectors.
Definition: GA_NUBBasis.h:44
SOP_NodeParms * allocParms() const override
Definition: SOP_SweepHDK.C:439
GU_API void computeCurveTransforms(const GEO_Detail *const geo, const GA_PrimitiveGroup *curve_group, const GA_RWHandleT< UT_Matrix4T< T >> &transform_attrib, const CurveFrameParms< T > &parms)
SYS_FORCE_INLINE GA_ATINumericUPtr createDetachedTupleAttribute(GA_AttributeOwner owner, GA_Storage storage, int tuple_size, const GA_Defaults &defaults=GA_Defaults(0.0f), const GA_AttributeOptions *attribute_options=nullptr) const
Definition: GA_Detail.h:906
GLdouble GLdouble GLint GLint order
Definition: glad.h:2676
long long int64
Definition: SYS_Types.h:116
UT::ArrayMap< exint, GA_Offset > myIntToPrimOff
Definition: SOP_SweepHDK.C:117
GLuint id
Definition: glcorearb.h:655
SYS_FORCE_INLINE const char * c_str() const
UT_Vector3T< fpreal64 > UT_Vector3D
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:203
SYS_FORCE_INLINE GA_Offset vertexPoint(GA_Offset vertex) const
Given a vertex, return the point it references.
Definition: GA_Detail.h:529
void identity()
Set the matrix to identity.
Definition: UT_Matrix4.h:1124
SYS_API fpreal32 SYSfloor(fpreal32 val)
bool myIsUsingMap
Definition: SOP_SweepHDK.C:135
GLuint const GLchar * name
Definition: glcorearb.h:786
SYS_FORCE_INLINE GA_DataId getDataId() const
Definition: GA_Attribute.h:299
UT_API T UTboundingCircle(const T *coords, exint n, T *centre_out=nullptr)
Returns radius squared.
SYS_FORCE_INLINE const GA_Attribute * findAttribute(GA_AttributeScope scope, const UT_StringRef &name, const GA_AttributeOwner search_order[], int search_size) const
Definition: GA_Detail.h:1021
OP_ERROR cookMySop(OP_Context &context) override
Since this SOP implements a verb, cookMySop just delegates to the verb.
Definition: SOP_SweepHDK.C:491
SYS_FORCE_INLINE bool destroyEdgeGroup(const UT_StringRef &name)
Definition: GA_Detail.h:1289
GLboolean GLboolean GLboolean b
Definition: glcorearb.h:1222
GA_Size GA_Index
Define the strictness of GA_Offset/GA_Index.
Definition: GA_Types.h:640
GA_API const UT_StringHolder transform
GLint GLenum GLint x
Definition: glcorearb.h:409
void GUiterateGridPoints(const GU_GridT< INT_TYPE > &grid, FUNCTOR &&functor)
Definition: GU_GridImpl.h:28
void initColSphere(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0), INT_TYPE end_pt=INT_TYPE(1), INT_TYPE start_mid_pt=INT_TYPE(2))
Definition: GU_GridImpl.h:1363
SYS_FORCE_INLINE const GA_AttributeDict & getDict(GA_AttributeOwner owner) const
Raw access to the GA_AttributeDict for a particular owner.
GEO_SurfaceType mySurfaceType
Definition: GU_Grid.h:77
exint myCurveNEdges
Definition: SOP_SweepHDK.C:279
bool contains(const GA_Primitive *prim) const
GU_Detail * gdpNC()
fpreal32 SYSrint(fpreal32 val)
Definition: SYS_Floor.h:163
GLenum GLenum GLsizei void * table
Definition: glad.h:5129
static UT_XformOrder::xyzOrder getRotOrder(int xyz)
Translate a XYZ parameter menu index into the UT_XformOrder type.
bool myLastRowIfNotWrapped
Definition: GU_Grid.h:110
exint append()
Definition: UT_Array.h:142
GA_TypeInfo
Definition: GA_Types.h:101
GLdouble t
Definition: glad.h:2397
GA_Offset myStartPtOff
Definition: SOP_SweepHDK.C:276
UT_UniquePtr< GA_Attribute > GA_AttributeUPtr
Definition: GA_Attribute.h:943
void setTranslates(const UT_Vector3T< S > &translates)
Definition: UT_Matrix4.h:1438
SYS_FORCE_INLINE const GA_AttributeDict & attribs() const
Definition: GEO_Detail.h:1971
GA_Topology & getTopology()
Definition: GA_Detail.h:801
exint myNumVertices
Definition: GU_Grid.h:146
static PRM_Template * buildTemplates()
Definition: SOP_SweepHDK.C:520
bool myUEndPoles
Definition: SOP_SweepHDK.C:294
int sprintf(const char *fmt,...) SYS_PRINTF_CHECK_ATTRIBUTE(2
SYS_FORCE_INLINE const GA_Attribute * findPrimitiveAttribute(GA_AttributeScope s, const UT_StringRef &name) const
Definition: GA_Detail.h:1062
INT_TYPE getPoint(exint row, exint col) const
Definition: GU_Grid.h:282
SYS_FORCE_INLINE bool isValid() const
Definition: GA_Handle.h:187
const GA_Basis * getBasis() const
Definition: GEO_Curve.h:310
ToType last() const
Return the value of the last element.
bool myLastColIfNotWrapped
Definition: GU_Grid.h:120
GU_API void GUcomputeTransformTypeCaches(GU_PointTransformCache *cache, exint num_target_points, bool transforms_changed, const bool needed_transforms[NeededTransforms::num_needed_transforms])
PrimitiveType myPrimitiveType
Definition: SOP_SweepHDK.C:298
SYS_FORCE_INLINE GA_Size getNumVertices() const
Return the number verticies in the entire detail.
Definition: GA_Detail.h:504
Data represents a quaternion. Token "quaternion".
Definition: GA_Types.h:120
bool myCurveUnrolled
Definition: SOP_SweepHDK.C:289
GA_Offset myStartPrimOff
Definition: SOP_SweepHDK.C:277
GA_AttributeOwner
Definition: GA_Types.h:35
Quaternion class.
Definition: GEO_Detail.h:49
UT_UniquePtr< UT_Matrix3F[]> myTransformInverse3F
Definition: GU_Copy2.h:96
GA_API const UT_StringHolder parms
IMATH_NAMESPACE::V2f IMATH_NAMESPACE::Box2i std::string this attribute is obsolete as of OpenEXR v3 float
UT_UniquePtr< UT_QuaternionD[]> myTransformQuaternionsD
Definition: GU_Copy2.h:99
void hardenAllPages(GA_Offset start_offset=GA_Offset(0), GA_Offset end_offset=GA_INVALID_OFFSET) override
Harden data pages.
bool myCrossSectionUnrolled
Definition: SOP_SweepHDK.C:291
SOP_SweepHDKEnums::SurfaceType myPrevSurfaceType
Definition: SOP_SweepHDK.C:338
static GA_AttributeFilter selectByPattern(const char *pattern)
SYS_FORCE_INLINE GA_TypeInfo getTypeInfo() const
Definition: GA_Attribute.h:252
SYS_FORCE_INLINE void set(GA_Offset off, const T &val) const
Definition: GA_Handle.h:362
SYS_FORCE_INLINE GA_Index indexSize() const
Definition: GA_IndexMap.h:104
bool myNoWrapV
Definition: GU_Grid.h:144
GA_BASIS_TYPE
Definition: GA_Basis.h:33
SYS_NO_DISCARD_RESULT UT_QuaternionT< T > interpolate(const UT_QuaternionT< T > &target, T t, T b=0.0f) const
Interpolates between this quat (t==0) and the target (t==1)
SYS_FORCE_INLINE GA_Offset primitiveOffset(GA_Index index) const
Given a primitive's index (in append order), return its data offset.
Definition: GA_Detail.h:419
GA_Size getEntries() const
Get an accurate count of the entries in the range.
Definition: GA_Range.h:252
virtual bool copy(GA_Offset desti, GA_Offset srci)
Definition: GA_Attribute.h:813
Data represents a normal vector. Token "normal".
Definition: GA_Types.h:114
LeafData & operator=(const LeafData &)=delete
SYS_FORCE_INLINE const GA_AttributeDict & primitiveAttribs() const
Definition: GEO_Detail.h:1963
exint myNumEdgeRows
Definition: GU_Grid.h:45
void append(GA_Size size, GA_Size count=1)
void destroyPointGroup(GA_PointGroup *g)
Definition: GEO_Detail.h:2174
int setBasis(GA_Basis *ub)
Definition: GEO_Curve.h:297
unsigned char myBasisOrderCrossSection
Definition: SOP_SweepHDK.C:300
static const char *const theDsFile
This is the parameter interface string, below.
Definition: SOP_SweepHDK.C:456
SYS_FORCE_INLINE GA_Size getNumPrimitives() const
Return the number of primitives.
Definition: GA_Detail.h:408
GA_AttributeOwner myCurveAttribOwner
Definition: SOP_SweepHDK.C:130
Data represents a direction vector. Token "vector".
Definition: GA_Types.h:112
if(num_boxed_items<=0)
Definition: UT_RTreeImpl.h:697
const GU_Detail * inputGeo(exint idx) const
Definition: SOP_NodeVerb.h:387
const GA_PrimitiveList & getPrimitiveList() const
Definition: GA_Detail.h:795
SOP_NodeCache * cache() const
Definition: SOP_NodeVerb.h:428
void constant(const T &v)
Quickly set the array to a single value.
constexpr SYS_FORCE_INLINE T distance(const UT_Vector3T &b) const noexcept
Definition: UT_Vector3.h:371
UT_StringHolder name() const override
Definition: SOP_SweepHDK.C:441
Data represents a position in space. Token "point".
Definition: GA_Types.h:106
GEO_SurfaceType
bool isDetached() const
Definition: GA_Attribute.h:445
SYS_FORCE_INLINE GA_AttributeOwner getOwner() const
Definition: GA_Attribute.h:210
Container class for all geometry.
Definition: GA_Detail.h:96
static const SOP_NodeVerb::Register< SOP_SweepHDKVerb > theVerb
Definition: SOP_SweepHDK.C:453
void zero()
Set the matrix to zero.
Definition: UT_Matrix3.h:1130
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
SYS_FORCE_INLINE UT_StorageMathFloat_t< T > normalize() noexcept
Definition: UT_Vector3.h:376
GLenum GLenum GLsizei void * row
Definition: glad.h:5135
void bind(GA_Detail *gdp, GA_AttributeOwner owner, const UT_StringRef &name, int minsize=1)
const UT_Vector3T< T > * getTranslates() const
Definition: SOP_SweepHDK.C:378
const char * inputLabel(OP_InputIdx idx) const override
These are the labels that appear when hovering over the inputs.
Definition: SOP_SweepHDK.C:497
GLboolean r
Definition: glcorearb.h:1222
bool myAllWrapV
Definition: GU_Grid.h:142
#define GA_DETAIL_OFFSET
Definition: GA_Types.h:691
exint myNumPrimitives
Definition: GU_Grid.h:145
PrimitiveType
Definition: GU_Grid.h:79
GA_Offset myStartVtxOff
Definition: SOP_SweepHDK.C:278
static const UT_StringHolder theSOPTypeName
Definition: SOP_SweepHDK.C:449
GA_OffsetList myPrevCrossSectionGroup
Definition: SOP_SweepHDK.C:335
SYS_FORCE_INLINE GA_Offset vertexOffset(GA_Index index) const
Given a vertex's index (in append order), return its data offset.
Definition: GA_Detail.h:515
SIM_API const UT_StringHolder distance
void clear()
Resets list to an empty list.
Definition: UT_Array.h:736
int isRefInput(OP_InputIdx i) const override
Definition: SOP_SweepHDK.C:512
SYS_FORCE_INLINE void setTypeInfo(GA_TypeInfo type)
Definition: GA_Attribute.h:260
void initRowSphere(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0), INT_TYPE end_pt=INT_TYPE(1), INT_TYPE start_mid_pt=INT_TYPE(2))
Definition: GU_GridImpl.h:1454
GA_Range getPrimitiveRange(const GA_PrimitiveGroup *group=0) const
Get a range of all primitives in the detail.
Definition: GA_Detail.h:1752
CookMode cookMode(const SOP_NodeParms *parms) const override
Definition: SOP_SweepHDK.C:443
#define SYSmin(a, b)
Definition: SYS_Math.h:1583
virtual void hardenAllPages(GA_Offset start_offset=GA_Offset(0), GA_Offset end_offset=GA_INVALID_OFFSET)=0
void destroyAttribute(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringRef &name, const GA_AttributeFilter *filter=0)
void bumpDataIdsForAddOrRemove(bool added_or_removed_points, bool added_or_removed_vertices, bool added_or_removed_primitives)
PrimitiveType myPrimitiveType
Definition: GU_Grid.h:88
Declare prior to use.
Bezier basis classes which maintain knot vectors.
Definition: GA_BezBasis.h:37
void initRowTube(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0))
Definition: GU_GridImpl.h:1273
SOP_SweepHDKEnums::EndCapType myPrevEndCapType
Definition: SOP_SweepHDK.C:343
static GA_Basis * newSpecies(GA_BASIS_TYPE type)
Data represents a transform matrix. Token "matrix".
Definition: GA_Types.h:118
void initTorus(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0))
Definition: GU_GridImpl.h:1744
UT_UniquePtr< UT_QuaternionF[]> myTransformQuaternionsF
Definition: GU_Copy2.h:98
T * getArray() const
Definition: UT_Array.h:843
SIM_DerVector3 cross(const SIM_DerVector3 &lhs, const SIM_DerVector3 &rhs)
GA_Storage
Definition: GA_Types.h:51
GA_Basis * getUBasis() const
Definition: GEO_TPSurf.h:384
GU_API bool getPolyProperties(const GEO_Detail *geometry, const GA_OffsetListRef &vertices, exint &nedges, bool &closed, bool &unrolled)
SYS_FORCE_INLINE void addOffset(GA_Offset ai)
static OP_Node * myConstructor(OP_Network *net, const char *name, OP_Operator *op)
Definition: SOP_SweepHDK.C:470
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
UT_UniquePtr< UT_Matrix3D[]> myTransformInverse3D
Definition: GU_Copy2.h:97
GA_OffsetList getOffsetFromIndexList() const
Definition: GA_IndexMap.h:245
bool fullBlockAdvance(GA_Offset &start, GA_Offset &end)
GLint GLsizei count
Definition: glcorearb.h:405
SYS_FORCE_INLINE bool isstring() const
unsigned char myBasisOrderCurve
Definition: SOP_SweepHDK.C:299
UT_UniquePtr< UT_Matrix3F[]> myTransformMatrices3F
Definition: GU_Copy2.h:92
int getOrder() const
Return the order of the basis.
Definition: GA_Basis.h:80
SYS_FORCE_INLINE FromType size() const
Returns the number of used elements in the list (always <= capacity())
unsigned char myBasisOrderV
Definition: GU_Grid.h:93
void getTranslates(UT_Vector3T< S > &translates) const
Definition: UT_Matrix4.h:1428
void initSingleGrid(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0))
Definition: GU_GridImpl.h:1094
SOP_SweepHDK(OP_Network *net, const char *name, OP_Operator *op)
Definition: SOP_SweepHDK.C:478
SYS_FORCE_INLINE const GA_AttributeDict & vertexAttribs() const
Definition: GEO_Detail.h:1967
void setSurfaceType(GEO_SurfaceType t)
Definition: GEO_Hull.h:480
void init(const SOP_SweepHDKCache &cache, exint gridi)
Definition: SOP_SweepHDK.C:364
UT_Matrix3T< fpreal64 > UT_Matrix3D
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: glcorearb.h:1297
void initSplitColSphere(exint nedgerows, exint nedgecols, INT_TYPE start_pt=INT_TYPE(0), INT_TYPE end_pt=INT_TYPE(1), INT_TYPE start_mid_pt=INT_TYPE(2))
Definition: GU_GridImpl.h:1653
bool isEmpty() const
Returns true iff there are no occupied elements in the array.
Definition: UT_Array.h:657
GA_Storage getStorage() const
GA_Basis * getVBasis() const
Definition: GEO_TPSurf.h:385