Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/curves/intern/curves_selection.cc
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
| /** \file | /** \file | ||||
| * \ingroup edcurves | * \ingroup edcurves | ||||
| */ | */ | ||||
| #include "BLI_array_utils.hh" | |||||
| #include "BLI_index_mask_ops.hh" | #include "BLI_index_mask_ops.hh" | ||||
| #include "BLI_rand.hh" | |||||
| #include "BKE_attribute.hh" | #include "BKE_attribute.hh" | ||||
| #include "BKE_curves.hh" | #include "BKE_curves.hh" | ||||
| #include "ED_curves.h" | #include "ED_curves.h" | ||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "ED_select_utils.h" | |||||
| namespace blender::ed::curves { | namespace blender::ed::curves { | ||||
| static IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, | static IndexMask retrieve_selected_curves(const bke::CurvesGeometry &curves, | ||||
| Vector<int64_t> &r_indices) | Vector<int64_t> &r_indices) | ||||
| { | { | ||||
| const IndexRange curves_range = curves.curves_range(); | const IndexRange curves_range = curves.curves_range(); | ||||
| const bke::AttributeAccessor attributes = curves.attributes(); | const bke::AttributeAccessor attributes = curves.attributes(); | ||||
| ▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| IndexMask retrieve_selected_points(const Curves &curves_id, Vector<int64_t> &r_indices) | IndexMask retrieve_selected_points(const Curves &curves_id, Vector<int64_t> &r_indices) | ||||
| { | { | ||||
| const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); | const bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); | ||||
| return retrieve_selected_points(curves, r_indices); | return retrieve_selected_points(curves, r_indices); | ||||
| } | } | ||||
| void ensure_selection_attribute(Curves &curves_id, const eCustomDataType create_type) | bke::GSpanAttributeWriter ensure_selection_attribute(bke::CurvesGeometry &curves, | ||||
| const eAttrDomain selection_domain, | |||||
| const eCustomDataType create_type) | |||||
| { | { | ||||
| bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); | |||||
| bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); | bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); | ||||
| if (attributes.contains(".selection")) { | if (attributes.contains(".selection")) { | ||||
| return; | return attributes.lookup_for_write_span(".selection"); | ||||
| } | } | ||||
| const eAttrDomain domain = eAttrDomain(curves_id.selection_domain); | const int domain_size = attributes.domain_size(selection_domain); | ||||
| const int domain_size = attributes.domain_size(domain); | |||||
| switch (create_type) { | switch (create_type) { | ||||
| case CD_PROP_BOOL: | case CD_PROP_BOOL: | ||||
| attributes.add(".selection", | attributes.add(".selection", | ||||
| domain, | selection_domain, | ||||
| CD_PROP_BOOL, | CD_PROP_BOOL, | ||||
| bke::AttributeInitVArray(VArray<bool>::ForSingle(true, domain_size))); | bke::AttributeInitVArray(VArray<bool>::ForSingle(true, domain_size))); | ||||
| break; | break; | ||||
| case CD_PROP_FLOAT: | case CD_PROP_FLOAT: | ||||
| attributes.add(".selection", | attributes.add(".selection", | ||||
| domain, | selection_domain, | ||||
| CD_PROP_FLOAT, | CD_PROP_FLOAT, | ||||
| bke::AttributeInitVArray(VArray<float>::ForSingle(1.0f, domain_size))); | bke::AttributeInitVArray(VArray<float>::ForSingle(1.0f, domain_size))); | ||||
| break; | break; | ||||
| default: | default: | ||||
| BLI_assert_unreachable(); | BLI_assert_unreachable(); | ||||
| } | } | ||||
| return attributes.lookup_for_write_span(".selection"); | |||||
| } | } | ||||
| void fill_selection_false(GMutableSpan selection) | void fill_selection_false(GMutableSpan selection) | ||||
| { | { | ||||
| if (selection.type().is<bool>()) { | if (selection.type().is<bool>()) { | ||||
| selection.typed<bool>().fill(false); | selection.typed<bool>().fill(false); | ||||
| } | } | ||||
| else if (selection.type().is<float>()) { | else if (selection.type().is<float>()) { | ||||
| selection.typed<float>().fill(0.0f); | selection.typed<float>().fill(0.0f); | ||||
| } | } | ||||
| } | } | ||||
| void fill_selection_true(GMutableSpan selection) | void fill_selection_true(GMutableSpan selection) | ||||
| { | { | ||||
| if (selection.type().is<bool>()) { | if (selection.type().is<bool>()) { | ||||
| selection.typed<bool>().fill(true); | selection.typed<bool>().fill(true); | ||||
| } | } | ||||
| else if (selection.type().is<float>()) { | else if (selection.type().is<float>()) { | ||||
| selection.typed<float>().fill(1.0f); | selection.typed<float>().fill(1.0f); | ||||
| } | } | ||||
| } | } | ||||
| static bool contains(const VArray<bool> &varray, const bool value) | |||||
| { | |||||
| const CommonVArrayInfo info = varray.common_info(); | |||||
| if (info.type == CommonVArrayInfo::Type::Single) { | |||||
| return *static_cast<const bool *>(info.data) == value; | |||||
| } | |||||
| if (info.type == CommonVArrayInfo::Type::Span) { | |||||
| const Span<bool> span(static_cast<const bool *>(info.data), varray.size()); | |||||
| return threading::parallel_reduce( | |||||
| span.index_range(), | |||||
| 4096, | |||||
| false, | |||||
| [&](const IndexRange range, const bool init) { | |||||
| return init || span.slice(range).contains(value); | |||||
| }, | |||||
| [&](const bool a, const bool b) { return a || b; }); | |||||
| } | |||||
| return threading::parallel_reduce( | |||||
| varray.index_range(), | |||||
| 2048, | |||||
| false, | |||||
| [&](const IndexRange range, const bool init) { | |||||
| if (init) { | |||||
| return init; | |||||
| } | |||||
| /* Alternatively, this could use #materialize to retrieve many values at once. */ | |||||
| for (const int64_t i : range) { | |||||
| if (varray[i] == value) { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| }, | |||||
| [&](const bool a, const bool b) { return a || b; }); | |||||
| } | |||||
| bool has_anything_selected(const bke::CurvesGeometry &curves) | |||||
| { | |||||
| const VArray<bool> selection = curves.attributes().lookup<bool>(".selection"); | |||||
| return !selection || contains(selection, true); | |||||
| } | |||||
| static void invert_selection(MutableSpan<float> selection) | |||||
| { | |||||
| threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) { | |||||
| for (const int i : range) { | |||||
| selection[i] = 1.0f - selection[i]; | |||||
| } | |||||
| }); | |||||
| } | |||||
| static void invert_selection(GMutableSpan selection) | |||||
| { | |||||
| if (selection.type().is<bool>()) { | |||||
| array_utils::invert_booleans(selection.typed<bool>()); | |||||
| } | |||||
| else if (selection.type().is<float>()) { | |||||
| invert_selection(selection.typed<float>()); | |||||
| } | |||||
| } | |||||
| void select_all(bke::CurvesGeometry &curves, const eAttrDomain selection_domain, int action) | |||||
| { | |||||
| bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); | |||||
| if (action == SEL_SELECT) { | |||||
| /* As an optimization, just remove the selection attributes when everything is selected. */ | |||||
| attributes.remove(".selection"); | |||||
| } | |||||
| else { | |||||
| bke::GSpanAttributeWriter selection = ensure_selection_attribute( | |||||
| curves, selection_domain, CD_PROP_BOOL); | |||||
| if (action == SEL_DESELECT) { | |||||
| fill_selection_false(selection.span); | |||||
| } | |||||
| else if (action == SEL_INVERT) { | |||||
| invert_selection(selection.span); | |||||
| } | |||||
| selection.finish(); | |||||
| } | |||||
| } | |||||
| void select_ends(bke::CurvesGeometry &curves, | |||||
| const eAttrDomain selection_domain, | |||||
| int amount, | |||||
| bool end_points) | |||||
| { | |||||
| const bool was_anything_selected = has_anything_selected(curves); | |||||
| bke::GSpanAttributeWriter selection = ensure_selection_attribute( | |||||
| curves, selection_domain, CD_PROP_BOOL); | |||||
| if (!was_anything_selected) { | |||||
| fill_selection_true(selection.span); | |||||
| } | |||||
| selection.span.type().to_static_type_tag<bool, float>([&](auto type_tag) { | |||||
| using T = typename decltype(type_tag)::type; | |||||
| if constexpr (std::is_void_v<T>) { | |||||
| BLI_assert_unreachable(); | |||||
| } | |||||
| else { | |||||
| MutableSpan<T> selection_typed = selection.span.typed<T>(); | |||||
| threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange range) { | |||||
| for (const int curve_i : range) { | |||||
| const OffsetIndices points_by_curve = curves.points_by_curve(); | |||||
| if (end_points) { | |||||
| selection_typed.slice(points_by_curve[curve_i].drop_back(amount)).fill(T(0)); | |||||
| } | |||||
| else { | |||||
| selection_typed.slice(points_by_curve[curve_i].drop_front(amount)).fill(T(0)); | |||||
| } | |||||
| } | |||||
| }); | |||||
| } | |||||
| }); | |||||
| selection.finish(); | |||||
| } | |||||
| void select_random(bke::CurvesGeometry &curves, | |||||
| const eAttrDomain selection_domain, | |||||
| uint32_t random_seed, | |||||
| float probability) | |||||
| { | |||||
| RandomNumberGenerator rng{random_seed}; | |||||
| const auto next_bool_random_value = [&]() { return rng.get_float() <= probability; }; | |||||
| const bool was_anything_selected = has_anything_selected(curves); | |||||
| bke::GSpanAttributeWriter selection = ensure_selection_attribute( | |||||
| curves, selection_domain, CD_PROP_BOOL); | |||||
| if (!was_anything_selected) { | |||||
| curves::fill_selection_true(selection.span); | |||||
| } | |||||
| selection.span.type().to_static_type_tag<bool, float>([&](auto type_tag) { | |||||
| using T = typename decltype(type_tag)::type; | |||||
| if constexpr (std::is_void_v<T>) { | |||||
| BLI_assert_unreachable(); | |||||
| } | |||||
| else { | |||||
| MutableSpan<T> selection_typed = selection.span.typed<T>(); | |||||
| switch (selection_domain) { | |||||
| case ATTR_DOMAIN_POINT: { | |||||
| for (const int point_i : selection_typed.index_range()) { | |||||
| const bool random_value = next_bool_random_value(); | |||||
| if (!random_value) { | |||||
| selection_typed[point_i] = T(0); | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| case ATTR_DOMAIN_CURVE: { | |||||
| for (const int curve_i : curves.curves_range()) { | |||||
| const bool random_value = next_bool_random_value(); | |||||
| if (!random_value) { | |||||
| selection_typed[curve_i] = T(0); | |||||
| } | |||||
| } | |||||
| break; | |||||
| } | |||||
| default: | |||||
| BLI_assert_unreachable(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| selection.finish(); | |||||
| } | |||||
| } // namespace blender::ed::curves | } // namespace blender::ed::curves | ||||