HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
GU_CurveFrame.C
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2026
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  * Definitions of functions for computing reference frames
27  * for curve vertices.
28  */
29 
30 #include "GU_CurveFrame.h"
31 
32 #include <GEO/GEO_Curve.h>
34 #include <GA/GA_Iterator.h>
35 #include <GA/GA_OffsetList.h>
36 #include <GA/GA_ElementGroup.h>
37 #include <GA/GA_Range.h>
38 #include <GA/GA_SplittableRange.h>
39 #include <GA/GA_Types.h>
40 #include <UT/UT_Assert.h>
41 #include <UT/UT_Interrupt.h>
42 #include <UT/UT_Matrix3.h>
43 #include <UT/UT_Matrix4.h>
44 #include <UT/UT_ParallelUtil.h>
45 #include <UT/UT_Ramp.h>
46 #include <UT/UT_SmallArray.h>
47 #include <UT/UT_StringHolder.h>
48 #include <UT/UT_Vector3.h>
49 #include <SYS/SYS_Math.h>
50 #include <SYS/SYS_Types.h>
51 
52 using namespace UT::Literal;
53 
54 namespace HDK_Sample {
55 
56 namespace GU_CurveFrame {
57 
58 static constexpr float theExtremelySmallLength2 = 1e-37f;
59 static constexpr float theQuiteSmallRelativeLength = 1e-5f;
60 static constexpr float theQuiteSmallRelativeLength2 = 1e-10f;
61 
62 template<typename T>
63 static void
64 interpolateTangent(
65  const TangentType tangent_type,
66  const bool stretch_using_backbone,
67  const T max_stretch_scale,
68  const T max_stretch_length_threshold, // 2/max_stretch_length
69  const UT_Vector3T<T> &prev_edge,
70  const UT_Vector3T<T> &next_edge,
71  const T prev_length2,
72  const T next_length2,
73  const bool is_last, // false for first, true for last
74  UT_Vector3T<T> *tangent,
75  UT_Vector3T<T> *pend_stretch_dir,
76  T *pend_stretch_scale,
77  bool normalize)
78 {
79  UT_ASSERT_MSG_P(prev_length2 > 0 && next_length2 > 0, "This function doesn't handle zero-length edges. The caller should.");
80  UT_Vector3T<T> next_dir = next_edge;
81  if (next_length2 != 1) {
82  next_dir /= SYSsqrt(next_length2);
83  }
84  // first_dir and next_dir are unit vectors.
85  // We want to project back what prev_dir would be
86  // and compute the tangent from first_dir
87  // and prev_dir.
88  // Projection back the same angle as
89  // the angle between first_dir and next_dir.
90  UT_Vector3T<T> prev_dir = prev_edge;
91  if (prev_length2 != 1) {
92  prev_dir /= SYSsqrt(prev_length2);
93  }
94  UT_Vector3T<T> mid_dir;
95  T mid_length;
96  if (tangent_type == TangentType::CIRCULAR || stretch_using_backbone)
97  {
98  mid_dir = prev_dir + next_dir;
99  T mid_length2 = mid_dir.length2();
100  mid_length = SYSsqrt(mid_length2);
101  if (mid_length < theQuiteSmallRelativeLength) {
102  // Almost perfectly backtracking, so just pick the inner one.
103  mid_dir = is_last ? prev_dir : next_dir;
104  }
105  else {
106  mid_dir /= mid_length;
107  }
108  }
109  if (stretch_using_backbone) {
110  T stretch_scale;
111  UT_Vector3T<T> stretch_dir;
112  if (mid_length < max_stretch_length_threshold) {
113  stretch_scale = max_stretch_scale;
114 
115  if (mid_length < theQuiteSmallRelativeLength) {
116  // Almost perfectly backtracking, so just pick the inner one.
117  stretch_dir = is_last ? prev_dir : next_dir;
118  }
119  else {
120  stretch_dir = next_dir - prev_dir;
121  stretch_dir.normalize();
122  }
123  }
124  else {
125  // It takes a bit of trig to confirm, but the
126  // stretch scale is 1/cos(angle/2), which is 2/L_mid.
127  stretch_scale = SYSmin(2.0f / mid_length, max_stretch_scale);
128  stretch_dir = next_dir - prev_dir;
129  stretch_dir.normalize();
130  }
131  *pend_stretch_dir = stretch_dir;
132  *pend_stretch_scale = stretch_scale;
133  }
134  if (!tangent)
135  return;
136 
137  switch (tangent_type) {
139  *tangent = mid_dir;
140  break;
141  case TangentType::PREV:
142  *tangent = normalize ? prev_dir : prev_edge;
143  break;
144  case TangentType::NEXT:
145  *tangent = normalize ? next_dir : next_edge;
146  break;
147  case TangentType::SUBD:
148  // We need to use the unnormalized directions to interpolate in the subd case,
149  // even if we're normalizing after.
150  mid_dir = prev_edge + next_edge;
151  if (normalize)
152  {
153  T mid_length2 = mid_dir.length2();
154  if (mid_length2 < theQuiteSmallRelativeLength2*next_length2) {
155  // Almost perfectly stopping, so just pick the inner one (normalized).
156  mid_dir = is_last ? prev_dir : next_dir;
157  }
158  else {
159  mid_dir /= SYSsqrt(mid_length2);
160  }
161  }
162  else
163  mid_dir *= T(0.5);
164  *tangent = mid_dir;
165  break;
166  case TangentType::NONE:
167  UT_ASSERT_MSG(0, "This case should have been excluded earlier.");
168  break;
169  }
170 }
171 
172 template<typename T>
174 extrapolateVectorCircular(const UT_Vector3T<T> &a, const UT_Vector3T<T> &b) {
175  // If 'b' is a unit vector, this is a reflection of a through the line that goes through 'b' and the origin,
176  // resulting in a vector that's 'a' rotated by twice the rotation that takes 'a' to 'b'.
177  return (2*dot(a,b))*b - a;
178 }
179 
180 template<typename T>
182 extrapolateVectorLinear(const UT_Vector3T<T> &a, const UT_Vector3T<T> &b) {
183  // This is a linear extrapolation as far from 'b' as 'b' is from 'a'.
184  return b + (b-a);
185 }
186 
187 template<typename T>
188 static void
189 extrapolateEndTangent(
190  const TangentType tangent_type,
191  const bool stretch_using_backbone,
192  const T max_stretch_scale,
193  const T max_stretch_length_threshold, // 2/max_stretch_length
194  const UT_Vector3T<T> &outer_edge,
195  const UT_Vector3T<T> &inner_edge,
196  const UT_Vector3T<T> &outer_dir, // outer_edge normalized
197  const bool is_last, // false for first, true for last
198  UT_Vector3T<T> *end_tangent,
199  UT_Vector3T<T> *pend_stretch_dir,
200  T *pend_stretch_scale,
201  const bool outer_edge_is_tangent)
202 {
203  UT_Vector3T<T> inner_dir = inner_edge;
204  T inner_length2 = inner_dir.length2();
205  if (inner_length2 == 0) {
206  if (end_tangent)
207  *end_tangent = outer_dir;
208  return;
209  }
210 
211  inner_dir /= SYSsqrt(inner_length2);
212 
213  UT_Vector3T<T> extrapolated_edge;
214  T extrapolated_length2;
215  UT_Vector3T<T> end_edge_or_dir;
216  T end_length2;
217  if (tangent_type != TangentType::SUBD) {
218  extrapolated_edge = extrapolateVectorCircular(inner_dir, outer_dir);
219  extrapolated_length2 = 1;
220  end_edge_or_dir = outer_edge_is_tangent ? inner_dir : outer_dir;
221  end_length2 = 1;
222  }
223  else {
224  // We need to use the unnormalized directions to extrapolate in the subd case.
225  extrapolated_edge = extrapolateVectorLinear(inner_edge, outer_edge);
226  extrapolated_length2 = extrapolated_edge.length2();
227  end_edge_or_dir = outer_edge_is_tangent ? inner_edge : outer_edge;
228  end_length2 = outer_edge_is_tangent ? inner_length2 : outer_edge.length2();
229  }
230 
231  interpolateTangent(tangent_type,
232  stretch_using_backbone,
233  max_stretch_scale,
234  max_stretch_length_threshold,
235  is_last ? end_edge_or_dir : extrapolated_edge,
236  is_last ? extrapolated_edge : end_edge_or_dir,
237  is_last ? end_length2 : extrapolated_length2,
238  is_last ? extrapolated_length2 : end_length2,
239  is_last,
240  end_tangent, pend_stretch_dir, pend_stretch_scale,
241  true); // FIXME: Support non-normalized scales!!!
242 }
243 
244 template<typename T,typename T2>
245 static void
246 computeSingleBackboneFrames(
247  bool &rotate_using_backbone,
248  bool &stretch_using_backbone,
249  bool need_directions,
250  UT_Array<UT_Vector3T<T>> &directions,
251  UT_Array<UT_Vector3T<T>> &tangents,
252  UT_Array<UT_Vector3T<T>> &up_vectors,
253  UT_Array<UT_Vector3T<T>> &stretch_dirs,
254  UT_Array<T> &stretch_scales,
255  T &total_twist_around_loop,
256  const GEO_Detail *geo,
257  GA_Size nedges,
258  GA_Size npoints,
259  GA_Size nverts,
260  bool closed,
261  TangentType tangent_type,
262  bool extrapolate_end_tangents,
263  const GA_ROHandleV3D &instance_N,
264  const GA_Offset primoff,
265  const GA_OffsetListRef &vertices,
266  const bool use_nurbs_tangents,
267  T max_stretch_scale,
268  T max_stretch_length_threshold,
269  const GA_ROHandleV3D &instance_up,
270  UT_Vector3T<T> target_up_vector,
271  const bool use_normal_vector_up,
272  const bool target_up_vector_at_start,
273  const bool continuous_closed_curves,
274  const bool use_end_target_up_vector,
275  UT_Vector3T<T> end_target_up_vector,
276  RotationPer twist_per,
277  const GA_ROHandleT<T2> &roll_attrib,
278  const int ucomponent)
279 {
280  rotate_using_backbone = (rotate_using_backbone && nedges >= 1);
281  stretch_using_backbone = (stretch_using_backbone && npoints >= 3);
282 
283  if (!rotate_using_backbone && !stretch_using_backbone && !need_directions)
284  return;
285 
286  // Compute the backbone frames for all vertices of the curve,
287  // as well as the stretch vectors, if applicable.
288 
289  directions.setSize(nedges);
290 
291  const UT_Vector3T<T> pos0 = geo->getPos3(geo->vertexPoint(vertices(0)));
292  UT_Vector3T<T> prev_pos = pos0;
293  T max_length_squared = 0;
294  for (GA_Size i = 1; i < nverts; ++i)
295  {
296  const UT_Vector3T<T> cur_pos = geo->getPos3(geo->vertexPoint(vertices(i)));
297  UT_Vector3T<T> dir = cur_pos - prev_pos;
298  T length2 = dir.length2();
299  // Force very short edges to exactly zero, so that length2()
300  // will always produce exactly zero for them, even if the
301  // compiler pulls some inconsistent shenanigans.
302  if (length2 < theExtremelySmallLength2)
303  {
304  length2 = 0;
305  dir.assign(0,0,0);
306  }
307  directions(i-1) = dir;
308  max_length_squared = SYSmax(max_length_squared, length2);
309  prev_pos = cur_pos;
310  }
311  if (closed && nverts == nedges)
312  {
313  // One last edge if closed and not unrolled
314  UT_Vector3T<T> dir = pos0 - prev_pos;
315  T length2 = dir.length2();
316  // Force very short edges to exactly zero, as above.
317  if (length2 < theExtremelySmallLength2)
318  {
319  length2 = 0;
320  dir.assign(0,0,0);
321  }
322  directions(nverts-1) = dir;
323  max_length_squared = SYSmax(max_length_squared, length2);
324  }
325 
326  if (!rotate_using_backbone && !stretch_using_backbone)
327  return;
328 
329  if (max_length_squared == 0)
330  {
331  // If all edges are effectively zero-length, stick
332  // with no rotation or stretching.
333  rotate_using_backbone = false;
334  stretch_using_backbone = false;
335  return;
336  }
337 
338  // We need to specially handle any zero-length edges at the
339  // beginning or end of the curve.
340  // These loops rely on length2() being consistently
341  // zero or non-zero, because we know that there's
342  // at least one non-zero-length edge; we just don't
343  // want to incorrectly loop all the way past the end.
344  GA_Size first_nonzero = 0;
345  GA_Size last_nonzero = directions.size()-1;
346  while (first_nonzero < last_nonzero && directions(first_nonzero).length2() == 0)
347  {
348  ++first_nonzero;
349  }
350  while (first_nonzero < last_nonzero && directions(last_nonzero).length2() == 0)
351  {
352  --last_nonzero;
353  }
354 
355  tangents.setSize(npoints);
356  up_vectors.setSize(npoints);
357 
358  if (instance_N.isValid())
359  {
360  UT_ASSERT(!instance_up.isValid());
361  for (exint i = 0; i < npoints; ++i)
362  {
363  GA_Offset ptoff = geo->vertexPoint(vertices(i));
364  UT_Vector3D normal = instance_N.get(ptoff);
365  normal.normalize();
366  tangents[i] = normal;
367  }
368  }
369  if (instance_up.isValid())
370  {
371  UT_ASSERT(!instance_N.isValid());
372  for (exint i = 0; i < npoints; ++i)
373  {
374  GA_Offset ptoff = geo->vertexPoint(vertices(i));
375  UT_Vector3D up = instance_up.get(ptoff);
376  up.normalize();
377  up_vectors[i] = up;
378  }
379  }
380 
381  if (first_nonzero == last_nonzero)
382  {
383  // Exactly one non-zero-length edge, (usually just one total).
384  if (!instance_N.isValid())
385  {
386  // The single edge length must have length sqrt(max_edge_length_squared).
387  UT_Vector3T<T> dir = directions(first_nonzero) / SYSsqrt(max_length_squared);
388  tangents.constant(dir);
389 
390  if (!instance_up.isValid())
391  {
392  // No N or up, so same up for all points
393  UT_Vector3T<T> other = target_up_vector;
394  other -= dot(other,dir)*dir;
395  T length2 = other.length2();
396  if (length2 >= theQuiteSmallRelativeLength2)
397  other /= SYSsqrt(length2);
398  else
399  other.arbitraryPerp(dir);
400  up_vectors.constant(other);
401  }
402  else
403  {
404  // up attribute, so get closest to up perpendicular to dir
405  for (exint i = 0; i < npoints; ++i)
406  {
407  UT_Vector3T<T> other = up_vectors[i];
408  other -= dot(other,dir)*dir;
409  T length2 = other.length2();
410  if (length2 >= theQuiteSmallRelativeLength2)
411  other /= SYSsqrt(length2);
412  else
413  other.arbitraryPerp(dir);
414  up_vectors[i] = other;
415  }
416  }
417  }
418  else
419  {
420  // N attribute, but no up, so get closest to target up perpendicular to N
421  UT_ASSERT(!instance_up.isValid());
422 
423  for (exint i = 0; i < npoints; ++i)
424  {
425  UT_Vector3D dir = tangents[i];
426  UT_Vector3D other = target_up_vector;
427  other -= dot(other,dir)*dir;
428  T length2 = other.length2();
429  if (length2 >= theQuiteSmallRelativeLength2)
430  other /= SYSsqrt(length2);
431  else
432  other.arbitraryPerp(dir);
433  up_vectors[i] = other;
434  }
435  }
436 
437  // No stretching if only one non-zero-length edge.
438  stretch_using_backbone = false;
439 
440  // Nothing more to do in this case.
441  return;
442  }
443 
444  // More than one non-zero-length edge.
445 
446  if (stretch_using_backbone)
447  {
448  stretch_dirs.setSizeNoInit(npoints);
449  // setSize would initialize to zero, but let's make it explicitly clear
450  // that we want to initialize these to a default of zero.
451  // A stretch vector being zero will result in no stretch.
452  stretch_dirs.constant(UT_Vector3T<T>(0,0,0));
453  stretch_scales.setSizeNoInit(npoints);
454  stretch_scales.constant(T(1));
455  }
456 
457  if (instance_N.isValid() && stretch_using_backbone)
458  {
459  // If instance_N is valid, don't compute tangents, but do still
460  // compute stretch_dirs and stretch_scales if stretch_using_backbone,
461  // including extrapolating ends as needed.
462  if (!closed)
463  {
464  // Always act as if extrapolating, but based on tangent,
465  // since end tangents may not line up with end directions.
466  if (first_nonzero == 0)
467  {
468  extrapolateEndTangent(tangent_type,
469  stretch_using_backbone, max_stretch_scale,
470  max_stretch_length_threshold,
471  tangents[0],
472  directions[0],
473  tangents[0], // Already normalized
474  false, // Not last edge (since first edge)
475  (UT_Vector3T<T>*)nullptr,
476  &stretch_dirs(0),
477  &stretch_scales(0),
478  true);// outer_edge is a tangent
479  }
480  if (last_nonzero == directions.size()-1)
481  {
482  extrapolateEndTangent(tangent_type,
483  stretch_using_backbone, max_stretch_scale,
484  max_stretch_length_threshold,
485  tangents.last(),
486  directions.last(),
487  tangents.last(), // Already normalized
488  true, // Last edge
489  (UT_Vector3T<T>*)nullptr,
490  &stretch_dirs(npoints-1),
491  &stretch_scales(npoints-1),
492  true);// outer_edge is a tangent
493  }
494  }
495  else if (first_nonzero == 0 && last_nonzero == directions.size()-1)
496  {
497  // Closed backbone and no zero-length edges at beginning or end, so
498  // it's like a normal case, except with non-adjacent edge indices.
499 
500  interpolateTangent(
501  tangent_type,
502  stretch_using_backbone,
503  max_stretch_scale,
504  max_stretch_length_threshold,
505  directions.last(),
506  directions(0),
507  directions.last().length2(),
508  directions(0).length2(),
509  false, // Arbitrarily choose not last
510  (UT_Vector3T<T>*)nullptr,
511  &stretch_dirs(0),
512  &stretch_scales(0),
513  true); // FIXME: Support non-normalized scales!!!
514  }
515 
516  // Middle stretch cases
517  GA_Size prev_nonzero = first_nonzero;
518  T prev_length2 = directions[first_nonzero].length2();
519  for (GA_Size next_edgei = first_nonzero+1; next_edgei <= last_nonzero; ++next_edgei) {
520  T next_length2 = directions[next_edgei].length2();
521  if (next_length2 != 0)
522  {
523  // Common case: Two adjacent, non-zero-length edges.
524  interpolateTangent(
525  tangent_type,
526  stretch_using_backbone,
527  max_stretch_scale,
528  max_stretch_length_threshold,
529  directions[prev_nonzero],
530  directions[next_edgei],
531  prev_length2,
532  next_length2,
533  false, // Arbitrarily choose not last
534  (UT_Vector3T<T>*)nullptr,
535  &stretch_dirs[next_edgei],
536  &stretch_scales[next_edgei],
537  true); // FIXME: Support non-normalized scales!!!
538  }
539  else
540  {
541  // Find next non-zero-length edge.
542  do {
543  ++next_edgei;
544  next_length2 = directions(next_edgei).length2();
545  } while (next_edgei != last_nonzero && next_length2 == 0);
546  }
547  prev_nonzero = next_edgei;
548  prev_length2 = next_length2;
549  }
550  }
551 
552  if (!instance_N.isValid() && use_nurbs_tangents)
553  {
554  int primtype = geo->getPrimitiveTypeId(primoff);
555  if (primtype == GA_PRIMNURBCURVE || primtype == GA_PRIMBEZCURVE)
556  {
557  const GEO_Curve *curve = UTverify_cast<const GEO_Curve *>(geo->getPrimitive(primoff));
558  const bool curve_closed = curve->isClosed();
559  const GA_Basis *basis = curve->getBasis();
560 
561  UT_Array<float> greville_us;
562  greville_us.setSizeNoInit(nverts);
563  for (exint i = 0; i < nverts; ++i)
564  {
565  float u = basis->getGreville(i, true, curve_closed);
566  greville_us(i) = u;
567  }
568 
569  // Request tangents from curve.
570  for (exint i = 0; i < nverts; ++i)
571  {
572  UT_Vector4 cur_tangent;
573  float u = greville_us(i);
574  curve->evaluate(u, cur_tangent, /*du=*/1);
575 
576 
577 
578 
579  }
580  }
581  }
582 
583  // Handle the beginning and end of the backbone first,
584  // so that everything else is just a common middle case.
585  UT_Vector3T<T> first_dir = directions(first_nonzero);
586  UT_Vector3T<T> last_dir = directions(last_nonzero);
587  first_dir.normalize();
588  last_dir.normalize();
589  if (!instance_N.isValid())
590  {
591  if (!closed) {
592  if (first_nonzero > 0) {
593  // If there are zero-length edges at the beginning,
594  // set tangents to the first non-zero-length edge direction,
595  // up to and including the first point of that edge.
596  for (GA_Size i = 0; i <= first_nonzero; ++i) {
597  tangents(i) = first_dir;
598  }
599  }
600  else if (extrapolate_end_tangents) {
601  extrapolateEndTangent(tangent_type,
602  stretch_using_backbone, max_stretch_scale,
603  max_stretch_length_threshold,
604  directions(0),
605  directions(1), // We know there are at least two, so this is safe.
606  first_dir,
607  false, // Not last edge (since first edge)
608  &tangents(0),
609  stretch_using_backbone ? &stretch_dirs(0) : nullptr,
610  stretch_using_backbone ? &stretch_scales(0) : nullptr,
611  false);// outer_edge is not a tangent
612  }
613  else {
614  // Not extrapolating end tangents, so just copy.
615  tangents(0) = first_dir;
616  }
617 
618  if (last_nonzero < directions.size()-1) {
619  // Same as above for zero-length edges at the end.
620  for (GA_Size i = last_nonzero+1; i < npoints; ++i) {
621  tangents(i) = last_dir;
622  }
623  }
624  else if (extrapolate_end_tangents) {
625  extrapolateEndTangent(tangent_type,
626  stretch_using_backbone, max_stretch_scale,
627  max_stretch_length_threshold,
628  directions.last(),
629  directions(directions.size()-2), // We know there are at least two, so this is safe.
630  last_dir,
631  true, // Last edge
632  &tangents(npoints-1),
633  stretch_using_backbone ? &stretch_dirs(npoints-1) : nullptr,
634  stretch_using_backbone ? &stretch_scales(npoints-1) : nullptr,
635  false);// outer_edge is not a tangent
636  }
637  else {
638  // Not extrapolating end tangents, so just copy.
639  tangents(npoints-1) = last_dir;
640  }
641  }
642  else if (first_nonzero > 0 || last_nonzero < directions.size()-1) {
643  // Closed backbone and at least one zero-length edge at beginning or end.
644 
645  switch (tangent_type) {
647  {
648  if (first_nonzero == 1 && last_nonzero == directions.size()-1) {
649  tangents(0) = last_dir;
650  tangents(1) = first_dir;
651  }
652  else if (first_nonzero == 0 && last_nonzero == directions.size()-2) {
653  tangents.last() = last_dir;
654  tangents(0) = first_dir;
655  }
656  else {
657  // 3+ coincident, so circularly interpolate tangent
658  T cos_angle = dot(first_dir,last_dir);
659  UT_Vector3T<T> sin_axis = first_dir - cos_angle*last_dir;
660  sin_axis.normalize();
661  // TODO: atan2(sin_angle,cos_angle) is more stable for small angles or half turns.
662  T full_angle = SYSacos(cos_angle);
663  GA_Size nsteps = first_nonzero + (directions.size()-1 - last_nonzero);
664  GA_Size j = 0;
665  for (GA_Size i = last_nonzero+1; i < npoints; ++i, ++j) {
666  T part_angle = j*full_angle/nsteps;
667  T sin_part_angle, cos_part_angle;
668  SYSsincos(part_angle, &sin_part_angle, &cos_part_angle);
669  tangents(i) = cos_part_angle*last_dir + sin_part_angle*sin_axis;
670  }
671  for (GA_Size i = 0; i <= first_nonzero; ++i, ++j) {
672  T part_angle = j*full_angle/nsteps;
673  T sin_part_angle, cos_part_angle;
674  SYSsincos(part_angle, &sin_part_angle, &cos_part_angle);
675  tangents(i) = cos_part_angle*last_dir + sin_part_angle*sin_axis;
676  }
677  }
678  break;
679  }
680  case TangentType::SUBD:
681  {
682  if (first_nonzero == 1 && last_nonzero == directions.size()-1) {
683  tangents(0) = last_dir;
684  tangents(1) = first_dir;
685  }
686  else if (first_nonzero == 0 && last_nonzero == directions.size()-2) {
687  tangents.last() = last_dir;
688  tangents(0) = first_dir;
689  }
690  else {
691  // 3+ coincident, so linearly interpolate tangent
692  GA_Size nsteps = first_nonzero + (directions.size()-1 - last_nonzero);
693  GA_Size j = 0;
694  // NOTE: This value 0,0,1 should never end up being used. It should be overwritten first.
695  UT_Vector3T<T> prev_tangent(0,0,1);
696  for (GA_Size i = last_nonzero+1; i < npoints; ++i, ++j) {
697  UT_Vector3T<T> tangent = SYSlerp(directions(last_nonzero), directions(first_nonzero), T(j)/nsteps);
698  tangent.normalize();
699  if (tangent.length2() < 0.5f) {
700  // If didn't normalize, fall back to previous tangent,
701  // (shouldn't happen on first iteration, since tangent should be last_dir).
702  tangent = prev_tangent;
703  }
704  else {
705  prev_tangent = tangent;
706  }
707  tangents(i) = tangent;
708  }
709  for (GA_Size i = 0; i <= first_nonzero; ++i, ++j) {
710  UT_Vector3T<T> tangent = SYSlerp(directions(last_nonzero), directions(first_nonzero), T(j)/nsteps);
711  tangent.normalize();
712  if (tangent.length2() < 0.5f) {
713  // If didn't normalize, fall back to previous tangent,
714  // (shouldn't happen on first iteration, since tangent should be last_dir).
715  tangent = prev_tangent;
716  }
717  else {
718  prev_tangent = tangent;
719  }
720  tangents(i) = tangent;
721  }
722  }
723  break;
724  }
725  case TangentType::PREV:
726  case TangentType::NEXT:
727  {
728  UT_Vector3T<T> tangent = (tangent_type == TangentType::PREV) ? last_dir : first_dir;
729  for (GA_Size i = last_nonzero+1; i < npoints; ++i) {
730  tangents(i) = tangent;
731  }
732  for (GA_Size i = 0; i <= first_nonzero; ++i) {
733  tangents(i) = tangent;
734  }
735  break;
736  }
737  case TangentType::NONE:
738  UT_ASSERT_MSG(0, "This case should have been excluded earlier.");
739  break;
740  }
741  }
742  else {
743  // Closed backbone and no zero-length edges at beginning or end, so
744  // it's like a normal case, except with non-adjacent edge indices.
745 
746  interpolateTangent(
747  tangent_type,
748  stretch_using_backbone,
749  max_stretch_scale,
750  max_stretch_length_threshold,
751  directions.last(),
752  directions(0),
753  directions.last().length2(),
754  directions(0).length2(),
755  false, // Arbitrarily choose not last
756  &tangents(0),
757  stretch_using_backbone ? &stretch_dirs(0) : nullptr,
758  stretch_using_backbone ? &stretch_scales(0) : nullptr,
759  true); // FIXME: Support non-normalized scales!!!
760  }
761 
762  // The beginning and end have been handled, and we have two non-zero-length
763  // edges bounding the remaining edges.
764 
765 
766  GA_Size prev_nonzero = first_nonzero;
767  T prev_length2 = directions(first_nonzero).length2();
768  for (GA_Size next_edgei = first_nonzero+1; next_edgei <= last_nonzero; ++next_edgei) {
769  T next_length2 = directions(next_edgei).length2();
770  if (next_length2 != 0) {
771  // Common case: Two adjacent, non-zero-length edges.
772  interpolateTangent(
773  tangent_type,
774  stretch_using_backbone,
775  max_stretch_scale,
776  max_stretch_length_threshold,
777  directions(prev_nonzero),
778  directions(next_edgei),
779  prev_length2,
780  next_length2,
781  false, // Arbitrarily choose not last
782  &tangents(next_edgei),
783  stretch_using_backbone ? &stretch_dirs(next_edgei) : nullptr,
784  stretch_using_backbone ? &stretch_scales(next_edgei) : nullptr,
785  true); // FIXME: Support non-normalized scales!!!
786  prev_nonzero = next_edgei;
787  prev_length2 = next_length2;
788  continue;
789  }
790 
791  // Less-common case: zero-length edge(s)
792  // Find next non-zero-length edge.
793  do {
794  ++next_edgei;
795  next_length2 = directions(next_edgei).length2();
796  } while (next_edgei != last_nonzero && next_length2 == 0);
797 
798  GA_Size nsteps = next_edgei - prev_nonzero - 1;
799 
800  UT_Vector3T<T> prev_dir = directions(prev_nonzero) / SYSsqrt(prev_length2);
801  UT_Vector3T<T> next_dir = directions(next_edgei) / SYSsqrt(next_length2);
802  switch (tangent_type) {
804  {
805  if (nsteps == 1) {
806  tangents(next_edgei-1) = prev_dir;
807  tangents(next_edgei) = next_dir;
808  }
809  else {
810  // 3+ coincident, so circularly interpolate tangent
811  T cos_angle = dot(next_dir,prev_dir);
812  UT_Vector3T<T> sin_axis = next_dir - cos_angle*prev_dir;
813  sin_axis.normalize();
814  // TODO: atan2(sin_angle,cos_angle) is more stable for small angles or half turns.
815  T full_angle = SYSacos(cos_angle);
816  GA_Size j = 0;
817  for (GA_Size i = prev_nonzero+1; i <= next_edgei; ++i, ++j) {
818  T part_angle = j*full_angle/nsteps;
819  T sin_part_angle, cos_part_angle;
820  SYSsincos(part_angle, &sin_part_angle, &cos_part_angle);
821  tangents(i) = cos_part_angle*prev_dir + sin_part_angle*sin_axis;
822  }
823  }
824  break;
825  }
826  case TangentType::SUBD:
827  {
828  if (nsteps == 1) {
829  tangents(next_edgei-1) = prev_dir;
830  tangents(next_edgei) = next_dir;
831  }
832  else {
833  // 3+ coincident, so linearly interpolate tangent
834  GA_Size j = 0;
835  // NOTE: This value 0,0,1 should never end up being used. It should be overwritten first.
836  UT_Vector3T<T> prev_tangent(0,0,1);
837  for (GA_Size i = prev_nonzero+1; i <= next_edgei; ++i, ++j) {
838  UT_Vector3T<T> tangent = SYSlerp(directions(prev_nonzero), directions(next_edgei), T(j)/nsteps);
839  tangent.normalize();
840  if (tangent.length2() < 0.5f) {
841  // If didn't normalize, fall back to previous tangent,
842  // (shouldn't happen on first iteration, since tangent should be last_dir).
843  tangent = prev_tangent;
844  }
845  else {
846  prev_tangent = tangent;
847  }
848  tangents(i) = tangent;
849  }
850  }
851  break;
852  }
853  case TangentType::PREV:
854  case TangentType::NEXT:
855  {
856  UT_Vector3T<T> tangent = (tangent_type == TangentType::PREV) ? prev_dir : next_dir;
857  for (GA_Size i = prev_nonzero+1; i <= next_edgei; ++i) {
858  tangents(i) = tangent;
859  }
860  break;
861  }
862  case TangentType::NONE:
863  UT_ASSERT_MSG(0, "This case should have been excluded earlier.");
864  break;
865  }
866 
867  prev_nonzero = next_edgei;
868  prev_length2 = next_length2;
869  }
870  }
871 
872  // We now have all of the tangents.
873  // stretch_dirs and stretch_scales have also been initialized
874  // if stretch_using_backbone is true.
875 
876  if (instance_up.isValid())
877  {
878  // up attribute, but we still need to force up_vectors
879  // to be perpendicular to tangents.
880 
881  for (exint i = 0; i < npoints; ++i)
882  {
883  UT_Vector3D dir = tangents[i];
884  UT_Vector3D other = up_vectors[i];
885  other -= dot(other,dir)*dir;
886  T length2 = other.length2();
887  if (length2 >= theQuiteSmallRelativeLength2)
888  other /= SYSsqrt(length2);
889  else
890  other.arbitraryPerp(dir);
891  up_vectors[i] = other;
892  }
893 
894  return;
895  }
896 
897  if (use_normal_vector_up) {
898  // Compute a face normal for target_up_vector, if possible.
899  // This isn't the most efficient way to compute a normal, but we want
900  // to try to fall back on the largest normal along the way, so we
901  // go one triangle at a time. We know that we have at least two
902  // non-zero-length edges here, so there is at least one triangle,
903  // even though it might have zero area.
904 
905  // Double-precision for the sum to reduce the risk of catastrophic
906  // roundoff error, (e.g. if a curve had more than 16,777,216 vertices,
907  // everything after that would effectively be lost in single-precision.)
908  UT_Vector3D normal_sum(0,0,0);
909  UT_Vector3D max_normal;
910  double length2_max_normal = 0;
911  T length2_max_chord = 0;
912  for (GA_Size i = 1; i < nverts-1; ++i) {
913  UT_Vector3T<T> posi = geo->getPos3(geo->vertexPoint(vertices(i)));
914  UT_Vector3T<T> chord = posi-pos0;
915 
916  // The length of the cross product is 2x the area of the triangle
917  // from 0 to i to i+1, but with the direction being the triangle's
918  // normal. When summed, they give a vector whose length is the
919  // area of the polygon projected into a plane perpendicular to the
920  // direction of the vector.
921  normal_sum += cross(directions(i), chord);
922 
923  // Record the longest normal along the way, for a fallback.
924  double length2 = normal_sum.length2();
925  if (length2 > length2_max_normal) {
926  length2_max_normal = length2;
927  max_normal = normal_sum;
928  }
929  length2_max_chord = SYSmax(length2_max_chord, chord.length2());
930  }
931  UT_Vector3T<T> posi = geo->getPos3(geo->vertexPoint(vertices(nverts-1)));
932  length2_max_chord = SYSmax(length2_max_chord, (posi-pos0).length2());
933 
934  // Double-precision to reduce risk of underflow/overflow below
935  double small_rel_length2 = theQuiteSmallRelativeLength2*length2_max_chord;
936  double small_rel_length4 = small_rel_length2*small_rel_length2;
937 
938  // length2_max_normal is the *square* of 2x the largest *area* along the way,
939  // so compare against length-squared *squared*.
940  if (length2_max_normal < small_rel_length4) {
941  // Even the max length normal along the way was quite small,
942  // which happens when the points are all along a line,
943  // so stick with default target_up_vector.
944  }
945  else {
946  double length2_normal = normal_sum.length2();
947  if (length2_normal < small_rel_length4) {
948  // normal_sum ended up being small, but it was large enough
949  // partway through, so use that. This can happen if a
950  // curve backtracks along itself to the beginning.
951  target_up_vector = max_normal / SYSsqrt(length2_max_normal);
952  }
953  else {
954  // The normal's good, so use that.
955  target_up_vector = normal_sum / SYSsqrt(length2_normal);
956  }
957  }
958  }
959 
960  // We have all tangents, but we need to compute consistent up vectors.
961  // First, we pick an arbitrary starting bitangent; it must be
962  // perpendicular to tangents(0) and it might as well be as close to
963  // perpendicular to target_up_vector as it can be.
964  UT_Vector3T<T> tangent0 = tangents(0);
965  UT_Vector3T<T> up_vectors0 = target_up_vector;
966  up_vectors0 -= dot(up_vectors0,tangent0)*tangent0;
967  T length2 = up_vectors0.length2();
968  if (length2 >= theQuiteSmallRelativeLength2) {
969  up_vectors0 /= SYSsqrt(length2);
970  }
971  else {
972  up_vectors0.arbitraryPerp(tangent0);
973  }
974  up_vectors(0) = up_vectors0;
975 
976  for (GA_Size i = 1; i < npoints; ++i) {
977  // Compute the minimal rotation matrix that takes tangents(i-1) to tangents(i).
978  // Vectors in tangents are already normalized, so no need to normalize inside (false).
979  // If this is a bottleneck, it's possible to compute the resulting vector
980  // without constructing the matrix itself, via:
981  // <v|(I-2|a><a|)(I-2|c><c|), where c is (a+b) normalized, and a and b are the tangents.
982  // v -= (2*dot(v,a))*a;
983  // v -= (2*dot(v,c))*c;
985  bool failure = rotation.dihedral(tangents(i-1), tangents(i), false);
986  if (failure) {
987  // The tangent flipped a half turn, so flip the bitangent a half turn,
988  // to keep the up vector the same.
989  up_vectors(i) = -up_vectors(i-1);
990  }
991  else {
992  // Rotate bitangent by the rotation matrix.
993  up_vectors(i) = up_vectors(i-1) * rotation;
994  }
995 
996  // Just to prevent roundoff error from getting bad, force up_vectors(i) to be orthogonal to tangents(i).
997  up_vectors(i) -= dot(up_vectors(i),tangents(i))*tangents(i);
998  // It seems implausible that up_vectors(i) could possibly be problematically small
999  // after the subtraction, since it should already have been approximately
1000  // orthogonal to tangents(i) and unit length, so let's take the risk and just normalize.
1001  up_vectors(i).normalize();
1002  }
1003 
1004  // Closed backbone curves need an adjustment to make sure that beginning and end
1005  // up_vectors line up, without a sudden roll rotation. In other words,
1006  // smooth the excess roll out over the whole curve.
1007  // Open curves with an end target up vector need the same type of adjustment.
1008  if ((closed && continuous_closed_curves) || use_end_target_up_vector)
1009  {
1010  UT_Vector3T<T> final_up_vector;
1011  UT_Vector3T<T> comparison_tangent;
1012  bool skip_twist = false;
1013  if (closed)
1014  {
1015  // Same as above regarding not really needing to compute this matrix
1016  // if it's a bottleneck.
1018  bool failure = rotation.dihedral(tangents[npoints-1], tangents[0], false);
1019  if (failure)
1020  {
1021  // The tangent flipped a half turn, so keep the up vector the same.
1022  // The bitangent will implicitly flip a half turn.
1023  final_up_vector = up_vectors[npoints-1];
1024  }
1025  else
1026  {
1027  // Rotate up vectors by the rotation matrix.
1028  final_up_vector = up_vectors[npoints-1] * rotation;
1029  }
1030  // Just to prevent roundoff error from getting bad, force final_up_vector to be orthogonal to tangents[0].
1031  final_up_vector -= dot(final_up_vector,tangents[0])*tangents[0];
1032  // It seems implausible that final_up_vector could possibly be problematically small
1033  // after the subtraction, since it should already have been approximately
1034  // orthogonal to tangents[0] and unit length, so let's take the risk and just normalize.
1035  final_up_vector.normalize();
1036 
1037  comparison_tangent = tangents[0];
1038  }
1039  else
1040  {
1041  // Open curve, so final up vector is already in the array.
1042  final_up_vector = up_vectors[npoints-1];
1043  comparison_tangent = tangents[npoints-1];
1044  }
1045 
1046  UT_Vector3T<T> comparison_up_vector;
1047  if (closed && continuous_closed_curves)
1048  {
1049  comparison_up_vector = up_vectors[0];
1050  }
1051  else
1052  {
1053  // Using an end target up vector.
1054  if (use_normal_vector_up)
1055  {
1056  // Normal vector was written into target_up_vector above.
1057  comparison_up_vector = target_up_vector;
1058  }
1059  else
1060  comparison_up_vector = end_target_up_vector;
1061  comparison_up_vector -= comparison_up_vector*dot(comparison_up_vector,comparison_tangent);
1062  T length2 = comparison_up_vector.length2();
1063  // If the target up vector is extremely close to the tangent, skip applying a twist.
1064  if (length2 >= theQuiteSmallRelativeLength2)
1065  comparison_up_vector /= SYSsqrt(length2);
1066  else
1067  skip_twist = true;
1068  }
1069 
1070  T net_twist = T(0);
1071  if (!skip_twist)
1072  {
1073  // NOTE: The cosine between the start and end up_vectors is
1074  // not sufficient to get the direction of the net twist.
1075  // We need to consider the direction turned relative to
1076  // the tangent. Thus, we need to use the cross product
1077  // between the two dotted against the tangent to get
1078  // the correct sine of the angle.
1079  UT_Vector3T<T> net_twist_vector = cross(final_up_vector, comparison_up_vector);
1080  T sin_net_twist = dot(net_twist_vector, comparison_tangent);
1081  T cos_net_twist = dot(final_up_vector, comparison_up_vector);
1082  net_twist = SYSatan2(sin_net_twist, cos_net_twist); // atan2(y,x) order
1083  }
1084 
1085  if (!SYSequalZero(net_twist, theQuiteSmallRelativeLength))
1086  {
1087  // Apply twist around the loop
1088  if (twist_per == RotationPer::EDGE || twist_per == RotationPer::FULLEDGES)
1089  {
1090  for (GA_Size i = 1; i < npoints; ++i)
1091  {
1092  T reverse_twist = -i*net_twist/nedges;
1093  T s = SYSsin(reverse_twist);
1094  T c = SYScos(reverse_twist);
1095  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1096  up_vectors(i) = s*bitangent + c*up_vectors(i);
1097  }
1098  }
1099  else if (twist_per == RotationPer::DISTANCE || twist_per == RotationPer::FULLDISTANCE ||
1100  (twist_per == RotationPer::ATTRIB && (
1101  !roll_attrib.isValid() ||
1102  roll_attrib->getOwner() == GA_ATTRIB_PRIMITIVE ||
1103  roll_attrib->getOwner() == GA_ATTRIB_DETAIL)))
1104  {
1105  double total_length = 0;
1106  for (GA_Size i = 0; i < nedges; ++i)
1107  {
1108  total_length += directions(i).length();
1109  }
1110 
1111  // NOTE: total_length shouldn't be zero, since we already ensured
1112  // that we have at least two non-zero-length edges.
1113  double cur_length = 0;
1114  for (GA_Size i = 1; i < npoints; ++i)
1115  {
1116  cur_length += directions(i-1).length();
1117  T reverse_twist = -cur_length*net_twist/total_length;
1118  T s = SYSsin(reverse_twist);
1119  T c = SYScos(reverse_twist);
1120  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1121  up_vectors(i) = s*bitangent + c*up_vectors(i);
1122  }
1123  }
1124  else
1125  { // twist_per == RotationPer::ATTRIB
1126  if (roll_attrib->getOwner() == GA_ATTRIB_VERTEX)
1127  {
1128 #if 0
1129  T2 ustart = 0;
1130  T2 uend = 1;
1131  if (nverts == npoints+1)
1132  {
1133  // We can really only use start and end uvs reliably
1134  // if the curve is unrolled, (1 extra vertex).
1135  // Otherwise, we default to assuming 0 to 1.
1136  ustart = roll_attrib.get(vertices(0), ucomponent);
1137  uend = roll_attrib.get(vertices.last(), ucomponent);
1138  }
1139  T2 uspan = (uend-ustart);
1140 #endif
1141 
1142  //if (uspan != T2(0))
1143  //{
1144  for (GA_Size i = 1; i < npoints; ++i)
1145  {
1146  T2 t = roll_attrib.get(vertices(i), ucomponent);
1147  //T2 t = (u-ustart)/uspan;
1148  T reverse_twist = -t*net_twist;
1149  T s = SYSsin(reverse_twist);
1150  T c = SYScos(reverse_twist);
1151  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1152  up_vectors(i) = s*bitangent + c*up_vectors(i);
1153  }
1154  //}
1155  }
1156  else
1157  {
1158  UT_ASSERT(roll_attrib->getOwner() == GA_ATTRIB_POINT);
1159  // Assume 0 to 1, since it won't end correctly and we have to guess.
1160 
1161  for (GA_Size i = 1; i < npoints; ++i)
1162  {
1163  T2 u = roll_attrib.get(geo->vertexPoint(vertices(i)), ucomponent);
1164  T reverse_twist = -u*net_twist;
1165  T s = SYSsin(reverse_twist);
1166  T c = SYScos(reverse_twist);
1167  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1168  up_vectors(i) = s*bitangent + c*up_vectors(i);
1169  }
1170  }
1171  }
1172 
1173  if (closed && continuous_closed_curves)
1174  {
1175  // The twist applied so far is the negative of the original net twist.
1176  total_twist_around_loop = -net_twist;
1177  }
1178  }
1179  }
1180 
1181  if (target_up_vector_at_start)
1182  {
1183  // Nothing else to do if target up vector is okay being at the start,
1184  // since that's what we used as our initial guess.
1185  return;
1186  }
1187 
1188  // Now that we have consistent up_vectors, we make them more stable by
1189  // making the average closer to perpendicular to the backbone polygon normal,
1190  // by introducing a global roll rotation.
1191 
1192  // This sounds pretty nebulous, but the math simplifies down nicely.
1193  // We want to find the z-axis rotation Rz such that
1194  // [0 1 0] * Rz * M
1195  // is in the direction of target_up_vector,
1196  // where M is the current average rotation matrix.
1197  // (Note that M itself likely isn't a rotation matrix, may not
1198  // have orthogonal rows, and may even be singular or near-singular.)
1199  // Depending on which direction we choose as the angle, this is:
1200  // [sin(theta) cos(theta) 0] * M
1201  // = [s c 0] * M
1202  // = s*Mx + c*My
1203  // If we solve:
1204  // [Mx.Mx Mx.My][x] [Mx.up]
1205  // [Mx.My My.My][y] = [My.up]
1206  // we'll get the vector that is least-squares closest to up
1207  // that can be made using Mx and My, as a linear combination of Mx and My.
1208  // If we normalize [x y], that gives us a valid [s c]. Then, we can apply
1209  // Rz = [ c -s 0]
1210  // [ s c 0]
1211  // [ 0 0 1]
1212  // on the left of each frame.
1213  // Of course, if Mx and My are parallel, or one of the two is much shorter
1214  // than the other, (or equivalently, if the determinant of the 2x2 matrix above
1215  // is small compared to the length-squared-squared of Mx and My), we need to handle
1216  // things differently.
1217 
1218  // Find Mx and My, (Mz isn't needed), in double-precision to avoid
1219  // catastrophic roundoff error in the accumulation.
1220  UT_Vector3D Mx(0,0,0);
1221  UT_Vector3D My(0,0,0);
1222  for (GA_Size i = 0; i < npoints; ++i) {
1223  My += up_vectors(i);
1224  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1225  Mx += bitangent;
1226  }
1227  // We don't strictly need to divide by npoints for the average,
1228  // since we'll be normalizing later, and there's little risk of overflow
1229  // using double-precision.
1230 
1231  double MxMx = dot(Mx,Mx);
1232  double MxMy = dot(Mx,My);
1233  double MyMy = dot(My,My);
1234  double Mxup = dot(Mx,UT_Vector3D(target_up_vector));
1235  double Myup = dot(My,UT_Vector3D(target_up_vector));
1236 
1237  // Part of this is effectively the same as UT_Matrix2T::solve
1238  // Side note: via awesome properties of cross & dot products,
1239  // determinant is equal to (Mx x My).(Mx x My),
1240  // so it's the length-squared of the cross product.
1241  double determinant = MxMx*MyMy - MxMy*MxMy;
1242  double scale2 = SYSmax(MxMx*MxMx, MyMy*MyMy);
1243  T s; T c;
1244  // 1e-6 is because if Mx is 1000x longer than My and they're in the ballpark
1245  // of orthogonal, My is almost certainly due to slightly imbalanced vectors
1246  // that should really cancel out exactly. If they're in the ballpark of
1247  // parallel and the non-parallel component of My is 1000x smaller than Mx,
1248  // they're effectively the same.
1249  // In either case, that yields determinant/scale2 ~ 1e-6
1250  if (!SYSequalZero(determinant, 1e-6*scale2)) {
1251  // Not near singular, so we can solve it.
1252  // We'd normally divide by determinant to solve a 2x2 system,
1253  // but we're normalizing after anyway.
1254  s = (MyMy*Mxup - MxMy*Myup);
1255  c = (MxMx*Myup - MxMy*Mxup);
1256  T normalization = s*s + c*c;
1257  if (SYSequalZero(normalization, theExtremelySmallLength2)) {
1258  s = 0;
1259  c = 1;
1260  }
1261  else {
1262  normalization = T(1) / SYSsqrt(normalization);
1263  s *= normalization;
1264  c *= normalization;
1265  }
1266  }
1267  else if (MxMx > MyMy) {
1268  s = (Mxup >= 0) ? 1 : -1;
1269  c = 0;
1270  }
1271  else {
1272  s = 0;
1273  c = (Myup >= 0) ? 1 : -1;
1274  }
1275 
1276  // New up vector =
1277  // [ c -s 0] [<--Mx-->] [cMx-sMy ]
1278  // [0 1 0] [ s c 0] [<--My-->] = [0 1 0] [sMx+cMy ] = [sMx+cMy ]
1279  // [ 0 0 1] [<--Mz-->] [<--Mz-->]
1280 
1281  for (GA_Size i = 0; i < npoints; ++i) {
1282  UT_Vector3T<T> bitangent = cross(up_vectors(i), tangents(i));
1283  up_vectors(i) = s*bitangent + c*up_vectors(i);
1284  }
1285 
1286  // Finally done computing up_vectors! Yay! :)
1287 }
1288 
1289 bool
1291  const GEO_Detail *geometry,
1292  const GA_OffsetListRef &vertices,
1293  exint &nedges,
1294  bool &closed,
1295  bool &unrolled)
1296 {
1297  if (vertices.size() == 0) {
1298  // Skip cross-sections with zero vertices
1299  return false;
1300  }
1301  if (vertices.size() == 1) {
1302  // Always treat single-vertex polygons as open,
1303  // to try to reduce confusion.
1304  closed = false;
1305  unrolled = false;
1306  nedges = 0;
1307  return true;
1308  }
1309 
1310  closed = vertices.getExtraFlag();
1311  unrolled = false;
1312  nedges = vertices.size();
1313 
1314  if (!closed) {
1315  --nedges;
1316 
1317  GA_Offset pt0 = geometry->vertexPoint(vertices(0));
1318  GA_Offset lastpt = geometry->vertexPoint(vertices.last());
1319  // If first and last point are the same, treat it as if it's closed
1320  // in most cases, but we want to record that it's an unrolled curve,
1321  // so that the row and col cases treat it as open with a shared point.
1322  unrolled = (pt0 == lastpt);
1323  closed = unrolled;
1324  }
1325  return true;
1326 }
1327 
1328 template<typename T>
1329 static void
1330 createRotationMatrix(
1332  const UT_Vector3T<T> &cur_angles,
1333  const UT_Axis3::axis order[3])
1334 {
1335  transform.identity();
1336 
1337  for (int i = 0; i < 3; ++i)
1338  {
1339  float angle = cur_angles[i];
1340  if (angle == 0)
1341  continue;
1342  transform.rotate(order[i], angle);
1343  }
1344 }
1345 
1346 template<typename T>
1347 void
1349  const GEO_Detail *const geo,
1350  const GA_PrimitiveGroup *curve_group,
1351  const GA_RWHandleT<UT_Matrix4T<T>> &transform_attrib,
1352  const CurveFrameParms<T> &parms)
1353 {
1354  UT_AutoInterrupt interrupt("Computing curve transforms");
1355  if (interrupt.wasInterrupted())
1356  return;
1357 
1358  UT_ASSERT(transform_attrib.isValid());
1359  if (!transform_attrib.isValid())
1360  return;
1361 
1362  UT_ASSERT(transform_attrib->getOwner() == GA_ATTRIB_VERTEX);
1363 
1364  // We're going to be writing to tangent_attrib in parallel, and although
1365  // we're using GA_SplittableRange, we're splitting over primitive pages,
1366  // not vertex pages, so we have to harden in advance.
1367  transform_attrib->hardenAllPages();
1368 
1369  bool rotate_using_backbone = parms.myTangentType != TangentType::NONE;
1370  UT_Vector3T<T> target_up_vector = parms.myTargetUpVector;
1371  UT_Vector3T<T> end_target_up_vector = parms.myEndTargetUpVector;
1372  if (rotate_using_backbone)
1373  {
1374  // Normalize up_vector
1375  T length2 = target_up_vector.length2();
1376  if (length2 < theExtremelySmallLength2)
1377  {
1378  // Fall back to 0,1,0 if need be.
1379  target_up_vector.assign(0,1,0);
1380  }
1381  else
1382  {
1383  target_up_vector /= SYSsqrt(length2);
1384  }
1385  if (parms.myUseEndTargetUpVector)
1386  {
1387  // Normalize end up_vector
1388  length2 = end_target_up_vector.length2();
1389  if (length2 < theExtremelySmallLength2)
1390  {
1391  // Fall back to 0,1,0 if need be.
1392  end_target_up_vector.assign(0,1,0);
1393  }
1394  else
1395  {
1396  end_target_up_vector /= SYSsqrt(length2);
1397  }
1398  }
1399  }
1400 
1401  // If we're transforming by instance transform attributes,
1402  // e.g. N, up, pscale, scale, rot, orient, pivot, trans, xform,
1403  // set up the cache of those attributes.
1404  GA_AttributeInstanceMatrix instance_attribs;
1405  GA_ROHandleV3D instance_N;
1406  GA_ROHandleV3D instance_up;
1407  bool transform_by_instance_attribs = parms.myTransformByInstanceAttribs;
1408  if (transform_by_instance_attribs)
1409  {
1410  instance_attribs.initialize(geo->pointAttribs());
1411  if (parms.myNormalizeScales)
1412  instance_attribs.resetScales();
1413 
1414  // If there's only one of either N or up, we want to handle those separately.
1415  bool has_N = instance_attribs.myN.isValid();
1416  bool has_up = instance_attribs.myUp.isValid();
1417  if (has_N && !has_up)
1418  {
1419  instance_N = instance_attribs.myN;
1420  instance_attribs.myN.clear();
1421  }
1422  else if (has_up && !has_N)
1423  {
1424  instance_up = instance_attribs.myUp;
1425  instance_attribs.myUp.clear();
1426  }
1427 
1428  if (!instance_attribs.hasAnyAttribs())
1429  transform_by_instance_attribs = false;
1430 
1431  // We don't want to rotate both based on the backbone curves
1432  // and based on attributes, but we may still want to use pscale
1433  // with the backbone curve rotations.
1434  rotate_using_backbone &= !instance_attribs.hasNonScales();
1435  }
1436 
1437  const bool use_rotation_attrib[3] =
1438  {
1439  parms.myIncAnglePer[0] == RotationPer::ATTRIB && parms.myRotAttribs[0].isValid() && parms.myIncAngles[0] != 0.0,
1440  parms.myIncAnglePer[1] == RotationPer::ATTRIB && parms.myRotAttribs[1].isValid() && parms.myIncAngles[1] != 0.0,
1441  parms.myIncAnglePer[2] == RotationPer::ATTRIB && parms.myRotAttribs[2].isValid() && parms.myIncAngles[2] != 0.0
1442  };
1443  const GA_AttributeOwner rot_attrib_owner[3] =
1444  {
1445  use_rotation_attrib[0] ? parms.myRotAttribs[0]->getOwner() : GA_ATTRIB_INVALID,
1446  use_rotation_attrib[1] ? parms.myRotAttribs[1]->getOwner() : GA_ATTRIB_INVALID,
1447  use_rotation_attrib[2] ? parms.myRotAttribs[2]->getOwner() : GA_ATTRIB_INVALID
1448  };
1449  const bool varying_attrib[3] =
1450  {
1451  rot_attrib_owner[0] == GA_ATTRIB_POINT || rot_attrib_owner[0] == GA_ATTRIB_VERTEX,
1452  rot_attrib_owner[1] == GA_ATTRIB_POINT || rot_attrib_owner[1] == GA_ATTRIB_VERTEX,
1453  rot_attrib_owner[2] == GA_ATTRIB_POINT || rot_attrib_owner[2] == GA_ATTRIB_VERTEX
1454  };
1455 
1456  UT_Axis3::axis order[3];
1457  switch (parms.myRotationOrder)
1458  {
1459  case UT_XformOrder::XYZ: order[0] = UT_Axis3::XAXIS; order[1] = UT_Axis3::YAXIS; order[2] = UT_Axis3::ZAXIS; break;
1460  case UT_XformOrder::XZY: order[0] = UT_Axis3::XAXIS; order[1] = UT_Axis3::ZAXIS; order[2] = UT_Axis3::YAXIS; break;
1461  case UT_XformOrder::YXZ: order[0] = UT_Axis3::YAXIS; order[1] = UT_Axis3::XAXIS; order[2] = UT_Axis3::ZAXIS; break;
1462  case UT_XformOrder::YZX: order[0] = UT_Axis3::YAXIS; order[1] = UT_Axis3::ZAXIS; order[2] = UT_Axis3::XAXIS; break;
1463  case UT_XformOrder::ZXY: order[0] = UT_Axis3::ZAXIS; order[1] = UT_Axis3::XAXIS; order[2] = UT_Axis3::YAXIS; break;
1464  case UT_XformOrder::ZYX: order[0] = UT_Axis3::ZAXIS; order[1] = UT_Axis3::YAXIS; order[2] = UT_Axis3::XAXIS; break;
1465  }
1466 
1467  // Loop over primitives in parallel, using a splittable range and a lambda functor.
1469  [geo,&parms,transform_by_instance_attribs,&instance_N,&instance_up,
1470  &instance_attribs,rotate_using_backbone,target_up_vector,end_target_up_vector,
1471  &transform_attrib,&interrupt,use_rotation_attrib,rot_attrib_owner,varying_attrib,
1472  &order](const GA_SplittableRange &r)
1473  {
1474  // An array to help with computing backbone rotations
1475  UT_SmallArray<UT_Vector3T<T>, 8*sizeof(UT_Vector3T<T>)> directions;
1476  UT_SmallArray<UT_Vector3T<T>, 8*sizeof(UT_Vector3T<T>)> tangents;
1477  UT_SmallArray<UT_Vector3T<T>, 8*sizeof(UT_Vector3T<T>)> up_vectors;
1478  UT_SmallArray<UT_Vector3T<T>, 8*sizeof(UT_Vector3T<T>)> stretch_dirs;
1479  UT_SmallArray<T, 8*sizeof(T)> stretch_scales;
1480 
1481  // Shortest length of edgedira+edgedirb before max_stretch_scale must be applied
1482  const T max_stretch_length_threshold = 2.0f / parms.myMaxStretchScale;
1483 
1484  const bool rotate_using_parameters = !parms.myAngles.isZero() || !parms.myIncAngles.isZero();
1485 
1486  const int curve_u_component(parms.myRotAttribComponent);
1487 
1488  // Inside the functor, we have a sub-range of primitives, so loop over that range.
1489  // We use blockAdvance, instead of !it.atEnd() and ++it, for less looping overhead.
1491  for (GA_Iterator it(r); it.blockAdvance(start,end); )
1492  {
1493  // We probably don't need to check for interruption on every curve,
1494  // (unless people put in a curve with millions of vertices), so we
1495  // can check it once for every contiguous block of up to GA_PAGE_SIZE
1496  // primitive offsets.
1497  if (interrupt.wasInterrupted())
1498  return;
1499 
1500  // Loop over all primitives in this contiguous block of offsets.
1501  for (GA_Offset primoff = start; primoff < end; ++primoff)
1502  {
1503  const GA_OffsetListRef vertices = geo->getPrimitiveVertexList(primoff);
1504  GA_Size nedges;
1505  bool closed;
1506  bool unrolled;
1507  bool nonempty = getPolyProperties(geo, vertices, nedges, closed, unrolled);
1508  const GA_Size npoints = nedges + !closed;
1509  const GA_Size nverts = vertices.size();
1510 
1511  // Nothing to do if no vertices
1512  if (!nonempty)
1513  continue;
1514 
1515  UT_Vector3T<T> local_target_up_vector = target_up_vector;
1516  UT_Vector3T<T> local_end_target_up_vector = end_target_up_vector;
1517  if (rotate_using_backbone && parms.myTargetUpVectorAttrib.isValid())
1518  {
1520  local_target_up_vector = parms.myTargetUpVectorAttrib.get(primoff);
1521 
1522  // Normalize up_vector
1523  T length2 = local_target_up_vector.length2();
1524  if (length2 < theExtremelySmallLength2)
1525  {
1526  // Fall back to 0,1,0 if need be.
1527  local_target_up_vector.assign(0,1,0);
1528  }
1529  else
1530  {
1531  local_target_up_vector /= SYSsqrt(length2);
1532  }
1533  }
1534  if (rotate_using_backbone && parms.myUseEndTargetUpVector && parms.myEndTargetUpVectorAttrib.isValid())
1535  {
1537  local_end_target_up_vector = parms.myEndTargetUpVectorAttrib.get(primoff);
1538 
1539  // Normalize end up_vector
1540  T length2 = local_end_target_up_vector.length2();
1541  if (length2 < theExtremelySmallLength2)
1542  {
1543  // Fall back to 0,1,0 if need be.
1544  local_end_target_up_vector.assign(0,1,0);
1545  }
1546  else
1547  {
1548  local_end_target_up_vector /= SYSsqrt(length2);
1549  }
1550  }
1551 
1552  bool local_rotate_using_backbone = rotate_using_backbone;
1553  bool local_stretch_using_backbone = parms.myStretchAroundTurns;
1554 
1555  // If the curve is closed, we may have to introduce a partial twist
1556  // around the loop, instead of having the twist all at one point.
1557  // This is used to adjust how much additional twist to apply.
1558  T total_twist_around_loop = 0;
1559 
1560  const bool continuous_closed = closed && parms.myContinuousClosedCurves;
1561 
1562  // FIXME: Add parameter for use_nurbs_tangents once implemented!!!
1563  bool use_nurbs_tangents = false;
1564 
1565  RotationPer local_dangle_per[3] =
1566  {
1567  parms.myIncAnglePer[0],
1568  parms.myIncAnglePer[1],
1569  parms.myIncAnglePer[2]
1570  };
1571  UT_Vector3T<T> local_dangles = parms.myIncAngles;
1572 
1573  bool needs_length = false;
1574  bool needs_total_length = false;
1575  if (rotate_using_parameters)
1576  {
1577  needs_length =
1578  (local_dangle_per[0] == RotationPer::FULLDISTANCE || local_dangle_per[0] == RotationPer::DISTANCE) ||
1579  (local_dangle_per[1] == RotationPer::FULLDISTANCE || local_dangle_per[1] == RotationPer::DISTANCE) ||
1580  (local_dangle_per[2] == RotationPer::FULLDISTANCE || local_dangle_per[2] == RotationPer::DISTANCE);
1581  needs_total_length =
1582  (local_dangle_per[0] == RotationPer::FULLDISTANCE || (continuous_closed && local_dangle_per[0] == RotationPer::DISTANCE)) ||
1583  (local_dangle_per[1] == RotationPer::FULLDISTANCE || (continuous_closed && local_dangle_per[1] == RotationPer::DISTANCE)) ||
1584  (local_dangle_per[2] == RotationPer::FULLDISTANCE || (continuous_closed && local_dangle_per[2] == RotationPer::DISTANCE));
1585  }
1586  if (parms.myScaleRamp)
1587  {
1588  needs_length = true;
1589  needs_total_length = true;
1590  }
1591 
1592  computeSingleBackboneFrames(
1593  local_rotate_using_backbone,
1594  local_stretch_using_backbone,
1595  needs_length,
1596  directions, tangents, up_vectors,
1597  stretch_dirs, stretch_scales,
1598  total_twist_around_loop,
1599  geo, nedges, npoints, nverts, closed,
1601  instance_N,
1602  primoff, vertices, use_nurbs_tangents,
1603  parms.myMaxStretchScale, max_stretch_length_threshold,
1604  instance_up,
1605  local_target_up_vector, parms.myUseCurveNormalAsTargetUp,
1606  parms.myTargetUpVectorAtStart, continuous_closed,
1607  parms.myUseEndTargetUpVector, local_end_target_up_vector,
1608  parms.myIncAnglePer[2],
1609  parms.myRotAttribs[2], curve_u_component);
1610 
1611  UT_Vector3T<T> local_angles(0,0,0);
1612  if (use_rotation_attrib[0] && !varying_attrib[0])
1613  {
1614  GA_Offset offset = (rot_attrib_owner[0] == GA_ATTRIB_PRIMITIVE) ? primoff : GA_DETAIL_OFFSET;
1615  local_angles[0] = local_dangles[0]*parms.myRotAttribs[0].get(offset);
1616  }
1617  if (use_rotation_attrib[1] && !varying_attrib[1])
1618  {
1619  GA_Offset offset = (rot_attrib_owner[1] == GA_ATTRIB_PRIMITIVE) ? primoff : GA_DETAIL_OFFSET;
1620  local_angles[1] = local_dangles[1]*parms.myRotAttribs[1].get(offset);
1621  }
1622  if (use_rotation_attrib[2] && !varying_attrib[2])
1623  {
1624  GA_Offset offset = (rot_attrib_owner[2] == GA_ATTRIB_PRIMITIVE) ? primoff : GA_DETAIL_OFFSET;
1625  local_angles[2] = local_dangles[2]*parms.myRotAttribs[2].get(offset);
1626  }
1627 
1628  // Double-precision accumulators to avoid catastrophic roundoff error
1629  // for curves with > 16,777,216 vertices, (i.e. 2^24).
1630  double total_length = 0;
1631  double cur_length = 0;
1632  if (needs_total_length)
1633  {
1634  for (GA_Size i = 0; i < nedges; ++i)
1635  {
1636  total_length += directions(i).length();
1637  }
1638  }
1639 
1640  // For closed curves, the total twist must be a multiple of a full turn,
1641  // so we have to round it, but carefully.
1642  const bool rotate_using_dangles = !local_dangles.isZero();
1643  if (rotate_using_parameters && continuous_closed && rotate_using_dangles)
1644  {
1645  for (int axis = 0; axis < 3; ++axis)
1646  {
1647  if (local_dangle_per[axis] == RotationPer::EDGE)
1648  {
1649  local_dangles[axis] *= nedges;
1650  local_dangle_per[axis] = RotationPer::FULLEDGES;
1651  }
1652  else if (local_dangle_per[axis] == RotationPer::DISTANCE)
1653  {
1654  local_dangles[axis] *= total_length;
1655  local_dangle_per[axis] = RotationPer::FULLDISTANCE;
1656  }
1657  }
1658 
1659  // We have a separate twist due to the base frame needing to line up
1660  // with itself after going around the loop, so we want to take into
1661  // account that it'll be applied anyway.
1662  local_dangles.z() -= total_twist_around_loop;
1663  total_twist_around_loop = 0;
1664 
1665  // Round angles to multiples of a full turn
1666  local_dangles /= (2*M_PI);
1667  local_dangles.x() = SYSrint(local_dangles.x());
1668  local_dangles.y() = SYSrint(local_dangles.y());
1669  local_dangles.z() = SYSrint(local_dangles.z());
1670  local_dangles *= (2*M_PI);
1671  }
1672 
1673  // NOTE: Although we're really iterating over vertices,
1674  // we don't need to write to the last vertex of an
1675  // unrolled curve (open but last point same as first),
1676  // and the code above figured out backbone rotations
1677  // and stretching for npoints locations.
1678  for (GA_Size i = 0; i < npoints; ++i)
1679  {
1680  GA_Offset vtxoff = vertices(i);
1681  GA_Offset ptoff = geo->vertexPoint(vtxoff);
1682 
1683  UT_Vector3T<T> cur_angles = parms.myAngles+local_angles;
1684 
1685  // Apply the parameter rotations before any backbone or
1686  // attribute rotations, because they're rotations relative
1687  // to those transformed bases, as if the cross section was
1688  // rotated before applying this node.
1689  if (rotate_using_dangles)
1690  {
1691  for (int axis = 0; axis < 3; ++axis)
1692  {
1693  if (axis != 2 && local_dangles[axis] == 0)
1694  continue;
1695 
1696  const RotationPer cur_dangle_per = local_dangle_per[axis];
1697  if (cur_dangle_per == RotationPer::EDGE)
1698  {
1699  cur_angles[axis] += local_dangles[axis]*i;
1700 
1701  // Remove portion of twist added to make closed curve cycle smoothly.
1702  if (axis == 2 && total_twist_around_loop != 0)
1703  {
1704  T t = T(i)/nedges;
1705  cur_angles.z() -= total_twist_around_loop*t;
1706  }
1707  }
1708  else if (cur_dangle_per == RotationPer::FULLEDGES)
1709  {
1710  T t = T(i)/nedges;
1711  cur_angles[axis] += local_dangles[axis]*t;
1712 
1713  // Remove portion of twist added to make closed curve cycle smoothly.
1714  if (axis == 2)
1715  cur_angles.z() -= total_twist_around_loop*t;
1716  }
1717  else if (cur_dangle_per == RotationPer::DISTANCE)
1718  {
1719  cur_angles[axis] += local_dangles[axis]*cur_length;
1720 
1721  if (axis == 2 && total_length != 0 && total_twist_around_loop != 0)
1722  {
1723  // Remove portion of twist added to make closed curve cycle smoothly.
1724  T t = (cur_length/total_length);
1725  cur_angles.z() -= total_twist_around_loop*t;
1726  }
1727  }
1728  else if (cur_dangle_per == RotationPer::FULLDISTANCE)
1729  {
1730  // NOTE: total_length could be zero, but cur_length can't be more
1731  // than total_length, so checking for zero exactly suffices.
1732  if (total_length != 0)
1733  {
1734  T t = (cur_length/total_length);
1735  cur_angles[axis] += t*local_dangles[axis];
1736 
1737  // Remove portion of twist added to make closed curve cycle smoothly.
1738  if (axis == 2)
1739  cur_angles.z() -= total_twist_around_loop*t;
1740  }
1741  }
1742  else if (use_rotation_attrib[axis] && varying_attrib[axis])
1743  {
1744  UT_ASSERT_P(cur_dangle_per == RotationPer::ATTRIB);
1745  if (rot_attrib_owner[axis] == GA_ATTRIB_VERTEX)
1746  {
1747 #if 0
1748  T ustart = 0;
1749  T uend = 1;
1750  if (!closed || unrolled) {
1751  // We can really only use start and end uvs reliably if the
1752  // curve is open or unrolled, (separate start and end vertices).
1753  // Otherwise, we default to assuming 0 to 1.
1754  ustart = parms.myRotAttribs[axis].get(vertices(0), curve_u_component);
1755  uend = parms.myRotAttribs[axis].get(vertices.last(), curve_u_component);
1756  }
1757  T uspan = (uend-ustart);
1758 #endif
1759 
1760  T t = parms.myRotAttribs[axis].get(vtxoff, curve_u_component);
1761 
1762  //if (uspan != 0) {
1763  // T t = (u-ustart)/uspan;
1764  cur_angles[axis] += t*local_dangles[axis];
1765 
1766  // Remove portion of twist added to make closed curve cycle smoothly.
1767  if (axis == 2)
1768  cur_angles.z() -= total_twist_around_loop*t;
1769  //}
1770  }
1771  else
1772  {
1773  UT_ASSERT(rot_attrib_owner[axis] == GA_ATTRIB_POINT);
1774  // Assume 0 to 1, since it won't end correctly and we have to guess.
1775 
1776  T u = parms.myRotAttribs[axis].get(ptoff, curve_u_component);
1777  cur_angles[axis] += u*local_dangles[axis];
1778 
1779  // Remove portion of twist added to make closed curve cycle smoothly.
1780  if (axis == 2)
1781  cur_angles.z() -= total_twist_around_loop*u;
1782  }
1783  }
1784  }
1785  }
1786 
1788  if (rotate_using_parameters)
1789  {
1790  createRotationMatrix(transform, cur_angles, order);
1791  }
1792  else
1793  {
1794  transform.identity();
1795  }
1796 
1797  if (transform_by_instance_attribs)
1798  {
1799  UT_Matrix4T<T> instance_transform;
1800  // Save post-translate P for until after backbone rotation
1801  // or scale might be applied.
1802  instance_attribs.getMatrix(instance_transform, UT_Vector3T<T>(0,0,0), ptoff);
1803 
1804  transform *= instance_transform;
1805  }
1806 
1807  if (local_rotate_using_backbone)
1808  {
1809  UT_Vector3T<T> zaxis = tangents(i);
1810  UT_Vector3T<T> yaxis = up_vectors(i);
1811  UT_Vector3T<T> xaxis = cross(yaxis, zaxis);
1812  UT_Matrix3T<T> rotation(xaxis, yaxis, zaxis);
1813  transform *= rotation;
1814  }
1815 
1816  // Apply the stretch vector (to avoid collapsing on turns)
1817  // after any rotations, since it should always be applied
1818  // in the same final direction.
1819  if (local_stretch_using_backbone)
1820  {
1821  UT_Matrix3T<T> stretch_matrix;
1822  stretch_matrix.identity();
1823  stretch_matrix.outerproductUpdateT(stretch_scales(i)-1, stretch_dirs(i), stretch_dirs(i));
1824 
1825  transform *= stretch_matrix;
1826  }
1827 
1828  fpreal scale = parms.myUniformScale;
1829  if (parms.myScaleRamp)
1830  {
1831  const T t = (total_length != 0) ? (cur_length/total_length) : T(i)/nedges;
1832  float vals[4];
1833  parms.myScaleRamp->rampLookup(t, vals);
1834  scale *= vals[0];
1835  }
1836 
1837  // Apply the overall scale
1838  if (scale != 1)
1839  {
1840  // NOTE: We don't want to just do "transform *= scale",
1841  // because we don't want to scale the w components
1842  // of any rows, else translate below will be skewed.
1843  transform.scale(scale);
1844  }
1845 
1846  transform.translate(geo->getPos3(ptoff));
1847 
1848  transform_attrib.set(vtxoff, transform);
1849 
1850  if (needs_length && i+1 < npoints)
1851  cur_length += directions(i).length();
1852  }
1853  }
1854  }
1855  });
1856 }
1857 
1858 #define TEMPLATE_INST2(T) \
1859 template void computeCurveTransforms<T>( \
1860  const GEO_Detail *const geo, \
1861  const GA_PrimitiveGroup *curve_group, \
1862  const GA_RWHandleT<UT_Matrix4T<T>> &transform_attrib, \
1863  const CurveFrameParms<T> &parms); \
1864 /* end of macro */
1865 
1868 
1869 }
1870 
1871 } // End of HDK_Sample namespace
constexpr SYS_FORCE_INLINE T length2() const noexcept
Definition: UT_Vector3.h:358
#define SYSmax(a, b)
Definition: SYS_Math.h:1952
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)
SYS_FORCE_INLINE GA_Primitive * getPrimitive(GA_Offset prim_off)
Definition: GA_Detail.h:438
SYS_FORCE_INLINE const GA_AttributeDict & pointAttribs() const
Definition: GEO_Detail.h:1979
Apply angle increment to each edge on top of previous edge's rotation.
Definition: GU_CurveFrame.h:69
void resetScales()
Resets only the scale attributes.
virtual fpreal getGreville(int idx, bool clamp=true, bool wrap=false) const =0
Iteration over a range of elements.
Definition: GA_Iterator.h:29
SIM_API const UT_StringHolder angle
SYS_FORCE_INLINE int getPrimitiveTypeId(GA_Offset primoff) const
Definition: GA_Primitive.h:922
GA_ROHandleT< UT_Vector3T< T > > myTargetUpVectorAttrib
#define M_PI
Definition: fmath.h:98
bool blockAdvance(GA_Offset &start, GA_Offset &end)
GLuint start
Definition: glcorearb.h:475
void setSizeNoInit(exint newsize)
Definition: UT_Array.h:719
SYS_FORCE_INLINE bool getExtraFlag() const
Synonym for isClosed()
constexpr SYS_FORCE_INLINE T & z() noexcept
Definition: UT_Vector3.h:669
int64 exint
Definition: SYS_Types.h:125
GLboolean GLboolean GLboolean GLboolean a
Definition: glcorearb.h:1222
GLdouble s
Definition: glad.h:3009
SYS_FORCE_INLINE TO_T UTverify_cast(FROM_T from)
Definition: UT_Assert.h:242
#define UT_ASSERT_MSG_P(ZZ,...)
Definition: UT_Assert.h:167
SYS_FORCE_INLINE UT_Vector3 getPos3(GA_Offset ptoff) const
The ptoff passed is the point offset.
Definition: GA_Detail.h:194
void arbitraryPerp(const UT_Vector3T< T > &v)
Finds an arbitrary perpendicular to v, and sets this to it.
UT_Matrix2T< T > SYSlerp(const UT_Matrix2T< T > &v1, const UT_Matrix2T< T > &v2, S t)
Definition: UT_Matrix2.h:675
3D Vector class.
SYS_FORCE_INLINE bool isClosed() const
Definition: GEO_Face.h:248
float fpreal32
Definition: SYS_Types.h:200
exint GA_Size
Defines the bit width for index and offset types in GA.
Definition: GA_Types.h:243
int myRotAttribComponent
Component of myRotAttribs being read. (default 0)
GA_ROHandleT< UT_Vector3T< T > > myEndTargetUpVectorAttrib
bool hasAnyAttribs() const
Returns true if there are any attributes bound.
double fpreal64
Definition: SYS_Types.h:201
#define UT_ASSERT_MSG(ZZ,...)
Definition: UT_Assert.h:168
GA_Size GA_Offset
Definition: GA_Types.h:653
void rampLookup(fpreal pos, float values[4], int order=0) const
GA_API const UT_StringHolder scale
GLfloat f
Definition: glcorearb.h:1926
GLintptr offset
Definition: glcorearb.h:665
void identity()
Set the matrix to identity.
Definition: UT_Matrix3.h:1128
void clear()
Definition: GA_Handle.h:187
#define TEMPLATE_INST2(T)
#define UT_ASSERT_P(ZZ)
Definition: UT_Assert.h:164
void rotate(UT_Vector3T< S > &axis, T theta, int norm=1)
static UT_Matrix3T< T > dihedral(UT_Vector3T< S > &a, UT_Vector3T< S > &b, UT_Vector3T< S > &c, int norm=1)
SYS_FORCE_INLINE GA_OffsetListRef getPrimitiveVertexList(GA_Offset primoff) const
Definition: GA_Primitive.h:901
void getMatrix(UT_Matrix4 &xform, const UT_Vector3 &P, GA_Offset offset, float default_pscale=1) const
fpreal64 dot(const CE_VectorT< T > &a, const CE_VectorT< T > &b)
Definition: CE_Vector.h:138
void outerproductUpdateT(T b, const UT_Vector3T< S > &v1, const UT_Vector3T< S > &v2)
Definition: UT_Matrix3.h:1431
GLuint GLuint end
Definition: glcorearb.h:475
#define SYS_FORCE_INLINE
Definition: SYS_Inline.h:45
Bezier or NURBS basis classes which maintain knot vectors.
Definition: GA_Basis.h:49
GLdouble GLdouble GLint GLint order
Definition: glad.h:2676
SIM_API const UT_StringHolder rotation
UT_Vector3T< fpreal64 > UT_Vector3D
SYS_FORCE_INLINE T get(GA_Offset off, int comp=0) const
Definition: GA_Handle.h:210
SYS_FORCE_INLINE GA_Offset vertexPoint(GA_Offset vertex) const
Given a vertex, return the point it references.
Definition: GA_Detail.h:538
void identity()
Set the matrix to identity.
Definition: UT_Matrix4.h:1126
GLboolean GLboolean GLboolean b
Definition: glcorearb.h:1222
GA_API const UT_StringHolder transform
bool evaluate(fpreal u, GEO_Vertex result, GEO_AttributeHandleList &gah, int du=0, int uoffset=-1) const
GLdouble t
Definition: glad.h:2397
SYS_FORCE_INLINE bool isValid() const
Definition: GA_Handle.h:194
const GA_Basis * getBasis() const
Definition: GEO_Curve.h:310
bool SYSequalZero(const UT_Vector3T< T > &v)
Definition: UT_Vector3.h:1071
ToType last() const
Return the value of the last element.
void scale(T sx, T sy, T sz, T sw=1)
Definition: UT_Matrix4.h:697
void computeCurveTransforms(const GEO_Detail *const geo, const GA_PrimitiveGroup *curve_group, const GA_RWHandleT< UT_Matrix4T< T >> &transform_attrib, const CurveFrameParms< T > &parms)
GLint j
Definition: glad.h:2733
void assign(T xx=0.0f, T yy=0.0f, T zz=0.0f)
Set the values of the vector components.
Definition: UT_Vector3.h:696
GA_AttributeOwner
Definition: GA_Types.h:35
bool myTransformByInstanceAttribs
Use incoming N, up, rot, orient, pscale, scale, pivot, trans, transform.
GA_API const UT_StringHolder parms
void translate(T dx, T dy, T dz=0)
Definition: UT_Matrix4.h:769
fpreal64 fpreal
Definition: SYS_Types.h:283
constexpr SYS_FORCE_INLINE bool isZero() const noexcept
Definition: UT_Vector3.h:395
bool getPolyProperties(const GEO_Detail *geometry, const GA_OffsetListRef &vertices, exint &nedges, bool &closed, bool &unrolled)
Compute an instance transform given a set of attributes.
FMT_CONSTEXPR basic_fp< F > normalize(basic_fp< F > value)
Definition: format.h:1701
void constant(const T &v)
Quickly set the array to a single value.
fpreal32 SYSrint(const fpreal32 val)
Definition: SYS_Floor.h:164
SYS_FORCE_INLINE GA_AttributeOwner getOwner() const
Definition: GA_Attribute.h:215
#define UT_ASSERT(ZZ)
Definition: UT_Assert.h:165
SYS_FORCE_INLINE UT_StorageMathFloat_t< T > normalize() noexcept
Definition: UT_Vector3.h:378
GLboolean r
Definition: glcorearb.h:1222
#define GA_DETAIL_OFFSET
Definition: GA_Types.h:698
constexpr SYS_FORCE_INLINE T & y() noexcept
Definition: UT_Vector3.h:667
GA_Range getPrimitiveRange(const GA_PrimitiveGroup *group=0) const
Get a range of all primitives in the detail.
Definition: GA_Detail.h:1761
#define SYSmin(a, b)
Definition: SYS_Math.h:1953
SIM_DerVector3 cross(const SIM_DerVector3 &lhs, const SIM_DerVector3 &rhs)
void initialize(const GA_AttributeDict &dict, const UT_StringRef &N_name=GA_Names::N, const UT_StringRef &v_name=GA_Names::v)
RotationPer myIncAnglePer[3]
NOTE: myIncAnglePer[2] will also be used for ensuring closed curve continuity.
SYS_FORCE_INLINE FromType size() const
Returns the number of used elements in the list (always <= capacity())
constexpr SYS_FORCE_INLINE T & x() noexcept
Definition: UT_Vector3.h:665