/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli */ #include "BLI_index_mask.hh" #include "BLI_math_base.hh" #include "BLI_math_color.hh" #include "BLI_math_vector.hh" namespace blender::length_parameterize { /** * Return the size of the necessary lengths array for a group of points, taking into account the * possible last cyclic segment. * * \note This is the same as #bke::curves::segments_num. */ inline int segments_num(const int points_num, const bool cyclic) { return cyclic ? points_num : points_num - 1; } /** * Accumulate the length of the next segment into each point. */ template void accumulate_lengths(const Span values, const bool cyclic, MutableSpan lengths) { BLI_assert(lengths.size() == segments_num(values.size(), cyclic)); float length = 0.0f; for (const int i : IndexRange(values.size() - 1)) { length += math::distance(values[i], values[i + 1]); lengths[i] = length; } if (cyclic) { lengths.last() = length + math::distance(values.last(), values.first()); } } template inline void interpolate_to_masked(const Span src, const Span indices, const Span factors, const IndexMask dst_mask, MutableSpan dst) { BLI_assert(indices.size() == factors.size()); BLI_assert(indices.size() == dst_mask.size()); const int last_src_index = src.size() - 1; dst_mask.to_best_mask_type([&](auto dst_mask) { for (const int i : IndexRange(dst_mask.size())) { const int prev_index = indices[i]; const float factor = factors[i]; const bool is_cyclic_case = prev_index == last_src_index; if (is_cyclic_case) { dst[dst_mask[i]] = math::interpolate(src.last(), src.first(), factor); } else { dst[dst_mask[i]] = math::interpolate(src[prev_index], src[prev_index + 1], factor); } } }); } template inline void interpolate(const Span src, const Span indices, const Span factors, MutableSpan dst) { interpolate_to_masked(src, indices, factors, dst.index_range(), dst); } /** * Passing this to consecutive calls of #sample_at_length can increase performance. */ struct SampleSegmentHint { int segment_index = -1; float segment_start; float segment_length_inv; }; /** * \param accumulated_segment_lengths: Lengths of individual segments added up. * Each value describes the total length at the end of the segment following a point. * \param sample_length: The position to sample at. * \param r_segment_index: Returns the index of the segment that #sample_length is in. * \param r_factor: Returns the position within the segment. * * \note #sample_length must not be outside of any segment. */ inline void sample_at_length(const Span accumulated_segment_lengths, const float sample_length, int &r_segment_index, float &r_factor, SampleSegmentHint *hint = nullptr) { /* Use a shorter variable name. */ const Span lengths = accumulated_segment_lengths; BLI_assert(lengths.size() > 0); BLI_assert(sample_length >= 0.0f); BLI_assert(sample_length <= lengths.last()); if (hint != nullptr && hint->segment_index >= 0) { const float length_in_segment = sample_length - hint->segment_start; const float factor = length_in_segment * hint->segment_length_inv; if (factor >= 0.0f && factor < 1.0f) { r_segment_index = hint->segment_index; r_factor = factor; return; } } const float total_length = lengths.last(); if (sample_length >= total_length) { /* Return the last position on the last segment. */ r_segment_index = lengths.size() - 1; r_factor = 1.0f; return; } const int prev_point_index = std::upper_bound(lengths.begin(), lengths.end(), sample_length) - lengths.begin(); const float segment_start = prev_point_index == 0 ? 0.0f : lengths[prev_point_index - 1]; const float segment_end = lengths[prev_point_index]; const float segment_length = segment_end - segment_start; const float segment_length_inv = math::safe_divide(1.0f, segment_length); const float length_in_segment = sample_length - segment_start; const float factor = length_in_segment * segment_length_inv; r_segment_index = prev_point_index; r_factor = factor; if (hint != nullptr) { hint->segment_index = r_segment_index; hint->segment_start = segment_start; hint->segment_length_inv = segment_length_inv; } } /** * Find evenly spaced samples along the lengths. * * \param accumulated_segment_lengths: The accumulated lengths of the original elements being * sampled. Could be calculated by #accumulate_lengths. * \param include_last_point: Generally false for cyclic sequences and true otherwise. * \param r_segment_indices: The index of the previous point at each sample. * \param r_factors: The portion of the length in each segment at each sample. */ void sample_uniform(Span accumulated_segment_lengths, bool include_last_point, MutableSpan r_segment_indices, MutableSpan r_factors); /** * For each provided sample length, find the segment index and interpolation factor. * * \param accumulated_segment_lengths: The accumulated lengths of the original elements being * sampled. Could be calculated by #accumulate_lengths. * \param sample_lengths: Sampled locations in the #lengths array. Must be sorted and is expected * to be within the range of the #lengths values. * \param r_segment_indices: The index of the previous point at each sample. * \param r_factors: The portion of the length in each segment at each sample. */ void sample_at_lengths(Span accumulated_segment_lengths, Span sample_lengths, MutableSpan r_segment_indices, MutableSpan r_factors); } // namespace blender::length_parameterize