3#ifdef MAYAFLUX_ARCH_X64
6#ifdef MAYAFLUX_ARCH_ARM64
25 constexpr Eigen::Index k_chunk_samples = 4096;
28 constexpr Eigen::Index k_parallel_min_samples = 8192;
31 constexpr Eigen::Index k_parallel_min_points = 8192;
33 Eigen::Index compute_num_segments(
34 Eigen::Index num_controls,
35 Eigen::Index points_per_segment,
39 ? num_controls / points_per_segment
40 : (num_controls - overlap) / (points_per_segment - overlap);
49 void flatten_basis(
const double* source, Eigen::Index pps, std::vector<double>& dst)
51 dst.resize(
static_cast<size_t>(pps * pps));
52 for (Eigen::Index
q = 0;
q < pps; ++
q) {
53 for (Eigen::Index p = 0; p < pps; ++p) {
54 dst[
static_cast<size_t>(
q * pps + p)] = source[
q + p * pps];
73 Eigen::Index start_col,
79 const double* block = ctrl +
static_cast<size_t>(start_col) * dim;
81 for (
size_t d = 0; d < dim; ++d) {
82 for (Eigen::Index p = 0; p < pps; ++p) {
84 for (Eigen::Index
q = 0;
q < pps; ++
q) {
85 sum += block[
static_cast<size_t>(
q) * dim + d]
86 * basis[
static_cast<size_t>(
q * pps + p)];
88 folded[d *
static_cast<size_t>(pps) +
static_cast<size_t>(p)] =
sum;
102 void fill_parameters(
104 const CurveChunk& chunk,
105 Eigen::Index num_samples,
106 Eigen::Index num_segments)
108 const auto count =
static_cast<size_t>(chunk.sample_count);
110 if (chunk.clamp_t_high) {
111 std::fill_n(dst,
count, 1.0);
115 const double delta =
static_cast<double>(num_segments)
116 /
static_cast<double>(num_samples - 1);
117 const double origin = -
static_cast<double>(chunk.segment);
118 const auto begin =
static_cast<double>(chunk.sample_begin);
122#ifdef MAYAFLUX_ARCH_X64
123 const __m256d v_delta = _mm256_set1_pd(delta);
124 const __m256d v_origin = _mm256_set1_pd(origin);
125 const __m256d v_step = _mm256_set1_pd(4.0);
126 __m256d idx = _mm256_set_pd(begin + 3.0, begin + 2.0, begin + 1.0, begin);
128 for (; j + 4 <=
count; j += 4) {
129 _mm256_storeu_pd(dst + j, _mm256_fmadd_pd(idx, v_delta, v_origin));
130 idx = _mm256_add_pd(idx, v_step);
132#elif defined(MAYAFLUX_ARCH_ARM64)
133 const float64x2_t v_delta = vdupq_n_f64(delta);
134 const float64x2_t v_step = vdupq_n_f64(2.0);
135 float64x2_t idx = { begin, begin + 1.0 };
137 for (; j + 2 <=
count; j += 2) {
138 vst1q_f64(dst + j, vfmaq_f64(vdupq_n_f64(origin), idx, v_delta));
139 idx = vaddq_f64(idx, v_step);
143 for (; j <
count; ++j) {
144 dst[j] = (begin +
static_cast<double>(j)) * delta + origin;
147 if (chunk.sample_begin + chunk.sample_count == num_samples) {
148 dst[
count - 1] = 1.0;
166 template <
size_t Pps>
168 const double* folded,
169 const double* params,
175 for (
size_t d = 0; d < dim; ++d) {
176 const double* co = folded + d * Pps;
177 double* row = dst + d * stride;
181#ifdef MAYAFLUX_ARCH_X64
183 for (
size_t p = 0; p < Pps; ++p) {
184 cv[p] = _mm256_set1_pd(co[p]);
187 for (; j + 4 <=
count; j += 4) {
188 const __m256d tv = _mm256_loadu_pd(params + j);
190 for (
size_t p = 1; p < Pps; ++p) {
191 acc = _mm256_fmadd_pd(acc, tv, cv[p]);
193 _mm256_storeu_pd(row + j, acc);
195#elif defined(MAYAFLUX_ARCH_ARM64)
197 for (
size_t p = 0; p < Pps; ++p) {
198 cv[p] = vdupq_n_f64(co[p]);
201 for (; j + 2 <=
count; j += 2) {
202 const float64x2_t tv = vld1q_f64(params + j);
203 float64x2_t acc = cv[0];
204 for (
size_t p = 1; p < Pps; ++p) {
205 acc = vfmaq_f64(cv[p], acc, tv);
207 vst1q_f64(row + j, acc);
211 for (; j <
count; ++j) {
213 for (
size_t p = 1; p < Pps; ++p) {
214 acc = acc * params[j] + co[p];
221 void evaluate_chunk_polynomial(
222 const double* folded,
223 const double* params,
232 evaluate_rows<2>(folded, params, dim,
count, dst, stride);
235 evaluate_rows<3>(folded, params, dim,
count, dst, stride);
238 evaluate_rows<4>(folded, params, dim,
count, dst, stride);
252 void evaluate_chunk_cosine(
254 Eigen::Index start_col,
255 const double* params,
261 const double* c0 = ctrl +
static_cast<size_t>(start_col) * dim;
262 const double* c1 = c0 + dim;
264 for (
size_t d = 0; d < dim; ++d) {
265 double* row = dst + d * stride;
266 const double a = c0[d];
267 const double b = c1[d];
269 for (
size_t j = 0; j <
count; ++j) {
270 const double mu = (1.0 - std::cos(params[j] * M_PI)) * 0.5;
271 row[j] = (1.0 - mu) *
a + mu *
b;
283 const Eigen::MatrixXd& control_points,
287 if (control_points.cols() != 4) {
288 error<std::invalid_argument>(
291 std::source_location::current(),
292 "Catmull-Rom interpolation requires 4 control points, but got {}",
293 control_points.cols());
297 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
298 Eigen::Vector4d coeffs = basis_matrix * t_vector;
300 return control_points * coeffs;
304 const Eigen::MatrixXd& control_points,
307 if (control_points.cols() != 4) {
308 error<std::invalid_argument>(
311 std::source_location::current(),
312 "Cubic Bezier interpolation requires 4 control points, but got {}",
313 control_points.cols());
316 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
319 return control_points * coeffs;
323 const Eigen::MatrixXd& control_points,
326 if (control_points.cols() != 3) {
327 error<std::invalid_argument>(
330 std::source_location::current(),
331 "Quadratic Bezier interpolation requires 3 control points, but got {}",
332 control_points.cols());
335 Eigen::Vector3d t_vector(t * t, t, 1.0);
338 return control_points * coeffs;
342 const Eigen::MatrixXd& endpoints,
343 const Eigen::MatrixXd& tangents,
346 if (endpoints.cols() != 2 || tangents.cols() != 2) {
347 error<std::invalid_argument>(
350 std::source_location::current(),
351 "Cubic Hermite interpolation requires 2 endpoints and 2 tangents, but got {} endpoints and {} tangents",
352 endpoints.cols(), tangents.cols());
358 double h00 = 2.0 * t3 - 3.0 * t2 + 1.0;
359 double h10 = t3 - 2.0 * t2 + t;
360 double h01 = -2.0 * t3 + 3.0 * t2;
361 double h11 = t3 - t2;
363 return h00 * endpoints.col(0) + h10 * tangents.col(0) + h01 * endpoints.col(1) + h11 * tangents.col(1);
367 const Eigen::MatrixXd& control_points,
370 if (control_points.cols() != 4) {
371 error<std::invalid_argument>(
374 std::source_location::current(),
375 "Cubic B-spline interpolation requires 4 control points, but got {}",
376 control_points.cols());
379 Eigen::Vector4d t_vector(t * t * t, t * t, t, 1.0);
382 return control_points * coeffs;
386 const Eigen::MatrixXd& control_points,
393 if (control_points.cols() < 2) {
394 error<std::invalid_argument>(
397 std::source_location::current(),
398 "Linear interpolation requires at least 2 points, but got {}",
399 control_points.cols());
401 return (1.0 - t) * control_points.col(0) + t * control_points.col(1);
407 Eigen::MatrixXd endpoints = control_points.leftCols(2);
408 Eigen::MatrixXd tangents = control_points.rightCols(2);
422 if (control_points.cols() < 2) {
423 error<std::invalid_argument>(
426 std::source_location::current(),
427 "Cosine interpolation requires at least 2 points, but got {}",
428 control_points.cols());
430 double mu2 = (1.0 - std::cos(t * M_PI)) * 0.5;
431 return (1.0 - mu2) * control_points.col(0) + mu2 * control_points.col(1);
435 error<std::invalid_argument>(
438 std::source_location::current(),
439 "Unsupported interpolation mode: {}",
440 static_cast<int>(mode));
475 m_basis = { -1.0, 1.0, 1.0, 0.0 };
492 flatten_basis(m.data(), 4,
m_basis);
539 std::span<const double> control_points,
543 count =
static_cast<Eigen::Index
>(control_points.size() / dim);
550 return control_points.data();
553 const auto n =
static_cast<size_t>(
count);
556 const double* src = control_points.data();
559 std::copy_n(src, dim, dst);
560 std::copy_n(src, n * dim, dst + dim);
561 std::copy_n(src + (n - 1) * dim, dim, dst + (n + 1) * dim);
563 count =
static_cast<Eigen::Index
>(n + 2);
568 Eigen::Index num_samples,
569 Eigen::Index num_segments,
570 Eigen::Index active_count)
572 const auto segments =
static_cast<size_t>(num_segments);
577 for (Eigen::Index i = 0; i < num_samples; ++i) {
578 const double t_global =
static_cast<double>(i) /
static_cast<double>(num_samples - 1);
579 const double segment_float = t_global *
static_cast<double>(num_segments);
580 auto seg_idx =
static_cast<Eigen::Index
>(std::floor(segment_float));
582 if (i == num_samples - 1 || seg_idx >= num_segments) {
583 seg_idx = num_segments - 1;
586 const auto s =
static_cast<size_t>(seg_idx);
595 for (Eigen::Index seg_idx = 0; seg_idx < num_segments; ++seg_idx) {
596 const auto s =
static_cast<size_t>(seg_idx);
607 for (Eigen::Index off = 0; off <
m_seg_total[s]; off += k_chunk_samples) {
608 m_chunks.push_back({ .segment = seg_idx,
609 .start_col = start_col,
611 .sample_count = std::min(k_chunk_samples,
m_seg_total[s] - off),
612 .clamp_t_high = clamp });
618 std::span<const double> control_points,
620 Eigen::Index num_samples,
621 std::vector<double>& out)
623 if (num_samples < 2) {
624 error<std::invalid_argument>(
627 std::source_location::current(),
628 "num_samples must be at least 2, but got {}",
633 error<std::invalid_argument>(
636 std::source_location::current(),
637 "dim must be at least 1");
640 const auto control_count =
static_cast<Eigen::Index
>(control_points.size() / dim);
642 if (control_count < 2) {
643 error<std::invalid_argument>(
646 std::source_location::current(),
647 "Need at least 2 control points, but got {}",
652 error<std::invalid_argument>(
655 std::source_location::current(),
656 "Unsupported interpolation mode: {}",
657 static_cast<int>(
m_mode));
661 error<std::invalid_argument>(
664 std::source_location::current(),
665 "{} interpolation requires exactly {} control points, but got {}",
669 Eigen::Index active_count = 0;
670 const double* active =
extend(control_points, dim, active_count);
672 const Eigen::Index num_segments = compute_num_segments(
675 if (num_segments < 1) {
676 error<std::invalid_argument>(
679 std::source_location::current(),
680 "Need sufficient control points for multi-segment {} interpolation, but got {}",
681 static_cast<int>(
m_mode), control_count);
684 const auto stride =
static_cast<size_t>(num_samples);
685 out.resize(dim * stride);
691 const double* basis =
m_basis.data();
692 double* out_base = out.data();
694 if (
m_chunks.size() > 1 && num_samples >= k_parallel_min_samples) {
697 const auto count = static_cast<size_t>(chunk.sample_count);
698 std::vector<double> params(count);
699 fill_parameters(params.data(), chunk, num_samples, num_segments);
701 double* dst = out_base + static_cast<size_t>(chunk.sample_begin);
704 evaluate_chunk_cosine(active, chunk.start_col, params.data(),
705 dim, count, dst, stride);
709 std::vector<double> folded(dim *
static_cast<size_t>(pps));
710 fold_controls(active, chunk.
start_col, dim, pps, basis, folded.data());
712 evaluate_chunk_polynomial(folded.data(), params.data(),
713 dim, pps,
count, dst, stride);
720 const auto count =
static_cast<size_t>(chunk.sample_count);
722 m_tbuf.resize(
count);
723 fill_parameters(m_tbuf.data(), chunk, num_samples, num_segments);
725 double* dst = out_base +
static_cast<size_t>(chunk.sample_begin);
728 evaluate_chunk_cosine(active, chunk.start_col, m_tbuf.data(),
729 dim,
count, dst, stride);
733 m_folded.resize(dim *
static_cast<size_t>(pps));
734 fold_controls(active, chunk.start_col, dim, pps, basis, m_folded.data());
736 evaluate_chunk_polynomial(m_folded.data(), m_tbuf.data(),
737 dim, pps,
count, dst, stride);
741void CurveEvaluator::reparameterize_planar(
742 std::span<const double>
points,
744 Eigen::Index point_count,
745 Eigen::Index num_samples,
746 std::vector<double>& out)
748 const auto n =
static_cast<size_t>(point_count);
749 const auto samples =
static_cast<size_t>(num_samples);
751 if (point_count < 2 || num_samples < 2) {
756 const double* src =
points.data();
758 m_arc.assign(n, 0.0);
760 for (
size_t d = 0; d < dim; ++d) {
761 const double* row = src + d * n;
762 for (
size_t i = 1; i < n; ++i) {
763 const double delta = row[i] - row[i - 1];
764 m_arc[i] += delta * delta;
768 for (
size_t i = 1; i < n; ++i) {
769 m_arc[i] = std::sqrt(m_arc[i]);
772 std::inclusive_scan(m_arc.data() + 1, m_arc.data() + n, m_arc.data() + 1);
774 const double total_length = m_arc[n - 1];
775 if (total_length == 0.0) {
780 m_lower.resize(samples);
781 m_frac.resize(samples);
783 const double step = total_length /
static_cast<double>(num_samples - 1);
786 for (
size_t i = 0; i < samples; ++i) {
787 const double target =
static_cast<double>(i) * step;
789 while (upper < n - 1 && m_arc[upper] < target) {
793 const size_t lower = upper - 1;
794 const double span = m_arc[upper] - m_arc[lower];
797 m_frac[i] = (span > 0.0) ? ((target - m_arc[lower]) / span) : 0.0;
800 out.resize(dim * samples);
802 for (
size_t d = 0; d < dim; ++d) {
803 const double* row = src + d * n;
804 double* dst = out.data() + d * samples;
806 for (
size_t i = 0; i < samples; ++i) {
807 const size_t lower = m_lower[i];
808 const double t = m_frac[i];
809 dst[i] = (1.0 - t) * row[lower] + t * row[lower + 1];
814void CurveEvaluator::evaluate(
815 const Eigen::MatrixXd& control_points,
816 Eigen::Index num_samples,
817 Eigen::MatrixXd& out)
819 const auto dim =
static_cast<size_t>(control_points.rows());
822 std::span<const double>(control_points.data(),
823 static_cast<size_t>(control_points.size())),
824 dim, num_samples, m_planar);
826 out.resize(control_points.rows(), num_samples);
828 const auto samples =
static_cast<size_t>(num_samples);
829 double* dst = out.data();
831 for (
size_t d = 0; d < dim; ++d) {
832 const double* row = m_planar.data() + d * samples;
833 for (
size_t i = 0; i < samples; ++i) {
834 dst[d + i * dim] = row[i];
839void CurveEvaluator::reparameterize(
840 const Eigen::MatrixXd&
points,
841 Eigen::Index num_samples,
842 Eigen::MatrixXd& out)
844 const auto dim =
static_cast<size_t>(
points.rows());
845 const auto n =
static_cast<size_t>(
points.cols());
847 m_planar.resize(dim * n);
848 const double* src =
points.data();
850 for (
size_t d = 0; d < dim; ++d) {
851 double* row = m_planar.data() + d * n;
852 for (
size_t i = 0; i < n; ++i) {
853 row[i] = src[d + i * dim];
857 reparameterize_planar(m_planar, dim,
points.cols(), num_samples, m_planar_alt);
859 const auto samples = m_planar_alt.size() / std::max<size_t>(dim, 1);
860 out.resize(
points.rows(),
static_cast<Eigen::Index
>(samples));
862 double* dst = out.data();
863 for (
size_t d = 0; d < dim; ++d) {
864 const double* row = m_planar_alt.data() + d * samples;
865 for (
size_t i = 0; i < samples; ++i) {
866 dst[d + i * dim] = row[i];
876 const Eigen::MatrixXd& control_points,
877 Eigen::Index num_samples,
882 Eigen::MatrixXd result;
883 evaluator.
evaluate(control_points, num_samples, result);
894 for (Eigen::Index i = 1; i <
points.cols(); ++i) {
903 const Eigen::Index n =
points.cols();
905 Eigen::VectorXd arc_lengths(n);
910 arc_lengths(0) = 0.0;
915 if (n >= k_parallel_min_points) {
916 P::for_each(P::par_unseq,
917 std::views::iota(Eigen::Index { 1 }, n).begin(),
918 std::views::iota(Eigen::Index { 1 }, n).end(),
919 [&](Eigen::Index i) {
920 arc_lengths(i) = (
points.col(i) -
points.col(i - 1)).norm();
923 for (Eigen::Index i = 1; i < n; ++i) {
924 arc_lengths(i) = (
points.col(i) -
points.col(i - 1)).norm();
929 arc_lengths.data() + 1,
930 arc_lengths.data() + n,
931 arc_lengths.data() + 1);
937 const Eigen::MatrixXd&
points,
938 Eigen::Index num_samples)
941 Eigen::MatrixXd result;
948 Eigen::Index num_samples,
955 control_matrix, num_samples, mode, tension);
std::vector< glm::vec2 > * points
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.
void evaluate(const Eigen::MatrixXd &control_points, Eigen::Index num_samples, Eigen::MatrixXd &out)
Evaluate a curve into a caller-owned matrix.
InterpolationMode mode() const
Currently configured mode.
std::vector< Eigen::Index > m_seg_total
double tension() const
Currently configured tension.
const double * extend(std::span< const double > control_points, size_t dim, Eigen::Index &count)
std::vector< double > m_extended
void reparameterize(const Eigen::MatrixXd &points, Eigen::Index num_samples, Eigen::MatrixXd &out)
Resample a polyline to uniform arc length into a caller-owned matrix.
void build_chunks(Eigen::Index num_samples, Eigen::Index num_segments, Eigen::Index active_count)
std::vector< double > m_basis
std::vector< CurveChunk > m_chunks
std::vector< Eigen::Index > m_seg_first
CurveEvaluator(InterpolationMode mode=InterpolationMode::CATMULL_ROM, double tension=0.5)
Construct and resolve the evaluation kernel.
void evaluate_planar(std::span< const double > control_points, size_t dim, Eigen::Index num_samples, std::vector< double > &out)
Evaluate a curve into a coordinate-major buffer.
void configure(InterpolationMode mode, double tension)
Re-resolve the kernel.
Eigen::Index m_points_per_segment
Reusable interpolation state for callers evaluating many curves.
@ 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.
@ SCALAR
Single row → scalar values.
@ COMPLEX
2 rows → complex (row 0 = real, row 1 = imag)
std::vector< double > sum(std::span< const double > data, size_t n_windows, uint32_t hop_size, uint32_t window_size)
Sum per window.
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.
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.
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
Eigen::Index start_col
First control point of the owning segment.
A contiguous run of samples evaluated in one pass.