Curves: support more curve type conversion options in edit mode

This adds a new `Handles` checkbox to the conversion operator that
affects how the conversion works in the following cases:
`Bezier -> Catmull Rom / Poly / Nurbs` and `Catmull Rom -> Nurbs`.
If enabled, three control points are added for each original control
point, otherwise only one.

-----

The images show the effect of the toggle. The top result is always the one with handles and the bottom one without.
* `Bezier -> Poly`
  ![image](/attachments/c4833568-fb8a-415e-b4fc-a8af2002ded8)
* `Bezier -> Catmull Rom`
  ![image](/attachments/df62e4c0-1a88-4f04-aa82-506bc40765a2)
* `Bezier -> Nurbs`
  ![image](/attachments/3b78d49d-c840-4c15-a342-050fb1f5c3f5)
* `Catmull Rom -> Nurbs`
  ![image](/attachments/de9a4c08-d442-4f97-a0e0-6393b0f0e6de)

Pull Request: https://projects.blender.org/blender/blender/pulls/120423
This commit is contained in:
Jacques Lucke
2024-04-25 10:56:43 +02:00
parent fa66b52d0a
commit 3f2c4db951
3 changed files with 190 additions and 5 deletions

View File

@@ -1394,6 +1394,7 @@ namespace curve_type_set {
static int exec(bContext *C, wmOperator *op)
{
const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
for (Curves *curves_id : get_unique_editable_curves(*C)) {
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
@@ -1403,7 +1404,13 @@ static int exec(bContext *C, wmOperator *op)
continue;
}
curves = geometry::convert_curves(curves, selection, dst_type, {});
geometry::ConvertCurvesOptions options;
options.convert_bezier_handles_to_poly_points = use_handles;
options.convert_bezier_handles_to_catmull_rom_points = use_handles;
options.keep_bezier_shape_as_nurbs = use_handles;
options.keep_catmull_rom_shape_as_nurbs = use_handles;
curves = geometry::convert_curves(curves, selection, dst_type, {}, options);
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
@@ -1426,6 +1433,12 @@ static void CURVES_OT_curve_type_set(wmOperatorType *ot)
ot->prop = RNA_def_enum(
ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
RNA_def_boolean(ot->srna,
"use_handles",
false,
"Handles",
"Take handle information into account in the conversion");
}
namespace switch_direction {

View File

@@ -11,12 +11,28 @@
namespace blender::geometry {
struct ConvertCurvesOptions {
bool convert_bezier_handles_to_poly_points = false;
bool convert_bezier_handles_to_catmull_rom_points = false;
/**
* Make the nurb curve behave like a bezier curve and also keep the handle positions as control
* points.
*/
bool keep_bezier_shape_as_nurbs = true;
/**
* Keep the exact shape of the catmull rom curve by inserting extra handle control points in the
* nurbs curve.
*/
bool keep_catmull_rom_shape_as_nurbs = true;
};
/**
* Change the types of the selected curves, potentially changing the total point count.
*/
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info);
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options = {});
} // namespace blender::geometry

View File

@@ -629,19 +629,175 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src
return dst_curves;
}
static bke::CurvesGeometry convert_curves_to_catmull_rom_or_poly(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options)
{
const bool use_bezier_handles = (dst_type == CURVE_TYPE_CATMULL_ROM) ?
options.convert_bezier_handles_to_catmull_rom_points :
options.convert_bezier_handles_to_poly_points;
if (!use_bezier_handles || !src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
return convert_curves_trivial(src_curves, selection, dst_type);
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const VArray<int8_t> src_types = src_curves.curve_types();
const VArray<bool> src_cyclic = src_curves.cyclic();
const Span<float3> src_positions = src_curves.positions();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
dst_curves.fill_curve_types(selection, dst_type);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
selection.foreach_index(GrainSize(1024), [&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const CurveType src_curve_type = CurveType(src_types[i]);
int &size = dst_offsets[i];
if (src_curve_type == CURVE_TYPE_BEZIER) {
size = src_points.size() * 3;
}
else {
size = src_points.size();
}
});
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
Vector<bke::AttributeTransferData> generic_attributes = bke::retrieve_attributes_for_transfer(
src_attributes,
dst_attributes,
ATTR_DOMAIN_MASK_POINT,
propagation_info,
{"position",
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left",
"nurbs_weight"});
auto convert_from_catmull_rom_or_poly_or_nurbs = [&](const IndexMask &selection) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
for (bke::AttributeTransferData &attribute : generic_attributes) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto convert_from_bezier = [&](const IndexMask &selection) {
const Span<float3> src_left_handles = src_curves.handle_positions_left();
const Span<float3> src_right_handles = src_curves.handle_positions_right();
/* Transfer positions. */
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
dst_positions[dst_points_start + 0] = src_left_handles[src_point_i];
dst_positions[dst_points_start + 1] = src_positions[src_point_i];
dst_positions[dst_points_start + 2] = src_right_handles[src_point_i];
}
});
/* Transfer attributes. The handles the same attribute values as their corresponding control
* point. */
for (bke::AttributeTransferData &attribute : generic_attributes) {
const CPPType &cpp_type = attribute.src.type();
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
const void *src_value = attribute.src[src_point_i];
cpp_type.fill_assign_n(src_value, attribute.dst.span[dst_points_start], 3);
}
});
}
};
bke::curves::foreach_curve_by_type(src_curves.curve_types(),
src_curves.curve_type_counts(),
selection,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_bezier,
convert_from_catmull_rom_or_poly_or_nurbs);
for (bke::AttributeTransferData &attribute : generic_attributes) {
attribute.dst.finish();
}
bke::copy_attributes_group_to_group(src_attributes,
bke::AttrDomain::Point,
propagation_info,
{},
src_points_by_curve,
dst_points_by_curve,
unselected,
dst_attributes);
return dst_curves;
}
/**
* Converts some curves to poly curves before they are converted to nurbs. This is useful because
* it discards the bezier/catmull-rom shape which is sometimes the desired behavior.
*/
static bke::CurvesGeometry convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const ConvertCurvesOptions &options)
{
const VArray<int8_t> src_curve_types = src_curves.curve_types();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(
selection, GrainSize(4096), memory, [&](const int curve_i) {
const CurveType type = CurveType(src_curve_types[curve_i]);
if (!options.keep_bezier_shape_as_nurbs && type == CURVE_TYPE_BEZIER) {
return true;
}
if (!options.keep_catmull_rom_shape_as_nurbs && type == CURVE_TYPE_CATMULL_ROM) {
return true;
}
return false;
});
return convert_curves_trivial(src_curves, mask, CURVE_TYPE_POLY);
}
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info)
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options)
{
switch (dst_type) {
case CURVE_TYPE_CATMULL_ROM:
case CURVE_TYPE_POLY:
return convert_curves_trivial(src_curves, selection, dst_type);
return convert_curves_to_catmull_rom_or_poly(
src_curves, selection, dst_type, propagation_info, options);
case CURVE_TYPE_BEZIER:
return convert_curves_to_bezier(src_curves, selection, propagation_info);
case CURVE_TYPE_NURBS:
case CURVE_TYPE_NURBS: {
if (!options.keep_bezier_shape_as_nurbs || !options.keep_catmull_rom_shape_as_nurbs) {
const bke::CurvesGeometry tmp_src_curves =
convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
src_curves, selection, options);
return convert_curves_to_nurbs(tmp_src_curves, selection, propagation_info);
}
return convert_curves_to_nurbs(src_curves, selection, propagation_info);
}
}
BLI_assert_unreachable();
return {};