diff options
-rw-r--r-- | release/scripts/startup/bl_ui/space_view3d.py | 4 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_math_geom.h | 20 | ||||
-rw-r--r-- | source/blender/blenlib/intern/math_geom.c | 38 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/curves_sculpt_3d_brush.cc | 232 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/curves_sculpt_comb.cc | 367 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/curves_sculpt_intern.hh | 23 |
7 files changed, 581 insertions, 104 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 70bc14dbc2b..b9415d00871 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -502,6 +502,10 @@ class _draw_tool_settings_context_mode: header=True ) + if brush.curves_sculpt_tool == 'COMB': + layout.prop(brush, "falloff_shape", expand=True) + layout.prop(brush, "curve_preset") + if brush.curves_sculpt_tool == 'ADD': layout.prop(brush, "use_frontface") layout.prop(brush, "falloff_shape", expand=True) diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h index 2f3fbd59b5f..c31e3045c97 100644 --- a/source/blender/blenlib/BLI_math_geom.h +++ b/source/blender/blenlib/BLI_math_geom.h @@ -322,18 +322,22 @@ double closest_to_line_v2_db(double r_close[2], float closest_to_line_v3(float r_close[3], const float p[3], const float l1[3], const float l2[3]); /** * Point closest to v1 on line v2-v3 in 2D. + * + * \return A value in [0, 1] that corresponds to the position of #r_close on the line segment. */ -void closest_to_line_segment_v2(float r_close[2], - const float p[2], - const float l1[2], - const float l2[2]); +float closest_to_line_segment_v2(float r_close[2], + const float p[2], + const float l1[2], + const float l2[2]); /** * Point closest to v1 on line v2-v3 in 3D. + * + * \return A value in [0, 1] that corresponds to the position of #r_close on the line segment. */ -void closest_to_line_segment_v3(float r_close[3], - const float p[3], - const float l1[3], - const float l2[3]); +float closest_to_line_segment_v3(float r_close[3], + const float p[3], + const float l1[3], + const float l2[3]); void closest_to_plane_normalized_v3(float r_close[3], const float plane[4], const float pt[3]); /** * Find the closest point on a plane. diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c index bc3ed099fd5..e1ec22063e0 100644 --- a/source/blender/blenlib/intern/math_geom.c +++ b/source/blender/blenlib/intern/math_geom.c @@ -294,46 +294,48 @@ float dist_to_line_segment_v2(const float p[2], const float l1[2], const float l return sqrtf(dist_squared_to_line_segment_v2(p, l1, l2)); } -void closest_to_line_segment_v2(float r_close[2], - const float p[2], - const float l1[2], - const float l2[2]) +float closest_to_line_segment_v2(float r_close[2], + const float p[2], + const float l1[2], + const float l2[2]) { float lambda, cp[2]; lambda = closest_to_line_v2(cp, p, l1, l2); /* flip checks for !finite case (when segment is a point) */ - if (!(lambda > 0.0f)) { + if (lambda <= 0.0f) { copy_v2_v2(r_close, l1); + return 0.0f; } - else if (!(lambda < 1.0f)) { + if (lambda >= 1.0f) { copy_v2_v2(r_close, l2); + return 1.0f; } - else { - copy_v2_v2(r_close, cp); - } + copy_v2_v2(r_close, cp); + return lambda; } -void closest_to_line_segment_v3(float r_close[3], - const float p[3], - const float l1[3], - const float l2[3]) +float closest_to_line_segment_v3(float r_close[3], + const float p[3], + const float l1[3], + const float l2[3]) { float lambda, cp[3]; lambda = closest_to_line_v3(cp, p, l1, l2); /* flip checks for !finite case (when segment is a point) */ - if (!(lambda > 0.0f)) { + if (lambda <= 0.0f) { copy_v3_v3(r_close, l1); + return 0.0f; } - else if (!(lambda < 1.0f)) { + if (lambda >= 1.0f) { copy_v3_v3(r_close, l2); + return 1.0f; } - else { - copy_v3_v3(r_close, cp); - } + copy_v3_v3(r_close, cp); + return lambda; } void closest_to_plane_v3(float r_close[3], const float plane[4], const float pt[3]) diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index a08ec49d9c4..eee6e680d70 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -27,6 +27,7 @@ set(INC ) set(SRC + curves_sculpt_3d_brush.cc curves_sculpt_add.cc curves_sculpt_comb.cc curves_sculpt_delete.cc diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_3d_brush.cc b/source/blender/editors/sculpt_paint/curves_sculpt_3d_brush.cc new file mode 100644 index 00000000000..94c0f5536b7 --- /dev/null +++ b/source/blender/editors/sculpt_paint/curves_sculpt_3d_brush.cc @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <algorithm> + +#include "curves_sculpt_intern.hh" + +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_curves.hh" + +#include "ED_view3d.h" + +#include "UI_interface.h" + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_task.hh" + +/** + * 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 { + +struct BrushPositionCandidate { + /** 3D position of the brush. */ + float3 position_cu; + /** Squared distance from the mouse position in screen space. */ + float distance_sq_re = FLT_MAX; + /** Measure for how far away the candidate is from the camera. */ + float depth_sq_cu = FLT_MAX; +}; + +/** + * Determine the 3D position of a brush based on curve segments under a screen position. + */ +static std::optional<float3> find_curves_brush_position(const CurvesGeometry &curves, + const float3 &ray_start_cu, + const float3 &ray_end_cu, + const float brush_radius_re, + ARegion ®ion, + RegionView3D &rv3d, + Object &object) +{ + /* This value might have to be adjusted based on user feedback. */ + const float brush_inner_radius_re = std::min<float>(brush_radius_re, (float)UI_UNIT_X / 3.0f); + const float brush_inner_radius_sq_re = pow2f(brush_inner_radius_re); + + float4x4 projection; + ED_view3d_ob_project_mat_get(&rv3d, &object, projection.values); + + float2 brush_pos_re; + ED_view3d_project_float_v2_m4(®ion, ray_start_cu, brush_pos_re, projection.values); + + const float max_depth_sq_cu = math::distance_squared(ray_start_cu, ray_end_cu); + + /* Contains the logic that checks if `b` is a better candidate than `a`. */ + auto is_better_candidate = [&](const BrushPositionCandidate &a, + const BrushPositionCandidate &b) { + if (b.distance_sq_re <= brush_inner_radius_sq_re) { + if (a.distance_sq_re > brush_inner_radius_sq_re) { + /* New candidate is in inner radius while old one is not. */ + return true; + } + else if (b.depth_sq_cu < a.depth_sq_cu) { + /* Both candidates are in inner radius, but new one is closer to the camera. */ + return true; + } + } + else if (b.distance_sq_re < a.distance_sq_re) { + /* Both candidates are outside of inner radius, but new on is closer to the brush center. */ + return true; + } + return false; + }; + + auto update_if_better = [&](BrushPositionCandidate &a, const BrushPositionCandidate &b) { + if (is_better_candidate(a, b)) { + a = b; + } + }; + + const Span<float3> positions = curves.positions(); + + BrushPositionCandidate best_candidate = threading::parallel_reduce( + curves.curves_range(), + 128, + BrushPositionCandidate(), + [&](IndexRange curves_range, const BrushPositionCandidate &init) { + BrushPositionCandidate best_candidate = init; + + for (const int curve_i : curves_range) { + const IndexRange points = curves.range_for_curve(curve_i); + const int tot_segments = points.size() - 1; + + for (const int segment_i : IndexRange(tot_segments)) { + const float3 &p1_cu = positions[points[segment_i]]; + const float3 &p2_cu = positions[points[segment_i] + 1]; + + float2 p1_re, p2_re; + ED_view3d_project_float_v2_m4(®ion, p1_cu, p1_re, projection.values); + ED_view3d_project_float_v2_m4(®ion, p2_cu, p2_re, projection.values); + + float2 closest_re; + const float lambda = closest_to_line_segment_v2( + closest_re, brush_pos_re, p1_re, p2_re); + + const float3 closest_cu = math::interpolate(p1_cu, p2_cu, lambda); + const float depth_sq_cu = math::distance_squared(ray_start_cu, closest_cu); + if (depth_sq_cu > max_depth_sq_cu) { + continue; + } + + const float distance_sq_re = math::distance_squared(brush_pos_re, closest_re); + + BrushPositionCandidate candidate; + candidate.position_cu = closest_cu; + candidate.depth_sq_cu = depth_sq_cu; + candidate.distance_sq_re = distance_sq_re; + + update_if_better(best_candidate, candidate); + } + } + return best_candidate; + }, + [&](const BrushPositionCandidate &a, const BrushPositionCandidate &b) { + return is_better_candidate(a, b) ? b : a; + }); + + if (best_candidate.distance_sq_re == FLT_MAX) { + /* Nothing found. */ + return std::nullopt; + } + + return best_candidate.position_cu; +} + +std::optional<CurvesBrush3D> sample_curves_3d_brush(bContext &C, + Object &curves_object, + const float2 &brush_pos_re, + const float brush_radius_re) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); + ARegion *region = CTX_wm_region(&C); + View3D *v3d = CTX_wm_view3d(&C); + RegionView3D *rv3d = CTX_wm_region_view3d(&C); + + Curves &curves_id = *static_cast<Curves *>(curves_object.data); + CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); + Object *surface_object = curves_id.surface; + + float3 center_ray_start_wo, center_ray_end_wo; + ED_view3d_win_to_segment_clipped( + depsgraph, region, v3d, brush_pos_re, center_ray_start_wo, center_ray_end_wo, true); + + /* Shorten ray when the surface object is hit. */ + if (surface_object != nullptr) { + const float4x4 surface_to_world_mat = surface_object->obmat; + const float4x4 world_to_surface_mat = surface_to_world_mat.inverted(); + + Mesh &surface = *static_cast<Mesh *>(surface_object->data); + BVHTreeFromMesh surface_bvh; + BKE_bvhtree_from_mesh_get(&surface_bvh, &surface, BVHTREE_FROM_LOOPTRI, 2); + BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); }); + + const float3 center_ray_start_su = world_to_surface_mat * center_ray_start_wo; + float3 center_ray_end_su = world_to_surface_mat * center_ray_end_wo; + const float3 center_ray_direction_su = math::normalize(center_ray_end_su - + center_ray_start_su); + + BVHTreeRayHit center_ray_hit; + center_ray_hit.dist = FLT_MAX; + center_ray_hit.index = -1; + BLI_bvhtree_ray_cast(surface_bvh.tree, + center_ray_start_su, + center_ray_direction_su, + 0.0f, + ¢er_ray_hit, + surface_bvh.raycast_callback, + &surface_bvh); + if (center_ray_hit.index >= 0) { + const float3 hit_position_su = center_ray_hit.co; + if (math::distance(center_ray_start_su, center_ray_end_su) > + math::distance(center_ray_start_su, hit_position_su)) { + center_ray_end_su = hit_position_su; + center_ray_end_wo = surface_to_world_mat * center_ray_end_su; + } + } + } + + const float4x4 curves_to_world_mat = curves_object.obmat; + const float4x4 world_to_curves_mat = curves_to_world_mat.inverted(); + + const float3 center_ray_start_cu = world_to_curves_mat * center_ray_start_wo; + const float3 center_ray_end_cu = world_to_curves_mat * center_ray_end_wo; + + const std::optional<float3> brush_position_optional_cu = find_curves_brush_position( + curves, + center_ray_start_cu, + center_ray_end_cu, + brush_radius_re, + *region, + *rv3d, + curves_object); + if (!brush_position_optional_cu.has_value()) { + /* Nothing found. */ + return std::nullopt; + } + const float3 brush_position_cu = *brush_position_optional_cu; + + /* Determine the 3D brush radius. */ + float3 radius_ray_start_wo, radius_ray_end_wo; + ED_view3d_win_to_segment_clipped(depsgraph, + region, + v3d, + brush_pos_re + float2(brush_radius_re, 0.0f), + radius_ray_start_wo, + radius_ray_end_wo, + true); + const float3 radius_ray_start_cu = world_to_curves_mat * radius_ray_start_wo; + const float3 radius_ray_end_cu = world_to_curves_mat * radius_ray_end_wo; + + CurvesBrush3D brush_3d; + brush_3d.position_cu = brush_position_cu; + brush_3d.radius_cu = dist_to_line_v3(brush_position_cu, radius_ray_start_cu, radius_ray_end_cu); + return brush_3d; +} + +} // namespace blender::ed::sculpt_paint diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc index 6c13704a3a6..35b2b2ce956 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_comb.cc @@ -35,129 +35,340 @@ #include "ED_screen.h" #include "ED_view3d.h" +#include "UI_interface.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; +using threading::EnumerableThreadSpecific; /** * Moves individual points under the brush and does a length preservation step afterwards. */ class CombOperation : public CurvesSculptStrokeOperation { private: - float2 last_mouse_position_; + /** Last mouse position. */ + float2 brush_pos_last_re_; + + /** Only used when a 3D brush is used. */ + CurvesBrush3D brush_3d_; + + /** Length of each segment indexed by the index of the first point in the segment. */ + Array<float> segment_lengths_cu_; + + friend struct CombOperationExecutor; 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 CombOperationExecutor { + CombOperation *self_ = nullptr; + bContext *C_ = nullptr; + Depsgraph *depsgraph_ = 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; + + const Object *surface_ob_ = nullptr; + const Mesh *surface_ = nullptr; + Span<MLoopTri> surface_looptris_; + + float2 brush_pos_prev_re_; + float2 brush_pos_re_; + float2 brush_pos_diff_re_; + float brush_pos_diff_length_re_; + + float4x4 curves_to_world_mat_; + float4x4 world_to_curves_mat_; + float4x4 surface_to_world_mat_; + float4x4 world_to_surface_mat_; + + BVHTreeFromMesh surface_bvh_; + + void execute(CombOperation &self, bContext *C, const StrokeExtension &stroke_extension) { - BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; }); + self_ = &self; + + BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = stroke_extension.mouse_position; }); + + C_ = C; + depsgraph_ = CTX_data_depsgraph_pointer(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_); + + curves_to_world_mat_ = object_->obmat; + world_to_curves_mat_ = curves_to_world_mat_.inverted(); + + falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape); + + curves_id_ = static_cast<Curves *>(object_->data); + curves_ = &CurvesGeometry::wrap(curves_id_->geometry); + + brush_pos_prev_re_ = self_->brush_pos_last_re_; + brush_pos_re_ = stroke_extension.mouse_position; + brush_pos_diff_re_ = brush_pos_re_ - brush_pos_prev_re_; + brush_pos_diff_length_re_ = math::length(brush_pos_diff_re_); + + surface_ob_ = curves_id_->surface; + if (surface_ob_ != nullptr) { + surface_ = static_cast<const Mesh *>(surface_ob_->data); + surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_), + BKE_mesh_runtime_looptri_len(surface_)}; + surface_to_world_mat_ = surface_ob_->obmat; + world_to_surface_mat_ = surface_to_world_mat_.inverted(); + BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2); + } + + BLI_SCOPED_DEFER([&]() { + if (surface_ob_ != nullptr) { + free_bvhtree_from_mesh(&surface_bvh_); + } + }); if (stroke_extension.is_first) { + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + this->initialize_spherical_brush_reference_point(); + } + this->initialize_segment_lengths(); + /* Combing does nothing when there is no mouse movement, so return directly. */ 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); + EnumerableThreadSpecific<Vector<int>> changed_curves; - 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); + if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) { + this->comb_projected(changed_curves); + } + else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) { + this->comb_spherical(changed_curves); + } + else { + BLI_assert_unreachable(); + } - const float4x4 ob_mat = object.obmat; - const float4x4 ob_imat = ob_mat.inverted(); + this->restore_segment_lengths(changed_curves); - float4x4 projection; - ED_view3d_ob_project_mat_get(rv3d, &object, projection.values); + curves_->tag_positions_changed(); + DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region_); + } + + /** + * Do combing in screen space. + */ + void comb_projected(EnumerableThreadSpecific<Vector<int>> &r_changed_curves) + { + MutableSpan<float3> positions_cu = curves_->positions(); - Curves &curves_id = *static_cast<Curves *>(object.data); - CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry); - MutableSpan<float3> positions = curves.positions(); + float4x4 projection; + ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values); - 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); + const float brush_radius_sq_re = pow2f(brush_radius_re_); - threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) { + threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) { + Vector<int> &local_changed_curves = r_changed_curves.local(); 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]; + const IndexRange points = curves_->range_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 old_pos_cu = positions_cu[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) { + float2 old_pos_re; + ED_view3d_project_float_v2_m4(region_, old_pos_cu, old_pos_re, projection.values); + + const float distance_to_brush_sq_re = dist_squared_to_line_segment_v2( + old_pos_re, brush_pos_prev_re_, brush_pos_re_); + if (distance_to_brush_sq_re > brush_radius_sq_re) { /* 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); + + const float distance_to_brush_re = std::sqrt(distance_to_brush_sq_re); /* 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; + const float radius_falloff = BKE_brush_curve_strength( + brush_, distance_to_brush_re, brush_radius_re_); + /* Combine the falloff and brush strength. */ + const float weight = brush_strength_ * 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; + const float2 new_position_re = old_pos_re + brush_pos_diff_re_ * weight; + float3 new_position_wo; 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; + 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; + positions_cu[point_i] = new_position_cu; curve_changed = true; } - if (!curve_changed) { - continue; + if (curve_changed) { + local_changed_curves.append(curve_i); } - /* 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; + } + }); + } + + /** + * Do combing in 3D space. + */ + void comb_spherical(EnumerableThreadSpecific<Vector<int>> &r_changed_curves) + { + 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) { + Vector<int> &local_changed_curves = r_changed_curves.local(); + for (const int curve_i : curves_range) { + bool curve_changed = false; + const IndexRange points = curves_->range_for_curve(curve_i); + for (const int point_i : points.drop_front(1)) { + const float3 pos_old_cu = positions_cu[point_i]; + + /* Compute distance to the brush. */ + const float distance_to_brush_sq_cu = dist_squared_to_line_segment_v3( + pos_old_cu, brush_start_cu, brush_end_cu); + if (distance_to_brush_sq_cu > brush_radius_sq_cu) { + /* Ignore the point because it's too far away. */ + continue; + } + + const float distance_to_brush_cu = std::sqrt(distance_to_brush_sq_cu); + + /* A falloff that is based on how far away the point is from the stroke. */ + const float radius_falloff = BKE_brush_curve_strength( + brush_, distance_to_brush_cu, brush_radius_cu); + /* Combine the falloff and brush strength. */ + const float weight = brush_strength_ * radius_falloff; + + /* Update the point position. */ + positions_cu[point_i] = pos_old_cu + weight * brush_diff_cu; + curve_changed = true; + } + if (curve_changed) { + local_changed_curves.append(curve_i); } } }); + } - curves.tag_positions_changed(); - DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); + /** + * Sample depth under mouse by looking at curves and the surface. + */ + void initialize_spherical_brush_reference_point() + { + 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; + } + } + + /** + * Remember the initial length of all curve segments. This allows restoring the length after + * combing. + */ + void initialize_segment_lengths() + { + const Span<float3> positions_cu = curves_->positions(); + self_->segment_lengths_cu_.reinitialize(curves_->points_size()); + threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) { + for (const int curve_i : range) { + const IndexRange points = curves_->range_for_curve(curve_i); + for (const int point_i : points.drop_back(1)) { + const float3 &p1_cu = positions_cu[point_i]; + const float3 &p2_cu = positions_cu[point_i + 1]; + const float length_cu = math::distance(p1_cu, p2_cu); + self_->segment_lengths_cu_[point_i] = length_cu; + } + } + }); + } + + /** + * Restore previously stored length for each segment in the changed curves. + */ + void restore_segment_lengths(EnumerableThreadSpecific<Vector<int>> &changed_curves) + { + const Span<float> expected_lengths_cu = self_->segment_lengths_cu_; + MutableSpan<float3> positions_cu = curves_->positions(); + + threading::parallel_for_each(changed_curves, [&](const Vector<int> &changed_curves) { + threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) { + for (const int curve_i : changed_curves.as_span().slice(range)) { + const IndexRange points = curves_->range_for_curve(curve_i); + for (const int segment_i : IndexRange(points.size() - 1)) { + const float3 &p1_cu = positions_cu[points[segment_i]]; + float3 &p2_cu = positions_cu[points[segment_i] + 1]; + const float3 direction = math::normalize(p2_cu - p1_cu); + const float expected_length_cu = expected_lengths_cu[points[segment_i]]; + p2_cu = p1_cu + direction * expected_length_cu; + } + } + }); + }); } }; +void CombOperation::on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) +{ + CombOperationExecutor executor; + executor.execute(*this, C, stroke_extension); +} + std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation() { return std::make_unique<CombOperation>(); diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh index d1de69f36b6..d021627921f 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh @@ -2,12 +2,22 @@ #pragma once +#include <optional> + #include "curves_sculpt_intern.h" #include "BLI_math_vector.hh" +#include "BKE_curves.hh" + +struct ARegion; +struct RegionView3D; +struct Object; + namespace blender::ed::sculpt_paint { +using bke::CurvesGeometry; + struct StrokeExtension { bool is_first; float2 mouse_position; @@ -27,4 +37,17 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation(); std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation(); std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation(); +struct CurvesBrush3D { + float3 position_cu; + float radius_cu; +}; + +/** + * Find 3d brush position based on cursor position for curves sculpting. + */ +std::optional<CurvesBrush3D> sample_curves_3d_brush(bContext &C, + Object &curves_object, + const float2 &brush_pos_re, + float brush_radius_re); + } // namespace blender::ed::sculpt_paint |