All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
knotData.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_KNOT_DATA_H
9#define PXR_BASE_TS_KNOT_DATA_H
10
11#include "pxr/pxr.h"
12#include "pxr/base/ts/api.h"
13#include "pxr/base/ts/types.h"
14#include "pxr/base/ts/typeHelpers.h"
15#include "pxr/base/vt/value.h"
16#include "pxr/base/tf/type.h"
17
18#include <memory>
19#include <cstring>
20
21PXR_NAMESPACE_OPEN_SCOPE
22
23
24// Ts API objects (Spline, Knot) are non-templated, but they can represent
25// different value types (double, float, half), and internally we handle those
26// different value types with templates. Some knot members (like time) are
27// type-independent, while others (like value) are type-dependent. All knots in
28// a spline have the same value type.
29//
30// Splines can have many knots, and we try to take up as little memory as
31// possible in storing them. We also try to be as fast as possible in accessing
32// them, but the combination of non-templated API classes and templated data is
33// a form of type erasure that requires a compromise: we use virtual methods to
34// retrieve data.
35//
36// We manage knot data with two class hierarchies:
37//
38// - The data itself is stored as a plain struct. There are two halves: a
39// non-templated base struct that contains the type-independent members, and a
40// templated derived struct that contains the type-dependent members. By
41// making the data a plain struct, we avoid storing a vtable pointer for every
42// instance, but in return we have no convenient way to get at the templated
43// data directly.
44//
45// - We access the type-dependent members using a proxy class. There is an
46// abstract base class that declares a virtual interface, and a templated
47// derived class that implements it. Proxy objects have a vtable pointer, and
48// contain a pointer to a templated derived data struct.
49
50
51// XXX TODO
52// review access patterns - do we need all?:
53// SplineData -> AsDouble -> direct access
54// SplineData virtuals
55// Ts_Get[Typed]SplineData
56// Knot -> proxy
57// Knot -> _TypedData -> direct access
58// TsDispatchToValueTypeTemplate
59
60
61// Non-template base class for knot data.
62//
63struct Ts_KnotData
64{
65public:
66 // Typically used by Create(), but can be invoked directly for clients that
67 // don't care about the value dimension, and instantiate this struct without
68 // subclassing.
69 Ts_KnotData();
70
71 // Creates an appropriately subtyped instance on the heap.
72 static Ts_KnotData* Create(TfType valueType);
73
74 // Compares two KnotData structs. Ignores subclasses.
75 bool operator==(const Ts_KnotData &other) const;
76
77public:
78 // Helpers that switch on flags.
79
80 TsTime GetPreTanWidth() const
81 {
82 return preTanWidth;
83 }
84
85 TsTime GetPostTanWidth() const
86 {
87 return postTanWidth;
88 }
89
90 void SetPreTanWidth(const TsTime width)
91 {
92 preTanWidth = width;
93 }
94
95 void SetPostTanWidth(const TsTime width)
96 {
97 postTanWidth = width;
98 }
99
100public:
101 // Knot time.
102 TsTime time;
103
104 // Time width of the pre-tangent. Always non-negative. Ignored for Hermite
105 // knots.
106 TsTime preTanWidth;
107
108 // Time width of the post-tangent. Always non-negative. Ignored for
109 // Hermite knots.
110 TsTime postTanWidth;
111
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 // Interpolation mode for the segment following this knot.
123 TsInterpMode nextInterp : 3;
124
125 // The spline type this knot belongs to, or is intended for.
126 TsCurveType curveType : 2;
127
128 // Whether this knot is dual-valued (value discontinuity at the knot).
129 bool dualValued : 1;
130};
131
132
133// Data for one knot in a spline.
134//
135// Tangents are expressed as width and slope.
136//
137template <typename T>
138struct Ts_TypedKnotData :
139 public Ts_KnotData
140{
141public:
142 Ts_TypedKnotData();
143
144 bool operator==(const Ts_TypedKnotData<T> &other) const;
145
146public:
147 // Helpers that switch on flags.
148 T GetPreValue() const;
149 T GetPreTanSlope() const;
150 T GetPreTanHeight() const;
151 T GetPostTanSlope() const;
152 T GetPostTanHeight() const;
153
154public:
155 // Value at this knot.
156 T value;
157
158 // If dual-valued, the pre-value at this knot.
159 T preValue;
160
161 // preTanSlope stores the slope of the pre-tangent, rise over run, value
162 // height divided by time width.
163 T preTanSlope;
164
165 // postTanSlope stores the slope of the post-tangent, rise over run, value
166 // height divided by time width.
167 T postTanSlope;
168};
169
170// For double-typed values, on x86-64, this struct should fit in a cache line.
171// Exceeding this size may impact performance.
172static_assert(sizeof(Ts_TypedKnotData<double>) <= 64);
173
174
175// Virtual interface to TypedKnotData.
176//
177// VtValue parameters are not type-checked. They are blindly cast. Callers
178// must verify types.
179//
180class Ts_KnotDataProxy
181{
182public:
183 // Creates an appropriately subtyped instance.
184 static std::unique_ptr<Ts_KnotDataProxy>
185 Create(Ts_KnotData *data, TfType valueType);
186
187 virtual ~Ts_KnotDataProxy();
188
189 virtual Ts_KnotData* CloneData() const = 0;
190 virtual void DeleteData() = 0;
191
192 virtual TfType GetValueType() const = 0;
193 virtual bool IsDataEqualTo(const Ts_KnotData &other) const = 0;
194
195 virtual void SetValue(VtValue value) = 0;
196 virtual void GetValue(VtValue *valueOut) const = 0;
197 virtual void SetPreValue(VtValue value) = 0;
198 virtual void GetPreValue(VtValue *valueOut) const = 0;
199
200 virtual void SetPreTanSlope(VtValue slope) = 0;
201 virtual void GetPreTanSlope(VtValue *slopeOut) const = 0;
202 virtual void SetPostTanSlope(VtValue slope) = 0;
203 virtual void GetPostTanSlope(VtValue *slopeOut) const = 0;
204};
205
206
207// A means of accessing TypedKnotData.
208//
209template <typename T>
210class Ts_TypedKnotDataProxy final :
211 public Ts_KnotDataProxy
212{
213public:
214 explicit Ts_TypedKnotDataProxy(Ts_TypedKnotData<T> *data);
215
216 Ts_KnotData* CloneData() const override;
217 void DeleteData() override;
218
219 TfType GetValueType() const override;
220 bool IsDataEqualTo(const Ts_KnotData &other) const override;
221
222 void SetValue(VtValue value) override;
223 void GetValue(VtValue *valueOut) const override;
224 void SetPreValue(VtValue value) override;
225 void GetPreValue(VtValue *valueOut) const override;
226
227 void SetPreTanSlope(VtValue slope) override;
228 void GetPreTanSlope(VtValue *slopeOut) const override;
229 void SetPostTanSlope(VtValue slope) override;
230 void GetPostTanSlope(VtValue *slopeOut) const override;
231
232private:
233 Ts_TypedKnotData<T> *_data;
234};
235
236
238// TEMPLATE IMPLEMENTATIONS
239
240template <typename T>
241Ts_TypedKnotData<T>::Ts_TypedKnotData()
242 : Ts_KnotData(),
243 value(T()),
244 preValue(T()),
245 preTanSlope(T()),
246 postTanSlope(T())
247{
248}
249
250#define COMP(member) \
251 if (member != other.member) \
252 { \
253 return false; \
254 }
255
256template <typename T>
257bool Ts_TypedKnotData<T>::operator==(
258 const Ts_TypedKnotData<T> &other) const
259{
260 COMP(time);
261 COMP(preTanWidth);
262 COMP(postTanWidth);
263 COMP(dualValued);
264 COMP(nextInterp);
265 COMP(curveType);
266
267 COMP(value);
268 COMP(preValue);
269 COMP(preTanSlope);
270 COMP(postTanSlope);
271
272 return true;
273}
274
275#undef COMP
276
277template <typename T>
278T Ts_TypedKnotData<T>::GetPreValue() const
279{
280 return (dualValued ? preValue : value);
281}
282
283template <typename T>
284T Ts_TypedKnotData<T>::GetPreTanSlope() const
285{
286 return preTanSlope;
287}
288
289template <typename T>
290T Ts_TypedKnotData<T>::GetPreTanHeight() const
291{
292 return -preTanWidth * preTanSlope;
293}
294
295template <typename T>
296T Ts_TypedKnotData<T>::GetPostTanSlope() const
297{
298 return postTanSlope;
299}
300
301template <typename T>
302T Ts_TypedKnotData<T>::GetPostTanHeight() const
303{
304 return postTanWidth * postTanSlope;
305}
306
308// TypedKnotDataProxy
309
310template <typename T>
311Ts_TypedKnotDataProxy<T>::Ts_TypedKnotDataProxy(
312 Ts_TypedKnotData<T> *data)
313 : _data(data)
314{
315}
316
317template <typename T>
318Ts_KnotData* Ts_TypedKnotDataProxy<T>::CloneData() const
319{
320 return new Ts_TypedKnotData<T>(*_data);
321}
322
323template <typename T>
324void Ts_TypedKnotDataProxy<T>::DeleteData()
325{
326 delete _data;
327}
328
329template <typename T>
330TfType Ts_TypedKnotDataProxy<T>::GetValueType() const
331{
332 return Ts_GetType<T>();
333}
334
335template <typename T>
336bool Ts_TypedKnotDataProxy<T>::IsDataEqualTo(const Ts_KnotData &other) const
337{
338 // Force-downcast to our value type. Callers must verify types match.
339 const Ts_TypedKnotData<T> *typedOther =
340 static_cast<const Ts_TypedKnotData<T>*>(&other);
341
342 return *_data == *typedOther;
343}
344
345template <typename T>
346void Ts_TypedKnotDataProxy<T>::SetValue(
347 const VtValue value)
348{
349 _data->value = value.UncheckedGet<T>();
350}
351
352template <typename T>
353void Ts_TypedKnotDataProxy<T>::GetValue(
354 VtValue* const valueOut) const
355{
356 *valueOut = VtValue(_data->value);
357}
358
359template <typename T>
360void Ts_TypedKnotDataProxy<T>::SetPreValue(
361 const VtValue value)
362{
363 _data->preValue = value.UncheckedGet<T>();
364}
365
366template <typename T>
367void Ts_TypedKnotDataProxy<T>::GetPreValue(
368 VtValue* const valueOut) const
369{
370 *valueOut = VtValue(_data->preValue);
371}
372
373template <typename T>
374void Ts_TypedKnotDataProxy<T>::SetPreTanSlope(
375 const VtValue slope)
376{
377 _data->preTanSlope = slope.UncheckedGet<T>();
378}
379
380template <typename T>
381void Ts_TypedKnotDataProxy<T>::GetPreTanSlope(
382 VtValue* const slopeOut) const
383{
384 *slopeOut = VtValue(_data->GetPreTanSlope());
385}
386
387template <typename T>
388void Ts_TypedKnotDataProxy<T>::SetPostTanSlope(
389 const VtValue slope)
390{
391 _data->postTanSlope = slope.UncheckedGet<T>();
392}
393
394template <typename T>
395void Ts_TypedKnotDataProxy<T>::GetPostTanSlope(
396 VtValue* const slopeOut) const
397{
398 *slopeOut = VtValue(_data->GetPostTanSlope());
399}
400
401
402PXR_NAMESPACE_CLOSE_SCOPE
403
404#endif
TfType represents a dynamic runtime type.
Definition: type.h:48
Provides a container which may hold any type, and provides introspection and iteration over array typ...
Definition: value.h:147
T const & UncheckedGet() const &
Returns a const reference to the held object if the held object is of type T.
Definition: value.h:1104