Curves: Skip CurveEval in legacy curve conversion

Currently when converting from the legacy curve type to the new type,
which happens during evaluation of every legacy curve object, the
`CurveEval` type is used as an intermediate step. This involves
copying all data twice, and allocating a bunch of temporary arrays.
It's also another use of `CurveEval` that has to be removed before
we remove the type.

The main user difference besides the subtlety described below
will be improved performance.

**Invalid Handles and Types**
One important note is that there are two cases (that I know of)
where handles and handle types can be invalid in the old curve
type. The first is animation, where animated handle positions don't
necessary respect the types. The second is control points with a
single aligned handle that didn't necessarily align with the other.

In master (partially on purpose) the code corrects the first situation
(which caused T98965). But it doesn't correct the second situation.
It's trivial to correct for the second case with this patch (because of the
eager calculation decided on in D14464), but this patch makes the choice
not to correct for //either//.

Though not correcting the handle types puts curves in an invalid state,
it also adds flexibility by allowing that option. Users must understand
that any deformation may correct invalid handles.

Fixes T98965

Differential Revision: https://developer.blender.org/D15290
This commit is contained in:
Hans Goudey 2022-06-25 11:11:12 -05:00
parent 2967726a29
commit 5606942c63
Notes: blender-bot 2023-02-14 03:52:45 +01:00
Referenced by commit a646a4b47e, Curves: Port string to curves node to the new data-block
Referenced by issue #98965, Regression: Bezier curves fail to respect keyframed handle positions
4 changed files with 236 additions and 4 deletions

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*/
#include "BKE_curves.hh"
struct Curve;
struct Curves;
namespace blender::bke {
Curves *curve_legacy_to_curves(const Curve &curve_legacy);
Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_list);
} // namespace blender::bke

View File

@ -111,6 +111,7 @@ set(SRC
intern/curve_decimate.c
intern/curve_deform.c
intern/curve_eval.cc
intern/curve_legacy_convert.cc
intern/curve_nurbs.cc
intern/curve_poly.cc
intern/curve_to_mesh_convert.cc
@ -353,6 +354,7 @@ set(SRC
BKE_cryptomatte.h
BKE_cryptomatte.hh
BKE_curve.h
BKE_curve_legacy_convert.hh
BKE_curve_to_mesh.hh
BKE_curveprofile.h
BKE_curves.h

View File

@ -0,0 +1,212 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "DNA_curve_types.h"
#include "DNA_curves_types.h"
#include "BKE_curve.h"
#include "BKE_curve_legacy_convert.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_geometry_set.hh"
namespace blender::bke {
static CurveType curve_type_from_legacy(const short type)
{
switch (type) {
case CU_POLY:
return CURVE_TYPE_POLY;
case CU_BEZIER:
return CURVE_TYPE_BEZIER;
case CU_NURBS:
return CURVE_TYPE_NURBS;
}
BLI_assert_unreachable();
return CURVE_TYPE_POLY;
}
static HandleType handle_type_from_legacy(const uint8_t handle_type_legacy)
{
switch (handle_type_legacy) {
case HD_FREE:
return BEZIER_HANDLE_FREE;
case HD_AUTO:
return BEZIER_HANDLE_AUTO;
case HD_VECT:
return BEZIER_HANDLE_VECTOR;
case HD_ALIGN:
return BEZIER_HANDLE_ALIGN;
case HD_AUTO_ANIM:
return BEZIER_HANDLE_AUTO;
case HD_ALIGN_DOUBLESIDE:
return BEZIER_HANDLE_ALIGN;
}
BLI_assert_unreachable();
return BEZIER_HANDLE_AUTO;
}
static NormalMode normal_mode_from_legacy(const short twist_mode)
{
switch (twist_mode) {
case CU_TWIST_Z_UP:
case CU_TWIST_TANGENT:
return NORMAL_MODE_Z_UP;
case CU_TWIST_MINIMUM:
return NORMAL_MODE_MINIMUM_TWIST;
}
BLI_assert_unreachable();
return NORMAL_MODE_MINIMUM_TWIST;
}
static KnotsMode knots_mode_from_legacy(const short flag)
{
switch (flag & (CU_NURB_ENDPOINT | CU_NURB_BEZIER)) {
case CU_NURB_ENDPOINT:
return NURBS_KNOT_MODE_ENDPOINT;
case CU_NURB_BEZIER:
return NURBS_KNOT_MODE_BEZIER;
case CU_NURB_ENDPOINT | CU_NURB_BEZIER:
return NURBS_KNOT_MODE_ENDPOINT_BEZIER;
case 0:
return NURBS_KNOT_MODE_NORMAL;
}
BLI_assert_unreachable();
return NURBS_KNOT_MODE_NORMAL;
}
Curves *curve_legacy_to_curves(const Curve &curve_legacy, const ListBase &nurbs_list)
{
const Vector<const Nurb *> src_curves(nurbs_list);
Curves *curves_id = curves_new_nomain(0, src_curves.size());
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id->geometry);
CurveComponent component;
component.replace(curves_id, GeometryOwnershipType::Editable);
MutableSpan<int8_t> types = curves.curve_types_for_write();
MutableSpan<bool> cyclic = curves.cyclic_for_write();
int offset = 0;
MutableSpan<int> offsets = curves.offsets_for_write();
for (const int i : src_curves.index_range()) {
offsets[i] = offset;
const Nurb &src_curve = *src_curves[i];
types[i] = curve_type_from_legacy(src_curve.type);
cyclic[i] = src_curve.flagu & CU_NURB_CYCLIC;
offset += src_curve.pntsu;
}
offsets.last() = offset;
curves.resize(curves.offsets().last(), curves.curves_num());
curves.update_curve_types();
MutableSpan<float3> positions = curves.positions_for_write();
OutputAttribute_Typed<float> radius_attribute =
component.attribute_try_get_for_output_only<float>("radius", ATTR_DOMAIN_POINT);
MutableSpan<float> radii = radius_attribute.as_span();
MutableSpan<float> tilts = curves.tilt_for_write();
auto create_poly = [&](IndexMask selection) {
threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) {
for (const int curve_i : selection.slice(range)) {
const Nurb &src_curve = *src_curves[curve_i];
const Span<BPoint> src_points(src_curve.bp, src_curve.pntsu);
const IndexRange points = curves.points_for_curve(curve_i);
for (const int i : src_points.index_range()) {
const BPoint &bp = src_points[i];
positions[points[i]] = bp.vec;
radii[points[i]] = bp.radius;
tilts[points[i]] = bp.tilt;
}
}
});
};
/* Note: For curve handles, legacy curves can end up in invalid situations where the handle
* positions don't agree with the types because of evaluation, or because one-sided aligned
* handles weren't considered. While recalculating automatic handles to fix those situations
* is an option, currently this opts not to for the sake of flexibility. */
auto create_bezier = [&](IndexMask selection) {
MutableSpan<int> resolutions = curves.resolution_for_write();
MutableSpan<float3> handle_positions_l = curves.handle_positions_left_for_write();
MutableSpan<float3> handle_positions_r = curves.handle_positions_right_for_write();
MutableSpan<int8_t> handle_types_l = curves.handle_types_left_for_write();
MutableSpan<int8_t> handle_types_r = curves.handle_types_right_for_write();
threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) {
for (const int curve_i : selection.slice(range)) {
const Nurb &src_curve = *src_curves[curve_i];
const Span<BezTriple> src_points(src_curve.bezt, src_curve.pntsu);
const IndexRange points = curves.points_for_curve(curve_i);
resolutions[curve_i] = src_curve.resolu;
for (const int i : src_points.index_range()) {
const BezTriple &point = src_points[i];
positions[points[i]] = point.vec[1];
handle_positions_l[points[i]] = point.vec[0];
handle_types_l[points[i]] = handle_type_from_legacy(point.h1);
handle_positions_r[points[i]] = point.vec[2];
handle_types_r[points[i]] = handle_type_from_legacy(point.h2);
radii[points[i]] = point.radius;
tilts[points[i]] = point.tilt;
}
}
});
};
auto create_nurbs = [&](IndexMask selection) {
threading::parallel_for(selection.index_range(), 246, [&](IndexRange range) {
MutableSpan<int> resolutions = curves.resolution_for_write();
MutableSpan<float> nurbs_weights = curves.nurbs_weights_for_write();
MutableSpan<int8_t> nurbs_orders = curves.nurbs_orders_for_write();
MutableSpan<int8_t> nurbs_knots_modes = curves.nurbs_knots_modes_for_write();
for (const int curve_i : selection.slice(range)) {
const Nurb &src_curve = *src_curves[curve_i];
const Span src_points(src_curve.bp, src_curve.pntsu);
const IndexRange points = curves.points_for_curve(curve_i);
resolutions[curve_i] = src_curve.resolu;
nurbs_orders[curve_i] = src_curve.orderu;
nurbs_knots_modes[curve_i] = knots_mode_from_legacy(src_curve.flagu);
for (const int i : src_points.index_range()) {
const BPoint &bp = src_points[i];
positions[points[i]] = bp.vec;
radii[points[i]] = bp.radius;
tilts[points[i]] = bp.tilt;
nurbs_weights[points[i]] = bp.vec[3];
}
}
});
};
bke::curves::foreach_curve_by_type(
curves.curve_types(),
curves.curve_type_counts(),
curves.curves_range(),
[&](IndexMask /* selection */) {},
create_poly,
create_bezier,
create_nurbs);
curves.normal_mode_for_write().fill(normal_mode_from_legacy(curve_legacy.twist_mode));
radius_attribute.save();
return curves_id;
}
Curves *curve_legacy_to_curves(const Curve &curve_legacy)
{
return curve_legacy_to_curves(curve_legacy, *BKE_curve_nurbs_get_for_read(&curve_legacy));
}
} // namespace blender::bke

View File

@ -30,6 +30,7 @@
#include "BKE_anim_path.h"
#include "BKE_curve.h"
#include "BKE_curve_legacy_convert.hh"
#include "BKE_displist.h"
#include "BKE_geometry_set.hh"
#include "BKE_key.h"
@ -40,7 +41,6 @@
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_spline.hh"
#include "BKE_vfont.h"
#include "BLI_sys_types.h" /* For #intptr_t support. */
@ -863,9 +863,8 @@ static GeometrySet curve_calc_modifiers_post(Depsgraph *depsgraph,
geometry_set.replace_mesh(mesh);
}
else {
std::unique_ptr<CurveEval> curve_eval = curve_eval_from_dna_curve(
*cu, ob->runtime.curve_cache->deformed_nurbs);
geometry_set.replace_curves(curve_eval_to_curves(*curve_eval));
geometry_set.replace_curves(
blender::bke::curve_legacy_to_curves(*cu, ob->runtime.curve_cache->deformed_nurbs));
}
for (; md; md = md->next) {