Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenkernel/intern/curve_nurbs.cc')
-rw-r--r--source/blender/blenkernel/intern/curve_nurbs.cc252
1 files changed, 252 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/curve_nurbs.cc b/source/blender/blenkernel/intern/curve_nurbs.cc
new file mode 100644
index 00000000000..3bec01520e8
--- /dev/null
+++ b/source/blender/blenkernel/intern/curve_nurbs.cc
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "BKE_attribute_math.hh"
+
+#include "BKE_curves.hh"
+
+namespace blender::bke::curves::nurbs {
+
+bool check_valid_size_and_order(const int size,
+ const int8_t order,
+ const bool cyclic,
+ const KnotsMode knots_mode)
+{
+ if (size < order) {
+ return false;
+ }
+
+ if (ELEM(knots_mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER)) {
+ if (knots_mode == NURBS_KNOT_MODE_BEZIER && size <= order) {
+ return false;
+ }
+ return (!cyclic || size % (order - 1) == 0);
+ }
+
+ return true;
+}
+
+int calculate_evaluated_size(const int size,
+ const int8_t order,
+ const bool cyclic,
+ const int resolution,
+ const KnotsMode knots_mode)
+{
+ if (!check_valid_size_and_order(size, order, cyclic, knots_mode)) {
+ return 0;
+ }
+ return resolution * curve_segment_size(size, cyclic);
+}
+
+int knots_size(const int size, const int8_t order, const bool cyclic)
+{
+ if (cyclic) {
+ return size + order * 2 - 1;
+ }
+ return size + order;
+}
+
+void calculate_knots(const int size,
+ const KnotsMode mode,
+ const int8_t order,
+ const bool cyclic,
+ MutableSpan<float> knots)
+{
+ BLI_assert(knots.size() == knots_size(size, order, cyclic));
+ UNUSED_VARS_NDEBUG(size);
+
+ const bool is_bezier = ELEM(mode, NURBS_KNOT_MODE_BEZIER, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
+ const bool is_end_point = ELEM(mode, NURBS_KNOT_MODE_ENDPOINT, NURBS_KNOT_MODE_ENDPOINT_BEZIER);
+ /* Inner knots are always repeated once except on Bezier case. */
+ const int repeat_inner = is_bezier ? order - 1 : 1;
+ /* How many times to repeat 0.0 at the beginning of knot. */
+ const int head = is_end_point ? (order - (cyclic ? 1 : 0)) :
+ (is_bezier ? min_ii(2, repeat_inner) : 1);
+ /* Number of knots replicating widths of the starting knots.
+ * Covers both Cyclic and EndPoint cases. */
+ const int tail = cyclic ? 2 * order - 1 : (is_end_point ? order : 0);
+
+ int r = head;
+ float current = 0.0f;
+
+ const int offset = is_end_point && cyclic ? 1 : 0;
+ if (offset) {
+ knots[0] = current;
+ current += 1.0f;
+ }
+
+ for (const int i : IndexRange(offset, knots.size() - offset - tail)) {
+ knots[i] = current;
+ r--;
+ if (r == 0) {
+ current += 1.0;
+ r = repeat_inner;
+ }
+ }
+
+ const int tail_index = knots.size() - tail;
+ for (const int i : IndexRange(tail)) {
+ knots[tail_index + i] = current + (knots[i] - knots[0]);
+ }
+}
+
+static void calculate_basis_for_point(const float parameter,
+ const int size,
+ const int degree,
+ const Span<float> knots,
+ MutableSpan<float> r_weights,
+ int &r_start_index)
+{
+ const int order = degree + 1;
+
+ int start = 0;
+ int end = 0;
+ for (const int i : IndexRange(size + degree)) {
+ const bool knots_equal = knots[i] == knots[i + 1];
+ if (knots_equal || parameter < knots[i] || parameter > knots[i + 1]) {
+ continue;
+ }
+
+ start = std::max(i - degree, 0);
+ end = i;
+ break;
+ }
+
+ Array<float, 12> buffer(order * 2, 0.0f);
+
+ buffer[end - start] = 1.0f;
+
+ for (const int i_order : IndexRange(2, degree)) {
+ if (end + i_order >= knots.size()) {
+ end = size + degree - i_order;
+ }
+ for (const int i : IndexRange(end - start + 1)) {
+ const int knot_index = start + i;
+
+ float new_basis = 0.0f;
+ if (buffer[i] != 0.0f) {
+ new_basis += ((parameter - knots[knot_index]) * buffer[i]) /
+ (knots[knot_index + i_order - 1] - knots[knot_index]);
+ }
+
+ if (buffer[i + 1] != 0.0f) {
+ new_basis += ((knots[knot_index + i_order] - parameter) * buffer[i + 1]) /
+ (knots[knot_index + i_order] - knots[knot_index + 1]);
+ }
+
+ buffer[i] = new_basis;
+ }
+ }
+
+ buffer.as_mutable_span().drop_front(end - start + 1).fill(0.0f);
+ r_weights.copy_from(buffer.as_span().take_front(order));
+ r_start_index = start;
+}
+
+void calculate_basis_cache(const int size,
+ const int evaluated_size,
+ const int8_t order,
+ const bool cyclic,
+ const Span<float> knots,
+ BasisCache &basis_cache)
+{
+ BLI_assert(size > 0);
+ BLI_assert(evaluated_size > 0);
+
+ const int8_t degree = order - 1;
+
+ basis_cache.weights.resize(evaluated_size * order);
+ basis_cache.start_indices.resize(evaluated_size);
+
+ if (evaluated_size == 0) {
+ return;
+ }
+
+ MutableSpan<float> basis_weights(basis_cache.weights);
+ MutableSpan<int> basis_start_indices(basis_cache.start_indices);
+
+ const int last_control_point_index = cyclic ? size + degree : size;
+ const int evaluated_segment_size = curve_segment_size(evaluated_size, cyclic);
+
+ const float start = knots[degree];
+ const float end = knots[last_control_point_index];
+ const float step = (end - start) / evaluated_segment_size;
+ for (const int i : IndexRange(evaluated_size)) {
+ /* Clamp parameter due to floating point inaccuracy. */
+ const float parameter = std::clamp(start + step * i, knots[0], knots[size + degree]);
+
+ MutableSpan<float> point_weights = basis_weights.slice(i * order, order);
+
+ calculate_basis_for_point(
+ parameter, last_control_point_index, degree, knots, point_weights, basis_start_indices[i]);
+ }
+}
+
+template<typename T>
+static void interpolate_to_evaluated(const BasisCache &basis_cache,
+ const int8_t order,
+ const Span<T> src,
+ MutableSpan<T> dst)
+{
+ attribute_math::DefaultMixer<T> mixer{dst};
+
+ for (const int i : dst.index_range()) {
+ Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
+
+ for (const int j : point_weights.index_range()) {
+ const int point_index = (basis_cache.start_indices[i] + j) % src.size();
+ mixer.mix_in(i, src[point_index], point_weights[j]);
+ }
+ }
+
+ mixer.finalize();
+}
+
+template<typename T>
+static void interpolate_to_evaluated_rational(const BasisCache &basis_cache,
+ const int8_t order,
+ const Span<float> control_weights,
+ const Span<T> src,
+ MutableSpan<T> dst)
+{
+ attribute_math::DefaultMixer<T> mixer{dst};
+
+ for (const int i : dst.index_range()) {
+ Span<float> point_weights = basis_cache.weights.as_span().slice(i * order, order);
+
+ for (const int j : point_weights.index_range()) {
+ const int point_index = (basis_cache.start_indices[i] + j) % src.size();
+ const float weight = point_weights[j] * control_weights[point_index];
+ mixer.mix_in(i, src[point_index], weight);
+ }
+ }
+
+ mixer.finalize();
+}
+
+void interpolate_to_evaluated(const BasisCache &basis_cache,
+ const int8_t order,
+ const Span<float> control_weights,
+ const fn::GSpan src,
+ fn::GMutableSpan dst)
+{
+ BLI_assert(dst.size() == basis_cache.start_indices.size());
+
+ attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
+ using T = decltype(dummy);
+ if constexpr (!std::is_void_v<attribute_math::DefaultMixer<T>>) {
+ if (control_weights.is_empty()) {
+ interpolate_to_evaluated(basis_cache, order, src.typed<T>(), dst.typed<T>());
+ }
+ else {
+ interpolate_to_evaluated_rational(
+ basis_cache, order, control_weights, src.typed<T>(), dst.typed<T>());
+ }
+ }
+ });
+}
+
+} // namespace blender::bke::curves::nurbs