Loading...
Searching...
No Matches
splineData.h
1//
2// Copyright 2024 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7
8#ifndef PXR_BASE_TS_SPLINE_DATA_H
9#define PXR_BASE_TS_SPLINE_DATA_H
10
11#include "pxr/pxr.h"
12#include "pxr/base/ts/api.h"
13#include "pxr/base/ts/knotData.h"
14#include "pxr/base/ts/types.h"
15#include "pxr/base/ts/typeHelpers.h"
18#include "pxr/base/tf/type.h"
19#include "pxr/base/tf/stl.h"
20
21#include <vector>
22#include <unordered_map>
23#include <algorithm>
24#include <iterator>
25#include <utility>
26#include <cmath>
27
28PXR_NAMESPACE_OPEN_SCOPE
29
30class TsSpline;
31
32
33// Primary data structure for splines. Abstract; subclasses store knot data,
34// which is flexibly typed (double/float/half). This is the unit of data that
35// is managed by shared_ptr, and forms the basis of copy-on-write data sharing.
36//
37struct Ts_SplineData
38{
39public:
40 // If valueType is known, create a TypedSplineData of the specified type.
41 // If valueType is unknown, create a TypedSplineData<double> to store
42 // overall spline parameters in the absence of a value type; this assumes
43 // that when knots arrive, they are most likely to be double-typed. If
44 // overallParamSource is provided, it is a previous overall-only struct, and
45 // our guess about double was wrong, so we are transferring the overall
46 // parameters.
47 static Ts_SplineData* Create(
48 TfType valueType,
49 const Ts_SplineData *overallParamSource = nullptr);
50
51 TS_API virtual ~Ts_SplineData();
52
53public:
54 // Virtual interface for typed data.
55
56 virtual TfType GetValueType() const = 0;
57 virtual size_t GetKnotStructSize() const = 0;
58 virtual Ts_SplineData* Clone() const = 0;
59
60 virtual bool operator==(const Ts_SplineData &other) const = 0;
61
62 virtual void ReserveForKnotCount(size_t count) = 0;
63 virtual void PushKnot(
64 const Ts_KnotData *knotData,
65 const VtDictionary &customData) = 0;
66 // // Overload of PushKnot that offsets the time by timeOffset and values by
67 // // valueOffset. This allows us to unroll knots in loops without having to
68 // // dispatch based on TfType comparisons.
69 // virtual void PushKnot(
70 // const Ts_KnotData *knotData,
71 // const VtDictionary &customData,
72 // const double timeOffset,
73 // const double valueOffset) = 0;
74 virtual size_t SetKnot(
75 const Ts_KnotData *knotData,
76 const VtDictionary &customData) = 0;
77
78 // For ease of use by breakdown, double knot data to be set into any type of
79 // spline.
80 virtual size_t SetKnotFromDouble(
81 const Ts_TypedKnotData<double>* knotData,
82 const VtDictionary &customData) = 0;
83
84 virtual Ts_KnotData* CloneKnotAtIndex(size_t index) const = 0;
85 virtual Ts_KnotData* CloneKnotAtTime(TsTime time) const = 0;
86 virtual Ts_KnotData* GetKnotPtrAtIndex(size_t index) = 0;
87 virtual const Ts_KnotData* GetKnotPtrAtIndex(size_t index) const = 0;
88 virtual Ts_TypedKnotData<double>
89 GetKnotDataAsDouble(size_t index) const = 0;
90 virtual double GetKnotValueAsDouble(size_t index) const = 0;
91 virtual double GetKnotPreValueAsDouble(size_t index) const = 0;
92
93 virtual void ClearKnots() = 0;
94 virtual void RemoveKnotAtTime(TsTime time) = 0;
95
96 virtual void ApplyOffsetAndScale(
97 TsTime offset,
98 double scale) = 0;
99
100 virtual bool HasValueBlocks() const = 0;
101 virtual bool HasValueBlockAtTime(TsTime time) const = 0;
102
103 virtual bool UpdateKnotTangentsAtIndex(size_t index) = 0;
104
105public:
106 // Returns whether there is a valid inner-loop configuration. If
107 // firstProtoIndexOut is provided, it receives the index of the first knot
108 // in the prototype.
109 TS_API
110 bool HasInnerLoops(
111 size_t *firstProtoIndexOut = nullptr) const;
112
113 // Return the time at which pre-extrapolation ends and knot interpolation
114 // begins. Returns 0.0 if there are no knots. It is the caller's
115 // responsibility to ensure that there are knots before relying on the
116 // answer.
117 TsTime GetPreExtrapTime() const;
118
119 // Return the time at which knot interpolation ends and post-extrapolation
120 // begins. Returns 0.0 if there are no knots. It is the caller's
121 // responsibility to ensure that there are knots before relying on the
122 // answer.
123 TsTime GetPostExtrapTime() const;
124
125 // Return the value from which pre-extrapolation extrapolates (as a double).
126 // This accounts for dual valued knots and inner looping. Returns 0.0 if
127 // there are no knots. It is the caller's responsibility to ensure that
128 // there are knots before relying on the answer.
129 double GetPreExtrapValue() const;
130
131 // Return the value from which post-extrapolation extrapolates (as a
132 // double). This accounts for inner looping. Returns 0.0 if there are no
133 // knots. It is the caller's responsibility to ensure that there are knots
134 // before relying on the answer.
135 double GetPostExtrapValue() const;
136
137public:
138 // BITFIELDS - note: for enum-typed bitfields, we declare one bit more than
139 // is minimally needed to represent all declared enum values. For example,
140 // TsCurveType has only two values, so it should be representable in one
141 // bit. However, compilers are free to choose the underlying representation
142 // of enums, and some platforms choose signed values, meaning that we
143 // actually need one bit more, so that we can hold the sign bit. We could
144 // declare the enums with unsigned underlying types, but that runs into a
145 // gcc 9.2 bug. We can spare the extra bit; alignment means there is no
146 // difference in struct size.
147
148 // If true, our subtype is authoritative; we know our value type. If false,
149 // then no value type was provided at initialization, and no knots have been
150 // set. In the latter case, we exist only to store overall parameters, and
151 // we have been presumptively created as TypedSplineData<double>.
152 bool isTyped : 1;
153
154 // Deprecated in favor of valueType for full type specification. This bit
155 // currently exists solely to ensure double values and not GfTimeCode
156 // values are extracted when the legacy TsSpline::SetTimeValued is invoked.
157 bool timeValued : 1;
158
159 // Overall spline parameters.
160 TsCurveType curveType : 2;
161 TfType valueType;
162 TsExtrapolation preExtrapolation;
163 TsExtrapolation postExtrapolation;
164 TsLoopParams loopParams;
165
166 // A duplicate of the knot times, so that we can maximize locality while
167 // performing binary searches for knots. This is part of the evaluation hot
168 // path; given an eval time, we must find either the knot at that time, or
169 // the knots before and after that time. The entries in this vector
170 // correspond exactly to the entries in the 'knots' vector in
171 // Ts_TypedSplineData. Times are unique and sorted in ascending order.
172 std::vector<TsTime> times;
173
174 // Custom data for knots, sparsely allocated, keyed by time.
175 std::unordered_map<TsTime, VtDictionary> customData;
176};
177
178
179// Concrete subclass of Ts_SplineData. Templated on T, the value type.
180//
181template <typename T>
182struct Ts_TypedSplineData final :
183 public Ts_SplineData
184{
185public:
186 TfType GetValueType() const override;
187 size_t GetKnotStructSize() const override;
188 Ts_SplineData* Clone() const override;
189
190 bool operator==(const Ts_SplineData &other) const override;
191
192 void ReserveForKnotCount(size_t count) override;
193 void PushKnot(
194 const Ts_KnotData *knotData,
195 const VtDictionary &customData) override;
196 // void PushKnot(
197 // const Ts_KnotData *knotData,
198 // const VtDictionary &customData,
199 // const double timeOffset,
200 // const double valueOffset) override;
201 size_t SetKnot(
202 const Ts_KnotData *knotData,
203 const VtDictionary &customData) override;
204
205 // For ease of use while splitting, double knot data to be set
206 // into any type of spline.
207 size_t SetKnotFromDouble(
208 const Ts_TypedKnotData<double>* knotData,
209 const VtDictionary &customData) override;
210
211 Ts_KnotData* CloneKnotAtIndex(size_t index) const override;
212 Ts_KnotData* CloneKnotAtTime(TsTime time) const override;
213 Ts_KnotData* GetKnotPtrAtIndex(size_t index) override;
214 const Ts_KnotData* GetKnotPtrAtIndex(size_t index) const override;
215 Ts_TypedKnotData<double>
216 GetKnotDataAsDouble(size_t index) const override;
217 double GetKnotValueAsDouble(size_t index) const override;
218 double GetKnotPreValueAsDouble(size_t index) const override;
219
220 void ClearKnots() override;
221 void RemoveKnotAtTime(TsTime time) override;
222
223 // Apply offset and scale to all spline data.
224 //
225 // If \p scale is negative, a coding error is generated. This is because
226 // the spline is not only scaled, but also time-reversed. Doing so can
227 // lead to incorrect evaluation results with any scenario where direction
228 // of time is assumed, like dual-value knots, inner looping,
229 // segment interpolation mode assignment, etc.
230 void ApplyOffsetAndScale(
231 TsTime offset,
232 double scale) override;
233
234 bool HasValueBlocks() const override;
235 bool HasValueBlockAtTime(TsTime time) const override;
236
237 bool UpdateKnotTangentsAtIndex(size_t index) override;
238
239public:
240 // Per-knot data.
241 std::vector<Ts_TypedKnotData<T>> knots;
242};
243
244
245// Data-access helpers for the Ts implementation. The untyped functions are
246// friends of TsSpline, and retrieve private data pointers.
247
248Ts_SplineData*
249Ts_GetSplineData(TsSpline &spline);
250
251const Ts_SplineData*
252Ts_GetSplineData(const TsSpline &spline);
253
254template <typename T>
255Ts_TypedSplineData<T>*
256Ts_GetTypedSplineData(TsSpline &spline);
257
258template <typename T>
259const Ts_TypedSplineData<T>*
260Ts_GetTypedSplineData(const TsSpline &spline);
261
262
264// TEMPLATE IMPLEMENTATIONS
265
266template <typename T>
267TfType Ts_TypedSplineData<T>::GetValueType() const
268{
269 if (!isTyped)
270 {
271 return TfType();
272 }
273
274 return valueType;
275}
276
277template <typename T>
278size_t Ts_TypedSplineData<T>::GetKnotStructSize() const
279{
280 return sizeof(Ts_TypedKnotData<T>);
281}
282
283template <typename T>
284Ts_SplineData*
285Ts_TypedSplineData<T>::Clone() const
286{
287 return new Ts_TypedSplineData<T>(*this);
288}
289
290template <typename T>
291bool Ts_TypedSplineData<T>::operator==(
292 const Ts_SplineData &other) const
293{
294 // Compare non-templated data.
295 if (isTyped != other.isTyped
296 || ((timeValued || valueType == Ts_GetType<GfTimeCode>())
297 != (other.timeValued ||
298 other.valueType == Ts_GetType<GfTimeCode>()))
299 || curveType != other.curveType
300 || preExtrapolation != other.preExtrapolation
301 || postExtrapolation != other.postExtrapolation
302 || loopParams != other.loopParams
303 || customData != other.customData)
304 {
305 return false;
306 }
307
308 // Downcast to our value type. If other is not of the same type, we're not
309 // equal.
310 const Ts_TypedSplineData<T>* const typedOther =
311 dynamic_cast<const Ts_TypedSplineData<T>*>(&other);
312 if (!typedOther)
313 {
314 return false;
315 }
316
317 // Compare all knots.
318 return knots == typedOther->knots;
319}
320
321template <typename T>
322void Ts_TypedSplineData<T>::ReserveForKnotCount(
323 const size_t count)
324{
325 times.reserve(count);
326 knots.reserve(count);
327}
328
329template <typename T>
330void Ts_TypedSplineData<T>::PushKnot(
331 const Ts_KnotData* const knotData,
332 const VtDictionary &customDataIn)
333{
334 const Ts_TypedKnotData<T>* const typedKnotData =
335 static_cast<const Ts_TypedKnotData<T>*>(knotData);
336
337 times.push_back(knotData->time);
338 knots.push_back(*typedKnotData);
339
340 if (!customDataIn.empty())
341 {
342 customData[knotData->time] = customDataIn;
343 }
344}
345
346// template <typename T>
347// void Ts_TypedSplineData<T>::PushKnot(
348// const Ts_KnotData *knotData,
349// const VtDictionary &customDataIn,
350// const double timeOffset,
351// const double valueOffset)
352// {
353// Ts_TypedKnotData<T> typedKnotData(
354// *static_cast<const Ts_TypedKnotData<T>*>(knotData));
355
356// typedKnotData.time += timeOffset;
357// typedKnotData.value += valueOffset;
358// typedKnotData.preValue += valueOffset;
359
360// // Clamp to prevent infinities in types smaller than double (especially
361// // GfHalf).
362// if constexpr(!std::is_same_v<T, double>) {
363// if (typedKnotData.value > std::numeric_limits<T>::max()) {
364// typedKnotData.value = std::numeric_limits<T>::max();
365// } else if (typedKnotData.value < std::numeric_limits<T>::lowest()) {
366// typedKnotData.value = std::numeric_limits<T>::lowest();
367// }
368
369// if (typedKnotData.preValue > std::numeric_limits<T>::max()) {
370// typedKnotData.preValue = std::numeric_limits<T>::max();
371// } else if (typedKnotData.preValue < std::numeric_limits<T>::lowest()) {
372// typedKnotData.preValue = std::numeric_limits<T>::lowest();
373// }
374// }
375
376// times.push_back(typedKnotData.time);
377// knots.push_back(typedKnotData);
378
379// if (!customDataIn.empty())
380// {
381// customData[knotData->time] = customDataIn;
382// }
383// }
384
385template <typename T>
386size_t Ts_TypedSplineData<T>::SetKnot(
387 const Ts_KnotData* const knotData,
388 const VtDictionary &customDataIn)
389{
390 const Ts_TypedKnotData<T>* const typedKnotData =
391 static_cast<const Ts_TypedKnotData<T>*>(knotData);
392
393 // Use binary search to find insert-or-overwrite position.
394 const auto it =
395 std::lower_bound(times.begin(), times.end(), knotData->time);
396 const size_t idx =
397 it - times.begin();
398 const bool overwrite =
399 (it != times.end() && *it == knotData->time);
400
401 // Insert or overwrite new time and knot data.
402 if (overwrite)
403 {
404 times[idx] = knotData->time;
405 knots[idx] = *typedKnotData;
406 }
407 else
408 {
409 times.insert(it, knotData->time);
410 knots.insert(knots.begin() + idx, *typedKnotData);
411 }
412
413 // Store customData, if any.
414 if (!customDataIn.empty())
415 {
416 customData[knotData->time] = customDataIn;
417 }
418
419 return idx;
420}
421
422template <typename T>
423size_t Ts_TypedSplineData<T>::SetKnotFromDouble(
424 const Ts_TypedKnotData<double>* knotData,
425 const VtDictionary &customDataIn)
426{
427 // If we have double data, just set it directly.
428 if constexpr(std::is_same_v<T, double>) {
429 return SetKnot(knotData, customDataIn);
430 }
431
432 Ts_TypedKnotData<T> typedData;
433
434 // Use operator= to copy base-class members. This is admittedly weird, but
435 // it will continue working if members are added to the base class.
436 static_cast<Ts_KnotData&>(typedData) =
437 static_cast<const Ts_KnotData&>(*knotData);
438
439 // We need to copy and convert the data from double to T. We don't want
440 // infinite values, so clamp to the largest possible finite value.
441 auto _Clamp_cast =
442 [](double v) -> T
443 {
444 if (v >= 0) {
445 return std::min(T(v), std::numeric_limits<T>::max());
446 } else {
447 return std::max(T(v), std::numeric_limits<T>::lowest());
448 }
449 };
450
451 // Convert and clamp the value fields.
452 typedData.value = _Clamp_cast(knotData->value);
453 typedData.preValue = _Clamp_cast(knotData->preValue);
454
455 // Slopes are tricky. If they overflow, we need to compute a new slope and a
456 // new width such that the new tangent end-point is as close as possible to
457 // the original tangent end point.
458
459 auto _ConvertTangent =
460 [&_Clamp_cast](double slope, double* width) -> T
461 {
462 T typedSlope = T(slope);
463 // std::isfinite<GfHalf>() is missing so use the helper from
464 // typeHelpers.h instead.
465 if (Ts_IsFinite(typedSlope)) {
466 return typedSlope;
467 }
468
469 // Convert both the slope and width to values that preserve the
470 // endpoint of the tangent as much as possible.
471 double height = *width * slope;
472
473 // typedSlope is infinite, clamp it to a finite value.
474 typedSlope = _Clamp_cast(slope);
475
476 // modify width to preserve the tangent's height.
477 *width = height / typedSlope;
478
479 return typedSlope;
480 };
481
482 typedData.preTanSlope = _ConvertTangent(knotData->preTanSlope,
483 &typedData.preTanWidth);
484 typedData.postTanSlope = _ConvertTangent(knotData->postTanSlope,
485 &typedData.postTanWidth);
486
487 return SetKnot(&typedData, customDataIn);
488}
489
490template <typename T>
491Ts_KnotData*
492Ts_TypedSplineData<T>::CloneKnotAtIndex(
493 const size_t index) const
494{
495 return new Ts_TypedKnotData<T>(knots[index]);
496}
497
498template <typename T>
499Ts_KnotData*
500Ts_TypedSplineData<T>::CloneKnotAtTime(
501 const TsTime time) const
502{
503 const auto it = std::lower_bound(times.begin(), times.end(), time);
504 if (it == times.end() || *it != time)
505 {
506 return nullptr;
507 }
508
509 const auto knotIt = knots.begin() + (it - times.begin());
510 return new Ts_TypedKnotData<T>(*knotIt);
511}
512
513template <typename T>
514Ts_KnotData*
515Ts_TypedSplineData<T>::GetKnotPtrAtIndex(
516 const size_t index)
517{
518 return &(knots[index]);
519}
520
521template <typename T>
522const Ts_KnotData*
523Ts_TypedSplineData<T>::GetKnotPtrAtIndex(
524 const size_t index) const
525{
526 return &(knots[index]);
527}
528
529// Depending on T, this is either a verbatim copy or an increase in precision.
530template <typename T>
531Ts_TypedKnotData<double>
532Ts_TypedSplineData<T>::GetKnotDataAsDouble(
533 const size_t index) const
534{
535 const Ts_TypedKnotData<T> &in = knots[index];
536 Ts_TypedKnotData<double> out;
537
538 // Use operator= to copy base-class members. This is admittedly weird, but
539 // it will continue working if members are added to the base class.
540 static_cast<Ts_KnotData&>(out) = static_cast<const Ts_KnotData&>(in);
541
542 // Copy derived members individually.
543 out.value = in.value;
544 out.preValue = in.preValue;
545 out.preTanSlope = in.preTanSlope;
546 out.postTanSlope = in.postTanSlope;
547
548 return out;
549}
550
551// Depending on T, this is either a verbatim copy or an increase in precision.
552template <typename T>
553double
554Ts_TypedSplineData<T>::GetKnotValueAsDouble(
555 const size_t index) const
556{
557 const Ts_TypedKnotData<T> &typedData = knots[index];
558 return typedData.value;
559}
560
561// Depending on T, this is either a verbatim copy or an increase in precision.
562template <typename T>
563double
564Ts_TypedSplineData<T>::GetKnotPreValueAsDouble(
565 const size_t index) const
566{
567 const Ts_TypedKnotData<T> &typedData = knots[index];
568 return typedData.GetPreValue();
569}
570
571template <typename T>
572void Ts_TypedSplineData<T>::ClearKnots()
573{
574 times.clear();
575 customData.clear();
576 knots.clear();
577}
578
579template <typename T>
580void Ts_TypedSplineData<T>::RemoveKnotAtTime(
581 const TsTime time)
582{
583 const auto it = std::lower_bound(times.begin(), times.end(), time);
584 if (it == times.end() || *it != time)
585 {
586 TF_CODING_ERROR("Cannot remove nonexistent knot from SplineData");
587 return;
588 }
589
590 const size_t idx = it - times.begin();
591 times.erase(it);
592 customData.erase(time);
593 knots.erase(knots.begin() + idx);
594
595 // Update the tangents on the knots either side of the one removed
596 if (idx > 0) {
597 UpdateKnotTangentsAtIndex(idx - 1);
598 }
599 if (idx < times.size()) {
600 UpdateKnotTangentsAtIndex(idx);
601 }
602}
603
604// Apply offset and scale to knot for knot fields that need to be
605// transformed regardless of whether the spline is time valued.
606template <typename T>
607static void _ApplyOffsetAndScaleToKnot(
608 Ts_TypedKnotData<T>* const knotData,
609 const TsTime offset,
610 const double scale)
611{
612 // Process knot time (absolute).
613 knotData->time = knotData->time * scale + offset;
614
615 // Process tangent widths (relative, strictly positive).
616 knotData->preTanWidth *= fabs(scale);
617 knotData->postTanWidth *= fabs(scale);
618}
619
620template <typename T>
621void Ts_TypedSplineData<T>::ApplyOffsetAndScale(
622 const TsTime offset,
623 const double scale)
624{
625 TF_VERIFY(scale != 0);
626
627 // XXX: Negative scaling doesn't perfectly invert splines. Some
628 // asymmetrical behavior persists. However, inverting a spline
629 // twice will recover the original spline's shape.
630 //
631 // - Evaluating in a held segment always produces the values from
632 // the preceding knot. After negative scaling, the value will
633 // be taken from the originally following knot instead.
634 // - Evaluating exactly at a dual-valued knot produces the ordinary
635 // value, not the pre-value. After negative scaling, the value
636 // will be taken from the original pre-value instead.
637
638 if (scale < 0 && HasInnerLoops())
639 {
640 TF_CODING_ERROR("Negative time scale factor is not compatible "
641 "inner loops. Please first bake inner loops.");
642 return;
643 }
644
645 if (scale < 0) {
646 // Flip pre and post extrapolation.
647 std::swap(preExtrapolation, postExtrapolation);
648 }
649
650 // The spline is changed in the time dimension only.
651 // Different parameters are affected in different ways:
652 // - Absolute times (e.g. knot times): apply scale and offset.
653 // - Relative times (e.g. tan widths): apply scale only.
654 // - Inverse relative (slopes): slope = height/width, so we apply 1/scale.
655 //
656 // Note: Time valued splines don't need slopes scaled, because their
657 // slopes are in units of time value / time value; the scale reduces to 1.
658
659 // Process loop boundary times if they exist.
660 if (preExtrapolation.IsLooping()
661 && preExtrapolation.loopBoundaryTime.has_value())
662 {
663 double& lbt = preExtrapolation.loopBoundaryTime.value();
664 lbt = lbt * scale + offset;
665 }
666 if (postExtrapolation.IsLooping()
667 && postExtrapolation.loopBoundaryTime.has_value())
668 {
669 double& lbt = postExtrapolation.loopBoundaryTime.value();
670 lbt = lbt * scale + offset;
671 }
672
673 // Process inner-loop params.
674 if (loopParams.protoEnd > loopParams.protoStart)
675 {
676 // Process start and end times (absolute).
677 loopParams.protoStart = loopParams.protoStart * scale + offset;
678 loopParams.protoEnd = loopParams.protoEnd * scale + offset;
679 }
680
681 // Process knot-times vector (absolute).
682 for (TsTime &time : times) {
683 time = time * scale + offset;
684 }
685
686 // Reverse knot order if scale is negative. This flips the spline about
687 // time 0. It swaps knots' pre and post values, changes interpolation
688 // knot sources accordingly, and reverses the items in the spline data
689 // `knots` and `times` vectors. This section does *not* scale any knot
690 // fields, including knot time.
691 if (scale < 0) {
692 std::reverse(times.begin(), times.end());
693
694 for (size_t i = 0; i < knots.size(); i++) {
695 size_t idx = knots.size() - 1 - i;
696 Ts_TypedKnotData<T> reversedKnot;
697 const Ts_TypedKnotData<T> knot = knots[idx];
698 if (knot.dualValued) {
699 reversedKnot.value = knot.preValue;
700 reversedKnot.dualValued = true;
701 reversedKnot.preValue = knot.value;
702 } else {
703 reversedKnot.value = knot.value;
704 }
705
706 reversedKnot.time = knot.time;
707 reversedKnot.preTanWidth = knot.postTanWidth;
708 reversedKnot.postTanWidth = knot.preTanWidth;
709 reversedKnot.preTanSlope = knot.postTanSlope;
710 reversedKnot.postTanSlope = knot.preTanSlope;
711 reversedKnot.preTanAlgorithm = knot.postTanAlgorithm;
712 reversedKnot.postTanAlgorithm = knot.preTanAlgorithm;
713
714 if (idx > 0) {
715 reversedKnot.nextInterp = knots[idx - 1].nextInterp;
716 }
717
718 knots[idx] = reversedKnot;
719 }
720 std::reverse(knots.begin(), knots.end());
721 }
722
723 // Scale and offset knot fields. Duplicate the logic that is applied
724 // unconditionally, so that we can rip through the entire vector just
725 // once, and we don't have to do the if-check on each iteration.
726 if (timeValued || valueType == Ts_GetType<GfTimeCode>())
727 {
728 for (Ts_TypedKnotData<T> &knotData : knots)
729 {
730 _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
731
732 // Process time values (absolute).
733 knotData.value =
734 static_cast<T>(knotData.value * scale + offset);
735 knotData.preValue =
736 static_cast<T>(knotData.preValue * scale + offset);
737 }
738 }
739 else
740 {
741 // Note that we scale slopes only for value types that are not
742 // time valued, because the units for time valued slopes are
743 // GfTimeCode over GfTimeCode, which reduces to 1 for any valid scale.
744 for (Ts_TypedKnotData<T> &knotData : knots) {
745 _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
746
747 // Process slopes (inverse relative).
748 knotData.preTanSlope /= scale;
749 knotData.postTanSlope /= scale;
750 }
751
752 // Scale extrapolation slopes if applicable (inverse relative).
753 if (preExtrapolation.mode == TsExtrapSloped)
754 {
755 preExtrapolation.slope /= scale;
756 }
757 if (postExtrapolation.mode == TsExtrapSloped)
758 {
759 postExtrapolation.slope /= scale;
760 }
761 }
762
763 // Re-index custom data. Times are adjusted absolutely.
764 if (!customData.empty())
765 {
766 std::unordered_map<TsTime, VtDictionary> newCustomData;
767 for (const auto &mapPair : customData) {
768 newCustomData[mapPair.first * scale + offset] = mapPair.second;
769 }
770 customData.swap(newCustomData);
771 }
772}
773
774template <typename T>
775bool Ts_TypedSplineData<T>::HasValueBlocks() const
776{
777 if (knots.empty())
778 {
779 return false;
780 }
781
782 if (preExtrapolation.mode == TsExtrapValueBlock
783 || postExtrapolation.mode == TsExtrapValueBlock)
784 {
785 return true;
786 }
787
788 for (const Ts_TypedKnotData<T> &knotData : knots)
789 {
790 if (knotData.nextInterp == TsInterpValueBlock)
791 {
792 return true;
793 }
794 }
795
796 return false;
797}
798
799template <typename T>
800bool Ts_TypedSplineData<T>::HasValueBlockAtTime(
801 const TsTime time) const
802{
803 // If no knots, no blocks.
804 if (knots.empty())
805 {
806 return false;
807 }
808
809 // Find first knot at or after time.
810 const auto lbIt =
811 std::lower_bound(times.begin(), times.end(), time);
812
813 // If time is after all knots, return whether we have blocked
814 // post-extrapolation.
815 if (lbIt == times.end())
816 {
817 return postExtrapolation.mode == TsExtrapValueBlock;
818 }
819
820 // If there is a knot at this time, return whether its segment has blocked
821 // interpolation.
822 if (*lbIt == time)
823 {
824 const auto knotIt = knots.begin() + (lbIt - times.begin());
825 return knotIt->nextInterp == TsInterpValueBlock;
826 }
827
828 // If time is before all knots, return whether we have blocked
829 // pre-extrapolation.
830 if (lbIt == times.begin())
831 {
832 return preExtrapolation.mode == TsExtrapValueBlock;
833 }
834
835 // Between knots. Return whether the segment that we're in has blocked
836 // interpolation.
837 const auto knotIt = knots.begin() + (lbIt - times.begin());
838 return (knotIt - 1)->nextInterp == TsInterpValueBlock;
839}
840
841template <typename T>
842bool Ts_TypedSplineData<T>::UpdateKnotTangentsAtIndex(size_t index)
843{
844 // XXX: Should we use PXR_PREFER_SAFETY_OVER_SPEED around this test?
845 if (!TF_VERIFY(index < knots.size(),
846 "Knot index (%zd) out of range [0 .. %zd)",
847 index, knots.size()))
848 {
849 return false;
850 }
851
852 Ts_TypedKnotData<T>* prevKnot = (index > 0 ? &knots[index - 1] : nullptr);
853 Ts_TypedKnotData<T>* knot = &knots[index];
854 Ts_TypedKnotData<T>* nextKnot = (index < knots.size() - 1
855 ? &knots[index + 1]
856 : nullptr);
857
858 return knot->UpdateTangents(prevKnot, nextKnot, curveType);
859}
860
861template <typename T>
862Ts_TypedSplineData<T>*
863Ts_GetTypedSplineData(TsSpline &spline)
864{
865 return static_cast<Ts_TypedSplineData<T>*>(
866 Ts_GetSplineData(spline));
867}
868
869template <typename T>
870const Ts_TypedSplineData<T>*
871Ts_GetTypedSplineData(const TsSpline &spline)
872{
873 return static_cast<Ts_TypedSplineData<T>*>(
874 Ts_GetSplineData(spline));
875}
876
877
878PXR_NAMESPACE_CLOSE_SCOPE
879
880#endif
Low-level utilities for informing users of various internal and external diagnostic conditions.
TfType represents a dynamic runtime type.
Definition type.h:48
Extrapolation parameters for the ends of a spline beyond the knots.
Definition types.h:231
Inner-loop parameters.
Definition types.h:204
A mathematical description of a curved function from time to value.
Definition spline.h:60
A map with string keys and VtValue values.
Definition dictionary.h:52
VT_API bool empty() const
true if the VtDictionary's size is 0.
#define TF_CODING_ERROR(fmt, args)
Issue an internal programming error, but continue execution.
Definition diagnostic.h:68
#define TF_VERIFY(cond, format,...)
Checks a condition and reports an error if it evaluates false.
Definition diagnostic.h:266