MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
MotionCurves.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <Eigen/Dense>
5
6namespace MayaFlux::Kinesis {
7
8/**
9 * @enum InterpolationMode
10 * @brief Mathematical interpolation methods
11 */
23
24/**
25 * @brief Catmull-Rom spline interpolation using Eigen matrices
26 * @param control_points 4xN matrix where columns are control points (p0, p1, p2, p3)
27 * @param t Parameter in [0,1]
28 * @param tension Tension parameter (default 0.5)
29 * @return Interpolated point as Nx1 vector
30 */
31MAYAFLUX_API Eigen::VectorXd catmull_rom_spline(
32 const Eigen::MatrixXd& control_points,
33 double t,
34 double tension = 0.5);
35
36/**
37 * @brief Origin-oriented cubic sweep from tension * (p0 - p1) to p3.
38 * @param control_points Matrix with four control-point columns.
39 * @param t Parameter in [0,1].
40 * @param tension Start displacement scale (default 0.5).
41 * @return Sampled point. Adjacent four-point windows need not meet.
42 */
43MAYAFLUX_API Eigen::VectorXd radial_fan_cubic(
44 const Eigen::MatrixXd& control_points,
45 double t,
46 double tension = 0.5);
47
48/**
49 * @brief Cubic Bezier interpolation using Eigen matrices
50 * @param control_points 4xN matrix where columns are control points
51 * @param t Parameter in [0,1]
52 * @return Interpolated point as Nx1 vector
53 */
54MAYAFLUX_API Eigen::VectorXd cubic_bezier(
55 const Eigen::MatrixXd& control_points,
56 double t);
57
58/**
59 * @brief Quadratic Bezier interpolation using Eigen matrices
60 * @param control_points 3xN matrix where columns are control points
61 * @param t Parameter in [0,1]
62 * @return Interpolated point as Nx1 vector
63 */
64MAYAFLUX_API Eigen::VectorXd quadratic_bezier(
65 const Eigen::MatrixXd& control_points,
66 double t);
67
68/**
69 * @brief Cubic Hermite interpolation using Eigen matrices
70 * @param endpoints 2xN matrix (start, end)
71 * @param tangents 2xN matrix (tangent_start, tangent_end)
72 * @param t Parameter in [0,1]
73 * @return Interpolated point as Nx1 vector
74 */
75MAYAFLUX_API Eigen::VectorXd cubic_hermite(
76 const Eigen::MatrixXd& endpoints,
77 const Eigen::MatrixXd& tangents,
78 double t);
79
80/**
81 * @brief Uniform B-spline interpolation using Eigen matrices
82 * @param control_points 4xN matrix where columns are control points
83 * @param t Parameter in [0,1]
84 * @return Interpolated point as Nx1 vector
85 */
86MAYAFLUX_API Eigen::VectorXd bspline_cubic(
87 const Eigen::MatrixXd& control_points,
88 double t);
89
90/**
91 * @brief Generic interpolation dispatcher
92 * @param control_points MxN matrix where columns are control points
93 * @param t Parameter in [0,1]
94 * @param mode Interpolation mode
95 * @param tension Tension parameter (for applicable modes)
96 * @return Interpolated point as Nx1 vector
97 */
98MAYAFLUX_API Eigen::VectorXd interpolate(
99 const Eigen::MatrixXd& control_points,
100 double t,
102 double tension = 0.5);
103
104/**
105 * @brief Tangents of a sampled curve by finite difference, coordinate-major.
106 * @param points Coordinate-major, dim * count doubles.
107 * @param dim Coordinate count per sample, at least 1.
108 * @param count Sample count, at least 2.
109 * @param tangents Resized to dim * count, coordinate-major.
110 *
111 * For curves whose samples did not come straight from CurveEvaluator's
112 * analytic derivative, most importantly after reparameterize_planar has
113 * resampled by arc length, which leaves analytic tangents attached to the
114 * original parameter values. Uniformly spaced samples make the central
115 * difference well conditioned; the endpoints take the one sided difference.
116 *
117 * Like the analytic tangents, these are directions and not rates.
118 */
119MAYAFLUX_API void central_difference_tangents(
120 std::span<const double> points,
121 size_t dim,
122 size_t count,
123 std::vector<double>& tangents);
124
125/**
126 * @struct CurveChunk
127 * @brief A contiguous run of samples evaluated in one pass.
128 *
129 * Segments own disjoint, contiguous sample ranges because the segment
130 * parameter is monotonic in the sample index. A segment longer than the
131 * internal chunk size is split so the parameter buffer stays cache resident.
132 */
134 Eigen::Index segment { 0 }; ///< Owning segment index.
135 Eigen::Index start_col { 0 }; ///< First control point of the owning segment.
136 Eigen::Index sample_begin { 0 }; ///< First output sample.
137 Eigen::Index sample_count { 0 }; ///< Output sample count.
138 bool clamp_t_high { false }; ///< Segment pinned to t = 1 by the control clamp.
139};
140
141/**
142 * @class CurveEvaluator
143 * @brief Reusable interpolation state for callers evaluating many curves.
144 *
145 * The evaluation core operates on plain double buffers with no Eigen types
146 * and no allocation after the first call at given dimensions. Eigen appears
147 * only at the API boundary and in the basis matrix declarations.
148 *
149 * ## Layouts
150 * Control points are point-major: the coordinate index varies fastest, so
151 * point q occupies `control_points[q * dim .. q * dim + dim)`. This matches
152 * the memory of a column-major Eigen matrix whose columns are control
153 * points, so `matrix.data()` can be passed directly.
154 *
155 * Planar output is coordinate-major: `out[d * num_samples + i]` is coordinate
156 * d of sample i. Consecutive samples of one coordinate are contiguous, which
157 * is what lets the sample axis occupy the SIMD lanes. Callers wanting an
158 * Eigen matrix pay one transpose in the boundary overloads.
159 *
160 * ## Evaluation
161 * Mode and tension resolve once to a single basis matrix, applied to the
162 * control block once per chunk rather than per sample. Each coordinate is
163 * then a Horner evaluation over the sample parameter, four samples per
164 * vector under AVX2 and two under NEON.
165 *
166 * Not thread safe. One instance per thread, or one per node.
167 *
168 * @code
169 * Kinesis::CurveEvaluator eval(Kinesis::InterpolationMode::CATMULL_ROM, 0.5);
170 * std::vector<double> out;
171 *
172 * eval.evaluate_planar(controls, 3, 32, out);
173 * const double* x = out.data();
174 * const double* y = x + 32;
175 * const double* z = y + 32;
176 * @endcode
177 */
178class MAYAFLUX_API CurveEvaluator {
179public:
180 /**
181 * @brief Construct and resolve the evaluation kernel.
182 * @param mode Interpolation mode.
183 * @param tension Tension, consumed only by CATMULL_ROM.
184 */
185 explicit CurveEvaluator(
186 InterpolationMode mode = InterpolationMode::CATMULL_ROM,
187 double tension = 0.5);
188
189 /**
190 * @brief Re-resolve the kernel. A no-op when both arguments are unchanged.
191 * @param mode Interpolation mode.
192 * @param tension Tension, consumed only by CATMULL_ROM.
193 */
194 void configure(InterpolationMode mode, double tension);
195
196 /** @brief Currently configured mode. */
197 [[nodiscard]] InterpolationMode mode() const { return m_mode; }
198
199 /** @brief Currently configured tension. */
200 [[nodiscard]] double tension() const { return m_tension; }
201
202 /**
203 * @brief Evaluate a curve into coordinate-major position and tangent buffers.
204 * @param control_points Point-major, dim * control_count doubles.
205 * @param dim Coordinate count per point, at least 1.
206 * @param num_samples Output sample count, at least 2.
207 * @param out Resized to dim * num_samples, coordinate-major.
208 * @param tangents Resized to dim * num_samples, coordinate-major.
209 *
210 * A sample's state is its position and its direction, so both are produced
211 * together: the basis fold already yields the polynomial coefficients, and
212 * differentiating them is a scale by power feeding the same Horner kernel
213 * one degree lower. Callers that ignore @p tangents pay one extra pass over
214 * the parameter buffer already in cache.
215 *
216 * @p tangents holds the derivative with respect to the segment parameter,
217 * so it is a direction and not a rate: its magnitude is not comparable
218 * across segments and changes under arc length reparameterization. At a
219 * cusp or a repeated control point the derivative is zero, and consumers
220 * must fall back to a difference of neighbouring positions there.
221 *
222 * @p control_points must not alias @p out or @p tangents.
223 */
224 void evaluate_planar(
225 std::span<const double> control_points,
226 size_t dim,
227 Eigen::Index num_samples,
228 std::vector<double>& out,
229 std::vector<double>& tangents);
230
231 /**
232 * @brief Resample a polyline to uniform arc length, coordinate-major.
233 * @param points Coordinate-major, dim * point_count doubles.
234 * @param dim Coordinate count per point, at least 1.
235 * @param point_count Input sample count.
236 * @param num_samples Output sample count, at least 2.
237 * @param out Resized to dim * num_samples, coordinate-major.
238 *
239 * @p points and @p out must not alias. A zero-length span between two
240 * consecutive points yields that span's start point rather than a
241 * division by zero.
242 */
243 void reparameterize_planar(
244 std::span<const double> points,
245 size_t dim,
246 Eigen::Index point_count,
247 Eigen::Index num_samples,
248 std::vector<double>& out);
249
250 /**
251 * @brief Evaluate a curve into a caller-owned matrix.
252 * @param control_points MxN matrix, columns are control points.
253 * @param num_samples Output column count, at least 2.
254 * @param out Resized to rows(control_points) x num_samples and overwritten.
255 *
256 * Boundary overload. Wraps evaluate_planar and transposes the result.
257 */
258 void evaluate(
259 const Eigen::MatrixXd& control_points,
260 Eigen::Index num_samples,
261 Eigen::MatrixXd& out);
262
263 /**
264 * @brief Resample a polyline to uniform arc length into a caller-owned matrix.
265 * @param points MxN matrix, columns are sequential points.
266 * @param num_samples Output column count, at least 2.
267 * @param out Resized to rows(points) x num_samples and overwritten.
268 *
269 * Boundary overload. Transposes in, wraps reparameterize_planar,
270 * transposes out.
271 */
272 void reparameterize(
273 const Eigen::MatrixXd& points,
274 Eigen::Index num_samples,
275 Eigen::MatrixXd& out);
276
277private:
279 double m_tension;
280
281 std::vector<double> m_basis;
282 Eigen::Index m_points_per_segment { 0 };
283 Eigen::Index m_overlap { 0 };
284 bool m_supports_multi { false };
285 bool m_trigonometric { false };
286
287 std::vector<double> m_extended;
288 std::vector<double> m_folded;
289 std::vector<double> m_dfolded;
290 std::vector<double> m_tbuf;
291 std::vector<double> m_arc;
292 std::vector<double> m_planar;
293 std::vector<double> m_planar_tangents;
294 std::vector<double> m_planar_alt;
295 std::vector<size_t> m_lower;
296 std::vector<double> m_frac;
297
298 std::vector<CurveChunk> m_chunks;
299 std::vector<Eigen::Index> m_seg_first;
300 std::vector<Eigen::Index> m_seg_total;
301
302 void rebuild_kernel();
303
304 const double* extend(
305 std::span<const double> control_points,
306 size_t dim,
307 Eigen::Index& count);
308
309 void build_chunks(
310 Eigen::Index num_samples,
311 Eigen::Index num_segments,
312 Eigen::Index active_count);
313};
314
315/**
316 * @brief Generate interpolated points from control points
317 * @param control_points MxN matrix where columns are control points
318 * @param num_samples Number of interpolated points to generate
319 * @param mode Interpolation mode
320 * @param tension Tension parameter
321 * @return Matrix where columns are interpolated points
322 */
323MAYAFLUX_API Eigen::MatrixXd generate_interpolated_points(
324 const Eigen::MatrixXd& control_points,
325 Eigen::Index num_samples,
327 double tension = 0.5);
328
329/**
330 * @brief Compute arc length of curve using trapezoidal rule
331 * @param points Columns are sequential points along curve
332 * @return Estimated arc length
333 */
334MAYAFLUX_API double compute_arc_length(const Eigen::MatrixXd& points);
335
336/**
337 * @brief Compute arc length parameterization table
338 * @param points Columns are sequential points along curve
339 * @return Vector of cumulative arc lengths
340 */
341MAYAFLUX_API Eigen::VectorXd compute_arc_length_table(const Eigen::MatrixXd& points);
342
343/**
344 * @brief Reparameterize curve by arc length
345 * @param points Original points (columns)
346 * @param num_samples Number of output samples
347 * @return Arc-length parameterized points
348 */
349MAYAFLUX_API Eigen::MatrixXd reparameterize_by_arc_length(
350 const Eigen::MatrixXd& points,
351 Eigen::Index num_samples);
352
353/**
354 * @brief Process DataVariant through interpolation
355 * @param control_points Input data as control points
356 * @param num_samples Number of output samples
357 * @param mode Interpolation mode
358 * @param tension Tension parameter
359 * @return Interpolated data
360 */
362 const Kakshya::DataVariant& control_points,
363 Eigen::Index num_samples,
365 double tension = 0.5);
366
367} // namespace MayaFlux::Kinesis
std::vector< glm::vec2 > * points
uint32_t mode
std::vector< float > * out
size_t count
InterpolationMode mode() const
Currently configured mode.
std::vector< Eigen::Index > m_seg_total
std::vector< double > m_planar_alt
double tension() const
Currently configured tension.
std::vector< CurveChunk > m_chunks
std::vector< Eigen::Index > m_seg_first
std::vector< double > m_planar_tangents
Reusable interpolation state for callers evaluating many curves.
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:70
double compute_arc_length(const Eigen::MatrixXd &points)
Compute arc length of curve using trapezoidal rule.
InterpolationMode
Mathematical interpolation methods.
Eigen::VectorXd bspline_cubic(const Eigen::MatrixXd &control_points, double t)
Uniform B-spline interpolation using Eigen matrices.
Eigen::VectorXd cubic_bezier(const Eigen::MatrixXd &control_points, double t)
Cubic Bezier interpolation using Eigen matrices.
Eigen::VectorXd quadratic_bezier(const Eigen::MatrixXd &control_points, double t)
Quadratic Bezier interpolation using Eigen matrices.
void central_difference_tangents(std::span< const double > points, size_t dim, size_t count, std::vector< double > &tangents)
Tangents of a sampled curve by finite difference, coordinate-major.
Eigen::VectorXd catmull_rom_spline(const Eigen::MatrixXd &control_points, double t, double tension)
Catmull-Rom spline interpolation using Eigen matrices.
Eigen::MatrixXd generate_interpolated_points(const Eigen::MatrixXd &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Generate interpolated points from control points.
Eigen::VectorXd compute_arc_length_table(const Eigen::MatrixXd &points)
Compute arc length parameterization table.
Kakshya::DataVariant interpolate_nddata(const Kakshya::DataVariant &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Process DataVariant through interpolation.
std::vector< Kakshya::LineVertex > reparameterize_by_arc_length(const std::vector< Kakshya::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
Eigen::VectorXd cubic_hermite(const Eigen::MatrixXd &endpoints, const Eigen::MatrixXd &tangents, double t)
Cubic Hermite interpolation using Eigen matrices.
Eigen::VectorXd interpolate(const Eigen::MatrixXd &control_points, double t, InterpolationMode mode, double tension)
Generic interpolation dispatcher.
Eigen::VectorXd radial_fan_cubic(const Eigen::MatrixXd &control_points, double t, double tension)
Origin-oriented cubic sweep from tension * (p0 - p1) to p3.
Eigen::Index start_col
First control point of the owning segment.
Eigen::Index sample_count
Output sample count.
Eigen::Index sample_begin
First output sample.
bool clamp_t_high
Segment pinned to t = 1 by the control clamp.
Eigen::Index segment
Owning segment index.
A contiguous run of samples evaluated in one pass.