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