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) 2024
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * This SOP 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 
478  SOP_SweepHDK(OP_Network *net, const char *name, OP_Operator *op)
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(unsigned idx) const override
498  {
499  switch (idx)
500  {
501  case 0: return "Backbone Curves";
502  case 1: return "Cross Section(s)";
503  default: return "Invalid Source";
504  }
505  }
506 
507  /// This just indicates whether an input wire gets drawn with a dotted line
508  /// in the network editor. If something is usually copied directly
509  /// into the output, a solid line (false) is used, but this SOP very often
510  /// doesn't do that for either input.
511  int isRefInput(unsigned i) const override
512  {
513  // First or second input both use dotted lines
514  return (i == 0 || i == 1);
515  }
516 };
517 
518 PRM_Template *SOP_SweepHDK::buildTemplates()
519 {
520  static PRM_TemplateBuilder templ("SOP_SweepHDK.C"_sh, SOP_SweepHDKVerb::theDsFile);
521  if (templ.justBuilt())
522  {
523  templ.setChoiceListPtr("curvegroup", &SOP_Node::primGroupMenu);
524  // FIXME: This needs to select primitive groups from input 1, not input 0!!!
525  templ.setChoiceListPtr("crosssectiongroup", &SOP_Node::primGroupMenu);
526  }
527  return templ.templates();
528 }
529 
530 const SOP_NodeVerb *SOP_SweepHDK::cookVerb() const
531 {
532  return SOP_SweepHDKVerb::theVerb.get();
533 }
534 
535 } // End of HDK_Sample namespace
536 
537 /// newSopOperator is the hook that Houdini grabs from this dll
538 /// and invokes to register the SOP. In this case, we add ourselves
539 /// to the specified operator table.
541 {
542  // NOTE: Even though this SOP requires at least one of the two
543  // inputs to be connected, if we specified the minimum number
544  // of sources as 1, the node would error out when providing only
545  // the second (cross section) input, which isn't what we want,
546  // so instead, we specify 0 as the minimum number of sources,
547  // and explicitly error in the cook function if neither input is
548  // present.
549  table->addOperator(new OP_Operator(
551  "HDK Sweep", // UI name
552  HDK_Sample::SOP_SweepHDK::myConstructor, // How to build the SOP
553  HDK_Sample::SOP_SweepHDK::buildTemplates(), // My parameters
554  0, // Min # of sources
555  2, // Max # of sources
556  nullptr,// Custom local variables (none)
557  0)); // No flags: not a generator, no merge input, not an output
558 }
559 
560 namespace HDK_Sample {
561 
562 OP_ERROR
563 SOP_SweepHDK::cookInputGroups(OP_Context &context, int alone)
564 {
565  UT_ASSERT(alone);
566  OP_AutoLockInputs inputs(this);
567  if (inputs.lock(context) >= UT_ERROR_ABORT)
568  return error();
569 
570  const GU_Detail *curve_input = inputGeo(theCurveInput);
571  const GA_PrimitiveGroup *curve_group;
572  OP_ERROR ret = cookInputPrimitiveGroups(context, curve_group, alone, true, 0, -1, true, false, GroupCreator(curve_input));
573  if (ret >= UT_ERROR_ABORT)
574  return ret;
575 
576  const GU_Detail *cross_section_input = inputGeo(theCrossSectionInput);
577  const GA_PrimitiveGroup *cross_section_group;
578  ret = cookInputPrimitiveGroups(context, cross_section_group, alone, true, 1, -1, true, false, GroupCreator(cross_section_input));
579  return ret;
580 }
581 
582 //******************************************************************************
583 //* Parameters *
584 //******************************************************************************
585 
586 /// This is a multi-line raw string specifying the parameter interface for this SOP.
587 const char *const SOP_SweepHDKVerb::theDsFile = R"THEDSFILE(
588 {
589  name parameters
590  parm {
591  name "curvegroup"
592  cppname "CurveGroup"
593  label "Backbone Curve Group"
594  type string
595  default { "" }
596  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
597  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
598  parmtag { "script_action_icon" "BUTTONS_reselect" }
599  }
600  parm {
601  name "crosssectiongroup"
602  cppname "CrossSectionGroup"
603  label "Cross Section Group"
604  type string
605  default { "" }
606  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 1\nsoputils.selectGroupParm(kwargs)" }
607  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
608  parmtag { "script_action_icon" "BUTTONS_reselect" }
609  }
610  parm {
611  name "sepparm"
612  label ""
613  type separator
614  default { "" }
615  }
616  group {
617  name "surface_folder"
618  label "Surface"
619  parm {
620  name "surfaceshape"
621  cppname "SurfaceShape"
622  label "Surface Shape"
623  type ordinal
624  default { "0" } // Default to first entry in menu, "input"
625  menu {
626  "input" "Second Input Cross Sections"
627  "tube" "Round Tube"
628  "square" "Square Tube"
629  "ribbon" "Ribbon"
630  }
631  }
632  parm {
633  name "surfacetype"
634  cppname "SurfaceType"
635  label "Surface Type"
636  type ordinal
637  default { "5" } // Default to menu entry "quads"
638  menu {
639  "points" "Points"
640  "rows" "Rows"
641  "cols" "Columns"
642  "rowcol" "Rows and Columns"
643  "tris" "Triangles"
644  "quads" "Quadrilaterals"
645  "alttris" "Alternating Triangles"
646  "revtris" "Reverse Triangles"
647  }
648  }
649  parm {
650  name "scale"
651  label "Scale Cross Sections"
652  type float
653  default { "1" }
654  range { 0 4 }
655  disablewhen "{ surfaceshape != input }"
656  hidewhen "{ surfaceshape != input }"
657  }
658  parm {
659  name "cols"
660  label "Columns"
661  type integer
662  default { "8" }
663  range { 1! 24 }
664  disablewhen "{ surfaceshape == input }"
665  hidewhen "{ surfaceshape == input }"
666  }
667  parm {
668  name "radius"
669  label "Radius"
670  type float
671  default { "0.1" }
672  range { 0 1 }
673  parmtag { "units" "m1" }
674  disablewhen "{ surfaceshape != tube }"
675  hidewhen "{ surfaceshape != tube }"
676  }
677  parm {
678  name "width"
679  label "Width"
680  type float
681  default { "0.2" }
682  range { 0 1 }
683  parmtag { "units" "m1" }
684  disablewhen "{ surfaceshape != ribbon surfaceshape != square }"
685  hidewhen "{ surfaceshape != ribbon surfaceshape != square }"
686  }
687  parm {
688  name "reversecrosssections"
689  cppname "ReverseCrossSections"
690  label "Reverse Cross Sections"
691  type toggle
692  default { "0" }
693  }
694  parm {
695  name "stretcharoundturns"
696  cppname "StretchAroundTurns"
697  label "Stretch Around Turns"
698  type toggle
699  default { "1" }
700  }
701  parm {
702  name "maxstretcharoundturns"
703  cppname "MaxStretchAroundTurns"
704  label "Max Stretch"
705  type log
706  default { "10" }
707  range { 1! 100 }
708  disablewhen "{ stretcharoundturns == 0 }"
709  }
710  groupsimple {
711  name "endcaps_folder"
712  label "End Caps"
713  parm {
714  name "endcaptype"
715  cppname "EndCapType"
716  label "End Cap Type"
717  type ordinal
718  default { "0" } // Default to menu entry "none"
719  menu {
720  "none" "None"
721  "single" "Single Polygon"
722  "grid" "Grid"
723  "sidesingle" "Side Single Polygon"
724  }
725  }
726  parm {
727  name "capdivs"
728  cppname "CapDivs"
729  label "Cap Divisions"
730  type integer
731  default { "3" }
732  range { 1! 10 }
733  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
734  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
735  joinnext
736  }
737  parm {
738  name "triangularpoles"
739  cppname "TriangularPoles"
740  label "Triangular Poles"
741  type toggle
742  default { "0" }
743  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
744  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
745  }
746  parm {
747  name "capscale"
748  cppname "CapScale"
749  label "End Cap Scale"
750  type float
751  default { "1" }
752  range { 0 1 }
753  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
754  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
755  }
756  parm {
757  name "caproundness"
758  cppname "CapRoundness"
759  label "End Cap Roundness"
760  type float
761  default { "1" }
762  range { 0 1 }
763  disablewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
764  hidewhen "{ endcaptype == none } { endcaptype == single } { endcaptype == sidesingle }"
765  }
766  parm {
767  name "addendcapsgroup"
768  cppname "AddEndCapsGroup"
769  label "Add End Caps Group"
770  type toggle
771  default { "0" }
772  nolabel
773  joinnext
774  }
775  parm {
776  name "endcapsgroup"
777  cppname "EndCapsGroup"
778  label "End Caps Group"
779  type string
780  default { "endcaps" }
781  disablewhen "{ addendcapsgroup == 0 }"
782  }
783  }
784  groupsimple {
785  name "scale_folder"
786  label "Scale"
787  parm {
788  name "applyscale"
789  cppname "ApplyScale"
790  label "Apply Scale Along Curve"
791  type toggle
792  default { "0" }
793  }
794  parm {
795  name "scaleramp"
796  cppname "ScaleRamp"
797  label "Scale Ramp"
798  type ramp_flt
799  default { "2" }
800  range { 0! 10 }
801  disablewhen "{ applyscale == 0 }"
802  parmtag { "rampfloatdefault" "1pos ( 0 ) 1value ( 1 ) 1interp ( linear ) 2pos ( 1 ) 2value ( 1 ) 2interp ( linear )" }
803  }
804  }
805  groupsimple {
806  name "rotation_folder"
807  label "Rotation"
808  parm {
809  name "rOrd"
810  cppname "ROrd"
811  label "Rotate Order"
812  type ordinal
813  // NOTE: The default rotation order X,Y,Z is semi-arbitrary, but Z
814  // should probably be last, since it always needs to twist
815  // around the curve tangent. The X and Y rotations may have
816  // just been to reorient a cross-section before copying.
817  default { "xyz" }
818  menu {
819  "xyz" "Pitch, Yaw, Roll"
820  "xzy" "Pitch, Roll, Yaw"
821  "yxz" "Yaw, Pitch, Roll"
822  "yzx" "Yaw, Roll, Pitch"
823  "zxy" "Roll, Pitch, Yaw"
824  "zyx" "Roll, Yaw, Pitch"
825  }
826  }
827  parm {
828  name "applyroll"
829  cppname "ApplyRoll"
830  label "Apply Roll or Twist"
831  type toggle
832  default { "1" }
833  }
834  parm {
835  name "roll"
836  label "Roll"
837  type float
838  default { "0" }
839  range { -180 180 }
840  hidewhen "{ applyroll == 0 }"
841  }
842  parm {
843  name "fulltwists"
844  cppname "FullTwists"
845  label "Full Twists"
846  type integer
847  default { "0" }
848  range { -10 10 }
849  hidewhen "{ applyroll == 0 }"
850  }
851  parm {
852  name "incroll"
853  cppname "IncRoll"
854  label "Partial Twist"
855  type float
856  default { "0" }
857  range { -180 180 }
858  hidewhen "{ applyroll == 0 }"
859  joinnext
860  }
861  parm {
862  name "rollper"
863  cppname "RollPer"
864  label "Twist Per"
865  type ordinal
866  default { "4" } // Default to "fulldistance" entry in menu
867  menu {
868  "edge" "Per Edge"
869  "distance" "Per Unit Distance"
870  "attrib" "Scale by Attribute"
871  "fulledges" "Per Full Curve by Edges"
872  "fulldistance" "Per Full Curve by Distance"
873  }
874  hidewhen "{ applyroll == 0 }"
875  }
876  parm {
877  name "rollattrib"
878  cppname "RollAttrib"
879  label "Twist Ramp Attribute"
880  type string
881  default { "roll" }
882  disablewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
883  hidewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
884  }
885  parm {
886  name "sepparmroll"
887  label ""
888  type separator
889  default { "" }
890  hidewhen "{ applyroll == 0 }"
891  }
892  parm {
893  name "applyyaw"
894  cppname "ApplyYaw"
895  label "Apply Yaw"
896  type toggle
897  default { "0" }
898  }
899  parm {
900  name "yaw"
901  label "Yaw"
902  type float
903  default { "0" }
904  range { -180 180 }
905  hidewhen "{ applyyaw == 0 }"
906  }
907  parm {
908  name "incyaw"
909  cppname "IncYaw"
910  label "Incremental Yaw"
911  type float
912  default { "0" }
913  range { -180 180 }
914  hidewhen "{ applyyaw == 0 }"
915  joinnext
916  }
917  parm {
918  name "yawper"
919  cppname "YawPer"
920  label "Yaw Per"
921  type ordinal
922  default { "4" } // Default to "fulldistance" entry in menu
923  menu {
924  "edge" "Per Edge"
925  "distance" "Per Unit Distance"
926  "attrib" "Scale By Attribute"
927  "fulledges" "Per Full Curve by Edges"
928  "fulldistance" "Per Full Curve by Distance"
929  }
930  hidewhen "{ applyyaw == 0 }"
931  }
932  parm {
933  name "yawattrib"
934  cppname "YawAttrib"
935  label "Yaw Ramp Attribute"
936  type string
937  default { "yaw" }
938  disablewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
939  hidewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
940  }
941  parm {
942  name "sepparmyaw"
943  label ""
944  type separator
945  default { "" }
946  hidewhen "{ applyyaw == 0 }"
947  }
948  parm {
949  name "applypitch"
950  cppname "ApplyPitch"
951  label "Apply Pitch"
952  type toggle
953  default { "0" }
954  }
955  parm {
956  name "pitch"
957  label "Pitch"
958  type float
959  default { "0" }
960  range { -180 180 }
961  hidewhen "{ applypitch == 0 }"
962  }
963  parm {
964  name "incpitch"
965  cppname "IncPitch"
966  label "Incremental Pitch"
967  type float
968  default { "0" }
969  range { -180 180 }
970  hidewhen "{ applypitch == 0 }"
971  joinnext
972  }
973  parm {
974  name "pitchper"
975  cppname "PitchPer"
976  label "Pitch Per"
977  type ordinal
978  default { "4" } // Default to "fulldistance" entry in menu
979  menu {
980  "edge" "Per Edge"
981  "distance" "Per Unit Distance"
982  "attrib" "Scale By Attribute"
983  "fulledges" "Per Full Curve by Edges"
984  "fulldistance" "Per Full Curve by Distance"
985  }
986  hidewhen "{ applypitch == 0 }"
987  }
988  parm {
989  name "pitchattrib"
990  cppname "PitchAttrib"
991  label "Pitch Ramp Attribute"
992  type string
993  default { "pitch" }
994  disablewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
995  hidewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
996  }
997  }
998  }
999 )THEDSFILE"
1000 // ==== This is necessary because MSVC++ has a limit of 16380 character per
1001 // ==== string literal
1002 R"THEDSFILE(
1003  group {
1004  name "construction_folder"
1005  label "Construction"
1006  groupsimple {
1007  name "cross_sections_folder"
1008  label "Cross Sections"
1009  parm {
1010  name "copyorder"
1011  cppname "CopyOrder"
1012  label "Cross Section Order"
1013  type ordinal
1014  default { "1" } // Default to third entry in menu, "each"
1015  menu {
1016  "all" "All Cross Sections At Each Curve Vertex"
1017  "each" "Each Cross Section At All Curve Vertices"
1018  "cyclevtx" "Cycle Through Cross Section Primitives per Vertex"
1019  "cyclepr" "Cycle Through Cross Section Primitives per Curve"
1020  "attrib" "Choose Cross Section Primitives by Attribute"
1021  }
1022  disablewhen "{ surfaceshape != input }"
1023  }
1024  parm {
1025  name "crosssectionattrib"
1026  cppname "CrossSectionAttrib"
1027  label "Cross Section Attribute"
1028  type string
1029  default { "variant" }
1030  disablewhen "{ surfaceshape != input } { copyorder != attrib }"
1031  hidewhen "{ surfaceshape != input } { copyorder != attrib }"
1032  }
1033  parm {
1034  name "primtype"
1035  cppname "PrimType"
1036  label "Primitive Type"
1037  type ordinal
1038  default { "0" } // Default to menu entry "auto"
1039  menu {
1040  "auto" "Automatic"
1041  "poly" "Polygons"
1042  "mesh" "Bilinear Mesh"
1043  "nurbs" "NURBS Surface"
1044  "bezier" "Bezier Surface"
1045  "polysoup" "Polygon Soup"
1046  }
1047  disablewhen "{ surfacetype == points }"
1048  }
1049  parm {
1050  name "unrollclosedrowcol"
1051  cppname "UnrollClosedRowCol"
1052  label "Ensure Unique Seam Vertices"
1053  type toggle
1054  default { "0" }
1055  disablewhen "{ surfacetype == points }"
1056  }
1057  parm {
1058  name "swaprowcol"
1059  cppname "SwapRowCol"
1060  label "Swap Rows and Columns"
1061  type toggle
1062  default { "0" }
1063  }
1064  parm {
1065  name "closeifnocurveinput"
1066  cppname "CloseIfNoCurveInput"
1067  label "Close Implicit Backbone Curve if No Curve Input"
1068  type toggle
1069  default { "0" }
1070  disablewhen "{ surfacetype == points } { surfacetype == rows } { hasinput(0) != 0 }"
1071  }
1072  //parm {
1073  // name "segdivs"
1074  // cppname "SegDivs"
1075  // label "Segment Divisions"
1076  // type integer
1077  // default { "1" }
1078  // range { 1! 10 }
1079  //}
1080  }
1081  groupsimple {
1082  name "up_folder"
1083  label "Up Vectors"
1084  parm {
1085  name "upvectortype"
1086  cppname "UpVectorType"
1087  label "Target Up Vector"
1088  type ordinal
1089  default { "0" } // Default to first entry in menu, "normal"
1090  menu {
1091  "normal" "Curve Normal"
1092  "x" "X Axis"
1093  "y" "Y Axis"
1094  "z" "Z Axis"
1095  "attrib" "Attribute"
1096  "custom" "Custom"
1097  }
1098  disablewhen "{ tangenttype == none }"
1099  }
1100  //parm {
1101  // name "usenormalup"
1102  // cppname "UseNormalUp"
1103  // label "Use Curve Normal as Up Vector (When Valid)"
1104  // type toggle
1105  // default { "1" }
1106  // disablewhen "{ tangenttype == none }"
1107  //}
1108  parm {
1109  name "upvectoratstart"
1110  cppname "UpVectorAtStart"
1111  label "Target Up Vector at Start (else Average)"
1112  type toggle
1113  default { "1" }
1114  disablewhen "{ tangenttype == none }"
1115  }
1116  parm {
1117  name "useendupvector"
1118  cppname "UseEndUpVector"
1119  label "Use Target End Up Vector"
1120  type toggle
1121  default { "0" }
1122  disablewhen "{ tangenttype == none } { upvectoratstart == 0 }"
1123  }
1124  parm {
1125  name "upvectorattrib"
1126  cppname "UpVectorAttrib"
1127  label "Start Up Attribute"
1128  type string
1129  default { "start_up" }
1130  disablewhen "{ tangenttype == none } { upvectortype != attrib }"
1131  hidewhen "{ tangenttype == none } { upvectortype != attrib }"
1132  }
1133  parm {
1134  name "endupvectorattrib"
1135  cppname "EndUpVectorAttrib"
1136  label "End Up Attribute"
1137  type string
1138  default { "end_up" }
1139  disablewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
1140  hidewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
1141  }
1142  parm {
1143  name "upvector"
1144  cppname "UpVector"
1145  label "Start Up Vector"
1146  type vector
1147  size 3
1148  default { "0" "1" "0" }
1149  disablewhen "{ tangenttype == none } { upvectortype != custom }"
1150  hidewhen "{ tangenttype == none } { upvectortype != custom }"
1151  }
1152  parm {
1153  name "endupvector"
1154  cppname "EndUpVector"
1155  label "End Up Vector"
1156  type vector
1157  size 3
1158  default { "0" "1" "0" }
1159  disablewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
1160  hidewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
1161  }
1162  }
1163  groupsimple {
1164  name "tangents_folder"
1165  label "Tangents"
1166  parm {
1167  name "tangenttype"
1168  cppname "TangentType"
1169  label "Tangent Type"
1170  type ordinal
1171  default { "0" } // Default to first entry in menu, "avgdir"
1172  menu {
1173  "avgdir" "Average of Edge Directions"
1174  "diff" "Central Difference"
1175  "prev" "Previous Edge"
1176  "next" "Next Edge"
1177  "none" "Z Axis (Ignore Curve)"
1178  }
1179  }
1180  parm {
1181  name "continuousclosed"
1182  cppname "ContinuousClosed"
1183  label "Make Closed Curve Orientations Continuous"
1184  type toggle
1185  default { "1" }
1186  disablewhen "{ tangenttype == none }"
1187  }
1188  parm {
1189  name "extrapolateendtangents"
1190  cppname "ExtrapolateEndTangents"
1191  label "Extrapolate End Tangents"
1192  type toggle
1193  default { "0" }
1194  disablewhen "{ tangenttype == none }"
1195  }
1196  parm {
1197  name "transformbyattribs"
1198  cppname "TransformByAttribs"
1199  label "Transform Using Curve Point Attributes"
1200  type toggle
1201  default { "1" }
1202  }
1203  }
1204  }
1205 )THEDSFILE"
1206 // ==== This is necessary because MSVC++ has a limit of 16380 character per
1207 // ==== string literal
1208 R"THEDSFILE(
1209  group {
1210  name "uvs_folder"
1211  label "UVs and Attributes"
1212  groupsimple {
1213  name "uv_folder"
1214  label "UV Coordinates"
1215  parm {
1216  name "computeuvs"
1217  cppname "ComputeUVs"
1218  label "Compute UVs"
1219  type toggle
1220  default { "0" }
1221  }
1222  parm {
1223  name "overrideexistinguvs"
1224  cppname "OverrideExistingUVs"
1225  label "Override Any Existing UVs"
1226  type toggle
1227  default { "0" }
1228  disablewhen "{ computeuvs == 0 }"
1229  }
1230  parm {
1231  name "lengthweighteduvs"
1232  cppname "LengthWeightedUVs"
1233  label "Length-Weighted UVs"
1234  type toggle
1235  default { "1" }
1236  disablewhen "{ computeuvs == 0 }"
1237  }
1238  parm {
1239  name "normalizeu"
1240  cppname "NormalizeU"
1241  label "Normalize Computed Us"
1242  type toggle
1243  default { "1" }
1244  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1245  }
1246  parm {
1247  name "normalizev"
1248  cppname "NormalizeV"
1249  label "Normalize Computed Vs"
1250  type toggle
1251  default { "0" }
1252  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1253  }
1254  parm {
1255  name "flipu"
1256  cppname "FlipU"
1257  label "Flip Computed Us"
1258  type toggle
1259  default { "1" }
1260  disablewhen "{ computeuvs == 0 }"
1261  }
1262  groupcollapsible {
1263  name "uvscale_folder"
1264  label "UV Scale"
1265  grouptag { "group_type" "collapsible" }
1266  parmtag { "group_default" "0" }
1267  parm {
1268  name "uvscale"
1269  cppname "UVScale"
1270  label "UV Scale"
1271  type float
1272  size 2
1273  default { "1" "1" }
1274  disablewhen "{ computeuvs == 0 }"
1275  }
1276  parm {
1277  name "usemeshedgelengths"
1278  cppname "UseMeshEdgeLengths"
1279  label "Use Mesh Edge Lengths Instead of Curve Edge Lengths"
1280  type toggle
1281  default { "1" }
1282  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 }"
1283  }
1284  parm {
1285  name "propscalepercurve"
1286  cppname "PropScalePerCurve"
1287  label "Use Max Cross Section Length per Curve for Proportional Scale"
1288  type toggle
1289  default { "1" }
1290  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizeu != 1 } { normalizev != 0 }"
1291  }
1292  }
1293  groupcollapsible {
1294  name "uvseams_folder"
1295  label "UV Seams"
1296  grouptag { "group_type" "collapsible" }
1297  parmtag { "group_default" "0" }
1298  parm {
1299  name "wrapu"
1300  cppname "WrapU"
1301  label "Snap U to Nearest Tile Boundary"
1302  type toggle
1303  default { "1" }
1304  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizeu == 1 }"
1305  }
1306  parm {
1307  name "wrapv"
1308  cppname "WrapV"
1309  label "Snap V to Nearest Tile Boundary"
1310  type toggle
1311  default { "1" }
1312  disablewhen "{ computeuvs == 0 } { lengthweighteduvs == 0 } { normalizev == 1 }"
1313  }
1314  }
1315  }
1316  groupcollapsible {
1317  name "attributes_folder"
1318  label "Attributes"
1319  grouptag { "group_type" "collapsible" }
1320  parmtag { "group_default" "0" }
1321  groupsimple {
1322  name "input_folder"
1323  label "Input"
1324  parm {
1325  name "attribsfrombackbone"
1326  cppname "AttribsFromBackbone"
1327  label "From Backbone Curves"
1328  type string
1329  default { "* ^P ^N ^up ^pscale ^scale ^orient ^rot ^pivot ^trans ^transform" }
1330  }
1331  parm {
1332  name "attribsfromcrosssection"
1333  cppname "AttribsFromCrossSection"
1334  label "From Cross Sections"
1335  type string
1336  default { "*" }
1337  }
1338  }
1339  groupsimple {
1340  name "output_folder"
1341  label "Output"
1342  parm {
1343  name "addptrow"
1344  cppname "AddPointRow"
1345  label "Add Point Row Attribute"
1346  type toggle
1347  default { "0" }
1348  nolabel
1349  joinnext
1350  }
1351  parm {
1352  name "ptrowattrib"
1353  cppname "PtRowAttrib"
1354  label "Point Row Attribute"
1355  type string
1356  default { "ptrow" }
1357  disablewhen "{ addptrow == 0 }"
1358  }
1359  parm {
1360  name "addptcol"
1361  cppname "AddPointCol"
1362  label "Add Point Col Attribute"
1363  type toggle
1364  default { "0" }
1365  nolabel
1366  joinnext
1367  }
1368  parm {
1369  name "ptcolattrib"
1370  cppname "PtColAttrib"
1371  label "Point Col Attribute"
1372  type string
1373  default { "ptcol" }
1374  disablewhen "{ addptcol == 0 }"
1375  }
1376  parm {
1377  name "addprimrow"
1378  cppname "AddPrimRow"
1379  label "Add Prim Row Attribute"
1380  type toggle
1381  default { "0" }
1382  nolabel
1383  joinnext
1384  }
1385  parm {
1386  name "primrowattrib"
1387  cppname "PrimRowAttrib"
1388  label "Prim Row Attribute"
1389  type string
1390  default { "primrow" }
1391  disablewhen "{ addprimrow == 0 }"
1392  }
1393  parm {
1394  name "addprimcol"
1395  cppname "AddPrimCol"
1396  label "Add Prim Col Attribute"
1397  type toggle
1398  default { "0" }
1399  nolabel
1400  joinnext
1401  }
1402  parm {
1403  name "primcolattrib"
1404  cppname "PrimColAttrib"
1405  label "Prim Col Attribute"
1406  type string
1407  default { "primcol" }
1408  disablewhen "{ addprimcol == 0 }"
1409  }
1410  parm {
1411  name "addcrosssectionnum"
1412  cppname "AddCrossSectionNum"
1413  label "Add Cross Section Num Attribute"
1414  type toggle
1415  default { "0" }
1416  nolabel
1417  joinnext
1418  }
1419  parm {
1420  name "crosssectionnumattrib"
1421  cppname "CrossSectionNumAttrib"
1422  label "Cross Section Num Attribute"
1423  type string
1424  default { "crossnum" }
1425  disablewhen "{ addcrosssectionnum == 0 }"
1426  }
1427  parm {
1428  name "addcurvenum"
1429  cppname "AddCurveNum"
1430  label "Add Curve Num Attribute"
1431  type toggle
1432  default { "0" }
1433  nolabel
1434  joinnext
1435  }
1436  parm {
1437  name "curvenumattrib"
1438  cppname "CurveNumAttrib"
1439  label "Curve Num Attribute"
1440  type string
1441  default { "curvenum" }
1442  disablewhen "{ addcurvenum == 0 }"
1443  }
1444  // TODO: Add option to compute vertex normals with cusp angle.
1445  }
1446  }
1447  }
1448 }
1449 )THEDSFILE";
1450 
1451 //******************************************************************************
1452 //* Cooking *
1453 //******************************************************************************
1454 
1455 using namespace SOP_SweepHDKEnums;
1456 using namespace GU_CurveFrame;
1457 
1458 #if 0
1459 /// This function takes edge lengths and a number of points to insert,
1460 /// and determines the number of those points that should go in each edge
1461 /// in order to minimize the maximum final interval length.
1462 template<typename T>
1463 static void
1464 insertIntoIntervals(
1465  const T *const edge_lengths,
1466  const T total_length,
1467  const exint nedges,
1468  const exint ninsertions,
1469  exint *const ninsertions_per_edge)
1470 {
1471  UT_ASSERT(nedges >= 1 && ninsertions >= 0);
1472  exint ninsertions_so_far = 0;
1473  for (exint i = 0; i < nedges; ++i)
1474  {
1475  T portion = (total_length > 0) ? (edge_lengths[i]/total_length) : (T(1)/T(nedges));
1476  // In exact arithmetic, the number of insertions in a given edge would
1477  // be at least floor(ninsertions*portion), but roundoff error could
1478  // cause problems, so subtract 1 to reduce the risk of the initial
1479  // number of insertions exceeding ninsertions.
1480  exint ki = SYSmax(exint(0), exint(SYSfloor(ninsertions*portion))-1);
1481  // Clamp it, just in case something weird happens.
1482  ki = SYSmin(ki, ninsertions-ninsertions_so_far);
1483  ninsertions_per_edge[i] = ki;
1484  ninsertions_so_far += ki;
1485  }
1486  if (ninsertions_so_far == ninsertions)
1487  return;
1488 
1489  struct IntervalComparator {
1490  IntervalComparator(const T *const edge_lengths, const exint *const ninsertions_per_edge) :
1491  myEdgeLengths(edge_lengths),
1492  myNInsertionsPerEdge(ninsertions_per_edge)
1493  {}
1494  bool operator()(const exint a, const exint b) const {
1495  // Compare the current lengths of the intervals along edge a vs those along edge b.
1496  // Multiply by both denomonators, instead of dividing, for better performance.
1497  const T nadb = myEdgeLengths[a]*(myNInsertionsPerEdge[b]+1);
1498  const T nbda = myEdgeLengths[b]*(myNInsertionsPerEdge[a]+1);
1499 
1500  // std::priority_queue is a max heap, and we want that, but the
1501  // comparator should act like std::less and return true if the
1502  // value for a is less than the value for b.
1503  // We also want a stable tie-breaker to avoid any problems with
1504  // different behaviour on different platforms.
1505  return nadb < nbda || (nadb == nbda && a < b);
1506  }
1507  const T *const myEdgeLengths;
1508  const exint *const myNInsertionsPerEdge;
1509  };
1510  IntervalComparator comparator(edge_lengths, ninsertions_per_edge);
1511 
1512  // Use n^2 approach for small nedges or small number of remaining
1513  // insertions, to avoid building a heap.
1514  if (nedges < 20 || (ninsertions-ninsertions_so_far) < 20) {
1515  do {
1516  exint edge_with_largest_intervals = 0;
1517  for (exint i = 1; i < nedges; ++i) {
1518  if (comparator(edge_with_largest_intervals, i)) {
1519  edge_with_largest_intervals = i;
1520  }
1521  }
1522  ++ninsertions_per_edge[edge_with_largest_intervals];
1523  ++ninsertions_so_far;
1524  } while (ninsertions_so_far < ninsertions);
1525 
1526  return;
1527  }
1528 
1529  // Make a binary heap of edge indices such that the edge with
1530  // longest current intervals will be at the top.
1531  UT_Array<exint> heap;
1532  heap.setSizeNoInit(nedges);
1533  for (exint i = 0; i < nedges; ++i)
1534  heap(i) = i;
1535  exint *const heap_begin = heap.array();
1536  exint *const heap_end = heap_begin+nedges;
1537  std::make_heap(heap_begin, heap_end, comparator);
1538 
1539  while (true)
1540  {
1541  // Insert another point in the edge with the largest intervals.
1542  exint edge_with_largest_intervals = heap_begin[0];
1543  ++ninsertions_per_edge[edge_with_largest_intervals];
1544  ++ninsertions_so_far;
1545  if (ninsertions_so_far == ninsertions)
1546  return;
1547 
1548  // Update the heap
1549  std::pop_heap(heap_begin, heap_end, comparator);
1550  heap_end[-1] = edge_with_largest_intervals;
1551  std::push_heap(heap_begin, heap_end, comparator);
1552  }
1553 }
1554 #endif
1555 
1556 static int
1557 sopParmToPrimType(const SOP_SweepHDKEnums::PrimType sop_primtype)
1558 {
1560  switch (sop_primtype)
1561  {
1562  case PrimType::AUTO: return GA_PRIMNONE;
1563  case PrimType::POLY: return GA_PRIMPOLY;
1564  case PrimType::POLYSOUP:return GA_PRIMPOLYSOUP;
1565  case PrimType::MESH: return GA_PRIMMESH;
1566  case PrimType::BEZIER: return GA_PRIMBEZSURF;
1567  case PrimType::NURBS: return GA_PRIMNURBSURF;
1568  }
1569  return GA_PRIMNONE;
1570 }
1571 
1572 static SYS_FORCE_INLINE exint
1573 reverseVtx(exint i, exint n, bool closed)
1574 {
1575  if (!closed)
1576  return (n-i-1);
1577  return (i==0) ? 0 : (n-i);
1578 }
1579 
1580 static void
1581 computeCurveUVs(
1582  const GEO_Detail *detail,
1583  const GA_PrimitiveGroup *prim_group,
1584  const GA_RWHandleV3 &uv_attrib,
1585  bool by_length,
1586  bool reverse)
1587 {
1588  // We're modifying this attribute, and it'll be in the output detail,
1589  // so we need to bump its data ID.
1590  uv_attrib->bumpDataId();
1591 
1592  // Because we're not parallelizing over vertex pages and we're writing
1593  // to a vertex attribute, we must harden all pages of the attribute in advance.
1594  uv_attrib->hardenAllPages();
1595 
1596  UT_AutoInterrupt interrupt("Computing curve UVs");
1597  if (interrupt.wasInterrupted())
1598  return;
1599 
1600  //const float fnprimitives = float(prim_group ? prim_group->entries() : detail->getNumPrimitives());
1601 
1602  // Loop over primitives in parallel, using a splittable range and a lambda functor.
1603  UTparallelFor(GA_SplittableRange(detail->getPrimitiveRange(prim_group)),
1604  [detail,&uv_attrib,&interrupt,by_length,reverse](const GA_SplittableRange &r)
1605  {
1606  // Inside the functor, we have a sub-range of primitives, so loop over that range.
1607  // We use blockAdvance, instead of !it.atEnd() and ++it, for less looping overhead.
1609  for (GA_Iterator it(r); it.blockAdvance(start,end); )
1610  {
1611  // We probably don't need to check for interruption on every curve,
1612  // (unless people put in a curve with millions of vertices), so we
1613  // can check it once for every contiguous block of up to GA_PAGE_SIZE
1614  // primitive offsets.
1615  if (interrupt.wasInterrupted())
1616  return;
1617 
1618  // Loop over all primitives in this contiguous block of offsets.
1619  for (GA_Offset primoff = start; primoff < end; ++primoff)
1620  {
1621  const GA_OffsetListRef vertices = detail->getPrimitiveVertexList(primoff);
1622  const GA_Size n = vertices.size();
1623 
1624  float v = 0;
1625  //if (separate_us)
1626  // v = float(GA_Size(detail->primitiveIndex(primoff))) / fnprimitives;
1627 
1628  // Nothing to do if no vertices; special case if 1 vertex
1629  if (n <= 1)
1630  {
1631  if (n == 1)
1632  uv_attrib.set(vertices(0), UT_Vector3(0,v,0));
1633  continue;
1634  }
1635 
1636  const bool closed = vertices.getExtraFlag();
1637 
1638  // Reversed closed polygons keep vertex 0 in the same place
1639  GA_Offset vtxoff0;
1640  if (!reverse || closed)
1641  vtxoff0 = vertices(0);
1642  else
1643  vtxoff0 = vertices(n-1);
1644 
1645  bool local_by_length = by_length;
1646  if (local_by_length)
1647  {
1648  // First, compute the full length of the curve
1649  float length = 0;
1650  const UT_Vector3 pos0 = detail->getPos3(detail->vertexPoint(vtxoff0));
1651  UT_Vector3 prev_pos = pos0;
1652  for (GA_Size i = 1; i < n; ++i)
1653  {
1654  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1655  const UT_Vector3 cur_pos = detail->getPos3(detail->vertexPoint(vertices(vtxi)));
1656  length += distance3d(prev_pos, cur_pos);
1657  prev_pos = cur_pos;
1658  }
1659  if (closed)
1660  {
1661  // One last edge if closed
1662  length += distance3d(prev_pos, pos0);
1663  }
1664  if (length == 0)
1665  {
1666  // Fall back to uniform weighting if total length is zero
1667  local_by_length = false;
1668  }
1669  else
1670  {
1671  // Compute the u values as cur_length/length
1672  uv_attrib.set(vtxoff0, UT_Vector3(0,v,0));
1673  float cur_length = 0;
1674  prev_pos = detail->getPos3(detail->vertexPoint(vtxoff0));
1675  for (GA_Size i = 1; i < n; ++i)
1676  {
1677  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1678  GA_Offset vtxoff = vertices(vtxi);
1679  const UT_Vector3 cur_pos = detail->getPos3(detail->vertexPoint(vtxoff));
1680  cur_length += distance3d(prev_pos, cur_pos);
1681  const float u = cur_length/length;
1682  uv_attrib.set(vtxoff, UT_Vector3(u,v,0));
1683  prev_pos = cur_pos;
1684  }
1685  }
1686  }
1687  if (!local_by_length)
1688  {
1689  // Uniform uv step on each edge, so each u value is vertex/nedges
1690  GA_Size nedges = closed ? n : (n-1);
1691  uv_attrib.set(vtxoff0, UT_Vector3(0,v,0));
1692  for (GA_Size i = 1; i < n; ++i)
1693  {
1694  GA_Size vtxi = (!reverse) ? i : (n-i-!closed);
1695  const float u = i/float(nedges);
1696  uv_attrib.set(vertices(vtxi), UT_Vector3(u,v,0));
1697  }
1698  }
1699 
1700  // This is a silly way to do it, but to try to get UV wrapping
1701  // correct when reversing a closed cross section, the first U value
1702  // needs to be 1, instead of 0. This is because of how code below
1703  // handles wrapping and closed cross sections from the cross section input.
1704  if (reverse && closed)
1705  {
1706  uv_attrib.set(vtxoff0, UT_Vector3(1,v,0));
1707  }
1708  }
1709  }
1710  });
1711 }
1712 
1713 static GA_Offset lookupCrossSectionFromAttrib(
1714  const CrossSectionAttribMatchData *copy_order_attrib_data,
1715  GA_Offset curve_offset,
1716  const GEO_Detail *cross_section_input,
1717  const GA_PrimitiveGroup *cross_section_group)
1718 {
1719  if (copy_order_attrib_data->myCurveIntAttrib.isValid())
1720  {
1721  // Integer attribute case
1722  const exint id = copy_order_attrib_data->myCurveIntAttrib.get(curve_offset);
1723  if (copy_order_attrib_data->myIsUsingMap) {
1724  // Using map (both inputs had integer attribute)
1726  // No cross section, since invalid key for map type.
1727  return GA_INVALID_OFFSET;
1728  }
1729  auto &&it = copy_order_attrib_data->myIntToPrimOff.find(id);
1730  if (it == copy_order_attrib_data->myIntToPrimOff.end()) {
1731  // No cross section, since key not in map.
1732  return GA_INVALID_OFFSET;
1733  }
1734  return it->second;
1735  }
1736  if (id < 0 || id >= cross_section_input->getNumPrimitives()) {
1737  // No cross section, since primitive index out of range.
1738  return GA_INVALID_OFFSET;
1739  }
1740  const GA_Offset cross_section_primoff = cross_section_input->primitiveOffset(id);
1741  if (cross_section_group && !cross_section_group->contains(cross_section_primoff)) {
1742  // No cross section, since primitive not in group.
1743  return GA_INVALID_OFFSET;
1744  }
1745  return cross_section_primoff;
1746  }
1747 
1748  // String attribute case
1749  UT_ASSERT_P(copy_order_attrib_data->myCurveStrAttrib.isValid());
1750  UT_ASSERT_P(copy_order_attrib_data->myIsUsingMap);
1751 
1752  const UT_StringHolder &name = copy_order_attrib_data->myCurveStrAttrib.get(curve_offset);
1753  if (!name.isstring()) {
1754  // No cross section, since invalid key for map type.
1755  return GA_INVALID_OFFSET;
1756  }
1757  auto &&it = copy_order_attrib_data->myStrToPrimOff.find(name);
1758  if (it == copy_order_attrib_data->myStrToPrimOff.end()) {
1759  // No cross section, since key not in map.
1760  return GA_INVALID_OFFSET;
1761  }
1762  return it->second;
1763 }
1764 
1765 static int
1766 updateAutoPrimType(
1767  const GEO_Detail *const cross_section_input,
1768  const GA_Offset cross_section_primoff,
1769  int &primitive_type,
1770  unsigned char &fallback_orderu,
1771  const GA_Basis **const pbasisu)
1772 {
1773  UT_ASSERT_P(cross_section_input != nullptr);
1774  int primtype = cross_section_input->getPrimitiveTypeId(cross_section_primoff);
1775  // Only change if cross section is NURBS or Bezier curve.
1776  if (primtype != GA_PRIMNURBCURVE && primtype != GA_PRIMBEZCURVE)
1777  return primtype;
1778 
1779  // NURBS takes precedence, so don't modify if already NURBS
1780  if (primitive_type != GA_PRIMNURBSURF)
1781  {
1782  if (primtype == GA_PRIMNURBCURVE)
1783  primitive_type = GA_PRIMNURBSURF;
1784  else //if (primtype == GA_PRIMBEZCURVE)
1785  primitive_type = GA_PRIMBEZSURF;
1786  }
1787 
1788  const GEO_Curve *curve = UTverify_cast<const GEO_Curve *>(cross_section_input->getPrimitive(cross_section_primoff));
1789  UT_ASSERT_P(curve->getBasis() != nullptr);
1790  fallback_orderu = SYSmax(fallback_orderu, curve->getBasis()->getOrder());
1791  if (pbasisu != nullptr)
1792  *pbasisu = curve->getBasis();
1793 
1794  return primtype;
1795 }
1796 
1797 static bool
1798 computeCombinedCrossSectionProperties(
1799  const GEO_Detail *const cross_section_input,
1800  const GA_PrimitiveGroup *const cross_section_group,
1801  GA_Iterator &cross_section_it,
1802  exint num_cross_sections,
1803  exint &cross_section_nedges,
1804  bool &cross_section_closed,
1805  bool &cross_section_unrolled,
1806  bool &varying_nedges,
1807  GA_OffsetList &cross_section_primoffs,
1808  const bool is_primtype_auto,
1809  int &primitive_type,
1810  unsigned char &fallback_orderu,
1811  const GA_Basis **const pbasisu)
1812 {
1813  if (num_cross_sections == 0)
1814  return false;
1815 
1816  UT_ASSERT(cross_section_it.isValid());
1817  UT_ASSERT(!cross_section_it.atEnd());
1818 
1819  // If the final values of closed, unrolled, or primtype differ from
1820  // the values for the U basis, the U basis must be thrown out.
1821  bool basis_cross_section_closed;
1822  bool basis_cross_section_unrolled;
1823  int basis_primtype;
1824 
1825  cross_section_nedges = -1;
1826  varying_nedges = false;
1827  //bool varying_cross_section_topology = false;
1828  for (exint cross_sectioni = 0; cross_sectioni < num_cross_sections; ++cross_sectioni)
1829  {
1830  GA_Offset cross_section_primoff = *cross_section_it;
1831  ++cross_section_it;
1832  if (cross_section_it.atEnd())
1833  {
1834  // Wrap around the cross sections
1835  cross_section_it.rewind();
1836  UT_ASSERT(!cross_section_it.atEnd());
1837  }
1838 
1839  cross_section_primoffs.append(cross_section_primoff);
1840  const GA_OffsetListRef cross_section_vertices = cross_section_input->getPrimitiveVertexList(cross_section_primoff);
1841  exint local_cross_section_nedges;
1842  bool local_cross_section_closed;
1843  bool local_cross_section_unrolled;
1844  bool nonempty = getPolyProperties(cross_section_input, cross_section_vertices, local_cross_section_nedges, local_cross_section_closed, local_cross_section_unrolled);
1845 
1846  // If any cross section has no vertices, don't generate this grid at all.
1847  if (!nonempty)
1848  return false;
1849 
1850  if (cross_section_nedges == -1)
1851  {
1852  // First iteration, so copy value.
1853  cross_section_closed = local_cross_section_closed;
1854  cross_section_unrolled = local_cross_section_unrolled;
1855  }
1856  else if (!local_cross_section_closed)
1857  {
1858  //varying_cross_section_topology |= cross_section_closed || cross_section_unrolled;
1859  // If any cross section is open, we go with open.
1860  cross_section_closed = false;
1861  cross_section_unrolled = false;
1862  }
1863  else if (local_cross_section_unrolled && cross_section_closed && !cross_section_unrolled)
1864  {
1865  // If we're closed and not yet unrolled and we encounter
1866  // an unrolled cross section, go with unrolled.
1867  cross_section_unrolled = true;
1868  //varying_cross_section_topology = true;
1869  }
1870 
1871  // Independent of that, we choose the number of edges to be the max number of edges.
1872  bool local_varying_nedges = (cross_section_nedges != -1 && local_cross_section_nedges != cross_section_nedges);
1873  //varying_cross_section_topology |= local_varying_nedges;
1874  varying_nedges |= local_varying_nedges;
1875 
1876  int local_primtype;
1877  if (is_primtype_auto)
1878  {
1879  local_primtype = updateAutoPrimType(cross_section_input, cross_section_primoff, primitive_type, fallback_orderu, nullptr);
1880  }
1881 
1882  // Pick the maximum cross_section_nedges.
1883  if (local_cross_section_nedges > cross_section_nedges)
1884  {
1885  cross_section_nedges = local_cross_section_nedges;
1886 
1887  if (is_primtype_auto && pbasisu != nullptr)
1888  {
1889  // Choose the basis with the maximum nedges, since it's the most likely to be valid.
1890  if (local_primtype == GA_PRIMNURBCURVE || local_primtype == GA_PRIMBEZCURVE)
1891  {
1892  basis_primtype = (local_primtype == GA_PRIMNURBCURVE) ? GA_PRIMNURBSURF : GA_PRIMBEZSURF;
1893  basis_cross_section_closed = local_cross_section_closed;
1894  basis_cross_section_unrolled = local_cross_section_unrolled;
1895 
1896  const GEO_Curve *curve = UTverify_cast<const GEO_Curve*>(cross_section_input->getPrimitive(cross_section_primoff));
1897  UT_ASSERT_P(curve->getBasis() != nullptr);
1898  *pbasisu = curve->getBasis();
1899  }
1900  else
1901  {
1902  // Not a NURBS or Bezier curve, so clear the basis.
1903  *pbasisu = nullptr;
1904  }
1905  }
1906  }
1907  }
1908 
1909  if (pbasisu != nullptr && *pbasisu != nullptr)
1910  {
1911  // NOTE: This basis not be valid with the final
1912  // cross_section_closed or cross_section_unrolled or primitive_type,
1913  // so clear it if it's not valid.
1914  if (basis_primtype != primitive_type ||
1915  basis_cross_section_closed != cross_section_closed ||
1916  basis_cross_section_unrolled != cross_section_unrolled)
1917  {
1918  *pbasisu = nullptr;
1919  }
1920  }
1921 
1922  UT_ASSERT(cross_section_primoffs.size() > 0);
1923 
1924  return true;
1925 }
1926 
1927 // This is primarily the same as computeCombinedCrossSectionProperties,
1928 // except selecting cross sections using copy_order_attrib_data,
1929 // instead of getting sequential ones with a GA_Iterator.
1930 static bool
1931 computeCombinedCrossSectionPropertiesAttrib(
1932  const GEO_Detail *const cross_section_input,
1933  const GA_PrimitiveGroup *const cross_section_group,
1934  const GEO_Detail *const curve_input,
1935  const GA_OffsetListRef &curve_vertices,
1936  const CrossSectionAttribMatchData *const copy_order_attrib_data,
1937  exint &cross_section_nedges,
1938  bool &cross_section_closed,
1939  bool &cross_section_unrolled,
1940  bool &varying_nedges,
1941  GA_OffsetList &cross_section_primoffs,
1942  const bool is_primtype_auto,
1943  int &primitive_type,
1944  unsigned char &fallback_orderu,
1945  const GA_Basis **const pbasisu)
1946 {
1947  exint num_vertices = curve_vertices.size();
1948  if (num_vertices == 0)
1949  return false;
1950 
1951  const GA_AttributeOwner curve_owner = copy_order_attrib_data->myCurveAttribOwner;
1952  UT_ASSERT(curve_owner == GA_ATTRIB_POINT || curve_owner == GA_ATTRIB_VERTEX);
1953 
1954  // If the final values of closed, unrolled, or primtype differ from
1955  // the values for the U basis, the U basis must be thrown out.
1956  bool basis_cross_section_closed;
1957  bool basis_cross_section_unrolled;
1958  int basis_primtype;
1959 
1960  cross_section_nedges = -1;
1961  varying_nedges = false;
1962  //bool varying_cross_section_topology = false;
1963  for (exint vtxi = 0; vtxi < num_vertices; ++vtxi)
1964  {
1965  GA_Offset curve_offset = curve_vertices[vtxi];
1966  if (curve_owner == GA_ATTRIB_POINT)
1967  curve_offset = curve_input->vertexPoint(curve_offset);
1968  GA_Offset cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data, curve_offset, cross_section_input, cross_section_group);
1969 
1970  // If any cross sections don't work out, give up on the grid,
1971  // for simplicity when copying curve attributes later.
1972  if (!GAisValid(cross_section_primoff))
1973  return false;
1974 
1975  cross_section_primoffs.append(cross_section_primoff);
1976  const GA_OffsetListRef cross_section_vertices = cross_section_input->getPrimitiveVertexList(cross_section_primoff);
1977  exint local_cross_section_nedges;
1978  bool local_cross_section_closed;
1979  bool local_cross_section_unrolled;
1980  bool nonempty = getPolyProperties(cross_section_input, cross_section_vertices, local_cross_section_nedges, local_cross_section_closed, local_cross_section_unrolled);
1981 
1982  // If any cross section has no vertices, don't generate this grid at all.
1983  if (!nonempty)
1984  return false;
1985 
1986  if (cross_section_nedges == -1)
1987  {
1988  // First iteration, so copy value.
1989  cross_section_closed = local_cross_section_closed;
1990  cross_section_unrolled = local_cross_section_unrolled;
1991  }
1992  else if (!local_cross_section_closed)
1993  {
1994  //varying_cross_section_topology |= cross_section_closed || cross_section_unrolled;
1995  // If any cross section is open, we go with open.
1996  cross_section_closed = false;
1997  cross_section_unrolled = false;
1998  }
1999  else if (local_cross_section_unrolled && cross_section_closed && !cross_section_unrolled)
2000  {
2001  // If we're closed and not yet unrolled and we encounter
2002  // an unrolled cross section, go with unrolled.
2003  cross_section_unrolled = true;
2004  //varying_cross_section_topology = true;
2005  }
2006 
2007  // Independent of that, we choose the number of edges to be the max number of edges.
2008  bool local_varying_nedges = (cross_section_nedges != -1 && local_cross_section_nedges != cross_section_nedges);
2009  //varying_cross_section_topology |= local_varying_nedges;
2010  varying_nedges |= local_varying_nedges;
2011 
2012  int local_primtype;
2013  if (is_primtype_auto)
2014  {
2015  local_primtype = updateAutoPrimType(cross_section_input, cross_section_primoff, primitive_type, fallback_orderu, nullptr);
2016  }
2017 
2018  // Pick the maximum cross_section_nedges.
2019  if (local_cross_section_nedges > cross_section_nedges)
2020  {
2021  cross_section_nedges = local_cross_section_nedges;
2022 
2023  if (is_primtype_auto && pbasisu != nullptr)
2024  {
2025  // Choose the basis with the maximum nedges, since it's the most likely to be valid.
2026  if (local_primtype == GA_PRIMNURBCURVE || local_primtype == GA_PRIMBEZCURVE)
2027  {
2028  basis_primtype = (local_primtype == GA_PRIMNURBCURVE) ? GA_PRIMNURBSURF : GA_PRIMBEZSURF;
2029  basis_cross_section_closed = local_cross_section_closed;
2030  basis_cross_section_unrolled = local_cross_section_unrolled;
2031 
2032  const GEO_Curve *curve = UTverify_cast<const GEO_Curve*>(cross_section_input->getPrimitive(cross_section_primoff));
2033  UT_ASSERT_P(curve->getBasis() != nullptr);
2034  *pbasisu = curve->getBasis();
2035  }
2036  else
2037  {
2038  // Not a NURBS or Bezier curve, so clear the basis.
2039  *pbasisu = nullptr;
2040  }
2041  }
2042  }
2043  }
2044 
2045  if (pbasisu != nullptr && *pbasisu != nullptr)
2046  {
2047  // NOTE: This basis not be valid with the final
2048  // cross_section_closed or cross_section_unrolled or primitive_type,
2049  // so clear it if it's not valid.
2050  if (basis_primtype != primitive_type ||
2051  basis_cross_section_closed != cross_section_closed ||
2052  basis_cross_section_unrolled != cross_section_unrolled)
2053  {
2054  *pbasisu = nullptr;
2055  }
2056  }
2057 
2058  UT_ASSERT(cross_section_primoffs.size() > 0);
2059 
2060  return true;
2061 }
2062 
2063 static void
2064 initPrimType(
2065  const bool output_points_only,
2066  const bool is_primtype_auto,
2067  int &primitive_type,
2068  unsigned char &fallback_orderv,
2069  const GA_Basis **const pbasisv,
2070  const GEO_Detail *const curve_input,
2071  const GA_Offset curve_primoff)
2072 {
2073  // When using automatic primitive type mode, we take the highest order NURBS,
2074  // or if the cross section with the most edges is Bezier, Bezier of that order;
2075  // 0 indicates that we haven't encountered any NURBS or Bezier curves yet.
2076  // NOTE: If primitive_type becomes GA_PRIMNURBSURF or GA_PRIMBEZSURF below,
2077  // fallback_orderv should be set to something between 2 and GA_MAXORDER.
2078  fallback_orderv = 0;
2079  if (!is_primtype_auto)
2080  return;
2081 
2082  // Default guess is polygon
2083  primitive_type = GA_PRIMPOLY;
2084  if (curve_input == nullptr)
2085  {
2086  // If no curve, and cross sections end up having NURBS or Bezier,
2087  // default to order 4, instead of assuming implicit curve should be a polygon (order 2).
2088  fallback_orderv = 4;
2089  if (pbasisv)
2090  *pbasisv = nullptr;
2091  return;
2092  }
2093 
2094  const int curve_primtype = curve_input->getPrimitiveTypeId(curve_primoff);
2095  bool is_curve = false;
2096  if (curve_primtype == GA_PRIMBEZCURVE)
2097  {
2098  // If curve primitive is a Bezier curve, default guess is Bezier surface.
2099  primitive_type = GA_PRIMBEZSURF;
2100  is_curve = true;
2101  }
2102  else if (curve_primtype == GA_PRIMNURBCURVE)
2103  {
2104  // If curve primitive is a NURBS curve, always use NURBS surface
2105  // primitive if automatic mode.
2106  primitive_type = GA_PRIMNURBSURF;
2107  is_curve = true;
2108  }
2109  if (is_curve)
2110  {
2111  const GEO_Curve *const curve = UTverify_cast<const GEO_Curve *>(curve_input->getPrimitive(curve_primoff));
2112  if (pbasisv)
2113  *pbasisv = curve->getBasis();
2114  UT_ASSERT_P(curve->getBasis());
2115  fallback_orderv = curve->getBasis()->getOrder();
2116  }
2117 }
2118 
2119 // This fills in grid_info based on the input data.
2120 // This function does *not* handle the copy cases; it only handles the grid cases.
2121 // It returns false if the grid is invalid; the caller should not rely on grid_info or num_points in that case.
2122 // NOTE: It does *not* fill in grid_info.myStartPtOff, grid_info.myStartVtxOff, or grid_info.myStartPrimOff.
2123 static bool
2124 computeSingleGridSetup(
2125  // Cross section parameters
2126  const GEO_Detail *const cross_section_input,
2127  const GA_PrimitiveGroup *const cross_section_group,
2128  const bool single_cross_section,
2129  GA_Iterator &cross_section_it,
2130  GA_Offset single_cross_section_primoff,
2131  exint cross_section_nedges,
2132  bool cross_section_closed,
2133  bool cross_section_unrolled,
2134  const CopyOrder copy_order,
2135  const CrossSectionAttribMatchData *const copy_order_attrib_data,
2136  bool varying_nedges_all_case,
2137  const GA_OffsetList *cross_section_primoffs_all_case,
2138 
2139  // Backbone curve parameters
2140  const GEO_Detail *const curve_input,
2141  const GA_Offset curve_primoff,
2142  const bool closed_if_no_curve_input,
2143 
2144  // Surface parameters
2145  const GEO_SurfaceType surface_type,
2146  const bool output_points_only,
2147  const bool unroll_closed_row_col,
2148  const bool is_primtype_auto,
2149  int primitive_type,
2150  unsigned char fallback_orderu,
2151  unsigned char fallback_orderv,
2152  const GA_Basis **const pbasisu,
2153  EndCapType end_cap_type,
2154  exint cap_divisions,
2155 
2156  // Output parameters
2157  sop_SweepGrid &grid_info,
2158  exint &num_points)
2159 {
2160  const bool has_col_surface_type =
2161  surface_type == GEO_PATCH_COLS ||
2162  surface_type == GEO_PATCH_ROWCOL;
2163  const bool has_row_surface_type =
2164  surface_type == GEO_PATCH_ROWS ||
2165  surface_type == GEO_PATCH_ROWCOL;
2166  const bool has_rowcol_surface_type =
2167  has_col_surface_type || has_row_surface_type;
2168 
2169  const bool is_polygon_type = (!output_points_only && primitive_type == GA_PRIMPOLY);
2170  const bool could_add_row_seam_vertex = (has_col_surface_type || !is_polygon_type) && unroll_closed_row_col;
2171  const bool could_add_col_seam_vertex = (has_row_surface_type || !is_polygon_type) && unroll_closed_row_col;
2172 
2173  if (curve_input == nullptr)
2174  {
2175  // Just input cross sections, no input curves, so only this one grid.
2176  UT_ASSERT(cross_section_input != nullptr);
2177  grid_info.myCurvePrimOff = GA_INVALID_OFFSET;
2178  grid_info.myCurveClosed = closed_if_no_curve_input;
2179  grid_info.myCurveUnrolled = (grid_info.myCurveClosed && could_add_row_seam_vertex);
2180  exint num_cross_sections = (cross_section_group ? cross_section_group->entries() : cross_section_input->getNumPrimitives());
2181  grid_info.myCurveNEdges = num_cross_sections - !grid_info.myCurveClosed;
2182 
2183  bool varying_nedges;
2184  GA_OffsetList cross_section_primoffs;
2185  bool is_valid_grid = computeCombinedCrossSectionProperties(
2186  cross_section_input,
2187  cross_section_group,
2188  cross_section_it,
2189  num_cross_sections,
2190  cross_section_nedges,
2191  cross_section_closed,
2192  cross_section_unrolled,
2193  varying_nedges,
2194  cross_section_primoffs,
2195  is_primtype_auto,
2196  primitive_type,
2197  fallback_orderu,
2198  pbasisu);
2199 
2200  if (!is_valid_grid)
2201  return false;
2202 
2203  grid_info.myCrossSectionNEdges = cross_section_nedges;
2204  grid_info.myCrossSectionClosed = cross_section_closed;
2205  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2206  grid_info.myAllEqualNEdges = !varying_nedges;
2207  grid_info.mySingleCrossSection = (cross_section_primoffs.size() == 1);
2208  if (grid_info.mySingleCrossSection)
2209  grid_info.myCrossSectionPrimOff = cross_section_primoffs(0);
2210  else
2211  grid_info.myCrossSectionPrimOffs = new GA_OffsetList(std::move(cross_section_primoffs));
2212  }
2213  else
2214  {
2215  // Input curve
2216  grid_info.myCurvePrimOff = curve_primoff;
2217 
2218  exint curve_nedges;
2219  bool curve_closed;
2220  bool curve_unrolled;
2221  const GA_OffsetListRef curve_vertices = curve_input->getPrimitiveVertexList(curve_primoff);
2222  bool nonempty = getPolyProperties(curve_input, curve_vertices, curve_nedges, curve_closed, curve_unrolled);
2223 
2224  // Skip curves with no vertices
2225  if (!nonempty)
2226  return false;
2227 
2228  grid_info.myCurveClosed = curve_closed;
2229  grid_info.myCurveUnrolled = curve_unrolled || (curve_closed && could_add_row_seam_vertex);
2230  grid_info.myCurveNEdges = curve_nedges;
2231 
2232  if (single_cross_section)
2233  {
2234  UT_ASSERT_MSG(copy_order != CopyOrder::ATTRIB, "This branch doesn't handle the possibility of invalid cross section selection in attribute.");
2235  grid_info.myCrossSectionNEdges = cross_section_nedges;
2236  grid_info.myCrossSectionClosed = cross_section_closed;
2237  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2238  grid_info.myAllEqualNEdges = true;
2239  grid_info.mySingleCrossSection = true;
2240  grid_info.myCrossSectionPrimOff = single_cross_section_primoff;
2241  }
2242  else
2243  {
2244  UT_ASSERT_MSG(copy_order == CopyOrder::CYCLEVTX || copy_order == CopyOrder::ALL || copy_order == CopyOrder::ATTRIB,
2245  "Other cases should have resolved to single cross section grids.");
2246 
2247  bool varying_nedges = varying_nedges_all_case;
2248  GA_OffsetList cross_section_primoffs;
2249  if (copy_order == CopyOrder::CYCLEVTX)
2250  {
2251  exint num_cross_sections = curve_nedges + !curve_closed;
2252  bool is_valid_grid = computeCombinedCrossSectionProperties(
2253  cross_section_input,
2254  cross_section_group,
2255  cross_section_it,
2256  num_cross_sections,
2257  cross_section_nedges,
2258  cross_section_closed,
2259  cross_section_unrolled,
2260  varying_nedges,
2261  cross_section_primoffs,
2262  is_primtype_auto,
2263  primitive_type,
2264  fallback_orderu,
2265  pbasisu);
2266 
2267  if (!is_valid_grid)
2268  return false;
2269  }
2270  else if (copy_order == CopyOrder::ATTRIB)
2271  {
2272  bool is_valid_grid = computeCombinedCrossSectionPropertiesAttrib(
2273  cross_section_input,
2274  cross_section_group,
2275  curve_input,
2276  curve_vertices,
2277  copy_order_attrib_data,
2278  cross_section_nedges,
2279  cross_section_closed,
2280  cross_section_unrolled,
2281  varying_nedges,
2282  cross_section_primoffs,
2283  is_primtype_auto,
2284  primitive_type,
2285  fallback_orderu,
2286  pbasisu);
2287 
2288  if (!is_valid_grid)
2289  return false;
2290  }
2291 
2292  grid_info.myCrossSectionNEdges = cross_section_nedges;
2293  grid_info.myCrossSectionClosed = cross_section_closed;
2294  grid_info.myCrossSectionUnrolled = cross_section_unrolled || (cross_section_closed && could_add_col_seam_vertex);
2295  grid_info.myAllEqualNEdges = !varying_nedges;
2296  grid_info.mySingleCrossSection = false;
2297  if (copy_order == CopyOrder::ALL)
2298  {
2299  exint ncopies = grid_info.myCurveNEdges + !grid_info.myCurveClosed;
2300  grid_info.myCurveNEdges = (ncopies * cross_section_primoffs_all_case->size()) - !grid_info.myCurveClosed;
2301  grid_info.myCrossSectionPrimOffs = new GA_OffsetList();
2302  for (exint copyi = 0; copyi < ncopies; ++copyi)
2303  {
2304  grid_info.myCrossSectionPrimOffs->append(*cross_section_primoffs_all_case);
2305  }
2306  }
2307  else
2308  {
2309  grid_info.myCrossSectionPrimOffs = new GA_OffsetList(std::move(cross_section_primoffs));
2310  }
2311  }
2312  }
2313 
2314  grid_info.myHasPolygonCaps =
2315  !output_points_only &&
2316  (
2317  (end_cap_type == EndCapType::SINGLE &&
2318  (!grid_info.myCurveClosed && grid_info.myCrossSectionClosed && grid_info.myCrossSectionNEdges > 1)) ||
2319  (end_cap_type == EndCapType::SIDESINGLE &&
2320  (!grid_info.myCrossSectionClosed && grid_info.myCurveClosed && grid_info.myCurveNEdges > 1))
2321  ) &&
2322  !has_rowcol_surface_type;
2323 
2324  // FIXME: Because we haven't sorted out how to represent transforms for U end caps yet, disable them for now!!!
2325  grid_info.myUEndPoles = false;
2326 #if 0
2327  grid_info.myUEndPoles =
2328  !output_points_only &&
2329  (end_cap_type != EndCapType::NONE && end_cap_type != EndCapType::SINGLE && end_cap_type != EndCapType::SIDESINGLE) &&
2330  (!grid_info.myCrossSectionClosed && grid_info.myCurveClosed) &&
2331  cap_divisions > 0;
2332 #endif
2333 
2334  // NOTE: Even non-closed cross sections can yield end "caps", so that
2335  // it's possible to do rounded and pointed ends on ribbons.
2336  // Even the flat case can be useful if the end cross section is not a straight line.
2337  grid_info.myVEndPoles =
2338  !output_points_only &&
2339  (end_cap_type != EndCapType::NONE && end_cap_type != EndCapType::SINGLE && end_cap_type != EndCapType::SIDESINGLE) &&
2340  (!grid_info.myCurveClosed) &&
2341  cap_divisions > 0;
2342 
2343  if (grid_info.myUEndPoles)
2344  {
2345  // Add in the divisions for the first and last col cap
2346  grid_info.myCrossSectionNEdges += 2*cap_divisions;
2347 
2348  // The first and last columns are collapsed to single points.
2349  UT_ASSERT(grid_info.myCurveNEdges >= 1);
2350  }
2351  else if (grid_info.myVEndPoles)
2352  {
2353  // Add in the divisions for the first and last row cap
2354  grid_info.myCurveNEdges += 2*cap_divisions;
2355 
2356  if (!grid_info.mySingleCrossSection)
2357  {
2358  // Add duplicates of first and last cross section primoffs for simplicity later.
2359  GA_OffsetList new_cross_section_primoffs;
2360  GA_OffsetList &primoffs = *grid_info.myCrossSectionPrimOffs;
2361  for (exint i = 0; i < cap_divisions; ++i)
2362  new_cross_section_primoffs.append(primoffs(0));
2363  new_cross_section_primoffs.append(primoffs);
2364  for (exint i = 0; i < cap_divisions; ++i)
2365  new_cross_section_primoffs.append(primoffs.last());
2366  primoffs = std::move(new_cross_section_primoffs);
2367  }
2368 
2369  // The first and last rows are collapsed to single points.
2370  UT_ASSERT(grid_info.myCrossSectionNEdges >= 1);
2371  }
2372 
2373  using PrimitiveType = sop_SweepGrid::PrimitiveType;
2374  grid_info.myPrimitiveType = PrimitiveType::POINTS;
2375  if (!output_points_only)
2376  {
2377  grid_info.myPrimitiveType = PrimitiveType::POLYGON;
2378  if (primitive_type == GA_PRIMPOLYSOUP)
2379  grid_info.myPrimitiveType = PrimitiveType::POLYSOUP;
2380  else if (primitive_type == GA_PRIMMESH)
2381  grid_info.myPrimitiveType = PrimitiveType::MESH;
2382  else if (primitive_type == GA_PRIMNURBSURF)
2383  grid_info.myPrimitiveType = PrimitiveType::NURBS;
2384  else if (primitive_type == GA_PRIMBEZSURF)
2385  grid_info.myPrimitiveType = PrimitiveType::BEZIER;
2386 
2387  // Fall back to polygon if number of rows or cols is too small.
2388  // TODO: Consider supporting outputting a single NURBS/Bezier curve.
2389  if (grid_info.myPrimitiveType != PrimitiveType::POLYGON &&
2390  (grid_info.myCurveNEdges <= 0 || grid_info.myCrossSectionNEdges <= 0))
2391  {
2392  grid_info.myPrimitiveType = PrimitiveType::POLYGON;
2393  }
2394  }
2395  if (grid_info.myPrimitiveType == PrimitiveType::NURBS)
2396  {
2397  if (fallback_orderv == 0)
2398  {
2399  // Either not auto, or curve was polygon but one of the cross sections was NURBS/Bezier.
2400  // Use order 2 if auto, since that's the closest match to polygon,
2401  // but use order 4 if not auto, since the user specifically chose NURBS.
2402  fallback_orderv = (is_primtype_auto && curve_input != nullptr) ? 2 : 4;
2403  }
2404  exint curve_nvertices = grid_info.myCurveNEdges + (!grid_info.myCurveClosed || grid_info.myCurveUnrolled);
2405  if (curve_nvertices < fallback_orderv)
2406  {
2407  // Too few vertices for fallback_orderv
2408  // Clamp just in case we made a mistake elsewhere with a closed 1-vertex curve having 1 edge.
2409  fallback_orderv = SYSclamp(int(curve_nvertices), 2, 3);
2410  }
2411  if (fallback_orderu == 0)
2412  {
2413  // Either not auto, or curve was NURBS but all of the cross sections were polygon.
2414  fallback_orderu = (is_primtype_auto && cross_section_input != nullptr) ? 2 : 4;
2415  }
2416  exint cross_section_nvertices = grid_info.myCrossSectionNEdges + (!grid_info.myCrossSectionClosed || grid_info.myCrossSectionUnrolled);
2417  if (cross_section_nvertices < fallback_orderu)
2418  {
2419  // Too few vertices for fallback_orderu
2420  // Clamp just in case we made a mistake elsewhere with a closed 1-vertex curve having 1 edge.
2421  fallback_orderu = SYSclamp(int(cross_section_nvertices), 2, 3);
2422  }
2423  }
2424  else if (grid_info.myPrimitiveType == PrimitiveType::BEZIER)
2425  {
2426  if (fallback_orderv == 0)
2427  {
2428  // Either not auto, or curve was polygon but one of the cross sections was Bezier.
2429  // Use order 2 if auto, since that's the closest match to polygon,
2430  // but use order 4 if not auto, since the user specifically chose Bezier.
2431  fallback_orderv = is_primtype_auto ? 2 : 4;
2432  }
2433  if (grid_info.myCurveNEdges % (fallback_orderv-1) != 0)
2434  {
2435  // Fall back to order 2, since number of edges isn't correct.
2436  fallback_orderv = 2;
2437  }
2438  if (fallback_orderu == 0)
2439  {
2440  // Either not auto, or curve was Bezier but all of the cross sections were polygon.
2441  fallback_orderu = is_primtype_auto ? 2 : 4;
2442  }
2443  if (grid_info.myCrossSectionNEdges % (fallback_orderu-1) != 0)
2444  {
2445  // Fall back to order 2, since number of edges isn't correct.
2446  fallback_orderu = 2;
2447  }
2448  }
2449  grid_info.myBasisOrderCrossSection = fallback_orderu;
2450  grid_info.myBasisOrderCurve = fallback_orderv;
2451 
2452  // Number of points, taking into account rows/cols that are collapsed into single points.
2453  num_points = (grid_info.myCurveNEdges + !grid_info.myCurveClosed - 2*exint(grid_info.myVEndPoles))
2454  * (grid_info.myCrossSectionNEdges + !grid_info.myCrossSectionClosed - 2*exint(grid_info.myUEndPoles))
2455  + 2*exint(grid_info.myVEndPoles) + 2*exint(grid_info.myUEndPoles);
2456 
2457  return true;
2458 }
2459 
2460 template<typename T>
2461 static void
2462 initGUGrid(
2463  const sop_SweepGrid &grid_info,
2464  const GEO_SurfaceType surface_type,
2465  const bool output_points_only,
2466  const bool triangular_poles,
2467  const bool swap_row_col,
2468  const T grid_start_ptnum,
2469 
2470  GU_GridT<T> &grid)
2471 {
2472  grid.myTriangularPoles = triangular_poles;
2473  // TODO: Add support to GU_GridT for separate unrolled rows vs. unrolled cols.
2474  grid.myUnrollCurves = grid_info.myCurveUnrolled || grid_info.myCrossSectionUnrolled;
2475  grid.mySurfaceType = surface_type;
2476  using PrimitiveType = typename GU_GridT<T>::PrimitiveType;
2477  bool is_single_grid_prim = false;
2478  if (output_points_only)
2479  grid.myPrimitiveType = PrimitiveType::POINTS;
2480  else
2481  {
2482  grid.myPrimitiveType = PrimitiveType(int(grid_info.myPrimitiveType));
2483  is_single_grid_prim = (grid.myPrimitiveType != PrimitiveType::POLYGON);
2484  }
2485 
2486  grid.myFirstColIfNotWrapped = true;
2487  grid.myLastColIfNotWrapped = true;
2488  grid.myFirstRowIfNotWrapped = true;
2489  grid.myLastRowIfNotWrapped = true;
2490  grid.myCurvesIfBasisRowCol = true;
2491 
2492  const exint nedgerows = swap_row_col ? grid_info.myCrossSectionNEdges : grid_info.myCurveNEdges;
2493  const exint nedgecols = swap_row_col ? grid_info.myCurveNEdges : grid_info.myCrossSectionNEdges;
2494  bool vclosed = swap_row_col ? grid_info.myCrossSectionClosed : grid_info.myCurveClosed;
2495  bool uclosed = swap_row_col ? grid_info.myCurveClosed : grid_info.myCrossSectionClosed;
2496  bool uendpoles = swap_row_col ? grid_info.myVEndPoles : grid_info.myUEndPoles;
2497  bool vendpoles = swap_row_col ? grid_info.myUEndPoles : grid_info.myVEndPoles;
2498  if (is_single_grid_prim)
2499  {
2500  const exint nvtxrows = nedgerows + (!vclosed || grid.myUnrollCurves);
2501  const exint nvtxcols = nedgecols + (!uclosed || grid.myUnrollCurves);
2502  if (nvtxrows < 2 || nvtxcols < 2)
2503  {
2504  is_single_grid_prim = false;
2505  grid.myPrimitiveType = PrimitiveType::POLYGON;
2506  }
2507  else
2508  {
2509  unsigned char orderv = swap_row_col ? grid_info.myBasisOrderCrossSection : grid_info.myBasisOrderCurve;
2510  unsigned char orderu = swap_row_col ? grid_info.myBasisOrderCurve : grid_info.myBasisOrderCrossSection;
2511  grid.myBasisOrderV = orderv;
2512  grid.myBasisOrderU = orderu;
2513  }
2514  }
2515  if (vclosed)
2516  {
2517  if (uclosed)
2518  grid.initTorus(nedgerows, nedgecols, grid_start_ptnum);
2519  else if (!uendpoles)
2520  grid.initRowTube(nedgerows, nedgecols, grid_start_ptnum);
2521  else
2522  {
2523  grid.myFirstColIfNotWrapped = false;
2524  grid.myLastColIfNotWrapped = false;
2525  const exint nmid_points = nedgerows*(nedgecols - 1);
2526  grid.initRowSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2527  }
2528  }
2529  else
2530  {
2531  if (uclosed)
2532  {
2533  if (!vendpoles)
2534  grid.initColTube(nedgerows, nedgecols, grid_start_ptnum);
2535  else
2536  {
2537  grid.myFirstRowIfNotWrapped = false;
2538  grid.myLastRowIfNotWrapped = false;
2539  const exint nmid_points = (nedgerows - 1)*nedgecols;
2540  grid.initColSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2541  }
2542  }
2543  else
2544  {
2545  if (!vendpoles)
2546  grid.initSingleGrid(nedgerows, nedgecols, grid_start_ptnum);
2547  else
2548  {
2549  grid.myFirstRowIfNotWrapped = false;
2550  grid.myLastRowIfNotWrapped = false;
2551  const exint nmid_points = (nedgerows - 1)*(nedgecols+1);
2552  grid.initSplitColSphere(nedgerows, nedgecols, grid_start_ptnum, grid_start_ptnum+1+nmid_points, grid_start_ptnum+1);
2553  }
2554  }
2555  }
2556 }
2557 
2558 static void
2559 appendPrimTypeCountPair(
2560  UT_Array<std::pair<int,exint>> &prim_type_count_pairs,
2561  int primtype,
2562  exint count)
2563 {
2564  if (prim_type_count_pairs.isEmpty() || prim_type_count_pairs.last().first != primtype)
2565  {
2566  prim_type_count_pairs.append(std::pair<int,exint>(primtype, count));
2567  }
2568  else
2569  {
2570  prim_type_count_pairs.last().second += count;
2571  }
2572 }
2573 
2574 // NOTE: closed_span_lengths must be non-empty. It can have an entry with value 0 instead.
2575 // createGrids always ensures that closed_span_lengths is not empty.
2576 static void
2577 appendClosedSpans(
2578  UT_Array<exint> &closed_span_lengths,
2579  bool closed)
2580 {
2581  // Odd sizes (even indices) are for open polygon counts.
2582  // Even sizes (odd indices) are for closed polygon counts.
2583  if (!(closed_span_lengths.size() & 1) == closed)
2584  ++closed_span_lengths.last();
2585  else
2586  closed_span_lengths.append(1);
2587 }
2588 
2589 // appends to GEObuildPrimitives arrays, the info for building
2590 // a single grid based on the data in grid_info.
2591 // NOTE: num_grid_prims and num_grid_verts include caps.
2592 static void
2593 appendSingleGridTopology(
2594  const sop_SweepGrid &grid_info,
2595  const GEO_SurfaceType surface_type,
2596  const bool output_points_only,
2597  const bool triangular_poles,
2598  const bool single_polygon_caps,
2599  const bool swap_row_col,
2600 
2601  UT_Array<std::pair<int,exint>> &prim_type_count_pairs,
2602  //GA_Size &npoints, // npoints should already have been computed for grid_info
2603  GA_PolyCounts &vertexlistsizelist,
2604  UT_Array<exint> &vertexpointnumbers,
2605  UT_Array<exint> &closed_span_lengths,
2606  GA_Size &num_grid_prims,
2607  GA_Size &num_grid_verts)
2608 {
2609  // Before creating the geometry, myStartPtOff is relative,
2610  // so just an exint, not a true GA_Offset.
2611  exint grid_start_ptnum = exint(grid_info.myStartPtOff);
2612  GU_GridT<exint> grid;
2613  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_start_ptnum, grid);
2614 
2615  num_grid_prims = grid.myNumPrimitives;
2616  num_grid_verts = grid.myNumVertices;
2617 
2618  if (num_grid_prims == 0)
2619  return;
2620 
2621  bool current_has_polygon_caps = grid_info.myHasPolygonCaps;
2622  bool is_row_cap = current_has_polygon_caps && grid.myNoWrapV;
2623  bool is_cross_section_cap = !grid_info.myCrossSectionClosed;
2624  exint cap_count = current_has_polygon_caps ? 2 : 0;
2625 
2626  int primtype = GA_PRIMPOLY;
2627  int cap_primtype = GA_PRIMPOLY;
2628  using PrimitiveType = GU_GridT<exint>::PrimitiveType;
2629  if (grid.myPrimitiveType == PrimitiveType::NURBS)
2630  {
2631  // FIXME: Take into account grid.myCurvesIfBasisRowCol when implemented!!!
2632  primtype = GA_PRIMNURBSURF;
2633  cap_primtype = GA_PRIMNURBCURVE;
2634  }
2635  else if (grid.myPrimitiveType == PrimitiveType::BEZIER)
2636  {
2637  // FIXME: Take into account grid.myCurvesIfBasisRowCol when implemented!!!
2638  primtype = GA_PRIMBEZSURF;
2639  cap_primtype = GA_PRIMBEZCURVE;
2640  }
2641  else if (grid.myPrimitiveType == PrimitiveType::MESH || grid.myPrimitiveType == PrimitiveType::POLYSOUP)
2642  {
2643  primtype = (grid.myPrimitiveType == PrimitiveType::MESH) ? GA_PRIMMESH : GA_PRIMPOLYSOUP;
2644  cap_primtype = GA_PRIMPOLY;
2645  }
2646 
2647  if (primtype == GA_PRIMPOLY || cap_count == 0)
2648  {
2649  appendPrimTypeCountPair(prim_type_count_pairs, primtype, num_grid_prims + cap_count);
2650  }
2651  else
2652  {
2653  appendPrimTypeCountPair(prim_type_count_pairs, cap_primtype, 1);
2654  appendPrimTypeCountPair(prim_type_count_pairs, primtype, num_grid_prims);
2655  appendPrimTypeCountPair(prim_type_count_pairs, cap_primtype, 1);
2656  }
2657 
2658  exint cap_vertex_count = 0;
2659  if (current_has_polygon_caps)
2660  cap_vertex_count = is_row_cap ? grid.myNumEdgeCols : grid.myNumEdgeRows;
2661  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)
2662  {
2663  vertexlistsizelist.append(cap_vertex_count);
2664 
2665  // Single polygon caps are closed.
2666  appendClosedSpans(closed_span_lengths, true);
2667 
2668  const exint old_size = vertexpointnumbers.size();
2669  vertexpointnumbers.bumpSize(old_size + cap_vertex_count);
2670  exint *vertexpointnumber_start = vertexpointnumbers.getArray() + old_size;
2671  if (is_row_cap)
2672  {
2673  // First or last row
2674  exint row = last_cap ? grid.myNumEdgeRows : 0;
2675  for (exint col = 0; col < cap_vertex_count; ++col)
2676  {
2677  exint point_col = ((last_cap != swap_row_col) != is_cross_section_cap) ? col : reverseVtx(col, cap_vertex_count, true);
2678  vertexpointnumber_start[col] = grid.getPoint(row, point_col);
2679  }
2680  }
2681  else
2682  {
2683  // First or last column
2684  exint col = last_cap ? grid.myNumEdgeCols : 0;
2685  for (exint row = 0; row < cap_vertex_count; ++row)
2686  {
2687  exint point_row = ((last_cap != swap_row_col) != is_cross_section_cap) ? row : reverseVtx(row, cap_vertex_count, true);
2688  vertexpointnumber_start[row] = grid.getPoint(point_row, col);
2689  }
2690  }
2691  };
2692  if (current_has_polygon_caps)
2693  {
2694  // Start cap comes before main grid
2695  add_polygon_cap_functor(false);
2696  }
2697 
2698  // Add main grid primitives
2700  [&vertexlistsizelist,&closed_span_lengths
2702  ,num_grid_prims
2703 #endif
2704  ](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
2705  {
2706  UT_ASSERT_P(primnum >= 0 && primnum < num_grid_prims);
2707  vertexlistsizelist.append(primvtxcount);
2708 
2709  appendClosedSpans(closed_span_lengths, closed);
2710  });
2711 
2712  const exint old_size = vertexpointnumbers.size();
2713  vertexpointnumbers.bumpSize(old_size + grid.myNumVertices);
2714  exint *vertexpointnumber_start = vertexpointnumbers.getArray() + old_size;
2715  GUiterateGridVertices(grid,
2716  [vertexpointnumber_start,&grid](exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
2717  {
2718  UT_ASSERT_P(vtxnum >= 0 && vtxnum < grid.myNumVertices);
2719  vertexpointnumber_start[vtxnum] = grid.getPoint(row+exint(isrowend),col+exint(iscolend));
2720  });
2721 
2722  if (current_has_polygon_caps)
2723  {
2724  // End cap comes after main grid
2725  add_polygon_cap_functor(true);
2726  }
2727 
2728  // Make sure that caps are counted in num_grid_prims and num_grid_verts,
2729  // so that the caller knows the total for this grid (including caps).
2730  num_grid_prims += cap_count;
2731  num_grid_verts += 2*cap_vertex_count;
2732 }
2733 
2734 static void
2735 initNonPolyGridPrims(
2736  const sop_SweepGrid &grid_info,
2737  const GU_GridT<GA_Offset> &grid,
2738  const bool swap_row_col,
2739  const GA_Basis *source_basisu,
2740  const GA_Basis *source_basisv,
2741  GEO_Detail *output_geo)
2742 {
2743  using PrimitiveType = GU_GridT<GA_Offset>::PrimitiveType;
2744  if (grid.myPrimitiveType == PrimitiveType::POLYGON ||
2745  grid.myPrimitiveType == PrimitiveType::POINTS)
2746  return;
2747 
2748  GA_Offset primoff = grid_info.myStartPrimOff;
2749  if (grid_info.myHasPolygonCaps)
2750  ++primoff;
2751 
2752  GA_Primitive *prim = output_geo->getPrimitive(primoff);
2753  const exint nedgerows = grid.myNumEdgeRows;
2754  const exint nedgecols = grid.myNumEdgeCols;
2755  const bool hull_wrapv = grid.myAllWrapV && !grid.myUnrollCurves;
2756  const bool hull_wrapu = grid.myAllWrapU && !grid.myUnrollCurves;
2757  const exint nvtxrows = nedgerows + exint(!hull_wrapv);
2758  const exint nvtxcols = nedgecols + exint(!hull_wrapu);
2759 
2760  if (grid.myPrimitiveType == PrimitiveType::POLYSOUP)
2761  {
2762  GEO_PrimPolySoup *polysoup = UTverify_cast<GEO_PrimPolySoup *>(prim);
2763 
2764  // Iterate over the internal polygons
2765  sop_SweepGrid polygrid_info(grid_info);
2766  polygrid_info.myPrimitiveType = sop_SweepGrid::PrimitiveType::POLYGON;
2767  GU_GridT<GA_Offset> polygrid;
2768  initGUGrid(polygrid_info, grid.mySurfaceType, false, grid.myTriangularPoles, swap_row_col, polygrid_info.myStartPtOff, polygrid);
2769 
2770  GA_PolyCounts polygonsizes;
2771  GUiterateGridPrimitives(polygrid,
2772  [&polygonsizes](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
2773  {
2774  polygonsizes.append(primvtxcount, 1);
2775  });
2776 
2777  // Preallocate memory for polygonvertexlist,
2778  // since it's unlikely to be trivial-compressed anyway.
2779  GA_OffsetList polygonvertexlist;
2780  polygonvertexlist.harden(polygrid.myNumVertices);
2781 
2782  // The following comment and two bool definitions were copied from GUiterateGridVertices.
2783  // "Unroll" in this case allows for UV seams.
2784  // FIXME: Does this need to take into account myFirstRowIfNotWrapped,
2785  // myLastRowIfNotWrapped, myFirstColIfNotWrapped, myLastColIfNotWrapped
2786  // if surface_type is rows or cols or rowcol?
2787  const bool has_endrow = grid.myNoWrapV || grid.myUnrollCurves;
2788  const bool has_endcol = grid.myNoWrapU || grid.myUnrollCurves;
2789  const exint nvtxcols = grid.myNumEdgeCols + exint(has_endcol);
2790 
2791  // Remember that the polygon soup primitive's vertices start after the first cap.
2792  exint cap_vertex_count = 0;
2793  if (grid_info.myHasPolygonCaps)
2794  {
2795  cap_vertex_count = !grid_info.myCurveClosed ? grid_info.myCrossSectionNEdges : grid_info.myCurveNEdges;
2796  }
2797  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
2798  GUiterateGridVertices(polygrid,
2799  [&polygonvertexlist,start_vtxoff,&grid,has_endrow,has_endcol,nvtxcols](exint vtxnum, exint row, exint col,
2800  bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
2801  {
2802  // NOTE: vtxnum is the vtxnum if it were polygons, but we need the vtxnum within the soup.
2803  if (isrowend)
2804  {
2805  if (!has_endrow && row == grid.myNumEdgeRows-1)
2806  row = 0;
2807  else
2808  ++row;
2809  }
2810  if (iscolend)
2811  {
2812  if (!has_endcol && col == grid.myNumEdgeCols-1)
2813  col = 0;
2814  else
2815  ++col;
2816  }
2817  exint soupvtxnum = row*nvtxcols + col;
2818  polygonvertexlist.append(start_vtxoff + soupvtxnum);
2819  });
2820 
2821  // This initializes GEO_PrimPolySoup::myPolygonSizes and myPolygonVertexList
2822  polysoup->appendPolygons(polygonsizes, polygonvertexlist);
2823 
2824  return;
2825  }
2826 
2827  // Bilinear mesh, NURBS surface, or Bezier surface
2828  GEO_Hull *hull = UTverify_cast<GEO_Hull *>(prim);
2829  hull->initHullData(nvtxrows, nvtxcols, hull_wrapv, hull_wrapu);
2830  hull->setSurfaceType(grid.mySurfaceType);
2831 
2832  const bool is_nurbs = (grid.myPrimitiveType == PrimitiveType::NURBS);
2833  const bool is_bezier = (grid.myPrimitiveType == PrimitiveType::BEZIER);
2834  if (!is_nurbs && !is_bezier)
2835  return;
2836 
2837  GEO_TPSurf *tpsurf = UTverify_cast<GEO_TPSurf *>(hull);
2838 
2839  GA_Basis *basisu = nullptr;
2840  GA_Basis *basisv = nullptr;
2841 
2842  GA_BASIS_TYPE basis_type = is_nurbs ? GA_NURBS_BASIS : GA_BEZIER_BASIS;
2843  if (source_basisu != nullptr && source_basisu->getType() != basis_type)
2844  source_basisu = nullptr;
2845  if (source_basisv != nullptr && source_basisv->getType() != basis_type)
2846  source_basisv = nullptr;
2847 
2848  // Copy any source basis
2849  if (source_basisu != nullptr)
2850  {
2851  basisu = GA_Basis::newSpecies(basis_type);
2852  basisu->copyFrom(*source_basisu);
2853 
2854  tpsurf->setUBasis(basisu);
2855  // NOTE: The "Ensure Unique Seam Vertices" option might mean
2856  // the source basis isn't valid for the target.
2857  if (!tpsurf->getUBasis())
2858  {
2859  delete basisu;
2860  basisu = nullptr;
2861  }
2862  }
2863  if (source_basisv != nullptr)
2864  {
2865  basisv = GA_Basis::newSpecies(basis_type);
2866  basisv->copyFrom(*source_basisv);
2867 
2868  tpsurf->setVBasis(basisv);
2869  // NOTE: The "Ensure Unique Seam Vertices" option might mean
2870  // the source basis isn't valid for the target.
2871  if (!tpsurf->getVBasis())
2872  {
2873  delete basisv;
2874  basisv = nullptr;
2875  }
2876  }
2877 
2878  if (is_nurbs)
2879  {
2880  auto &&make_nurbs_basis = [](exint nvertices, int order, bool closed) -> GA_NUBBasis*
2881  {
2882  // For now, we never end-interpolate closed cases.
2883  bool interpolate_ends = !closed;
2884  int extra_knots = order + (closed ? (interpolate_ends ? 1 : (order - 1)) : 0);
2885  // Number of knots = cvs+order.
2886  // However, if closed we need to add yet a few more knots:
2887  // 1 if interpEnds and order-1 otherwise
2888  return new GA_NUBBasis(0, 1,
2889  (nvertices + extra_knots),
2890  order,
2891  interpolate_ends);
2892  };
2893 
2894  if (basisu == nullptr)
2895  {
2896  basisu = make_nurbs_basis(nvtxcols, grid.myBasisOrderU, hull_wrapu);
2897  tpsurf->setUBasis(basisu);
2898  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2899  }
2900  if (basisv == nullptr)
2901  {
2902  basisv = make_nurbs_basis(nvtxrows, grid.myBasisOrderV, hull_wrapv);
2903  tpsurf->setVBasis(basisv);
2904  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2905  }
2906  }
2907  else
2908  {
2909  UT_ASSERT(is_bezier);
2910 
2911  auto &&make_bez_basis = [](exint nvertices, int order, bool closed) -> GA_BezBasis*
2912  {
2913  int extra = ((nvertices > 2 && order > 2) || (order <= 2 && closed));
2914  return new GA_BezBasis(0, 1,
2915  extra + (nvertices / (order - 1)),
2916  order);
2917  };
2918  if (basisu == nullptr)
2919  {
2920  basisu = make_bez_basis(nvtxcols, grid.myBasisOrderU, hull_wrapu);
2921  tpsurf->setUBasis(basisu);
2922  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2923  }
2924  if (basisv == nullptr)
2925  {
2926  basisv = make_bez_basis(nvtxrows, grid.myBasisOrderV, hull_wrapv);
2927  tpsurf->setVBasis(basisv);
2928  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2929  }
2930  }
2931 
2932  // setUBasis and setVBasis should always have succeeded,
2933  // because we created the bases correctly.
2934  UT_ASSERT(tpsurf->getUBasis() != nullptr);
2935  UT_ASSERT(tpsurf->getVBasis() != nullptr);
2936 
2937  if (grid_info.myHasPolygonCaps)
2938  {
2939  const bool side_caps = !grid_info.myCrossSectionClosed;
2940  const GA_Basis *cap_source_basis = (swap_row_col != side_caps) ? basisv : basisu;
2941  // Copy the U basis for each of the cap curves.
2942  GA_Basis *cap0_basis = GA_Basis::newSpecies(cap_source_basis->getType());
2943  GA_Basis *cap1_basis = GA_Basis::newSpecies(cap_source_basis->getType());
2944  cap0_basis->copyFrom(*cap_source_basis);
2945  cap1_basis->copyFrom(*cap_source_basis);
2946  // The caps are just before and just after the surface primitive.
2947  GEO_Curve *cap0 = UTverify_cast<GEO_Curve *>(output_geo->getPrimitive(primoff-1));
2948  GEO_Curve *cap1 = UTverify_cast<GEO_Curve *>(output_geo->getPrimitive(primoff+1));
2949  cap0->setBasis(cap0_basis);
2950  cap1->setBasis(cap1_basis);
2951  }
2952 }
2953 
2954 static bool
2955 createGrids(
2956  const GEO_Detail *cross_section_input,
2957  const GA_PrimitiveGroup *cross_section_group,
2958  SurfaceShape cross_section_shape,
2959  exint cross_section_nedges,
2960  const CopyOrder copy_order,
2961  const CrossSectionAttribMatchData *const copy_order_attrib_data,
2962 
2963  const GEO_Detail *curve_input,
2964  const GA_PrimitiveGroup *curve_group,
2965  const bool closed_if_no_curve_input,
2966 
2967  const GEO_SurfaceType surface_type,
2968  const bool output_points_only,
2969  const bool unroll_closed_row_col,
2970  const int primitive_type,
2971  const EndCapType end_cap_type,
2972  const exint cap_divisions,
2973  const bool triangular_poles,
2974  const bool swap_row_col,
2975 
2976  GEO_Detail *output_geo,
2977  UT_Array<sop_SweepGrid> &grids)
2978 {
2979  // Clear the array of grids.
2980  grids.setCapacity(0);
2981 
2982  // Destroy whatever was in the previous detail.
2983  output_geo->clearAndDestroy();
2984 
2985  UT_AutoInterrupt interrupt("Computing surface topology");
2986  if (interrupt.wasInterrupted())
2987  return false;
2988 
2989  // These arrays keep a pointer to the U or V basis of each grid,
2990  // if there are NURBS/Bezier curves or cross sections.
2991  UT_Array<const GA_Basis*> grid_basisus;
2992  UT_Array<const GA_Basis*> grid_basisvs;
2993  if (cross_section_input != nullptr && (
2994  cross_section_input->countPrimitiveType(GA_PRIMNURBCURVE) != 0 ||
2995  cross_section_input->countPrimitiveType(GA_PRIMBEZCURVE) != 0))
2996  {
2997  // Some grids may be invalid, so this will sometimes overallocate, but should be fine.
2998  exint max_num_grids = 1;
2999  if (curve_input != nullptr)
3000  max_num_grids = curve_group ? curve_group->entries() : curve_input->getNumPrimitives();
3001  grid_basisus.setSizeNoInit(max_num_grids);
3002  grid_basisus.constant(nullptr);
3003  }
3004  if (curve_input != nullptr && (
3005  curve_input->countPrimitiveType(GA_PRIMNURBCURVE) != 0 ||
3006  curve_input->countPrimitiveType(GA_PRIMBEZCURVE) != 0))
3007  {
3008  // Some grids may be invalid, so this will sometimes overallocate, but should be fine.
3009  const exint max_num_grids = curve_group ? curve_group->entries() : curve_input->getNumPrimitives();
3010  grid_basisvs.setSizeNoInit(max_num_grids);
3011  grid_basisvs.constant(nullptr);
3012  }
3013 
3014  bool single_cross_section = true;
3015  GA_Offset single_cross_section_primoff = GA_INVALID_OFFSET;
3016  bool cross_section_closed = (cross_section_shape == SurfaceShape::TUBE || cross_section_shape == SurfaceShape::SQUARE);
3017  bool cross_section_unrolled = false;
3018  GA_Range cross_section_range;
3019  if (cross_section_input != nullptr)
3020  {
3021  // If cross_section_group is null, this will be the whole range of primitives.
3022  cross_section_range = cross_section_input->getPrimitiveRange(cross_section_group);
3023  GA_Size ncross_sections = cross_section_range.getEntries();
3024  if (ncross_sections == 0)
3025  {
3026  // Nothing to do if no cross sections.
3027  // We must exit early, because code below assumes that there's
3028  // at least one cross section.
3029  return true;
3030  }
3031  // If selecting cross sections by attribute, there may be some invalid selections
3032  // that must be handled, even if there's only one valid selection.
3033  single_cross_section = (cross_section_range.getEntries() == 1 && copy_order != CopyOrder::ATTRIB);
3034  if (single_cross_section)
3035  {
3036  // Special case for a single cross section, since
3037  // it's a common case, and can be faster.
3038  GA_Iterator it(cross_section_range);
3039  UT_ASSERT(!it.atEnd());
3040  single_cross_section_primoff = *it;
3041  bool nonempty = getPolyProperties(cross_section_input, single_cross_section_primoff,
3042  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3043  if (!nonempty)
3044  return true;
3045  }
3046  }
3047 
3048  GA_Iterator cross_section_it;
3049  if (!single_cross_section || curve_input == nullptr)
3050  cross_section_it = GA_Iterator(cross_section_range);
3051 
3052  const bool is_primtype_auto = !output_points_only && (primitive_type == GA_PRIMNONE);
3053 
3054  UT_SmallArray<std::pair<int,exint>,sizeof(std::pair<int,exint>)> prim_type_count_pairs;
3055  GA_PolyCounts vertexlistsizelist;
3056  UT_Array<exint> vertexpointnumbers;
3057  UT_SmallArray<exint,2*sizeof(exint)> closed_span_lengths;
3058  // Code in appendSingleGridTopology requires at least one initial entry in closed_span_lengths.
3059  closed_span_lengths.append(0);
3060 
3061  // TODO: If it's worthwhile for the no shared points case,
3062  // take into account unrolling to determine hassharedpoints accurately.
3063  bool hassharedpoints = true;//(!output_points_only && surface_type != GEO_PATCH_COLS && surface_type != GEO_PATCH_ROWS);
3064  exint total_num_points = 0;
3065  exint total_num_verts = 0;
3066  exint total_num_prims = 0;
3067  if (curve_input == nullptr)
3068  {
3069  // No curve input, so just one grid of all cross sections.
3070  UT_ASSERT(cross_section_input != nullptr);
3071 
3072  UT_ASSERT(grid_basisvs.size() == 0);
3073 
3074  int local_primitive_type = primitive_type;
3075  unsigned char fallback_orderu = 0;
3076  unsigned char fallback_orderv;
3077  initPrimType(
3078  output_points_only,
3079  is_primtype_auto,
3080  local_primitive_type,
3081  fallback_orderv,
3082  nullptr,
3083  nullptr, GA_INVALID_OFFSET); // curve
3084 
3085  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[0];
3086 
3087  sop_SweepGrid grid_info;
3088  exint num_points;
3089  bool valid_grid = computeSingleGridSetup(
3090  cross_section_input,
3091  cross_section_group,
3092  false, // single_cross_section
3093  cross_section_it,
3095  0, false, false, CopyOrder::CYCLEVTX, nullptr, false, nullptr, // unused
3096  nullptr, GA_INVALID_OFFSET, // curve
3097  closed_if_no_curve_input,
3098  surface_type, output_points_only,
3099  unroll_closed_row_col,
3100  is_primtype_auto,
3101  local_primitive_type,
3102  fallback_orderu, fallback_orderv,
3103  pbasisu,
3104  end_cap_type,
3105  cap_divisions,
3106  grid_info, num_points);
3107 
3108  if (!valid_grid)
3109  return true;
3110 
3111  grid_info.myStartPtOff = GA_Offset(0);
3112  total_num_points = num_points;
3113 
3114  exint num_grid_prims;
3115  exint num_grid_verts;
3116  appendSingleGridTopology(
3117  grid_info, surface_type, output_points_only, triangular_poles,
3118  grid_info.myHasPolygonCaps, swap_row_col,
3119  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3120  closed_span_lengths, num_grid_prims, num_grid_verts);
3121 
3122  grid_info.myStartVtxOff = GA_Offset(0);
3123  total_num_verts = num_grid_verts;
3124  grid_info.myStartPrimOff = GA_Offset(0);
3125  total_num_prims = num_grid_prims;
3126  grids.append(std::move(grid_info));
3127  }
3128  else if (!single_cross_section && copy_order == CopyOrder::EACH)
3129  {
3130  // For each cross section, loop over all curves.
3131  // FIXME: Parallelize this!!!
3132  for (; !cross_section_it.atEnd(); ++cross_section_it)
3133  {
3134  GA_Offset cross_section_primoff = *cross_section_it;
3135 
3136  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3137  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3138  if (!nonempty)
3139  continue;
3140 
3141  GA_Offset start;
3142  GA_Offset end;
3143  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3144  {
3145  if (interrupt.wasInterrupted())
3146  return false;
3147 
3148  GA_Iterator invalid_it;
3149 
3150  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3151  {
3152  const exint gridi = grids.size();
3153  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3154  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3155 
3156  int local_primitive_type = primitive_type;
3157  unsigned char fallback_orderu = 0;
3158  unsigned char fallback_orderv;
3159  initPrimType(
3160  output_points_only,
3161  is_primtype_auto,
3162  local_primitive_type,
3163  fallback_orderv,
3164  pbasisv,
3165  curve_input,
3166  curve_primoff);
3167 
3168  // Single cross section, so primitive type must be handled in advance.
3169  if (is_primtype_auto && cross_section_input != nullptr)
3170  updateAutoPrimType(cross_section_input, cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3171 
3172  sop_SweepGrid grid_info;
3173  exint num_points;
3174  bool valid_grid = computeSingleGridSetup(
3175  nullptr, nullptr, // cross section resolved above
3176  true, // single_cross_section for this grid
3177  invalid_it,
3178  cross_section_primoff,
3179  cross_section_nedges,
3180  cross_section_closed,
3181  cross_section_unrolled,
3182  copy_order,
3183  nullptr,// copy_order_attrib_data unused
3184  false, // varying_nedges_all_case unused
3185  nullptr,// cross_section_primoffs_all_case unused
3186  curve_input, curve_primoff,
3187  false, // closed_if_no_curve_input unused
3188  surface_type,
3189  output_points_only,
3190  unroll_closed_row_col,
3191  is_primtype_auto,
3192  local_primitive_type,
3193  fallback_orderu, fallback_orderv,
3194  pbasisu,
3195  end_cap_type,
3196  cap_divisions,
3197  grid_info,
3198  num_points);
3199 
3200  if (!valid_grid)
3201  continue;
3202 
3203  grid_info.myStartPtOff = GA_Offset(total_num_points);
3204  total_num_points += num_points;
3205 
3206  exint num_grid_prims;
3207  exint num_grid_verts;
3208  appendSingleGridTopology(
3209  grid_info, surface_type, output_points_only, triangular_poles,
3210  grid_info.myHasPolygonCaps, swap_row_col,
3211  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3212  closed_span_lengths, num_grid_prims, num_grid_verts);
3213 
3214  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3215  total_num_verts += num_grid_verts;
3216  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3217  total_num_prims += num_grid_prims;
3218  grids.append(std::move(grid_info));
3219  }
3220  }
3221  }
3222  }
3223  else if (!single_cross_section && (copy_order == CopyOrder::CYCLEPR ||
3224  (copy_order == CopyOrder::ATTRIB &&
3225  (copy_order_attrib_data->myCurveAttribOwner == GA_ATTRIB_DETAIL ||
3226  copy_order_attrib_data->myCurveAttribOwner == GA_ATTRIB_PRIMITIVE))))
3227  {
3228  // CopyOrder::CYCLEPR means "For each curve, pick the next cross section."
3229  // CopyOrder::ATTRIB has primitive or detail attribute on curve input selecting the cross section primitives in this case.
3230  GA_AttributeOwner curve_attrib_owner = (copy_order == CopyOrder::ATTRIB) ? copy_order_attrib_data->myCurveAttribOwner : GA_ATTRIB_INVALID;
3231  GA_Offset cross_section_primoff;
3232  if (curve_attrib_owner == GA_ATTRIB_DETAIL)
3233  {
3234  cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data,
3235  GA_DETAIL_OFFSET, cross_section_input, cross_section_group);
3236  // If no cross section for this detail, skip
3237  if (!GAisValid(cross_section_primoff))
3238  return true;
3239  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3240  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3241  if (!nonempty)
3242  return true;
3243  }
3244 
3245  GA_Offset start;
3246  GA_Offset end;
3247  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3248  {
3249  if (interrupt.wasInterrupted())
3250  return false;
3251 
3252  GA_Iterator invalid_it;
3253 
3254  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3255  {
3256  if (copy_order == CopyOrder::CYCLEPR)
3257  {
3258  cross_section_primoff = *cross_section_it;
3259  ++cross_section_it;
3260  if (cross_section_it.atEnd())
3261  {
3262  cross_section_it.rewind();
3263  UT_ASSERT(!cross_section_it.atEnd());
3264  }
3265  }
3266  else if (curve_attrib_owner == GA_ATTRIB_PRIMITIVE)
3267  {
3268  cross_section_primoff = lookupCrossSectionFromAttrib(copy_order_attrib_data,
3269  curve_primoff, cross_section_input, cross_section_group);
3270  // If no cross section for this curve, skip
3271  if (!GAisValid(cross_section_primoff))
3272  continue;
3273  }
3274 
3275  if (curve_attrib_owner != GA_ATTRIB_DETAIL)
3276  {
3277  bool nonempty = getPolyProperties(cross_section_input, cross_section_primoff,
3278  cross_section_nedges, cross_section_closed, cross_section_unrolled);
3279  if (!nonempty)
3280  continue;
3281  }
3282 
3283  const exint gridi = grids.size();
3284  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3285  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3286 
3287  int local_primitive_type = primitive_type;
3288  unsigned char fallback_orderu = 0;
3289  unsigned char fallback_orderv;
3290  initPrimType(
3291  output_points_only,
3292  is_primtype_auto,
3293  local_primitive_type,
3294  fallback_orderv,
3295  pbasisv,
3296  curve_input,
3297  curve_primoff);
3298 
3299  // Single cross section, so primitive type must be handled in advance.
3300  if (is_primtype_auto && cross_section_input != nullptr)
3301  updateAutoPrimType(cross_section_input, cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3302 
3303  sop_SweepGrid grid_info;
3304  exint num_points;
3305  bool valid_grid = computeSingleGridSetup(
3306  nullptr, nullptr, // cross section resolved above
3307  true, // single_cross_section for this grid
3308  invalid_it,
3309  cross_section_primoff,
3310  cross_section_nedges,
3311  cross_section_closed,
3312  cross_section_unrolled,
3313  copy_order,
3314  nullptr,// copy_order_attrib_data unused
3315  false, // varying_nedges_all_case unused
3316  nullptr,// cross_section_primoffs_all_case unused
3317  curve_input, curve_primoff,
3318  false, // closed_if_no_curve_input unused
3319  surface_type, output_points_only,
3320  unroll_closed_row_col,
3321  is_primtype_auto,
3322  local_primitive_type,
3323  fallback_orderu, fallback_orderv,
3324  pbasisu,
3325  end_cap_type,
3326  cap_divisions,
3327  grid_info, num_points);
3328 
3329  if (!valid_grid)
3330  continue;
3331 
3332  grid_info.myStartPtOff = GA_Offset(total_num_points);
3333  total_num_points += num_points;
3334 
3335  exint num_grid_prims;
3336  exint num_grid_verts;
3337  appendSingleGridTopology(
3338  grid_info, surface_type, output_points_only, triangular_poles,
3339  grid_info.myHasPolygonCaps, swap_row_col,
3340  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3341  closed_span_lengths, num_grid_prims, num_grid_verts);
3342 
3343  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3344  total_num_verts += num_grid_verts;
3345  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3346  total_num_prims += num_grid_prims;
3347  grids.append(std::move(grid_info));
3348  }
3349  }
3350  }
3351  else
3352  {
3353  unsigned char fallback_orderu = 0;
3354  int cross_section_primitive_type = is_primtype_auto ? GA_PRIMPOLY : primitive_type;
3355  bool varying_nedges_all_case = false;
3356  GA_OffsetList cross_section_primoffs_all_case;
3357  const bool all_case = (!single_cross_section && copy_order == CopyOrder::ALL);
3358  const GA_Basis *all_case_grid_basisu = nullptr;
3359  if (all_case)
3360  {
3361  // At each vertex, we'll want all cross sections.
3362  // To avoid having to recompute the cross section properties for every vertex,
3363  // iterate through all cross sections in advance.
3364  exint num_cross_sections = (cross_section_group ? cross_section_group->entries() : cross_section_input->getNumPrimitives());
3365 
3366  bool is_valid_grid = computeCombinedCrossSectionProperties(
3367  cross_section_input,
3368  cross_section_group,
3369  cross_section_it,
3370  num_cross_sections,
3371  cross_section_nedges,
3372  cross_section_closed,
3373  cross_section_unrolled,
3374  varying_nedges_all_case,
3375  cross_section_primoffs_all_case,
3376  is_primtype_auto,
3377  cross_section_primitive_type,
3378  fallback_orderu,
3379  &all_case_grid_basisu);
3380 
3381  if (!is_valid_grid)
3382  return true;
3383  }
3384 
3385  // FIXME: Parallelize this!!!
3386  GA_Offset start;
3387  GA_Offset end;
3388  for (GA_Iterator it(curve_input->getPrimitiveRange(curve_group)); it.blockAdvance(start, end); )
3389  {
3390  if (interrupt.wasInterrupted())
3391  return false;
3392 
3393  for (GA_Offset curve_primoff = start; curve_primoff < end; ++curve_primoff)
3394  {
3395  const exint gridi = grids.size();
3396  const GA_Basis **pbasisu = grid_basisus.isEmpty() ? nullptr : &grid_basisus[gridi];
3397  const GA_Basis **pbasisv = grid_basisvs.isEmpty() ? nullptr : &grid_basisvs[gridi];
3398 
3399  int local_primitive_type = primitive_type;
3400  unsigned char fallback_orderv;
3401  initPrimType(
3402  output_points_only,
3403  is_primtype_auto,
3404  local_primitive_type,
3405  fallback_orderv,
3406  pbasisv,
3407  curve_input,
3408  curve_primoff);
3409 
3410  // If single cross section, primitive type must be handled in advance.
3411  if (single_cross_section && is_primtype_auto && cross_section_input != nullptr)
3412  updateAutoPrimType(cross_section_input, single_cross_section_primoff, local_primitive_type, fallback_orderu, pbasisu);
3413  else if (all_case && local_primitive_type != GA_PRIMNURBSURF)
3414  {
3415  if (cross_section_primitive_type == GA_PRIMNURBSURF || cross_section_primitive_type == GA_PRIMBEZSURF)
3416  local_primitive_type = cross_section_primitive_type;
3417  }
3418 
3419  if (all_case && pbasisu != nullptr)
3420  {
3421  // In the all cross sections at each vertex case,
3422  // all grids have the same U basis.
3423  *pbasisu = all_case_grid_basisu;
3424  }
3425 
3426  sop_SweepGrid grid_info;
3427  exint num_points;
3428  bool valid_grid = computeSingleGridSetup(
3429  cross_section_input,
3430  cross_section_group,
3431  single_cross_section,
3432  cross_section_it,
3433  single_cross_section_primoff,
3434  cross_section_nedges,
3435  cross_section_closed,
3436  cross_section_unrolled,
3437  copy_order,
3438  copy_order_attrib_data,
3439  varying_nedges_all_case,
3440  (cross_section_primoffs_all_case.size() == 0) ? nullptr : &cross_section_primoffs_all_case,
3441  curve_input, curve_primoff,
3442  closed_if_no_curve_input,
3443  surface_type, output_points_only,
3444  unroll_closed_row_col,
3445  is_primtype_auto,
3446  local_primitive_type,
3447  fallback_orderu, fallback_orderv,
3448  pbasisu,
3449  end_cap_type,
3450  cap_divisions,
3451  grid_info, num_points);
3452 
3453  if (!valid_grid)
3454  continue;
3455 
3456  grid_info.myStartPtOff = GA_Offset(total_num_points);
3457  total_num_points += num_points;
3458 
3459  exint num_grid_prims;
3460  exint num_grid_verts;
3461  appendSingleGridTopology(
3462  grid_info, surface_type, output_points_only, triangular_poles,
3463  grid_info.myHasPolygonCaps, swap_row_col,
3464  prim_type_count_pairs, vertexlistsizelist, vertexpointnumbers,
3465  closed_span_lengths, num_grid_prims, num_grid_verts);
3466 
3467  grid_info.myStartVtxOff = GA_Offset(total_num_verts);
3468  total_num_verts += num_grid_verts;
3469  grid_info.myStartPrimOff = GA_Offset(total_num_prims);
3470  total_num_prims += num_grid_prims;
3471  grids.append(std::move(grid_info));
3472  }
3473  }
3474  }
3475 
3476  if (interrupt.wasInterrupted())
3477  return false;
3478 
3479  // Create the geometry in output_geo
3480  GA_Offset start_ptoff = output_geo->appendPointBlock(total_num_points);
3481  GA_Offset start_vtxoff = GA_INVALID_OFFSET;
3482  GA_Offset start_primoff = GA_INVALID_OFFSET;
3483  if (output_points_only)
3484  output_geo->bumpDataIdsForAddOrRemove(true, false, false);
3485  else
3486  {
3487  start_primoff = GEObuildPrimitives(
3488  output_geo,
3489  prim_type_count_pairs.getArray(),
3490  start_ptoff, total_num_points,
3491  vertexlistsizelist, vertexpointnumbers.getArray(),
3492  hassharedpoints,
3493  closed_span_lengths.getArray());
3494  if (output_geo->getNumVertices() != 0)
3495  start_vtxoff = output_geo->vertexOffset(GA_Index(0));
3496 
3497  output_geo->bumpDataIdsForAddOrRemove(true, true, true);
3498  }
3499 
3500  // NOTE: Since there was never anything else in the detail, start_ptoff,
3501  // start_primoff, and start_vtxoff should all be GA_Offset(0),
3502  // but just in case we ever have anything before it, we'll write
3503  // the code as if they could be anything.
3504 
3505  // Adjust all myStartPtOff, myStartVtxOff, and myStartPrimOff if starts aren't zero.
3506  if (start_ptoff != GA_Offset(0))
3507  {
3508  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3509  {
3510  grids[gridi].myStartPtOff += start_ptoff;
3511  }
3512  }
3513  if (start_vtxoff != GA_INVALID_OFFSET && start_vtxoff != GA_Offset(0))
3514  {
3515  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3516  {
3517  grids[gridi].myStartVtxOff += start_vtxoff;
3518  }
3519  }
3520  if (start_primoff != GA_INVALID_OFFSET && start_primoff != GA_Offset(0))
3521  {
3522  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3523  {
3524  grids[gridi].myStartPrimOff += start_primoff;
3525  }
3526  }
3527 
3528  if (output_geo->getNumPrimitives() != output_geo->countPrimitiveType(GA_PRIMPOLY))
3529  {
3530  // There are non-polygon primitives, so we must initialize
3531  // primitive member data.
3532  for (exint gridi = 0, num_grids = grids.size(); gridi < num_grids; ++gridi)
3533  {
3534  const sop_SweepGrid &grid_info = grids[gridi];
3535  GU_GridT<GA_Offset> grid;
3536  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3537  const GA_Basis *basisu = grid_basisus.isEmpty() ? nullptr : grid_basisus[gridi];
3538  const GA_Basis *basisv = grid_basisvs.isEmpty() ? nullptr : grid_basisvs[gridi];
3540  {
3541  initNonPolyGridPrims(grid_info, grid, swap_row_col, basisu, basisv, output_geo);
3542  }
3543  }
3544  }
3545 
3546  return true;
3547 }
3548 
3549 template<typename FUNCTOR>
3550 static void
3551 copyVertexCrossSectionWrapper(
3552  const GU_GridT<GA_Offset> &grid,
3553  const sop_SweepGrid &grid_info,
3554  const FUNCTOR &functor)
3555 {
3556  // FIXME: Is this function producing correct results when swap_row_col is true???!!!
3557 
3558  exint cap_vertex_count = 0;
3559  if (grid_info.myHasPolygonCaps)
3560  {
3561  if (grid.myNoWrapV)
3562  {
3563  // First row cap
3564  cap_vertex_count = grid.myNumEdgeCols;
3565 
3566  for (exint col = 0; col < cap_vertex_count; ++col)
3567  {
3568  // First cap is reversed
3569  functor(grid_info.myStartVtxOff + col, 0, reverseVtx(col, cap_vertex_count, true));
3570  }
3571  }
3572  else
3573  {
3574  // First side col cap
3575  cap_vertex_count = grid.myNumEdgeRows;
3576 
3577  for (exint row = 0; row < cap_vertex_count; ++row)
3578  {
3579  // First cap is reversed
3580  functor(grid_info.myStartVtxOff + row, reverseVtx(row, cap_vertex_count, true), 0);
3581  }
3582  }
3583  }
3584  // Main grid vertices
3585  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
3586  auto &&functor_wrapper = [&functor,start_vtxoff](exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
3587  {
3588  GA_Offset output_vtxoff = start_vtxoff + vtxnum;
3589  functor(output_vtxoff, row + exint(isrowend), col + exint(iscolend));
3590  };
3591  GUiterateGridVertices(grid, functor_wrapper);
3592  if (grid_info.myHasPolygonCaps)
3593  {
3594  // Last cap starts after main grid
3595  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
3596  if (grid.myNoWrapV)
3597  {
3598  // Last row cap
3599  for (exint col = 0; col < cap_vertex_count; ++col)
3600  {
3601  functor(cap_start_vtxoff + col, grid.myNumEdgeRows, col);
3602  }
3603  }
3604  else
3605  {
3606  // Last side col cap
3607  for (exint row = 0; row < cap_vertex_count; ++row)
3608  {
3609  functor(cap_start_vtxoff + row, row, grid.myNumEdgeCols);
3610  }
3611  }
3612  }
3613 }
3614 
3615 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>
3616 static void
3617 copyCrossSectionAttributeWrapper2(
3618  const GU_GridT<GA_Offset> &grid,
3619  const OUTPUT_ATTRIB_T &outputh,
3620  const INPUT_ATTRIB_T &cross_sectionh,
3621  const GA_AttributeOwner cross_section_owner,
3622  const GEO_Detail *cross_section_input,
3623  const sop_SweepGridTransformWrapper *transforms,
3624  const sop_SweepGrid &grid_info,
3625  const bool reverse_cross_sections,
3626  const bool swap_row_col,
3627  const GET_TRANSFORM_FUNCTOR &get_transform_functor,
3628  const TRANSFORM_FUNCTOR &transform_and_copy_functor,
3629  const INTERP_FUNCTOR &transform_and_interp_functor)
3630 {
3631  UT_ASSERT(cross_section_owner != GA_ATTRIB_INVALID);
3632  UT_ASSERT(cross_section_owner == output_attrib_owner ||
3633  (cross_section_owner == GA_ATTRIB_POINT && output_attrib_owner == GA_ATTRIB_VERTEX) ||
3634  (cross_section_owner == GA_ATTRIB_VERTEX && output_attrib_owner == GA_ATTRIB_POINT));
3635 
3636  exint prev_curve_vtxi = -1;
3637  GA_Offset source_cross_section_primoff;
3638  GA_OffsetListRef cross_section_vertices;
3639  exint current_cross_section_nedges;
3640  bool current_cross_section_closed;
3641  bool current_cross_section_unrolled;
3642  const TRANSFORM_T *transform;
3643  auto &&functor = [&grid_info,&outputh,&cross_sectionh,
3644  cross_section_owner,
3645  &get_transform_functor,&transform_and_copy_functor,
3646  &transform_and_interp_functor,
3647  cross_section_input,
3648  transforms,
3649  reverse_cross_sections,
3650  swap_row_col,
3651  &prev_curve_vtxi,
3652  &source_cross_section_primoff,
3653  &cross_section_vertices,
3654  &current_cross_section_nedges,
3655  &current_cross_section_closed,
3656  &current_cross_section_unrolled,
3657  &transform](GA_Offset output_offset, exint row, exint col)
3658  {
3659  exint num_curve_edges = grid_info.myCurveNEdges;
3660  bool is_curve_closed = grid_info.myCurveClosed;
3661  exint curve_vtxi = swap_row_col ? col : row;
3662  exint num_cross_section_edges = grid_info.myCrossSectionNEdges;
3663  //bool is_cross_section_closed = grid_info.myCrossSectionClosed;
3664  exint cross_section_vtxi = swap_row_col ? row : col;
3665 
3666  if (curve_vtxi != prev_curve_vtxi)
3667  {
3668  // NOTE: This function is only for copying cross section attributes,
3669  // so wrap_to_invalid only applies to column wrapping.
3670  // Rows wrap normally when closed in this case.
3671  if (output_attrib_owner == GA_ATTRIB_VERTEX && is_curve_closed && curve_vtxi == num_curve_edges)
3672  curve_vtxi = 0;
3673  transform = get_transform_functor(transforms, curve_vtxi, grid_info);
3674  source_cross_section_primoff =
3675  grid_info.mySingleCrossSection ?
3676  GA_Offset(grid_info.myCrossSectionPrimOff) :
3677  (*grid_info.myCrossSectionPrimOffs)[curve_vtxi];
3678  UT_ASSERT_P(GAisValid(source_cross_section_primoff));
3679  if (output_attrib_owner != GA_ATTRIB_PRIMITIVE)
3680  {
3681  cross_section_vertices = cross_section_input->getPrimitiveVertexList(source_cross_section_primoff);
3682  getPolyProperties(cross_section_input, cross_section_vertices, current_cross_section_nedges, current_cross_section_closed, current_cross_section_unrolled);
3683  }
3684  prev_curve_vtxi = curve_vtxi;
3685  }
3686 
3687  if (output_attrib_owner == GA_ATTRIB_PRIMITIVE)
3688  {
3689  transform_and_copy_functor(cross_sectionh, source_cross_section_primoff, transform, outputh, output_offset);
3690  return;
3691  }
3692 
3693  constexpr bool is_vertex_attrib = (output_attrib_owner == GA_ATTRIB_VERTEX);
3694 
3695  if (current_cross_section_nedges == num_cross_section_edges)
3696  {
3697  // Copy from a single point.
3698 
3699  if (reverse_cross_sections)
3700  {
3701  // We handle wrapping below, so we reverse like an open polygon for all vertex attributes.
3702  cross_section_vtxi = reverseVtx(cross_section_vtxi, current_cross_section_nedges + exint(is_vertex_attrib || !current_cross_section_closed),
3703  !is_vertex_attrib && current_cross_section_closed);
3704  }
3705 
3706  // NOTE: cross_section_vtxi may not be in-bounds, if this cross section is closed and some are open,
3707  // since in that case, the grid will be open in that direction,
3708  // so two output points will need to copy from vertex 0 of this cross section.
3709  GA_Offset vtxoff;
3710  if (cross_section_vtxi == cross_section_vertices.size())
3711  {
3712  if (wrap_to_invalid)
3713  {
3714  // vertex UV attribute wraps to 1.0 value, not first value.
3715  UT_ASSERT(is_vertex_attrib);
3716  vtxoff = GA_INVALID_OFFSET;
3717  }
3718  else
3719  vtxoff = cross_section_vertices[0];
3720  }
3721  else
3722  vtxoff = cross_section_vertices[cross_section_vtxi];
3723  GA_Offset cross_section_offset = (cross_section_owner == GA_ATTRIB_VERTEX || vtxoff == GA_INVALID_OFFSET) ? vtxoff : cross_section_input->vertexPoint(vtxoff);
3724  transform_and_copy_functor(cross_sectionh, cross_section_offset, transform, outputh, output_offset);
3725  }
3726  else
3727  {
3728  // Possibly interpolate between points.
3729 
3730  // The grid should have at least as many columns as the number of edges in each cross section.
3731  UT_ASSERT_P(num_cross_section_edges > current_cross_section_nedges);
3732 
3733  // Distribute the points evenly, but then snap the first point on a current edge
3734  // to the beginning of the edge and redistribute the points along that edge.
3735  // The division/modulus math is a bit hairy, but hopefully this works correctly.
3736  exint fine_fractions = cross_section_vtxi*current_cross_section_nedges;
3737  if (reverse_cross_sections)
3738  fine_fractions = num_cross_section_edges*current_cross_section_nedges - fine_fractions;
3739  exint current_cross_section_edge = fine_fractions / num_cross_section_edges;
3740  exint current_cross_section_fractions = fine_fractions % num_cross_section_edges;
3741  exint current_cross_section_pt = current_cross_section_fractions / current_cross_section_nedges;
3742  if (current_cross_section_pt == 0)
3743  {
3744  // First point on this edge: no need to interpolate, can just copy.
3745  GA_Offset vtxoff;
3746  if (current_cross_section_edge == cross_section_vertices.size())
3747  {
3748  if (wrap_to_invalid)
3749  {
3750  // vertex UV attribute wraps to 1.0 value, not first value.
3751  UT_ASSERT(is_vertex_attrib);
3752  vtxoff = GA_INVALID_OFFSET;
3753  }
3754  else
3755  vtxoff = cross_section_vertices[0];
3756  }
3757  else
3758  vtxoff = cross_section_vertices[current_cross_section_edge];
3759  GA_Offset cross_section_offset = (cross_section_owner == GA_ATTRIB_VERTEX || vtxoff == GA_INVALID_OFFSET) ? vtxoff : cross_section_input->vertexPoint(vtxoff);
3760  transform_and_copy_functor(cross_sectionh, cross_section_offset, transform, outputh, output_offset);
3761  }
3762  else
3763  {
3764  // Partway along an edge: interpolate.
3765  // current_cross_section_edge_start is the smallest value of cross_section_vtxi that would yield current_cross_section_edge
3766  // current_cross_section_edge_end is the smallest value of cross_section_vtxi that would yield current_cross_section_edge+1
3767  // The difference is the number of points that will yield current_cross_section_edge
3768  exint current_cross_section_edge_start = (current_cross_section_edge*num_cross_section_edges + current_cross_section_nedges-1) / current_cross_section_nedges;
3769  exint current_cross_section_edge_end = ((current_cross_section_edge+1)*num_cross_section_edges + current_cross_section_nedges-1) / current_cross_section_nedges;
3770  exint current_cross_section_edge_npts = current_cross_section_edge_end - current_cross_section_edge_start;
3771  double u = double(current_cross_section_pt)/double(current_cross_section_edge_npts);
3772 
3773  // If current_cross_section_edge == current_cross_section_nedges,
3774  // the branch above to copy should have been taken.
3775  UT_ASSERT_P(current_cross_section_edge != current_cross_section_nedges);
3776  exint vtxi0 = current_cross_section_edge;
3777  GA_Offset vtxoff0 = cross_section_vertices[vtxi0];
3778 
3779  GA_Offset vtxoff1;
3780  if (current_cross_section_edge+1 == cross_section_vertices.size())
3781  {
3782  if (wrap_to_invalid)
3783  {
3784  // vertex UV attribute wraps to 1.0 value, not first value.
3785  UT_ASSERT(is_vertex_attrib);
3786  vtxoff1 = GA_INVALID_OFFSET;
3787  }
3788  else
3789  vtxoff1 = cross_section_vertices[0];
3790  }
3791  else
3792  vtxoff1 = cross_section_vertices[current_cross_section_edge+1];
3793 
3794  GA_Offset cross_section_offset0;
3795  GA_Offset cross_section_offset1;
3796  if (cross_section_owner == GA_ATTRIB_VERTEX)
3797  {
3798  cross_section_offset0 = vtxoff0;
3799  cross_section_offset1 = vtxoff1;
3800  }
3801  else
3802  {
3803  cross_section_offset0 = cross_section_input->vertexPoint(vtxoff0);
3804  if (vtxoff1 != GA_INVALID_OFFSET)
3805  cross_section_offset1 = cross_section_input->vertexPoint(vtxoff1);
3806  else
3807  cross_section_offset1 = vtxoff1;
3808  }
3809  transform_and_interp_functor(cross_sectionh, cross_section_offset0, cross_section_offset1, u, transform, outputh, output_offset);
3810  }
3811  }
3812  };
3813 
3814  SYS_STATIC_ASSERT(output_attrib_owner != GA_ATTRIB_DETAIL);
3815 
3816  if (output_attrib_owner == GA_ATTRIB_VERTEX)
3817  {
3818  copyVertexCrossSectionWrapper(grid, grid_info, functor);
3819  }
3820  else if (output_attrib_owner == GA_ATTRIB_POINT)
3821  GUiterateGridPoints(grid, functor);
3822  else
3823  {
3824  UT_ASSERT_P(output_attrib_owner == GA_ATTRIB_PRIMITIVE);
3825  if (grid_info.myHasPolygonCaps)
3826  {
3827  // First row/col cap
3828  functor(grid_info.myStartPrimOff, 0, 0);
3829  }
3830  // Main grid primitives
3831  const GA_Offset start_primoff = grid_info.myStartPrimOff + exint(grid_info.myHasPolygonCaps);
3832  auto &&functor_wrapper = [&functor,start_primoff](exint primnum, exint row, exint col, exint primvtxcount, bool closed)
3833  {
3834  GA_Offset output_primoff = start_primoff + primnum;
3835  functor(output_primoff, row, col);
3836  };
3837  GUiterateGridPrimitives(grid, functor_wrapper);
3838  if (grid_info.myHasPolygonCaps)
3839  {
3840  const GA_Offset cap_start_primoff = start_primoff + grid.myNumPrimitives;
3841  if (grid.myNoWrapV)
3842  {
3843  // Last row cap
3844  functor(cap_start_primoff, grid.myNumEdgeRows, 0);
3845  }
3846  else
3847  {
3848  // Last side col cap
3849  functor(cap_start_primoff, 0, grid.myNumEdgeCols);
3850  }
3851  }
3852  }
3853 }
3854 
3855 template<typename VALUE_T,typename TRANSFORM_T,GA_AttributeOwner attrib_owner,typename GET_TRANSFORM_FUNCTOR,typename TRANSFORM_FUNCTOR,typename INTERP_FUNCTOR>
3856 static void
3857 copyCrossSectionAttributeWrapper(
3858  GU_GridT<GA_Offset> &grid,
3859  GA_ATINumeric *output_attrib,
3860  const GA_ATINumeric *cross_section_attrib,
3861  const GEO_Detail *cross_section_input,
3862  const sop_SweepGridTransformWrapper *transforms,
3863  const sop_SweepGrid &grid_info,
3864  const bool reverse_cross_sections,
3865  const bool swap_row_col,
3866  const GET_TRANSFORM_FUNCTOR &get_transform_functor,
3867  const TRANSFORM_FUNCTOR &transform_and_copy_functor,
3868  const INTERP_FUNCTOR &transform_and_interp_functor)
3869 {
3870  GA_RWHandleT<VALUE_T> outputh(output_attrib);
3871  GA_ROHandleT<VALUE_T> cross_sectionh(cross_section_attrib);
3872  UT_ASSERT_P(outputh.isValid());
3873  UT_ASSERT_P(cross_sectionh.isValid());
3874  UT_ASSERT(attrib_owner == cross_section_attrib->getOwner());
3875  copyCrossSectionAttributeWrapper2<TRANSFORM_T,attrib_owner>(
3876  grid, outputh, cross_sectionh, attrib_owner,
3877  cross_section_input, transforms, grid_info,
3878  reverse_cross_sections,
3879  swap_row_col,
3880  get_transform_functor,
3881  transform_and_copy_functor,
3882  transform_and_interp_functor);
3883 }
3884 
3885 template<typename T,GA_AttributeOwner attrib_owner>
3886 static void
3887 copyIntegerSingleGrid(
3888  GA_ATINumeric *output_attrib,
3889  const GA_ATINumeric *cross_section_attrib,
3890  const GEO_Detail *cross_section_input,
3891  const sop_SweepGridTransformWrapper *transforms,
3892  const sop_SweepGrid &grid_info,
3893  const GEO_SurfaceType surface_type,
3894  const bool output_points_only,
3895  const bool triangular_poles,
3896  const bool reverse_cross_sections,
3897  const bool swap_row_col)
3898 {
3899  UT_ASSERT_P(cross_section_attrib != nullptr);
3900  UT_ASSERT_P(cross_section_input != nullptr);
3901 
3903 
3904  GU_GridT<GA_Offset> grid;
3905  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3906 
3907  const int tuple_size = output_attrib->getTupleSize();
3908 
3909  // Non-transforming cases follow
3910  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
3911  {
3912  return nullptr;
3913  };
3914 
3915  if (tuple_size == 1)
3916  {
3917  // Copy single-component numeric attribute
3918  auto &&transform_and_copy = [](
3919  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
3920  const void *transform,
3921  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3922  {
3923  T value = cross_sectionh.get(cross_section_offset);
3924  outputh.set(output_offset, value);
3925  };
3926  auto &&transform_and_interp = [](
3927  const GA_ROHandleT<T> &cross_sectionh,
3928  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
3929  const void *transform,
3930  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3931  {
3932  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
3933  T value = cross_sectionh.get(cross_section_offset);
3934  outputh.set(output_offset, value);
3935  };
3936  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
3937  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
3938  reverse_cross_sections, swap_row_col,
3939  get_transform, transform_and_copy, transform_and_interp);
3940  return;
3941  }
3942 
3943  // Copy arbitrary-component numeric attribute
3944  auto &&transform_and_copy = [tuple_size](
3945  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
3946  const void *transform,
3947  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3948  {
3949  for (exint component = 0; component < tuple_size; ++component)
3950  {
3951  T value = cross_sectionh.get(cross_section_offset, component);
3952  outputh.set(output_offset, component, value);
3953  }
3954  };
3955  auto &&transform_and_interp = [tuple_size](
3956  const GA_ROHandleT<T> &cross_sectionh,
3957  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
3958  const void *transform,
3959  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
3960  {
3961  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
3962  for (exint component = 0; component < tuple_size; ++component)
3963  {
3964  T value = cross_sectionh.get(cross_section_offset, component);
3965  outputh.set(output_offset, component, value);
3966  }
3967  };
3968  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
3969  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
3970  reverse_cross_sections, swap_row_col,
3971  get_transform, transform_and_copy, transform_and_interp);
3972 }
3973 
3974 template<typename T,GA_TypeInfo transform_type,GA_AttributeOwner attrib_owner>
3975 static void
3976 copyFloatSingleGrid(
3977  GA_ATINumeric *output_attrib,
3978  const GA_ATINumeric *cross_section_attrib,
3979  const GEO_Detail *cross_section_input,
3980  const sop_SweepGridTransformWrapper *transforms,
3981  const sop_SweepGrid &grid_info,
3982  const exint cross_sections_per_vertex,
3983  const exint cap_divisions,
3984  const GEO_SurfaceType surface_type,
3985  const bool output_points_only,
3986  const bool triangular_poles,
3987  const bool reverse_cross_sections,
3988  const bool swap_row_col)
3989 {
3990  UT_ASSERT_P(cross_section_attrib != nullptr);
3991  UT_ASSERT_P(cross_section_input != nullptr);
3992 
3994 
3995  GU_GridT<GA_Offset> grid;
3996  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
3997 
3998  const exint cap_rows = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
3999  exint last_cap_start_row = -1;
4000  exint last_cap_start_transform = -1;
4001  if (cross_sections_per_vertex != 1 && cap_rows > 0)
4002  {
4003  last_cap_start_row = grid_info.myCurveNEdges - cap_rows;
4004  last_cap_start_transform = (last_cap_start_row - cap_rows)/cross_sections_per_vertex + cap_rows;
4005  }
4006 
4007  auto &&fix_transformi_for_all_case = [cap_rows,cross_sections_per_vertex,last_cap_start_row,last_cap_start_transform](exint &transformi)
4008  {
4009  // We have multiple cross sections per vertex, except in the caps,
4010  // when CopyOrder::ALL, but only one transform, so we need to
4011  // adjust transformi.
4012  if (cap_rows == 0)
4013  transformi /= cross_sections_per_vertex;
4014  else if (transformi > cap_rows)
4015  {
4016  if (transformi < last_cap_start_row)
4017  transformi = (transformi - cap_rows)/cross_sections_per_vertex + cap_rows;
4018  else
4019  transformi = last_cap_start_transform + (transformi - last_cap_start_row);
4020  }
4021  };
4022 
4023  const int tuple_size = output_attrib->getTupleSize();
4024 
4025  if (transforms)
4026  {
4027  if (transform_type == GA_TYPE_POINT)
4028  {
4029  // Transform position attribute
4031  transform.identity();
4032  UT_ASSERT(tuple_size == 3);
4033  auto &&get_transform = [&transform,cross_sections_per_vertex,&fix_transformi_for_all_case]
4034  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix4T<T>*
4035  {
4036  exint transformi = row;
4037  if (cross_sections_per_vertex != 1)
4038  fix_transformi_for_all_case(transformi);
4039  transform = UT_Matrix4T<T>(transforms->getMatrix3s<T>()[transformi]);
4040  transform.setTranslates(transforms->getTranslates<T>()[transformi]);
4041  return &transform;
4042  };
4043  auto &&transform_and_copy = [](
4044  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4045  const UT_Matrix4T<T> *transform,
4046  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4047  {
4048  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4049  value *= *transform;
4050  outputh.set(output_offset, value);
4051  };
4052  auto &&transform_and_interp = [](
4053  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4054  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4055  const UT_Matrix4T<T> *transform,
4056  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4057  {
4058  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4059  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4060  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4061  value *= *transform;
4062  outputh.set(output_offset, value);
4063  };
4064  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix4T<T>, attrib_owner>(grid,
4065  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4066  reverse_cross_sections, swap_row_col,
4067  get_transform, transform_and_copy, transform_and_interp);
4068  return;
4069  }
4070  if (transform_type == GA_TYPE_NORMAL)
4071  {
4072  // Transform normal attribute
4073  UT_ASSERT(tuple_size == 3);
4074  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4075  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4076  {
4077  exint transformi = row;
4078  if (cross_sections_per_vertex != 1)
4079  fix_transformi_for_all_case(transformi);
4080  return &transforms->getInverse3s<T>()[transformi];
4081  };
4082  auto &&transform_and_copy = [](
4083  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4084  const UT_Matrix3T<T> *transform,
4085  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4086  {
4087  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4088  value.colVecMult(*transform);
4089  outputh.set(output_offset, value);
4090  };
4091  auto &&transform_and_interp = [](
4092  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4093  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4094  const UT_Matrix3T<T> *transform,
4095  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4096  {
4097  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4098  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4099 
4100  // FIXME: slerp, instead of lerping and normalizing!!!
4101  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4102  value.normalize();
4103 
4104  T orig_length2 = value.length2();
4105  value.colVecMult(*transform);
4106  T new_length2 = value.length2();
4107  // Preserve normal length
4108  if (new_length2 != 0)
4109  value *= SYSsqrt(orig_length2/new_length2);
4110  outputh.set(output_offset, value);
4111  };
4112  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4113  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4114  reverse_cross_sections, swap_row_col,
4115  get_transform, transform_and_copy, transform_and_interp);
4116  return;
4117  }
4118  if (transform_type == GA_TYPE_VECTOR)
4119  {
4120  // Transform vector attribute
4121  UT_ASSERT(tuple_size == 3);
4122  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4123  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4124  {
4125  exint transformi = row;
4126  if (cross_sections_per_vertex != 1)
4127  fix_transformi_for_all_case(transformi);
4128  return &transforms->getMatrix3s<T>()[transformi];
4129  };
4130  auto &&transform_and_copy = [](
4131  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4132  const UT_Matrix3T<T> *transform,
4133  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4134  {
4135  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4136  value *= *transform;
4137  outputh.set(output_offset, value);
4138  };
4139  auto &&transform_and_interp = [](
4140  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4141  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4142  const UT_Matrix3T<T> *transform,
4143  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4144  {
4145  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4146  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4147  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4148  value *= *transform;
4149  outputh.set(output_offset, value);
4150  };
4151  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4152  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4153  reverse_cross_sections, swap_row_col,
4154  get_transform, transform_and_copy, transform_and_interp);
4155  return;
4156  }
4157  if (transform_type == GA_TYPE_QUATERNION)
4158  {
4159  // Transform vector attribute
4160  UT_ASSERT(tuple_size == 4);
4161  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4162  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_QuaternionT<T>*
4163  {
4164  exint transformi = row;
4165  if (cross_sections_per_vertex != 1)
4166  fix_transformi_for_all_case(transformi);
4167  return &transforms->getQuaternions<T>()[transformi];
4168  };
4169  auto &&transform_and_copy = [](
4170  const GA_ROHandleT<UT_QuaternionT<T>> &cross_sectionh, GA_Offset cross_section_offset,
4172  const GA_RWHandleT<UT_QuaternionT<T>> &outputh, GA_Offset output_offset)
4173  {
4174  UT_QuaternionT<T> value = cross_sectionh.get(cross_section_offset);
4175  value *= *transform;
4176  outputh.set(output_offset, value);
4177  };
4178  auto &&transform_and_interp = [](
4179  const GA_ROHandleT<UT_QuaternionT<T>> &cross_sectionh,
4180  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4182  const GA_RWHandleT<UT_QuaternionT<T>> &outputh, GA_Offset output_offset)
4183  {
4184  const UT_QuaternionT<T> value0 = cross_sectionh.get(cross_section_offset0);
4185  const UT_QuaternionT<T> value1 = cross_sectionh.get(cross_section_offset1);
4186  UT_QuaternionT<T> value = value0;
4187  value.interpolate(value1, t);
4188  value = *transform * value;
4189  outputh.set(output_offset, value);
4190  };
4191  copyCrossSectionAttributeWrapper<UT_QuaternionT<T>, UT_QuaternionT<T>, attrib_owner>(grid,
4192  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4193  reverse_cross_sections, swap_row_col,
4194  get_transform, transform_and_copy, transform_and_interp);
4195  return;
4196  }
4197  if (transform_type == GA_TYPE_TRANSFORM)
4198  {
4199  if (tuple_size == 9)
4200  {
4201  // Transform 3x3 matrix attribute
4202  auto &&get_transform = [cross_sections_per_vertex,&fix_transformi_for_all_case]
4203  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix3T<T>*
4204  {
4205  exint transformi = row;
4206  if (cross_sections_per_vertex != 1)
4207  fix_transformi_for_all_case(transformi);
4208  return &transforms->getMatrix3s<T>()[transformi];
4209  };
4210  auto &&transform_and_copy = [](
4211  const GA_ROHandleT<UT_Matrix3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4212  const UT_Matrix3T<T> *transform,
4213  const GA_RWHandleT<UT_Matrix3T<T>> &outputh, GA_Offset output_offset)
4214  {
4215  UT_Matrix3T<T> value = cross_sectionh.get(cross_section_offset);
4216  value *= *transform;
4217  outputh.set(output_offset, value);
4218  };
4219  auto &&transform_and_interp = [](
4220  const GA_ROHandleT<UT_Matrix3T<T>> &cross_sectionh,
4221  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4222  const UT_Matrix3T<T> *transform,
4223  const GA_RWHandleT<UT_Matrix3T<T>> &outputh, GA_Offset output_offset)
4224  {
4225  const UT_Matrix3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4226  const UT_Matrix3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4227  UT_Matrix3T<T> value = SYSlerp(value0, value1, t);
4228  value *= *transform;
4229  outputh.set(output_offset, value);
4230  };
4231  copyCrossSectionAttributeWrapper<UT_Matrix3T<T>, UT_Matrix3T<T>, attrib_owner>(grid,
4232  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4233  reverse_cross_sections, swap_row_col,
4234  get_transform, transform_and_copy, transform_and_interp);
4235  return;
4236  }
4237  if (tuple_size == 16)
4238  {
4239  // Transform 4x4 matrix attribute
4241  transform.identity();
4242  auto &&get_transform = [&transform,cross_sections_per_vertex,&fix_transformi_for_all_case]
4243  (const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const UT_Matrix4T<T>*
4244  {
4245  exint transformi = row;
4246  if (cross_sections_per_vertex != 1)
4247  fix_transformi_for_all_case(transformi);
4248  transform = UT_Matrix4T<T>(transforms->getMatrix3s<T>()[transformi]);
4249  transform.setTranslates(transforms->getTranslates<T>()[transformi]);
4250  return &transform;
4251  };
4252  auto &&transform_and_copy = [](
4253  const GA_ROHandleT<UT_Matrix4T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4254  const UT_Matrix4T<T> *transform,
4255  const GA_RWHandleT<UT_Matrix4T<T>> &outputh, GA_Offset output_offset)
4256  {
4257  UT_Matrix4T<T> value = cross_sectionh.get(cross_section_offset);
4258  value *= *transform;
4259  outputh.set(output_offset, value);
4260  };
4261  auto &&transform_and_interp = [](
4262  const GA_ROHandleT<UT_Matrix4T<T>> &cross_sectionh,
4263  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4264  const UT_Matrix4T<T> *transform,
4265  const GA_RWHandleT<UT_Matrix4T<T>> &outputh, GA_Offset output_offset)
4266  {
4267  const UT_Matrix4T<T> value0 = cross_sectionh.get(cross_section_offset0);
4268  const UT_Matrix4T<T> value1 = cross_sectionh.get(cross_section_offset1);
4269  UT_Matrix4T<T> value = SYSlerp(value0, value1, t);
4270  value *= *transform;
4271  outputh.set(output_offset, value);
4272  };
4273  copyCrossSectionAttributeWrapper<UT_Matrix4T<T>, UT_Matrix4T<T>, attrib_owner>(grid,
4274  output_attrib, cross_section_attrib, cross_section_input, transforms, grid_info,
4275  reverse_cross_sections, swap_row_col,
4276  get_transform, transform_and_copy, transform_and_interp);
4277  return;
4278  }
4279  }
4280  }
4281 
4282  // Non-transforming cases follow
4283  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
4284  {
4285  return nullptr;
4286  };
4287 
4288  bool is_integer_type = std::is_integral<T>::value;
4289  if (tuple_size == 1)
4290  {
4291  // Copy single-component numeric attribute
4292  auto &&transform_and_copy = [](
4293  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
4294  const void *transform,
4295  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4296  {
4297  T value = cross_sectionh.get(cross_section_offset);
4298  outputh.set(output_offset, value);
4299  };
4300  if (is_integer_type)
4301  {
4302  auto &&transform_and_interp = [](
4303  const GA_ROHandleT<T> &cross_sectionh,
4304  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4305  const void *transform,
4306  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4307  {
4308  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4309  T value = cross_sectionh.get(cross_section_offset);
4310  outputh.set(output_offset, value);
4311  };
4312  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4313  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4314  reverse_cross_sections, swap_row_col,
4315  get_transform, transform_and_copy, transform_and_interp);
4316  }
4317  else
4318  {
4319  auto &&transform_and_interp = [](
4320  const GA_ROHandleT<T> &cross_sectionh,
4321  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4322  const void *transform,
4323  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4324  {
4325  const T value0 = cross_sectionh.get(cross_section_offset0);
4326  const T value1 = cross_sectionh.get(cross_section_offset1);
4327  T value = SYSlerp(value0, value1, t);
4328  outputh.set(output_offset, value);
4329  };
4330  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4331  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4332  reverse_cross_sections, swap_row_col,
4333  get_transform, transform_and_copy, transform_and_interp);
4334  }
4335  return;
4336  }
4337  if (tuple_size == 2 && !is_integer_type)
4338  {
4339  // Copy 2-component numeric attribute
4340  auto &&transform_and_copy = [](
4341  const GA_ROHandleT<UT_Vector2T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4342  const void *transform,
4343  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
4344  {
4345  UT_Vector2T<T> value = cross_sectionh.get(cross_section_offset);
4346  outputh.set(output_offset, value);
4347  };
4348  auto &&transform_and_interp = [](
4349  const GA_ROHandleT<UT_Vector2T<T>> &cross_sectionh,
4350  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4351  const void *transform,
4352  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
4353  {
4354  const UT_Vector2T<T> value0 = cross_sectionh.get(cross_section_offset0);
4355  const UT_Vector2T<T> value1 = cross_sectionh.get(cross_section_offset1);
4356  UT_Vector2T<T> value = SYSlerp(value0, value1, t);
4357  outputh.set(output_offset, value);
4358  };
4359  copyCrossSectionAttributeWrapper<UT_Vector2T<T>, void, attrib_owner>(grid,
4360  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4361  reverse_cross_sections, swap_row_col,
4362  get_transform, transform_and_copy, transform_and_interp);
4363  return;
4364  }
4365  if (tuple_size == 3 && !is_integer_type)
4366  {
4367  // Copy 3-component numeric attribute
4368  auto &&transform_and_copy = [](
4369  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh, GA_Offset cross_section_offset,
4370  const void *transform,
4371  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4372  {
4373  UT_Vector3T<T> value = cross_sectionh.get(cross_section_offset);
4374  outputh.set(output_offset, value);
4375  };
4376  auto &&transform_and_interp = [](
4377  const GA_ROHandleT<UT_Vector3T<T>> &cross_sectionh,
4378  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
4379  const void *transform,
4380  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
4381  {
4382  const UT_Vector3T<T> value0 = cross_sectionh.get(cross_section_offset0);
4383  const UT_Vector3T<T> value1 = cross_sectionh.get(cross_section_offset1);
4384  UT_Vector3T<T> value = SYSlerp(value0, value1, t);
4385  outputh.set(output_offset, value);
4386  };
4387  copyCrossSectionAttributeWrapper<UT_Vector3T<T>, void, attrib_owner>(grid,
4388  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4389  reverse_cross_sections, swap_row_col,
4390  get_transform, transform_and_copy, transform_and_interp);
4391  return;
4392  }
4393 
4394  // Copy arbitrary-component numeric attribute
4395  auto &&transform_and_copy = [tuple_size](
4396  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
4397  const void *transform,
4398  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4399  {
4400  for (exint component = 0; component < tuple_size; ++component)
4401  {
4402  T value = cross_sectionh.get(cross_section_offset, component);
4403  outputh.set(output_offset, component, value);
4404  }
4405  };
4406  if (is_integer_type)
4407  {
4408  auto &&transform_and_interp = [tuple_size](
4409  const GA_ROHandleT<T> &cross_sectionh,
4410  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4411  const void *transform,
4412  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4413  {
4414  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4415  for (exint component = 0; component < tuple_size; ++component)
4416  {
4417  T value = cross_sectionh.get(cross_section_offset, component);
4418  outputh.set(output_offset, component, value);
4419  }
4420  };
4421  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4422  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4423  reverse_cross_sections, swap_row_col,
4424  get_transform, transform_and_copy, transform_and_interp);
4425  }
4426  else
4427  {
4428  auto &&transform_and_interp = [tuple_size](
4429  const GA_ROHandleT<T> &cross_sectionh,
4430  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4431  const void *transform,
4432  const GA_RWHandleT<T> &outputh, GA_Offset output_offset)
4433  {
4434  for (exint component = 0; component < tuple_size; ++component)
4435  {
4436  const T value0 = cross_sectionh.get(cross_section_offset0, component);
4437  const T value1 = cross_sectionh.get(cross_section_offset1, component);
4438  T value = SYSlerp(value0, value1, t);
4439  outputh.set(output_offset, component, value);
4440  }
4441  };
4442  copyCrossSectionAttributeWrapper<T, void, attrib_owner>(grid,
4443  output_attrib, cross_section_attrib, cross_section_input, nullptr, grid_info,
4444  reverse_cross_sections, swap_row_col,
4445  get_transform, transform_and_copy, transform_and_interp);
4446  }
4447 }
4448 
4449 template<GA_AttributeOwner attrib_owner>
4450 static void
4451 copyAttribSingleGrid(
4452  GA_Attribute *output_attrib,
4453  const GA_Attribute *cross_section_attrib,
4454  const GEO_Detail *cross_section_input,
4455  const sop_SweepGrid &grid_info,
4456  const GEO_SurfaceType surface_type,
4457  const bool output_points_only,
4458  const bool triangular_poles,
4459  const bool reverse_cross_sections,
4460  const bool swap_row_col)
4461 {
4462  // Copy non-numeric attribute
4463 
4464  GU_GridT<GA_Offset> grid;
4465  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
4466 
4467  // Non-transforming
4468  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
4469  {
4470  return nullptr;
4471  };
4472 
4473  auto &&transform_and_copy = [](
4474  const GA_Attribute *cross_section_attrib, GA_Offset cross_section_offset,
4475  const void *transform,
4476  GA_Attribute *output_attrib, GA_Offset output_offset)
4477  {
4478  output_attrib->copy(output_offset, *cross_section_attrib, cross_section_offset);
4479  };
4480  auto &&transform_and_interp = [](
4481  const GA_Attribute *cross_section_attrib,
4482  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, double t,
4483  const void *transform,
4484  GA_Attribute *output_attrib, GA_Offset output_offset)
4485  {
4486  GA_Offset cross_section_offset = (t < 0.5) ? cross_section_offset0 : cross_section_offset1;
4487  output_attrib->copy(output_offset, *cross_section_attrib, cross_section_offset);
4488  };
4489 
4490  UT_ASSERT(attrib_owner == cross_section_attrib->getOwner());
4491  copyCrossSectionAttributeWrapper2<void, attrib_owner>(grid,
4492  output_attrib, cross_section_attrib, attrib_owner, cross_section_input, nullptr, grid_info,
4493  reverse_cross_sections, swap_row_col,
4494  get_transform, transform_and_copy, transform_and_interp);
4495 }
4496 
4497 static const sop_SweepGridTransformWrapper *
4498 gridOffsetTransforms(
4499  const SOP_SweepHDKCache *const transform_cache,
4500  sop_SweepGridTransformWrapper &grid_transforms,
4501  const exint gridi)
4502 {
4503  if (transform_cache == nullptr)
4504  return nullptr;
4505 
4506  grid_transforms.init(*transform_cache, gridi);
4507 
4508  return &grid_transforms;
4509 }
4510 
4511 template<GA_AttributeOwner attrib_owner>
4512 static void
4513 copyCrossSectionAttrib2(
4514  GA_Attribute *output_attrib,
4515  const GA_Attribute *cross_section_attrib,
4516  const GEO_Detail *cross_section_input,
4517  const SOP_SweepHDKCache *transform_cache,
4518  const sop_SweepGrid *grids,
4519  exint ngrids,
4520  const exint cross_sections_per_vertex,
4521  const exint cap_divisions,
4522  const GEO_SurfaceType surface_type,
4523  const bool output_points_only,
4524  const bool triangular_poles,
4525  const bool reverse_cross_sections,
4526  const bool swap_row_col)
4527 {
4528  UT_ASSERT(output_attrib->getOwner() == attrib_owner);
4529  UT_ASSERT(cross_section_attrib->getOwner() == attrib_owner);
4530 
4531  const exint PARALLEL_THRESHOLD = 2048;
4532  const bool parallel = (ngrids > 1 &&
4533  output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD);
4534  const UT_BlockedRange<exint> grid_range(0, ngrids);
4535 
4536  GA_ATINumeric *const output_numeric = GA_ATINumeric::cast(output_attrib);
4537  if (!output_numeric)
4538  {
4539  output_attrib->bumpDataId();
4540  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4541  {
4542  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4543  {
4544  copyAttribSingleGrid<attrib_owner>(
4545  output_attrib, cross_section_attrib, cross_section_input,
4546  grids[gridi],
4547  surface_type, output_points_only, triangular_poles,
4548  reverse_cross_sections, swap_row_col);
4549  }
4550  };
4551  if (parallel)
4552  {
4553  // Must harden all pages before multi-threading.
4554  output_attrib->hardenAllPages();
4555  UTparallelFor(grid_range, functor);
4556  }
4557  else
4558  {
4559  functor(grid_range);
4560  }
4561  return;
4562  }
4563 
4564  const GA_ATINumeric *const cross_section_numeric = GA_ATINumeric::cast(cross_section_attrib);
4565  UT_ASSERT(cross_section_numeric);
4566  if (!cross_section_numeric)
4567  return;
4568  const int tuple_size = output_numeric->getTupleSize();
4569  UT_ASSERT(tuple_size == cross_section_numeric->getTupleSize());
4570  if (tuple_size != cross_section_numeric->getTupleSize())
4571  return;
4572 
4573  output_attrib->bumpDataId();
4574 
4575  if (parallel)
4576  {
4577  // Must harden all pages before multi-threading.
4578  output_attrib->hardenAllPages();
4579  }
4580 
4581  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4582  {
4583  const GA_TypeInfo type_info = cross_section_numeric->getTypeInfo();
4584  const GA_Storage storage_type = output_numeric->getStorage();
4585  sop_SweepGridTransformWrapper local_grid_transforms;
4586  if (tuple_size == 3)
4587  {
4588  if (type_info == GA_TYPE_POINT)
4589  {
4590  if (storage_type == GA_STORE_REAL64)
4591  {
4592  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4593  {
4594  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4595  copyFloatSingleGrid<fpreal64,GA_TYPE_POINT,attrib_owner>(
4596  output_numeric, cross_section_numeric, cross_section_input,
4597  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4598  surface_type, output_points_only, triangular_poles,
4599  reverse_cross_sections, swap_row_col);
4600  }
4601  }
4602  else
4603  {
4604  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4605  {
4606  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4607  copyFloatSingleGrid<fpreal32,GA_TYPE_POINT,attrib_owner>(
4608  output_numeric, cross_section_numeric, cross_section_input,
4609  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4610  surface_type, output_points_only, triangular_poles,
4611  reverse_cross_sections, swap_row_col);
4612  }
4613  }
4614  return;
4615  }
4616  if (type_info == GA_TYPE_NORMAL)
4617  {
4618  if (storage_type == GA_STORE_REAL64)
4619  {
4620  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4621  {
4622  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4623  copyFloatSingleGrid<fpreal64,GA_TYPE_NORMAL,attrib_owner>(
4624  output_numeric, cross_section_numeric, cross_section_input,
4625  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4626  surface_type, output_points_only, triangular_poles,
4627  reverse_cross_sections, swap_row_col);
4628  }
4629  }
4630  else
4631  {
4632  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4633  {
4634  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4635  copyFloatSingleGrid<fpreal32,GA_TYPE_NORMAL,attrib_owner>(
4636  output_numeric, cross_section_numeric, cross_section_input,
4637  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4638  surface_type, output_points_only, triangular_poles,
4639  reverse_cross_sections, swap_row_col);
4640  }
4641  }
4642  return;
4643  }
4644  if (type_info == GA_TYPE_VECTOR)
4645  {
4646  if (storage_type == GA_STORE_REAL64)
4647  {
4648  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4649  {
4650  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4651  copyFloatSingleGrid<fpreal64,GA_TYPE_VECTOR,attrib_owner>(
4652  output_numeric, cross_section_numeric, cross_section_input,
4653  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4654  surface_type, output_points_only, triangular_poles,
4655  reverse_cross_sections, swap_row_col);
4656  }
4657  }
4658  else
4659  {
4660  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4661  {
4662  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4663  copyFloatSingleGrid<fpreal32,GA_TYPE_VECTOR,attrib_owner>(
4664  output_numeric, cross_section_numeric, cross_section_input,
4665  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4666  surface_type, output_points_only, triangular_poles,
4667  reverse_cross_sections, swap_row_col);
4668  }
4669  }
4670  return;
4671  }
4672  }
4673  else if (tuple_size == 4)
4674  {
4675  if (type_info == GA_TYPE_QUATERNION)
4676  {
4677  if (storage_type == GA_STORE_REAL64)
4678  {
4679  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4680  {
4681  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4682  copyFloatSingleGrid<fpreal64,GA_TYPE_QUATERNION,attrib_owner>(
4683  output_numeric, cross_section_numeric, cross_section_input,
4684  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4685  surface_type, output_points_only, triangular_poles,
4686  reverse_cross_sections, swap_row_col);
4687  }
4688  }
4689  else
4690  {
4691  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4692  {
4693  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4694  copyFloatSingleGrid<fpreal32,GA_TYPE_QUATERNION,attrib_owner>(
4695  output_numeric, cross_section_numeric, cross_section_input,
4696  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4697  surface_type, output_points_only, triangular_poles,
4698  reverse_cross_sections, swap_row_col);
4699  }
4700  }
4701  return;
4702  }
4703  }
4704  else if (tuple_size == 9 || tuple_size == 16)
4705  {
4706  if (type_info == GA_TYPE_TRANSFORM)
4707  {
4708  if (storage_type == GA_STORE_REAL64)
4709  {
4710  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4711  {
4712  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4713  copyFloatSingleGrid<fpreal64,GA_TYPE_TRANSFORM,attrib_owner>(
4714  output_numeric, cross_section_numeric, cross_section_input,
4715  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4716  surface_type, output_points_only, triangular_poles,
4717  reverse_cross_sections, swap_row_col);
4718  }
4719  }
4720  else
4721  {
4722  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4723  {
4724  auto grid_transforms = gridOffsetTransforms(transform_cache, local_grid_transforms, gridi);
4725  copyFloatSingleGrid<fpreal32,GA_TYPE_TRANSFORM,attrib_owner>(
4726  output_numeric, cross_section_numeric, cross_section_input,
4727  grid_transforms, grids[gridi], cross_sections_per_vertex, cap_divisions,
4728  surface_type, output_points_only, triangular_poles,
4729  reverse_cross_sections, swap_row_col);
4730  }
4731  }
4732  return;
4733  }
4734  }
4735 
4736  if (storage_type == GA_STORE_REAL64)
4737  {
4738  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4739  {
4740  copyFloatSingleGrid<fpreal64,GA_TYPE_VOID,attrib_owner>(
4741  output_numeric, cross_section_numeric, cross_section_input,
4742  nullptr, grids[gridi], cross_sections_per_vertex, cap_divisions,
4743  surface_type, output_points_only, triangular_poles,
4744  reverse_cross_sections, swap_row_col);
4745  }
4746  }
4747  else if (storage_type == GA_STORE_REAL32 || storage_type == GA_STORE_REAL16)
4748  {
4749  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4750  {
4751  copyFloatSingleGrid<fpreal32,GA_TYPE_VOID,attrib_owner>(
4752  output_numeric, cross_section_numeric, cross_section_input,
4753  nullptr, grids[gridi], cross_sections_per_vertex, cap_divisions,
4754  surface_type, output_points_only, triangular_poles,
4755  reverse_cross_sections, swap_row_col);
4756  }
4757  }
4758  else if (storage_type == GA_STORE_INT64)
4759  {
4760  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4761  {
4762  copyIntegerSingleGrid<int64,attrib_owner>(
4763  output_numeric, cross_section_numeric, cross_section_input,
4764  nullptr, grids[gridi],
4765  surface_type, output_points_only, triangular_poles,
4766  reverse_cross_sections, swap_row_col);
4767  }
4768  }
4769  else if (storage_type == GA_STORE_INT32 || storage_type == GA_STORE_INT16 || storage_type == GA_STORE_INT8)
4770  {
4771  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4772  {
4773  copyIntegerSingleGrid<int32,attrib_owner>(
4774  output_numeric, cross_section_numeric, cross_section_input,
4775  nullptr, grids[gridi],
4776  surface_type, output_points_only, triangular_poles,
4777  reverse_cross_sections, swap_row_col);
4778  }
4779  }
4780  };
4781 
4782  if (parallel)
4783  UTparallelFor(grid_range, functor);
4784  else
4785  functor(grid_range);
4786 }
4787 
4788 static void
4789 copyCrossSectionAttrib(
4790  GA_Attribute *output_attrib,
4791  const GA_Attribute *cross_section_attrib,
4792  const GEO_Detail *cross_section_input,
4793  const SOP_SweepHDKCache *transform_cache,
4794  const sop_SweepGrid *grids,
4795  exint ngrids,
4796  const exint cross_sections_per_vertex,
4797  const exint cap_divisions,
4798  const GEO_SurfaceType surface_type,
4799  const bool output_points_only,
4800  const bool triangular_poles,
4801  const bool reverse_cross_sections,
4802  const bool swap_row_col)
4803 {
4804  UT_ASSERT(output_attrib->getOwner() == cross_section_attrib->getOwner());
4805 
4806  GA_AttributeOwner owner = output_attrib->getOwner();
4807  if (owner == GA_ATTRIB_POINT)
4808  {
4809  copyCrossSectionAttrib2<GA_ATTRIB_POINT>(
4810  output_attrib, cross_section_attrib, cross_section_input,
4811  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4812  surface_type, output_points_only, triangular_poles,
4813  reverse_cross_sections, swap_row_col);
4814  }
4815  else if (owner == GA_ATTRIB_VERTEX)
4816  {
4817  copyCrossSectionAttrib2<GA_ATTRIB_VERTEX>(
4818  output_attrib, cross_section_attrib, cross_section_input,
4819  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4820  surface_type, output_points_only, triangular_poles,
4821  reverse_cross_sections, swap_row_col);
4822  }
4823  else if (owner == GA_ATTRIB_PRIMITIVE)
4824  {
4825  copyCrossSectionAttrib2<GA_ATTRIB_PRIMITIVE>(
4826  output_attrib, cross_section_attrib, cross_section_input,
4827  transform_cache, grids, ngrids, cross_sections_per_vertex, cap_divisions,
4828  surface_type, output_points_only, triangular_poles,
4829  reverse_cross_sections, swap_row_col);
4830  }
4831  else if (owner == GA_ATTRIB_DETAIL)
4832  {
4833  output_attrib->replace(*cross_section_attrib);
4834  }
4835 }
4836 
4837 template<bool wrap_to_invalid,typename FUNCTOR>
4838 static void
4839 copyVertexCurveAttribWrapper(
4840  const sop_SweepGrid &grid_info,
4841  const GU_GridT<GA_Offset> &grid,
4842  exint num_vertices,
4843  exint cap_divisions,
4844  exint cross_sections_per_vertex,
4845  const bool swap_row_col,
4846  const FUNCTOR &copy_functor)
4847 {
4848  const exint curve_nedges = grid_info.myCurveNEdges;
4849  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
4850 
4851  const exint cap_divs = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
4852  exint prev_vtxi = -1;
4853  exint curve_vtxi;
4854  exint cap_vertex_count = 0;
4855  if (grid_info.myHasPolygonCaps)
4856  {
4857  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
4858  {
4859  // First row cap
4860  cap_vertex_count = cross_section_nedges;
4861 
4862  curve_vtxi = 0;
4863  prev_vtxi = 0;
4864 
4865  for (exint col = 0; col < cap_vertex_count; ++col)
4866  {
4867  copy_functor(grid_info.myStartVtxOff + col, 0);
4868  }
4869  }
4870  else
4871  {
4872  // First side col cap
4873  cap_vertex_count = curve_nedges;
4874 
4875  for (exint row = 0; row < cap_vertex_count; ++row)
4876  {
4877  copy_functor(grid_info.myStartVtxOff + row, row);
4878  }
4879  }
4880  }
4881  // Copy to main grid vertices
4882  const GA_Offset start_vtxoff = grid_info.myStartVtxOff + cap_vertex_count;
4883  const bool closed_curve = grid_info.myCurveClosed;
4884  GUiterateGridVertices(grid, [cap_divs, &prev_vtxi, &curve_vtxi, num_vertices,
4885  start_vtxoff, cross_sections_per_vertex, swap_row_col, closed_curve, &copy_functor]
4886  (exint vtxnum, exint row, exint col, bool isrowend, bool iscolend, exint primnum, exint primvtxnum)
4887  {
4888  exint vtxi = swap_row_col ? col : row;
4889  vtxi += exint(swap_row_col ? iscolend : isrowend);
4890  if (vtxi != prev_vtxi)
4891  {
4892  curve_vtxi = vtxi - cap_divs;
4893  if (cross_sections_per_vertex != 1)
4894  curve_vtxi /= cross_sections_per_vertex;
4895  if (curve_vtxi < 0)
4896  curve_vtxi = 0;
4897  else if (curve_vtxi >= num_vertices)
4898  {
4899  if (!closed_curve)
4900  curve_vtxi = num_vertices-1;
4901  else if (wrap_to_invalid)
4902  curve_vtxi = -1;
4903  else
4904  curve_vtxi = 0;
4905  }
4906  prev_vtxi = vtxi;
4907  }
4908  copy_functor(start_vtxoff + vtxnum, curve_vtxi);
4909  });
4910  if (grid_info.myHasPolygonCaps)
4911  {
4912  // Last cap starts after main grid
4913  const GA_Offset cap_start_vtxoff = start_vtxoff + grid.myNumVertices;
4914  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
4915  {
4916  // Last row cap
4917  curve_vtxi = num_vertices-1;
4918 
4919  for (exint col = 0; col < cap_vertex_count; ++col)
4920  {
4921  copy_functor(cap_start_vtxoff + col, curve_vtxi);
4922  }
4923  }
4924  else
4925  {
4926  // Last side col cap
4927  for (exint row = 0; row < cap_vertex_count; ++row)
4928  {
4929  copy_functor(cap_start_vtxoff + row, row);
4930  }
4931  }
4932  }
4933 }
4934 
4935 template<GA_AttributeOwner attrib_owner,bool wrap_to_invalid=false,typename COPY_FUNCTOR>
4936 static void
4937 copyCurveAttrib2(
4938  GA_Attribute *output_attrib,
4939  const GEO_Detail *curve_input,
4940  const sop_SweepGrid *grids,
4941  exint ngrids,
4942  const exint cross_sections_per_vertex,
4943  const GEO_SurfaceType surface_type,
4944  const bool output_points_only,
4945  const bool triangular_poles,
4946  const exint cap_divisions,
4947  const bool swap_row_col,
4948  const COPY_FUNCTOR &copy_functor)
4949 {
4950  UT_ASSERT(output_attrib->getOwner() == attrib_owner);
4951  SYS_STATIC_ASSERT(attrib_owner == GA_ATTRIB_POINT || attrib_owner == GA_ATTRIB_VERTEX);
4952 
4953  output_attrib->bumpDataId();
4954 
4955  auto&& functor = [&](const UT_BlockedRange<exint>& r)
4956  {
4957  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
4958  {
4959  const sop_SweepGrid &grid_info = grids[gridi];
4960  GU_GridT<GA_Offset> grid;
4961  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
4962 
4963  const GA_OffsetListRef vertices = curve_input->getPrimitiveVertexList(grid_info.myCurvePrimOff);
4964  if (attrib_owner == GA_ATTRIB_VERTEX)
4965  {
4966  copyVertexCurveAttribWrapper<wrap_to_invalid>(grid_info, grid, vertices.size(), cap_divisions, cross_sections_per_vertex, swap_row_col,
4967  [&copy_functor,&vertices](GA_Offset output_vtxoff, exint curve_vtxi)
4968  {
4969  GA_Offset curve_vtxoff = (wrap_to_invalid && curve_vtxi < 0) ? GA_INVALID_OFFSET : vertices(curve_vtxi);
4970  copy_functor(output_vtxoff, curve_vtxoff);
4971  });
4972  }
4973  else
4974  {
4975  UT_ASSERT(attrib_owner == GA_ATTRIB_POINT);
4976  const exint cap_divs = (grid_info.myVEndPoles && !grid_info.myHasPolygonCaps) ? cap_divisions : 0;
4977  exint prev_vtxi = -1;
4978  GA_Offset curve_ptoff;
4979  GUiterateGridPoints(grid, [cap_divs, &vertices, &prev_vtxi,
4980  &curve_ptoff, curve_input, cross_sections_per_vertex, swap_row_col, &copy_functor]
4981  (GA_Offset output_ptoff, exint row, exint col)
4982  {
4983  exint vtxi = swap_row_col ? col : row;
4984  if (vtxi != prev_vtxi)
4985  {
4986  exint curve_vtxi = vtxi - cap_divs;
4987  if (cross_sections_per_vertex != 1)
4988  curve_vtxi /= cross_sections_per_vertex;
4989  if (curve_vtxi < 0)
4990  curve_vtxi = 0;
4991  else if (curve_vtxi >= vertices.size())
4992  curve_vtxi = vertices.size()-1;
4993  GA_Offset curve_vtxoff = vertices(curve_vtxi);
4994  curve_ptoff = curve_input->vertexPoint(curve_vtxoff);
4995  prev_vtxi = vtxi;
4996  }
4997  copy_functor(output_ptoff, curve_ptoff);
4998  });
4999  }
5000  }
5001  };
5002 
5003  const exint PARALLEL_THRESHOLD = 2048;
5004  if (ngrids > 1 && output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD)
5005  {
5006  // Must harden all pages before multi-threading.
5007  output_attrib->hardenAllPages();
5008 
5009  UTparallelFor(UT_BlockedRange<exint>(0, ngrids), functor);
5010  }
5011  else
5012  {
5013  functor(UT_BlockedRange<exint>(0, ngrids));
5014  }
5015 }
5016 
5017 static void
5018 copyCurveAttrib(
5019  GA_Attribute *output_attrib,
5020  const GA_Attribute *curve_attrib,
5021  const GEO_Detail *curve_input,
5022  const sop_SweepGrid *grids,
5023  exint ngrids,
5024  const exint cross_sections_per_vertex,
5025  const GEO_SurfaceType surface_type,
5026  const bool output_points_only,
5027  const bool triangular_poles,
5028  const exint cap_divisions,
5029  const bool swap_row_col)
5030 {
5031  UT_ASSERT(output_attrib->getOwner() == curve_attrib->getOwner());
5032 
5033  const GA_AttributeOwner owner = output_attrib->getOwner();
5034  if (owner == GA_ATTRIB_POINT)
5035  {
5036  auto &&copy_functor = [output_attrib,curve_attrib](GA_Offset output_off, GA_Offset curve_off)
5037  {
5038  output_attrib->copy(output_off, *curve_attrib, curve_off);
5039  };
5040  copyCurveAttrib2<GA_ATTRIB_POINT>(
5041  output_attrib, curve_input,
5042  grids, ngrids, cross_sections_per_vertex,
5043  surface_type, output_points_only, triangular_poles, cap_divisions,
5044  swap_row_col, copy_functor);
5045  }
5046  else if (owner == GA_ATTRIB_VERTEX)
5047  {
5048  if (!output_points_only)
5049  {
5050  auto &&copy_functor = [output_attrib,curve_attrib](GA_Offset output_off, GA_Offset curve_off)
5051  {
5052  output_attrib->copy(output_off, *curve_attrib, curve_off);
5053  };
5054  copyCurveAttrib2<GA_ATTRIB_VERTEX>(
5055  output_attrib, curve_input,
5056  grids, ngrids, cross_sections_per_vertex,
5057  surface_type, output_points_only, triangular_poles, cap_divisions,
5058  swap_row_col, copy_functor);
5059  }
5060  }
5061  else if (owner == GA_ATTRIB_PRIMITIVE)
5062  {
5063  if (!output_points_only)
5064  {
5065  UT_ASSERT(curve_attrib->getOwner() == GA_ATTRIB_PRIMITIVE);
5066 
5067  output_attrib->bumpDataId();
5068 
5069  auto&& functor = [&](const UT_BlockedRange<exint>& r)
5070  {
5071  for (exint gridi = r.begin(); gridi < r.end(); ++gridi)
5072  {
5073  const sop_SweepGrid &grid_info = grids[gridi];
5074  GU_GridT<GA_Offset> grid;
5075  initGUGrid(grid_info, surface_type, output_points_only, triangular_poles, swap_row_col, grid_info.myStartPtOff, grid);
5076 
5077  // Copy same value to all primitives in grid
5078  const GA_Offset startprimoff = grid_info.myStartPrimOff;
5079  const GA_Offset endprimoff = startprimoff + grid.myNumPrimitives + (grid_info.myHasPolygonCaps ? 2 : 0);
5080  output_attrib->fill(GA_Range(output_attrib->getIndexMap(), startprimoff, endprimoff), *curve_attrib, grid_info.myCurvePrimOff);
5081  }
5082  };
5083 
5084  const exint PARALLEL_THRESHOLD = 2048;
5085  if (ngrids > 1 && output_attrib->getIndexMap().indexSize() >= PARALLEL_THRESHOLD)
5086  {
5087  // Must harden all pages before multi-threading.
5088  output_attrib->hardenAllPages();
5089 
5090  UTparallelFor(UT_BlockedRange<exint>(0, ngrids), functor);
5091  }
5092  else
5093  {
5094  functor(UT_BlockedRange<exint>(0, ngrids));
5095  }
5096  }
5097  }
5098  else if (owner == GA_ATTRIB_DETAIL)
5099  {
5100  output_attrib->replace(*curve_attrib);
5101  }
5102 }
5103 
5104 enum class UVStyle {
5105  NORMALIZED,
5106  ROUNDED,
5107  FULL
5108 };
5109 
5110 // This is for copying a single component from the cross section attribute to the grid.
5111 // NOTE: UVStyle should already be folded into uscale by the caller!
5112 template<typename T,GA_AttributeOwner output_owner>
5113 static void
5114 copyCrossSectionUVSingleGrid(
5115  GA_ATINumeric *output_attrib,
5116  const GA_ATINumeric *cross_section_attrib,
5117  const GA_AttributeOwner cross_section_owner,
5118  const GEO_Detail *cross_section_input,
5119  const sop_SweepGrid &grid_info,
5120  const GU_GridT<GA_Offset> &grid,
5121  const bool is_cross_section_uv_computed,
5122  const bool reverse_cross_sections,
5123  const bool swap_row_col,
5124  const bool flipu,
5125  const T uscale,
5126  const UT_Array<double> *missing_input_us = nullptr)
5127 {
5128  UT_ASSERT_P((cross_section_attrib != nullptr && cross_section_input != nullptr && cross_section_owner != GA_ATTRIB_INVALID)
5129  || (cross_section_owner == GA_ATTRIB_INVALID && missing_input_us != nullptr));
5130 
5132 
5133  const int tuple_size = output_attrib->getTupleSize();
5134 
5135  // Non-transforming
5136  auto &&get_transform = [](const sop_SweepGridTransformWrapper *transforms, exint row, const sop_SweepGrid &grid_info) -> const void*
5137  {
5138  return nullptr;
5139  };
5140 
5141  if (cross_section_owner == GA_ATTRIB_INVALID)
5142  {
5143  // Single cross section with implicit U value already computed.
5144  UT_ASSERT(missing_input_us != nullptr);
5145  UT_ASSERT(output_owner == GA_ATTRIB_VERTEX || output_owner == GA_ATTRIB_POINT);
5146  if (tuple_size == 2)
5147  {
5148  const GA_RWHandleT<UT_Vector2T<T>> outputh(output_attrib);
5149  UT_ASSERT_P(outputh.isValid());
5150  auto &&copy_functor = [missing_input_us,&outputh,flipu,swap_row_col,uscale](
5151  GA_Offset output_offset, exint row, exint col)
5152  {
5153  const exint cross_section_vtxi = swap_row_col ? row : col;
5154  T value = (cross_section_vtxi == missing_input_us->size()) ? T(1.0) : (*missing_input_us)[cross_section_vtxi];
5155  if (flipu)
5156  value = T(1.0)-value;
5157  UT_Vector2T<T> output_value = outputh.get(output_offset);
5158  output_value[theUComponent] = uscale*value;
5159  outputh.set(output_offset, output_value);
5160  };
5161  if (output_owner == GA_ATTRIB_VERTEX)
5162  copyVertexCrossSectionWrapper(grid, grid_info, copy_functor);
5163  else
5164  GUiterateGridPoints(grid, copy_functor);
5165  }
5166  else if (tuple_size == 3)
5167  {
5168  const GA_RWHandleT<UT_Vector3T<T>> outputh(output_attrib);
5169  UT_ASSERT_P(outputh.isValid());
5170  auto &&copy_functor = [missing_input_us,&outputh,flipu,swap_row_col,uscale](
5171  GA_Offset output_offset, exint row, exint col)
5172  {
5173  const exint cross_section_vtxi = swap_row_col ? row : col;
5174  T value = (cross_section_vtxi == missing_input_us->size()) ? T(1.0) : (*missing_input_us)[cross_section_vtxi];
5175  if (flipu)
5176  value = T(1.0)-value;
5177  UT_Vector3T<T> output_value = outputh.get(output_offset);
5178  output_value[theUComponent] = uscale*value;
5179  outputh.set(output_offset, output_value);
5180  };
5181  if (output_owner == GA_ATTRIB_VERTEX)
5182  copyVertexCrossSectionWrapper(grid, grid_info, copy_functor);
5183  else
5184  GUiterateGridPoints(grid, copy_functor);
5185  }
5186  return;
5187  }
5188 
5189  if (tuple_size == 2)
5190  {
5191  // Copy u component of 2-component numeric attribute
5192  auto &&transform_and_copy = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5193  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
5194  const void *transform,
5195  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
5196  {
5197  T value;
5198  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset == GA_INVALID_OFFSET)
5199  value = reverse_cross_sections ? T(0.0) : T(1.0);
5200  else
5201  value = cross_sectionh.get(cross_section_offset);
5202 
5203  if (flipu)
5204  value = T(1.0)-value;
5205  if (is_cross_section_uv_computed)
5206  value *= uscale;
5207  UT_Vector2T<T> output_value = outputh.get(output_offset);
5208  output_value[theUComponent] = value;
5209  outputh.set(output_offset, output_value);
5210  };
5211  auto &&transform_and_interp = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5212  const GA_ROHandleT<T> &cross_sectionh,
5213  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
5214  const void *transform,
5215  const GA_RWHandleT<UT_Vector2T<T>> &outputh, GA_Offset output_offset)
5216  {
5217  UT_ASSERT_P(GAisValid(cross_section_offset0));
5218  const T value0 = cross_sectionh.get(cross_section_offset0);
5219  // cross_section_offset1 might be invalid here if we have closed, non-unrolled
5220  // cross sections with different numbers of vertices and we're on the last edge.
5221  T value1;
5222  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset1 == GA_INVALID_OFFSET)
5223  value1 = reverse_cross_sections ? T(0.0) : T(1.0);
5224  else
5225  value1 = cross_sectionh.get(cross_section_offset1);
5226 
5227  T value = SYSlerp(value0, value1, t);
5228  if (flipu)
5229  value = T(1.0)-value;
5230  if (is_cross_section_uv_computed)
5231  value *= uscale;
5232  UT_Vector2T<T> output_value = outputh.get(output_offset);
5233  output_value[theUComponent] = value;
5234  outputh.set(output_offset, output_value);
5235  };
5236  GA_ROHandleT<T> cross_sectionh(cross_section_attrib);
5237  GA_RWHandleT<UT_Vector2T<T>> outputh(output_attrib);
5238  // true indicates to specify GA_INVALID_OFFSET, instead of the first vertex,
5239  // when wrapping, so that 1.0 can be written.
5240  constexpr bool wrap_to_invalid = output_owner == GA_ATTRIB_VERTEX;
5241  copyCrossSectionAttributeWrapper2<void, output_owner, wrap_to_invalid>(grid,
5242  outputh, cross_sectionh, cross_section_owner, cross_section_input, nullptr, grid_info,
5243  reverse_cross_sections, swap_row_col,
5244  get_transform, transform_and_copy, transform_and_interp);
5245  return;
5246  }
5247  if (tuple_size == 3)
5248  {
5249  // Copy u component of 3-component numeric attribute
5250  auto &&transform_and_copy = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5251  const GA_ROHandleT<T> &cross_sectionh, GA_Offset cross_section_offset,
5252  const void *transform,
5253  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
5254  {
5255  T value;
5256  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset == GA_INVALID_OFFSET)
5257  value = reverse_cross_sections ? T(0.0) : T(1.0);
5258  else
5259  value = cross_sectionh.get(cross_section_offset);
5260 
5261  if (flipu)
5262  value = T(1.0)-value;
5263  if (is_cross_section_uv_computed)
5264  value *= uscale;
5265  UT_Vector3T<T> output_value = outputh.get(output_offset);
5266  output_value[theUComponent] = value;
5267  outputh.set(output_offset, output_value);
5268  };
5269  auto &&transform_and_interp = [flipu,reverse_cross_sections,uscale,is_cross_section_uv_computed](
5270  const GA_ROHandleT<T> &cross_sectionh,
5271  GA_Offset cross_section_offset0, GA_Offset cross_section_offset1, T t,
5272  const void *transform,
5273  const GA_RWHandleT<UT_Vector3T<T>> &outputh, GA_Offset output_offset)
5274  {
5275  UT_ASSERT_P(GAisValid(cross_section_offset0));
5276  const T value0 = cross_sectionh.get(cross_section_offset0);
5277  // cross_section_offset1 might be invalid here if we have closed, non-unrolled
5278  // cross sections with different numbers of vertices and we're on the last edge.
5279  T value1;
5280  if (output_owner == GA_ATTRIB_VERTEX && cross_section_offset1 == GA_INVALID_OFFSET)
5281  value1 = reverse_cross_sections ? T(0.0) : T(1.0);
5282  else
5283  value1 = cross_sectionh.get(cross_section_offset1);
5284 
5285  T value = SYSlerp(value0, value1, t);
5286  if (flipu)
5287  value = T(1.0)-value;
5288  if (is_cross_section_uv_computed)
5289  value *= uscale;
5290  UT_Vector3T<T> output_value = outputh.get(output_offset);
5291  output_value[theUComponent] = value;
5292  outputh.set(output_offset, output_value);
5293  };
5294  GA_ROHandleT<T> cross_sectionh(cross_section_attrib);
5295  GA_RWHandleT<UT_Vector3T<T>> outputh(output_attrib);
5296  // true indicates to specify GA_INVALID_OFFSET, instead of the first vertex,
5297  // when wrapping, so that 1.0 can be written.
5298  constexpr bool wrap_to_invalid = output_owner == GA_ATTRIB_VERTEX;
5299  copyCrossSectionAttributeWrapper2<void, output_owner, wrap_to_invalid>(grid,
5300  outputh, cross_sectionh, cross_section_owner, cross_section_input, nullptr, grid_info,
5301  reverse_cross_sections, swap_row_col,
5302  get_transform, transform_and_copy, transform_and_interp);
5303  return;
5304  }
5305 }
5306 
5307 template<typename T>
5308 static UT_Vector3D crossSectionCenter(
5309  bool swap_row_col,
5310  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5311  const GU_GridT<GA_Offset> &grid,
5312  const exint cross_section_nedges,
5313  const exint curve_row)
5314 {
5315  UT_Vector3D sum(0,0,0);
5316  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_nedges; ++cross_section_vtxi)
5317  {
5318  exint row = curve_row;;
5319  exint col = cross_section_vtxi;
5320  if (swap_row_col)
5321  UTswap(row, col);
5322  const UT_Vector3T<T> curr_pos = pos.get(grid.getPoint(row, col));
5323  sum += curr_pos;
5324  }
5325 
5326  return sum / cross_section_nedges;
5327 }
5328 
5329 // Computes the total "length" of a single cross section in
5330 // the given grid. If dir0 is null, positions are treated as is.
5331 // If dir0 is non-null and dir1 is null, dir0 will be projected
5332 // out of the positions before length is computed.
5333 // If dir0 and dir1 are both non-null, the average of the two projections
5334 // will be computed. Each must be normalized if non-null.
5335 template<typename T>
5336 static double crossSectionLength(
5337  bool swap_row_col,
5338  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5339  const GU_GridT<GA_Offset> &grid,
5340  const exint cross_section_nedges,
5341  const exint curve_row,
5342  const UT_Vector3T<T> *dir0,
5343  const UT_Vector3T<T> *dir1)
5344 {
5345  double cross_section_length = 0;
5346  exint row = curve_row;
5347  exint col = 0;
5348  if (swap_row_col)
5349  UTswap(row, col);
5350  UT_Vector3T<T> prevpos = pos.get(grid.getPoint(row, col));
5351  UT_Vector3T<T> prevpos0;
5352  UT_Vector3T<T> prevpos1;
5353  if (dir0 != nullptr)
5354  {
5355  T d = prevpos.dot(*dir0);
5356  prevpos0 = prevpos - d*(*dir0);
5357  if (dir1 != nullptr)
5358  {
5359  d = prevpos.dot(*dir1);
5360  prevpos1 = prevpos - d*(*dir1);
5361  }
5362  }
5363  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_nedges; ++cross_section_vtxi)
5364  {
5365  exint row = curve_row;
5366  exint col = cross_section_vtxi+1;
5367  if (swap_row_col)
5368  UTswap(row, col);
5369  const UT_Vector3T<T> nextpos = pos.get(grid.getPoint(row, col));
5370  UT_Vector3T<T> nextpos0;
5371  UT_Vector3T<T> nextpos1;
5372  if (dir0 != nullptr)
5373  {
5374  T d = nextpos.dot(*dir0);
5375  nextpos0 = nextpos - d*(*dir0);
5376  T dist0 = prevpos0.distance(nextpos0);
5377  if (dir1 != nullptr)
5378  {
5379  d = nextpos.dot(*dir1);
5380  nextpos1 = nextpos - d*(*dir1);
5381 
5382  cross_section_length += 0.5f*(dist0 + prevpos1.distance(nextpos1));
5383  }
5384  else
5385  {
5386  cross_section_length += dist0;
5387  }
5388  }
5389  else
5390  {
5391  cross_section_length += prevpos.distance(nextpos);
5392  }
5393  prevpos = nextpos;
5394  if (dir0 != nullptr)
5395  {
5396  prevpos0 = nextpos0;
5397  if (dir1 != nullptr)
5398  prevpos1 = nextpos1;
5399  }
5400  }
5401  return cross_section_length;
5402 }
5403 
5404 template<typename T, typename FUNCTOR>
5405 static void iterateCurveEdgeLengths(
5406  const exint curve_nedges,
5407  const bool use_mesh_edge_lengths,
5408 
5409  const exint cross_section_npts,
5410  const bool swap_row_col,
5411  const GA_ROHandleT<UT_Vector3T<T>> &pos,
5412  const GU_GridT<GA_Offset> &grid,
5413 
5414  const sop_SweepGrid &grid_info,
5415  const exint cap_divisions,
5416  const GA_ROHandleT<UT_Vector3T<T>> &curve_pos,
5417  const GEO_Detail *curve_input,
5418  const GA_OffsetListRef &curve_vertices,
5419 
5420  FUNCTOR&& functor)
5421 {
5422  for (exint curve_vtxi = 0; curve_vtxi < curve_nedges; ++curve_vtxi)
5423  {
5424  double length_sum = 0;
5425  exint length_sum_count;
5426  if (use_mesh_edge_lengths)
5427  {
5428  length_sum_count = cross_section_npts;
5429  for (exint cross_section_vtxi = 0; cross_section_vtxi < cross_section_npts; ++cross_section_vtxi)
5430  {
5431  exint col0 = swap_row_col ? curve_vtxi : cross_section_vtxi;
5432  exint col1 = swap_row_col ? curve_vtxi+1 : cross_section_vtxi;
5433  exint row0 = swap_row_col ? cross_section_vtxi : curve_vtxi;
5434  exint row1 = swap_row_col ? cross_section_vtxi : curve_vtxi+1;
5435  const UT_Vector3T<T> p0 = pos.get(grid.getPoint(row0, col0));
5436  const UT_Vector3T<T> p1 = pos.get(grid.getPoint(row1, col1));
5437  length_sum += p0.distance(p1);
5438  }
5439  }
5440  else
5441  {
5442  length_sum_count = 1;
5443  exint orig_curve_vtxi = curve_vtxi;
5444  if (grid_info.myVEndPoles)
5445  {
5446  // Caps all correspond with the curve end positions
5447  orig_curve_vtxi -= cap_divisions;
5448  orig_curve_vtxi = SYSclamp(orig_curve_vtxi, GA_Size(0), curve_vertices.size()-1);
5449  }
5450  const UT_Vector3T<T> p0 = curve_pos.get(curve_input->vertexPoint(curve_vertices(orig_curve_vtxi)));
5451  exint orig_curve_vtxi1 = (curve_vertices.getExtraFlag() && curve_vtxi+1 == curve_nedges) ? 0 : curve_vtxi+1;
5452  if (grid_info.myVEndPoles)
5453  {
5454  // Same shift and clamping as with the previous vertex.
5455  // This can end up with both at the same vertex, so a length of zero for this edge.
5456  orig_curve_vtxi1 -= cap_divisions;
5457  orig_curve_vtxi1 = SYSclamp(orig_curve_vtxi1, GA_Size(0), curve_vertices.size()-1);
5458  }
5459  const UT_Vector3T<T> p1 = curve_pos.get(curve_input->vertexPoint(curve_vertices(orig_curve_vtxi1)));
5460  length_sum = p0.distance(p1);
5461  }
5462 
5463  functor(curve_vtxi, length_sum, length_sum_count);
5464  }
5465 }
5466 
5467 // NOTE: pos is double-precision to match computeGridUVScales
5468 template<GA_AttributeOwner output_owner, typename T>
5469 static void
5470 generateLengthWeightedV(
5471  const sop_SweepGrid &grid_info,
5472  const GU_GridT<GA_Offset> &grid,
5473  const exint cross_sections_per_vertex,
5474  const GA_RWHandleT<T> &outputh,
5475  const GA_ROHandleV3D &pos,
5476  UVStyle ustyle,
5477  UVStyle vstyle,
5478  bool scalevasu_usingmax,
5479  double vscale,
5480  double orig_vscale,
5481  bool swap_row_col,
5482  bool use_mesh_edge_lengths,
5483  const GEO_Detail *curve_input)
5484 {
5485  //const exint nedgerows = grid.myNumEdgeRows;
5486  //const exint nedgecols = grid.myNumEdgeCols;
5487  const exint curve_nedges = grid_info.myCurveNEdges;
5488  const exint cross_section_nedges = grid_info.myCrossSectionNEdges;
5489 
5490  GA_OffsetListRef curve_vertices;
5491  // NOTE: curve_pos is double-precision to match computeGridUVScales
5492  GA_ROHandleV3D curve_pos;
5493  exint cap_divisions = 0;
5494  if (curve_input != nullptr)
5495  {
5496  curve_vertices = curve_input->getPrimitiveVertexList(grid_info.myCurvePrimOff);
5497  if (grid_info.myVEndPoles)
5498  {
5499  UT_ASSERT_MSG(!curve_vertices.getExtraFlag(), "Curve should be open, not closed");
5500  exint orig_curve_nedges = curve_vertices.size() - 1;
5501  cap_divisions = (grid_info.myCurveNEdges - orig_curve_nedges)/2;
5502  UT_ASSERT(cap_divisions >= 1);
5503  }
5504  curve_pos.bind(curve_input->getP());
5505  }
5506 
5507  bool norm_u_unnorm_v = (ustyle == UVStyle::NORMALIZED && vstyle != UVStyle::NORMALIZED);
5508  bool special_case_u_division = (!scalevasu_usingmax && norm_u_unnorm_v);
5509  //bool need_max_cross_section_length = (scalevasu_usingmax && norm_u_unnorm_v);
5510 
5511  UT_Vector3D prevPos;
5512  bool prevPosValid = false;
5513  UT_Vector3D currPos;
5514  bool currPosValid = false;
5515  UT_Vector3D nextPos;
5516  UT_Vector3D dir0;
5517  UT_Vector3D dir1;
5518  double length0 = 0;
5519  double length1 = 0;
5520  UT_Vector3D *pdir0 = nullptr;
5521  UT_Vector3D *pdir1 = nullptr;
5522  double prev_cross_section_length = 0;
5523  if (special_case_u_division)
5524  {
5525  if (curve_input != nullptr)
5526  {
5527  UT_ASSERT_P(curve_pos.isValid());
5528  UT_ASSERT(curve_vertices.size() > 0);
5529  if (curve_vertices.size() >= 2)
5530  {
5531  currPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[0]));
5532  nextPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[1]));
5533  currPosValid = true;
5534  if (grid_info.myCurveClosed)
5535  {
5536  // Closed grid in curve direction, possibly open curve with shared point (unrolled)
5537  bool unrolled = !curve_vertices.getExtraFlag();
5538  exint last_vtxi = curve_vertices.size() - (unrolled ? 2 : 1);
5539  prevPos = curve_pos.get(curve_input->vertexPoint(curve_vertices[last_vtxi]));
5540  prevPosValid = true;
5541  }
5542  }
5543  }
5544  else if (curve_nedges >= 1 + (grid_info.myVEndPoles ? 2*cap_divisions : 0))
5545  {
5546  // If there's no curve, we use the cross section centers to
5547  // determine the implicit curve egde directions.
5548  exint start = grid_info.myVEndPoles ? cap_divisions : 0;
5549  currPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start);
5550  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, start+1);
5551  currPosValid = true;
5552  if (grid_info.myCurveClosed)
5553  {
5554  prevPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, curve_nedges-1);
5555  prevPosValid = true;
5556  }
5557  }
5558 
5559  if (prevPosValid)
5560  {
5561  dir0 = (currPos - prevPos);
5562  dir1 = (nextPos - currPos);
5563  length0 = dir0.normalize();
5564  length1 = dir1.normalize();
5565  if (length0 != 0 && length1 != 0)
5566  {
5567  pdir0 = &dir0;
5568  pdir1 = &dir1;
5569  }
5570  else if (length0 != 0 || length1 != 0)
5571  pdir0 = (length0 != 0) ? &dir0 : &dir1;
5572  }
5573  else if (currPosValid)
5574  {
5575  dir1 = (nextPos - currPos);
5576  length1 = dir1.normalize();
5577  if (length1 != 0)
5578  pdir0 = &dir1;
5579  }
5580 
5581  prev_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, 0, pdir0, pdir1);
5582  if (currPosValid)
5583  {
5584  currPos = nextPos;
5585  dir0 = dir1;
5586  length0 = length1;
5587  }
5588  }
5589 #if 0
5590  double max_cross_section_length = 0;
5591  if (need_max_cross_section_length)
5592  {
5593  for (exint curve_vtxi = 0; curve_vtxi <= curve_nedges; ++curve_vtxi)
5594  {
5595  double cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, curve_vtxi, dir0, dir1);
5596  max_cross_section_length = SYSmax(cross_section_length, max_cross_section_length);
5597  }
5598  }
5599 #endif
5600 
5601  UT_SmallArray<double> length_sums;
5602  length_sums.setSizeNoInit(curve_nedges+1);
5603  length_sums[0] = 0;
5604  double total_length_sum = 0;
5605  exint curve_npts = curve_nedges + !grid_info.myCurveClosed;
5606  exint cross_section_npts = cross_section_nedges + !grid_info.myCrossSectionClosed;
5607  iterateCurveEdgeLengths(
5608  curve_nedges,
5609  use_mesh_edge_lengths,
5610  cross_section_npts,
5611  swap_row_col,
5612  pos,
5613  grid,
5614  grid_info,
5615  cap_divisions,
5616  curve_pos,
5617  curve_input,
5618  curve_vertices,
5619  [&](exint curve_vtxi, double length_sum, exint length_sum_count)
5620  {
5621  if (special_case_u_division)
5622  {
5623  bool nextPosValid = false;
5624  // FIXME: Double-check these bounds!!!
5625  if (!grid_info.myVEndPoles || (curve_vtxi >= cap_divisions && curve_vtxi < curve_npts-cap_divisions))
5626  {
5627  nextPosValid = true;
5628  if (cross_section_nedges >= 1)
5629  {
5630  exint nextPosVtxi = curve_vtxi+2;
5631  exint threshold = curve_npts;
5632  if (grid_info.myVEndPoles)
5633  {
5634  nextPosVtxi -= cap_divisions;
5635  threshold -= 2*cap_divisions;
5636  }
5637  if (nextPosVtxi >= threshold)
5638  {
5639  if (grid_info.myCurveClosed)
5640  nextPosVtxi -= curve_npts;
5641  else
5642  nextPosValid = false;
5643  }
5644  if (nextPosValid)
5645  {
5646  if (curve_input != nullptr)
5647  {
5648  UT_ASSERT_P(curve_pos.isValid());
5649  exint next_curve_vtxi = nextPosVtxi;
5650  if (cross_sections_per_vertex != 1)
5651  next_curve_vtxi /= cross_sections_per_vertex;
5652  if (next_curve_vtxi < 0)
5653  next_curve_vtxi = 0;
5654  else if (next_curve_vtxi >= curve_vertices.size())
5655  next_curve_vtxi = curve_vertices.size()-1;
5656  GA_Offset curve_vtxoff = curve_vertices[next_curve_vtxi];
5657  GA_Offset curve_ptoff = curve_input->vertexPoint(curve_vtxoff);
5658  nextPos = curve_pos.get(curve_ptoff);
5659  }
5660  else
5661  {
5662  nextPos = crossSectionCenter(swap_row_col, pos, grid, cross_section_nedges, nextPosVtxi);
5663  }
5664  }
5665  }
5666  pdir0 = nullptr;
5667  pdir1 = nullptr;
5668  if (nextPosValid)
5669  {
5670  dir1 = (nextPos - currPos);
5671  length1 = dir1.normalize();
5672  if (length0 != 0 && length1 != 0)
5673  {
5674  pdir0 = &dir0;
5675  pdir1 = &dir1;
5676  }
5677  else if (length0 != 0 || length1 != 0)
5678  pdir0 = (length0 != 0) ? &dir0 : &dir1;
5679  }
5680  else if (length0 != 0)
5681  {
5682  pdir0 = &dir0;
5683  }
5684  }
5685  double next_cross_section_length = crossSectionLength(swap_row_col, pos, grid, cross_section_nedges, curve_vtxi+1, pdir0, pdir1);
5686  if (nextPosValid)
5687  {
5688  currPos = nextPos;
5689  dir0 = dir1;
5690  length0 = length1;
5691  }
5692  // Use adjacent cross section lengths
5693  double curr_cross_section_length_avg = 0.5*(prev_cross_section_length + next_cross_section_length);
5694  if (curr_cross_section_length_avg == 0)
5695  length_sum = 0;
5696  else
5697  {
5698  // Also normalize length_sum to an average at the same time
5699  length_sum /= (length_sum_count*curr_cross_section_length_avg);
5700  }
5701 
5702  prev_cross_section_length = next_cross_section_length;
5703  }
5704  else
5705  {
5706  // Normalize length_sum to an average
5707  length_sum /= length_sum_count;
5708  }
5709 
5710  total_length_sum += length_sum;
5711  length_sums[curve_vtxi+1] = vscale*total_length_sum;
5712  });
5713 
5714  UT_ASSERT_MSG(total_length_sum != 0 || (vstyle != UVStyle::NORMALIZED), "computeGridUVScales should have fallen back to v being uniform");
5715 
5716  if (vstyle == UVStyle::NORMALIZED)
5717  {
5718  // Ensure that the end is exactly 1 (or the original V scale), regardless of any roundoff error
5719  length_sums[curve_nedges] = orig_vscale;
5720  // If any earlier values are greater than 1, make them exactly 1.
5721  for (exint curve_vtxi = curve_nedges-1; curve_vtxi >= 0 && length_sums[curve_vtxi] > orig_vscale; ++curve_vtxi)
5722  {
5723  length_sums[curve_vtxi] = orig_vscale;
5724  }
5725  }
5726  else if (vstyle == UVStyle::ROUNDED)
5727  {
5728  // Ensure that the end is exactly an integer >= 1, regardless of any roundoff error
5729  double &rounded = length_sums[curve_nedges];
5730  rounded = SYSrint(rounded);
5731  if (rounded < 1)
5732  rounded = 1;
5733  length_sums[curve_nedges] = rounded;
5734  // If any earlier values are greater than rounded, make them exactly rounded.
5735  for (exint curve_vtxi = curve_nedges-1; curve_vtxi >= 0 && length_sums[curve_vtxi] > rounded; ++curve_vtxi)
5736  {
5737  length_sums[curve_vtxi] = rounded;
5738  }
5739  }
5740 
5741  // length_sums array should be ready for use, at this point.
5742 
5743 
5744  exint cap_vertex_count = 0;
5745  if (grid_info.myHasPolygonCaps && output_owner == GA_ATTRIB_VERTEX)
5746  {
5747  if (swap_row_col ? grid.myNoWrapU : grid.myNoWrapV)
5748  {
5749  // First row cap
5750  cap_vertex_count = cross_section_nedges;
5751  for (exint col = 0; col < cap_vertex_count; ++col)
5752  {