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