Curves: Simplify calculation of NURBS basis functions

Replaces current basis function calculation which seems to be a direct
implementation of the recursive NURBS formulation. New implementation
avoids the need to check for zero divisions during iteration. Out of
bounds checks are also converted to asserts, assuming input provides
valid span index.

Performance wise this nets a 7+% performance improvement with the
average result being as fast or faster then the fastest execution
from previous implementation!

Pull Request: https://projects.blender.org/blender/blender/pulls/144457
This commit is contained in:
Mattias Fredriksson
2025-09-04 21:49:58 +02:00
committed by Hans Goudey
parent fff3af04c4
commit ccd8926717
8 changed files with 27 additions and 38 deletions

View File

@@ -190,47 +190,38 @@ Vector<int> calculate_multiplicity_sequence(const Span<float> knots)
return multiplicity;
}
/* Basis function calculation, implementation based on 'The NURBS Book' p. 70, ISBN: 3540615458.
*/
static void calculate_basis_for_point(const Span<float> knots,
const int degree,
const int wrapped_points_num,
const float parameter,
const int span_index,
MutableSpan<float> r_weights,
int &r_start_index)
{
BLI_assert(degree >= 1);
BLI_assert(span_index >= degree);
BLI_assert(span_index + degree < knots.size());
BLI_assert(knots[span_index + 1] > knots[span_index]);
const int order = degree + 1;
const int start = std::max(span_index - degree, 0);
int end = span_index;
r_start_index = span_index - degree;
Array<float, 12> buffer(order * 2, 0.0f);
buffer[end - start] = 1.0f;
Array<float, 12> left(order);
Array<float, 12> right(order);
r_weights[0] = 1.0f;
for (const int i_order : IndexRange(2, degree)) {
if (end + i_order >= knots.size()) {
end = wrapped_points_num + 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;
for (const int j : IndexRange(1, degree)) {
left[j] = parameter - knots[span_index + 1 - j];
right[j] = knots[span_index + j] - parameter;
float saved = 0.0f;
for (const int r : IndexRange(j)) {
const float temp = r_weights[r] / (right[r + 1] + left[j - r]);
r_weights[r] = saved + right[r + 1] * temp;
saved = left[j - r] * temp;
}
r_weights[j] = saved;
}
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 points_num,
@@ -288,7 +279,6 @@ void calculate_basis_cache(const int points_num,
const float parameter = knots[span_index] + step * knot_step;
calculate_basis_for_point(knots,
degree,
wrapped_points_num,
parameter,
span_index,
basis_weights.slice(eval_point * order, order),
@@ -300,7 +290,6 @@ void calculate_basis_cache(const int points_num,
if (!cyclic) {
calculate_basis_for_point(knots,
degree,
wrapped_points_num,
knots[wrapped_points_num],
span_offsets.last(),
basis_weights.slice(basis_weights.size() - order, order),

BIN
tests/files/io_tests/obj/all_curves.obj (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,4 @@
# Blender 4.1.0 Alpha MTL File: 'all_objects.blend'
# Blender 5.0.0 Alpha MTL File: 'all_objects.blend'
# www.blender.org
newmtl Blue

BIN
tests/files/io_tests/obj/all_objects.obj (Stored with Git LFS)

Binary file not shown.

View File

@@ -1,4 +1,4 @@
# Blender 4.1.0 Alpha MTL File: 'all_objects.blend'
# Blender 5.0.0 Alpha MTL File: 'all_objects.blend'
# www.blender.org
newmtl Blue

Binary file not shown.

BIN
tests/files/io_tests/obj/nurbs_mesh.obj (Stored with Git LFS)

Binary file not shown.