diff options
author | Jacques Lucke <jacques@blender.org> | 2022-03-29 15:46:14 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-03-29 15:46:14 +0300 |
commit | fced604acfc90ae2bdca068ce1898cdc39838b5f (patch) | |
tree | b76445f76381c16a245e7a80df5864a73fbd7d2d | |
parent | 4def6e80728a120977f869b475251b133d88e4ab (diff) |
Curves: improve Snake Hook brush
This implements the spherical brush and different falloff
modes for the Snake Hook brush.
Differential Revision: https://developer.blender.org/D14408
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d.py | 4 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc | 249 |
2 files changed, 190 insertions, 63 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index f40d96e30a0..1c42bc85fae 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -514,6 +514,10 @@ class _draw_tool_settings_context_mode: layout.prop(tool_settings.curves_sculpt, "interpolate_length") layout.prop(tool_settings.curves_sculpt, "interpolate_shape") + if brush.curves_sculpt_tool == 'SNAKE_HOOK': + layout.prop(brush, "falloff_shape", expand=True) + layout.prop(brush, "curve_preset") + if brush.curves_sculpt_tool == 'TEST1': layout.prop(tool_settings.curves_sculpt, "distance") diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc index 682cd3b47ca..117b2265015 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_snake_hook.cc @@ -36,6 +36,14 @@ #include "ED_screen.h" #include "ED_view3d.h" +/** + * The code below uses a prefix naming convention to indicate the coordinate space: + * cu: Local space of the curves object that is being edited. + * su: Local space of the surface object. + * wo: World space. + * re: 2D coordinates within the region. + */ + namespace blender::ed::sculpt_paint { using blender::bke::CurvesGeometry; @@ -45,116 +53,231 @@ using blender::bke::CurvesGeometry; */ class SnakeHookOperation : public CurvesSculptStrokeOperation { private: - float2 last_mouse_position_; + float2 last_mouse_position_re_; + + CurvesBrush3D brush_3d_; + + friend struct SnakeHookOperatorExecutor; public: - void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override + void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override; +}; + +/** + * Utility class that actually executes the update when the stroke is updated. That's useful + * because it avoids passing a very large number of parameters between functions. + */ +struct SnakeHookOperatorExecutor { + SnakeHookOperation *self_ = nullptr; + bContext *C_ = nullptr; + Scene *scene_ = nullptr; + Object *object_ = nullptr; + ARegion *region_ = nullptr; + View3D *v3d_ = nullptr; + RegionView3D *rv3d_ = nullptr; + + CurvesSculpt *curves_sculpt_ = nullptr; + Brush *brush_ = nullptr; + float brush_radius_re_; + float brush_strength_; + eBrushFalloffShape falloff_shape_; + + Curves *curves_id_ = nullptr; + CurvesGeometry *curves_ = nullptr; + + float4x4 curves_to_world_mat_; + float4x4 world_to_curves_mat_; + + float2 brush_pos_prev_re_; + float2 brush_pos_re_; + float2 brush_pos_diff_re_; + + void execute(SnakeHookOperation &self, bContext *C, const StrokeExtension &stroke_extension) { - BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); + BLI_SCOPED_DEFER([&]() { self.last_mouse_position_re_ = stroke_extension.mouse_position; }); + + self_ = &self; + C_ = C; + scene_ = CTX_data_scene(C); + object_ = CTX_data_active_object(C); + region_ = CTX_wm_region(C); + v3d_ = CTX_wm_view3d(C); + rv3d_ = CTX_wm_region_view3d(C); + + curves_sculpt_ = scene_->toolsettings->curves_sculpt; + brush_ = BKE_paint_brush(&curves_sculpt_->paint); + brush_radius_re_ = BKE_brush_size_get(scene_, brush_); + brush_strength_ = BKE_brush_alpha_get(scene_, brush_); + falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape); + + curves_to_world_mat_ = object_->obmat; + world_to_curves_mat_ = curves_to_world_mat_.inverted(); + + curves_id_ = static_cast<Curves *>(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + + brush_pos_prev_re_ = self.last_mouse_position_re_; + brush_pos_re_ = stroke_extension.mouse_position; + brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_; if (stroke_extension.is_first) { + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush( + *C_, *object_, brush_pos_re_, brush_radius_re_); + if (brush_3d.has_value()) { + self_->brush_3d_ = *brush_3d; + } + } 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); + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + this->spherical_snake_hook(); + } + else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { + this->projected_snake_hook(); + } + else { + BLI_assert_unreachable(); + } - 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); + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region_); + } - const float4x4 ob_mat = object.obmat; - const float4x4 ob_imat = ob_mat.inverted(); + void projected_snake_hook() + { + MutableSpan<float3> positions_cu = curves_->positions(); float4x4 projection; - ED_view3d_ob_project_mat_get(rv3d, &object, projection.values); + 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(); + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + for (const int curve_i : curves_range) { + const IndexRange points = curves_->points_for_curve(curve_i); + const int last_point_i = points.last(); + const float3 old_pos_cu = positions_cu[last_point_i]; - const float2 mouse_prev = last_mouse_position_; - const float2 mouse_cur = stroke_extension.mouse_position; - const float2 mouse_diff = mouse_cur - mouse_prev; + float2 old_pos_re; + ED_view3d_project_float_v2_m4(region_, old_pos_cu, old_pos_re, projection.values); - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { - for (const int curve_i : curves_range) { - const IndexRange curve_points = curves.points_for_curve(curve_i); - const int last_point_i = curve_points.last(); + const float distance_to_brush_re = math::distance(old_pos_re, brush_pos_prev_re_); + if (distance_to_brush_re > brush_radius_re_) { + continue; + } - const float3 old_position = positions[last_point_i]; + const float radius_falloff = BKE_brush_curve_strength( + brush_, distance_to_brush_re, brush_radius_re_); + const float weight = brush_strength_ * radius_falloff; - float2 old_position_screen; - ED_view3d_project_float_v2_m4( - region, old_position, old_position_screen, projection.values); + const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; + float3 new_position_wo; + ED_view3d_win_to_3d( + v3d_, region_, curves_to_world_mat_ * old_pos_cu, new_position_re, new_position_wo); + const float3 new_position_cu = world_to_curves_mat_ * new_position_wo; - const float distance_screen = math::distance(old_position_screen, mouse_prev); - if (distance_screen > brush_radius) { + this->move_last_point_and_resample(positions_cu.slice(points), new_position_cu); + } + }); + } + + void spherical_snake_hook() + { + MutableSpan<float3> positions_cu = curves_->positions(); + + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values); + + float3 brush_start_wo, brush_end_wo; + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * self_->brush_3d_.position_cu, + brush_pos_prev_re_, + brush_start_wo); + ED_view3d_win_to_3d(v3d_, + region_, + curves_to_world_mat_ * self_->brush_3d_.position_cu, + brush_pos_re_, + brush_end_wo); + const float3 brush_start_cu = world_to_curves_mat_ * brush_start_wo; + const float3 brush_end_cu = world_to_curves_mat_ * brush_end_wo; + const float3 brush_diff_cu = brush_end_cu - brush_start_cu; + + const float brush_radius_cu = self_->brush_3d_.radius_cu; + const float brush_radius_sq_cu = pow2f(brush_radius_cu); + + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + for (const int curve_i : curves_range) { + const IndexRange points = curves_->points_for_curve(curve_i); + const int last_point_i = points.last(); + const float3 old_pos_cu = positions_cu[last_point_i]; + + const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( + old_pos_cu, brush_start_cu, brush_end_cu); + if (distance_to_brush_sq_cu > brush_radius_sq_cu) { continue; } - const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius); - const float weight = brush_strength * radius_falloff; + const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu); + + const float radius_falloff = BKE_brush_curve_strength( + brush_, distance_to_brush_cu, brush_radius_cu); + 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; + const float3 new_pos_cu = old_pos_cu + weight * brush_diff_cu; - this->move_last_point_and_resample(positions, curve_points, new_position); + this->move_last_point_and_resample(positions_cu.slice(points), new_pos_cu); } }); - - 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 + void move_last_point_and_resample(MutableSpan<float3> positions_cu, + const float3 &new_last_point_position_cu) const { - Vector<float> old_lengths; - old_lengths.append(0.0f); + Vector<float> old_lengths_cu; + old_lengths_cu.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); + for (const int segment_i : IndexRange(positions_cu.size() - 1)) { + const float3 &p1_cu = positions_cu[segment_i]; + const float3 &p2_cu = positions_cu[segment_i + 1]; + const float length_cu = math::distance(p1_cu, p2_cu); + old_lengths_cu.append(old_lengths_cu.last() + length_cu + extra_length); } Vector<float> point_factors; - for (float &old_length : old_lengths) { - point_factors.append(old_length / old_lengths.last()); + for (float &old_length_cu : old_lengths_cu) { + point_factors.append(old_length_cu / old_lengths_cu.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.resize(positions_cu.size()); + MutableSpan<float3> new_spline_positions_cu = new_spline.positions(); + for (const int i : IndexRange(positions_cu.size() - 1)) { + new_spline_positions_cu[i] = positions_cu[i]; } - new_spline_positions.last() = new_last_point_position; + new_spline_positions_cu.last() = new_last_point_position_cu; new_spline.mark_cache_invalid(); - for (const int i : IndexRange(curve_points.size())) { + for (const int i : positions_cu.index_range()) { 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; + float3 p_cu; new_spline.sample_with_index_factors<float3>( - new_spline_positions, {&index_factor, 1}, {&p, 1}); - positions[curve_points[i]] = p; + new_spline_positions_cu, {&index_factor, 1}, {&p_cu, 1}); + positions_cu[i] = p_cu; } } }; +void SnakeHookOperation::on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) +{ + SnakeHookOperatorExecutor executor; + executor.execute(*this, C, stroke_extension); +} + std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation() { return std::make_unique<SnakeHookOperation>(); |