diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index b9a7da58a35..2cc5bd1044d 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -854,8 +854,12 @@ bool check_valid_num_and_order(int points_num, int8_t order, bool cyclic, KnotsM * for predictability and so that cached basis weights of NURBS curves with these properties can be * shared. */ -int calculate_evaluated_num( - int points_num, int8_t order, bool cyclic, int resolution, KnotsMode knots_mode); +int calculate_evaluated_num(int points_num, + int8_t order, + bool cyclic, + int resolution, + KnotsMode knots_mode, + Span knots); /** * Calculate the length of the knot vector for a NURBS curve with the given properties. @@ -906,6 +910,7 @@ Vector calculate_multiplicity_sequence(Span knots); void calculate_basis_cache(int points_num, int evaluated_num, int8_t order, + int resolution, bool cyclic, Span knots, BasisCache &basis_cache); diff --git a/source/blender/blenkernel/intern/curve_nurbs.cc b/source/blender/blenkernel/intern/curve_nurbs.cc index de6703e2929..773150d0c13 100644 --- a/source/blender/blenkernel/intern/curve_nurbs.cc +++ b/source/blender/blenkernel/intern/curve_nurbs.cc @@ -32,16 +32,57 @@ bool check_valid_num_and_order(const int points_num, return true; } +static int calc_nonzero_knot_spans(const int points_num, + const KnotsMode mode, + const int8_t order, + const bool cyclic) +{ + 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; + /* For non endpoint Bezier repeated knots are shifted by one. */ + const int knots_before_geometry = order + int(is_bezier && !is_end_point && order > 2); + const int knots_after_geometry = order - 1 + + (cyclic && mode == NURBS_KNOT_MODE_ENDPOINT ? order - 2 : 0); + + const int knots_total = knots_num(points_num, order, cyclic); + /* On these knots as parameters actual geometry is generated. */ + const int geometry_knots = knots_total - knots_before_geometry - knots_after_geometry; + /* `repeat_inner - 1` is added to `ceil`. */ + const int non_zero_knots = (geometry_knots + repeat_inner - 1) / repeat_inner; + return non_zero_knots; +} + +static int count_nonzero_knot_spans(const int points_num, + const int order, + const bool cyclic, + const Span knots) +{ + BLI_assert(points_num > 0); + const int degree = order - 1; + int span_num = 0; + for (const int knot_span : IndexRange::from_begin_end(cyclic ? 0 : degree, points_num)) { + span_num += (knots[knot_span + 1] - knots[knot_span]) > 0.0f; + } + return span_num; +} + int calculate_evaluated_num(const int points_num, const int8_t order, const bool cyclic, const int resolution, - const KnotsMode knots_mode) + const KnotsMode knots_mode, + const Span knots) { if (!check_valid_num_and_order(points_num, order, cyclic, knots_mode)) { return points_num; } - return resolution * segments_num(points_num, cyclic); + const int nonzero_span_num = knots_mode == KnotsMode::NURBS_KNOT_MODE_CUSTOM && + !knots.is_empty() ? + count_nonzero_knot_spans(points_num, order, cyclic, knots) : + calc_nonzero_knot_spans(points_num, knots_mode, order, cyclic); + return resolution * nonzero_span_num + int(!cyclic); } int knots_num(const int points_num, const int8_t order, const bool cyclic) @@ -206,6 +247,7 @@ static void calculate_basis_for_point(const float parameter, void calculate_basis_cache(const int points_num, const int evaluated_num, const int8_t order, + const int resolution, const bool cyclic, const Span knots, BasisCache &basis_cache) @@ -225,19 +267,34 @@ void calculate_basis_cache(const int points_num, MutableSpan basis_start_indices(basis_cache.start_indices); const int last_control_point_index = cyclic ? points_num + degree : points_num; - const int evaluated_segment_num = segments_num(evaluated_num, cyclic); - const float start = knots[degree]; - const float end = knots[last_control_point_index]; - const float step = (end - start) / evaluated_segment_num; - for (const int i : IndexRange(evaluated_num)) { - /* Clamp parameter due to floating point inaccuracy. */ - const float parameter = std::clamp(start + step * i, knots[0], knots[points_num + degree]); + int eval_point = 0; - MutableSpan 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]); + for (const int knot_span : IndexRange::from_begin_end(degree, last_control_point_index)) { + const float start = knots[knot_span]; + const float end = knots[knot_span + 1]; + if (start == end) { + continue; + } + const float step_width = (end - start) / resolution; + for (const int step : IndexRange::from_begin_size(0, resolution)) { + const float parameter = start + step * step_width; + calculate_basis_for_point(parameter, + last_control_point_index, + degree, + knots, + basis_weights.slice(eval_point * order, order), + basis_start_indices[eval_point]); + eval_point++; + } + } + if (!cyclic) { + calculate_basis_for_point(knots[last_control_point_index], + last_control_point_index, + degree, + knots, + basis_weights.slice(eval_point * order, order), + basis_start_indices[eval_point]); } } diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index d9a107d000f..c6a7d39ef24 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -501,7 +501,10 @@ static void build_mesh_positions(const CurvesInfo &curves_info, const bool ignore_profile_position = profile_positions.size() == 1 && math::is_equal(profile_positions.first(), float3(0.0f)); if (ignore_profile_position) { - if (mesh.verts_num == curves_info.main.points_num()) { + if (mesh.verts_num == curves_info.main.points_num() && + /* NURBS can have equal evaluated and positions sizes, but different coords. */ + !curves_info.main.has_curve_with_type(CURVE_TYPE_NURBS)) + { const GAttributeReader src = curves_info.main.attributes().lookup("position"); if (src.sharing_info && src.varray.is_span()) { const AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info); diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index c0b2042c265..29feabb0083 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -657,6 +657,8 @@ static void calculate_evaluated_offsets(const CurvesGeometry &curves, const VArray nurbs_orders = curves.nurbs_orders(); const VArray nurbs_knots_modes = curves.nurbs_knots_modes(); + const OffsetIndices custom_knots_by_curve = curves.nurbs_custom_knots_by_curve(); + const Span all_custom_knots = curves.nurbs_custom_knots(); build_offsets(offsets, [&](const int curve_index) -> int { const IndexRange points = points_by_curve[curve_index]; @@ -676,11 +678,17 @@ static void calculate_evaluated_offsets(const CurvesGeometry &curves, return all_bezier_offsets[offsets.last()]; } case CURVE_TYPE_NURBS: - return curves::nurbs::calculate_evaluated_num(points.size(), - nurbs_orders[curve_index], - cyclic[curve_index], - resolution[curve_index], - KnotsMode(nurbs_knots_modes[curve_index])); + const bool is_cyclic = cyclic[curve_index]; + const int8_t order = nurbs_orders[curve_index]; + const KnotsMode knots_mode = KnotsMode(nurbs_knots_modes[curve_index]); + const IndexRange custom_knots_range = custom_knots_by_curve[curve_index]; + const Span custom_knots = knots_mode == NURBS_KNOT_MODE_CUSTOM && + !all_custom_knots.is_empty() && + !custom_knots_range.is_empty() ? + all_custom_knots.slice(custom_knots_range) : + Span(); + return curves::nurbs::calculate_evaluated_num( + points.size(), order, is_cyclic, resolution[curve_index], knots_mode, custom_knots); } BLI_assert_unreachable(); return 0; @@ -755,6 +763,7 @@ void CurvesGeometry::ensure_nurbs_basis_cache() const const OffsetIndices custom_knots_by_curve = this->nurbs_custom_knots_by_curve(); const VArray cyclic = this->cyclic(); const VArray orders = this->nurbs_orders(); + const VArray resolutions = this->resolution(); const VArray knots_modes = this->nurbs_knots_modes(); const Span custom_knots = this->nurbs_custom_knots(); @@ -765,6 +774,7 @@ void CurvesGeometry::ensure_nurbs_basis_cache() const const IndexRange evaluated_points = evaluated_points_by_curve[curve_index]; const int8_t order = orders[curve_index]; + const int resolution = resolutions[curve_index]; const bool is_cyclic = cyclic[curve_index]; const KnotsMode mode = KnotsMode(knots_modes[curve_index]); @@ -782,8 +792,13 @@ void CurvesGeometry::ensure_nurbs_basis_cache() const custom_knots, knots); - curves::nurbs::calculate_basis_cache( - points.size(), evaluated_points.size(), order, is_cyclic, knots, r_data[curve_index]); + curves::nurbs::calculate_basis_cache(points.size(), + evaluated_points.size(), + order, + resolution, + is_cyclic, + knots, + r_data[curve_index]); } }); }); diff --git a/source/blender/blenkernel/intern/curves_geometry_test.cc b/source/blender/blenkernel/intern/curves_geometry_test.cc index e0deafd901a..8a6d0f16204 100644 --- a/source/blender/blenkernel/intern/curves_geometry_test.cc +++ b/source/blender/blenkernel/intern/curves_geometry_test.cc @@ -329,16 +329,17 @@ TEST(curves_geometry, NURBSEvaluation) Span evaluated_positions = curves.evaluated_positions(); static const Array result_1{{ - {0.166667, 0.833333, 0}, {0.150006, 0.815511, 0}, {0.134453, 0.796582, 0}, - {0.119924, 0.776627, 0}, {0.106339, 0.75573, 0}, {0.0936146, 0.733972, 0}, - {0.0816693, 0.711434, 0}, {0.0704211, 0.6882, 0}, {0.0597879, 0.66435, 0}, - {0.0496877, 0.639968, 0}, {0.0400385, 0.615134, 0}, {0.0307584, 0.589931, 0}, - {0.0217653, 0.564442, 0}, {0.0129772, 0.538747, 0}, {0.00431208, 0.512929, 0}, - {-0.00431208, 0.487071, 0}, {-0.0129772, 0.461253, 0}, {-0.0217653, 0.435558, 0}, - {-0.0307584, 0.410069, 0}, {-0.0400385, 0.384866, 0}, {-0.0496877, 0.360032, 0}, - {-0.0597878, 0.33565, 0}, {-0.0704211, 0.3118, 0}, {-0.0816693, 0.288566, 0}, - {-0.0936146, 0.266028, 0}, {-0.106339, 0.24427, 0}, {-0.119924, 0.223373, 0}, - {-0.134453, 0.203418, 0}, {-0.150006, 0.184489, 0}, {-0.166667, 0.166667, 0}, + {0.166667, 0.833333, 0}, + {0.121333, 0.778667, 0}, + {0.084, 0.716, 0}, + {0.0526667, 0.647333, 0}, + {0.0253333, 0.574667, 0}, + {0, 0.5, 0}, + {-0.0253333, 0.425333, 0}, + {-0.0526667, 0.352667, 0}, + {-0.084, 0.284, 0}, + {-0.121333, 0.221333, 0}, + {-0.166667, 0.166667, 0}, }}; for (const int i : evaluated_positions.index_range()) { EXPECT_V3_NEAR(evaluated_positions[i], result_1[i], 1e-5f); diff --git a/tests/files/io_tests/obj/all_curves.obj b/tests/files/io_tests/obj/all_curves.obj index 6e894c04223..c62588a7263 100644 --- a/tests/files/io_tests/obj/all_curves.obj +++ b/tests/files/io_tests/obj/all_curves.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b265d844db7a6f84520e6c01987eec91eab20bd4eeb3e061e83e6b4a35d4323 -size 8652 +oid sha256:550fa013ce1c048e889087fcbda18c8043456cca3759375064e1b4c8111308be +size 7531 diff --git a/tests/files/io_tests/obj/all_curves_as_nurbs.obj b/tests/files/io_tests/obj/all_curves_as_nurbs.obj index 16007e73e3a..b97065949f2 100644 --- a/tests/files/io_tests/obj/all_curves_as_nurbs.obj +++ b/tests/files/io_tests/obj/all_curves_as_nurbs.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72b89d5470bb42af366946d1d32cc653001facae1266ea6342f1609a190e55a9 +oid sha256:c2098e5dce32abb74d49e49f71479ecdd7a9acfe4f86b699e36584c4103fc4ba size 6709 diff --git a/tests/files/io_tests/obj/all_objects.obj b/tests/files/io_tests/obj/all_objects.obj index 8515098238e..c620d038b5c 100644 --- a/tests/files/io_tests/obj/all_objects.obj +++ b/tests/files/io_tests/obj/all_objects.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d11c9cb2b0299b3d425864f1f583a7de2f4152306d7ed886cdcffb829bf93a8 +oid sha256:f3cc276cb772ecef16c332de06fce6c5bfc577fabdea07a8d3b6293f8727bdfe size 321069 diff --git a/tests/files/io_tests/obj/all_objects_mat_groups.obj b/tests/files/io_tests/obj/all_objects_mat_groups.obj index 590022fd085..d6fc059015d 100644 --- a/tests/files/io_tests/obj/all_objects_mat_groups.obj +++ b/tests/files/io_tests/obj/all_objects_mat_groups.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be872ec81f8cbb50121b40c5de90cb3ca7e19ff1eb1421ba8ad27fa4681473f6 +oid sha256:c3a4e24475815b526f6fc180c9d0016a482434e822a3270846ecda9e103a60c7 size 321239 diff --git a/tests/files/io_tests/obj/nurbs_mesh.obj b/tests/files/io_tests/obj/nurbs_mesh.obj index 7b4d2dea28d..84654ba12a2 100644 --- a/tests/files/io_tests/obj/nurbs_mesh.obj +++ b/tests/files/io_tests/obj/nurbs_mesh.obj @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7da3b2cb989d0fe71a95a4e32b302b538a7baf406d2728d7b5d2265c49cd8c04 +oid sha256:98389651f47f3d6b2f5eca34716d8151a7c6a033e379a9cdc03219514fe034f8 size 4213 diff --git a/tests/files/io_tests/obj/reference/all_curves.txt b/tests/files/io_tests/obj/reference/all_curves.txt index bb2877e61e7..cbb5cd2c6f1 100644 --- a/tests/files/io_tests/obj/reference/all_curves.txt +++ b/tests/files/io_tests/obj/reference/all_curves.txt @@ -65,37 +65,34 @@ - (11.193, 0.000, 0.969) - (10.989, 0.000, 0.883) -- Mesh 'NurbsCurve' vtx:9 face:0 loop:0 edge:8 - - 0/1 1/2 2/3 3/4 4/5 5/6 6/7 7/8 +- Mesh 'NurbsCurve' vtx:4 face:0 loop:0 edge:3 + - 0/1 1/2 2/3 - attr 'position' FLOAT_VECTOR POINT - (7.730, 0.000, -0.375) - - (7.886, 0.000, -0.581) - - (8.063, 0.000, -0.734) - ... - - (8.846, 0.000, -0.920) - - (9.030, 0.000, -0.887) + - (8.189, 0.000, -0.809) + - (8.717, 0.000, -0.927) - (9.197, 0.000, -0.833) -- Mesh 'NurbsCurve2' vtx:24 face:0 loop:0 edge:23 - - 0/1 1/2 2/3 3/4 4/5 ... 18/19 19/20 20/21 21/22 22/23 +- Mesh 'NurbsCurve2' vtx:17 face:0 loop:0 edge:16 + - 0/1 1/2 2/3 3/4 4/5 ... 11/12 12/13 13/14 14/15 15/16 - attr 'position' FLOAT_VECTOR POINT - (5.857, 0.000, 5.211) - - (5.883, 0.000, 4.843) - - (5.990, 0.000, 4.491) + - (5.921, 0.000, 4.685) + - (6.135, 0.000, 4.218) ... - - (9.419, 0.000, 3.357) - - (9.557, 0.000, 3.570) + - (9.283, 0.000, 3.248) + - (9.500, 0.000, 3.465) - (9.665, 0.000, 3.871) -- Mesh 'NurbsPathCurve' vtx:31 face:0 loop:0 edge:29 - - 0/1 1/2 2/3 3/4 4/5 ... 25/26 26/27 27/28 28/29 29/30 +- Mesh 'NurbsPathCurve' vtx:15 face:0 loop:0 edge:13 + - 0/1 1/2 2/3 3/4 4/5 ... 9/10 10/11 11/12 12/13 13/14 - attr 'position' FLOAT_VECTOR POINT - (13.691, 0.000, 0.000) - - (14.059, 0.000, 0.419) - - (14.383, 0.000, 0.670) + - (14.345, 0.000, 0.646) + - (14.865, 0.000, 0.823) ... - - (16.242, 0.000, -0.671) - - (16.339, 0.000, -0.583) + - (15.852, 0.000, -0.877) + - (16.160, 0.000, -0.732) - (16.430, 0.000, -0.479) - Mesh 'PolyCircle' vtx:4 face:0 loop:0 edge:4 diff --git a/tests/files/modeling/geometry_nodes/curves/knot_structure.blend b/tests/files/modeling/geometry_nodes/curves/knot_structure.blend index 8f998616679..7a9d246de3e 100644 --- a/tests/files/modeling/geometry_nodes/curves/knot_structure.blend +++ b/tests/files/modeling/geometry_nodes/curves/knot_structure.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a8eda24804f2c3ac01e93af91af20b45ccbf57fee6bf16b81abc47ce781eb99 -size 145664 +oid sha256:6393161f668410636448320997eabecaff0d2174bcbec6e2b580cac62487b8f1 +size 1603172 diff --git a/tests/files/modeling/geometry_nodes/curves/many_nurbs_curves.blend b/tests/files/modeling/geometry_nodes/curves/many_nurbs_curves.blend index ccd4bfd9f66..605c3b4558e 100644 --- a/tests/files/modeling/geometry_nodes/curves/many_nurbs_curves.blend +++ b/tests/files/modeling/geometry_nodes/curves/many_nurbs_curves.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19a9d325f95220e8ddc3a787a02d18a78480a6a43ad7177d128f069bc6163dbf -size 150542 +oid sha256:fc4ccfecbac1c16487705e4d62137f40874df01f1606a2912d25d01f38fba768 +size 991954 diff --git a/tests/files/modeling/geometry_nodes/curves/normals_free_nurbs.blend b/tests/files/modeling/geometry_nodes/curves/normals_free_nurbs.blend index 2c479992366..c32187b97be 100644 --- a/tests/files/modeling/geometry_nodes/curves/normals_free_nurbs.blend +++ b/tests/files/modeling/geometry_nodes/curves/normals_free_nurbs.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81af832bacc4e62ad28785a998d1b3a09b555058466553481ce30434b041d402 -size 89539 +oid sha256:14b15a0a23f21cfefa67b3b437ae68615464a1275c354ca9b6c92b98f388d584 +size 642331 diff --git a/tests/files/modeling/geometry_nodes/curves/set_handle_type.blend b/tests/files/modeling/geometry_nodes/curves/set_handle_type.blend index 75ee0d36351..a6d831dd05d 100644 --- a/tests/files/modeling/geometry_nodes/curves/set_handle_type.blend +++ b/tests/files/modeling/geometry_nodes/curves/set_handle_type.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6346756e7672b3330f0564518c98419d2616ab5d221e0afa4066397e733db118 -size 135396 +oid sha256:8bdd3fb883c784f7dfe59ab11315596f577f537cb429ca23a7229f1ef5bd0cbb +size 1014822 diff --git a/tests/files/modeling/geometry_nodes/curves/spline_parameter.blend b/tests/files/modeling/geometry_nodes/curves/spline_parameter.blend index a0af4cd52cd..78f1d87faa1 100644 --- a/tests/files/modeling/geometry_nodes/curves/spline_parameter.blend +++ b/tests/files/modeling/geometry_nodes/curves/spline_parameter.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ed3a58f5c685a7dbae64b7cfaee3170b90eced80ea77061ef790f5e9c7b94dd -size 124628 +oid sha256:5bca0f399a4a8eab1d3e9ace766c1acc55f8b1b5c542020a84b7cd3db5876f12 +size 1060327 diff --git a/tests/python/bl_usd_export_test.py b/tests/python/bl_usd_export_test.py index 84eeb455290..a780f2ba518 100644 --- a/tests/python/bl_usd_export_test.py +++ b/tests/python/bl_usd_export_test.py @@ -936,7 +936,7 @@ class USDExportTest(AbstractUSDTest): # Contains 2 NURBS curves curve = UsdGeom.NurbsCurves(stage.GetPrimAtPath("/root/NurbsCurve/NurbsCurve")) - check_nurbs_curve(curve, False, [4, 4], [6, 6], 10, [[-1.75, -2.6898, -1.0117], [3.0896, 1.9583, 1.0293]]) + check_nurbs_curve(curve, False, [4, 4], [6, 6], 10, [[-1.75, -2.6891, -1.0117], [3.0896, 1.9583, 1.0293]]) # Contains 1 NURBS curve curve = UsdGeom.NurbsCurves(stage.GetPrimAtPath("/root/NurbsCircle/NurbsCircle"))