diff --git a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh index 70bc30997ff..7d92b503c8a 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_intern.hh +++ b/source/blender/editors/sculpt_paint/grease_pencil_intern.hh @@ -4,6 +4,8 @@ #pragma once +#include + #include "BLI_color.hh" #include "BLI_task.hh" @@ -29,6 +31,14 @@ struct GeometryDeformation; namespace blender::ed::sculpt_paint { +/** + * Projects a screen-space displacement vector into layer space. + * Current position (in layer space) is used to compute the perspective distance (zfac). + * Returns the new layer space position with the projected delta applied. + */ +using DeltaProjectionFunc = + std::function; + struct InputSample { float2 mouse_position; float pressure; @@ -85,6 +95,8 @@ bool is_brush_inverted(const Brush &brush, BrushStrokeMode stroke_mode); struct GreasePencilStrokeParams { const ToolSettings &toolsettings; const ARegion ®ion; + const RegionView3D &rv3d; + const Scene &scene; Object &ob_orig; Object &ob_eval; const bke::greasepencil::Layer &layer; @@ -98,6 +110,7 @@ struct GreasePencilStrokeParams { static GreasePencilStrokeParams from_context(const Scene &scene, Depsgraph &depsgraph, ARegion ®ion, + RegionView3D &rv3d, Object &object, int layer_index, int frame_number, @@ -125,6 +138,12 @@ Array calculate_view_positions(const GreasePencilStrokeParams ¶ms, Array calculate_view_radii(const GreasePencilStrokeParams ¶ms, const IndexMask &selection); +/* Get an appropriate projection function from screen space to layer space. + * This is an alternative to using the DrawingPlacement. */ +DeltaProjectionFunc get_screen_projection_fn(const GreasePencilStrokeParams ¶ms, + const Object &object, + const bke::greasepencil::Layer &layer); + bool do_vertex_color_points(const Brush &brush); bool do_vertex_color_fill(const Brush &brush); @@ -158,9 +177,10 @@ class GreasePencilStrokeOperationCommon : public GreasePencilStrokeOperation { const bContext &C, GrainSize grain_size, FunctionRef fn) const; - void foreach_editable_drawing(const bContext &C, - FunctionRef fn) const; + void foreach_editable_drawing( + const bContext &C, + FunctionRef fn) const; }; /* Operations */ diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc index 6aa6a4a5e47..eb0601a6b6b 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint_common.cc @@ -194,10 +194,80 @@ bool is_brush_inverted(const Brush &brush, const BrushStrokeMode stroke_mode) return bool(brush.flag & BRUSH_DIR_IN) ^ (stroke_mode == BrushStrokeMode::BRUSH_STROKE_INVERT); } +DeltaProjectionFunc get_screen_projection_fn(const GreasePencilStrokeParams ¶ms, + const Object &object, + const bke::greasepencil::Layer &layer) +{ + const float4x4 view_to_world = float4x4(params.rv3d.viewinv); + const float4x4 layer_to_world = layer.to_world_space(object); + const float4x4 world_to_layer = math::invert(layer_to_world); + + auto screen_to_world = [=](const float3 &world_pos, const float2 &screen_delta) { + const float zfac = ED_view3d_calc_zfac(¶ms.rv3d, world_pos); + float3 world_delta; + ED_view3d_win_to_delta(¶ms.region, screen_delta, zfac, world_delta); + return world_delta; + }; + + switch (params.toolsettings.gp_sculpt.lock_axis) { + case GP_LOCKAXIS_VIEW: { + const float3 world_normal = view_to_world.z_axis(); + return [=](const float3 &position, const float2 &screen_delta) { + const float3 world_pos = math::transform_point(layer_to_world, position); + const float3 world_delta = screen_to_world(world_pos, screen_delta); + const float3 layer_delta = math::transform_direction( + world_to_layer, world_delta - world_normal * math::dot(world_delta, world_normal)); + return position + layer_delta; + }; + } + case GP_LOCKAXIS_X: { + return [=](const float3 &position, const float2 &screen_delta) { + const float3 world_pos = math::transform_point(layer_to_world, position); + const float3 world_delta = screen_to_world(world_pos, screen_delta); + const float3 layer_delta = math::transform_direction( + world_to_layer, float3(0.0f, world_delta.y, world_delta.z)); + return position + layer_delta; + }; + } + case GP_LOCKAXIS_Y: { + return [=](const float3 &position, const float2 &screen_delta) { + const float3 world_pos = math::transform_point(layer_to_world, position); + const float3 world_delta = screen_to_world(world_pos, screen_delta); + const float3 layer_delta = math::transform_direction( + world_to_layer, float3(world_delta.x, 0.0f, world_delta.z)); + return position + layer_delta; + }; + } + case GP_LOCKAXIS_Z: { + return [=](const float3 &position, const float2 &screen_delta) { + const float3 world_pos = math::transform_point(layer_to_world, position); + const float3 world_delta = screen_to_world(world_pos, screen_delta); + const float3 layer_delta = math::transform_direction( + world_to_layer, float3(world_delta.x, world_delta.y, 0.0f)); + return position + layer_delta; + }; + } + case GP_LOCKAXIS_CURSOR: { + const float3 world_normal = params.scene.cursor.matrix().z_axis(); + return [=](const float3 &position, const float2 &screen_delta) { + const float3 world_pos = math::transform_point(layer_to_world, position); + const float3 world_delta = screen_to_world(world_pos, screen_delta); + const float3 layer_delta = math::transform_direction( + world_to_layer, world_delta - world_normal * math::dot(world_delta, world_normal)); + return position + layer_delta; + }; + } + } + + BLI_assert_unreachable(); + return [](const float3 &, const float2 &) { return float3(); }; +} + GreasePencilStrokeParams GreasePencilStrokeParams::from_context( const Scene &scene, Depsgraph &depsgraph, ARegion ®ion, + RegionView3D &rv3d, Object &object, const int layer_index, const int frame_number, @@ -210,6 +280,8 @@ GreasePencilStrokeParams GreasePencilStrokeParams::from_context( const bke::greasepencil::Layer &layer = grease_pencil.layer(layer_index); return {*scene.toolsettings, region, + rv3d, + scene, object, ob_eval, layer, @@ -327,6 +399,7 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( const Scene &scene = *CTX_data_scene(&C); Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); ARegion ®ion = *CTX_wm_region(&C); + RegionView3D &rv3d = *CTX_wm_region_view3d(&C); Object &object = *CTX_data_active_object(&C); GreasePencil &grease_pencil = *static_cast(object.data); @@ -338,6 +411,7 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( scene, depsgraph, region, + rv3d, object, info.layer_index, info.frame_number, @@ -364,6 +438,7 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( const Scene &scene = *CTX_data_scene(&C); Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); ARegion ®ion = *CTX_wm_region(&C); + RegionView3D &rv3d = *CTX_wm_region_view3d(&C); Object &object = *CTX_data_active_object(&C); GreasePencil &grease_pencil = *static_cast(object.data); @@ -376,6 +451,7 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( scene, depsgraph, region, + rv3d, object, info.layer_index, info.frame_number, @@ -395,15 +471,15 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( void GreasePencilStrokeOperationCommon::foreach_editable_drawing( const bContext &C, - FunctionRef - fn) const + FunctionRef fn) const { using namespace blender::bke::greasepencil; const Scene &scene = *CTX_data_scene(&C); Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); - View3D &view3d = *CTX_wm_view3d(&C); ARegion ®ion = *CTX_wm_region(&C); + RegionView3D &rv3d = *CTX_wm_region_view3d(&C); Object &object = *CTX_data_active_object(&C); Object &object_eval = *DEG_get_evaluated_object(&depsgraph, &object); GreasePencil &grease_pencil = *static_cast(object.data); @@ -413,25 +489,18 @@ void GreasePencilStrokeOperationCommon::foreach_editable_drawing( threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) { const Layer &layer = grease_pencil.layer(info.layer_index); - DrawingPlacement placement(scene, region, view3d, object_eval, &layer); - if (placement.use_project_to_surface()) { - placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); - } - else if (placement.use_project_to_nearest_stroke()) { - placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); - placement.set_origin_to_nearest_stroke(this->start_mouse_position); - } - - GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( + const GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( scene, depsgraph, region, + rv3d, object, info.layer_index, info.frame_number, info.multi_frame_falloff, info.drawing); - if (fn(params, placement)) { + const DeltaProjectionFunc projection_fn = get_screen_projection_fn(params, object_eval, layer); + if (fn(params, projection_fn)) { changed = true; } }); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc index a971fcacadb..1064941211d 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_clone.cc @@ -47,15 +47,14 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start * * Here we only have the GPv2 behavior that actually works for now. */ this->foreach_editable_drawing( - C, - [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement) { + C, [&](const GreasePencilStrokeParams ¶ms, const DeltaProjectionFunc &projection_fn) { const IndexRange pasted_curves = ed::greasepencil::clipboard_paste_strokes( bmain, object, params.drawing, false); if (pasted_curves.is_empty()) { return false; } + const bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); const OffsetIndices pasted_points_by_curve = curves.points_by_curve().slice( pasted_curves); @@ -73,7 +72,7 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start MutableSpan positions = curves.positions_for_write(); threading::parallel_for(pasted_points, 4096, [&](const IndexRange range) { for (const int point_i : range) { - positions[point_i] = placement.project(view_positions[point_i] + mouse_delta); + positions[point_i] = projection_fn(deformation.positions[point_i], mouse_delta); } }); params.drawing.tag_positions_changed(); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc index fbcdd85b485..33b8ed17920 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_grab.cc @@ -31,7 +31,6 @@ class GrabOperation : public GreasePencilStrokeOperationCommon { public: using GreasePencilStrokeOperationCommon::GreasePencilStrokeOperationCommon; using MutableDrawingInfo = blender::ed::greasepencil::MutableDrawingInfo; - using DrawingPlacement = ed::greasepencil::DrawingPlacement; /* Cached point mask and influence for a particular drawing. */ struct PointWeights { @@ -51,12 +50,11 @@ class GrabOperation : public GreasePencilStrokeOperationCommon { /* Cached point data for each affected drawing. */ Array drawing_data; - void foreach_grabbed_drawing( - const bContext &C, - FunctionRef weights)> fn) const; + void foreach_grabbed_drawing(const bContext &C, + FunctionRef weights)> fn) const; void on_stroke_begin(const bContext &C, const InputSample &start_sample) override; void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override; @@ -66,7 +64,7 @@ class GrabOperation : public GreasePencilStrokeOperationCommon { void GrabOperation::foreach_grabbed_drawing( const bContext &C, FunctionRef weights)> fn) const { @@ -76,7 +74,7 @@ void GrabOperation::foreach_grabbed_drawing( const Scene &scene = *CTX_data_scene(&C); Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C); ARegion ®ion = *CTX_wm_region(&C); - View3D &view3d = *CTX_wm_view3d(&C); + RegionView3D &rv3d = *CTX_wm_region_view3d(&C); Object &object = *CTX_data_active_object(&C); Object &object_eval = *DEG_get_evaluated_object(&depsgraph, &object); GreasePencil &grease_pencil = *static_cast(object.data); @@ -94,25 +92,18 @@ void GrabOperation::foreach_grabbed_drawing( return; } - ed::greasepencil::DrawingPlacement placement(scene, region, view3d, object_eval, &layer); - if (placement.use_project_to_surface()) { - placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); - } - else if (placement.use_project_to_nearest_stroke()) { - placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); - placement.set_origin_to_nearest_stroke(this->start_mouse_position); - } - GreasePencilStrokeParams params = GreasePencilStrokeParams::from_context( scene, depsgraph, region, + rv3d, object, data.layer_index, data.frame_number, data.multi_frame_falloff, *drawing); - if (fn(params, placement, data.point_mask, data.weights)) { + DeltaProjectionFunc projection_fn = get_screen_projection_fn(params, object_eval, layer); + if (fn(params, projection_fn, data.point_mask, data.weights)) { changed = true; } }); @@ -154,6 +145,8 @@ void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_ GreasePencilStrokeParams params = {*scene.toolsettings, region, + rv3d, + scene, ob_orig, ob_eval, layer, @@ -195,38 +188,22 @@ void GrabOperation::on_stroke_begin(const bContext &C, const InputSample &start_ void GrabOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample) { - const ARegion ®ion = *CTX_wm_region(&C); - const RegionView3D &rv3d = *CTX_wm_region_view3d(&C); - this->foreach_grabbed_drawing( C, [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement, + const DeltaProjectionFunc &projection_fn, const IndexMask &mask, const Span weights) { - /* Crazy-space deformation. */ - bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); - /* Transform mouse delta into layer space. */ const float2 mouse_delta_win = this->mouse_delta(extension_sample); - const float3 layer_origin = params.layer.to_world_space(params.ob_eval).location(); - const float zfac = ED_view3d_calc_zfac(&rv3d, layer_origin); - float3 mouse_delta; - ED_view3d_win_to_delta(®ion, mouse_delta_win, zfac, mouse_delta); - const float4x4 &world_to_layer = math::invert(params.layer.to_world_space(params.ob_eval)); - mouse_delta = math::transform_direction(world_to_layer, mouse_delta); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); MutableSpan positions = curves.positions_for_write(); - mask.foreach_index(GrainSize(1024), [&](const int point_i, const int index) { + mask.foreach_index(GrainSize(4096), [&](const int point_i, const int index) { /* Translate the point with the influence factor. */ - const float3 new_pos_layer = deformation.positions[point_i] + - mouse_delta * weights[index]; - const float3 new_pos_world = math::transform_point( - params.layer.to_world_space(params.ob_eval), new_pos_layer); - float2 new_pos_view; - ED_view3d_project_float_global(®ion, new_pos_world, new_pos_view, V3D_PROJ_TEST_NOP); - positions[point_i] = placement.project(new_pos_view); + positions[point_i] = projection_fn(deformation.positions[point_i], + mouse_delta_win * weights[index]); }); params.drawing.tag_positions_changed(); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc index c5536ca29ec..e79fda5b8d9 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_pinch.cc @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_context.hh" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" #include "BKE_paint.hh" @@ -42,15 +43,14 @@ void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &ex eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); this->foreach_editable_drawing( - C, - [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement) { + C, [&](const GreasePencilStrokeParams ¶ms, const DeltaProjectionFunc &projection_fn) { IndexMaskMemory selection_memory; const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); Array view_positions = calculate_view_positions(params, selection); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); MutableSpan positions = curves.positions_for_write(); @@ -65,9 +65,11 @@ void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &ex return; } - const float scale_offset = influence * influence / 25.0f; - const float scale = invert ? 1.0 + scale_offset : 1.0f - scale_offset; - positions[point_i] = placement.project(target + (co - target) * scale); + const float influence_squared = influence * influence / 25.0f; + const float influence_final = invert ? 1.0 + influence_squared : + 1.0f - influence_squared; + positions[point_i] = projection_fn(deformation.positions[point_i], + (target - co) * (1.0f - influence_final)); }); params.drawing.tag_positions_changed(); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc index de87f6be25c..aeba883f602 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_push.cc @@ -3,6 +3,7 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "BKE_context.hh" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" #include "BKE_paint.hh" @@ -42,15 +43,14 @@ void PushOperation::on_stroke_extended(const bContext &C, const InputSample &ext eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); this->foreach_editable_drawing( - C, - [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement) { + C, [&](const GreasePencilStrokeParams ¶ms, const DeltaProjectionFunc &projection_fn) { IndexMaskMemory selection_memory; const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); Array view_positions = calculate_view_positions(params, selection); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); MutableSpan positions = curves.positions_for_write(); @@ -65,7 +65,8 @@ void PushOperation::on_stroke_extended(const bContext &C, const InputSample &ext return; } - positions[point_i] = placement.project(co + mouse_delta * influence); + positions[point_i] = projection_fn(deformation.positions[point_i], + mouse_delta * influence); }); params.drawing.tag_positions_changed(); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc index fa2835f6096..aabaf75df82 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_randomize.cc @@ -8,6 +8,7 @@ #include "BLI_task.hh" #include "BKE_context.hh" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" #include "BKE_paint.hh" @@ -62,9 +63,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); this->foreach_editable_drawing( - C, - [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement) { + C, [&](const GreasePencilStrokeParams ¶ms, const DeltaProjectionFunc &projection_fn) { const uint32_t seed = this->unique_seed(); IndexMaskMemory selection_memory; @@ -73,6 +72,7 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample return false; } + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); Array view_positions = calculate_view_positions(params, selection); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); @@ -93,7 +93,8 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample return; } const float noise = 2.0f * hash_rng(seed, 5678, point_i) - 1.0f; - positions[point_i] = placement.project(co + sideways * influence * noise); + positions[point_i] = projection_fn(deformation.positions[point_i], + sideways * influence * noise); }); params.drawing.tag_positions_changed(); diff --git a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc index df7100109a0..e3aeb874c1f 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_sculpt_twist.cc @@ -5,6 +5,7 @@ #include "BLI_math_rotation.h" #include "BKE_context.hh" +#include "BKE_crazyspace.hh" #include "BKE_curves.hh" #include "BKE_grease_pencil.hh" #include "BKE_paint.hh" @@ -52,15 +53,14 @@ void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &ex eGP_Sculpt_SelectMaskFlag(scene.toolsettings->gpencil_selectmode_sculpt)); this->foreach_editable_drawing( - C, - [&](const GreasePencilStrokeParams ¶ms, - const ed::greasepencil::DrawingPlacement &placement) { + C, [&](const GreasePencilStrokeParams ¶ms, const DeltaProjectionFunc &projection_fn) { IndexMaskMemory selection_memory; const IndexMask selection = point_selection_mask(params, is_masking, selection_memory); if (selection.is_empty()) { return false; } + bke::crazyspace::GeometryDeformation deformation = get_drawing_deformation(params); Array view_positions = calculate_view_positions(params, selection); bke::CurvesGeometry &curves = params.drawing.strokes_for_write(); MutableSpan positions = curves.positions_for_write(); @@ -76,8 +76,10 @@ void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &ex } const float angle = DEG2RADF(invert ? -1.0f : 1.0f) * influence; - positions[point_i] = placement.project(rotate_by_angle(co - mouse_pos, angle) + - mouse_pos); + const float2 radial_offset = co - mouse_pos; + positions[point_i] = projection_fn(deformation.positions[point_i], + rotate_by_angle(radial_offset, angle) - + radial_offset); }); params.drawing.tag_positions_changed();