HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
regressionPreventer.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_REGRESSION_PREVENTER_H
9 #define PXR_BASE_TS_REGRESSION_PREVENTER_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/knot.h"
15 #include "pxr/base/ts/knotData.h"
16 #include "pxr/base/ts/segment.h"
17 
18 #include <optional>
19 #include <string>
20 
22 
23 class TsSpline;
24 
25 
26 /// An authoring helper class that enforces non-regression in splines.
27 ///
28 /// See \ref page_ts_regression for a general introduction to regression and
29 /// anti-regression.
30 ///
31 /// Construct an instance of this class when a knot is being interactively
32 /// edited. Call Set for each change.
33 ///
34 /// \bug This class does not yet work correctly with inner loops (TsLoopParams).
35 ///
37 {
38 public:
39  /// Anti-regression modes that are specific to interactive usage. These are
40  /// similar to the modes in \ref TsAntiRegressionMode, except the
41  /// interactive modes differentiate between the 'active' and 'opposite'
42  /// knots in each segment, favoring one or the other of them. The 'active'
43  /// knot is the one that is being edited in an interactive case. Batch
44  /// cases can't use these modes because we are adjusting an existing spline,
45  /// rather than editing a single knot.
46  enum TS_API InteractiveMode
47  {
48  /// Shorten the proposed tangents of the active knot so that there is no
49  /// regression, leaving the neighbor tangents alone.
51 
52  /// When the oppposite tangent is > 1/3 of the interval, shorten it
53  /// until non-regression is achieved or the opposite tangent reaches
54  /// 1/3; then cap the active tangent at 4/3.
55  ///
56  /// When the opposite tangent is < 1/3 of the interval, just limit the
57  /// active tangent. This avoids the counter-intuitive result of
58  /// lengthening the oposite tangent.
59  ModeLimitOpposite
60  };
61 
62  /// Details of the result of an interactive Set call.
63  class SetResult
64  {
65  public:
66  /// Whether any adjustments were made.
67  bool adjusted = false;
68 
69  /// If there is a pre-segment, what adjustments were made to it.
70  bool havePreSegment = false;
71  bool preActiveAdjusted = false;
73  bool preOppositeAdjusted = false;
75 
76  /// If there is a post-segment, what adjustments were made to it.
77  bool havePostSegment = false;
78  bool postActiveAdjusted = false;
80  bool postOppositeAdjusted = false;
82 
83  public:
84  TS_API
85  std::string GetDebugDescription(int precision = 6) const;
86  };
87 
88 public:
89  /// Constructor for interactive use (repeated calls to Set). The mode will
90  /// be determined by the value of TsSpline::GetAntiRegressionAuthoringMode()
91  /// at the time of construction. If 'limit' is true, adjustments will be
92  /// enforced before knots are written to the spline. Otherwise, knots will
93  /// be written without adjustment, but the SetResult will describe the
94  /// adjustments that would be made. The spline must remain valid for the
95  /// lifetime of this object.
96  TS_API
99  TsTime activeKnotTime,
100  bool limit = true);
101 
102  /// Same as the above, but with an InteractiveMode. This form ignores
103  /// GetAntiRegressionAuthoringMode(), because interactive modes can't be
104  /// specified through that mechanism, since they apply only to
105  /// RegressionPreventer.
106  TS_API
108  TsSpline *spline,
109  TsTime activeKnotTime,
110  InteractiveMode mode,
111  bool limit = true);
112 
113  /// Set an edited version of the active knot into the spline, adjusting
114  /// tangent widths if needed, based on the mode. Any aspect of the active
115  /// knot may be changed; the aspects that affect regression are knot time
116  /// and tangent widths. Returns true on success, false on failure.
117  ///
118  /// If this is the first call to Set, and the spline was initially
119  /// regressive, the opposite tangent may be shortened, in a way that isn't
120  /// required when the spline starts out non-regressive. In Contain mode,
121  /// this initial anti-regression will limit the opposite tangent following
122  /// the usual Contain rules. In any other mode, initial anti-regression
123  /// will behave as though Limit Opposite were in effect: the opposite
124  /// tangent will be shortened so that the spline is not regressive given the
125  /// initial active knot, or to 1/3 of the interval if the active tangent is
126  /// longer than 4/3 of the interval.
127  ///
128  /// When knot time is changed, the tangent widths in the altered segments on
129  /// either side are adjusted to prevent regression. If knot time is changed
130  /// to match another existing knot, the prior knot is removed, and the
131  /// active knot substituted for it; this is undone if the time is again
132  /// changed. If knot time changes enough to alter the sort order of knots
133  /// in the spline, the active knot's neighbor knots will be recomputed for
134  /// the new insert point, and the resulting new segments will be adjusted to
135  /// prevent regression as needed.
136  ///
137  /// When a loop-prototype knot is being edited, the spline's loop parameters
138  /// may fall out of sync if the knot time is changed. This can include the
139  /// knot drifting out of the prototype interval and becoming hidden; it can
140  /// also include the prototype interval bounds failing to track the first or
141  /// last prototype knot as it moves. Clients should make policy as to how
142  /// this situation should be handled. If loop parameters are going to be
143  /// updated to match moved knots, that edit should be done before calling
144  /// Set.
145  ///
146  TS_API
147  bool Set(
148  const TsKnot &proposedActiveKnot,
149  SetResult *resultOut = nullptr);
150 
151 private:
153 
154  // Unified enum for both interactive and batch use.
155  enum _Mode
156  {
157  _ModeNone = TsAntiRegressionNone,
158  _ModeContain = TsAntiRegressionContain,
159  _ModeKeepRatio = TsAntiRegressionKeepRatio,
160  _ModeKeepStart = TsAntiRegressionKeepStart,
161  _ModeLimitActive = ModeLimitActive,
162  _ModeLimitOpposite = ModeLimitOpposite
163  };
164 
165  // Private constructor to which the public constructors delegate.
167  TsSpline *spline,
168  TsTime activeKnotTime,
169  _Mode mode,
170  bool limit);
171 
172  // PERFORMANCE NOTE: this class would probably be faster if it dealt
173  // directly with Ts_SplineData and Ts_KnotData, rather than going through
174  // TsSpline and TsKnot.
175 
176  // Knot state stored for the lifetime of an interactive Preventer. Tracks
177  // the original knot from construction time, and the current time parameters
178  // in the spline.
179  struct _KnotState
180  {
181  public:
182  // Uses the original value for both 'original' and 'current'.
183  _KnotState(
184  TsSpline *spline,
185  const TsKnot &originalKnot);
186 
187  // Write the original back to the spline, undoing any prior writes.
188  void RestoreOriginal();
189 
190  // Remove the knot from the spline. This is needed when knot time is
191  // changing.
192  void RemoveCurrent();
193 
194  // Write a new version of the knot, and record it as 'current'.
195  void Write(
196  const TsKnot &newKnot);
197 
198  public:
199  // The spline, so we can write into it.
200  TsSpline* const spline;
201 
202  // Original knot.
203  const TsKnot originalKnot;
204 
205  // Current time parameters, possibly modified from original.
206  Ts_KnotData currentParams;
207  };
208 
209  // Knot state used for the duration of a single Preventer iteration (Set or
210  // _ProcessSegment). Tracks the proposed new knot, and a potentially
211  // adjusted working version of the time parameters.
212  struct _WorkingKnotState
213  {
214  public:
215  // Uses the proposed value for 'proposed' and 'working'. This is for
216  // interactive use with active knots, for which a proposed new value is
217  // given as input.
218  _WorkingKnotState(
219  _KnotState *parentState,
220  const TsKnot &proposedKnot);
221 
222  // Uses the parent's original for 'proposed' and 'working'. This is for
223  // interactive use with opposite knots, which always start out proposed
224  // as the original knots.
225  _WorkingKnotState(
226  _KnotState *parentState);
227 
228  // For batch use. Stores only the original time parameters, which are
229  // used as the proposed parameters. Has no parent state, and cannot be
230  // used to write to the spline. The only output is 'working'.
231  _WorkingKnotState(
232  const Ts_KnotData &originalParams);
233 
234  // Write the proposed value to the spline, without adjustment. Update
235  // the parent state's 'current'.
236  void WriteProposed();
237 
238  // Write the possibly adjusted value to the spline. Update the parent
239  // state's 'current'.
240  void WriteWorking();
241 
242  public:
243  // Link to whole-operation state.
244  _KnotState* const parentState;
245 
246  // The proposed knot, if one was provided.
247  const TsKnot proposedKnot;
248 
249  // The proposed time parameters.
250  const Ts_KnotData proposedParams;
251 
252  // Copy of time parameters that we are modifying.
253  Ts_KnotData workingParams;
254  };
255 
256  // Encapsulates the core math, and the details specific to whether we're
257  // operating on a pre-segment (the one before the active knot) or a
258  // post-segment (the one after).
259  class _SegmentSolver
260  {
261  public:
262  enum WhichSegment
263  {
264  PreSegment,
265  PostSegment
266  };
267 
268  _SegmentSolver(
269  WhichSegment whichSegment,
270  _Mode mode,
271  _WorkingKnotState* activeKnotState,
272  _WorkingKnotState* oppositeKnotState,
273  SetResult* result);
274 
275  _SegmentSolver(
276  _Mode mode,
277  Ts_Segment* segment,
278  SetResult* result);
279 
280  // If adjustments are needed, update activeKnotState->working,
281  // oppositeKnotState->working, and *result. Does not immediately write
282  // to the spline. Returns true on success, false on failure.
283  bool Adjust();
284 
285  private:
286  // Mode kernels.
287  bool _AdjustWithContain();
288  bool _AdjustWithKeepRatio();
289  bool _AdjustWithKeepStart();
290  bool _AdjustWithLimitActive();
291  bool _AdjustWithLimitOpposite();
292 
293  // Accessors and mutators for the active and opposite tangent widths.
294  // The widths passed and returned here are always normalized to the
295  // [0, 1] segment time interval.
296  TsTime _GetProposedActiveWidth() const;
297  TsTime _GetProposedOppositeWidth() const;
298  void _SetActiveWidth(TsTime width);
299  void _SetOppositeWidth(TsTime width);
300 
301  // Like the above, but for asymmetrical algorithms that differentiate
302  // between start and end knots rather than active and opposite.
303  TsTime _GetProposedStartWidth() const;
304  TsTime _GetProposedEndWidth() const;
305  void _SetStartWidth(TsTime width);
306  void _SetEndWidth(TsTime width);
307 
308  // Plumbing helpers.
309  TsTime _GetSegmentWidth() const;
310 
311  private:
312  const WhichSegment _whichSegment;
313  const _Mode _mode;
314  Ts_Segment* const _segment;
315  _WorkingKnotState* const _activeKnotState;
316  _WorkingKnotState* const _oppositeKnotState;
317  SetResult* const _result;
318  };
319 
320 private:
321  // Set() helpers.
322  void _InitSetResult(
323  const TsKnot &proposedActiveKnot,
324  SetResult *resultOut) const;
325  void _HandleInitialAdjustment(
326  const TsKnot &proposedActiveKnot,
327  SetResult* resultOut);
328  void _HandleTimeChange(TsTime proposedActiveTime);
329  void _DoSet(
330  const TsKnot &proposedActiveKnot,
331  _Mode mode,
332  SetResult* resultOut);
333 
334 private:
335  TsSpline* const _spline;
336  const _Mode _mode;
337  const bool _limit;
338 
339  bool _valid;
340  bool _initialAdjustmentDone;
341 
342  std::optional<_KnotState> _activeKnotState;
343  std::optional<_KnotState> _preKnotState;
344  std::optional<_KnotState> _postKnotState;
345  std::optional<_KnotState> _overwrittenKnotState;
346 };
347 
349 {
350  // Batch operation for one segment of a spline. In Contain mode, this
351  // method returns true for "bold" tangents that are non-regressive but
352  // exceed the segment interval.
353  TS_API
354  static bool IsSegmentRegressive(
355  const Ts_KnotData *startKnot,
356  const Ts_KnotData *endKnot,
358 
359  // Overload of the above function that operates on a Ts_Segment
360  TS_API
361  static bool IsSegmentRegressive(
362  const Ts_Segment* segment,
363  TsAntiRegressionMode mode);
364 
365  // Batch operation for one segment of a spline. Returns whether anything
366  // was changed.
367  TS_API
368  static bool ProcessSegment(
369  Ts_KnotData *startKnot,
370  Ts_KnotData *endKnot,
371  TsAntiRegressionMode mode);
372 
373  // Overload of the above function that operates on a Ts_Segment
374  TS_API
375  static bool ProcessSegment(
376  Ts_Segment* segment,
377  TsAntiRegressionMode mode);
378 };
379 
380 
382 
383 #endif
TsAntiRegressionMode
Definition: types.h:295
TS_API bool Set(const TsKnot &proposedActiveKnot, SetResult *resultOut=nullptr)
bool adjusted
Whether any adjustments were made.
#define PXR_NAMESPACE_OPEN_SCOPE
Definition: pxr.h:73
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:621
static TS_API bool ProcessSegment(Ts_KnotData *startKnot, Ts_KnotData *endKnot, TsAntiRegressionMode mode)
Details of the result of an interactive Set call.
**But if you need a result
Definition: thread.h:622
TS_API std::string GetDebugDescription(int precision=6) const
bool havePreSegment
If there is a pre-segment, what adjustments were made to it.
static TS_API bool IsSegmentRegressive(const Ts_KnotData *startKnot, const Ts_KnotData *endKnot, TsAntiRegressionMode mode)
#define TS_API
Definition: api.h:24
GLenum mode
Definition: glcorearb.h:99
Definition: knot.h:40
GLenum GLint GLint * precision
Definition: glcorearb.h:1925
ModeLimitActive
TS_API TsRegressionPreventer(TsSpline *spline, TsTime activeKnotTime, bool limit=true)
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
bool havePostSegment
If there is a post-segment, what adjustments were made to it.
GLint GLsizei width
Definition: glcorearb.h:103