diff --git a/source/blender/blenkernel/BKE_brush.h b/source/blender/blenkernel/BKE_brush.h index c3a2aba18b5..4b84c0cfe23 100644 --- a/source/blender/blenkernel/BKE_brush.h +++ b/source/blender/blenkernel/BKE_brush.h @@ -8,13 +8,13 @@ * General operations for brushes. */ +#include "DNA_color_types.h" #include "DNA_object_enums.h" #ifdef __cplusplus extern "C" { #endif -enum eCurveMappingPreset; struct Brush; struct ImBuf; struct ImagePool; diff --git a/source/blender/blenlib/BLI_index_mask_ops.hh b/source/blender/blenlib/BLI_index_mask_ops.hh new file mode 100644 index 00000000000..48a1f27a2fa --- /dev/null +++ b/source/blender/blenlib/BLI_index_mask_ops.hh @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** \file + * \ingroup bli + * + * This is separate from `BLI_index_mask.hh` because it includes headers just `IndexMask` shouldn't + * depend on. + */ + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_index_mask.hh" +#include "BLI_task.hh" +#include "BLI_vector.hh" + +namespace blender::index_mask_ops { + +namespace detail { +IndexMask find_indices_based_on_predicate__merge( + IndexMask indices_to_check, + threading::EnumerableThreadSpecific>> &sub_masks, + Vector &r_indices); +} // namespace detail + +/** + * Evaluate the #predicate for all indices in #indices_to_check and return a mask that contains all + * indices where the predicate was true. + * + * #r_indices indices is only used if necessary. + */ +template +inline IndexMask find_indices_based_on_predicate(const IndexMask indices_to_check, + const int64_t parallel_grain_size, + Vector &r_indices, + const Predicate &predicate) +{ + /* Evaluate predicate in parallel. Since the size of the final mask is not known yet, many + * smaller vectors have to be filled with all indices where the predicate is true. Those smaller + * vectors are joined afterwards. */ + threading::EnumerableThreadSpecific>> sub_masks; + threading::parallel_for( + indices_to_check.index_range(), parallel_grain_size, [&](const IndexRange range) { + const IndexMask sub_mask = indices_to_check.slice(range); + Vector masked_indices; + for (const int64_t i : sub_mask) { + if (predicate(i)) { + masked_indices.append(i); + } + } + if (!masked_indices.is_empty()) { + sub_masks.local().append(std::move(masked_indices)); + } + }); + + /* This part doesn't have to be in the header. */ + return detail::find_indices_based_on_predicate__merge(indices_to_check, sub_masks, r_indices); +} + +} // namespace blender::index_mask_ops diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index 3d2ac5688ff..4bba84f2e29 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -303,6 +303,9 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4], const float bbmin[3], const float bbmax[3]); +/** Returns the distance between two 2D line segments. */ +float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3]); + float closest_to_ray_v3(float r_close[3], const float p[3], const float ray_orig[3], diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index ca22315b2ed..6e3e84f6495 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -203,6 +203,7 @@ set(SRC BLI_heap.h BLI_heap_simple.h BLI_index_mask.hh + BLI_index_mask_ops.hh BLI_index_range.hh BLI_inplace_priority_queue.hh BLI_iterator.h diff --git a/source/blender/blenlib/intern/index_mask.cc b/source/blender/blenlib/intern/index_mask.cc index 8c03df2d4c3..1e301bc5fb9 100644 --- a/source/blender/blenlib/intern/index_mask.cc +++ b/source/blender/blenlib/intern/index_mask.cc @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_index_mask.hh" +#include "BLI_index_mask_ops.hh" namespace blender { @@ -126,3 +127,70 @@ Vector IndexMask::extract_ranges_invert(const IndexRange full_range, } } // namespace blender + +namespace blender::index_mask_ops::detail { + +IndexMask find_indices_based_on_predicate__merge( + IndexMask indices_to_check, + threading::EnumerableThreadSpecific>> &sub_masks, + Vector &r_indices) +{ + /* Gather vectors that have been generated by possibly multiple threads. */ + Vector *> all_vectors; + int64_t result_mask_size = 0; + for (Vector> &local_sub_masks : sub_masks) { + for (Vector &sub_mask : local_sub_masks) { + all_vectors.append(&sub_mask); + result_mask_size += sub_mask.size(); + } + } + + if (all_vectors.is_empty()) { + /* Special case when the predicate was false for all elements. */ + return {}; + } + if (result_mask_size == indices_to_check.size()) { + /* Special case when the predicate was true for all elements. */ + return indices_to_check; + } + if (all_vectors.size() == 1) { + /* Special case when all indices for which the predicate is true happen to be in a single + * vector. */ + r_indices = std::move(*all_vectors[0]); + return r_indices.as_span(); + } + + /* Indices in separate vectors don't overlap. So it is ok to sort the vectors just by looking at + * the first element. */ + std::sort(all_vectors.begin(), + all_vectors.end(), + [](const Vector *a, const Vector *b) { return (*a)[0] < (*b)[0]; }); + + /* Precompute the offsets for the individual vectors, so that the indices can be copied into the + * final vector in parallel. */ + Vector offsets; + offsets.reserve(all_vectors.size() + 1); + offsets.append(0); + for (Vector *vector : all_vectors) { + offsets.append(offsets.last() + vector->size()); + } + + r_indices.resize(result_mask_size); + + /* Fill the final index mask in parallel again. */ + threading::parallel_for(all_vectors.index_range(), 100, [&](const IndexRange all_vectors_range) { + for (const int64_t vector_index : all_vectors_range) { + Vector &vector = *all_vectors[vector_index]; + const int64_t offset = offsets[vector_index]; + threading::parallel_for(vector.index_range(), 1024, [&](const IndexRange range) { + initialized_copy_n(vector.data() + range.start(), + range.size(), + r_indices.data() + offset + range.start()); + }); + } + }); + + return r_indices.as_span(); +} + +} // namespace blender::index_mask_ops::detail diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index f96c80185b1..bc3ed099fd5 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -903,6 +903,18 @@ float dist_squared_to_projected_aabb_simple(const float projmat[4][4], /** \} */ +float dist_seg_seg_v2(const float a1[3], const float a2[3], const float b1[3], const float b2[3]) +{ + if (isect_seg_seg_v2_simple(a1, a2, b1, b2)) { + return 0.0f; + } + const float d1 = dist_squared_to_line_segment_v2(a1, b1, b2); + const float d2 = dist_squared_to_line_segment_v2(a2, b1, b2); + const float d3 = dist_squared_to_line_segment_v2(b1, a1, a2); + const float d4 = dist_squared_to_line_segment_v2(b2, a1, a2); + return sqrtf(min_ffff(d1, d2, d3, d4)); +} + void closest_on_tri_to_point_v3( float r[3], const float p[3], const float v1[3], const float v2[3], const float v3[3]) { diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index de7888aa1e8..59fbc3a64fb 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -9,6 +9,7 @@ set(INC ../../bmesh ../../depsgraph ../../draw + ../../functions ../../gpu ../../imbuf ../../makesdna @@ -78,5 +79,16 @@ set(LIB bf_blenlib ) +if(WITH_TBB) + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() +endif() blender_add_lib(bf_editor_sculpt_paint "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 04a14712d03..936226a03ed 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -2,7 +2,9 @@ #include "BLI_utildefines.h" +#include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_curves.hh" #include "BKE_paint.h" #include "WM_api.h" @@ -10,6 +12,19 @@ #include "ED_curves_sculpt.h" #include "ED_object.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +#include "DEG_depsgraph.h" + +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_screen_types.h" + +#include "RNA_access.h" + +#include "BLI_index_mask_ops.hh" +#include "BLI_math_vector.hh" #include "curves_sculpt_intern.h" #include "paint_intern.h" @@ -35,14 +50,178 @@ bool CURVES_SCULPT_mode_poll_view3d(bContext *C) return true; } +/** \} */ + namespace blender::ed::sculpt_paint { -/** \} */ +using blender::bke::CurvesGeometry; /* -------------------------------------------------------------------- */ /** \name * SCULPT_CURVES_OT_brush_stroke * \{ */ +struct StrokeExtension { + bool is_first; + float2 mouse_position; +}; + +/** + * Base class for stroke based operations in curves sculpt mode. + */ +class CurvesSculptStrokeOperation { + public: + virtual ~CurvesSculptStrokeOperation() = default; + virtual void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) = 0; +}; + +class DeleteOperation : public CurvesSculptStrokeOperation { + private: + float2 last_mouse_position_; + + public: + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) + { + Scene &scene = *CTX_data_scene(C); + Object &object = *CTX_data_active_object(C); + ARegion *region = CTX_wm_region(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt; + Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); + const float brush_radius = BKE_brush_size_get(&scene, &brush); + + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d, &object, projection.values); + + Curves &curves_id = *static_cast(object.data); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + MutableSpan positions = curves.positions(); + + const float2 mouse_start = stroke_extension.is_first ? stroke_extension.mouse_position : + last_mouse_position_; + const float2 mouse_end = stroke_extension.mouse_position; + + /* Find indices of curves that have to be removed. */ + Vector indices; + const IndexMask curves_to_remove = index_mask_ops::find_indices_based_on_predicate( + curves.curves_range(), 512, indices, [&](const int curve_i) { + const IndexRange point_range = curves.range_for_curve(curve_i); + for (const int segment_i : IndexRange(point_range.size() - 1)) { + const float3 pos1 = positions[point_range[segment_i]]; + const float3 pos2 = positions[point_range[segment_i + 1]]; + + float2 pos1_proj, pos2_proj; + ED_view3d_project_float_v2_m4(region, pos1, pos1_proj, projection.values); + ED_view3d_project_float_v2_m4(region, pos2, pos2_proj, projection.values); + + const float dist = dist_seg_seg_v2(pos1_proj, pos2_proj, mouse_start, mouse_end); + if (dist <= brush_radius) { + return true; + } + } + return false; + }); + + /* Just reset positions instead of actually removing the curves. This is just a prototype. */ + threading::parallel_for(curves_to_remove.index_range(), 512, [&](const IndexRange range) { + for (const int curve_i : curves_to_remove.slice(range)) { + for (const int point_i : curves.range_for_curve(curve_i)) { + positions[point_i] = {0.0f, 0.0f, 0.0f}; + } + } + }); + + curves.tag_positions_changed(); + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); + + last_mouse_position_ = stroke_extension.mouse_position; + } +}; + +class MoveOperation : public CurvesSculptStrokeOperation { + private: + Vector points_to_move_indices_; + IndexMask points_to_move_; + float2 last_mouse_position_; + + public: + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) + { + Scene &scene = *CTX_data_scene(C); + Object &object = *CTX_data_active_object(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt; + Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); + const float brush_radius = BKE_brush_size_get(&scene, &brush); + + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d, &object, projection.values); + + Curves &curves_id = *static_cast(object.data); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + MutableSpan positions = curves.positions(); + + if (stroke_extension.is_first) { + /* Find point indices to move. */ + points_to_move_ = index_mask_ops::find_indices_based_on_predicate( + curves.points_range(), 512, points_to_move_indices_, [&](const int64_t point_i) { + const float3 position = positions[point_i]; + float2 screen_position; + ED_view3d_project_float_v2_m4(region, position, screen_position, projection.values); + const float distance = len_v2v2(screen_position, stroke_extension.mouse_position); + return distance <= brush_radius; + }); + } + else { + /* Move points based on mouse movement. */ + const float2 mouse_diff = stroke_extension.mouse_position - last_mouse_position_; + threading::parallel_for(points_to_move_.index_range(), 512, [&](const IndexRange range) { + for (const int point_i : points_to_move_.slice(range)) { + const float3 old_position = positions[point_i]; + float2 old_position_screen; + ED_view3d_project_float_v2_m4( + region, old_position, old_position_screen, projection.values); + const float2 new_position_screen = old_position_screen + mouse_diff; + float3 new_position; + ED_view3d_win_to_3d(v3d, region, old_position, new_position_screen, new_position); + positions[point_i] = new_position; + } + }); + + curves.tag_positions_changed(); + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); + } + + last_mouse_position_ = stroke_extension.mouse_position; + } +}; + +static std::unique_ptr start_brush_operation(bContext *C, + wmOperator *UNUSED(op)) +{ + Scene &scene = *CTX_data_scene(C); + CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt; + Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); + switch (brush.curves_sculpt_tool) { + case CURVES_SCULPT_TOOL_TEST1: + return std::make_unique(); + case CURVES_SCULPT_TOOL_TEST2: + return std::make_unique(); + } + BLI_assert_unreachable(); + return {}; +} + +struct SculptCurvesBrushStrokeData { + std::unique_ptr operation; + PaintStroke *stroke; +}; + static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) { out[0] = mouse[0]; @@ -60,10 +239,24 @@ static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mo static void stroke_update_step(bContext *C, wmOperator *op, - PaintStroke *stroke, - PointerRNA *itemptr) + PaintStroke *UNUSED(stroke), + PointerRNA *stroke_element) { - UNUSED_VARS(C, op, stroke, itemptr); + SculptCurvesBrushStrokeData *op_data = static_cast( + op->customdata); + + StrokeExtension stroke_extension; + RNA_float_get_array(stroke_element, "mouse", stroke_extension.mouse_position); + + if (!op_data->operation) { + stroke_extension.is_first = true; + op_data->operation = start_brush_operation(C, op); + } + else { + stroke_extension.is_first = false; + } + + op_data->operation->on_stroke_extended(C, stroke_extension); } static void stroke_done(const bContext *C, PaintStroke *stroke) @@ -73,15 +266,23 @@ static void stroke_done(const bContext *C, PaintStroke *stroke) static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) { - PaintStroke *stroke = paint_stroke_new(C, - op, - stroke_get_location, - stroke_test_start, - stroke_update_step, - nullptr, - stroke_done, - event->type); - op->customdata = stroke; + SculptCurvesBrushStrokeData *op_data = MEM_new(__func__); + op_data->stroke = paint_stroke_new(C, + op, + stroke_get_location, + stroke_test_start, + stroke_update_step, + nullptr, + stroke_done, + event->type); + op->customdata = op_data; + + int return_value = op->type->modal(C, op, event); + if (return_value == OPERATOR_FINISHED) { + paint_stroke_free(C, op, op_data->stroke); + MEM_delete(op_data); + return OPERATOR_FINISHED; + } WM_event_add_modal_handler(C, op); return OPERATOR_RUNNING_MODAL; @@ -89,12 +290,21 @@ static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEven static int sculpt_curves_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) { - return paint_stroke_modal(C, op, event, static_cast(op->customdata)); + SculptCurvesBrushStrokeData *op_data = static_cast( + op->customdata); + int return_value = paint_stroke_modal(C, op, event, op_data->stroke); + if (ELEM(return_value, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { + MEM_delete(op_data); + } + return return_value; } static void sculpt_curves_stroke_cancel(bContext *C, wmOperator *op) { - paint_stroke_cancel(C, op, static_cast(op->customdata)); + SculptCurvesBrushStrokeData *op_data = static_cast( + op->customdata); + paint_stroke_cancel(C, op, op_data->stroke); + MEM_delete(op_data); } static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot)