Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenlib/BLI_length_parameterize.hh
- This file was added.
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | |||||
| #pragma once | |||||
| /** \file | |||||
| * \ingroup bli | |||||
| */ | |||||
| #include "BLI_color.hh" | |||||
| #include "BLI_math_base.hh" | |||||
| #include "BLI_math_vector.h" | |||||
| #include "BLI_math_vector.hh" | |||||
| #include "BLI_vector.hh" | |||||
| namespace blender::length_parameterize { | |||||
| /* TODO: Move these to blender::math somehow, including `Color` and other types. */ | |||||
| template<typename T> T mix2(float factor, const T &a, const T &b); | |||||
| template<> inline bool mix2(const float factor, const bool &a, const bool &b) | |||||
| { | |||||
| return ((1.0f - factor) * a + factor * b) >= 0.5f; | |||||
| } | |||||
JacquesLucke: I think it is not obvious that a curve cannot be cyclic when it only has two points. I don't… | |||||
Done Inline ActionsI think I was copying the logic from existing curve code, but I also can't think of a fundamental reason, at this level. HooglyBoogly: I think I was copying the logic from existing curve code, but I also can't think of a… | |||||
Done Inline ActionsWell, there are always ways to visualize it. We could just use the arrows that you see when adjusting the tilt. Then better mention that reason in a comment. JacquesLucke: Well, there are always ways to visualize it. We could just use the arrows that you see when… | |||||
Done Inline ActionsHonestly I don't remember the reasoning well enough to document it. I'll just remove that 2 point check for now I think. If it becomes a problem we can add it back. HooglyBoogly: Honestly I don't remember the reasoning well enough to document it. I'll just remove that 2… | |||||
| template<> inline int8_t mix2(const float factor, const int8_t &a, const int8_t &b) | |||||
| { | |||||
| return static_cast<int8_t>((1.0f - factor) * a + factor * b); | |||||
| } | |||||
| template<> inline int mix2(const float factor, const int &a, const int &b) | |||||
| { | |||||
| return static_cast<int>((1.0f - factor) * a + factor * b); | |||||
| } | |||||
| template<> inline float mix2(const float factor, const float &a, const float &b) | |||||
| { | |||||
| return (1.0f - factor) * a + factor * b; | |||||
| } | |||||
| template<> inline float2 mix2(const float factor, const float2 &a, const float2 &b) | |||||
| { | |||||
| return math::interpolate(a, b, factor); | |||||
| } | |||||
| template<> inline float3 mix2(const float factor, const float3 &a, const float3 &b) | |||||
| { | |||||
| return math::interpolate(a, b, factor); | |||||
| } | |||||
| template<> | |||||
| inline ColorGeometry4f mix2(const float factor, const ColorGeometry4f &a, const ColorGeometry4f &b) | |||||
| { | |||||
| ColorGeometry4f result; | |||||
| interp_v4_v4v4(result, a, b, factor); | |||||
| return result; | |||||
| } | |||||
| /** | |||||
| * \return The size of the necessary lengths array for a group of points, taking into account the | |||||
| * possible last cyclic segment. | |||||
| */ | |||||
| inline int lengths_size(const int values_size, const bool cyclic) | |||||
| { | |||||
| return values_size - (cyclic ? 0 : 1); | |||||
| } | |||||
| /** | |||||
| * Accumulate the length of the next segment into each point. | |||||
| */ | |||||
| template<typename T> | |||||
| void accumulate_lengths(const Span<T> values, const bool cyclic, MutableSpan<float> lengths) | |||||
| { | |||||
| BLI_assert(lengths.size() == lengths_size(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()); | |||||
| } | |||||
| } | |||||
| class LengthResampler { | |||||
| Span<float> lengths_; | |||||
| bool cyclic_; | |||||
| int src_points_size_; | |||||
| int src_edges_size_; | |||||
| /** The index of the sample's previous point. */ | |||||
| Vector<int> sample_indices_; | |||||
| /** The sample's factor from the previous point to the next point. */ | |||||
| Vector<float> sample_factors_; | |||||
| /** Sample factors for the last cyclic segment connecting the first and last point. */ | |||||
Done Inline ActionsEven when count is zero or one? JacquesLucke: Even when `count` is zero or one? | |||||
Done Inline ActionsGood point, I adjusted the comment. And the function expects the count to be greater than zero so I added that too. HooglyBoogly: Good point, I adjusted the comment. And the function expects the count to be greater than zero… | |||||
| Vector<float> cyclic_samples_; | |||||
| public: | |||||
| LengthResampler(Span<float> lengths, const bool cyclic) : lengths_(lengths), cyclic_(cyclic) | |||||
| { | |||||
| BLI_assert(std::is_sorted(lengths_.begin(), lengths_.end())); | |||||
| BLI_assert(lengths_.size() >= 1); | |||||
| src_edges_size_ = lengths_.size(); | |||||
| src_points_size_ = cyclic_ ? src_edges_size_ : src_edges_size_ + 1; | |||||
| } | |||||
| /** | |||||
| * Find the given number of points, evenly spaced along the lengths provided to the sampler. For | |||||
| * non-cyclic sequences, the first and last point will always be included, for cyclic sequences, | |||||
| * the first point will always be included. | |||||
| */ | |||||
| void sample(const int count) | |||||
| { | |||||
| sample_indices_.clear(); | |||||
| sample_factors_.clear(); | |||||
| cyclic_samples_.clear(); | |||||
| sample_indices_.reserve(count); | |||||
| sample_factors_.reserve(count); | |||||
| if (count == 1) { | |||||
| return; | |||||
| } | |||||
| const float total_length = lengths_.last(); | |||||
| const float sample_length = total_length / (count - (cyclic_ ? 0 : 1)); | |||||
| /* Start at the first sample to avoid storing the first, which always corresponds exactly to | |||||
| * the first source point anyway. */ | |||||
| int i_dst = 1; | |||||
| /* Store the length at the previous point in a variable so it can start out at zero | |||||
| * (the lengths array doesn't contain 0 for the first point). */ | |||||
| float prev_length = 0.0f; | |||||
| for (const int i_src : IndexRange(src_points_size_ - 1)) { | |||||
| const float next_length = lengths_[i_src]; | |||||
| const float segment_length = next_length - prev_length; | |||||
| /* Add every sample that fits in this edge. */ | |||||
| while (sample_length * i_dst <= next_length) { | |||||
| const float length_in_segment = sample_length * i_dst - prev_length; | |||||
| const float factor = length_in_segment / segment_length; | |||||
| sample_indices_.append_unchecked(i_src); | |||||
| sample_factors_.append_unchecked(factor); | |||||
| i_dst++; | |||||
| } | |||||
| prev_length = next_length; | |||||
| } | |||||
| /* Deal with the samples for the last cyclic segment separately. */ | |||||
| if (cyclic_) { | |||||
| const float segment_length = lengths_.last() - lengths_.last(1); | |||||
| while (sample_length * i_dst < total_length) { | |||||
| const float length_in_segment = sample_length * i_dst - prev_length; | |||||
| const float factor = length_in_segment / segment_length; | |||||
| cyclic_samples_.append(factor); | |||||
| i_dst++; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* Do linear interpolation of the values from the samples' neighboring points. */ | |||||
| template<typename T> void interpolate_with_samples(const Span<T> src, MutableSpan<T> dst) const | |||||
| { | |||||
| BLI_assert(!sample_indices_.is_empty()); | |||||
| BLI_assert(src.size() == src_points_size_); | |||||
| dst.first() = src.first(); | |||||
| dst = dst.drop_front(1); | |||||
| if (cyclic_) { | |||||
| for (const int i : sample_indices_.index_range()) { | |||||
| const int index = sample_indices_[i]; | |||||
| const float factor = sample_factors_[i]; | |||||
| dst[i] = mix2(factor, src[index], src[index + 1]); | |||||
| } | |||||
| MutableSpan<T> cyclic_results = dst.take_back(cyclic_samples_.size()); | |||||
| for (const int i : cyclic_samples_.index_range()) { | |||||
| cyclic_results[i] = mix2(cyclic_samples_[i], src.last(), src.first()); | |||||
| } | |||||
| } | |||||
| else { | |||||
| for (const int i : sample_indices_.index_range()) { | |||||
| const int index = sample_indices_[i]; | |||||
| const float factor = sample_factors_[i]; | |||||
| dst[i] = mix2(factor, src[index], src[index + 1]); | |||||
| } | |||||
| dst.last() = src.last(); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace blender::length_parameterize | |||||
I think it is not obvious that a curve cannot be cyclic when it only has two points. I don't see a fundamental reason for why this should be the case. If there is a reason for this behavior, it should be mentioned here.