diff options
author | Jacques Lucke <jacques@blender.org> | 2022-03-17 12:03:46 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-03-17 12:04:02 +0300 |
commit | b6702aa604f2b022186e4d6663531341d4082947 (patch) | |
tree | 11f4d7564419c89db077ed69db47503021d832a3 /source/blender/editors | |
parent | 884b167f74b2ec9e800cc64476bc036035139e02 (diff) |
Curves: separate sculpt brushes into separate files
This makes it easier to work on these brushes in parallel.
Diffstat (limited to 'source/blender/editors')
6 files changed, 473 insertions, 309 deletions
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index ccbdb3c4145..a2043c9be21 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -27,7 +27,10 @@ set(INC ) set(SRC + curves_sculpt_comb.cc + curves_sculpt_delete.cc curves_sculpt_ops.cc + curves_sculpt_snake_hook.cc paint_cursor.c paint_curve.c paint_curve_undo.c @@ -72,6 +75,7 @@ set(SRC sculpt_uv.c curves_sculpt_intern.h + curves_sculpt_intern.hh paint_intern.h sculpt_intern.h ) diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc new file mode 100644 index 00000000000..318c7c0c668 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <algorithm> + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_index_mask_ops.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_paint.h" +#include "BKE_spline.hh" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +namespace blender::ed::sculpt_paint { + +using blender::bke::CurvesGeometry; + +/** + * Moves individual points under the brush and does a length preservation step afterwards. + */ +class CombOperation : public CurvesSculptStrokeOperation { + private: + float2 last_mouse_position_; + + public: + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override + { + BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); + + if (stroke_extension.is_first) { + return; + } + + 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); + const float brush_strength = BKE_brush_alpha_get(&scene, &brush); + + const float4x4 ob_mat = object.obmat; + const float4x4 ob_imat = ob_mat.inverted(); + + 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_prev = last_mouse_position_; + const float2 mouse_cur = stroke_extension.mouse_position; + const float2 mouse_diff = mouse_cur - mouse_prev; + const float mouse_diff_len = math::length(mouse_diff); + + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { + for (const int curve_i : curves_range) { + const IndexRange curve_points = curves.range_for_curve(curve_i); + /* Compute lengths of the segments. Those are used to make sure that the lengths don't + * change. */ + Vector<float, 16> segment_lengths(curve_points.size() - 1); + for (const int segment_i : IndexRange(curve_points.size() - 1)) { + const float3 &p1 = positions[curve_points[segment_i]]; + const float3 &p2 = positions[curve_points[segment_i] + 1]; + const float length = math::distance(p1, p2); + segment_lengths[segment_i] = length; + } + bool curve_changed = false; + for (const int point_i : curve_points.drop_front(1)) { + const float3 old_position = positions[point_i]; + + /* Find the position of the point in screen space. */ + float2 old_position_screen; + ED_view3d_project_float_v2_m4( + region, old_position, old_position_screen, projection.values); + + /* Project the point onto the line drawn by the mouse. Note, it's projected on the + * infinite line, not only on the line segment. */ + float2 old_position_screen_proj; + /* t is 0 when the point is closest to the previous mouse position and 1 when it's + * closest to the current mouse position. */ + const float t = closest_to_line_v2( + old_position_screen_proj, old_position_screen, mouse_prev, mouse_cur); + + /* Compute the distance to the mouse line segment. */ + const float2 old_position_screen_proj_segment = mouse_prev + + std::clamp(t, 0.0f, 1.0f) * mouse_diff; + const float distance_screen = math::distance(old_position_screen, + old_position_screen_proj_segment); + if (distance_screen > brush_radius) { + /* Ignore the point because it's too far away. */ + continue; + } + /* Compute a falloff that is based on how far along the point along the last stroke + * segment is. */ + const float t_overshoot = brush_radius / mouse_diff_len; + const float t_falloff = 1.0f - std::max(t, 0.0f) / (1.0f + t_overshoot); + /* A falloff that is based on how far away the point is from the stroke. */ + const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); + /* Combine the different falloffs and brush strength. */ + const float weight = brush_strength * t_falloff * radius_falloff; + + /* Offset the old point position in screen space and transform it back into 3D space. */ + const float2 new_position_screen = old_position_screen + mouse_diff * weight; + float3 new_position; + ED_view3d_win_to_3d( + v3d, region, ob_mat * old_position, new_position_screen, new_position); + new_position = ob_imat * new_position; + positions[point_i] = new_position; + + curve_changed = true; + } + if (!curve_changed) { + continue; + } + /* Ensure that the length of each segment stays the same. */ + for (const int segment_i : IndexRange(curve_points.size() - 1)) { + const float3 &p1 = positions[curve_points[segment_i]]; + float3 &p2 = positions[curve_points[segment_i] + 1]; + const float3 direction = math::normalize(p2 - p1); + const float desired_length = segment_lengths[segment_i]; + p2 = p1 + direction * desired_length; + } + } + }); + + curves.tag_positions_changed(); + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); + } +}; + +std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation() +{ + return std::make_unique<CombOperation>(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc new file mode 100644 index 00000000000..8a87fb729ae --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_delete.cc @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <algorithm> + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_index_mask_ops.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_paint.h" +#include "BKE_spline.hh" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +namespace blender::ed::sculpt_paint { + +using blender::bke::CurvesGeometry; + +class DeleteOperation : public CurvesSculptStrokeOperation { + private: + float2 last_mouse_position_; + + public: + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override + { + 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; + }); + + curves.remove_curves(curves_to_remove); + + 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; + } +}; + +std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation() +{ + return std::make_unique<DeleteOperation>(); +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh new file mode 100644 index 00000000000..14d7ec01251 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "curves_sculpt_intern.h" + +#include "BLI_math_vector.hh" + +namespace blender::ed::sculpt_paint { + +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; +}; + +std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation(); +std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation(); +std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation(); + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 63202f3902a..178505e2276 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -40,6 +40,7 @@ #include "PIL_time.h" #include "curves_sculpt_intern.h" +#include "curves_sculpt_intern.hh" #include "paint_intern.h" /* -------------------------------------------------------------------- */ @@ -74,312 +75,6 @@ using blender::fn::CPPType; /** \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) override - { - 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; - }); - - curves.remove_curves(curves_to_remove); - - 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; - } -}; - -/** - * Moves individual points under the brush and does a length preservation step afterwards. - */ -class CombOperation : public CurvesSculptStrokeOperation { - private: - float2 last_mouse_position_; - - public: - void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override - { - BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); - - if (stroke_extension.is_first) { - return; - } - - 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); - const float brush_strength = BKE_brush_alpha_get(&scene, &brush); - - const float4x4 ob_mat = object.obmat; - const float4x4 ob_imat = ob_mat.inverted(); - - 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_prev = last_mouse_position_; - const float2 mouse_cur = stroke_extension.mouse_position; - const float2 mouse_diff = mouse_cur - mouse_prev; - const float mouse_diff_len = math::length(mouse_diff); - - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { - for (const int curve_i : curves_range) { - const IndexRange curve_points = curves.range_for_curve(curve_i); - /* Compute lengths of the segments. Those are used to make sure that the lengths don't - * change. */ - Vector<float, 16> segment_lengths(curve_points.size() - 1); - for (const int segment_i : IndexRange(curve_points.size() - 1)) { - const float3 &p1 = positions[curve_points[segment_i]]; - const float3 &p2 = positions[curve_points[segment_i] + 1]; - const float length = math::distance(p1, p2); - segment_lengths[segment_i] = length; - } - bool curve_changed = false; - for (const int point_i : curve_points.drop_front(1)) { - const float3 old_position = positions[point_i]; - - /* Find the position of the point in screen space. */ - float2 old_position_screen; - ED_view3d_project_float_v2_m4( - region, old_position, old_position_screen, projection.values); - - /* Project the point onto the line drawn by the mouse. Note, it's projected on the - * infinite line, not only on the line segment. */ - float2 old_position_screen_proj; - /* t is 0 when the point is closest to the previous mouse position and 1 when it's - * closest to the current mouse position. */ - const float t = closest_to_line_v2( - old_position_screen_proj, old_position_screen, mouse_prev, mouse_cur); - - /* Compute the distance to the mouse line segment. */ - const float2 old_position_screen_proj_segment = mouse_prev + - std::clamp(t, 0.0f, 1.0f) * mouse_diff; - const float distance_screen = math::distance(old_position_screen, - old_position_screen_proj_segment); - if (distance_screen > brush_radius) { - /* Ignore the point because it's too far away. */ - continue; - } - /* Compute a falloff that is based on how far along the point along the last stroke - * segment is. */ - const float t_overshoot = brush_radius / mouse_diff_len; - const float t_falloff = 1.0f - std::max(t, 0.0f) / (1.0f + t_overshoot); - /* A falloff that is based on how far away the point is from the stroke. */ - const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); - /* Combine the different falloffs and brush strength. */ - const float weight = brush_strength * t_falloff * radius_falloff; - - /* Offset the old point position in screen space and transform it back into 3D space. */ - const float2 new_position_screen = old_position_screen + mouse_diff * weight; - float3 new_position; - ED_view3d_win_to_3d( - v3d, region, ob_mat * old_position, new_position_screen, new_position); - new_position = ob_imat * new_position; - positions[point_i] = new_position; - - curve_changed = true; - } - if (!curve_changed) { - continue; - } - /* Ensure that the length of each segment stays the same. */ - for (const int segment_i : IndexRange(curve_points.size() - 1)) { - const float3 &p1 = positions[curve_points[segment_i]]; - float3 &p2 = positions[curve_points[segment_i] + 1]; - const float3 direction = math::normalize(p2 - p1); - const float desired_length = segment_lengths[segment_i]; - p2 = p1 + direction * desired_length; - } - } - }); - - curves.tag_positions_changed(); - DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); - } -}; - -/** - * Drags the tip point of each curve and resamples the rest of the curve. - */ -class SnakeHookOperation : public CurvesSculptStrokeOperation { - private: - float2 last_mouse_position_; - - public: - void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override - { - BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); - - if (stroke_extension.is_first) { - return; - } - - 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); - const float brush_strength = BKE_brush_alpha_get(&scene, &brush); - - const float4x4 ob_mat = object.obmat; - const float4x4 ob_imat = ob_mat.inverted(); - - 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_prev = last_mouse_position_; - const float2 mouse_cur = stroke_extension.mouse_position; - const float2 mouse_diff = mouse_cur - mouse_prev; - - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { - for (const int curve_i : curves_range) { - const IndexRange curve_points = curves.range_for_curve(curve_i); - const int last_point_i = curve_points.last(); - - const float3 old_position = positions[last_point_i]; - - float2 old_position_screen; - ED_view3d_project_float_v2_m4( - region, old_position, old_position_screen, projection.values); - - const float distance_screen = math::distance(old_position_screen, mouse_prev); - if (distance_screen > brush_radius) { - continue; - } - - const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); - const float weight = brush_strength * radius_falloff; - - const float2 new_position_screen = old_position_screen + mouse_diff * weight; - float3 new_position; - ED_view3d_win_to_3d(v3d, region, ob_mat * old_position, new_position_screen, new_position); - new_position = ob_imat * new_position; - - this->move_last_point_and_resample(positions, curve_points, new_position); - } - }); - - curves.tag_positions_changed(); - DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); - } - - void move_last_point_and_resample(MutableSpan<float3> positions, - const IndexRange curve_points, - const float3 &new_last_point_position) const - { - Vector<float> old_lengths; - old_lengths.append(0.0f); - /* Used to (1) normalize the segment sizes over time and (2) support making zero-length - * segments */ - const float extra_length = 0.001f; - for (const int segment_i : IndexRange(curve_points.size() - 1)) { - const float3 &p1 = positions[curve_points[segment_i]]; - const float3 &p2 = positions[curve_points[segment_i] + 1]; - const float length = math::distance(p1, p2); - old_lengths.append(old_lengths.last() + length + extra_length); - } - Vector<float> point_factors; - for (float &old_length : old_lengths) { - point_factors.append(old_length / old_lengths.last()); - } - - PolySpline new_spline; - new_spline.resize(curve_points.size()); - MutableSpan<float3> new_spline_positions = new_spline.positions(); - for (const int i : IndexRange(curve_points.size() - 1)) { - new_spline_positions[i] = positions[curve_points[i]]; - } - new_spline_positions.last() = new_last_point_position; - new_spline.mark_cache_invalid(); - - for (const int i : IndexRange(curve_points.size())) { - const float factor = point_factors[i]; - const Spline::LookupResult lookup = new_spline.lookup_evaluated_factor(factor); - const float index_factor = lookup.evaluated_index + lookup.factor; - float3 p; - new_spline.sample_with_index_factors<float3>( - new_spline_positions, {&index_factor, 1}, {&p, 1}); - positions[curve_points[i]] = p; - } - } -}; - /** * Resamples the curves to a shorter length. */ @@ -932,11 +627,11 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte Brush &brush = *BKE_paint_brush(&curves_sculpt.paint); switch (brush.curves_sculpt_tool) { case CURVES_SCULPT_TOOL_COMB: - return std::make_unique<CombOperation>(); + return new_comb_operation(); case CURVES_SCULPT_TOOL_DELETE: - return std::make_unique<DeleteOperation>(); + return new_delete_operation(); case CURVES_SCULPT_TOOL_SNAKE_HOOK: - return std::make_unique<SnakeHookOperation>(); + return new_snake_hook_operation(); case CURVES_SCULPT_TOOL_TEST1: return std::make_unique<AddOperation>(); case CURVES_SCULPT_TOOL_TEST2: diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc new file mode 100644 index 00000000000..bd6b238ea72 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <algorithm> + +#include "curves_sculpt_intern.hh" + +#include "BLI_float4x4.hh" +#include "BLI_index_mask_ops.hh" +#include "BLI_kdtree.h" +#include "BLI_rand.hh" +#include "BLI_vector.hh" + +#include "PIL_time.h" + +#include "DEG_depsgraph.h" + +#include "BKE_attribute_math.hh" +#include "BKE_brush.h" +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" +#include "BKE_mesh.h" +#include "BKE_mesh_runtime.h" +#include "BKE_paint.h" +#include "BKE_spline.hh" + +#include "DNA_brush_enums.h" +#include "DNA_brush_types.h" +#include "DNA_curves_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" + +#include "ED_screen.h" +#include "ED_view3d.h" + +namespace blender::ed::sculpt_paint { + +using blender::bke::CurvesGeometry; + +/** + * Drags the tip point of each curve and resamples the rest of the curve. + */ +class SnakeHookOperation : public CurvesSculptStrokeOperation { + private: + float2 last_mouse_position_; + + public: + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override + { + BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); + + if (stroke_extension.is_first) { + return; + } + + 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); + const float brush_strength = BKE_brush_alpha_get(&scene, &brush); + + const float4x4 ob_mat = object.obmat; + const float4x4 ob_imat = ob_mat.inverted(); + + 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_prev = last_mouse_position_; + const float2 mouse_cur = stroke_extension.mouse_position; + const float2 mouse_diff = mouse_cur - mouse_prev; + + threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { + for (const int curve_i : curves_range) { + const IndexRange curve_points = curves.range_for_curve(curve_i); + const int last_point_i = curve_points.last(); + + const float3 old_position = positions[last_point_i]; + + float2 old_position_screen; + ED_view3d_project_float_v2_m4( + region, old_position, old_position_screen, projection.values); + + const float distance_screen = math::distance(old_position_screen, mouse_prev); + if (distance_screen > brush_radius) { + continue; + } + + const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); + const float weight = brush_strength * radius_falloff; + + const float2 new_position_screen = old_position_screen + mouse_diff * weight; + float3 new_position; + ED_view3d_win_to_3d(v3d, region, ob_mat * old_position, new_position_screen, new_position); + new_position = ob_imat * new_position; + + this->move_last_point_and_resample(positions, curve_points, new_position); + } + }); + + curves.tag_positions_changed(); + DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); + } + + void move_last_point_and_resample(MutableSpan<float3> positions, + const IndexRange curve_points, + const float3 &new_last_point_position) const + { + Vector<float> old_lengths; + old_lengths.append(0.0f); + /* Used to (1) normalize the segment sizes over time and (2) support making zero-length + * segments */ + const float extra_length = 0.001f; + for (const int segment_i : IndexRange(curve_points.size() - 1)) { + const float3 &p1 = positions[curve_points[segment_i]]; + const float3 &p2 = positions[curve_points[segment_i] + 1]; + const float length = math::distance(p1, p2); + old_lengths.append(old_lengths.last() + length + extra_length); + } + Vector<float> point_factors; + for (float &old_length : old_lengths) { + point_factors.append(old_length / old_lengths.last()); + } + + PolySpline new_spline; + new_spline.resize(curve_points.size()); + MutableSpan<float3> new_spline_positions = new_spline.positions(); + for (const int i : IndexRange(curve_points.size() - 1)) { + new_spline_positions[i] = positions[curve_points[i]]; + } + new_spline_positions.last() = new_last_point_position; + new_spline.mark_cache_invalid(); + + for (const int i : IndexRange(curve_points.size())) { + const float factor = point_factors[i]; + const Spline::LookupResult lookup = new_spline.lookup_evaluated_factor(factor); + const float index_factor = lookup.evaluated_index + lookup.factor; + float3 p; + new_spline.sample_with_index_factors<float3>( + new_spline_positions, {&index_factor, 1}, {&p, 1}); + positions[curve_points[i]] = p; + } + } +}; + +std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation() +{ + return std::make_unique<SnakeHookOperation>(); +} + +} // namespace blender::ed::sculpt_paint |