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
This commit is contained in:
parent
e40b0d52cf
commit
190334b47d
Notes:
blender-bot
2023-02-13 22:38:46 +01:00
Referenced by issue #96447, Grow/Shrink - curve sculpting
|
@ -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"),
|
||||
])
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue