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