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:
authorJacques Lucke <jacques@blender.org>2022-04-05 16:24:12 +0300
committerJacques Lucke <jacques@blender.org>2022-04-05 16:24:12 +0300
commit190334b47df46f0e912c873077163e6129e4cbae (patch)
tree23972d3063cdd88204b4d55f7204349767ddc000
parente40b0d52cfcc44486fe3c2a96c5746dd1c8f4471 (diff)
Curves: new Grow/Shrink brush
This adds a new Grow/Shrink brush which is similar to the Length brush in the old hair system. * It's possible to switch between growing and shrinking by hold down ctrl and/or by changing the direction enum. * 3d brush is supported. * Different brush falloffs are supported. * Supports scaling curves uniformly or shrinking/extrapolating them. Extrapolation is linear only in this patch. * A minimum length settings helps to avoid creating zero-sized curves. Differential Revision: https://developer.blender.org/D14474
-rw-r--r--release/datafiles/icons/ops.curves.sculpt_grow_shrink.dat (renamed from release/datafiles/icons/ops.curves.sculpt_grow.dat)bin1736 -> 1736 bytes
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py5
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py7
-rw-r--r--source/blender/blenkernel/intern/brush.c1
-rw-r--r--source/blender/blenlib/BLI_math_geom.h18
-rw-r--r--source/blender/blenlib/intern/math_geom.c60
-rw-r--r--source/blender/editors/datafiles/CMakeLists.txt2
-rw-r--r--source/blender/editors/sculpt_paint/CMakeLists.txt1
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc526
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_intern.hh3
-rw-r--r--source/blender/editors/sculpt_paint/curves_sculpt_ops.cc124
-rw-r--r--source/blender/makesdna/DNA_brush_enums.h9
-rw-r--r--source/blender/makesdna/DNA_brush_types.h4
-rw-r--r--source/blender/makesrna/intern/rna_brush.c22
14 files changed, 660 insertions, 122 deletions
diff --git a/release/datafiles/icons/ops.curves.sculpt_grow.dat b/release/datafiles/icons/ops.curves.sculpt_grow_shrink.dat
index 9b3453085e4..9b3453085e4 100644
--- a/release/datafiles/icons/ops.curves.sculpt_grow.dat
+++ b/release/datafiles/icons/ops.curves.sculpt_grow_shrink.dat
Binary files differ
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 2c30859a2f3..0422f1afc58 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -5576,7 +5576,10 @@ def km_sculpt_curves(params):
)
items.extend([
- ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
+ ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'},
+ {"properties": [("mode", 'NORMAL')]}),
+ ("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
+ {"properties": [("mode", 'INVERT')]}),
*_template_paint_radial_control("curves_sculpt"),
])
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 3c663f1371a..b786f5adf33 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -514,6 +514,13 @@ 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 == 'GROW_SHRINK':
+ layout.prop(brush, "direction", expand=True, text="")
+ layout.prop(brush, "falloff_shape", expand=True)
+ layout.prop(brush.curves_sculpt_settings, "scale_uniform")
+ layout.prop(brush.curves_sculpt_settings, "minimum_length")
+ layout.prop(brush, "curve_preset")
+
if brush.curves_sculpt_tool == 'SNAKE_HOOK':
layout.prop(brush, "falloff_shape", expand=True)
layout.prop(brush, "curve_preset")
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index ff07d061a20..601c867d8db 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -1558,6 +1558,7 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush)
brush->curves_sculpt_settings = MEM_callocN(sizeof(BrushCurvesSculptSettings), __func__);
}
brush->curves_sculpt_settings->add_amount = 1;
+ brush->curves_sculpt_settings->minimum_length = 0.01f;
}
struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode)
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index 9e65093a05f..5c1b6c8d774 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -329,6 +329,24 @@ float closest_to_line_segment_v2(float r_close[2],
const float p[2],
const float l1[2],
const float l2[2]);
+
+/**
+ * Finds the points where two line segments are closest to each other.
+ *
+ * `lambda_*` is a value between 0 and 1 for each segment that indicates where `r_closest_*` is on
+ * the corresponding segment.
+ *
+ * \return Squared distance between both segments.
+ */
+float closest_seg_seg_v2(float r_closest_a[2],
+ float r_closest_b[2],
+ float *r_lambda_a,
+ float *r_lambda_b,
+ const float a1[2],
+ const float a2[2],
+ const float b1[2],
+ const float b2[2]);
+
/**
* Point closest to v1 on line v2-v3 in 3D.
*
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index 1b13493e00c..e7ccdeab80a 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -294,6 +294,66 @@ 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));
}
+float closest_seg_seg_v2(float r_closest_a[2],
+ float r_closest_b[2],
+ float *r_lambda_a,
+ float *r_lambda_b,
+ const float a1[2],
+ const float a2[2],
+ const float b1[2],
+ const float b2[2])
+{
+ if (isect_seg_seg_v2_simple(a1, a2, b1, b2)) {
+ float intersection[2];
+ isect_line_line_v2_point(a1, a2, b1, b2, intersection);
+ copy_v2_v2(r_closest_a, intersection);
+ copy_v2_v2(r_closest_b, intersection);
+ float tmp[2];
+ *r_lambda_a = closest_to_line_v2(tmp, intersection, a1, a2);
+ *r_lambda_b = closest_to_line_v2(tmp, intersection, b1, b2);
+ const float min_dist_sq = len_squared_v2v2(r_closest_a, r_closest_b);
+ return min_dist_sq;
+ }
+
+ float p1[2], p2[2], p3[2], p4[2];
+ const float lambda1 = closest_to_line_segment_v2(p1, a1, b1, b2);
+ const float lambda2 = closest_to_line_segment_v2(p2, a2, b1, b2);
+ const float lambda3 = closest_to_line_segment_v2(p3, b1, a1, a2);
+ const float lambda4 = closest_to_line_segment_v2(p4, b2, a1, a2);
+ const float dist_sq1 = len_squared_v2v2(p1, a1);
+ const float dist_sq2 = len_squared_v2v2(p2, a2);
+ const float dist_sq3 = len_squared_v2v2(p3, b1);
+ const float dist_sq4 = len_squared_v2v2(p4, b2);
+
+ const float min_dist_sq = min_ffff(dist_sq1, dist_sq2, dist_sq3, dist_sq4);
+ if (min_dist_sq == dist_sq1) {
+ copy_v2_v2(r_closest_a, a1);
+ copy_v2_v2(r_closest_b, p1);
+ *r_lambda_a = 0.0f;
+ *r_lambda_b = lambda1;
+ }
+ else if (min_dist_sq == dist_sq2) {
+ copy_v2_v2(r_closest_a, a2);
+ copy_v2_v2(r_closest_b, p2);
+ *r_lambda_a = 1.0f;
+ *r_lambda_b = lambda2;
+ }
+ else if (min_dist_sq == dist_sq3) {
+ copy_v2_v2(r_closest_a, p3);
+ copy_v2_v2(r_closest_b, b1);
+ *r_lambda_a = lambda3;
+ *r_lambda_b = 0.0f;
+ }
+ else {
+ BLI_assert(min_dist_sq == dist_sq4);
+ copy_v2_v2(r_closest_a, p4);
+ copy_v2_v2(r_closest_b, b2);
+ *r_lambda_a = lambda4;
+ *r_lambda_b = 1.0f;
+ }
+ return min_dist_sq;
+}
+
float closest_to_line_segment_v2(float r_close[2],
const float p[2],
const float l1[2],
diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt
index 0810e76fe37..ebe61d217ed 100644
--- a/source/blender/editors/datafiles/CMakeLists.txt
+++ b/source/blender/editors/datafiles/CMakeLists.txt
@@ -774,7 +774,7 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
ops.curves.sculpt_comb
ops.curves.sculpt_cut
ops.curves.sculpt_delete
- ops.curves.sculpt_grow
+ ops.curves.sculpt_grow_shrink
ops.generic.cursor
ops.generic.select
ops.generic.select_box
diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt
index c422c8c2033..fe7683d12f5 100644
--- a/source/blender/editors/sculpt_paint/CMakeLists.txt
+++ b/source/blender/editors/sculpt_paint/CMakeLists.txt
@@ -31,6 +31,7 @@ set(SRC
curves_sculpt_add.cc
curves_sculpt_comb.cc
curves_sculpt_delete.cc
+ curves_sculpt_grow_shrink.cc
curves_sculpt_ops.cc
curves_sculpt_snake_hook.cc
paint_cursor.c
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
new file mode 100644
index 00000000000..d26af20799e
--- /dev/null
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_grow_shrink.cc
@@ -0,0 +1,526 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <algorithm>
+
+#include "curves_sculpt_intern.hh"
+
+#include "BLI_enumerable_thread_specific.hh"
+#include "BLI_float4x4.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"
+
+#include "curves_sculpt_intern.hh"
+
+/**
+ * The code below uses a suffix 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 bke::CurvesGeometry;
+
+/**
+ * Utility class to wrap different grow/shrink behaviors.
+ * It might be useful to use this for other future brushes as well, but better see if this
+ * abstraction holds up for a while before using it in more places.
+ */
+class CurvesEffect {
+ public:
+ virtual void execute(CurvesGeometry &curves,
+ Span<int> curve_indices,
+ Span<float> move_distances_cu) = 0;
+};
+
+/**
+ * Make curves smaller by trimming the end off.
+ */
+class ShrinkCurvesEffect : public CurvesEffect {
+ private:
+ Brush &brush_;
+
+ public:
+ ShrinkCurvesEffect(Brush &brush) : brush_(brush)
+ {
+ }
+
+ void execute(CurvesGeometry &curves,
+ const Span<int> curve_indices,
+ const Span<float> move_distances_cu) override
+ {
+ MutableSpan<float3> positions_cu = curves.positions();
+ threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
+ for (const int influence_i : range) {
+ const int curve_i = curve_indices[influence_i];
+ const float move_distance_cu = move_distances_cu[influence_i];
+ const IndexRange curve_points = curves.points_for_curve(curve_i);
+ this->shrink_curve(positions_cu, curve_points, move_distance_cu);
+ }
+ });
+ }
+
+ void shrink_curve(MutableSpan<float3> positions,
+ const IndexRange curve_points,
+ const float shrink_length) const
+ {
+ PolySpline spline;
+ spline.resize(curve_points.size());
+ MutableSpan<float3> spline_positions = spline.positions();
+ spline_positions.copy_from(positions.slice(curve_points));
+ spline.mark_cache_invalid();
+ const float min_length = brush_.curves_sculpt_settings->minimum_length;
+ const float old_length = spline.length();
+ const float new_length = std::max(min_length, old_length - shrink_length);
+ const float length_factor = std::clamp(new_length / old_length, 0.0f, 1.0f);
+
+ Vector<float> old_point_lengths;
+ old_point_lengths.append(0.0f);
+ for (const int i : spline_positions.index_range().drop_back(1)) {
+ const float3 &p1 = spline_positions[i];
+ const float3 &p2 = spline_positions[i + 1];
+ const float length = math::distance(p1, p2);
+ old_point_lengths.append(old_point_lengths.last() + length);
+ }
+
+ for (const int i : spline_positions.index_range()) {
+ const float eval_length = old_point_lengths[i] * length_factor;
+ const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length);
+ const float index_factor = lookup.evaluated_index + lookup.factor;
+ float3 p;
+ spline.sample_with_index_factors<float3>(spline_positions, {&index_factor, 1}, {&p, 1});
+ positions[curve_points[i]] = p;
+ }
+ }
+};
+
+/**
+ * Make the curves longer by extrapolating them linearly.
+ */
+class ExtrapolateCurvesEffect : public CurvesEffect {
+ void execute(CurvesGeometry &curves,
+ const Span<int> curve_indices,
+ const Span<float> move_distances_cu) override
+ {
+ MutableSpan<float3> positions_cu = curves.positions();
+ threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
+ for (const int influence_i : range) {
+ const int curve_i = curve_indices[influence_i];
+ const float move_distance_cu = move_distances_cu[influence_i];
+ const IndexRange curve_points = curves.points_for_curve(curve_i);
+
+ if (curve_points.size() <= 1) {
+ continue;
+ }
+
+ const float3 old_last_pos_cu = positions_cu[curve_points.last()];
+ /* Use some point within the curve rather than the end point to smooth out some random
+ * variation. */
+ const float3 direction_reference_point =
+ positions_cu[curve_points[curve_points.size() / 2]];
+ const float3 direction = math::normalize(old_last_pos_cu - direction_reference_point);
+
+ const float3 new_last_pos_cu = old_last_pos_cu + direction * move_distance_cu;
+ this->move_last_point_and_resample(positions_cu, curve_points, new_last_pos_cu);
+ }
+ });
+ }
+
+ 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;
+ }
+ }
+};
+
+/**
+ * Change the length of curves by scaling them uniformly.
+ */
+class ScaleCurvesEffect : public CurvesEffect {
+ private:
+ bool scale_up_;
+ Brush &brush_;
+
+ public:
+ ScaleCurvesEffect(bool scale_up, Brush &brush) : scale_up_(scale_up), brush_(brush)
+ {
+ }
+
+ void execute(CurvesGeometry &curves,
+ const Span<int> curve_indices,
+ const Span<float> move_distances_cu) override
+ {
+ MutableSpan<float3> positions_cu = curves.positions();
+ threading::parallel_for(curve_indices.index_range(), 256, [&](const IndexRange range) {
+ for (const int influence_i : range) {
+ const int curve_i = curve_indices[influence_i];
+ const float move_distance_cu = move_distances_cu[influence_i];
+ const IndexRange points = curves.points_for_curve(curve_i);
+
+ const float old_length = this->compute_poly_curve_length(positions_cu.slice(points));
+ const float length_diff = scale_up_ ? move_distance_cu : -move_distance_cu;
+ const float min_length = brush_.curves_sculpt_settings->minimum_length;
+ const float new_length = std::max(min_length, old_length + length_diff);
+ const float scale_factor = safe_divide(new_length, old_length);
+
+ const float3 &root_pos_cu = positions_cu[points[0]];
+ for (float3 &pos_cu : positions_cu.slice(points.drop_front(1))) {
+ pos_cu = (pos_cu - root_pos_cu) * scale_factor + root_pos_cu;
+ }
+ }
+ });
+ }
+
+ float compute_poly_curve_length(const Span<float3> positions)
+ {
+ float length = 0.0f;
+ const int segments_num = positions.size() - 1;
+ for (const int segment_i : IndexRange(segments_num)) {
+ const float3 &p1 = positions[segment_i];
+ const float3 &p2 = positions[segment_i + 1];
+ length += math::distance(p1, p2);
+ }
+ return length;
+ }
+};
+
+class CurvesEffectOperation : public CurvesSculptStrokeOperation {
+ private:
+ std::unique_ptr<CurvesEffect> effect_;
+ float2 last_mouse_position_;
+ CurvesBrush3D brush_3d_;
+
+ friend struct CurvesEffectOperationExecutor;
+
+ public:
+ CurvesEffectOperation(std::unique_ptr<CurvesEffect> effect) : effect_(std::move(effect))
+ {
+ }
+
+ 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 CurvesEffectOperationExecutor {
+ CurvesEffectOperation *self_ = nullptr;
+ Depsgraph *depsgraph_ = nullptr;
+ Scene *scene_ = nullptr;
+ Object *object_ = nullptr;
+ ARegion *region_ = nullptr;
+ View3D *v3d_ = nullptr;
+ RegionView3D *rv3d_ = nullptr;
+
+ Curves *curves_id_ = nullptr;
+ CurvesGeometry *curves_ = nullptr;
+
+ Brush *brush_ = nullptr;
+ float brush_radius_re_;
+ float brush_radius_sq_re_;
+ float brush_strength_;
+ eBrushFalloffShape falloff_shape_;
+
+ float4x4 curves_to_world_mat_;
+ float4x4 world_to_curves_mat_;
+
+ float2 brush_pos_start_re_;
+ float2 brush_pos_end_re_;
+
+ struct Influences {
+ Vector<int> curve_indices;
+ Vector<float> move_distances_cu;
+ };
+
+ void execute(CurvesEffectOperation &self, bContext *C, const StrokeExtension &stroke_extension)
+ {
+ BLI_SCOPED_DEFER([&]() { self.last_mouse_position_ = stroke_extension.mouse_position; });
+
+ self_ = &self;
+ 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_id_ = static_cast<Curves *>(object_->data);
+ curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
+
+ CurvesSculpt &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_);
+ brush_radius_sq_re_ = pow2f(brush_radius_re_);
+ falloff_shape_ = eBrushFalloffShape(brush_->falloff_shape);
+
+ curves_to_world_mat_ = object_->obmat;
+ world_to_curves_mat_ = curves_to_world_mat_.inverted();
+
+ brush_pos_start_re_ = self.last_mouse_position_;
+ brush_pos_end_re_ = stroke_extension.mouse_position;
+
+ if (stroke_extension.is_first) {
+ if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
+ if (std::optional<CurvesBrush3D> brush_3d = sample_curves_3d_brush(
+ *C, *object_, stroke_extension.mouse_position, brush_radius_re_)) {
+ self.brush_3d_ = *brush_3d;
+ }
+ }
+
+ return;
+ }
+
+ /* Compute influences. */
+ threading::EnumerableThreadSpecific<Influences> influences_for_thread;
+ if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) {
+ this->gather_influences_projected(influences_for_thread);
+ }
+ else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
+ this->gather_influences_spherical(influences_for_thread);
+ }
+
+ /* Execute effect. */
+ threading::parallel_for_each(influences_for_thread, [&](const Influences &influences) {
+ BLI_assert(influences.curve_indices.size() == influences.move_distances_cu.size());
+ self_->effect_->execute(*curves_, influences.curve_indices, influences.move_distances_cu);
+ });
+
+ curves_->tag_positions_changed();
+ DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
+ ED_region_tag_redraw(region_);
+ }
+
+ void gather_influences_projected(
+ threading::EnumerableThreadSpecific<Influences> &influences_for_thread)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+
+ float4x4 projection;
+ ED_view3d_ob_project_mat_get(rv3d_, object_, projection.values);
+
+ threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange curves_range) {
+ Influences &local_influences = influences_for_thread.local();
+
+ for (const int curve_i : curves_range) {
+ const IndexRange points = curves_->points_for_curve(curve_i);
+ const int tot_segments = points.size() - 1;
+ float max_move_distance_cu = 0.0f;
+ for (const int segment_i : IndexRange(tot_segments)) {
+ const float3 &p1_cu = positions_cu[points[segment_i]];
+ const float3 &p2_cu = positions_cu[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_on_brush_re;
+ float2 closest_on_segment_re;
+ float lambda_on_brush;
+ float lambda_on_segment;
+ const float dist_to_brush_sq_re = closest_seg_seg_v2(closest_on_brush_re,
+ closest_on_segment_re,
+ &lambda_on_brush,
+ &lambda_on_segment,
+ brush_pos_start_re_,
+ brush_pos_end_re_,
+ p1_re,
+ p2_re);
+
+ if (dist_to_brush_sq_re > brush_radius_sq_re_) {
+ continue;
+ }
+
+ const float dist_to_brush_re = std::sqrt(dist_to_brush_sq_re);
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, dist_to_brush_re, brush_radius_re_);
+ const float weight = brush_strength_ * radius_falloff;
+
+ const float3 closest_on_segment_cu = math::interpolate(p1_cu, p2_cu, lambda_on_segment);
+
+ float3 brush_start_pos_wo, brush_end_pos_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * closest_on_segment_cu,
+ brush_pos_start_re_,
+ brush_start_pos_wo);
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * closest_on_segment_cu,
+ brush_pos_end_re_,
+ brush_end_pos_wo);
+ const float3 brush_start_pos_cu = world_to_curves_mat_ * brush_start_pos_wo;
+ const float3 brush_end_pos_cu = world_to_curves_mat_ * brush_end_pos_wo;
+
+ const float move_distance_cu = weight *
+ math::distance(brush_start_pos_cu, brush_end_pos_cu);
+ max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
+ }
+ if (max_move_distance_cu > 0.0f) {
+ local_influences.curve_indices.append(curve_i);
+ local_influences.move_distances_cu.append(max_move_distance_cu);
+ }
+ }
+ });
+ }
+
+ void gather_influences_spherical(
+ threading::EnumerableThreadSpecific<Influences> &influences_for_thread)
+ {
+ const Span<float3> positions_cu = curves_->positions();
+
+ float3 brush_pos_start_wo, brush_pos_end_wo;
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_start_re_,
+ brush_pos_start_wo);
+ ED_view3d_win_to_3d(v3d_,
+ region_,
+ curves_to_world_mat_ * self_->brush_3d_.position_cu,
+ brush_pos_end_re_,
+ brush_pos_end_wo);
+ const float3 brush_pos_start_cu = world_to_curves_mat_ * brush_pos_start_wo;
+ const float3 brush_pos_end_cu = world_to_curves_mat_ * brush_pos_end_wo;
+ const float3 brush_pos_diff_cu = brush_pos_end_cu - brush_pos_start_cu;
+ const float brush_pos_diff_length_cu = math::length(brush_pos_diff_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) {
+ Influences &local_influences = influences_for_thread.local();
+
+ for (const int curve_i : curves_range) {
+ const IndexRange points = curves_->points_for_curve(curve_i);
+ const int tot_segments = points.size() - 1;
+ float max_move_distance_cu = 0.0f;
+ for (const int segment_i : IndexRange(tot_segments)) {
+ const float3 &p1_cu = positions_cu[points[segment_i]];
+ const float3 &p2_cu = positions_cu[points[segment_i] + 1];
+
+ float3 closest_on_segment_cu;
+ float3 closest_on_brush_cu;
+ isect_seg_seg_v3(p1_cu,
+ p2_cu,
+ brush_pos_start_cu,
+ brush_pos_end_cu,
+ closest_on_segment_cu,
+ closest_on_brush_cu);
+
+ const float dist_to_brush_sq_cu = math::distance_squared(closest_on_segment_cu,
+ closest_on_brush_cu);
+ if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
+ continue;
+ }
+
+ const float dist_to_brush_cu = std::sqrt(dist_to_brush_sq_cu);
+ const float radius_falloff = BKE_brush_curve_strength(
+ brush_, dist_to_brush_cu, brush_radius_cu);
+ const float weight = brush_strength_ * radius_falloff;
+
+ const float move_distance_cu = weight * brush_pos_diff_length_cu;
+ max_move_distance_cu = std::max(max_move_distance_cu, move_distance_cu);
+ }
+ if (max_move_distance_cu > 0.0f) {
+ local_influences.curve_indices.append(curve_i);
+ local_influences.move_distances_cu.append(max_move_distance_cu);
+ }
+ }
+ });
+ }
+};
+
+void CurvesEffectOperation::on_stroke_extended(bContext *C,
+ const StrokeExtension &stroke_extension)
+{
+ CurvesEffectOperationExecutor executor;
+ executor.execute(*this, C, stroke_extension);
+}
+
+std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
+ const BrushStrokeMode brush_mode, bContext *C)
+{
+ Scene &scene = *CTX_data_scene(C);
+ Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint);
+ const bool use_scale_uniform = brush.curves_sculpt_settings->flag &
+ BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM;
+ const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0);
+
+ if (use_grow) {
+ if (use_scale_uniform) {
+ return std::make_unique<CurvesEffectOperation>(
+ std::make_unique<ScaleCurvesEffect>(true, brush));
+ }
+ return std::make_unique<CurvesEffectOperation>(std::make_unique<ExtrapolateCurvesEffect>());
+ }
+ if (use_scale_uniform) {
+ return std::make_unique<CurvesEffectOperation>(
+ std::make_unique<ScaleCurvesEffect>(false, brush));
+ }
+ return std::make_unique<CurvesEffectOperation>(std::make_unique<ShrinkCurvesEffect>(brush));
+}
+
+} // 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
index d021627921f..03413221907 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_intern.hh
@@ -5,6 +5,7 @@
#include <optional>
#include "curves_sculpt_intern.h"
+#include "paint_intern.h"
#include "BLI_math_vector.hh"
@@ -36,6 +37,8 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_add_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
+std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
+ const BrushStrokeMode brush_mode, bContext *C);
struct CurvesBrush3D {
float3 position_cu;
diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
index de53c118b20..893b2640427 100644
--- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
+++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc
@@ -74,118 +74,6 @@ using blender::bke::CurvesGeometry;
/** \name * SCULPT_CURVES_OT_brush_stroke
* \{ */
-/**
- * Resamples the curves to a shorter length.
- */
-class ShrinkOperation : 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.points_for_curve(curve_i);
- const int last_point_i = curve_points.last();
-
- const float3 old_tip_position = positions[last_point_i];
-
- float2 old_tip_position_screen;
- ED_view3d_project_float_v2_m4(
- region, old_tip_position, old_tip_position_screen, projection.values);
-
- const float distance_screen = math::distance(old_tip_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 offset_tip_position_screen = old_tip_position_screen + weight * mouse_diff;
- float3 offset_tip_position;
- ED_view3d_win_to_3d(v3d,
- region,
- ob_mat * old_tip_position,
- offset_tip_position_screen,
- offset_tip_position);
- offset_tip_position = ob_imat * offset_tip_position;
- const float shrink_length = math::distance(offset_tip_position, old_tip_position);
-
- this->shrink_curve(positions, curve_points, shrink_length);
- }
- });
-
- curves.tag_positions_changed();
- DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
- ED_region_tag_redraw(region);
- }
-
- void shrink_curve(MutableSpan<float3> positions,
- const IndexRange curve_points,
- const float shrink_length) const
- {
- PolySpline spline;
- spline.resize(curve_points.size());
- MutableSpan<float3> spline_positions = spline.positions();
- spline_positions.copy_from(positions.slice(curve_points));
- spline.mark_cache_invalid();
- const float old_length = spline.length();
- const float new_length = std::max(0.0f, old_length - shrink_length);
- const float length_factor = new_length / old_length;
-
- Vector<float> old_point_lengths;
- old_point_lengths.append(0.0f);
- for (const int i : spline_positions.index_range().drop_back(1)) {
- const float3 &p1 = spline_positions[i];
- const float3 &p2 = spline_positions[i + 1];
- const float length = math::distance(p1, p2);
- old_point_lengths.append(old_point_lengths.last() + length);
- }
-
- for (const int i : spline_positions.index_range()) {
- const float eval_length = old_point_lengths[i] * length_factor;
- const Spline::LookupResult lookup = spline.lookup_evaluated_length(eval_length);
- const float index_factor = lookup.evaluated_index + lookup.factor;
- float3 p;
- spline.sample_with_index_factors<float3>(spline_positions, {&index_factor, 1}, {&p, 1});
- positions[curve_points[i]] = p;
- }
- }
-};
-
class DensityAddOperation : public CurvesSculptStrokeOperation {
private:
/** Contains the root points of the curves that existed before this operation started. */
@@ -612,8 +500,10 @@ class DensityAddOperation : public CurvesSculptStrokeOperation {
};
static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bContext *C,
- wmOperator *UNUSED(op))
+ wmOperator *op)
{
+ const BrushStrokeMode mode = static_cast<BrushStrokeMode>(RNA_enum_get(op->ptr, "mode"));
+
Scene &scene = *CTX_data_scene(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
@@ -626,10 +516,10 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte
return new_snake_hook_operation();
case CURVES_SCULPT_TOOL_ADD:
return new_add_operation();
+ case CURVES_SCULPT_TOOL_GROW_SHRINK:
+ return new_grow_shrink_operation(mode, C);
case CURVES_SCULPT_TOOL_TEST1:
return std::make_unique<DensityAddOperation>();
- case CURVES_SCULPT_TOOL_TEST2:
- return std::make_unique<ShrinkOperation>();
}
BLI_assert_unreachable();
return {};
@@ -674,7 +564,9 @@ static void stroke_update_step(bContext *C,
stroke_extension.is_first = false;
}
- op_data->operation->on_stroke_extended(C, stroke_extension);
+ if (op_data->operation) {
+ op_data->operation->on_stroke_extended(C, stroke_extension);
+ }
}
static void stroke_done(const bContext *C, PaintStroke *stroke)
diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h
index 18d82448d1f..96a97648ac9 100644
--- a/source/blender/makesdna/DNA_brush_enums.h
+++ b/source/blender/makesdna/DNA_brush_enums.h
@@ -461,8 +461,8 @@ typedef enum eBrushCurvesSculptTool {
CURVES_SCULPT_TOOL_DELETE = 1,
CURVES_SCULPT_TOOL_SNAKE_HOOK = 2,
CURVES_SCULPT_TOOL_ADD = 3,
- CURVES_SCULPT_TOOL_TEST1 = 4,
- CURVES_SCULPT_TOOL_TEST2 = 5,
+ CURVES_SCULPT_TOOL_GROW_SHRINK = 4,
+ CURVES_SCULPT_TOOL_TEST1 = 5,
} eBrushCurvesSculptTool;
/** When #BRUSH_ACCUMULATE is used */
@@ -608,6 +608,11 @@ typedef enum eBrushFalloffShape {
PAINT_FALLOFF_SHAPE_TUBE = 1,
} eBrushFalloffShape;
+typedef enum eBrushCurvesSculptFlag {
+ BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM = (1 << 0),
+ BRUSH_CURVES_SCULPT_FLAG_GROW_SHRINK_INVERT = (1 << 1),
+} eBrushCurvesSculptFlag;
+
#define MAX_BRUSH_PIXEL_RADIUS 500
#define GP_MAX_BRUSH_PIXEL_RADIUS 1000
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 1382efca409..2d879f5afa0 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -140,6 +140,10 @@ typedef struct BrushGpencilSettings {
typedef struct BrushCurvesSculptSettings {
/** Number of curves added by the add brush. */
int add_amount;
+ /* eBrushCurvesSculptFlag. */
+ uint32_t flag;
+ /** When shrinking curves, they shouldn't become shorter than this length. */
+ float minimum_length;
} BrushCurvesSculptSettings;
typedef struct Brush {
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index b2b6bdbcffc..062c827b9d0 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -248,8 +248,8 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = {
{CURVES_SCULPT_TOOL_DELETE, "DELETE", ICON_NONE, "Delete", ""},
{CURVES_SCULPT_TOOL_SNAKE_HOOK, "SNAKE_HOOK", ICON_NONE, "Snake Hook", ""},
{CURVES_SCULPT_TOOL_ADD, "ADD", ICON_NONE, "Add", ""},
+ {CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_NONE, "Grow / Shrink", ""},
{CURVES_SCULPT_TOOL_TEST1, "TEST1", ICON_NONE, "Test 1", ""},
- {CURVES_SCULPT_TOOL_TEST2, "TEST2", ICON_NONE, "Test 2", ""},
{0, NULL, 0, NULL, NULL},
};
@@ -883,7 +883,13 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C,
default:
return DummyRNA_DEFAULT_items;
}
-
+ case PAINT_MODE_SCULPT_CURVES:
+ switch (me->curves_sculpt_tool) {
+ case CURVES_SCULPT_TOOL_GROW_SHRINK:
+ return prop_direction_items;
+ default:
+ return DummyRNA_DEFAULT_items;
+ }
default:
return DummyRNA_DEFAULT_items;
}
@@ -1927,6 +1933,18 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
prop = RNA_def_property(srna, "add_amount", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 1, INT32_MAX);
RNA_def_property_ui_text(prop, "Add Amount", "Number of curves added by the Add brush");
+
+ prop = RNA_def_property(srna, "scale_uniform", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM);
+ RNA_def_property_ui_text(prop,
+ "Scale Uniform",
+ "Grow or shrink curves by changing their size uniformly instead of "
+ "using trimming or extrapolation");
+
+ prop = RNA_def_property(srna, "minimum_length", PROP_FLOAT, PROP_DISTANCE);
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_text(
+ prop, "Minimum Length", "Avoid shrinking curves shorter than this length");
}
static void rna_def_brush(BlenderRNA *brna)