HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP_OrientAlongCurve.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2025
3  * Side Effects Software Inc. All rights reserved.
4  *
5  * Redistribution and use of Houdini Development Kit samples in source and
6  * binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  * 2. The name of Side Effects Software may not be used to endorse or
11  * promote products derived from this software without specific prior
12  * written permission.
13  *
14  * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
15  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
17  * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
20  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  *----------------------------------------------------------------------------
26  * This SOP computes orientations along curves, including treating closed
27  * polygons as closed loop curves.
28  */
29 
30 // A .proto.h file is an automatically generated header file based on theDsFile,
31 // below, to provide SOP_OrientAlongCurveParms, an easy way to access parameter
32 // values from SOP_OrientAlongCurveVerb::cook with the correct type, and
33 // SOP_OrientAlongCurveEnums, a namespace containing enum types for any ordinal
34 // menu parameters.
35 #include "SOP_OrientAlongCurve.proto.h"
36 
37 #include "GU_CurveFrame.h"
38 
39 #include <SOP/SOP_Node.h>
40 #include <SOP/SOP_NodeVerb.h>
41 #include <GU/GU_Detail.h>
42 #include <GOP/GOP_Manager.h>
43 #include <GA/GA_Handle.h>
44 #include <GA/GA_Iterator.h>
45 #include <GA/GA_Names.h>
46 #include <GA/GA_OffsetList.h>
47 #include <GA/GA_SplittableRange.h>
48 #include <GA/GA_Types.h>
50 #include <UT/UT_DSOVersion.h>
51 #include <UT/UT_Interrupt.h>
52 #include <UT/UT_PageArray.h>
53 #include <UT/UT_PageArrayImpl.h>
54 #include <UT/UT_ParallelUtil.h>
55 #include <UT/UT_StringHolder.h>
56 #include <UT/UT_UniquePtr.h>
57 #include <UT/UT_Vector3.h>
58 #include <SYS/SYS_Math.h>
59 
60 using namespace UT::Literal;
61 
62 
63 namespace HDK_Sample {
64 
65 //******************************************************************************
66 //* Setup *
67 //******************************************************************************
68 
70 {
71 public:
72  SOP_NodeParms *allocParms() const override { return new SOP_OrientAlongCurveParms(); }
73  //SOP_NodeCache *allocCache() const override { return new SOP_OrientAlongCurveCache(); }
74  UT_StringHolder name() const override { return theSOPTypeName; }
75 
76  CookMode cookMode(const SOP_NodeParms *parms) const override { return COOK_GENERIC; }
77 
78  void cook(const CookParms &cookparms) const override;
79 
80  /// This is the internal name of the SOP type.
81  /// It isn't allowed to be the same as any other SOP's type name.
83 
84  /// This static data member automatically registers
85  /// this verb class at library load time.
87 
88  /// This is the parameter interface string, below.
89  static const char *const theDsFile;
90 };
91 
92 // The static member variable definitions have to be outside the class definition.
93 // The declarations are inside the class.
94 const UT_StringHolder SOP_OrientAlongCurveVerb::theSOPTypeName("hdk_orientalongcurve"_sh);
95 const SOP_NodeVerb::Register<SOP_OrientAlongCurveVerb> SOP_OrientAlongCurveVerb::theVerb;
96 
97 /// This is the SOP class definition.
99 {
100 public:
101  static PRM_Template *buildTemplates();
102 
103  static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op)
104  {
105  return new SOP_OrientAlongCurve(net, name, op);
106  }
107 
108 protected:
109  const SOP_NodeVerb *cookVerb() const override;
110 
112  : SOP_Node(net, name, op)
113  {
114  // All verb SOPs must manage data IDs, to track what's changed
115  // from cook to cook.
116  mySopFlags.setManagesDataIDs(true);
117  }
118 
119  ~SOP_OrientAlongCurve() override {}
120 
121  /// Since this SOP implements a verb, cookMySop just delegates to the verb.
122  OP_ERROR cookMySop(OP_Context &context) override
123  {
124  return cookMyselfAsVerb(context);
125  }
126 
127  /// These are the labels that appear when hovering over the inputs.
128  const char *inputLabel(OP_InputIdx idx) const override
129  {
130  UT_ASSERT(idx >= 0);
131  switch (idx)
132  {
133  case 0: return "Curves";
134  default: return "Invalid Source";
135  }
136  }
137 
138  /// This just indicates whether an input wire gets drawn with a dotted line
139  /// in the network editor. If something is usually copied directly
140  /// into the output, a solid line (false) is used, but this SOP very often
141  /// doesn't do that for either input.
142  int isRefInput(OP_InputIdx i) const override
143  {
144  UT_ASSERT(i >= 0);
145  // The curves are passed through
146  return false;
147  }
148 };
149 
150 PRM_Template *SOP_OrientAlongCurve::buildTemplates()
151 {
152  static PRM_TemplateBuilder templ("SOP_OrientAlongCurve.C"_sh, SOP_OrientAlongCurveVerb::theDsFile);
153  if (templ.justBuilt())
154  {
155  templ.setChoiceListPtr("group", &SOP_Node::primGroupMenu);
156  }
157  return templ.templates();
158 }
159 
160 const SOP_NodeVerb *SOP_OrientAlongCurve::cookVerb() const
161 {
162  return SOP_OrientAlongCurveVerb::theVerb.get();
163 }
164 
165 } // End of HDK_Sample namespace
166 
167 /// newSopOperator is the hook that Houdini grabs from this dll
168 /// and invokes to register the SOP. In this case, we add ourselves
169 /// to the specified operator table.
171 {
172  table->addOperator(new OP_Operator(
174  "HDK Orientation Along Curve", // UI name
175  HDK_Sample::SOP_OrientAlongCurve::myConstructor, // How to build the SOP
177  1, // Min # of sources
178  1, // Max # of sources
179  nullptr,// Custom local variables (none)
180  0)); // No flags: not a generator, no merge input, not an output
181 }
182 
183 namespace HDK_Sample {
184 
185 //******************************************************************************
186 //* Parameters *
187 //******************************************************************************
188 
189 /// This is a multi-line raw string specifying the parameter interface for this SOP.
190 const char *const SOP_OrientAlongCurveVerb::theDsFile = R"THEDSFILE(
191 {
192  name parameters
193  parm {
194  name "group"
195  cppname "CurveGroup"
196  label "Curve Group"
197  type string
198  default { "" }
199  parmtag { "script_action" "import soputils\nkwargs['geometrytype'] = (hou.geometryType.Primitives,)\nkwargs['inputindex'] = 0\nsoputils.selectGroupParm(kwargs)" }
200  parmtag { "script_action_help" "Select geometry from an available viewport.\nShift-click to turn on Select Groups." }
201  parmtag { "script_action_icon" "BUTTONS_reselect" }
202  }
203  groupsimple {
204  name "frame_folder"
205  label "Frame"
206 
207  parm {
208  name "tangenttype"
209  cppname "TangentType"
210  label "Tangent Type"
211  type ordinal
212  default { "0" } // Default to first entry in menu, "avgdir"
213  menu {
214  "avgdir" "Average of Edge Directions"
215  "diff" "Central Difference"
216  "prev" "Previous Edge"
217  "next" "Next Edge"
218  "none" "Z Axis (Ignore Curve)"
219  }
220  }
221  parm {
222  name "continuousclosed"
223  cppname "ContinuousClosed"
224  label "Make Closed Curve Orientations Continuous"
225  type toggle
226  default { "1" }
227  disablewhen "{ tangenttype == none }"
228  }
229  parm {
230  name "extrapolateendtangents"
231  cppname "ExtrapolateEndTangents"
232  label "Extrapolate End Tangents"
233  type toggle
234  default { "0" }
235  disablewhen "{ tangenttype == none }"
236  }
237  parm {
238  name "transformbyattribs"
239  cppname "TransformByAttribs"
240  label "Transform Using Point Attributes"
241  type toggle
242  default { "0" }
243  }
244  //parm {
245  // name "orienttype"
246  // cppname "OrientType"
247  // label "Orientation Type"
248  // type ordinal
249  // default { "0" } // Default to first entry in menu, "minrot"
250  // menu {
251  // "minrot" "Minimal Rotation"
252  // "curvature" "Curvature"
253  // "bank" "Bank Around Turns"
254  // }
255  // disablewhen "{ tangenttype == none }"
256  //}
257  parm {
258  name "sepparm"
259  label ""
260  type separator
261  default { "" }
262  }
263  parm {
264  name "upvectortype"
265  cppname "UpVectorType"
266  label "Target Up Vector"
267  type ordinal
268  default { "0" } // Default to first entry in menu, "normal"
269  menu {
270  "normal" "Curve Normal"
271  "x" "X Axis"
272  "y" "Y Axis"
273  "z" "Z Axis"
274  "attrib" "Attribute"
275  "custom" "Custom"
276  }
277  disablewhen "{ tangenttype == none }"
278  }
279  //parm {
280  // name "usenormalup"
281  // cppname "UseNormalUp"
282  // label "Use Curve Normal as Up Vector (When Valid)"
283  // type toggle
284  // default { "1" }
285  // disablewhen "{ tangenttype == none }"
286  //}
287  parm {
288  name "upvectoratstart"
289  cppname "UpVectorAtStart"
290  label "Target Up Vector at Start (else Average)"
291  type toggle
292  default { "1" }
293  disablewhen "{ tangenttype == none }"
294  }
295  parm {
296  name "useendupvector"
297  cppname "UseEndUpVector"
298  label "Use Target End Up Vector"
299  type toggle
300  default { "0" }
301  disablewhen "{ tangenttype == none } { upvectoratstart == 0 }"
302  }
303  parm {
304  name "upvectorattrib"
305  cppname "UpVectorAttrib"
306  label "Start Up Attribute"
307  type string
308  default { "target_up" }
309  disablewhen "{ tangenttype == none } { upvectortype != attrib }"
310  hidewhen "{ tangenttype == none } { upvectortype != attrib }"
311  }
312  parm {
313  name "endupvectorattrib"
314  cppname "EndUpVectorAttrib"
315  label "End Up Attribute"
316  type string
317  default { "target_up_end" }
318  disablewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
319  hidewhen "{ tangenttype == none } { upvectortype != attrib } { useendupvector == 0 } { upvectoratstart == 0 }"
320  }
321  parm {
322  name "upvector"
323  cppname "UpVector"
324  label "Start Up Vector"
325  type vector
326  size 3
327  default { "0" "1" "0" }
328  disablewhen "{ tangenttype == none } { upvectortype != custom }"
329  hidewhen "{ tangenttype == none } { upvectortype != custom }"
330  }
331  parm {
332  name "endupvector"
333  cppname "EndUpVector"
334  label "End Up Vector"
335  type vector
336  size 3
337  default { "0" "1" "0" }
338  disablewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
339  hidewhen "{ tangenttype == none } { upvectortype != custom } { useendupvector == 0 } { upvectoratstart == 0 }"
340  }
341  }
342 )THEDSFILE"
343 // ==== This is necessary because MSVC++ has a limit of 16380 character per
344 // ==== string literal
345 R"THEDSFILE(
346  groupcollapsible {
347  name "rotation_folder"
348  label "Additional Rotations"
349  grouptag { "group_type" "collapsible" }
350  parmtag { "group_default" "0" }
351 
352  parm {
353  name "rOrd"
354  cppname "ROrd"
355  label "Rotate Order"
356  type ordinal
357  // NOTE: The default rotation order X,Y,Z is semi-arbitrary, but Z
358  // should probably be last, since it always needs to twist
359  // around the curve tangent. The X and Y rotations may have
360  // just been to reorient a cross-section before copying.
361  default { "xyz" }
362  menu {
363  "xyz" "Pitch, Yaw, Roll"
364  "xzy" "Pitch, Roll, Yaw"
365  "yxz" "Yaw, Pitch, Roll"
366  "yzx" "Yaw, Roll, Pitch"
367  "zxy" "Roll, Pitch, Yaw"
368  "zyx" "Roll, Yaw, Pitch"
369  }
370  }
371  parm {
372  name "applyroll"
373  cppname "ApplyRoll"
374  label "Apply Roll or Twist"
375  type toggle
376  default { "0" }
377  }
378  parm {
379  name "roll"
380  label "Roll"
381  type float
382  default { "0" }
383  range { -180 180 }
384  hidewhen "{ applyroll == 0 }"
385  }
386  parm {
387  name "rollper"
388  cppname "RollPer"
389  label "Twist Per"
390  type ordinal
391  default { "4" } // Default to "fulldistance" entry in menu
392  menu {
393  "edge" "Per Edge"
394  "distance" "Per Unit Distance"
395  "attrib" "Scale by Attribute"
396  "fulledges" "Per Full Curve by Edges"
397  "fulldistance" "Per Full Curve by Distance"
398  }
399  hidewhen "{ applyroll == 0 }"
400  }
401  parm {
402  name "fulltwists"
403  cppname "FullTwists"
404  label "Full Twists"
405  type integer
406  default { "0" }
407  range { -10 10 }
408  hidewhen "{ applyroll == 0 }"
409  }
410  parm {
411  name "incroll"
412  cppname "IncRoll"
413  label "Partial Twist"
414  type float
415  default { "0" }
416  range { -180 180 }
417  hidewhen "{ applyroll == 0 }"
418  }
419  parm {
420  name "rollattrib"
421  cppname "RollAttrib"
422  label "Twist Ramp Attribute"
423  type string
424  default { "roll" }
425  disablewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
426  hidewhen "{ applyroll == 0 } { applyroll == 1 rollper != attrib }"
427  }
428  parm {
429  name "sepparmroll"
430  label ""
431  type separator
432  default { "" }
433  hidewhen "{ applyroll == 0 }"
434  }
435  parm {
436  name "applyyaw"
437  cppname "ApplyYaw"
438  label "Apply Yaw"
439  type toggle
440  default { "0" }
441  }
442  parm {
443  name "yaw"
444  label "Yaw"
445  type float
446  default { "0" }
447  range { -180 180 }
448  hidewhen "{ applyyaw == 0 }"
449  }
450  parm {
451  name "yawper"
452  cppname "YawPer"
453  label "Yaw Per"
454  type ordinal
455  default { "4" } // Default to "fulldistance" entry in menu
456  menu {
457  "edge" "Per Edge"
458  "distance" "Per Unit Distance"
459  "attrib" "Scale By Attribute"
460  "fulledges" "Per Full Curve by Edges"
461  "fulldistance" "Per Full Curve by Distance"
462  }
463  hidewhen "{ applyyaw == 0 }"
464  }
465  parm {
466  name "incyaw"
467  cppname "IncYaw"
468  label "Incremental Yaw"
469  type float
470  default { "0" }
471  range { -180 180 }
472  hidewhen "{ applyyaw == 0 }"
473  }
474  parm {
475  name "yawattrib"
476  cppname "YawAttrib"
477  label "Yaw Ramp Attribute"
478  type string
479  default { "yaw" }
480  disablewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
481  hidewhen "{ applyyaw == 0 } { applyyaw == 1 yawper != attrib }"
482  }
483  parm {
484  name "sepparmyaw"
485  label ""
486  type separator
487  default { "" }
488  hidewhen "{ applyyaw == 0 }"
489  }
490  parm {
491  name "applypitch"
492  cppname "ApplyPitch"
493  label "Apply Pitch"
494  type toggle
495  default { "0" }
496  }
497  parm {
498  name "pitch"
499  label "Pitch"
500  type float
501  default { "0" }
502  range { -180 180 }
503  hidewhen "{ applypitch == 0 }"
504  }
505  parm {
506  name "pitchper"
507  cppname "PitchPer"
508  label "Pitch Per"
509  type ordinal
510  default { "4" } // Default to "fulldistance" entry in menu
511  menu {
512  "edge" "Per Edge"
513  "distance" "Per Unit Distance"
514  "attrib" "Scale By Attribute"
515  "fulledges" "Per Full Curve by Edges"
516  "fulldistance" "Per Full Curve by Distance"
517  }
518  hidewhen "{ applypitch == 0 }"
519  }
520  parm {
521  name "incpitch"
522  cppname "IncPitch"
523  label "Incremental Pitch"
524  type float
525  default { "0" }
526  range { -180 180 }
527  hidewhen "{ applypitch == 0 }"
528  }
529  parm {
530  name "pitchattrib"
531  cppname "PitchAttrib"
532  label "Pitch Ramp Attribute"
533  type string
534  default { "pitch" }
535  disablewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
536  hidewhen "{ applypitch == 0 } { applypitch == 1 pitchper != attrib }"
537  }
538  }
539 )THEDSFILE"
540 // ==== This is necessary because MSVC++ has a limit of 16380 character per
541 // ==== string literal
542 R"THEDSFILE(
543  groupcollapsible {
544  name "scales_folder"
545  label "Scales and Shears"
546  grouptag { "group_type" "collapsible" }
547  parmtag { "group_default" "0" }
548 
549  parm {
550  name "normalize"
551  label "Normalize Scales"
552  type toggle
553  default { "1" }
554  }
555  parm {
556  name "scale"
557  label "Uniform Scale"
558  type float
559  default { "1" }
560  range { 0 4 }
561  }
562  parm {
563  name "stretcharoundturns"
564  cppname "StretchAroundTurns"
565  label "Stretch Around Turns"
566  type toggle
567  default { "0" }
568  }
569  parm {
570  name "maxstretcharoundturns"
571  cppname "MaxStretchAroundTurns"
572  label "Max Stretch"
573  type log
574  default { "10" }
575  range { 1! 100 }
576  disablewhen "{ stretcharoundturns == 0 }"
577  }
578  }
579 )THEDSFILE"
580 // ==== This is necessary because MSVC++ has a limit of 16380 character per
581 // ==== string literal
582 R"THEDSFILE(
583  groupcollapsible {
584  name "output_folder"
585  label "Output Attributes"
586  grouptag { "group_type" "collapsible" }
587  parmtag { "group_default" "1" }
588 
589  parm {
590  name "class"
591  cppname "Class"
592  label "Class"
593  type ordinal
594  default { "0" } // Default to first entry in menu, "point"
595  menu {
596  "point" "Point"
597  "vertex" "Vertex"
598  }
599  //joinnext
600  }
601  //parm {
602  // name "precision"
603  // cppname "Precision"
604  // label "Precision"
605  // type ordinal
606  // default { "1" } // Default to first entry in menu, "32"
607  // menu {
608  // "16" "16-bit"
609  // "32" "32-bit"
610  // "64" "64-bit"
611  // }
612  //}
613  parm {
614  name "outputxaxis"
615  cppname "OutputXAxis"
616  label "Output X Axis"
617  type toggle
618  default { "0" }
619  nolabel
620  joinnext
621  }
622  parm {
623  name "xaxisname"
624  cppname "XAxisName"
625  label "X Axis"
626  type string
627  default { "out" }
628  disablewhen "{ outputxaxis == 0 }"
629  }
630  parm {
631  name "outputyaxis"
632  cppname "OutputYAxis"
633  label "Output Y Axis"
634  type toggle
635  default { "1" }
636  nolabel
637  joinnext
638  }
639  parm {
640  name "yaxisname"
641  cppname "YAxisName"
642  label "Y Axis"
643  type string
644  default { "up" }
645  disablewhen "{ outputyaxis == 0 }"
646  }
647  parm {
648  name "outputzaxis"
649  cppname "OutputZAxis"
650  label "Output Z Axis"
651  type toggle
652  default { "1" }
653  nolabel
654  joinnext
655  }
656  parm {
657  name "zaxisname"
658  cppname "ZAxisName"
659  label "Tangent (Z Axis)"
660  type string
661  default { "N" }
662  disablewhen "{ outputzaxis == 0 }"
663  }
664  parm {
665  name "sepparmattrib"
666  label ""
667  type separator
668  default { "" }
669  }
670  parm {
671  name "outputtranslation"
672  cppname "OutputTranslation"
673  label "Output Translation"
674  type toggle
675  default { "0" }
676  nolabel
677  joinnext
678  }
679  parm {
680  name "translationname"
681  cppname "TranslationName"
682  label "Translation"
683  type string
684  default { "P" }
685  disablewhen "{ outputtranslation == 0 }"
686  }
687  parm {
688  name "outputquaternion"
689  cppname "OutputQuaternion"
690  label "Output Quaternion"
691  type toggle
692  default { "0" }
693  nolabel
694  joinnext
695  }
696  parm {
697  name "quaternionname"
698  cppname "QuaternionName"
699  label "Quaternion"
700  type string
701  default { "orient" }
702  disablewhen "{ outputquaternion == 0 }"
703  }
704  parm {
705  name "outputtransform3"
706  cppname "OutputTransform3"
707  label "Output 3x3 Transform"
708  type toggle
709  default { "0" }
710  nolabel
711  joinnext
712  }
713  parm {
714  name "transform3name"
715  cppname "Transform3Name"
716  label "3x3 Transform"
717  type string
718  default { "transform" }
719  disablewhen "{ outputtransform3 == 0 }"
720  }
721  parm {
722  name "outputtransform4"
723  cppname "OutputTransform4"
724  label "Output 4x4 Transform"
725  type toggle
726  default { "0" }
727  nolabel
728  joinnext
729  }
730  parm {
731  name "transform4name"
732  cppname "Transform4Name"
733  label "4x4 Transform"
734  type string
735  default { "transform" }
736  disablewhen "{ outputtransform4 == 0 }"
737  }
738  }
739 }
740 )THEDSFILE";
741 
742 //******************************************************************************
743 //* Cooking *
744 //******************************************************************************
745 
746 using namespace SOP_OrientAlongCurveEnums;
747 using namespace GU_CurveFrame;
748 
749 template<typename T>
750 static void
751 sopSetupTransformAttribs(
752  GEO_Detail *output_geo,
753  const SOP_NodeVerb::CookParms &cookparms,
754  const UT_StringHolder &axis_attrib_name,
755  const GA_AttributeOwner output_owner,
756  const GA_Storage output_storage,
757  const exint tuple_size,
758  GA_ATINumeric *&output_attrib,
759  GA_ATINumeric *&intermediate_attrib,
760  GA_ATINumericUPtr &intermediate_attrib_deleter,
761  GA_TypeInfo type_info)
762 {
763  if (output_owner == GA_ATTRIB_VERTEX && axis_attrib_name == GA_Names::P)
764  {
765  cookparms.sopAddWarning(SOP_ErrorCodes::SOP_MESSAGE, "Unable to create a vertex attribute named \"P\".");
766  output_attrib = nullptr;
767  return;
768  }
769  // Use existing attribute if present, in case we're just writing to part of it.
770  output_attrib = GA_ATINumeric::cast(output_geo->findAttribute(output_owner, axis_attrib_name));
771  if (!output_attrib)
772  {
773  output_attrib = UTverify_cast<GA_ATINumeric*>(output_geo->createTupleAttribute(output_owner, axis_attrib_name, output_storage, tuple_size));
774  if (!output_attrib)
775  {
777  return;
778  }
779  }
780  else
781  {
782  if (output_attrib->getTupleSize() < tuple_size)
783  output_attrib->setTupleSize(tuple_size);
784  if (output_attrib->getStorage() != output_storage)
785  output_attrib->setStorage(output_storage);
786  }
787  output_attrib->bumpDataId();
788  output_attrib->setTypeInfo((axis_attrib_name == GA_Names::N && type_info == GA_TYPE_VECTOR) ? GA_TYPE_NORMAL : type_info);
789 
790  if (output_owner != GA_ATTRIB_VERTEX)
791  {
792  const GA_Storage intermediate_storage = SYSisSame<T,fpreal32>() ? GA_STORE_REAL32 : GA_STORE_REAL64;
793  intermediate_attrib_deleter = output_geo->createDetachedTupleAttribute(GA_ATTRIB_VERTEX, intermediate_storage, tuple_size);
794  intermediate_attrib = intermediate_attrib_deleter.get();
795  }
796  else
797  {
798  intermediate_attrib = output_attrib;
799  }
800 }
801 
802 template<typename T>
803 static void
804 sopCreateQuaternionAttrib(
805  GEO_Detail *output_geo,
806  const GA_PrimitiveGroup *curve_group,
807  const SOP_NodeVerb::CookParms &cookparms,
808  const GA_ROHandleM4D &transform_attrib,
809  const UT_StringHolder &axis_attrib_name,
810  const GA_AttributeOwner output_owner,
811  const GA_Storage output_storage)
812 {
813  GA_ATINumeric *output_attrib;
814  GA_ATINumeric *intermediate_attrib;
815  GA_ATINumericUPtr intermediate_attrib_deleter;
816  sopSetupTransformAttribs<T>(
817  output_geo, cookparms, axis_attrib_name,
818  output_owner, output_storage, 4, output_attrib,
819  intermediate_attrib, intermediate_attrib_deleter,
821 
822  if (!output_attrib)
823  return;
824 
825  GA_RWHandleT<UT_QuaternionT<T>> quat_attrib(intermediate_attrib);
826  UT_ASSERT(quat_attrib.isValid());
827  extractAttribFromTransform(output_geo, curve_group, transform_attrib, quat_attrib,
829  UT_QuaternionT<T> quaternion;
831  return quaternion;
832  });
833 
834  if (output_owner == GA_ATTRIB_VERTEX)
835  return;
836 
837  UT_ASSERT(output_owner == GA_ATTRIB_POINT);
838 
839  // Convert from vertex to point by averaging "directions" of the quaternions.
840  // Averaging quaternions linearly isn't as perfect as slerping, but if we
841  // normalize, it's usually pretty close in practice. If it's only 2 of them
842  // and they're averaged uniformly, it's exact.
843 
844  GA_RWHandleT<UT_QuaternionT<T>> output_handle(output_attrib);
845  UT_ASSERT(output_handle.isValid());
846 
847  auto &&functor = [output_geo,curve_group,&output_handle,&quat_attrib](const GA_SplittableRange &r)
848  {
850  GA_Offset end;
851  for (GA_Iterator it(r); it.blockAdvance(start, end); )
852  {
853  for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
854  {
855  // Sum up all of the values from vertices
856  UT_QuaternionT<T> direction_sum;
857  exint num_vertices = 0;
858 
859  for (GA_Offset vtxoff = output_geo->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = output_geo->vertexToNextVertex(vtxoff))
860  {
861  if (curve_group)
862  {
863  // Exclude vertices outside curve_group
864  GA_Offset primoff = output_geo->vertexPrimitive(vtxoff);
865  if (!curve_group->contains(primoff))
866  continue;
867  }
868 
869  UT_QuaternionT<T> direction = quat_attrib.get(vtxoff);
870  if (num_vertices == 0)
871  {
872  // Just copy first, since often only one
873  direction_sum = direction;
874  }
875  else
876  {
877  // NOTE: Don't need to normalize each one,
878  // since they should already be normalized.
879  // Avoid quaternions that represent rotations more than 180 degrees apart
880  if (dot(direction_sum, direction) < 0)
881  direction.negate();
882  direction_sum += direction;
883  }
884  ++num_vertices;
885  }
886 
887  if (num_vertices == 1)
888  output_handle.set(ptoff, direction_sum);
889  else if (num_vertices > 1)
890  {
891  // Normalize the direction sum.
892  direction_sum.normalize();
893  output_handle.set(ptoff, direction_sum);
894  }
895  }
896  }
897  };
898  if (curve_group)
899  {
900  GA_PointGroup point_group(*output_geo);
901  point_group.combine(curve_group);
902 
903  UTparallelFor(GA_SplittableRange(output_geo->getPointRange(&point_group)), functor);
904  }
905  else
906  {
907  UTparallelFor(GA_SplittableRange(output_geo->getPointRange()), functor);
908  }
909 }
910 
911 template<typename T,bool include_translates>
912 static void
913 sopCreateTransformAttrib(
914  GEO_Detail *output_geo,
915  const GA_PrimitiveGroup *curve_group,
916  const SOP_NodeVerb::CookParms &cookparms,
917  const GA_ROHandleM4D &transform_attrib,
918  const UT_StringHolder &axis_attrib_name,
919  const GA_AttributeOwner output_owner,
920  const GA_Storage output_storage)
921 {
922  const int tuple_size = include_translates ? 16 : 9;
923  GA_ATINumeric *output_attrib;
924  GA_ATINumeric *intermediate_attrib;
925  GA_ATINumericUPtr intermediate_attrib_deleter;
926  sopSetupTransformAttribs<T>(
927  output_geo, cookparms, axis_attrib_name,
928  output_owner, output_storage, tuple_size, output_attrib,
929  intermediate_attrib, intermediate_attrib_deleter,
931 
932  if (!output_attrib)
933  return;
934 
935  GA_RWHandleT<UT_Matrix3T<T>> transform3_attrib(include_translates ? nullptr : intermediate_attrib);
936  GA_RWHandleT<UT_Matrix4T<T>> transform4_attrib(include_translates ? intermediate_attrib : nullptr);
937  if (!include_translates)
938  {
939  UT_ASSERT(transform3_attrib.isValid());
940  extractAttribFromTransform(output_geo, curve_group, transform_attrib, transform3_attrib,
942  return UT_Matrix3T<T>(transform);
943  });
944  }
945  else
946  {
947  UT_ASSERT(transform4_attrib.isValid());
948  extractAttribFromTransform(output_geo, curve_group, transform_attrib, transform4_attrib,
950  return UT_Matrix4T<T>(transform);
951  });
952  }
953 
954  if (output_owner == GA_ATTRIB_VERTEX)
955  return;
956 
957  UT_ASSERT(output_owner == GA_ATTRIB_POINT);
958 
959  // Convert from vertex to point by averaging "directions" of the quaternions,
960  // averaging the scales, averaging the shears, and averaging the translates.
961  // Averaging quaternions linearly isn't as perfect as slerping, but if we
962  // normalize, it's usually pretty close in practice. If it's only 2 of them
963  // and they're averaged uniformly, it's exact.
964 
965  GA_RWHandleT<UT_Matrix3T<T>> output3_handle(include_translates ? nullptr : output_attrib);
966  GA_RWHandleT<UT_Matrix4T<T>> output4_handle(include_translates ? output_attrib : nullptr);
967  UT_ASSERT(output3_handle.isValid() || output4_handle.isValid());
968 
969  auto &&functor = [output_geo,curve_group,&output3_handle,&output4_handle,&transform3_attrib,&transform4_attrib](const GA_SplittableRange &r)
970  {
971  GA_Offset start;
972  GA_Offset end;
973  for (GA_Iterator it(r); it.blockAdvance(start, end); )
974  {
975  for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
976  {
977  UT_Matrix3T<T> first_matrix3;
978  UT_Matrix4T<T> first_matrix4;
979 
980  // Sum up all of the values from vertices
981  UT_QuaternionT<T> direction_sum;
982  UT_Matrix3T<T> stretch_matrix_sum;
983  UT_Vector3T<T> translate_sum;
984 
985  exint num_vertices = 0;
986 
987  for (GA_Offset vtxoff = output_geo->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = output_geo->vertexToNextVertex(vtxoff))
988  {
989  if (curve_group)
990  {
991  // Exclude vertices outside curve_group
992  GA_Offset primoff = output_geo->vertexPrimitive(vtxoff);
993  if (!curve_group->contains(primoff))
994  continue;
995  }
996 
997  if (!include_translates)
998  {
999  UT_Matrix3T<T> matrix3 = transform3_attrib.get(vtxoff);
1000  if (num_vertices == 0)
1001  {
1002  // Just copy first, since often only one
1003  first_matrix3 = matrix3;
1004  }
1005  else
1006  {
1007  if (num_vertices == 1)
1008  {
1009  first_matrix3.makeRotationMatrix(&stretch_matrix_sum);
1010  direction_sum.updateFromRotationMatrix(first_matrix3);
1011  }
1012  UT_Matrix3T<T> stretch_matrix;
1013  matrix3.makeRotationMatrix(&stretch_matrix);
1014  stretch_matrix_sum += stretch_matrix;
1016  direction.updateFromRotationMatrix(matrix3);
1017  // Avoid quaternions that represent rotations more than 180 degrees apart
1018  if (dot(direction_sum, direction) < 0)
1019  direction.negate();
1020  direction_sum += direction;
1021  }
1022  }
1023  else
1024  {
1025  UT_Matrix4T<T> matrix4 = transform4_attrib.get(vtxoff);
1026  if (num_vertices == 0)
1027  {
1028  // Just copy first, since often only one
1029  first_matrix4 = matrix4;
1030  }
1031  else
1032  {
1033  if (num_vertices == 1)
1034  {
1035  first_matrix3 = UT_Matrix3T<T>(first_matrix4);
1036  first_matrix3.makeRotationMatrix(&stretch_matrix_sum);
1037  direction_sum.updateFromRotationMatrix(first_matrix3);
1038  first_matrix4.getTranslates(translate_sum);
1039  }
1040  UT_Matrix3T<T> matrix3(matrix4);
1041  UT_Matrix3T<T> stretch_matrix;
1042  matrix3.makeRotationMatrix(&stretch_matrix);
1043  stretch_matrix_sum += stretch_matrix;
1045  direction.updateFromRotationMatrix(matrix3);
1046  // Avoid quaternions that represent rotations more than 180 degrees apart
1047  if (dot(direction_sum, direction) < 0)
1048  direction.negate();
1049  direction_sum += direction;
1051  first_matrix4.getTranslates(translate);
1052  translate_sum += translate;
1053  }
1054  }
1055  ++num_vertices;
1056  }
1057 
1058  if (num_vertices == 1)
1059  {
1060  if (!include_translates)
1061  output3_handle.set(ptoff, first_matrix3);
1062  else
1063  output4_handle.set(ptoff, first_matrix4);
1064  }
1065  else if (num_vertices > 1)
1066  {
1067  // Normalize the direction sum.
1068  direction_sum.normalize();
1069  stretch_matrix_sum /= num_vertices;
1070 
1071  UT_Matrix3T<T> rotation_matrix;
1072  direction_sum.getRotationMatrix(rotation_matrix);
1073  // NOTE: The order matters: check the comment on UT_Matrix3T::polarDecompose()
1074  UT_Matrix3T<T> matrix3 = stretch_matrix_sum * rotation_matrix;
1075  if (!include_translates)
1076  {
1077  output3_handle.set(ptoff, matrix3);
1078  }
1079  else
1080  {
1081  translate_sum /= num_vertices;
1082  UT_Matrix4T<T> matrix4(matrix3);
1083  matrix4.setTranslates(translate_sum);
1084  output4_handle.set(ptoff, matrix4);
1085  }
1086  }
1087  }
1088  }
1089  };
1090  if (curve_group)
1091  {
1092  GA_PointGroup point_group(*output_geo);
1093  point_group.combine(curve_group);
1094 
1095  UTparallelFor(GA_SplittableRange(output_geo->getPointRange(&point_group)), functor);
1096  }
1097  else
1098  {
1099  UTparallelFor(GA_SplittableRange(output_geo->getPointRange()), functor);
1100  }
1101 }
1102 
1103 template<int AXIS,typename T>
1104 static void
1105 sopCreateAxisAttrib(
1106  GEO_Detail *output_geo,
1107  const GA_PrimitiveGroup *curve_group,
1108  const SOP_NodeVerb::CookParms &cookparms,
1109  const GA_ROHandleM4D &transform_attrib,
1110  const UT_StringHolder &axis_attrib_name,
1111  const GA_AttributeOwner output_owner,
1112  const GA_Storage output_storage)
1113 {
1114  GA_ATINumeric *output_attrib;
1115  GA_ATINumeric *intermediate_attrib;
1116  GA_ATINumericUPtr intermediate_attrib_deleter;
1117  sopSetupTransformAttribs<T>(
1118  output_geo, cookparms, axis_attrib_name,
1119  output_owner, output_storage, 3, output_attrib,
1120  intermediate_attrib, intermediate_attrib_deleter,
1121  (AXIS == 3) ? GA_TYPE_POINT : GA_TYPE_VECTOR);
1122 
1123  if (!output_attrib)
1124  return;
1125 
1126  GA_RWHandleT<UT_Vector3T<T>> axis_attrib(intermediate_attrib);
1127  UT_ASSERT(axis_attrib.isValid());
1128  extractAxisAttrib<AXIS>(output_geo, curve_group, transform_attrib, axis_attrib);
1129 
1130  if (output_owner == GA_ATTRIB_VERTEX)
1131  return;
1132 
1133  UT_ASSERT(output_owner == GA_ATTRIB_POINT);
1134 
1135  // Convert from vertex to point by averaging directions and lengths.
1136 
1137  GA_RWHandleT<UT_Vector3T<T>> output_handle(output_attrib);
1138  UT_ASSERT(output_handle.isValid());
1139 
1140  auto &&functor = [output_geo,curve_group,&output_handle,&axis_attrib](const GA_SplittableRange &r)
1141  {
1142  GA_Offset start;
1143  GA_Offset end;
1144  for (GA_Iterator it(r); it.blockAdvance(start, end); )
1145  {
1146  for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
1147  {
1148  // Sum up all of the values from vertices
1149  UT_Vector3T<T> direction_sum;
1150  T length_sum;
1151  exint num_vertices = 0;
1152 
1153  for (GA_Offset vtxoff = output_geo->pointVertex(ptoff); GAisValid(vtxoff); vtxoff = output_geo->vertexToNextVertex(vtxoff))
1154  {
1155  if (curve_group)
1156  {
1157  // Exclude vertices outside curve_group
1158  GA_Offset primoff = output_geo->vertexPrimitive(vtxoff);
1159  if (!curve_group->contains(primoff))
1160  continue;
1161  }
1162 
1163  UT_Vector3T<T> direction = axis_attrib.get(vtxoff);
1164  if (num_vertices == 0)
1165  {
1166  // Just copy first, since often only one
1167  direction_sum = direction;
1168  }
1169  else
1170  {
1171  if (num_vertices == 1)
1172  length_sum = direction_sum.normalize();
1173 
1174  length_sum += direction.normalize();
1175  // Avoid quaternions that represent rotations more than 180 degrees apart
1176  if (dot(direction_sum, direction) < 0)
1177  direction.negate();
1178  direction_sum += direction;
1179  }
1180  ++num_vertices;
1181  }
1182 
1183  if (num_vertices == 1)
1184  output_handle.set(ptoff, direction_sum);
1185  else if (num_vertices > 1)
1186  {
1187  // Normalize the direction sum and use the average length.
1188  direction_sum.normalize();
1189  output_handle.set(ptoff, direction_sum*(length_sum/num_vertices));
1190  }
1191  }
1192  }
1193  };
1194  if (curve_group)
1195  {
1196  GA_PointGroup point_group(*output_geo);
1197  point_group.combine(curve_group);
1198 
1199  UTparallelFor(GA_SplittableRange(output_geo->getPointRange(&point_group)), functor);
1200  }
1201  else
1202  {
1203  UTparallelFor(GA_SplittableRange(output_geo->getPointRange()), functor);
1204  }
1205 }
1206 
1207 /// This is the function that does the actual work.
1208 void SOP_OrientAlongCurveVerb::cook(const CookParms &cookparms) const
1209 {
1210  ///////////////////////////////////////////////////////////////////
1211  // //
1212  // 0. PREP //
1213  // //
1214  ///////////////////////////////////////////////////////////////////
1215 
1216  // This gives easy access to all of the current parameter values
1217  auto &&sopparms = cookparms.parms<SOP_OrientAlongCurveParms>();
1218  //auto sopcache = (SOP_OrientAlongCurveCache *)cookparms.cache();
1219 
1220  // The output detail
1221  GEO_Detail *output_geo = cookparms.gdh().gdpNC();
1222 
1223  const GEO_Detail *const curve_input = cookparms.inputGeo(0);
1224  UT_ASSERT(curve_input);
1225 
1226  // Copy the input geometry if we're not surfacing the curves.
1227  // Most data will be referenced instead of deep-copied.
1228  output_geo->replaceWith(*curve_input);
1229 
1230  // GOP_Manager will own any temporary groups it creates
1231  // and automatically destroy them when it goes out of scope.
1232  GOP_Manager group_manager;
1233  const GA_PrimitiveGroup *curve_group = nullptr;
1234  if (sopparms.getCurveGroup().isstring()) {
1235  // Parse curve group on input detail if surfacing.
1236  // Parse curve group on output detail if keeping curves.
1237  curve_group = group_manager.parsePrimitiveGroups(sopparms.getCurveGroup(),
1238  GOP_Manager::GroupCreator(output_geo));
1239  }
1240 
1241  // Warn if we know there are going to be non-polygon primitives, since
1242  // the results may be a little to very unusual for non-polygon primitives.
1243  if (!curve_group && curve_input->getNumPrimitives() != curve_input->countPrimitiveType(GA_PRIMPOLY)) {
1244  cookparms.sopAddWarning(SOP_MESSAGE, "Input geometry contains non-polygon primitives, so results may not be as expected");
1245  }
1246 
1247  ///////////////////////////////////////////////////////////////////
1248  // //
1249  // 3. TRANSFORMS //
1250  // //
1251  ///////////////////////////////////////////////////////////////////
1252 
1253  // Compute transforms on the curves in a detached attribute,
1254  // since we won't be keeping it, and the index map won't be changing
1255  // while it's alive.
1256  GA_ATINumericUPtr detached_transform_attrib = output_geo->createDetachedTupleAttribute(GA_ATTRIB_VERTEX, GA_STORE_REAL64, 16);
1257  GA_RWHandleM4D transform_attrib(detached_transform_attrib.get());
1258 
1260 
1261  parms.myTangentType = GU_CurveFrame::TangentType(int(sopparms.getTangentType()));
1262  parms.myExtrapolateEndTangents = sopparms.getExtrapolateEndTangents();
1263  parms.myTransformByInstanceAttribs = sopparms.getTransformByAttribs();
1264 
1265  parms.myRotationOrder = OP_Node::getRotOrder(int(sopparms.getROrd()));
1266 
1267  const bool applypitch = sopparms.getApplyPitch();
1268  const bool applyyaw = sopparms.getApplyYaw();
1269  const bool applyroll = sopparms.getApplyRoll();
1270  const UT_Vector3D angles(
1271  applypitch ? SYSdegToRad(sopparms.getPitch()) : 0.0,
1272  applyyaw ? SYSdegToRad(sopparms.getYaw()) : 0.0,
1273  applyroll ? SYSdegToRad(sopparms.getRoll()) : 0.0);
1274  parms.myAngles = angles;
1275  const UT_Vector3D incangles(
1276  applypitch ? SYSdegToRad(sopparms.getIncPitch()) : 0.0,
1277  applyyaw ? SYSdegToRad(sopparms.getIncYaw()) : 0.0,
1278  applyroll ? SYSdegToRad(sopparms.getIncRoll() + 360.0*sopparms.getFullTwists()) : 0.0);
1279  parms.myIncAngles = incangles;
1280  parms.myIncAnglePer[0] = GU_CurveFrame::RotationPer(int(sopparms.getPitchPer()));
1281  parms.myIncAnglePer[1] = GU_CurveFrame::RotationPer(int(sopparms.getYawPer()));
1282  parms.myIncAnglePer[2] = GU_CurveFrame::RotationPer(int(sopparms.getRollPer()));
1283 
1284  const GA_AttributeOwner search_order[4] = {
1289  };
1290  if (parms.myIncAngles[0] != 0.0 && parms.myIncAnglePer[0] == GU_CurveFrame::RotationPer::ATTRIB)
1291  parms.myRotAttribs[0].bind(output_geo->findAttribute(sopparms.getPitchAttrib(), search_order, 4));
1292  if (parms.myIncAngles[1] != 0.0 && parms.myIncAnglePer[1] == GU_CurveFrame::RotationPer::ATTRIB)
1293  parms.myRotAttribs[1].bind(output_geo->findAttribute(sopparms.getYawAttrib(), search_order, 4));
1294  if (parms.myIncAngles[2] != 0.0 && parms.myIncAnglePer[2] == GU_CurveFrame::RotationPer::ATTRIB)
1295  parms.myRotAttribs[2].bind(output_geo->findAttribute(sopparms.getRollAttrib(), search_order, 4));
1296 
1298 
1299  switch (sopparms.getUpVectorType())
1300  {
1301  case UpVectorType::X: parms.myTargetUpVector = UT_Vector3D(1,0,0); break;
1302  case UpVectorType::NORMAL: SYS_FALLTHROUGH; // Fallback up vector for curve normal case is Y Axis.
1303  case UpVectorType::Y: parms.myTargetUpVector = UT_Vector3D(0,1,0); break;
1304  case UpVectorType::Z: parms.myTargetUpVector = UT_Vector3D(0,0,1); break;
1305  case UpVectorType::ATTRIB:
1306  {
1307  parms.myTargetUpVectorAttrib.bind(output_geo, GA_ATTRIB_PRIMITIVE, sopparms.getUpVectorAttrib());
1308  if (!parms.myTargetUpVectorAttrib.isValid())
1309  {
1310  parms.myTargetUpVectorAttrib.bind(output_geo, GA_ATTRIB_DETAIL, sopparms.getUpVectorAttrib());
1311  }
1312  if (parms.myTargetUpVectorAttrib.isValid() && parms.myTargetUpVectorAttrib->getOwner() == GA_ATTRIB_DETAIL)
1313  {
1314  // If it's a detail attribute, just read the value here.
1315  parms.myTargetUpVector = parms.myTargetUpVectorAttrib.get(GA_DETAIL_OFFSET);
1316  parms.myTargetUpVectorAttrib.clear();
1317  }
1318  else
1319  {
1320  if (!parms.myTargetUpVectorAttrib.isValid())
1321  {
1322  cookparms.sopAddWarning(SOP_MESSAGE, "Target up vector attribute not found; defaulting to y axis.");
1323  }
1324  parms.myTargetUpVector = UT_Vector3D(0,1,0);
1325  }
1326  break;
1327  }
1328  case UpVectorType::CUSTOM: parms.myTargetUpVector = sopparms.getUpVector(); break;
1329  }
1330  parms.myUseCurveNormalAsTargetUp = (sopparms.getUpVectorType() == UpVectorType::NORMAL);
1331  parms.myTargetUpVectorAtStart = sopparms.getUpVectorAtStart();
1332  parms.myContinuousClosedCurves = sopparms.getContinuousClosed();
1333 
1334  if (parms.myTargetUpVectorAtStart && sopparms.getUseEndUpVector())
1335  {
1336  parms.myUseEndTargetUpVector = true;
1337  if (sopparms.getUpVectorType() == UpVectorType::ATTRIB)
1338  {
1339  parms.myEndTargetUpVectorAttrib.bind(output_geo, GA_ATTRIB_PRIMITIVE, sopparms.getEndUpVectorAttrib());
1340  if (!parms.myEndTargetUpVectorAttrib.isValid())
1341  {
1342  parms.myEndTargetUpVectorAttrib.bind(output_geo, GA_ATTRIB_DETAIL, sopparms.getEndUpVectorAttrib());
1343  }
1344  if (parms.myEndTargetUpVectorAttrib.isValid() && parms.myEndTargetUpVectorAttrib->getOwner() == GA_ATTRIB_DETAIL)
1345  {
1346  // If it's a detail attribute, just read the value here.
1347  parms.myEndTargetUpVector = parms.myEndTargetUpVectorAttrib.get(GA_DETAIL_OFFSET);
1348  parms.myEndTargetUpVectorAttrib.clear();
1349  }
1350  else
1351  {
1352  if (!parms.myEndTargetUpVectorAttrib.isValid())
1353  {
1354  cookparms.sopAddWarning(SOP_MESSAGE, "End target up vector attribute not found; defaulting to no end target up vector.");
1355  parms.myUseEndTargetUpVector = false;
1356  }
1357  parms.myEndTargetUpVector = UT_Vector3D(0,1,0);
1358  }
1359  }
1360  else if (sopparms.getUpVectorType() == UpVectorType::CUSTOM)
1361  {
1362  parms.myEndTargetUpVector = sopparms.getEndUpVector();
1363  }
1364  else
1365  {
1366  parms.myEndTargetUpVector = parms.myTargetUpVector;
1367  }
1368  }
1369 
1370  parms.myNormalizeScales = sopparms.getNormalize();
1371  //parms.myMakeOrthogonal = sopparms.getOrthogonal();
1372  parms.myUniformScale = sopparms.getScale();
1373  parms.myStretchAroundTurns = sopparms.getStretchAroundTurns();
1374  parms.myMaxStretchScale = sopparms.getMaxStretchAroundTurns();
1375 
1376  computeCurveTransforms(output_geo, curve_group, transform_attrib, parms);
1377 
1378  ///////////////////////////////////////////////////////////////////
1379  // //
1380  // 4. ATTRIBUTES //
1381  // //
1382  ///////////////////////////////////////////////////////////////////
1383 
1384  // Output any frame attributes
1385  GA_AttributeOwner output_owner = (sopparms.getClass() == Class::POINT) ? GA_ATTRIB_POINT : GA_ATTRIB_VERTEX;
1386  GA_Storage output_storage = (
1387 #if 1
1388  (UTverify_cast<const GA_ATINumeric*>(output_geo->getP())->getStorage() == GA_STORE_REAL64) ? GA_STORE_REAL64 : GA_STORE_REAL32);
1389 #else
1390  (sopparms.getPrecision() == Precision::_16)
1391  ? GA_STORE_REAL16
1392  : ((sopparms.getPrecision() == Precision::_32)
1393  ? GA_STORE_REAL32
1394  : GA_STORE_REAL64));
1395 #endif
1396  GA_Storage intermediate_storage = (output_storage == GA_STORE_REAL16) ? GA_STORE_REAL32 : output_storage;
1397 
1398  if (sopparms.getOutputXAxis() && sopparms.getXAxisName().isstring())
1399  {
1400 
1401  if (intermediate_storage == GA_STORE_REAL32)
1402  {
1403  sopCreateAxisAttrib<0,fpreal32>(
1404  output_geo, curve_group, cookparms, transform_attrib,
1405  sopparms.getXAxisName(), output_owner, output_storage);
1406  }
1407  else
1408  {
1409  sopCreateAxisAttrib<0,fpreal64>(
1410  output_geo, curve_group, cookparms, transform_attrib,
1411  sopparms.getXAxisName(), output_owner, output_storage);
1412  }
1413  }
1414  if (sopparms.getOutputYAxis() && sopparms.getYAxisName().isstring())
1415  {
1416  if (intermediate_storage == GA_STORE_REAL32)
1417  {
1418  sopCreateAxisAttrib<1,fpreal32>(
1419  output_geo, curve_group, cookparms, transform_attrib,
1420  sopparms.getYAxisName(), output_owner, output_storage);
1421  }
1422  else
1423  {
1424  sopCreateAxisAttrib<1,fpreal64>(
1425  output_geo, curve_group, cookparms, transform_attrib,
1426  sopparms.getYAxisName(), output_owner, output_storage);
1427  }
1428  }
1429  if (sopparms.getOutputZAxis() && sopparms.getZAxisName().isstring())
1430  {
1431  if (intermediate_storage == GA_STORE_REAL32)
1432  {
1433  sopCreateAxisAttrib<2,fpreal32>(
1434  output_geo, curve_group, cookparms, transform_attrib,
1435  sopparms.getZAxisName(), output_owner, output_storage);
1436  }
1437  else
1438  {
1439  sopCreateAxisAttrib<2,fpreal64>(
1440  output_geo, curve_group, cookparms, transform_attrib,
1441  sopparms.getZAxisName(), output_owner, output_storage);
1442  }
1443  }
1444  if (sopparms.getOutputTranslation() && sopparms.getTranslationName().isstring())
1445  {
1446  if (intermediate_storage == GA_STORE_REAL32)
1447  {
1448  sopCreateAxisAttrib<3,fpreal32>(
1449  output_geo, curve_group, cookparms, transform_attrib,
1450  sopparms.getTranslationName(), output_owner, output_storage);
1451  }
1452  else
1453  {
1454  sopCreateAxisAttrib<3,fpreal64>(
1455  output_geo, curve_group, cookparms, transform_attrib,
1456  sopparms.getTranslationName(), output_owner, output_storage);
1457  }
1458  }
1459  if (sopparms.getOutputQuaternion() && sopparms.getQuaternionName().isstring())
1460  {
1461  if (intermediate_storage == GA_STORE_REAL32)
1462  {
1463  sopCreateQuaternionAttrib<fpreal32>(
1464  output_geo, curve_group, cookparms, transform_attrib,
1465  sopparms.getQuaternionName(), output_owner, output_storage);
1466  }
1467  else
1468  {
1469  sopCreateQuaternionAttrib<fpreal64>(
1470  output_geo, curve_group, cookparms, transform_attrib,
1471  sopparms.getQuaternionName(), output_owner, output_storage);
1472  }
1473  }
1474  if (sopparms.getOutputTransform3() && sopparms.getTransform3Name().isstring())
1475  {
1476  if (intermediate_storage == GA_STORE_REAL32)
1477  {
1478  sopCreateTransformAttrib<fpreal32,false>(
1479  output_geo, curve_group, cookparms, transform_attrib,
1480  sopparms.getTransform3Name(), output_owner, output_storage);
1481  }
1482  else
1483  {
1484  sopCreateTransformAttrib<fpreal64,false>(
1485  output_geo, curve_group, cookparms, transform_attrib,
1486  sopparms.getTransform3Name(), output_owner, output_storage);
1487  }
1488  }
1489  if (sopparms.getOutputTransform4() && sopparms.getTransform4Name().isstring())
1490  {
1491  if (intermediate_storage == GA_STORE_REAL32)
1492  {
1493  sopCreateTransformAttrib<fpreal32,true>(
1494  output_geo, curve_group, cookparms, transform_attrib,
1495  sopparms.getTransform4Name(), output_owner, output_storage);
1496  }
1497  else
1498  {
1499  sopCreateTransformAttrib<fpreal64,true>(
1500  output_geo, curve_group, cookparms, transform_attrib,
1501  sopparms.getTransform4Name(), output_owner, output_storage);
1502  }
1503  }
1504 }
1505 
1506 } // End of HDK_Sample namespace
static PRM_ChoiceList primGroupMenu
Definition: SOP_Node.h:1193
SYS_FORCE_INLINE void bumpDataId()
Definition: GA_Attribute.h:306
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)
int isRefInput(OP_InputIdx i) const override
Iteration over a range of elements.
Definition: GA_Iterator.h:29
Y
Definition: ImathEuler.h:184
void setChoiceListPtr(const UT_StringRef &name, PRM_ChoiceList *list)
UT_ErrorSeverity sopAddWarning(int code, const char *msg=0, const UT_SourceLocation *loc=0) const
Definition: SOP_NodeVerb.h:509
GOP_GroupParse::GroupCreator GroupCreator
Definition: GOP_Manager.h:35
static const UT_StringHolder theSOPTypeName
IMF_EXPORT IMATH_NAMESPACE::V3f direction(const IMATH_NAMESPACE::Box2i &dataWindow, const IMATH_NAMESPACE::V2f &pixelPosition)
bool blockAdvance(GA_Offset &start, GA_Offset &end)
GLuint start
Definition: glcorearb.h:475
bool setTupleSize(int size)
int getTupleSize() const
CookMode cookMode(const SOP_NodeParms *parms) const override
int OP_InputIdx
Definition: OP_DataTypes.h:184
static OP_Node * myConstructor(OP_Network *net, const char *name, OP_Operator *op)
static const SOP_NodeVerb::Register< SOP_OrientAlongCurveVerb > theVerb
int64 exint
Definition: SYS_Types.h:125
UT_ErrorSeverity
Definition: UT_Error.h:25
X
Definition: ImathEuler.h:183
static const char *const theDsFile
This is the parameter interface string, below.
SYS_FORCE_INLINE TO_T UTverify_cast(FROM_T from)
Definition: UT_Assert.h:229
GA_API const UT_StringHolder P
bool addOperator(OP_Operator *op, std::ostream *err=nullptr)
void getRotationMatrix(UT_Matrix3 &mat) const
This is the SOP class definition.
3D Vector class.
SYS_FORCE_INLINE bool GAisValid(GA_Size v)
Definition: GA_Types.h:655
SOP_NodeParms * allocParms() const override
GA_Size countPrimitiveType(const GA_PrimitiveTypeId &type) const
Definition: GA_Detail.h:2311
SOP_OrientAlongCurve(OP_Network *net, const char *name, OP_Operator *op)
GA_Size GA_Offset
Definition: GA_Types.h:646
void updateFromArbitraryMatrix(const UT_Matrix3 &)
const T & parms() const
Definition: SOP_NodeVerb.h:423
Constructs a PRM_Template list from an embedded .ds file or an istream.
const char * inputLabel(OP_InputIdx idx) const override
These are the labels that appear when hovering over the inputs.
void extractAttribFromTransform(GEO_Detail *geo, const GA_PrimitiveGroup *curve_group, const GA_ROHandleT< UT_Matrix4T< T >> &transform_attrib, const GA_RWHandleT< OUTPUT_T > &output_attrib, FUNCTOR &&extract_functor)
constexpr SYS_FORCE_INLINE void negate() noexcept
Definition: UT_Vector3.h:351
PRM_Template * templates() const
const GA_PrimitiveGroup * parsePrimitiveGroups(const char *pat, const GroupCreator &creator, bool numok=true, bool ordered=false, bool strict=false, GA_Index prim_offset=GA_Index(0), ParseInfo *info=0)
static SYS_FORCE_INLINE GA_ATINumeric * cast(GA_Attribute *attrib)
Definition: GA_ATINumeric.h:65
fpreal64 dot(const CE_VectorT< T > &a, const CE_VectorT< T > &b)
Definition: CE_Vector.h:137
#define SYS_FALLTHROUGH
Definition: SYS_Compiler.h:68
GLuint GLuint end
Definition: glcorearb.h:475
GU_API void computeCurveTransforms(const GEO_Detail *const geo, const GA_PrimitiveGroup *curve_group, const GA_RWHandleT< UT_Matrix4T< T >> &transform_attrib, const CurveFrameParms< T > &parms)
SYS_FORCE_INLINE GA_ATINumericUPtr createDetachedTupleAttribute(GA_AttributeOwner owner, GA_Storage storage, int tuple_size, const GA_Defaults &defaults=GA_Defaults(0.0f), const GA_AttributeOptions *attribute_options=nullptr) const
Definition: GA_Detail.h:906
void newSopOperator(OP_OperatorTable *table)
SYS_FORCE_INLINE const char * c_str() const
UT_Vector3T< fpreal64 > UT_Vector3D
GLuint const GLchar * name
Definition: glcorearb.h:786
bool setStorage(GA_Storage storage)
SYS_FORCE_INLINE const GA_Attribute * findAttribute(GA_AttributeScope scope, const UT_StringRef &name, const GA_AttributeOwner search_order[], int search_size) const
Definition: GA_Detail.h:1021
GA_API const UT_StringHolder transform
GU_Detail * gdpNC()
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.
GA_TypeInfo
Definition: GA_Types.h:101
void setTranslates(const UT_Vector3T< S > &translates)
Definition: UT_Matrix4.h:1438
Data represents a quaternion. Token "quaternion".
Definition: GA_Types.h:120
GA_AttributeOwner
Definition: GA_Types.h:35
Quaternion class.
Definition: GEO_Detail.h:49
GA_API const UT_StringHolder parms
UT_StringHolder name() const override
static PRM_Template * buildTemplates()
Data represents a normal vector. Token "normal".
Definition: GA_Types.h:114
SYS_FORCE_INLINE GA_Size getNumPrimitives() const
Return the number of primitives.
Definition: GA_Detail.h:408
Data represents a direction vector. Token "vector".
Definition: GA_Types.h:112
GA_API const UT_StringHolder N
const GU_Detail * inputGeo(exint idx) const
Definition: SOP_NodeVerb.h:387
Data represents a position in space. Token "point".
Definition: GA_Types.h:106
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:156
SYS_FORCE_INLINE UT_StorageMathFloat_t< T > normalize() noexcept
Definition: UT_Vector3.h:376
GLboolean r
Definition: glcorearb.h:1222
OP_ERROR cookMySop(OP_Context &context) override
Since this SOP implements a verb, cookMySop just delegates to the verb.
#define GA_DETAIL_OFFSET
Definition: GA_Types.h:691
PUGI__FN char_t * translate(char_t *buffer, const char_t *from, const char_t *to, size_t to_length)
Definition: pugixml.cpp:8574
SYS_FORCE_INLINE void setTypeInfo(GA_TypeInfo type)
Definition: GA_Attribute.h:260
Data represents a transform matrix. Token "matrix".
Definition: GA_Types.h:118
GA_Storage
Definition: GA_Types.h:51
UT_UniquePtr< GA_ATINumeric > GA_ATINumericUPtr
GU_DetailHandle & gdh() const
The initial state of gdh depends on the cookMode()
Definition: SOP_NodeVerb.h:341
GA_Attribute * createTupleAttribute(GA_AttributeOwner owner, GA_AttributeScope scope, const UT_StringHolder &name, GA_Storage storage, int tuple_size, const GA_Defaults &defaults=GA_Defaults(0.0f), const UT_Options *create_args=nullptr, const GA_AttributeOptions *attribute_options=nullptr)
Definition: GA_Detail.h:968
void getTranslates(UT_Vector3T< S > &translates) const
Definition: UT_Matrix4.h:1428
bool makeRotationMatrix(UT_Matrix3T< T > *stretch=nullptr, bool reverse=true, const int max_iter=64, const T rel_tol=FLT_EPSILON)
void updateFromRotationMatrix(const UT_Matrix3 &)
UT_Matrix3T< fpreal64 > UT_Matrix3D
GA_Storage getStorage() const