From 4889aed8ac8f3c604e7e735da8ef6ecc7591ab42 Mon Sep 17 00:00:00 2001 From: Laurynas Duburas Date: Wed, 3 Apr 2024 16:40:36 +0200 Subject: [PATCH] Curves: Bezier handle selection support Adds ".selection_handle_left" and ".selection_handle_right" attributes to `CurvesGeometry` and their support in curve's select all, pick, box, circle and lasso select tools. Pull Request: https://projects.blender.org/blender/blender/pulls/119712 --- .../draw/intern/draw_cache_impl_curves.cc | 4 +- .../editors/curves/intern/curves_draw.cc | 63 +- .../editors/curves/intern/curves_edit.cc | 24 +- .../editors/curves/intern/curves_ops.cc | 33 +- .../editors/curves/intern/curves_selection.cc | 579 +++++++++++++----- source/blender/editors/include/ED_curves.hh | 66 +- .../editors/space_view3d/view3d_select.cc | 112 ++-- 7 files changed, 618 insertions(+), 263 deletions(-) diff --git a/source/blender/draw/intern/draw_cache_impl_curves.cc b/source/blender/draw/intern/draw_cache_impl_curves.cc index b1085a68a43..fb4b2d92084 100644 --- a/source/blender/draw/intern/draw_cache_impl_curves.cc +++ b/source/blender/draw/intern/draw_cache_impl_curves.cc @@ -407,9 +407,9 @@ static void create_edit_points_selection(const bke::CurvesGeometry &curves, } const VArray attribute_left = *curves.attributes().lookup_or_default( - ".selection_handle_left", bke::AttrDomain::Point, 0.0f); + ".selection_handle_left", bke::AttrDomain::Point, 1.0f); const VArray attribute_right = *curves.attributes().lookup_or_default( - ".selection_handle_right", bke::AttrDomain::Point, 0.0f); + ".selection_handle_right", bke::AttrDomain::Point, 1.0f); const OffsetIndices points_by_curve = curves.points_by_curve(); diff --git a/source/blender/editors/curves/intern/curves_draw.cc b/source/blender/editors/curves/intern/curves_draw.cc index a11605b145b..be1b11da931 100644 --- a/source/blender/editors/curves/intern/curves_draw.cc +++ b/source/blender/editors/curves/intern/curves_draw.cc @@ -773,8 +773,8 @@ static int curves_draw_exec(bContext *C, wmOperator *op) (cps->radius_taper_end != 0.0f)); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - - attributes.remove(".selection"); + Span selection_attribute_names = get_curves_selection_attribute_names(curves); + remove_selection_attributes(attributes, selection_attribute_names); if (cdd->curve_type == CU_BEZIER) { /* Allow to interpolate multiple channels */ @@ -918,10 +918,19 @@ static int curves_draw_exec(bContext *C, wmOperator *op) curves.nurbs_orders_for_write()[curve_index] = order; curves.fill_curve_types(IndexRange(curve_index, 1), curve_type); - bke::AttributeWriter selection = attributes.lookup_or_add_for_write( - ".selection", bke::AttrDomain::Curve); - selection.varray.set(curve_index, true); - selection.finish(); + /* If Bezier curve is being added, loop through all three names, otherwise through ones in + * `selection_attribute_names`. */ + for (const StringRef selection_name : + (bezier_as_nurbs ? selection_attribute_names : + get_curves_all_selection_attribute_names())) + { + bke::AttributeWriter selection = attributes.lookup_or_add_for_write( + selection_name, bke::AttrDomain::Curve); + if (selection_name == ".selection" || !bezier_as_nurbs) { + selection.varray.set(curve_index, true); + } + selection.finish(); + } if (attributes.contains("resolution")) { curves.resolution_for_write()[curve_index] = 12; @@ -935,13 +944,21 @@ static int curves_draw_exec(bContext *C, wmOperator *op) "handle_type_left", "handle_type_right", "nurbs_weight", - ".selection"}, + ".selection", + ".selection_handle_left", + ".selection_handle_right"}, curves.points_by_curve()[curve_index]); - bke::fill_attribute_range_default( - attributes, - bke::AttrDomain::Curve, - {"curve_type", "resolution", "cyclic", "nurbs_order", "knots_mode", ".selection"}, - IndexRange(curve_index, 1)); + bke::fill_attribute_range_default(attributes, + bke::AttrDomain::Curve, + {"curve_type", + "resolution", + "cyclic", + "nurbs_order", + "knots_mode", + ".selection", + ".selection_handle_left", + ".selection_handle_right"}, + IndexRange(curve_index, 1)); } if (corners_index) { @@ -986,12 +1003,24 @@ static int curves_draw_exec(bContext *C, wmOperator *op) selection.varray.set(curve_index, true); selection.finish(); + /* Creates ".selection_handle_left" and ".selection_handle_right" attributes, otherwise all + * existing Bezier handles would be treated as selected. */ + for (const StringRef selection_name : get_curves_bezier_selection_attribute_names(curves)) { + bke::AttributeWriter selection = attributes.lookup_or_add_for_write( + selection_name, bke::AttrDomain::Curve); + selection.finish(); + } + bke::fill_attribute_range_default( - attributes, bke::AttrDomain::Point, {"position", "radius", ".selection"}, new_points); - bke::fill_attribute_range_default(attributes, - bke::AttrDomain::Curve, - {"curve_type", ".selection"}, - IndexRange(curve_index, 1)); + attributes, + bke::AttrDomain::Point, + {"position", "radius", ".selection", ".selection_handle_left", ".selection_handle_right"}, + new_points); + bke::fill_attribute_range_default( + attributes, + bke::AttrDomain::Curve, + {"curve_type", ".selection", ".selection_handle_left", ".selection_handle_right"}, + IndexRange(curve_index, 1)); } if (is_cyclic) { diff --git a/source/blender/editors/curves/intern/curves_edit.cc b/source/blender/editors/curves/intern/curves_edit.cc index 274e667c934..938433b22d8 100644 --- a/source/blender/editors/curves/intern/curves_edit.cc +++ b/source/blender/editors/curves/intern/curves_edit.cc @@ -105,7 +105,7 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); /* Delete selection attribute so that it will not have to be resized. */ - attributes.remove(".selection"); + remove_selection_attributes(attributes); curves.resize(old_points_num + num_points_to_add, old_curves_num + num_curves_to_add); @@ -160,10 +160,12 @@ void duplicate_points(bke::CurvesGeometry &curves, const IndexMask &mask) curves.update_curve_types(); curves.tag_topology_changed(); - bke::SpanAttributeWriter selection = attributes.lookup_or_add_for_write_span( - ".selection", bke::AttrDomain::Point); - selection.span.take_back(num_points_to_add).fill(true); - selection.finish(); + for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) { + bke::SpanAttributeWriter selection = attributes.lookup_or_add_for_write_span( + selection_name, bke::AttrDomain::Point); + selection.span.take_back(num_points_to_add).fill(true); + selection.finish(); + } } void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask) @@ -173,7 +175,7 @@ void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask) bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); /* Delete selection attribute so that it will not have to be resized. */ - attributes.remove(".selection"); + remove_selection_attributes(attributes); /* Resize the curves and copy the offsets of duplicated curves into the new offsets. */ curves.resize(curves.points_num(), orig_curves_num + mask.size()); @@ -215,10 +217,12 @@ void duplicate_curves(bke::CurvesGeometry &curves, const IndexMask &mask) curves.update_curve_types(); curves.tag_topology_changed(); - bke::SpanAttributeWriter selection = attributes.lookup_or_add_for_write_span( - ".selection", bke::AttrDomain::Curve); - selection.span.take_back(mask.size()).fill(true); - selection.finish(); + for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) { + bke::SpanAttributeWriter selection = attributes.lookup_or_add_for_write_span( + selection_name, bke::AttrDomain::Curve); + selection.span.take_back(mask.size()).fill(true); + selection.finish(); + } } } // namespace blender::ed::curves diff --git a/source/blender/editors/curves/intern/curves_ops.cc b/source/blender/editors/curves/intern/curves_ops.cc index 401fcbd9633..3d9cbe36457 100644 --- a/source/blender/editors/curves/intern/curves_ops.cc +++ b/source/blender/editors/curves/intern/curves_ops.cc @@ -806,26 +806,29 @@ static int curves_set_selection_domain_exec(bContext *C, wmOperator *op) * * This would be unnecessary if the active attribute were stored as a string on the ID. */ std::string active_attribute; - if (const CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id)) { + const CustomDataLayer *layer = BKE_id_attributes_active_get(&curves_id->id); + if (layer) { active_attribute = layer->name; } + for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) { + if (const GVArray src = *attributes.lookup(selection_name, domain)) { + const CPPType &type = src.type(); + void *dst = MEM_malloc_arrayN(attributes.domain_size(domain), type.size(), __func__); + src.materialize(dst); - if (const GVArray src = *attributes.lookup(".selection", domain)) { - const CPPType &type = src.type(); - void *dst = MEM_malloc_arrayN(attributes.domain_size(domain), type.size(), __func__); - src.materialize(dst); - - attributes.remove(".selection"); - if (!attributes.add(".selection", - domain, - bke::cpp_type_to_custom_data_type(type), - bke::AttributeInitMoveArray(dst))) - { - MEM_freeN(dst); + attributes.remove(selection_name); + if (!attributes.add(selection_name, + domain, + bke::cpp_type_to_custom_data_type(type), + bke::AttributeInitMoveArray(dst))) + { + MEM_freeN(dst); + } } } - - BKE_id_attributes_active_set(&curves_id->id, active_attribute.c_str()); + if (!active_attribute.empty()) { + BKE_id_attributes_active_set(&curves_id->id, active_attribute.c_str()); + } /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a generic * attribute for now. */ diff --git a/source/blender/editors/curves/intern/curves_selection.cc b/source/blender/editors/curves/intern/curves_selection.cc index 4085d2d4e26..0da1210e2a5 100644 --- a/source/blender/editors/curves/intern/curves_selection.cc +++ b/source/blender/editors/curves/intern/curves_selection.cc @@ -62,9 +62,16 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, IndexMaskMemory &mem } IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory) +{ + return retrieve_selected_points(curves, ".selection", memory); +} + +IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, + StringRef attribute_name, + IndexMaskMemory &memory) { return IndexMask::from_bools( - *curves.attributes().lookup_or_default(".selection", bke::AttrDomain::Point, true), + *curves.attributes().lookup_or_default(attribute_name, bke::AttrDomain::Point, true), memory); } @@ -74,30 +81,187 @@ IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &mem return retrieve_selected_points(curves, memory); } +Span get_curves_selection_attribute_names(const bke::CurvesGeometry &curves) +{ + static const std::array selection_attribute_names{".selection"}; + const bke::AttributeAccessor attributes = curves.attributes(); + return (attributes.contains("handle_type_left") && attributes.contains("handle_type_right")) ? + get_curves_all_selection_attribute_names() : + selection_attribute_names; +} + +Span get_curves_all_selection_attribute_names() +{ + static const std::array selection_attribute_names{ + ".selection", ".selection_handle_left", ".selection_handle_right"}; + return selection_attribute_names; +} + +Span get_curves_bezier_selection_attribute_names(const bke::CurvesGeometry &curves) +{ + static const std::array selection_attribute_names{".selection_handle_left", + ".selection_handle_right"}; + const bke::AttributeAccessor attributes = curves.attributes(); + return (attributes.contains("handle_type_left") && attributes.contains("handle_type_right")) ? + selection_attribute_names : + Span(); +} + +void remove_selection_attributes(bke::MutableAttributeAccessor &attributes, + Span selection_attribute_names) +{ + for (const StringRef selection_name : selection_attribute_names) { + attributes.remove(selection_name); + } +} + +static Vector init_selection_writers(bke::CurvesGeometry &curves, + bke::AttrDomain selection_domain) +{ + const eCustomDataType create_type = CD_PROP_BOOL; + Span selection_attribute_names = get_curves_selection_attribute_names(curves); + Vector writers; + for (const int i : selection_attribute_names.index_range()) { + writers.append(ensure_selection_attribute( + curves, selection_domain, create_type, selection_attribute_names[i])); + }; + return writers; +} + +static void finish_attribute_writers(MutableSpan attribute_writers) +{ + for (auto &attribute_writer : attribute_writers) { + attribute_writer.finish(); + } +} + +static bke::GSpanAttributeWriter &selection_attribute_writer_by_name( + MutableSpan selections, StringRef attribute_name) +{ + Span selection_attribute_names = get_curves_all_selection_attribute_names(); + + BLI_assert(selection_attribute_names.contains(attribute_name)); + + for (const int index : selections.index_range()) { + if (attribute_name.size() == selection_attribute_names[index].size()) { + return selections[index]; + } + } + BLI_assert_unreachable(); + return selections[0]; +} + +void foreach_selection_attribute_writer( + bke::CurvesGeometry &curves, + bke::AttrDomain selection_domain, + blender::FunctionRef fn) +{ + Vector selection_writers = init_selection_writers(curves, + selection_domain); + for (bke::GSpanAttributeWriter &selection_writer : selection_writers) { + fn(selection_writer); + } + finish_attribute_writers(selection_writers); +} + +static void init_selectable_foreach(const bke::CurvesGeometry &curves, + const bke::crazyspace::GeometryDeformation &deformation, + Span &r_bezier_attribute_names, + Span &r_positions, + std::array, 2> &r_bezier_handle_positions, + IndexMaskMemory &r_memory, + IndexMask &r_bezier_curves) +{ + r_bezier_attribute_names = get_curves_bezier_selection_attribute_names(curves); + r_positions = deformation.positions; + if (r_bezier_attribute_names.size() > 0) { + r_bezier_handle_positions[0] = curves.handle_positions_left(); + r_bezier_handle_positions[1] = curves.handle_positions_right(); + r_bezier_curves = curves.indices_for_curve_type(CURVE_TYPE_BEZIER, r_memory); + } +} + +void foreach_selectable_point_range(const bke::CurvesGeometry &curves, + const bke::crazyspace::GeometryDeformation &deformation, + SelectionRangeFn range_consumer) +{ + Span bezier_attribute_names; + Span positions; + std::array, 2> bezier_handle_positions; + IndexMaskMemory memory; + IndexMask bezier_curves; + init_selectable_foreach(curves, + deformation, + bezier_attribute_names, + positions, + bezier_handle_positions, + memory, + bezier_curves); + + range_consumer(curves.points_range(), positions, ".selection"); + + OffsetIndices points_by_curve = curves.points_by_curve(); + for (const int attribute_i : bezier_attribute_names.index_range()) { + bezier_curves.foreach_index(GrainSize(512), [&](const int64_t curve_i) { + range_consumer(points_by_curve[curve_i], + bezier_handle_positions[attribute_i], + bezier_attribute_names[attribute_i]); + }); + } +} + +void foreach_selectable_curve_range(const bke::CurvesGeometry &curves, + const bke::crazyspace::GeometryDeformation &deformation, + SelectionRangeFn range_consumer) +{ + Span bezier_attribute_names; + Span positions; + std::array, 2> bezier_handle_positions; + IndexMaskMemory memory; + IndexMask bezier_curves; + init_selectable_foreach(curves, + deformation, + bezier_attribute_names, + positions, + bezier_handle_positions, + memory, + bezier_curves); + + range_consumer(curves.curves_range(), positions, ".selection"); + + for (const int attribute_i : bezier_attribute_names.index_range()) { + bezier_curves.foreach_range([&](const IndexRange curves_range) { + range_consumer( + curves_range, bezier_handle_positions[attribute_i], bezier_attribute_names[attribute_i]); + }); + } +} + bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, - const bke::AttrDomain selection_domain, - const eCustomDataType create_type) + bke::AttrDomain selection_domain, + eCustomDataType create_type, + StringRef attribute_name) { bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - if (attributes.contains(".selection")) { - bke::GSpanAttributeWriter selection_attr = attributes.lookup_for_write_span(".selection"); + if (attributes.contains(attribute_name)) { + bke::GSpanAttributeWriter selection_attr = attributes.lookup_for_write_span(attribute_name); /* Check domain type. */ if (selection_attr.domain == selection_domain) { return selection_attr; } selection_attr.finish(); - attributes.remove(".selection"); + attributes.remove(attribute_name); } const int domain_size = attributes.domain_size(selection_domain); switch (create_type) { case CD_PROP_BOOL: - attributes.add(".selection", + attributes.add(attribute_name, selection_domain, CD_PROP_BOOL, bke::AttributeInitVArray(VArray::ForSingle(true, domain_size))); break; case CD_PROP_FLOAT: - attributes.add(".selection", + attributes.add(attribute_name, selection_domain, CD_PROP_FLOAT, bke::AttributeInitVArray(VArray::ForSingle(1.0f, domain_size))); @@ -105,7 +269,7 @@ bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves default: BLI_assert_unreachable(); } - return attributes.lookup_for_write_span(".selection"); + return attributes.lookup_for_write_span(attribute_name); } void fill_selection_false(GMutableSpan selection) @@ -239,6 +403,17 @@ bool has_anything_selected(const bke::CurvesGeometry &curves) return !selection || contains(selection, selection.index_range(), true); } +bool has_anything_selected(const bke::CurvesGeometry &curves, bke::AttrDomain selection_domain) +{ + for (const StringRef selection_name : get_curves_selection_attribute_names(curves)) { + const VArray selection = *curves.attributes().lookup(selection_name, + selection_domain); + if (!selection || contains(selection, selection.index_range(), true)) + return true; + } + return false; +} + bool has_anything_selected(const bke::CurvesGeometry &curves, const IndexMask &mask) { const VArray selection = *curves.attributes().lookup(".selection"); @@ -300,29 +475,29 @@ void select_all(bke::CurvesGeometry &curves, const bke::AttrDomain selection_domain, int action) { - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); if (action == SEL_SELECT) { std::optional range = mask.to_range(); if (range.has_value() && (*range == IndexRange(curves.attributes().domain_size(selection_domain)))) { + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); /* As an optimization, just remove the selection attributes when everything is selected. */ - attributes.remove(".selection"); + remove_selection_attributes(attributes); return; } } - bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - if (action == SEL_SELECT) { - fill_selection_true(selection.span, mask); - } - else if (action == SEL_DESELECT) { - fill_selection_false(selection.span, mask); - } - else if (action == SEL_INVERT) { - invert_selection(selection.span, mask); - } - selection.finish(); + foreach_selection_attribute_writer( + curves, selection_domain, [&](bke::GSpanAttributeWriter &selection) { + if (action == SEL_SELECT) { + fill_selection_true(selection.span, mask); + } + else if (action == SEL_DESELECT) { + fill_selection_false(selection.span, mask); + } + else if (action == SEL_INVERT) { + invert_selection(selection.span, mask); + } + }); } void select_all(bke::CurvesGeometry &curves, const bke::AttrDomain selection_domain, int action) @@ -334,17 +509,31 @@ void select_all(bke::CurvesGeometry &curves, const bke::AttrDomain selection_dom void select_linked(bke::CurvesGeometry &curves, const IndexMask &curves_mask) { const OffsetIndices points_by_curve = curves.points_by_curve(); - bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, bke::AttrDomain::Point, CD_PROP_BOOL); + const VArray curve_types = curves.curve_types(); + + Vector selection_writers = init_selection_writers( + curves, bke::AttrDomain::Point); curves_mask.foreach_index(GrainSize(256), [&](const int64_t curve_i) { - GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]); - if (has_anything_selected(selection_curve)) { - fill_selection_true(selection_curve); + for (const int i : selection_writers.index_range()) { + bke::GSpanAttributeWriter &selection = selection_writers[i]; + GMutableSpan selection_curve = selection.span.slice(points_by_curve[curve_i]); + if (has_anything_selected(selection_curve)) { + fill_selection_true(selection_curve); + for (const int j : selection_writers.index_range()) { + if (j == i) { + continue; + } + fill_selection_true(selection_writers[j].span.slice(points_by_curve[curve_i])); + } + return; + } + if (curve_types[curve_i] != CURVE_TYPE_BEZIER) { + return; + } } }); - - selection.finish(); + finish_attribute_writers(selection_writers); } void select_linked(bke::CurvesGeometry &curves) @@ -695,68 +884,89 @@ std::optional closest_elem_find_screen_space( bool select_box(const ViewContext &vc, bke::CurvesGeometry &curves, - const Span positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection, const IndexMask &mask, const bke::AttrDomain selection_domain, const rcti &rect, const eSelectOp sel_op) { - bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); + Vector selection_writers = init_selection_writers(curves, + selection_domain); bool changed = false; if (sel_op == SEL_OP_SET) { - fill_selection_false(selection.span, mask); + for (bke::GSpanAttributeWriter &selection : selection_writers) { + fill_selection_false(selection.span, mask); + }; changed = true; } - const OffsetIndices points_by_curve = curves.points_by_curve(); if (selection_domain == bke::AttrDomain::Point) { - mask.foreach_index(GrainSize(1024), [&](const int point_i) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[point_i], projection); - if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) { - apply_selection_operation_at_index(selection.span, point_i, sel_op); - changed = true; - } - }); + foreach_selectable_point_range( + curves, + deformation, + [&](IndexRange range, Span positions, StringRef selection_attribute_name) { + mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[point_i], projection); + if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) { + apply_selection_operation_at_index( + selection_attribute_writer_by_name(selection_writers, selection_attribute_name) + .span, + point_i, + sel_op); + changed = true; + } + }); + }); } else if (selection_domain == bke::AttrDomain::Curve) { - mask.foreach_index(GrainSize(512), [&](const int curve_i) { - const IndexRange points = points_by_curve[curve_i]; - if (points.size() == 1) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[points.first()], projection); - if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - } - return; - } - for (const int segment_i : points.drop_back(1)) { - const float3 pos1 = positions[segment_i]; - const float3 pos2 = positions[segment_i + 1]; + const OffsetIndices points_by_curve = curves.points_by_curve(); + foreach_selectable_curve_range( + curves, + deformation, + [&](const IndexRange range, + const Span positions, + StringRef /* selection_attribute_name */) { + mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) { + const IndexRange points = points_by_curve[curve_i]; + if (points.size() == 1) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[points.first()], projection); + if (BLI_rcti_isect_pt_v(&rect, int2(pos_proj))) { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + }; + changed = true; + } + return; + } + for (const int segment_i : points.drop_back(1)) { + const float3 pos1 = positions[segment_i]; + const float3 pos2 = positions[segment_i + 1]; - const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection); - const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection); + const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection); + const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection); - if (BLI_rcti_isect_segment(&rect, int2(pos1_proj), int2(pos2_proj))) { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - break; - } - } - }); + if (BLI_rcti_isect_segment(&rect, int2(pos1_proj), int2(pos2_proj))) { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + }; + changed = true; + break; + } + } + }); + }); } - selection.finish(); - + finish_attribute_writers(selection_writers); return changed; } bool select_lasso(const ViewContext &vc, bke::CurvesGeometry &curves, - const Span positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection_matrix, const IndexMask &mask, const bke::AttrDomain selection_domain, @@ -765,76 +975,99 @@ bool select_lasso(const ViewContext &vc, { rcti bbox; BLI_lasso_boundbox(&bbox, lasso_coords); - - bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - + Vector selection_writers = init_selection_writers(curves, + selection_domain); bool changed = false; if (sel_op == SEL_OP_SET) { - fill_selection_false(selection.span, mask); + for (bke::GSpanAttributeWriter &selection : selection_writers) { + fill_selection_false(selection.span, mask); + }; changed = true; } - const OffsetIndices points_by_curve = curves.points_by_curve(); if (selection_domain == bke::AttrDomain::Point) { - mask.foreach_index(GrainSize(1024), [&](const int point_i) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[point_i], projection_matrix); - /* Check the lasso bounding box first as an optimization. */ - if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) && - BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED)) - { - apply_selection_operation_at_index(selection.span, point_i, sel_op); - changed = true; - } - }); + foreach_selectable_point_range( + curves, + deformation, + [&](IndexRange range, Span positions, StringRef selection_attribute_name) { + mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[point_i], projection_matrix); + /* Check the lasso bounding box first as an optimization. */ + if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) && + BLI_lasso_is_point_inside( + lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED)) + { + apply_selection_operation_at_index( + selection_attribute_writer_by_name(selection_writers, selection_attribute_name) + .span, + point_i, + sel_op); + changed = true; + } + }); + }); } else if (selection_domain == bke::AttrDomain::Curve) { - mask.foreach_index(GrainSize(512), [&](const int curve_i) { - const IndexRange points = points_by_curve[curve_i]; - if (points.size() == 1) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[points.first()], projection_matrix); - /* Check the lasso bounding box first as an optimization. */ - if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) && - BLI_lasso_is_point_inside(lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED)) - { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - } - return; - } - for (const int segment_i : points.drop_back(1)) { - const float3 pos1 = positions[segment_i]; - const float3 pos2 = positions[segment_i + 1]; + const OffsetIndices points_by_curve = curves.points_by_curve(); + foreach_selectable_curve_range( + curves, + deformation, + [&](const IndexRange range, + const Span positions, + StringRef /* selection_attribute_name */) { + mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) { + const IndexRange points = points_by_curve[curve_i]; + if (points.size() == 1) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[points.first()], projection_matrix); + /* Check the lasso bounding box first as an optimization. */ + if (BLI_rcti_isect_pt_v(&bbox, int2(pos_proj)) && + BLI_lasso_is_point_inside( + lasso_coords, int(pos_proj.x), int(pos_proj.y), IS_CLIPPED)) + { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + } + changed = true; + } + return; + } + for (const int segment_i : points.drop_back(1)) { + const float3 pos1 = positions[segment_i]; + const float3 pos2 = positions[segment_i + 1]; - const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection_matrix); - const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection_matrix); + const float2 pos1_proj = ED_view3d_project_float_v2_m4( + vc.region, pos1, projection_matrix); + const float2 pos2_proj = ED_view3d_project_float_v2_m4( + vc.region, pos2, projection_matrix); - /* Check the lasso bounding box first as an optimization. */ - if (BLI_rcti_isect_segment(&bbox, int2(pos1_proj), int2(pos2_proj)) && - BLI_lasso_is_edge_inside(lasso_coords, - int(pos1_proj.x), - int(pos1_proj.y), - int(pos2_proj.x), - int(pos2_proj.y), - IS_CLIPPED)) - { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - break; - } - } - }); + /* Check the lasso bounding box first as an optimization. */ + if (BLI_rcti_isect_segment(&bbox, int2(pos1_proj), int2(pos2_proj)) && + BLI_lasso_is_edge_inside(lasso_coords, + int(pos1_proj.x), + int(pos1_proj.y), + int(pos2_proj.x), + int(pos2_proj.y), + IS_CLIPPED)) + { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + } + changed = true; + break; + } + } + }); + }); } - selection.finish(); - + finish_attribute_writers(selection_writers); return changed; } bool select_circle(const ViewContext &vc, bke::CurvesGeometry &curves, - const Span positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection, const IndexMask &mask, const bke::AttrDomain selection_domain, @@ -843,57 +1076,77 @@ bool select_circle(const ViewContext &vc, const eSelectOp sel_op) { const float radius_sq = pow2f(radius); - bke::GSpanAttributeWriter selection = ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - + Vector selection_writers = init_selection_writers(curves, + selection_domain); bool changed = false; if (sel_op == SEL_OP_SET) { - fill_selection_false(selection.span, mask); + for (bke::GSpanAttributeWriter &selection : selection_writers) { + fill_selection_false(selection.span, mask); + }; changed = true; } - const OffsetIndices points_by_curve = curves.points_by_curve(); if (selection_domain == bke::AttrDomain::Point) { - mask.foreach_index(GrainSize(1024), [&](const int point_i) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[point_i], projection); - if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) { - apply_selection_operation_at_index(selection.span, point_i, sel_op); - changed = true; - } - }); + foreach_selectable_point_range( + curves, + deformation, + [&](IndexRange range, Span positions, StringRef selection_attribute_name) { + mask.slice_content(range).foreach_index(GrainSize(1024), [&](const int point_i) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[point_i], projection); + if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) { + apply_selection_operation_at_index( + selection_attribute_writer_by_name(selection_writers, selection_attribute_name) + .span, + point_i, + sel_op); + changed = true; + } + }); + }); } else if (selection_domain == bke::AttrDomain::Curve) { - mask.foreach_index(GrainSize(512), [&](const int curve_i) { - const IndexRange points = points_by_curve[curve_i]; - if (points.size() == 1) { - const float2 pos_proj = ED_view3d_project_float_v2_m4( - vc.region, positions[points.first()], projection); - if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - } - return; - } - for (const int segment_i : points.drop_back(1)) { - const float3 pos1 = positions[segment_i]; - const float3 pos2 = positions[segment_i + 1]; + const OffsetIndices points_by_curve = curves.points_by_curve(); + foreach_selectable_curve_range( + curves, + deformation, + [&](const IndexRange range, + const Span positions, + StringRef /* selection_attribute_name */) { + mask.slice_content(range).foreach_index(GrainSize(512), [&](const int curve_i) { + const IndexRange points = points_by_curve[curve_i]; + if (points.size() == 1) { + const float2 pos_proj = ED_view3d_project_float_v2_m4( + vc.region, positions[points.first()], projection); + if (math::distance_squared(pos_proj, float2(coord)) <= radius_sq) { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + } + changed = true; + } + return; + } + for (const int segment_i : points.drop_back(1)) { + const float3 pos1 = positions[segment_i]; + const float3 pos2 = positions[segment_i + 1]; - const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection); - const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection); + const float2 pos1_proj = ED_view3d_project_float_v2_m4(vc.region, pos1, projection); + const float2 pos2_proj = ED_view3d_project_float_v2_m4(vc.region, pos2, projection); - const float distance_proj_sq = dist_squared_to_line_segment_v2( - float2(coord), pos1_proj, pos2_proj); - if (distance_proj_sq <= radius_sq) { - apply_selection_operation_at_index(selection.span, curve_i, sel_op); - changed = true; - break; - } - } - }); + const float distance_proj_sq = dist_squared_to_line_segment_v2( + float2(coord), pos1_proj, pos2_proj); + if (distance_proj_sq <= radius_sq) { + for (bke::GSpanAttributeWriter &selection : selection_writers) { + apply_selection_operation_at_index(selection.span, curve_i, sel_op); + } + changed = true; + break; + } + } + }); + }); } - selection.finish(); - + finish_attribute_writers(selection_writers); return changed; } diff --git a/source/blender/editors/include/ED_curves.hh b/source/blender/editors/include/ED_curves.hh index b017ef7484d..2be8aec15fd 100644 --- a/source/blender/editors/include/ED_curves.hh +++ b/source/blender/editors/include/ED_curves.hh @@ -44,6 +44,52 @@ void keymap_curves(wmKeyConfig *keyconf); */ float (*point_normals_array_create(const Curves *curves_id))[3]; +/** + * Get selection attribute names need for given curve. + * Possible outcomes: [".selection"] if Bezier curves are present, + * [".selection", ".selection_handle_left", ".selection_handle_right"] otherwise. */ +Span get_curves_selection_attribute_names(const bke::CurvesGeometry &curves); + +/* Get all possible curve selection attribute names. */ +Span get_curves_all_selection_attribute_names(); + +/** + * Returns [".selection_handle_left", ".selection_handle_right"] if argument contains Bezier + * curves, empty span otherwise. + */ +Span get_curves_bezier_selection_attribute_names(const bke::CurvesGeometry &curves); + +/** + * Used to select everything or to delete selection attribute so that it will not have to be + * resized. + */ +void remove_selection_attributes( + bke::MutableAttributeAccessor &attributes, + Span selection_attribute_names = get_curves_all_selection_attribute_names()); + +using SelectionRangeFn = FunctionRef positions, StringRef selection_attribute_name)>; +/** + * Traverses all ranges of control points possible select. Callback function is provided with a + * range being visited, positions (deformed if possible) referenced by the range and selection + * attribute name positions belongs to: + * curves.positions() belong to ".selection", + * curves.handle_positions_left() belong to ".selection_handle_left", + * curves.handle_positions_right() belong to ".selection_handle_right". + */ +void foreach_selectable_point_range(const bke::CurvesGeometry &curves, + const bke::crazyspace::GeometryDeformation &deformation, + SelectionRangeFn range_consumer); + +/** + * Same logic as in foreach_selectable_point_range, just ranges reference curves instead of + * positions directly. Futher positions can be referenced by using curves.points_by_curve() in a + * callback function. + */ +void foreach_selectable_curve_range(const bke::CurvesGeometry &curves, + const bke::crazyspace::GeometryDeformation &deformation, + SelectionRangeFn range_consumer); + bool object_has_editable_curves(const Main &bmain, const Object &object); bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve); VectorSet get_unique_editable_curves(const bContext &C); @@ -146,6 +192,7 @@ void fill_selection_true(GMutableSpan selection, const IndexMask &mask); * Return true if any element is selected, on either domain with either type. */ bool has_anything_selected(const bke::CurvesGeometry &curves); +bool has_anything_selected(const bke::CurvesGeometry &curves, bke::AttrDomain selection_domain); bool has_anything_selected(const bke::CurvesGeometry &curves, const IndexMask &mask); /** @@ -167,14 +214,23 @@ IndexMask retrieve_selected_curves(const Curves &curves_id, IndexMaskMemory &mem * or points in curves with a selection factor greater than zero). */ IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, IndexMaskMemory &memory); +IndexMask retrieve_selected_points(const bke::CurvesGeometry &curves, + StringRef attribute_name, + IndexMaskMemory &memory); IndexMask retrieve_selected_points(const Curves &curves_id, IndexMaskMemory &memory); /** - * If the ".selection" attribute doesn't exist, create it with the requested type (bool or float). + * If the selection_id attribute doesn't exist, create it with the requested type (bool or float). */ bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, bke::AttrDomain selection_domain, - eCustomDataType create_type); + eCustomDataType create_type, + StringRef attribute_name = ".selection"); + +void foreach_selection_attribute_writer( + bke::CurvesGeometry &curves, + bke::AttrDomain selection_domain, + FunctionRef fn); /** Apply a change to a single curve or point. Avoid using this when affecting many elements. */ void apply_selection_operation_at_index(GMutableSpan selection, int index, eSelectOp sel_op); @@ -246,7 +302,7 @@ std::optional closest_elem_find_screen_space(const ViewContext */ bool select_box(const ViewContext &vc, bke::CurvesGeometry &curves, - Span deformed_positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection, const IndexMask &mask, bke::AttrDomain selection_domain, @@ -258,7 +314,7 @@ bool select_box(const ViewContext &vc, */ bool select_lasso(const ViewContext &vc, bke::CurvesGeometry &curves, - Span deformed_positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection_matrix, const IndexMask &mask, bke::AttrDomain selection_domain, @@ -270,7 +326,7 @@ bool select_lasso(const ViewContext &vc, */ bool select_circle(const ViewContext &vc, bke::CurvesGeometry &curves, - Span deformed_positions, + const bke::crazyspace::GeometryDeformation &deformation, const float4x4 &projection, const IndexMask &mask, bke::AttrDomain selection_domain, diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index 8eaf16a2fd0..3be6fa5d05e 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -1210,7 +1210,7 @@ static bool do_lasso_select_grease_pencil(const ViewContext *vc, const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world); changed = ed::curves::select_lasso(*vc, info.drawing.strokes_for_write(), - deformation.positions, + deformation, projection, elements, selection_domain, @@ -1440,14 +1440,8 @@ static bool view3d_lasso_select(bContext *C, const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain); const IndexRange elements(curves.attributes().domain_size(selection_domain)); const float4x4 projection = ED_view3d_ob_project_mat_get(vc->rv3d, vc->obedit); - changed = ed::curves::select_lasso(*vc, - curves, - deformation.positions, - projection, - elements, - selection_domain, - mcoords, - sel_op); + changed = ed::curves::select_lasso( + *vc, curves, deformation, projection, elements, selection_domain, mcoords, sel_op); if (changed) { /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a * generic attribute for now. */ @@ -3081,6 +3075,7 @@ static bool ed_wpaint_vertex_select_pick(bContext *C, } struct ClosestCurveDataBlock { + blender::StringRef selection_name; Curves *curves_id = nullptr; blender::ed::curves::FindClosestData elem = {}; }; @@ -3116,20 +3111,33 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi bke::crazyspace::get_evaluated_curves_deformation(*vc.depsgraph, *vc.obedit); const bke::CurvesGeometry &curves = curves_id.geometry.wrap(); const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, &curves_ob); - const IndexRange elements(curves.attributes().domain_size(selection_domain)); - std::optional new_closest_elem = - ed::curves::closest_elem_find_screen_space(vc, - curves.points_by_curve(), - deformation.positions, - projection, - elements, - selection_domain, - mval, - new_closest.elem); - if (new_closest_elem) { - new_closest.elem = *new_closest_elem; - new_closest.curves_id = &curves_id; + const IndexMask elements(curves.attributes().domain_size(selection_domain)); + const auto range_consumer = + [&](IndexRange range, Span positions, StringRef selection_attribute_name) { + IndexMask mask = elements.slice_content(range); + + std::optional new_closest_elem = + ed::curves::closest_elem_find_screen_space(vc, + curves.points_by_curve(), + positions, + projection, + mask, + selection_domain, + mval, + new_closest.elem); + if (new_closest_elem) { + new_closest.selection_name = selection_attribute_name; + new_closest.elem = *new_closest_elem; + new_closest.curves_id = &curves_id; + } + }; + + if (selection_domain == bke::AttrDomain::Point) { + ed::curves::foreach_selectable_point_range(curves, deformation, range_consumer); } + else if (selection_domain == bke::AttrDomain::Curve) { + ed::curves::foreach_selectable_curve_range(curves, deformation, range_consumer); + }; } return new_closest; }, @@ -3143,13 +3151,14 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi for (Base *base : bases.as_span().slice(range)) { Curves &curves_id = *static_cast(base->object->data); bke::CurvesGeometry &curves = curves_id.geometry.wrap(); - if (!ed::curves::has_anything_selected(curves)) { + if (!ed::curves::has_anything_selected(curves, selection_domain)) { continue; } - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - curves, selection_domain, CD_PROP_BOOL); - ed::curves::fill_selection_false(selection.span); - selection.finish(); + + ed::curves::foreach_selection_attribute_writer( + curves, selection_domain, [](bke::GSpanAttributeWriter &selection) { + ed::curves::fill_selection_false(selection.span); + }); deselected = true; /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a @@ -3164,11 +3173,25 @@ static bool ed_curves_select_pick(bContext &C, const int mval[2], const SelectPi return deselected; } - bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( - closest.curves_id->geometry.wrap(), selection_domain, CD_PROP_BOOL); - ed::curves::apply_selection_operation_at_index( - selection.span, closest.elem.index, params.sel_op); - selection.finish(); + if (selection_domain == bke::AttrDomain::Point) { + bke::GSpanAttributeWriter selection = ed::curves::ensure_selection_attribute( + closest.curves_id->geometry.wrap(), + bke::AttrDomain::Point, + CD_PROP_BOOL, + closest.selection_name); + ed::curves::apply_selection_operation_at_index( + selection.span, closest.elem.index, params.sel_op); + selection.finish(); + } + else if (selection_domain == bke::AttrDomain::Curve) { + ed::curves::foreach_selection_attribute_writer( + closest.curves_id->geometry.wrap(), + bke::AttrDomain::Curve, + [&](bke::GSpanAttributeWriter &selection) { + ed::curves::apply_selection_operation_at_index( + selection.span, closest.elem.index, params.sel_op); + }); + } /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a * generic attribute for now. */ @@ -4245,7 +4268,7 @@ static bool do_grease_pencil_box_select(const ViewContext *vc, const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world); changed |= ed::curves::select_box(*vc, info.drawing.strokes_for_write(), - deformation.positions, + deformation, projection, elements, selection_domain, @@ -4334,14 +4357,8 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op) const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain); const float4x4 projection = ED_view3d_ob_project_mat_get(vc.rv3d, vc.obedit); const IndexRange elements(curves.attributes().domain_size(selection_domain)); - changed = ed::curves::select_box(vc, - curves, - deformation.positions, - projection, - elements, - selection_domain, - rect, - sel_op); + changed = ed::curves::select_box( + vc, curves, deformation, projection, elements, selection_domain, rect, sel_op); if (changed) { /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a * generic attribute for now. */ @@ -5113,7 +5130,7 @@ static bool grease_pencil_circle_select(const ViewContext *vc, const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc->rv3d, layer_to_world); changed = ed::curves::select_circle(*vc, info.drawing.strokes_for_write(), - deformation.positions, + deformation, projection, elements, selection_domain, @@ -5173,15 +5190,8 @@ static bool obedit_circle_select(bContext *C, const bke::AttrDomain selection_domain = bke::AttrDomain(curves_id.selection_domain); const float4x4 projection = ED_view3d_ob_project_mat_get(vc->rv3d, vc->obedit); const IndexRange elements(curves.attributes().domain_size(selection_domain)); - changed = ed::curves::select_circle(*vc, - curves, - deformation.positions, - projection, - elements, - selection_domain, - mval, - rad, - sel_op); + changed = ed::curves::select_circle( + *vc, curves, deformation, projection, elements, selection_domain, mval, rad, sel_op); if (changed) { /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a * generic attribute for now. */