Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py4
-rw-r--r--source/blender/blenlib/BLI_math_geom.h20
-rw-r--r--source/blender/blenlib/intern/math_geom.c38
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_3d_brush.cc232
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_comb.cc367
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_intern.hh23
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 &region,
+ 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(&region, 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(&region, p1_cu, p1_re, projection.values);
+ ED_view3d_project_float_v2_m4(&region, 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,
+ &center_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