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