diff --git a/source/blender/editors/curves/intern/curves_selection.cc b/source/blender/editors/curves/intern/curves_selection.cc index d40e736b266..fc077b2bdfc 100644 --- a/source/blender/editors/curves/intern/curves_selection.cc +++ b/source/blender/editors/curves/intern/curves_selection.cc @@ -9,11 +9,13 @@ #include "BLI_rand.hh" #include "BKE_attribute.hh" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "ED_curves.h" #include "ED_object.h" #include "ED_select_utils.h" +#include "ED_view3d.h" namespace blender::ed::curves { @@ -283,4 +285,137 @@ void select_random(bke::CurvesGeometry &curves, selection.finish(); } +/** + * Helper struct for `find_closest_point_to_screen_co`. + */ +struct FindClosestPointData { + int index = -1; + float distance = FLT_MAX; +}; + +static bool find_closest_point_to_screen_co(const Depsgraph &depsgraph, + const ARegion *region, + const RegionView3D *rv3d, + const Object &object, + const bke::CurvesGeometry &curves, + float2 mouse_pos, + float radius, + FindClosestPointData &closest_data) +{ + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d, &object, projection.ptr()); + + const bke::crazyspace::GeometryDeformation deformation = + bke::crazyspace::get_evaluated_curves_deformation(depsgraph, object); + + const float radius_sq = pow2f(radius); + auto [min_point_index, min_distance] = threading::parallel_reduce( + curves.points_range(), + 1024, + FindClosestPointData(), + [&](const IndexRange point_range, const FindClosestPointData &init) { + FindClosestPointData best_match = init; + for (const int point_i : point_range) { + const float3 pos = deformation.positions[point_i]; + + /* Find the position of the point in screen space. */ + float2 pos_proj; + ED_view3d_project_float_v2_m4(region, pos, pos_proj, projection.ptr()); + + const float distance_proj_sq = math::distance_squared(pos_proj, mouse_pos); + if (distance_proj_sq > radius_sq || + distance_proj_sq > best_match.distance * best_match.distance) { + /* Ignore the point because it's too far away or there is already a better point. */ + continue; + } + + FindClosestPointData better_candidate; + better_candidate.index = point_i; + better_candidate.distance = std::sqrt(distance_proj_sq); + + best_match = better_candidate; + } + return best_match; + }, + [](const FindClosestPointData &a, const FindClosestPointData &b) { + return std::min(a.distance, b.distance); + }); + + if (min_point_index > 0) { + closest_data.index = min_point_index; + closest_data.distance = min_distance; + return true; + } + return false; +} + +bool select_pick(const ViewContext &vc, + bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + const SelectPick_Params ¶ms, + const int2 mval) +{ + FindClosestPointData closest_point; + bool found = find_closest_point_to_screen_co(*vc.depsgraph, + vc.region, + vc.rv3d, + *vc.obact, + curves, + float2(mval), + ED_view3d_select_dist_px(), + closest_point); + + bool changed = false; + if (params.sel_op == SEL_OP_SET) { + if (found || params.deselect_all) { + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + fill_selection_false(selection.span); + selection.finish(); + changed = true; + } + } + + if (found) { + bke::GSpanAttributeWriter selection = ensure_selection_attribute( + curves, selection_domain, CD_PROP_BOOL); + + int elem_index = closest_point.index; + if (selection_domain == ATTR_DOMAIN_CURVE) { + /* Find the curve index for the found point. */ + auto it = std::upper_bound( + curves.offsets().begin(), curves.offsets().end(), closest_point.index); + BLI_assert(it != curves.offsets().end()); + elem_index = std::distance(curves.offsets().begin(), it) - 1; + } + + selection.span.type().to_static_type_tag([&](auto type_tag) { + using T = typename decltype(type_tag)::type; + if constexpr (std::is_void_v) { + BLI_assert_unreachable(); + } + else { + MutableSpan selection_typed = selection.span.typed(); + switch (params.sel_op) { + case SEL_OP_ADD: + case SEL_OP_SET: + selection_typed[elem_index] = T(1); + break; + case SEL_OP_SUB: + selection_typed[elem_index] = T(0); + break; + case SEL_OP_XOR: + selection_typed[elem_index] = T(1 - selection_typed[elem_index]); + break; + default: + break; + } + } + }); + selection.finish(); + } + + return changed || found; +} + } // namespace blender::ed::curves diff --git a/source/blender/editors/include/ED_curves.h b/source/blender/editors/include/ED_curves.h index 5c7cadd9d75..a63e35313b1 100644 --- a/source/blender/editors/include/ED_curves.h +++ b/source/blender/editors/include/ED_curves.h @@ -9,6 +9,8 @@ struct bContext; struct Curves; struct UndoType; +struct SelectPick_Params; +struct ViewContext; #ifdef __cplusplus extern "C" { @@ -134,6 +136,17 @@ void select_random(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, uint32_t random_seed, float probability); + +/** + * Select point or curve under the cursor. + */ +bool select_pick(const ViewContext &vc, + bke::CurvesGeometry &curves, + const eAttrDomain selection_domain, + const SelectPick_Params ¶ms, + const int2 mval); + + /** \} */ } // namespace blender::ed::curves diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index a3b4e670795..691bba4c02d 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -73,6 +73,7 @@ set(SRC ) set(LIB + bf_editor_curves bf_editor_lattice bf_editor_mesh ) diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index 79c7bb496d5..b8c744f1c56 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -69,6 +69,7 @@ #include "ED_armature.h" #include "ED_curve.h" +#include "ED_curves.h" #include "ED_gpencil.h" #include "ED_lattice.h" #include "ED_mball.h" @@ -2968,10 +2969,15 @@ static bool ed_wpaint_vertex_select_pick(bContext *C, static int view3d_select_exec(bContext *C, wmOperator *op) { + using namespace blender; Scene *scene = CTX_data_scene(C); Object *obedit = CTX_data_edit_object(C); Object *obact = CTX_data_active_object(C); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + SelectPick_Params params{}; ED_select_pick_params_from_operator(op->ptr, ¶ms); @@ -3021,10 +3027,6 @@ static int view3d_select_exec(bContext *C, wmOperator *op) } else if (obedit->type == OB_ARMATURE) { if (enumerate) { - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - ED_view3d_viewcontext_init(C, &vc, depsgraph); - GPUSelectResult buffer[MAXPICKELEMS]; const int hits = mixed_bones_object_selectbuffer( &vc, buffer, ARRAY_SIZE(buffer), mval, VIEW3D_SELECT_FILTER_NOP, false, true, false); @@ -3047,6 +3049,19 @@ static int view3d_select_exec(bContext *C, wmOperator *op) else if (obedit->type == OB_FONT) { changed = ED_curve_editfont_select_pick(C, mval, ¶ms); } + else if (obedit->type == OB_CURVES) { + Curves &curves_id = *static_cast(obact->data); + bke::CurvesGeometry &curves = curves_id.geometry.wrap(); + changed = ed::curves::select_pick( + vc, curves, eAttrDomain(curves_id.selection_domain), params, mval); + if (changed) { + /* Use #ID_RECALC_GEOMETRY instead of #ID_RECALC_SELECT because it is handled as a + * generic attribute for now. */ + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, &curves_id); + return true; + } + } } else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT) { changed = PE_mouse_particles(C, mval, ¶ms); @@ -3535,8 +3550,7 @@ static bool do_mesh_box_select(ViewContext *vc, } if (ts->selectmode & SCE_SELECT_EDGE) { /* Does both use_zbuf and non-use_zbuf versions (need screen cos for both) */ - struct BoxSelectUserData_ForMeshEdge cb_data { - }; + struct BoxSelectUserData_ForMeshEdge cb_data {}; cb_data.data = &data; cb_data.esel = use_zbuf ? esel : nullptr; cb_data.backbuf_offset = use_zbuf ? DRW_select_buffer_context_offset_for_object_elem(