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