HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
splineData.h
Go to the documentation of this file.
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"
16 #include "pxr/base/vt/dictionary.h"
17 #include "pxr/base/tf/diagnostic.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 
29 
30 class 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 //
38 {
39 public:
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 
53 public:
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  virtual size_t SetKnot(
67  const Ts_KnotData *knotData,
68  const VtDictionary &customData) = 0;
69 
70  virtual Ts_KnotData* CloneKnotAtIndex(size_t index) const = 0;
71  virtual Ts_KnotData* CloneKnotAtTime(TsTime time) const = 0;
72  virtual Ts_KnotData* GetKnotPtrAtIndex(size_t index) = 0;
74  GetKnotDataAsDouble(size_t index) const = 0;
75 
76  virtual void ClearKnots() = 0;
77  virtual void RemoveKnotAtTime(TsTime time) = 0;
78 
79  virtual void ApplyOffsetAndScale(
80  TsTime offset,
81  double scale) = 0;
82 
83  virtual bool HasValueBlocks() const = 0;
84  virtual bool HasValueBlockAtTime(TsTime time) const = 0;
85 
86 public:
87  // Returns whether there is a valid inner-loop configuration. If
88  // firstProtoIndexOut is provided, it receives the index of the first knot
89  // in the prototype.
90  bool HasInnerLoops(
91  size_t *firstProtoIndexOut = nullptr) const;
92 
93 public:
94  // BITFIELDS - note: for enum-typed bitfields, we declare one bit more than
95  // is minimally needed to represent all declared enum values. For example,
96  // TsCurveType has only two values, so it should be representable in one
97  // bit. However, compilers are free to choose the underlying representation
98  // of enums, and some platforms choose signed values, meaning that we
99  // actually need one bit more, so that we can hold the sign bit. We could
100  // declare the enums with unsigned underlying types, but that runs into a
101  // gcc 9.2 bug. We can spare the extra bit; alignment means there is no
102  // difference in struct size.
103 
104  // If true, our subtype is authoritative; we know our value type. If false,
105  // then no value type was provided at initialization, and no knots have been
106  // set. In the latter case, we exist only to store overall parameters, and
107  // we have been presumptively created as TypedSplineData<double>.
108  bool isTyped : 1;
109 
110  // Whether ApplyOffsetAndScale applies to values also.
111  bool timeValued : 1;
112 
113  // Overall spline parameters.
118 
119  // A duplicate of the knot times, so that we can maximize locality while
120  // performing binary searches for knots. This is part of the evaluation hot
121  // path; given an eval time, we must find either the knot at that time, or
122  // the knots before and after that time. The entries in this vector
123  // correspond exactly to the entries in the 'knots' vector in
124  // Ts_TypedSplineData. Times are unique and sorted in ascending order.
125  std::vector<TsTime> times;
126 
127  // Custom data for knots, sparsely allocated, keyed by time.
128  std::unordered_map<TsTime, VtDictionary> customData;
129 };
130 
131 
132 // Concrete subclass of Ts_SplineData. Templated on T, the value type.
133 //
134 template <typename T>
135 struct Ts_TypedSplineData final :
136  public Ts_SplineData
137 {
138 public:
139  TfType GetValueType() const override;
140  size_t GetKnotStructSize() const override;
141  Ts_SplineData* Clone() const override;
142 
143  bool operator==(const Ts_SplineData &other) const override;
144 
145  void ReserveForKnotCount(size_t count) override;
146  void PushKnot(
147  const Ts_KnotData *knotData,
148  const VtDictionary &customData) override;
149  size_t SetKnot(
150  const Ts_KnotData *knotData,
151  const VtDictionary &customData) override;
152 
153  Ts_KnotData* CloneKnotAtIndex(size_t index) const override;
154  Ts_KnotData* CloneKnotAtTime(TsTime time) const override;
155  Ts_KnotData* GetKnotPtrAtIndex(size_t index) override;
157  GetKnotDataAsDouble(size_t index) const override;
158 
159  void ClearKnots() override;
160  void RemoveKnotAtTime(TsTime time) override;
161 
162  // Apply offset and scale to all spline data.
163  //
164  // If \p scale is negative, a coding error is generated. This is because
165  // the spline is not only scaled, but also time-reversed. Doing so can
166  // lead to incorrect evaluation results with any scenario where direction
167  // of time is assumed, like dual-value knots, inner looping,
168  // segment interpolation mode assignment, etc.
169  void ApplyOffsetAndScale(
170  TsTime offset,
171  double scale) override;
172 
173  bool HasValueBlocks() const override;
174  bool HasValueBlockAtTime(TsTime time) const override;
175 
176 public:
177  // Per-knot data.
178  std::vector<Ts_TypedKnotData<T>> knots;
179 };
180 
181 
182 // Data-access helpers for the Ts implementation. The untyped functions are
183 // friends of TsSpline, and retrieve private data pointers.
184 
187 
188 const Ts_SplineData*
190 
191 template <typename T>
194 
195 template <typename T>
198 
199 
200 ////////////////////////////////////////////////////////////////////////////////
201 // TEMPLATE IMPLEMENTATIONS
202 
203 template <typename T>
205 {
206  if (!isTyped)
207  {
208  return TfType();
209  }
210 
211  return Ts_GetType<T>();
212 }
213 
214 template <typename T>
216 {
217  return sizeof(Ts_TypedKnotData<T>);
218 }
219 
220 template <typename T>
223 {
224  return new Ts_TypedSplineData<T>(*this);
225 }
226 
227 template <typename T>
229  const Ts_SplineData &other) const
230 {
231  // Compare non-templated data.
232  if (isTyped != other.isTyped
233  || timeValued != other.timeValued
234  || curveType != other.curveType
235  || preExtrapolation != other.preExtrapolation
236  || postExtrapolation != other.postExtrapolation
237  || loopParams != other.loopParams
238  || customData != other.customData)
239  {
240  return false;
241  }
242 
243  // Downcast to our value type. If other is not of the same type, we're not
244  // equal.
245  const Ts_TypedSplineData<T>* const typedOther =
246  dynamic_cast<const Ts_TypedSplineData<T>*>(&other);
247  if (!typedOther)
248  {
249  return false;
250  }
251 
252  // Compare all knots.
253  return knots == typedOther->knots;
254 }
255 
256 template <typename T>
258  const size_t count)
259 {
260  times.reserve(count);
261  knots.reserve(count);
262 }
263 
264 template <typename T>
266  const Ts_KnotData* const knotData,
267  const VtDictionary &customDataIn)
268 {
269  const Ts_TypedKnotData<T>* const typedKnotData =
270  static_cast<const Ts_TypedKnotData<T>*>(knotData);
271 
272  times.push_back(knotData->time);
273  knots.push_back(*typedKnotData);
274 
275  if (!customDataIn.empty())
276  {
277  customData[knotData->time] = customDataIn;
278  }
279 }
280 
281 template <typename T>
283  const Ts_KnotData* const knotData,
284  const VtDictionary &customDataIn)
285 {
286  const Ts_TypedKnotData<T>* const typedKnotData =
287  static_cast<const Ts_TypedKnotData<T>*>(knotData);
288 
289  // Use binary search to find insert-or-overwrite position.
290  const auto it =
291  std::lower_bound(times.begin(), times.end(), knotData->time);
292  const size_t idx =
293  it - times.begin();
294  const bool overwrite =
295  (it != times.end() && *it == knotData->time);
296 
297  // Insert or overwrite new time and knot data.
298  if (overwrite)
299  {
300  times[idx] = knotData->time;
301  knots[idx] = *typedKnotData;
302  }
303  else
304  {
305  times.insert(it, knotData->time);
306  knots.insert(knots.begin() + idx, *typedKnotData);
307  }
308 
309  // Store customData, if any.
310  if (!customDataIn.empty())
311  {
312  customData[knotData->time] = customDataIn;
313  }
314 
315  return idx;
316 }
317 
318 template <typename T>
321  const size_t index) const
322 {
323  return new Ts_TypedKnotData<T>(knots[index]);
324 }
325 
326 template <typename T>
329  const TsTime time) const
330 {
331  const auto it = std::lower_bound(times.begin(), times.end(), time);
332  if (it == times.end() || *it != time)
333  {
334  return nullptr;
335  }
336 
337  const auto knotIt = knots.begin() + (it - times.begin());
338  return new Ts_TypedKnotData<T>(*knotIt);
339 }
340 
341 template <typename T>
344  const size_t index)
345 {
346  return &(knots[index]);
347 }
348 
349 // Depending on T, this is either a verbatim copy or an increase in precision.
350 template <typename T>
353  const size_t index) const
354 {
355  const Ts_TypedKnotData<T> &in = knots[index];
357 
358  // Use operator= to copy base-class members. This is admittedly weird, but
359  // it will continue working if members are added to the base class.
360  static_cast<Ts_KnotData&>(out) = static_cast<const Ts_KnotData&>(in);
361 
362  // Copy derived members individually.
363  out.value = in.value;
364  out.preValue = in.preValue;
365  out.preTanSlope = in.preTanSlope;
366  out.postTanSlope = in.postTanSlope;
367 
368  return out;
369 }
370 
371 template <typename T>
373 {
374  times.clear();
375  customData.clear();
376  knots.clear();
377 }
378 
379 template <typename T>
381  const TsTime time)
382 {
383  const auto it = std::lower_bound(times.begin(), times.end(), time);
384  if (it == times.end() || *it != time)
385  {
386  TF_CODING_ERROR("Cannot remove nonexistent knot from SplineData");
387  return;
388  }
389 
390  const size_t idx = it - times.begin();
391  times.erase(it);
392  customData.erase(time);
393  knots.erase(knots.begin() + idx);
394 }
395 
396 template <typename T>
397 static void _ApplyOffsetAndScaleToKnot(
398  Ts_TypedKnotData<T>* const knotData,
399  const TsTime offset,
400  const double scale)
401 {
402  // In our private implementation, we must have set a positive scale.
403  TF_VERIFY(scale > 0);
404 
405  // Process knot time (absolute).
406  knotData->time = knotData->time * scale + offset;
407 
408  // Process tangent widths (relative, strictly positive).
409  knotData->preTanWidth *= scale;
410  knotData->postTanWidth *= scale;
411 
412  // Process slopes (inverse relative).
413  knotData->preTanSlope /= scale;
414  knotData->postTanSlope /= scale;
415 }
416 
417 template <typename T>
419  const TsTime offset,
420  const double scale)
421 {
422  if (scale <= 0)
423  {
424  TF_CODING_ERROR("Applying zero or negative scale to spline data, "
425  "collapsing/reversing time and spline representation "
426  "is not allowed.");
427  return;
428  }
429 
430  // The spline is changed in the time dimension only.
431  // Different parameters are affected in different ways:
432  // - Absolute times (e.g. knot times): apply scale and offset.
433  // - Relative times (e.g. tan widths): apply scale only.
434  // - Inverse relative (slopes): slope = height/width, so we apply 1/scale.
435 
436  // Scale extrapolation slopes if applicable (inverse relative).
437  if (preExtrapolation.mode == TsExtrapSloped)
438  {
439  preExtrapolation.slope /= scale;
440  }
441  if (postExtrapolation.mode == TsExtrapSloped)
442  {
443  postExtrapolation.slope /= scale;
444  }
445 
446  // Process inner-loop params.
447  if (loopParams.protoEnd > loopParams.protoStart)
448  {
449  // Process start and end times (absolute).
450  loopParams.protoStart = loopParams.protoStart * scale + offset;
451  loopParams.protoEnd = loopParams.protoEnd * scale + offset;
452  }
453 
454  // Process knot-times vector (absolute).
455  for (TsTime &time : times) {
456  time = time * scale + offset;
457  }
458 
459  // Process knots. Duplicate the logic that is applied unconditionally, so
460  // that we can rip through the entire vector just once, and we don't have to
461  // do the if-check on each iteration.
462  if (timeValued)
463  {
464  for (Ts_TypedKnotData<T> &knotData : knots)
465  {
466  _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
467 
468  // Process time values (absolute).
469  knotData.value =
470  static_cast<T>(knotData.value * scale + offset);
471  knotData.preValue =
472  static_cast<T>(knotData.preValue * scale + offset);
473  }
474  }
475  else
476  {
477  for (Ts_TypedKnotData<T> &knotData : knots) {
478  _ApplyOffsetAndScaleToKnot(&knotData, offset, scale);
479  }
480  }
481 
482  // Re-index custom data. Times are adjusted absolutely.
483  if (!customData.empty())
484  {
485  std::unordered_map<TsTime, VtDictionary> newCustomData;
486  for (const auto &mapPair : customData) {
487  newCustomData[mapPair.first * scale + offset] = mapPair.second;
488  }
489  customData.swap(newCustomData);
490  }
491 }
492 
493 template <typename T>
495 {
496  if (knots.empty())
497  {
498  return false;
499  }
500 
501  if (preExtrapolation.mode == TsExtrapValueBlock
502  || postExtrapolation.mode == TsExtrapValueBlock)
503  {
504  return true;
505  }
506 
507  for (const Ts_TypedKnotData<T> &knotData : knots)
508  {
509  if (knotData.nextInterp == TsInterpValueBlock)
510  {
511  return true;
512  }
513  }
514 
515  return false;
516 }
517 
518 template <typename T>
520  const TsTime time) const
521 {
522  // If no knots, no blocks.
523  if (knots.empty())
524  {
525  return false;
526  }
527 
528  // Find first knot at or after time.
529  const auto lbIt =
530  std::lower_bound(times.begin(), times.end(), time);
531 
532  // If time is after all knots, return whether we have blocked
533  // post-extrapolation.
534  if (lbIt == times.end())
535  {
536  return postExtrapolation.mode == TsExtrapValueBlock;
537  }
538 
539  // If there is a knot at this time, return whether its segment has blocked
540  // interpolation.
541  if (*lbIt == time)
542  {
543  const auto knotIt = knots.begin() + (lbIt - times.begin());
544  return knotIt->nextInterp == TsInterpValueBlock;
545  }
546 
547  // If time is before all knots, return whether we have blocked
548  // pre-extrapolation.
549  if (lbIt == times.begin())
550  {
551  return preExtrapolation.mode == TsExtrapValueBlock;
552  }
553 
554  // Between knots. Return whether the segment that we're in has blocked
555  // interpolation.
556  const auto knotIt = knots.begin() + (lbIt - times.begin());
557  return (knotIt - 1)->nextInterp == TsInterpValueBlock;
558 }
559 
560 template <typename T>
563 {
564  return static_cast<Ts_TypedSplineData<T>*>(
565  Ts_GetSplineData(spline));
566 }
567 
568 template <typename T>
571 {
572  return static_cast<Ts_TypedSplineData<T>*>(
573  Ts_GetSplineData(spline));
574 }
575 
576 
578 
579 #endif
void PushKnot(const Ts_KnotData *knotData, const VtDictionary &customData) override
Definition: splineData.h:265
std::vector< Ts_TypedKnotData< T > > knots
Definition: splineData.h:178
std::unordered_map< TsTime, VtDictionary > customData
Definition: splineData.h:128
Ts_KnotData * CloneKnotAtIndex(size_t index) const override
Definition: splineData.h:320
bool operator==(const Ts_SplineData &other) const override
Definition: splineData.h:228
virtual Ts_KnotData * CloneKnotAtIndex(size_t index) const =0
GT_API const UT_StringHolder time
virtual Ts_SplineData * Clone() const =0
virtual bool operator==(const Ts_SplineData &other) const =0
bool HasValueBlockAtTime(TsTime time) const override
Definition: splineData.h:519
#define TF_CODING_ERROR
TsTime preTanWidth
Definition: knotData.h:106
void RemoveKnotAtTime(TsTime time) override
Definition: splineData.h:380
virtual bool HasValueBlocks() const =0
virtual void ClearKnots()=0
IMATH_HOSTDEVICE IMATH_CONSTEXPR14 Quat< T > spline(const Quat< T > &q0, const Quat< T > &q1, const Quat< T > &q2, const Quat< T > &q3, T t) IMATH_NOEXCEPT
Definition: ImathQuat.h:576
TsExtrapolation postExtrapolation
Definition: splineData.h:116
bool HasInnerLoops(size_t *firstProtoIndexOut=nullptr) const
TsCurveType curveType
Definition: splineData.h:114
constexpr auto in(type t, int set) -> bool
Definition: core.h:611
std::vector< TsTime > times
Definition: splineData.h:125
GA_API const UT_StringHolder scale
virtual size_t SetKnot(const Ts_KnotData *knotData, const VtDictionary &customData)=0
GLintptr offset
Definition: glcorearb.h:665
TsExtrapolation preExtrapolation
Definition: splineData.h:115
virtual Ts_KnotData * GetKnotPtrAtIndex(size_t index)=0
size_t GetKnotStructSize() const override
Definition: splineData.h:215
virtual bool HasValueBlockAtTime(TsTime time) const =0
TsCurveType
Definition: types.h:92
virtual ~Ts_SplineData()
virtual Ts_KnotData * CloneKnotAtTime(TsTime time) const =0
Ts_SplineData * Clone() const override
Definition: splineData.h:222
TfType GetValueType() const override
Definition: splineData.h:204
Ts_TypedKnotData< double > GetKnotDataAsDouble(size_t index) const override
Definition: splineData.h:352
Ts_KnotData * CloneKnotAtTime(TsTime time) const override
Definition: splineData.h:328
VT_API bool empty() const
true if the VtDictionary's size is 0.
void ClearKnots() override
Definition: splineData.h:372
Ts_TypedSplineData< T > * Ts_GetTypedSplineData(TsSpline &spline)
Definition: splineData.h:562
void ReserveForKnotCount(size_t count) override
Definition: splineData.h:257
size_t SetKnot(const Ts_KnotData *knotData, const VtDictionary &customData) override
Definition: splineData.h:282
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
virtual Ts_TypedKnotData< double > GetKnotDataAsDouble(size_t index) const =0
TsLoopParams loopParams
Definition: splineData.h:117
virtual void PushKnot(const Ts_KnotData *knotData, const VtDictionary &customData)=0
GLuint index
Definition: glcorearb.h:786
virtual TfType GetValueType() const =0
bool HasValueBlocks() const override
Definition: splineData.h:494
TsTime time
Definition: knotData.h:102
virtual void ApplyOffsetAndScale(TsTime offset, double scale)=0
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
void ApplyOffsetAndScale(TsTime offset, double scale) override
Definition: splineData.h:418
Definition: type.h:47
Ts_KnotData * GetKnotPtrAtIndex(size_t index) override
Definition: splineData.h:343
static Ts_SplineData * Create(TfType valueType, const Ts_SplineData *overallParamSource=nullptr)
virtual void RemoveKnotAtTime(TsTime time)=0
virtual void ReserveForKnotCount(size_t count)=0
TsTime postTanWidth
Definition: knotData.h:110
Ts_SplineData * Ts_GetSplineData(TsSpline &spline)
virtual size_t GetKnotStructSize() const =0
GLint GLsizei count
Definition: glcorearb.h:405