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 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 bool HasInnerLoops(
110 size_t *firstProtoIndexOut = nullptr) const;
111
112public:
113 // BITFIELDS - note: for enum-typed bitfields, we declare one bit more than
114 // is minimally needed to represent all declared enum values. For example,
115 // TsCurveType has only two values, so it should be representable in one
116 // bit. However, compilers are free to choose the underlying representation
117 // of enums, and some platforms choose signed values, meaning that we
118 // actually need one bit more, so that we can hold the sign bit. We could
119 // declare the enums with unsigned underlying types, but that runs into a
120 // gcc 9.2 bug. We can spare the extra bit; alignment means there is no
121 // difference in struct size.
122
123 // If true, our subtype is authoritative; we know our value type. If false,
124 // then no value type was provided at initialization, and no knots have been
125 // set. In the latter case, we exist only to store overall parameters, and
126 // we have been presumptively created as TypedSplineData<double>.
127 bool isTyped : 1;
128
129 // Whether ApplyOffsetAndScale applies to values also.
130 bool timeValued : 1;
131
132 // Overall spline parameters.
133 TsCurveType curveType : 2;
134 TsExtrapolation preExtrapolation;
135 TsExtrapolation postExtrapolation;
136 TsLoopParams loopParams;
137
138 // A duplicate of the knot times, so that we can maximize locality while
139 // performing binary searches for knots. This is part of the evaluation hot
140 // path; given an eval time, we must find either the knot at that time, or
141 // the knots before and after that time. The entries in this vector
142 // correspond exactly to the entries in the 'knots' vector in
143 // Ts_TypedSplineData. Times are unique and sorted in ascending order.
144 std::vector<TsTime> times;
145
146 // Custom data for knots, sparsely allocated, keyed by time.
147 std::unordered_map<TsTime, VtDictionary> customData;
148};
149
150
151// Concrete subclass of Ts_SplineData. Templated on T, the value type.
152//
153template <typename T>
154struct Ts_TypedSplineData final :
155 public Ts_SplineData
156{
157public:
158 TfType GetValueType() const override;
159 size_t GetKnotStructSize() const override;
160 Ts_SplineData* Clone() const override;
161
162 bool operator==(const Ts_SplineData &other) const override;
163
164 void ReserveForKnotCount(size_t count) override;
165 void PushKnot(
166 const Ts_KnotData *knotData,
167 const VtDictionary &customData) override;
168 // void PushKnot(
169 // const Ts_KnotData *knotData,
170 // const VtDictionary &customData,
171 // const double timeOffset,
172 // const double valueOffset) override;
173 size_t SetKnot(
174 const Ts_KnotData *knotData,
175 const VtDictionary &customData) override;
176
177 // For ease of use while splitting, double knot data to be set
178 // into any type of spline.
179 size_t SetKnotFromDouble(
180 const Ts_TypedKnotData<double>* knotData,
181 const VtDictionary &customData) override;
182
183 Ts_KnotData* CloneKnotAtIndex(size_t index) const override;
184 Ts_KnotData* CloneKnotAtTime(TsTime time) const override;
185 Ts_KnotData* GetKnotPtrAtIndex(size_t index) override;
186 const Ts_KnotData* GetKnotPtrAtIndex(size_t index) const override;
187 Ts_TypedKnotData<double>
188 GetKnotDataAsDouble(size_t index) const override;
189 double GetKnotValueAsDouble(size_t index) const override;
190 double GetKnotPreValueAsDouble(size_t index) const override;
191
192 void ClearKnots() override;
193 void RemoveKnotAtTime(TsTime time) override;
194
195 // Apply offset and scale to all spline data.
196 //
197 // If \p scale is negative, a coding error is generated. This is because
198 // the spline is not only scaled, but also time-reversed. Doing so can
199 // lead to incorrect evaluation results with any scenario where direction
200 // of time is assumed, like dual-value knots, inner looping,
201 // segment interpolation mode assignment, etc.
202 void ApplyOffsetAndScale(
203 TsTime offset,
204 double scale) override;
205
206 bool HasValueBlocks() const override;
207 bool HasValueBlockAtTime(TsTime time) const override;
208
209 bool UpdateKnotTangentsAtIndex(size_t index) override;
210
211public:
212 // Per-knot data.
213 std::vector<Ts_TypedKnotData<T>> knots;
214};
215
216
217// Data-access helpers for the Ts implementation. The untyped functions are
218// friends of TsSpline, and retrieve private data pointers.
219
220Ts_SplineData*
221Ts_GetSplineData(TsSpline &spline);
222
223const Ts_SplineData*
224Ts_GetSplineData(const TsSpline &spline);
225
226template <typename T>
227Ts_TypedSplineData<T>*
228Ts_GetTypedSplineData(TsSpline &spline);
229
230template <typename T>
231const Ts_TypedSplineData<T>*
232Ts_GetTypedSplineData(const TsSpline &spline);
233
234
236// TEMPLATE IMPLEMENTATIONS
237
238template <typename T>
239TfType Ts_TypedSplineData<T>::GetValueType() const
240{
241 if (!isTyped)
242 {
243 return TfType();
244 }
245
246 return Ts_GetType<T>();
247}
248
249template <typename T>
250size_t Ts_TypedSplineData<T>::GetKnotStructSize() const
251{
252 return sizeof(Ts_TypedKnotData<T>);
253}
254
255template <typename T>
256Ts_SplineData*
257Ts_TypedSplineData<T>::Clone() const
258{
259 return new Ts_TypedSplineData<T>(*this);
260}
261
262template <typename T>
263bool Ts_TypedSplineData<T>::operator==(
264 const Ts_SplineData &other) const
265{
266 // Compare non-templated data.
267 if (isTyped != other.isTyped
268 || timeValued != other.timeValued
269 || curveType != other.curveType
270 || preExtrapolation != other.preExtrapolation
271 || postExtrapolation != other.postExtrapolation
272 || loopParams != other.loopParams
273 || customData != other.customData)
274 {
275 return false;
276 }
277
278 // Downcast to our value type. If other is not of the same type, we're not
279 // equal.
280 const Ts_TypedSplineData<T>* const typedOther =
281 dynamic_cast<const Ts_TypedSplineData<T>*>(&other);
282 if (!typedOther)
283 {
284 return false;
285 }
286
287 // Compare all knots.
288 return knots == typedOther->knots;
289}
290
291template <typename T>
292void Ts_TypedSplineData<T>::ReserveForKnotCount(
293 const size_t count)
294{
295 times.reserve(count);
296 knots.reserve(count);
297}
298
299template <typename T>
300void Ts_TypedSplineData<T>::PushKnot(
301 const Ts_KnotData* const knotData,
302 const VtDictionary &customDataIn)
303{
304 const Ts_TypedKnotData<T>* const typedKnotData =
305 static_cast<const Ts_TypedKnotData<T>*>(knotData);
306
307 times.push_back(knotData->time);
308 knots.push_back(*typedKnotData);
309
310 if (!customDataIn.empty())
311 {
312 customData[knotData->time] = customDataIn;
313 }
314}
315
316// template <typename T>
317// void Ts_TypedSplineData<T>::PushKnot(
318// const Ts_KnotData *knotData,
319// const VtDictionary &customDataIn,
320// const double timeOffset,
321// const double valueOffset)
322// {
323// Ts_TypedKnotData<T> typedKnotData(
324// *static_cast<const Ts_TypedKnotData<T>*>(knotData));
325
326// typedKnotData.time += timeOffset;
327// typedKnotData.value += valueOffset;
328// typedKnotData.preValue += valueOffset;
329
330// // Clamp to prevent infinities in types smaller than double (especially
331// // GfHalf).
332// if constexpr(!std::is_same_v<T, double>) {
333// if (typedKnotData.value > std::numeric_limits<T>::max()) {
334// typedKnotData.value = std::numeric_limits<T>::max();
335// } else if (typedKnotData.value < std::numeric_limits<T>::lowest()) {
336// typedKnotData.value = std::numeric_limits<T>::lowest();
337// }
338
339// if (typedKnotData.preValue > std::numeric_limits<T>::max()) {
340// typedKnotData.preValue = std::numeric_limits<T>::max();
341// } else if (typedKnotData.preValue < std::numeric_limits<T>::lowest()) {
342// typedKnotData.preValue = std::numeric_limits<T>::lowest();
343// }
344// }
345
346// times.push_back(typedKnotData.time);
347// knots.push_back(typedKnotData);
348
349// if (!customDataIn.empty())
350// {
351// customData[knotData->time] = customDataIn;
352// }
353// }
354
355template <typename T>
356size_t Ts_TypedSplineData<T>::SetKnot(
357 const Ts_KnotData* const knotData,
358 const VtDictionary &customDataIn)
359{
360 const Ts_TypedKnotData<T>* const typedKnotData =
361 static_cast<const Ts_TypedKnotData<T>*>(knotData);
362
363 // Use binary search to find insert-or-overwrite position.
364 const auto it =
365 std::lower_bound(times.begin(), times.end(), knotData->time);
366 const size_t idx =
367 it - times.begin();
368 const bool overwrite =
369 (it != times.end() && *it == knotData->time);
370
371 // Insert or overwrite new time and knot data.
372 if (overwrite)
373 {
374 times[idx] = knotData->time;
375 knots[idx] = *typedKnotData;
376 }
377 else
378 {
379 times.insert(it, knotData->time);
380 knots.insert(knots.begin() + idx, *typedKnotData);
381 }
382
383 // Store customData, if any.
384 if (!customDataIn.empty())
385 {
386 customData[knotData->time] = customDataIn;
387 }
388
389 return idx;
390}
391
392template <typename T>
393size_t Ts_TypedSplineData<T>::SetKnotFromDouble(
394 const Ts_TypedKnotData<double>* knotData,
395 const VtDictionary &customDataIn)
396{
397 // If we have double data, just set it directly.
398 if constexpr(std::is_same_v<T, double>) {
399 return SetKnot(knotData, customDataIn);
400 }
401
402 Ts_TypedKnotData<T> typedData;
403
404 // Use operator= to copy base-class members. This is admittedly weird, but
405 // it will continue working if members are added to the base class.
406 static_cast<Ts_KnotData&>(typedData) =
407 static_cast<const Ts_KnotData&>(*knotData);
408
409 // We need to copy and convert the data from double to T. We don't want
410 // infinite values, so clamp to the largest possible finite value.
411 auto _Clamp_cast =
412 [](double v) -> T
413 {
414 if (v >= 0) {
415 return std::min(T(v), std::numeric_limits<T>::max());
416 } else {
417 return std::max(T(v), std::numeric_limits<T>::lowest());
418 }
419 };
420
421 // Convert and clamp the value fields.
422 typedData.value = _Clamp_cast(knotData->value);
423 typedData.preValue = _Clamp_cast(knotData->preValue);
424
425 // Slopes are tricky. If they overflow, we need to compute a new slope and a
426 // new width such that the new tangent end-point is as close as possible to
427 // the original tangent end point.
428
429 auto _ConvertTangent =
430 [&_Clamp_cast](double slope, double* width) -> T
431 {
432 T typedSlope = T(slope);
433 // std::isfinite<GfHalf>() is missing so use the helper from
434 // typeHelpers.h instead.
435 if (Ts_IsFinite(typedSlope)) {
436 return typedSlope;
437 }
438
439 // Convert both the slope and width to values that preserve the
440 // endpoint of the tangent as much as possible.
441 double height = *width * slope;
442
443 // typedSlope is infinite, clamp it to a finite value.
444 typedSlope = _Clamp_cast(slope);
445
446 // modify width to preserve the tangent's height.
447 *width = height / typedSlope;
448
449 return typedSlope;
450 };
451
452 typedData.preTanSlope = _ConvertTangent(knotData->preTanSlope,
453 &typedData.preTanWidth);
454 typedData.postTanSlope = _ConvertTangent(knotData->postTanSlope,
455 &typedData.postTanWidth);
456
457 return SetKnot(&typedData, customDataIn);
458}
459
460template <typename T>
461Ts_KnotData*
462Ts_TypedSplineData<T>::CloneKnotAtIndex(
463 const size_t index) const
464{
465 return new Ts_TypedKnotData<T>(knots[index]);
466}
467
468template <typename T>
469Ts_KnotData*
470Ts_TypedSplineData<T>::CloneKnotAtTime(
471 const TsTime time) const
472{
473 const auto it = std::lower_bound(times.begin(), times.end(), time);
474 if (it == times.end() || *it != time)
475 {
476 return nullptr;
477 }
478
479 const auto knotIt = knots.begin() + (it - times.begin());
480 return new Ts_TypedKnotData<T>(*knotIt);
481}
482
483template <typename T>
484Ts_KnotData*
485Ts_TypedSplineData<T>::GetKnotPtrAtIndex(
486 const size_t index)
487{
488 return &(knots[index]);
489}
490
491template <typename T>
492const Ts_KnotData*
493Ts_TypedSplineData<T>::GetKnotPtrAtIndex(
494 const size_t index) const
495{
496 return &(knots[index]);
497}
498
499// Depending on T, this is either a verbatim copy or an increase in precision.
500template <typename T>
501Ts_TypedKnotData<double>
502Ts_TypedSplineData<T>::GetKnotDataAsDouble(
503 const size_t index) const
504{
505 const Ts_TypedKnotData<T> &in = knots[index];
506 Ts_TypedKnotData<double> out;
507
508 // Use operator= to copy base-class members. This is admittedly weird, but
509 // it will continue working if members are added to the base class.
510 static_cast<Ts_KnotData&>(out) = static_cast<const Ts_KnotData&>(in);
511
512 // Copy derived members individually.
513 out.value = in.value;
514 out.preValue = in.preValue;
515 out.preTanSlope = in.preTanSlope;
516 out.postTanSlope = in.postTanSlope;
517
518 return out;
519}
520
521// Depending on T, this is either a verbatim copy or an increase in precision.
522template <typename T>
523double
524Ts_TypedSplineData<T>::GetKnotValueAsDouble(
525 const size_t index) const
526{
527 const Ts_TypedKnotData<T> &typedData = knots[index];
528 return typedData.value;
529}
530
531// Depending on T, this is either a verbatim copy or an increase in precision.
532template <typename T>
533double
534Ts_TypedSplineData<T>::GetKnotPreValueAsDouble(
535 const size_t index) const
536{
537 const Ts_TypedKnotData<T> &typedData = knots[index];
538 return typedData.GetPreValue();
539}
540
541template <typename T>
542void Ts_TypedSplineData<T>::ClearKnots()
543{
544 times.clear();
545 customData.clear();
546 knots.clear();
547}
548
549template <typename T>
550void Ts_TypedSplineData<T>::RemoveKnotAtTime(
551 const TsTime time)
552{
553 const auto it = std::lower_bound(times.begin(), times.end(), time);
554 if (it == times.end() || *it != time)
555 {
556 TF_CODING_ERROR("Cannot remove nonexistent knot from SplineData");
557 return;
558 }
559
560 const size_t idx = it - times.begin();
561 times.erase(it);
562 customData.erase(time);
563 knots.erase(knots.begin() + idx);
564
565 // Update the tangents on the knots either side of the one removed
566 if (idx > 0) {
567 UpdateKnotTangentsAtIndex(idx - 1);
568 }
569 if (idx < times.size()) {
570 UpdateKnotTangentsAtIndex(idx);
571 }
572}
573
574template <typename T>
575static void _ApplyOffsetAndScaleToKnot(
576 Ts_TypedKnotData<T>* const knotData,
577 const TsTime offset,
578 const double scale)
579{
580 // In our private implementation, we must have set a positive scale.
581 TF_VERIFY(scale > 0);
582
583 // Process knot time (absolute).
584 knotData->time = knotData->time * scale + offset;
585
586 // Process tangent widths (relative, strictly positive).
587 knotData->preTanWidth *= scale;
588 knotData->postTanWidth *= scale;
589
590 // Process slopes (inverse relative).
591 knotData->preTanSlope /= scale;
592 knotData->postTanSlope /= scale;
593}
594
595template <typename T>
596void Ts_TypedSplineData<T>::ApplyOffsetAndScale(
597 const TsTime offset,
598 const double scale)
599{
600 if (scale <= 0)
601 {
602 TF_CODING_ERROR("Applying zero or negative scale to spline data, "
603 "collapsing/reversing time and spline representation "
604 "is not allowed.");
605 return;
606 }
607
608 // The spline is changed in the time dimension only.
609 // Different parameters are affected in different ways:
610 // - Absolute times (e.g. knot times): apply scale and offset.
611 // - Relative times (e.g. tan widths): apply scale only.
612 // - Inverse relative (slopes): slope = height/width, so we apply 1/scale.
613
614 // Scale extrapolation slopes if applicable (inverse relative).
615 if (preExtrapolation.mode == TsExtrapSloped)
616 {
617 preExtrapolation.slope /= scale;
618 }
619 if (postExtrapolation.mode == TsExtrapSloped)
620 {
621 postExtrapolation.slope /= scale;
622 }
623
624 // Process inner-loop params.
625 if (loopParams.protoEnd > loopParams.protoStart)
626 {
627 // Process start and end times (absolute).
628 loopParams.protoStart = loopParams.protoStart * scale + offset;
629 loopParams.protoEnd = loopParams.protoEnd * scale + offset;
630 }
631
632 // Process knot-times vector (absolute).
633 for (TsTime &time : times) {
634 time = time * scale + offset;
635 }
636
637 // Process knots. Duplicate the logic that is applied unconditionally, so
638 // that we can rip through the entire vector just once, and we don't have to
639 // do the if-check on each iteration.
640 if (timeValued)
641 {
642 for (Ts_TypedKnotData<T> &knotData : knots)
643 {
644 _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
645
646 // Process time values (absolute).
647 knotData.value =
648 static_cast<T>(knotData.value * scale + offset);
649 knotData.preValue =
650 static_cast<T>(knotData.preValue * scale + offset);
651 }
652 }
653 else
654 {
655 for (Ts_TypedKnotData<T> &knotData : knots) {
656 _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
657 }
658 }
659
660 // Re-index custom data. Times are adjusted absolutely.
661 if (!customData.empty())
662 {
663 std::unordered_map<TsTime, VtDictionary> newCustomData;
664 for (const auto &mapPair : customData) {
665 newCustomData[mapPair.first * scale + offset] = mapPair.second;
666 }
667 customData.swap(newCustomData);
668 }
669}
670
671template <typename T>
672bool Ts_TypedSplineData<T>::HasValueBlocks() const
673{
674 if (knots.empty())
675 {
676 return false;
677 }
678
679 if (preExtrapolation.mode == TsExtrapValueBlock
680 || postExtrapolation.mode == TsExtrapValueBlock)
681 {
682 return true;
683 }
684
685 for (const Ts_TypedKnotData<T> &knotData : knots)
686 {
687 if (knotData.nextInterp == TsInterpValueBlock)
688 {
689 return true;
690 }
691 }
692
693 return false;
694}
695
696template <typename T>
697bool Ts_TypedSplineData<T>::HasValueBlockAtTime(
698 const TsTime time) const
699{
700 // If no knots, no blocks.
701 if (knots.empty())
702 {
703 return false;
704 }
705
706 // Find first knot at or after time.
707 const auto lbIt =
708 std::lower_bound(times.begin(), times.end(), time);
709
710 // If time is after all knots, return whether we have blocked
711 // post-extrapolation.
712 if (lbIt == times.end())
713 {
714 return postExtrapolation.mode == TsExtrapValueBlock;
715 }
716
717 // If there is a knot at this time, return whether its segment has blocked
718 // interpolation.
719 if (*lbIt == time)
720 {
721 const auto knotIt = knots.begin() + (lbIt - times.begin());
722 return knotIt->nextInterp == TsInterpValueBlock;
723 }
724
725 // If time is before all knots, return whether we have blocked
726 // pre-extrapolation.
727 if (lbIt == times.begin())
728 {
729 return preExtrapolation.mode == TsExtrapValueBlock;
730 }
731
732 // Between knots. Return whether the segment that we're in has blocked
733 // interpolation.
734 const auto knotIt = knots.begin() + (lbIt - times.begin());
735 return (knotIt - 1)->nextInterp == TsInterpValueBlock;
736}
737
738template <typename T>
739bool Ts_TypedSplineData<T>::UpdateKnotTangentsAtIndex(size_t index)
740{
741 // XXX: Should we use PXR_PREFER_SAFETY_OVER_SPEED around this test?
742 if (!TF_VERIFY(index < knots.size(),
743 "Knot index (%zd) out of range [0 .. %zd)",
744 index, knots.size()))
745 {
746 return false;
747 }
748
749 Ts_TypedKnotData<T>* prevKnot = (index > 0 ? &knots[index - 1] : nullptr);
750 Ts_TypedKnotData<T>* knot = &knots[index];
751 Ts_TypedKnotData<T>* nextKnot = (index < knots.size() - 1
752 ? &knots[index + 1]
753 : nullptr);
754
755 return knot->UpdateTangents(prevKnot, nextKnot, curveType);
756}
757
758template <typename T>
759Ts_TypedSplineData<T>*
760Ts_GetTypedSplineData(TsSpline &spline)
761{
762 return static_cast<Ts_TypedSplineData<T>*>(
763 Ts_GetSplineData(spline));
764}
765
766template <typename T>
767const Ts_TypedSplineData<T>*
768Ts_GetTypedSplineData(const TsSpline &spline)
769{
770 return static_cast<Ts_TypedSplineData<T>*>(
771 Ts_GetSplineData(spline));
772}
773
774
775PXR_NAMESPACE_CLOSE_SCOPE
776
777#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:220
Inner-loop parameters.
Definition: types.h:193
A mathematical description of a curved function from time to value.
Definition: spline.h:59
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