diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/curves_sculpt_ops.cc')
-rw-r--r-- | source/blender/editors/sculpt_paint/curves_sculpt_ops.cc | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc new file mode 100644 index 00000000000..936226a03ed --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_utildefines.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_paint.h" + +#include "WM_api.h" +#include "WM_toolsystem.h" + +#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" + +/* -------------------------------------------------------------------- */ +/** \name Poll Functions + * \{ */ + +bool CURVES_SCULPT_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && ob->mode & OB_MODE_SCULPT_CURVES; +} + +bool CURVES_SCULPT_mode_poll_view3d(bContext *C) +{ + if (!CURVES_SCULPT_mode_poll(C)) { + return false; + } + if (CTX_wm_region_view3d(C) == nullptr) { + return false; + } + 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<Curves *>(object.data); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + MutableSpan<float3> 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<int64_t> 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<int64_t> 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<Curves *>(object.data); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + MutableSpan<float3> 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<CurvesSculptStrokeOperation> 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<MoveOperation>(); + case CURVES_SCULPT_TOOL_TEST2: + return std::make_unique<DeleteOperation>(); + } + BLI_assert_unreachable(); + return {}; +} + +struct SculptCurvesBrushStrokeData { + std::unique_ptr<CurvesSculptStrokeOperation> operation; + PaintStroke *stroke; +}; + +static bool stroke_get_location(bContext *C, float out[3], const float mouse[2]) +{ + out[0] = mouse[0]; + out[1] = mouse[1]; + out[2] = 0; + UNUSED_VARS(C); + return true; +} + +static bool stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +{ + UNUSED_VARS(C, op, mouse); + return true; +} + +static void stroke_update_step(bContext *C, + wmOperator *op, + PaintStroke *UNUSED(stroke), + PointerRNA *stroke_element) +{ + SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>( + 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) +{ + UNUSED_VARS(C, stroke); +} + +static int sculpt_curves_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + SculptCurvesBrushStrokeData *op_data = MEM_new<SculptCurvesBrushStrokeData>(__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; +} + +static int sculpt_curves_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>( + 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) +{ + SculptCurvesBrushStrokeData *op_data = static_cast<SculptCurvesBrushStrokeData *>( + op->customdata); + paint_stroke_cancel(C, op, op_data->stroke); + MEM_delete(op_data); +} + +static void SCULPT_CURVES_OT_brush_stroke(struct wmOperatorType *ot) +{ + ot->name = "Stroke Curves Sculpt"; + ot->idname = "SCULPT_CURVES_OT_brush_stroke"; + ot->description = "Sculpt curves using a brush"; + + ot->invoke = sculpt_curves_stroke_invoke; + ot->modal = sculpt_curves_stroke_modal; + ot->cancel = sculpt_curves_stroke_cancel; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + paint_stroke_operator_properties(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name * CURVES_OT_sculptmode_toggle + * \{ */ + +static bool curves_sculptmode_toggle_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + if (ob == nullptr) { + return false; + } + if (ob->type != OB_CURVES) { + return false; + } + return true; +} + +static void curves_sculptmode_enter(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + Object *ob = CTX_data_active_object(C); + BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt); + CurvesSculpt *curves_sculpt = scene->toolsettings->curves_sculpt; + + ob->mode = OB_MODE_SCULPT_CURVES; + + paint_cursor_start(&curves_sculpt->paint, CURVES_SCULPT_mode_poll_view3d); +} + +static void curves_sculptmode_exit(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + ob->mode = OB_MODE_OBJECT; +} + +static int curves_sculptmode_toggle_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + const bool is_mode_set = ob->mode == OB_MODE_SCULPT_CURVES; + + if (is_mode_set) { + if (!ED_object_mode_compat_set(C, ob, OB_MODE_SCULPT_CURVES, op->reports)) { + return OPERATOR_CANCELLED; + } + } + + if (is_mode_set) { + curves_sculptmode_exit(C); + } + else { + curves_sculptmode_enter(C); + } + + WM_toolsystem_update_from_context_view3d(C); + WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr); + return OPERATOR_CANCELLED; +} + +static void CURVES_OT_sculptmode_toggle(wmOperatorType *ot) +{ + ot->name = "Curve Sculpt Mode Toggle"; + ot->idname = "CURVES_OT_sculptmode_toggle"; + ot->description = "Enter/Exit sculpt mode for curves"; + + ot->exec = curves_sculptmode_toggle_exec; + ot->poll = curves_sculptmode_toggle_poll; + + ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER; +} + +} // namespace blender::ed::sculpt_paint + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name * Registration + * \{ */ + +void ED_operatortypes_sculpt_curves() +{ + using namespace blender::ed::sculpt_paint; + WM_operatortype_append(SCULPT_CURVES_OT_brush_stroke); + WM_operatortype_append(CURVES_OT_sculptmode_toggle); +} + +/** \} */ |