Curves: Enhance tesselation of NURBS with corners
Current NURBS evaluation handles corners or sharp angles poorly. Sharp edges appear when a knot vector value is repeated `order - 1` times. Users can make sharp corners by creating NURBS curve with `Bezier` knot mode or by setting `order` to 2 for legacy curves. The problem occurs because current algorithm takes all the curve's definition interval, divides it into equal parts and evaluates at those points, but corners are exactly on repeated knot's. To hit those, the resolution has to be increased higher than required for the rest of the curve. The new algorithm divides non zero length intervals between two adjacent knots into equal parts. This way corners are hit with a resolution of 1. This does change the evaluated points of NURBS curves, which is why some test results have to be updated in this commit. Pull Request: https://projects.blender.org/blender/blender/pulls/138565
This commit is contained in:
parent
e9bb58e6df
commit
3c407ebeaa
@ -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<float> knots);
|
||||
|
||||
/**
|
||||
* Calculate the length of the knot vector for a NURBS curve with the given properties.
|
||||
@ -906,6 +910,7 @@ Vector<int> calculate_multiplicity_sequence(Span<float> knots);
|
||||
void calculate_basis_cache(int points_num,
|
||||
int evaluated_num,
|
||||
int8_t order,
|
||||
int resolution,
|
||||
bool cyclic,
|
||||
Span<float> knots,
|
||||
BasisCache &basis_cache);
|
||||
|
@ -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<float> 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<float> 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<float> knots,
|
||||
BasisCache &basis_cache)
|
||||
@ -225,19 +267,34 @@ void calculate_basis_cache(const int points_num,
|
||||
MutableSpan<int> 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<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]);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -657,6 +657,8 @@ static void calculate_evaluated_offsets(const CurvesGeometry &curves,
|
||||
|
||||
const VArray<int8_t> nurbs_orders = curves.nurbs_orders();
|
||||
const VArray<int8_t> nurbs_knots_modes = curves.nurbs_knots_modes();
|
||||
const OffsetIndices<int> custom_knots_by_curve = curves.nurbs_custom_knots_by_curve();
|
||||
const Span<float> 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<float> 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<float>();
|
||||
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<int> custom_knots_by_curve = this->nurbs_custom_knots_by_curve();
|
||||
const VArray<bool> cyclic = this->cyclic();
|
||||
const VArray<int8_t> orders = this->nurbs_orders();
|
||||
const VArray<int> resolutions = this->resolution();
|
||||
const VArray<int8_t> knots_modes = this->nurbs_knots_modes();
|
||||
const Span<float> 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]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -329,16 +329,17 @@ TEST(curves_geometry, NURBSEvaluation)
|
||||
|
||||
Span<float3> evaluated_positions = curves.evaluated_positions();
|
||||
static const Array<float3> 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);
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5b265d844db7a6f84520e6c01987eec91eab20bd4eeb3e061e83e6b4a35d4323
|
||||
size 8652
|
||||
oid sha256:550fa013ce1c048e889087fcbda18c8043456cca3759375064e1b4c8111308be
|
||||
size 7531
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:72b89d5470bb42af366946d1d32cc653001facae1266ea6342f1609a190e55a9
|
||||
oid sha256:c2098e5dce32abb74d49e49f71479ecdd7a9acfe4f86b699e36584c4103fc4ba
|
||||
size 6709
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d11c9cb2b0299b3d425864f1f583a7de2f4152306d7ed886cdcffb829bf93a8
|
||||
oid sha256:f3cc276cb772ecef16c332de06fce6c5bfc577fabdea07a8d3b6293f8727bdfe
|
||||
size 321069
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:be872ec81f8cbb50121b40c5de90cb3ca7e19ff1eb1421ba8ad27fa4681473f6
|
||||
oid sha256:c3a4e24475815b526f6fc180c9d0016a482434e822a3270846ecda9e103a60c7
|
||||
size 321239
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7da3b2cb989d0fe71a95a4e32b302b538a7baf406d2728d7b5d2265c49cd8c04
|
||||
oid sha256:98389651f47f3d6b2f5eca34716d8151a7c6a033e379a9cdc03219514fe034f8
|
||||
size 4213
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5a8eda24804f2c3ac01e93af91af20b45ccbf57fee6bf16b81abc47ce781eb99
|
||||
size 145664
|
||||
oid sha256:6393161f668410636448320997eabecaff0d2174bcbec6e2b580cac62487b8f1
|
||||
size 1603172
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:19a9d325f95220e8ddc3a787a02d18a78480a6a43ad7177d128f069bc6163dbf
|
||||
size 150542
|
||||
oid sha256:fc4ccfecbac1c16487705e4d62137f40874df01f1606a2912d25d01f38fba768
|
||||
size 991954
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:81af832bacc4e62ad28785a998d1b3a09b555058466553481ce30434b041d402
|
||||
size 89539
|
||||
oid sha256:14b15a0a23f21cfefa67b3b437ae68615464a1275c354ca9b6c92b98f388d584
|
||||
size 642331
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6346756e7672b3330f0564518c98419d2616ab5d221e0afa4066397e733db118
|
||||
size 135396
|
||||
oid sha256:8bdd3fb883c784f7dfe59ab11315596f577f537cb429ca23a7229f1ef5bd0cbb
|
||||
size 1014822
|
||||
|
@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4ed3a58f5c685a7dbae64b7cfaee3170b90eced80ea77061ef790f5e9c7b94dd
|
||||
size 124628
|
||||
oid sha256:5bca0f399a4a8eab1d3e9ace766c1acc55f8b1b5c542020a84b7cd3db5876f12
|
||||
size 1060327
|
||||
|
@ -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"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user