MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
MotionCurves.cpp
Go to the documentation of this file.
1#include "MotionCurves.hpp"
2
5
6#include "BasisMatrices.hpp"
7
9
10namespace MayaFlux::Kinesis {
11
12namespace {
13 struct SegmentInfo {
14 Eigen::Index points_per_segment;
15 Eigen::Index overlap;
17 };
18
19 SegmentInfo get_segment_info(InterpolationMode mode)
20 {
21 switch (mode) {
24 return {
25 .points_per_segment = 2,
26 .overlap = 1,
27 .supports_multi = true
28 };
29
32 return {
33 .points_per_segment = 4,
34 .overlap = 3,
35 .supports_multi = true
36 };
37
39 return {
40 .points_per_segment = 4,
41 .overlap = 1,
42 .supports_multi = true
43 };
44
46 return {
47 .points_per_segment = 3,
48 .overlap = 1,
49 .supports_multi = true
50 };
51
53 return {
54 .points_per_segment = 4,
55 .overlap = 0,
56 .supports_multi = false
57 };
58
59 default:
60 return {
61 .points_per_segment = 0,
62 .overlap = 0,
63 .supports_multi = false
64 };
65 }
66 }
67
68 struct ExtendedControls {
69 const Eigen::MatrixXd* controls;
70 Eigen::Index count;
71 Eigen::MatrixXd storage;
72
73 ExtendedControls(const Eigen::MatrixXd& original, InterpolationMode mode, Eigen::Index points_per_segment)
74 : controls(&original)
75 , count(original.cols())
76 {
79 storage.resize(original.rows(), count + 2);
80 storage.col(0) = original.col(0);
81 storage.col(count + 1) = original.col(count - 1);
82 storage.middleCols(1, count) = original;
83
85 count = count + 2;
86 }
87 }
88 };
89
90 struct SegmentLocation {
91 Eigen::Index seg_idx;
92 double t_local;
93
94 static SegmentLocation compute(
95 Eigen::Index sample_idx,
96 Eigen::Index num_samples,
97 Eigen::Index num_segments)
98 {
99 double t_global = static_cast<double>(sample_idx) / static_cast<double>(num_samples - 1);
100 double segment_float = t_global * static_cast<double>(num_segments);
101 auto seg_idx = static_cast<Eigen::Index>(std::floor(segment_float));
102 double t_local = segment_float - static_cast<double>(seg_idx);
103
104 if (sample_idx == num_samples - 1 || seg_idx >= num_segments) {
105 seg_idx = num_segments - 1;
106 t_local = 1.0;
107 }
108
109 return {
110 .seg_idx = seg_idx,
111 .t_local = t_local
112 };
113 }
114 };
115
116 Eigen::MatrixXd interpolate_single_segment(
117 const Eigen::MatrixXd& control_points,
118 Eigen::Index num_samples,
120 double tension)
121 {
122 Eigen::MatrixXd result(control_points.rows(), num_samples);
123 for (Eigen::Index i = 0; i < num_samples; ++i) {
124 double t = static_cast<double>(i) / static_cast<double>(num_samples - 1);
125 result.col(i) = interpolate(control_points, t, mode, tension);
126 }
127 return result;
128 }
129
130 Eigen::MatrixXd extract_segment_controls(
131 const Eigen::MatrixXd& active_controls,
132 Eigen::Index active_num_controls,
133 Eigen::Index seg_idx,
134 Eigen::Index points_per_segment,
135 Eigen::Index overlap,
136 double& t_local)
137 {
138 Eigen::Index start_col = seg_idx * (points_per_segment - overlap);
139
140 if (start_col + points_per_segment > active_num_controls) {
141 start_col = active_num_controls - points_per_segment;
142 t_local = 1.0;
143 }
144
145 return active_controls.middleCols(start_col, points_per_segment);
146 }
147
148 Eigen::Index compute_num_segments(
149 Eigen::Index num_controls,
150 Eigen::Index points_per_segment,
151 Eigen::Index overlap)
152 {
153 return (overlap == 0)
154 ? num_controls / points_per_segment
155 : (num_controls - overlap) / (points_per_segment - overlap);
156 }
157}
158
159Eigen::VectorXd catmull_rom_spline(
160 const Eigen::MatrixXd& control_points,
161 double t,
162 double tension)
163{
164 if (control_points.cols() != 4) {
165 error<std::invalid_argument>(
168 std::source_location::current(),
169 "Catmull-Rom interpolation requires 4 control points, but got {}",
170 control_points.cols());
171 }
172
173 Eigen::Matrix4d basis_matrix = BasisMatrices::catmull_rom_with_tension(tension);
174 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
175 Eigen::Vector4d coeffs = basis_matrix * t_vector;
176
177 return control_points * coeffs;
178}
179
180Eigen::VectorXd cubic_bezier(
181 const Eigen::MatrixXd& control_points,
182 double t)
183{
184 if (control_points.cols() != 4) {
185 error<std::invalid_argument>(
188 std::source_location::current(),
189 "Cubic Bezier interpolation requires 4 control points, but got {}",
190 control_points.cols());
191 }
192
193 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
194 Eigen::Vector4d coeffs = BasisMatrices::CUBIC_BEZIER * t_vector;
195
196 return control_points * coeffs;
197}
198
199Eigen::VectorXd quadratic_bezier(
200 const Eigen::MatrixXd& control_points,
201 double t)
202{
203 if (control_points.cols() != 3) {
204 error<std::invalid_argument>(
207 std::source_location::current(),
208 "Quadratic Bezier interpolation requires 3 control points, but got {}",
209 control_points.cols());
210 }
211
212 Eigen::Vector3d t_vector(t * t, t, 1.0);
213 Eigen::Vector3d coeffs = BasisMatrices::QUADRATIC_BEZIER * t_vector;
214
215 return control_points * coeffs;
216}
217
218Eigen::VectorXd cubic_hermite(
219 const Eigen::MatrixXd& endpoints,
220 const Eigen::MatrixXd& tangents,
221 double t)
222{
223 if (endpoints.cols() != 2 || tangents.cols() != 2) {
224 error<std::invalid_argument>(
227 std::source_location::current(),
228 "Cubic Hermite interpolation requires 2 endpoints and 2 tangents, but got {} endpoints and {} tangents",
229 endpoints.cols(), tangents.cols());
230 }
231
232 double t2 = t * t;
233 double t3 = t2 * t;
234
235 double h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
236 double h10 = t3 - 2.0 * t2 + t;
237 double h01 = -2.0 * t3 + 3.0 * t2;
238 double h11 = t3 - t2;
239
240 return h00 * endpoints.col(0) + h10 * tangents.col(0) + h01 * endpoints.col(1) + h11 * tangents.col(1);
241}
242
243Eigen::VectorXd bspline_cubic(
244 const Eigen::MatrixXd& control_points,
245 double t)
246{
247 if (control_points.cols() != 4) {
248 error<std::invalid_argument>(
251 std::source_location::current(),
252 "Cubic B-spline interpolation requires 4 control points, but got {}",
253 control_points.cols());
254 }
255
256 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
257 Eigen::Vector4d coeffs = BasisMatrices::BSPLINE_CUBIC * t_vector;
258
259 return control_points * coeffs;
260}
261
262Eigen::VectorXd interpolate(
263 const Eigen::MatrixXd& control_points,
264 double t,
266 double tension)
267{
268 switch (mode) {
270 if (control_points.cols() < 2) {
271 error<std::invalid_argument>(
274 std::source_location::current(),
275 "Linear interpolation requires at least 2 points, but got {}",
276 control_points.cols());
277 }
278 return (1.0 - t) * control_points.col(0) + t * control_points.col(1);
279
281 return catmull_rom_spline(control_points, t, tension);
282
284 Eigen::MatrixXd endpoints = control_points.leftCols(2);
285 Eigen::MatrixXd tangents = control_points.rightCols(2);
286 return cubic_hermite(endpoints, tangents, t);
287 }
288
290 return cubic_bezier(control_points, t);
291
293 return quadratic_bezier(control_points, t);
294
296 return bspline_cubic(control_points, t);
297
299 if (control_points.cols() < 2) {
300 error<std::invalid_argument>(
303 std::source_location::current(),
304 "Cosine interpolation requires at least 2 points, but got {}",
305 control_points.cols());
306 }
307 double mu2 = (1.0 - std::cos(t * M_PI)) * 0.5;
308 return (1.0 - mu2) * control_points.col(0) + mu2 * control_points.col(1);
309 }
310
311 default:
312 error<std::invalid_argument>(
315 std::source_location::current(),
316 "Unsupported interpolation mode: {}",
317 static_cast<int>(mode));
318 }
319}
320
322 const Eigen::MatrixXd& control_points,
323 Eigen::Index num_samples,
325 double tension)
326{
327 if (num_samples < 2) {
328 error<std::invalid_argument>(
331 std::source_location::current(),
332 "num_samples must be at least 2, but got {}",
333 num_samples);
334 }
335
336 if (control_points.cols() < 2) {
337 error<std::invalid_argument>(
340 std::source_location::current(),
341 "Need at least 2 control points, but got {}",
342 control_points.cols());
343 }
344
345 auto [points_per_segment, overlap, supports_multi] = get_segment_info(mode);
346
347 if (!supports_multi) {
348 if (control_points.cols() != points_per_segment) {
349 error<std::invalid_argument>(
352 std::source_location::current(),
353 "{} interpolation requires exactly {} control points, but got {}",
354 static_cast<int>(mode), points_per_segment, control_points.cols());
355 }
356 return interpolate_single_segment(control_points, num_samples, mode, tension);
357 }
358
359 if (control_points.cols() == points_per_segment) {
360 return interpolate_single_segment(control_points, num_samples, mode, tension);
361 }
362
363 ExtendedControls extended(control_points, mode, points_per_segment);
364 Eigen::Index num_segments = compute_num_segments(extended.count, points_per_segment, overlap);
365
366 if (num_segments < 1) {
367 error<std::invalid_argument>(
370 std::source_location::current(),
371 "Need sufficient control points for multi-segment {} interpolation, but got {}",
372 static_cast<int>(mode), control_points.cols());
373 }
374
375 Eigen::MatrixXd result(control_points.rows(), num_samples);
376
377 for (Eigen::Index i = 0; i < num_samples; ++i) {
378 auto [seg_idx, t_local] = SegmentLocation::compute(i, num_samples, num_segments);
379
380 Eigen::MatrixXd segment_controls = extract_segment_controls(
381 *extended.controls,
382 extended.count,
383 seg_idx,
385 overlap,
386 t_local);
387
388 result.col(i) = interpolate(segment_controls, t_local, mode, tension);
389 }
390
391 return result;
392}
393
394double compute_arc_length(const Eigen::MatrixXd& points)
395{
396 if (points.cols() < 2) {
397 return 0.0;
398 }
399
400 double length = 0.0;
401 for (Eigen::Index i = 1; i < points.cols(); ++i) {
402 length += (points.col(i) - points.col(i - 1)).norm();
403 }
404
405 return length;
406}
407
408Eigen::VectorXd compute_arc_length_table(const Eigen::MatrixXd& points)
409{
410 Eigen::VectorXd arc_lengths(points.cols());
411 arc_lengths(0) = 0.0;
412
413 for (Eigen::Index i = 1; i < points.cols(); ++i) {
414 arc_lengths(i) = arc_lengths(i - 1) + (points.col(i) - points.col(i - 1)).norm();
415 }
416
417 return arc_lengths;
418}
419
421 const Eigen::MatrixXd& points,
422 Eigen::Index num_samples)
423{
424 Eigen::VectorXd arc_lengths = compute_arc_length_table(points);
425 double total_length = arc_lengths(arc_lengths.size() - 1);
426
427 if (total_length == 0.0) {
428 return points;
429 }
430
431 Eigen::MatrixXd result(points.rows(), num_samples);
432
433 for (Eigen::Index i = 0; i < num_samples; ++i) {
434 double target = (static_cast<double>(i) / static_cast<double>(num_samples - 1)) * total_length;
435
436 Eigen::Index idx = 0;
437 for (Eigen::Index j = 1; j < arc_lengths.size(); ++j) {
438 if (arc_lengths(j) >= target) {
439 idx = j - 1;
440 break;
441 }
442 }
443
444 if (idx + 1 < points.cols()) {
445 double segment_start = arc_lengths(idx);
446 double segment_end = arc_lengths(idx + 1);
447 double t = (target - segment_start) / (segment_end - segment_start);
448
449 result.col(i) = (1.0 - t) * points.col(idx) + t * points.col(idx + 1);
450 } else {
451 result.col(i) = points.col(points.cols() - 1);
452 }
453 }
454
455 return result;
456}
457
459 const Kakshya::DataVariant& control_points,
460 Eigen::Index num_samples,
462 double tension)
463{
464 Eigen::MatrixXd control_matrix = Kakshya::to_eigen_matrix(control_points);
465
466 Eigen::MatrixXd interpolated = generate_interpolated_points(
467 control_matrix, num_samples, mode, tension);
468
469 Kakshya::EigenAccess input_access(control_points);
470
471 if (input_access.is_complex()) {
473 }
474
475 if (input_access.is_structured()) {
476 switch (input_access.component_count()) {
477 case 2:
479 case 3:
481 case 4:
483 default:
485 }
486 } else {
488 }
489}
490
491} // namespace MayaFlux::Kinesis
const Eigen::MatrixXd * controls
double t_local
Eigen::Index points_per_segment
Eigen::Index count
Eigen::MatrixXd storage
Eigen::Index overlap
bool supports_multi
Eigen::Index seg_idx
bool is_complex() const
Check if data contains complex numbers.
bool is_structured() const
Check if data contains GLM types.
size_t component_count() const
Get number of components per element (rows in matrix representation)
Type-erased accessor for converting DataVariant to Eigen types.
@ Runtime
General runtime operations (default fallback)
@ Kinesis
General mathematical and physics algorithns.
Eigen::MatrixXd to_eigen_matrix(const Kakshya::DataVariant &variant)
Convenience function for direct conversion.
Kakshya::DataVariant from_eigen_matrix(const Eigen::MatrixXd &matrix, MatrixInterpretation interpretation=MatrixInterpretation::AUTO)
Convenience function for direct conversion.
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:73
@ SCALAR
Single row → scalar values.
@ COMPLEX
2 rows → complex (row 0 = real, row 1 = imag)
std::vector< Nodes::LineVertex > reparameterize_by_arc_length(const std::vector< Nodes::LineVertex > &path_vertices, size_t num_samples)
Resample path vertices for arc-length parameterization.
Eigen::MatrixXd generate_interpolated_points(const Eigen::MatrixXd &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Generate interpolated points from control points.
InterpolationMode
Mathematical interpolation methods.
Eigen::VectorXd interpolate(const Eigen::MatrixXd &control_points, double t, InterpolationMode mode, double tension)
Generic interpolation dispatcher.
Eigen::VectorXd bspline_cubic(const Eigen::MatrixXd &control_points, double t)
Uniform B-spline interpolation using Eigen matrices.
double compute_arc_length(const Eigen::MatrixXd &points)
Compute arc length of curve using trapezoidal rule.
Eigen::VectorXd cubic_hermite(const Eigen::MatrixXd &endpoints, const Eigen::MatrixXd &tangents, double t)
Cubic Hermite interpolation using Eigen matrices.
Eigen::VectorXd cubic_bezier(const Eigen::MatrixXd &control_points, double t)
Cubic Bezier interpolation using Eigen matrices.
Kakshya::DataVariant interpolate_nddata(const Kakshya::DataVariant &control_points, Eigen::Index num_samples, InterpolationMode mode, double tension)
Process DataVariant through interpolation.
Eigen::VectorXd compute_arc_length_table(const Eigen::MatrixXd &points)
Compute arc length parameterization table.
Eigen::VectorXd quadratic_bezier(const Eigen::MatrixXd &control_points, double t)
Quadratic Bezier interpolation using Eigen matrices.
Eigen::VectorXd catmull_rom_spline(const Eigen::MatrixXd &control_points, double t, double tension)
Catmull-Rom spline interpolation using Eigen matrices.
static const Eigen::Matrix4d BSPLINE_CUBIC
static const Eigen::Matrix3d QUADRATIC_BEZIER
static Eigen::Matrix4d catmull_rom_with_tension(double tension)
static const Eigen::Matrix4d CUBIC_BEZIER