Curves: New tools for curves sculpt mode.
This commit contains various new features for curves sculpt mode that have been developed in parallel. * Selection: * Operator to select points/curves randomly. * Operator to select endpoints of curves. * Operator to grow/shrink an existing selection. * New Brushes: * Pinch: Moves points towards the brush center. * Smooth: Makes individual curves straight without changing the root or tip position. * Puff: Makes curves stand up, aligning them with the surface normal. * Density: Add or remove curves to achieve a certain density defined by a minimum distance value. * Slide: Move root points on the surface. Differential Revision: https://developer.blender.org/D15134
This commit is contained in:
parent
0f22b5599a
commit
416aef4e13
Notes:
blender-bot
2023-11-20 12:14:32 +01:00
Referenced by issue #96259, Curves Sculpt Brushes
|
@ -5624,6 +5624,7 @@ def km_sculpt_curves(params):
|
|||
("curves.disable_selection", {"type": 'TWO', "value": 'PRESS', "alt": True}, None),
|
||||
*_template_paint_radial_control("curves_sculpt"),
|
||||
*_template_items_select_actions(params, "sculpt_curves.select_all"),
|
||||
("sculpt_curves.min_distance_edit", {"type": 'R', "value": 'PRESS', "shift": True}, {}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
|
|
@ -2377,6 +2377,51 @@ class _defs_curves_sculpt:
|
|||
data_block='GROW_SHRINK'
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def pinch():
|
||||
return dict(
|
||||
idname="builtin_brush.pinch",
|
||||
label="Pinch",
|
||||
icon="ops.curves.sculpt_pinch",
|
||||
data_block='PINCH'
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def smooth():
|
||||
return dict(
|
||||
idname="builtin_brush.smooth",
|
||||
label="Smooth",
|
||||
icon="ops.curves.sculpt_smooth",
|
||||
data_block='SMOOTH'
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def puff():
|
||||
return dict(
|
||||
idname="builtin_brush.puff",
|
||||
label="Puff",
|
||||
icon="ops.curves.sculpt_puff",
|
||||
data_block='PUFF'
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def density():
|
||||
return dict(
|
||||
idname="builtin_brush.density",
|
||||
label="Density",
|
||||
icon="ops.curves.sculpt_density",
|
||||
data_block="DENSITY"
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def slide():
|
||||
return dict(
|
||||
idname="builtin_brush.slide",
|
||||
label="Slide",
|
||||
icon="ops.curves.sculpt_slide",
|
||||
data_block="SLIDE"
|
||||
)
|
||||
|
||||
|
||||
class _defs_gpencil_vertex:
|
||||
|
||||
|
@ -3140,6 +3185,11 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
|
|||
_defs_curves_sculpt.delete,
|
||||
_defs_curves_sculpt.snake_hook,
|
||||
_defs_curves_sculpt.grow_shrink,
|
||||
_defs_curves_sculpt.pinch,
|
||||
_defs_curves_sculpt.smooth,
|
||||
_defs_curves_sculpt.puff,
|
||||
_defs_curves_sculpt.density,
|
||||
_defs_curves_sculpt.slide,
|
||||
None,
|
||||
*_tools_annotate,
|
||||
],
|
||||
|
|
|
@ -535,6 +535,31 @@ class _draw_tool_settings_context_mode:
|
|||
layout.prop(brush, "direction", expand=True, text="")
|
||||
layout.prop(brush, "falloff_shape", expand=True)
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
elif curves_tool == 'PINCH':
|
||||
layout.prop(brush, "direction", expand=True, text="")
|
||||
layout.prop(brush, "falloff_shape", expand=True)
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
elif curves_tool == 'SMOOTH':
|
||||
layout.prop(brush, "falloff_shape", expand=True)
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
elif curves_tool == 'PUFF':
|
||||
layout.prop(brush, "falloff_shape", expand=True)
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
elif curves_tool == 'DENSITY':
|
||||
layout.prop(brush, "falloff_shape", expand=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(brush.curves_sculpt_settings, "density_mode", text="", expand=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(brush.curves_sculpt_settings, "minimum_distance")
|
||||
row.operator_context = 'INVOKE_REGION_WIN'
|
||||
row.operator("sculpt_curves.min_distance_edit", text="", icon='DRIVER_DISTANCE')
|
||||
row = layout.row(align=True)
|
||||
row.enabled = brush.curves_sculpt_settings.density_mode != 'REMOVE'
|
||||
row.prop(brush.curves_sculpt_settings, "density_add_attempts", text="Max Count")
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
layout.popover("VIEW3D_PT_curves_sculpt_add_shape", text="Curve Shape")
|
||||
elif curves_tool == "SLIDE":
|
||||
layout.popover("VIEW3D_PT_tools_brush_falloff")
|
||||
|
||||
|
||||
class VIEW3D_HT_header(Header):
|
||||
|
@ -2005,6 +2030,9 @@ class VIEW3D_MT_select_sculpt_curves(Menu):
|
|||
layout.operator("sculpt_curves.select_all", text="All").action = 'SELECT'
|
||||
layout.operator("sculpt_curves.select_all", text="None").action = 'DESELECT'
|
||||
layout.operator("sculpt_curves.select_all", text="Invert").action = 'INVERT'
|
||||
layout.operator("sculpt_curves.select_random", text="Random")
|
||||
layout.operator("sculpt_curves.select_end", text="Endpoints")
|
||||
layout.operator("sculpt_curves.select_grow", text="Grow")
|
||||
|
||||
|
||||
class VIEW3D_MT_angle_control(Menu):
|
||||
|
|
|
@ -1562,6 +1562,7 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush)
|
|||
settings->points_per_curve = 8;
|
||||
settings->minimum_length = 0.01f;
|
||||
settings->curve_length = 0.3f;
|
||||
settings->density_add_attempts = 100;
|
||||
}
|
||||
|
||||
struct Brush *BKE_brush_first_search(struct Main *bmain, const eObjectMode ob_mode)
|
||||
|
|
|
@ -3243,5 +3243,13 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain)
|
|||
*/
|
||||
{
|
||||
/* Keep this block, even when empty. */
|
||||
|
||||
/* Initialize brush curves sculpt settings. */
|
||||
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
|
||||
if (brush->ob_mode != OB_MODE_SCULPT_CURVES) {
|
||||
continue;
|
||||
}
|
||||
brush->curves_sculpt_settings->density_add_attempts = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ set(INC
|
|||
../../depsgraph
|
||||
../../functions
|
||||
../../geometry
|
||||
../../gpu
|
||||
../../makesdna
|
||||
../../makesrna
|
||||
../../windowmanager
|
||||
|
@ -27,5 +28,17 @@ set(LIB
|
|||
bf_blenlib
|
||||
)
|
||||
|
||||
if(WITH_TBB)
|
||||
list(APPEND INC_SYS
|
||||
${TBB_INCLUDE_DIRS}
|
||||
)
|
||||
add_definitions(-DWITH_TBB)
|
||||
if(WIN32)
|
||||
# TBB includes Windows.h which will define min/max macros
|
||||
# that will collide with the stl versions.
|
||||
add_definitions(-DNOMINMAX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
blender_add_lib(bf_editor_curves "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
||||
add_dependencies(bf_editor_curves bf_rna)
|
||||
|
|
|
@ -72,7 +72,7 @@ static bool object_has_editable_curves(const Main &bmain, const Object &object)
|
|||
return true;
|
||||
}
|
||||
|
||||
static VectorSet<Curves *> get_unique_editable_curves(const bContext &C)
|
||||
VectorSet<Curves *> get_unique_editable_curves(const bContext &C)
|
||||
{
|
||||
VectorSet<Curves *> unique_curves;
|
||||
|
||||
|
@ -715,7 +715,7 @@ static void CURVES_OT_snap_curves_to_surface(wmOperatorType *ot)
|
|||
"How to find the point on the surface to attach to");
|
||||
}
|
||||
|
||||
static bool selection_poll(bContext *C)
|
||||
bool selection_operator_poll(bContext *C)
|
||||
{
|
||||
const Object *object = CTX_data_active_object(C);
|
||||
if (object == nullptr) {
|
||||
|
@ -784,7 +784,7 @@ static void CURVES_OT_set_selection_domain(wmOperatorType *ot)
|
|||
ot->description = "Change the mode used for selection masking in curves sculpt mode";
|
||||
|
||||
ot->exec = set_selection_domain::curves_set_selection_domain_exec;
|
||||
ot->poll = selection_poll;
|
||||
ot->poll = selection_operator_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
|
@ -820,13 +820,11 @@ static void CURVES_OT_disable_selection(wmOperatorType *ot)
|
|||
ot->description = "Disable the drawing of influence of selection in sculpt mode";
|
||||
|
||||
ot->exec = disable_selection::curves_disable_selection_exec;
|
||||
ot->poll = selection_poll;
|
||||
ot->poll = selection_operator_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
namespace select_all {
|
||||
|
||||
static bool varray_contains_nonzero(const VArray<float> &data)
|
||||
{
|
||||
bool contains_nonzero = false;
|
||||
|
@ -841,6 +839,19 @@ static bool varray_contains_nonzero(const VArray<float> &data)
|
|||
return contains_nonzero;
|
||||
}
|
||||
|
||||
bool has_anything_selected(const Curves &curves_id)
|
||||
{
|
||||
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
|
||||
switch (curves_id.selection_domain) {
|
||||
case ATTR_DOMAIN_POINT:
|
||||
return varray_contains_nonzero(curves.selection_point_float());
|
||||
case ATTR_DOMAIN_CURVE:
|
||||
return varray_contains_nonzero(curves.selection_curve_float());
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool any_point_selected(const CurvesGeometry &curves)
|
||||
{
|
||||
return varray_contains_nonzero(curves.selection_point_float());
|
||||
|
@ -856,6 +867,8 @@ static bool any_point_selected(const Span<Curves *> curves_ids)
|
|||
return false;
|
||||
}
|
||||
|
||||
namespace select_all {
|
||||
|
||||
static void invert_selection(MutableSpan<float> selection)
|
||||
{
|
||||
threading::parallel_for(selection.index_range(), 2048, [&](IndexRange range) {
|
||||
|
@ -924,7 +937,7 @@ static void SCULPT_CURVES_OT_select_all(wmOperatorType *ot)
|
|||
ot->description = "(De)select all control points";
|
||||
|
||||
ot->exec = select_all::select_all_exec;
|
||||
ot->poll = selection_poll;
|
||||
ot->poll = selection_operator_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
struct bContext;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -19,10 +21,14 @@ void ED_operatortypes_curves(void);
|
|||
#ifdef __cplusplus
|
||||
|
||||
# include "BKE_curves.hh"
|
||||
# include "BLI_vector_set.hh"
|
||||
|
||||
namespace blender::ed::curves {
|
||||
|
||||
bke::CurvesGeometry primitive_random_sphere(int curves_size, int points_per_curve);
|
||||
bool selection_operator_poll(bContext *C);
|
||||
bool has_anything_selected(const Curves &curves_id);
|
||||
VectorSet<Curves *> get_unique_editable_curves(const bContext &C);
|
||||
|
||||
}
|
||||
} // namespace blender::ed::curves
|
||||
#endif
|
||||
|
|
|
@ -32,10 +32,15 @@ set(SRC
|
|||
curves_sculpt_brush.cc
|
||||
curves_sculpt_comb.cc
|
||||
curves_sculpt_delete.cc
|
||||
curves_sculpt_density.cc
|
||||
curves_sculpt_grow_shrink.cc
|
||||
curves_sculpt_ops.cc
|
||||
curves_sculpt_selection.cc
|
||||
curves_sculpt_pinch.cc
|
||||
curves_sculpt_puff.cc
|
||||
curves_sculpt_selection_paint.cc
|
||||
curves_sculpt_selection.cc
|
||||
curves_sculpt_slide.cc
|
||||
curves_sculpt_smooth.cc
|
||||
curves_sculpt_snake_hook.cc
|
||||
paint_canvas.cc
|
||||
paint_cursor.c
|
||||
|
|
|
@ -0,0 +1,834 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_bvhutils.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
|
||||
#include "ED_screen.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "BLI_index_mask_ops.hh"
|
||||
#include "BLI_kdtree.h"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "PIL_time.h"
|
||||
|
||||
#include "GEO_add_curves_on_mesh.hh"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include "curves_sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
class DensityAddOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
/** Used when some data should be interpolated from existing curves. */
|
||||
KDTree_3d *curve_roots_kdtree_ = nullptr;
|
||||
int original_curve_num_ = 0;
|
||||
|
||||
friend struct DensityAddOperationExecutor;
|
||||
|
||||
public:
|
||||
~DensityAddOperation() override
|
||||
{
|
||||
if (curve_roots_kdtree_ != nullptr) {
|
||||
BLI_kdtree_3d_free(curve_roots_kdtree_);
|
||||
}
|
||||
}
|
||||
|
||||
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
|
||||
};
|
||||
|
||||
struct DensityAddOperationExecutor {
|
||||
DensityAddOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
Object *surface_ob_ = nullptr;
|
||||
Mesh *surface_ = nullptr;
|
||||
Span<MLoopTri> surface_looptris_;
|
||||
Span<float3> corner_normals_su_;
|
||||
VArray_Span<float2> surface_uv_map_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
const BrushCurvesSculptSettings *brush_settings_ = nullptr;
|
||||
|
||||
float brush_strength_;
|
||||
float brush_radius_re_;
|
||||
float2 brush_pos_re_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
|
||||
BVHTreeFromMesh surface_bvh_;
|
||||
|
||||
DensityAddOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(DensityAddOperation &self,
|
||||
const bContext &C,
|
||||
const StrokeExtension &stroke_extension)
|
||||
{
|
||||
self_ = &self;
|
||||
object_ = CTX_data_active_object(&C);
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
self_->original_curve_num_ = curves_->curves_num();
|
||||
}
|
||||
|
||||
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
|
||||
return;
|
||||
}
|
||||
|
||||
surface_ob_ = curves_id_->surface;
|
||||
surface_ = static_cast<Mesh *>(surface_ob_->data);
|
||||
|
||||
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
|
||||
BKE_mesh_runtime_looptri_len(surface_)};
|
||||
|
||||
transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
|
||||
|
||||
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
|
||||
BKE_mesh_calc_normals_split(surface_);
|
||||
}
|
||||
corner_normals_su_ = {
|
||||
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
|
||||
surface_->totloop};
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_settings_ = brush_->curves_sculpt_settings;
|
||||
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
brush_radius_re_ = brush_radius_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
brush_pos_re_ = stroke_extension.mouse_position;
|
||||
|
||||
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
|
||||
brush_->falloff_shape);
|
||||
|
||||
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
|
||||
|
||||
Vector<float3> new_bary_coords;
|
||||
Vector<int> new_looptri_indices;
|
||||
Vector<float3> new_positions_cu;
|
||||
const double time = PIL_check_seconds_timer() * 1000000.0;
|
||||
RandomNumberGenerator rng{*(uint32_t *)(&time)};
|
||||
|
||||
/* Find potential new curve root points. */
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->sample_projected_with_symmetry(
|
||||
rng, new_bary_coords, new_looptri_indices, new_positions_cu);
|
||||
}
|
||||
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->sample_spherical_with_symmetry(
|
||||
rng, new_bary_coords, new_looptri_indices, new_positions_cu);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
for (float3 &pos : new_positions_cu) {
|
||||
pos = transforms_.surface_to_curves * pos;
|
||||
}
|
||||
|
||||
this->ensure_curve_roots_kdtree();
|
||||
|
||||
const int already_added_curves = curves_->curves_num() - self_->original_curve_num_;
|
||||
KDTree_3d *new_roots_kdtree = BLI_kdtree_3d_new(already_added_curves +
|
||||
new_positions_cu.size());
|
||||
BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(new_roots_kdtree); });
|
||||
|
||||
/* Used to tag all curves that are too close to existing curves or too close to other new
|
||||
* curves. */
|
||||
Array<bool> new_curve_skipped(new_positions_cu.size(), false);
|
||||
threading::parallel_invoke(
|
||||
/* Build kdtree from root points created by the current stroke. */
|
||||
[&]() {
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
for (const int curve_i : curves_->curves_range().take_back(already_added_curves)) {
|
||||
const float3 &root_pos_cu = positions_cu[curves_->offsets()[curve_i]];
|
||||
BLI_kdtree_3d_insert(new_roots_kdtree, curve_i, root_pos_cu);
|
||||
}
|
||||
for (const int new_i : new_positions_cu.index_range()) {
|
||||
const int index_in_kdtree = curves_->curves_num() + new_i;
|
||||
const float3 &root_pos_cu = new_positions_cu[new_i];
|
||||
BLI_kdtree_3d_insert(new_roots_kdtree, index_in_kdtree, root_pos_cu);
|
||||
}
|
||||
BLI_kdtree_3d_balance(new_roots_kdtree);
|
||||
},
|
||||
/* Check which new root points are close to roots that existed before the current stroke
|
||||
* started. */
|
||||
[&]() {
|
||||
threading::parallel_for(
|
||||
new_positions_cu.index_range(), 128, [&](const IndexRange range) {
|
||||
for (const int new_i : range) {
|
||||
const float3 &new_root_pos_cu = new_positions_cu[new_i];
|
||||
KDTreeNearest_3d nearest;
|
||||
nearest.dist = FLT_MAX;
|
||||
BLI_kdtree_3d_find_nearest(
|
||||
self_->curve_roots_kdtree_, new_root_pos_cu, &nearest);
|
||||
if (nearest.dist < brush_settings_->minimum_distance) {
|
||||
new_curve_skipped[new_i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Find new points that are too close too other new points. */
|
||||
for (const int new_i : new_positions_cu.index_range()) {
|
||||
if (new_curve_skipped[new_i]) {
|
||||
continue;
|
||||
}
|
||||
const float3 &root_pos_cu = new_positions_cu[new_i];
|
||||
BLI_kdtree_3d_range_search_cb_cpp(
|
||||
new_roots_kdtree,
|
||||
root_pos_cu,
|
||||
brush_settings_->minimum_distance,
|
||||
[&](const int other_i, const float *UNUSED(co), float UNUSED(dist_sq)) {
|
||||
if (other_i < curves_->curves_num()) {
|
||||
new_curve_skipped[new_i] = true;
|
||||
return false;
|
||||
}
|
||||
const int other_new_i = other_i - curves_->curves_num();
|
||||
if (new_i == other_new_i) {
|
||||
return true;
|
||||
}
|
||||
new_curve_skipped[other_new_i] = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/* Remove points that are too close to others. */
|
||||
for (int64_t i = new_positions_cu.size() - 1; i >= 0; i--) {
|
||||
if (new_curve_skipped[i]) {
|
||||
new_positions_cu.remove_and_reorder(i);
|
||||
new_bary_coords.remove_and_reorder(i);
|
||||
new_looptri_indices.remove_and_reorder(i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Find UV map. */
|
||||
VArray_Span<float2> surface_uv_map;
|
||||
if (curves_id_->surface_uv_map != nullptr) {
|
||||
MeshComponent surface_component;
|
||||
surface_component.replace(surface_, GeometryOwnershipType::ReadOnly);
|
||||
surface_uv_map = surface_component
|
||||
.attribute_try_get_for_read(curves_id_->surface_uv_map,
|
||||
ATTR_DOMAIN_CORNER)
|
||||
.typed<float2>();
|
||||
}
|
||||
|
||||
/* Find normals. */
|
||||
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
|
||||
BKE_mesh_calc_normals_split(surface_);
|
||||
}
|
||||
const Span<float3> corner_normals_su = {
|
||||
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
|
||||
surface_->totloop};
|
||||
|
||||
geometry::AddCurvesOnMeshInputs add_inputs;
|
||||
add_inputs.root_positions_cu = new_positions_cu;
|
||||
add_inputs.bary_coords = new_bary_coords;
|
||||
add_inputs.looptri_indices = new_looptri_indices;
|
||||
add_inputs.interpolate_length = brush_settings_->flag &
|
||||
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH;
|
||||
add_inputs.interpolate_shape = brush_settings_->flag &
|
||||
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE;
|
||||
add_inputs.interpolate_point_count = brush_settings_->flag &
|
||||
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT;
|
||||
add_inputs.fallback_curve_length = brush_settings_->curve_length;
|
||||
add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve);
|
||||
add_inputs.surface = surface_;
|
||||
add_inputs.surface_bvh = &surface_bvh_;
|
||||
add_inputs.surface_looptris = surface_looptris_;
|
||||
add_inputs.surface_uv_map = surface_uv_map;
|
||||
add_inputs.corner_normals_su = corner_normals_su;
|
||||
add_inputs.curves_to_surface_mat = transforms_.curves_to_surface;
|
||||
add_inputs.surface_to_curves_normal_mat = transforms_.surface_to_curves_normal;
|
||||
add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_;
|
||||
|
||||
geometry::add_curves_on_mesh(*curves_, add_inputs);
|
||||
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void ensure_curve_roots_kdtree()
|
||||
{
|
||||
if (self_->curve_roots_kdtree_ == nullptr) {
|
||||
self_->curve_roots_kdtree_ = BLI_kdtree_3d_new(curves_->curves_num());
|
||||
for (const int curve_i : curves_->curves_range()) {
|
||||
const int root_point_i = curves_->offsets()[curve_i];
|
||||
const float3 &root_pos_cu = curves_->positions()[root_point_i];
|
||||
BLI_kdtree_3d_insert(self_->curve_roots_kdtree_, curve_i, root_pos_cu);
|
||||
}
|
||||
BLI_kdtree_3d_balance(self_->curve_roots_kdtree_);
|
||||
}
|
||||
}
|
||||
|
||||
void sample_projected_with_symmetry(RandomNumberGenerator &rng,
|
||||
Vector<float3> &r_bary_coords,
|
||||
Vector<int> &r_looptri_indices,
|
||||
Vector<float3> &r_positions_su)
|
||||
{
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
const float4x4 transform = transforms_.curves_to_surface * brush_transform *
|
||||
transforms_.world_to_curves;
|
||||
const int new_points = bke::mesh_surface_sample::sample_surface_points_projected(
|
||||
rng,
|
||||
*surface_,
|
||||
surface_bvh_,
|
||||
brush_pos_re_,
|
||||
brush_radius_re_,
|
||||
[&](const float2 &pos_re, float3 &r_start_su, float3 &r_end_su) {
|
||||
float3 start_wo, end_wo;
|
||||
ED_view3d_win_to_segment_clipped(
|
||||
ctx_.depsgraph, ctx_.region, ctx_.v3d, pos_re, start_wo, end_wo, true);
|
||||
r_start_su = transform * start_wo;
|
||||
r_end_su = transform * end_wo;
|
||||
},
|
||||
true,
|
||||
brush_settings_->density_add_attempts,
|
||||
brush_settings_->density_add_attempts,
|
||||
r_bary_coords,
|
||||
r_looptri_indices,
|
||||
r_positions_su);
|
||||
|
||||
/* Remove some sampled points randomly based on the brush falloff and strength. */
|
||||
const int old_points = r_bary_coords.size() - new_points;
|
||||
for (int i = r_bary_coords.size() - 1; i >= old_points; i--) {
|
||||
const float3 pos_su = r_positions_su[i];
|
||||
const float3 pos_cu = brush_transform_inv * transforms_.surface_to_curves * pos_su;
|
||||
float2 pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
|
||||
const float dist_to_brush_re = math::distance(brush_pos_re_, pos_re);
|
||||
const float radius_falloff = BKE_brush_curve_strength(
|
||||
brush_, dist_to_brush_re, brush_radius_re_);
|
||||
const float weight = brush_strength_ * radius_falloff;
|
||||
if (rng.get_float() > weight) {
|
||||
r_bary_coords.remove_and_reorder(i);
|
||||
r_looptri_indices.remove_and_reorder(i);
|
||||
r_positions_su.remove_and_reorder(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sample_spherical_with_symmetry(RandomNumberGenerator &rng,
|
||||
Vector<float3> &r_bary_coords,
|
||||
Vector<int> &r_looptri_indices,
|
||||
Vector<float3> &r_positions_su)
|
||||
{
|
||||
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
*ctx_.v3d,
|
||||
transforms_,
|
||||
surface_bvh_,
|
||||
brush_pos_re_,
|
||||
brush_radius_re_);
|
||||
if (!brush_3d.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
const float3 brush_pos_cu = brush_transform * brush_3d->position_cu;
|
||||
const float3 brush_pos_su = transforms_.curves_to_surface * brush_pos_cu;
|
||||
const float brush_radius_su = transform_brush_radius(
|
||||
transforms_.curves_to_surface, brush_pos_cu, brush_3d->radius_cu);
|
||||
const float brush_radius_sq_su = pow2f(brush_radius_su);
|
||||
|
||||
Vector<int> looptri_indices;
|
||||
BLI_bvhtree_range_query_cpp(
|
||||
*surface_bvh_.tree,
|
||||
brush_pos_su,
|
||||
brush_radius_su,
|
||||
[&](const int index, const float3 &UNUSED(co), const float UNUSED(dist_sq)) {
|
||||
looptri_indices.append(index);
|
||||
});
|
||||
|
||||
const float brush_plane_area_su = M_PI * brush_radius_sq_su;
|
||||
const float approximate_density_su = brush_settings_->density_add_attempts /
|
||||
brush_plane_area_su;
|
||||
|
||||
const int new_points = bke::mesh_surface_sample::sample_surface_points_spherical(
|
||||
rng,
|
||||
*surface_,
|
||||
looptri_indices,
|
||||
brush_pos_su,
|
||||
brush_radius_su,
|
||||
approximate_density_su,
|
||||
r_bary_coords,
|
||||
r_looptri_indices,
|
||||
r_positions_su);
|
||||
|
||||
/* Remove some sampled points randomly based on the brush falloff and strength. */
|
||||
const int old_points = r_bary_coords.size() - new_points;
|
||||
for (int i = r_bary_coords.size() - 1; i >= old_points; i--) {
|
||||
const float3 pos_su = r_positions_su[i];
|
||||
const float3 pos_cu = transforms_.surface_to_curves * pos_su;
|
||||
const float dist_to_brush_cu = math::distance(pos_cu, brush_pos_cu);
|
||||
const float radius_falloff = BKE_brush_curve_strength(
|
||||
brush_, dist_to_brush_cu, brush_3d->radius_cu);
|
||||
const float weight = brush_strength_ * radius_falloff;
|
||||
if (rng.get_float() > weight) {
|
||||
r_bary_coords.remove_and_reorder(i);
|
||||
r_looptri_indices.remove_and_reorder(i);
|
||||
r_positions_su.remove_and_reorder(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DensityAddOperation::on_stroke_extended(const bContext &C,
|
||||
const StrokeExtension &stroke_extension)
|
||||
{
|
||||
DensityAddOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
class DensitySubtractOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
||||
friend struct DensitySubtractOperationExecutor;
|
||||
|
||||
public:
|
||||
void on_stroke_extended(const 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 DensitySubtractOperationExecutor {
|
||||
DensitySubtractOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
|
||||
Object *surface_ob_ = nullptr;
|
||||
Mesh *surface_ = nullptr;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
float brush_radius_base_re_;
|
||||
float brush_radius_factor_;
|
||||
float brush_strength_;
|
||||
float2 brush_pos_re_;
|
||||
|
||||
float minimum_distance_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
BVHTreeFromMesh surface_bvh_;
|
||||
|
||||
KDTree_3d *root_points_kdtree_;
|
||||
|
||||
DensitySubtractOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(DensitySubtractOperation &self,
|
||||
const bContext &C,
|
||||
const StrokeExtension &stroke_extension)
|
||||
{
|
||||
self_ = &self;
|
||||
|
||||
object_ = CTX_data_active_object(&C);
|
||||
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
if (curves_->curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
surface_ob_ = curves_id_->surface;
|
||||
if (surface_ob_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
surface_ = static_cast<Mesh *>(surface_ob_->data);
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
|
||||
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
|
||||
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
brush_pos_re_ = stroke_extension.mouse_position;
|
||||
|
||||
minimum_distance_ = brush_->curves_sculpt_settings->minimum_distance;
|
||||
|
||||
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
|
||||
|
||||
transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
|
||||
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
|
||||
brush_->falloff_shape);
|
||||
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
|
||||
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
|
||||
root_points_kdtree_ = BLI_kdtree_3d_new(curve_selection_.size());
|
||||
BLI_SCOPED_DEFER([&]() { BLI_kdtree_3d_free(root_points_kdtree_); });
|
||||
for (const int curve_i : curve_selection_) {
|
||||
const int first_point_i = curves_->offsets()[curve_i];
|
||||
const float3 &pos_cu = positions_cu[first_point_i];
|
||||
BLI_kdtree_3d_insert(root_points_kdtree_, curve_i, pos_cu);
|
||||
}
|
||||
BLI_kdtree_3d_balance(root_points_kdtree_);
|
||||
|
||||
/* Find all curves that should be deleted. */
|
||||
Array<bool> curves_to_delete(curves_->curves_num(), false);
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->reduce_density_projected_with_symmetry(curves_to_delete);
|
||||
}
|
||||
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->reduce_density_spherical_with_symmetry(curves_to_delete);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
Vector<int64_t> indices;
|
||||
const IndexMask mask = index_mask_ops::find_indices_based_on_predicate(
|
||||
curves_->curves_range(), 4096, indices, [&](const int curve_i) {
|
||||
return curves_to_delete[curve_i];
|
||||
});
|
||||
|
||||
curves_->remove_curves(mask);
|
||||
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void reduce_density_projected_with_symmetry(MutableSpan<bool> curves_to_delete)
|
||||
{
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->reduce_density_projected(brush_transform, curves_to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_density_projected(const float4x4 &brush_transform,
|
||||
MutableSpan<bool> curves_to_delete)
|
||||
{
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
const Span<int> offsets = curves_->offsets();
|
||||
|
||||
/* Randomly select the curves that are allowed to be removed, based on the brush radius and
|
||||
* strength. */
|
||||
Array<bool> allow_remove_curve(curves_->curves_num(), false);
|
||||
threading::parallel_for(curves_->curves_range(), 512, [&](const IndexRange range) {
|
||||
RandomNumberGenerator rng((int)(PIL_check_seconds_timer() * 1000000.0));
|
||||
|
||||
for (const int curve_i : range) {
|
||||
if (curves_to_delete[curve_i]) {
|
||||
allow_remove_curve[curve_i] = true;
|
||||
continue;
|
||||
}
|
||||
const int first_point_i = offsets[curve_i];
|
||||
const float3 pos_cu = brush_transform * positions_cu[first_point_i];
|
||||
|
||||
float2 pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
|
||||
const float dist_to_brush_sq_re = math::distance_squared(brush_pos_re_, pos_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;
|
||||
if (rng.get_float() < weight) {
|
||||
allow_remove_curve[curve_i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Detect curves that are too close to other existing curves. */
|
||||
for (const int curve_i : curve_selection_) {
|
||||
if (curves_to_delete[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
if (!allow_remove_curve[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
const int first_point_i = offsets[curve_i];
|
||||
const float3 orig_pos_cu = positions_cu[first_point_i];
|
||||
const float3 pos_cu = brush_transform * orig_pos_cu;
|
||||
float2 pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
|
||||
const float dist_to_brush_sq_re = math::distance_squared(brush_pos_re_, pos_re);
|
||||
if (dist_to_brush_sq_re > brush_radius_sq_re) {
|
||||
continue;
|
||||
}
|
||||
BLI_kdtree_3d_range_search_cb_cpp(
|
||||
root_points_kdtree_,
|
||||
orig_pos_cu,
|
||||
minimum_distance_,
|
||||
[&](const int other_curve_i, const float *UNUSED(co), float UNUSED(dist_sq)) {
|
||||
if (other_curve_i == curve_i) {
|
||||
return true;
|
||||
}
|
||||
if (allow_remove_curve[other_curve_i]) {
|
||||
curves_to_delete[other_curve_i] = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_density_spherical_with_symmetry(MutableSpan<bool> curves_to_delete)
|
||||
{
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
*ctx_.v3d,
|
||||
transforms_,
|
||||
surface_bvh_,
|
||||
brush_pos_re_,
|
||||
brush_radius_re);
|
||||
if (!brush_3d.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
const float3 brush_pos_cu = brush_transform * brush_3d->position_cu;
|
||||
this->reduce_density_spherical(brush_pos_cu, brush_3d->radius_cu, curves_to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
void reduce_density_spherical(const float3 &brush_pos_cu,
|
||||
const float brush_radius_cu,
|
||||
MutableSpan<bool> curves_to_delete)
|
||||
{
|
||||
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
const Span<int> offsets = curves_->offsets();
|
||||
|
||||
/* Randomly select the curves that are allowed to be removed, based on the brush radius and
|
||||
* strength. */
|
||||
Array<bool> allow_remove_curve(curves_->curves_num(), false);
|
||||
threading::parallel_for(curves_->curves_range(), 512, [&](const IndexRange range) {
|
||||
RandomNumberGenerator rng((int)(PIL_check_seconds_timer() * 1000000.0));
|
||||
|
||||
for (const int curve_i : range) {
|
||||
if (curves_to_delete[curve_i]) {
|
||||
allow_remove_curve[curve_i] = true;
|
||||
continue;
|
||||
}
|
||||
const int first_point_i = offsets[curve_i];
|
||||
const float3 pos_cu = positions_cu[first_point_i];
|
||||
|
||||
const float dist_to_brush_sq_cu = math::distance_squared(brush_pos_cu, pos_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;
|
||||
if (rng.get_float() < weight) {
|
||||
allow_remove_curve[curve_i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Detect curves that are too close to other existing curves. */
|
||||
for (const int curve_i : curve_selection_) {
|
||||
if (curves_to_delete[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
if (!allow_remove_curve[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
const int first_point_i = offsets[curve_i];
|
||||
const float3 &pos_cu = positions_cu[first_point_i];
|
||||
const float dist_to_brush_sq_cu = math::distance_squared(pos_cu, brush_pos_cu);
|
||||
if (dist_to_brush_sq_cu > brush_radius_sq_cu) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BLI_kdtree_3d_range_search_cb_cpp(
|
||||
root_points_kdtree_,
|
||||
pos_cu,
|
||||
minimum_distance_,
|
||||
[&](const int other_curve_i, const float *UNUSED(co), float UNUSED(dist_sq)) {
|
||||
if (other_curve_i == curve_i) {
|
||||
return true;
|
||||
}
|
||||
if (allow_remove_curve[other_curve_i]) {
|
||||
curves_to_delete[other_curve_i] = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DensitySubtractOperation::on_stroke_extended(const bContext &C,
|
||||
const StrokeExtension &stroke_extension)
|
||||
{
|
||||
DensitySubtractOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether the brush should be in Add or Subtract mode.
|
||||
*/
|
||||
static bool use_add_density_mode(const BrushStrokeMode brush_mode,
|
||||
const bContext &C,
|
||||
const StrokeExtension &stroke_start)
|
||||
{
|
||||
const Scene &scene = *CTX_data_scene(&C);
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
|
||||
const eBrushCurvesSculptDensityMode density_mode = static_cast<eBrushCurvesSculptDensityMode>(
|
||||
brush.curves_sculpt_settings->density_mode);
|
||||
const bool use_invert = brush_mode == BRUSH_STROKE_INVERT;
|
||||
|
||||
if (density_mode == BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD) {
|
||||
return !use_invert;
|
||||
}
|
||||
if (density_mode == BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE) {
|
||||
return use_invert;
|
||||
}
|
||||
|
||||
const Object &curves_ob = *CTX_data_active_object(&C);
|
||||
const Curves &curves_id = *static_cast<Curves *>(curves_ob.data);
|
||||
const CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
|
||||
if (curves_id.surface == nullptr) {
|
||||
/* The brush won't do anything in this case anyway. */
|
||||
return true;
|
||||
}
|
||||
if (curves.curves_num() <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const CurvesSculptTransforms transforms(curves_ob, curves_id.surface);
|
||||
BVHTreeFromMesh surface_bvh;
|
||||
BKE_bvhtree_from_mesh_get(
|
||||
&surface_bvh, static_cast<const Mesh *>(curves_id.surface->data), BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh); });
|
||||
|
||||
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(&C);
|
||||
const ARegion ®ion = *CTX_wm_region(&C);
|
||||
const View3D &v3d = *CTX_wm_view3d(&C);
|
||||
|
||||
const float2 brush_pos_re = stroke_start.mouse_position;
|
||||
/* Reduce radius so that only an inner circle is used to determine the existing density. */
|
||||
const float brush_radius_re = BKE_brush_size_get(&scene, &brush) * 0.5f;
|
||||
|
||||
/* Find the surface point under the brush. */
|
||||
const std::optional<CurvesBrush3D> brush_3d = sample_curves_surface_3d_brush(
|
||||
depsgraph, region, v3d, transforms, surface_bvh, brush_pos_re, brush_radius_re);
|
||||
if (!brush_3d.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float3 brush_pos_cu = brush_3d->position_cu;
|
||||
const float brush_radius_cu = brush_3d->radius_cu;
|
||||
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
|
||||
|
||||
const Span<int> offsets = curves.offsets();
|
||||
const Span<float3> positions_cu = curves.positions();
|
||||
|
||||
/* Compute distance from brush to curve roots. */
|
||||
Array<std::pair<float, int>> distances_sq_to_brush(curves.curves_num());
|
||||
threading::EnumerableThreadSpecific<int> valid_curve_count_by_thread;
|
||||
threading::parallel_for(curves.curves_range(), 512, [&](const IndexRange range) {
|
||||
int &valid_curve_count = valid_curve_count_by_thread.local();
|
||||
for (const int curve_i : range) {
|
||||
const int root_point_i = offsets[curve_i];
|
||||
const float3 &root_pos_cu = positions_cu[root_point_i];
|
||||
const float dist_sq_cu = math::distance_squared(root_pos_cu, brush_pos_cu);
|
||||
if (dist_sq_cu < brush_radius_sq_cu) {
|
||||
distances_sq_to_brush[curve_i] = {math::distance_squared(root_pos_cu, brush_pos_cu),
|
||||
curve_i};
|
||||
valid_curve_count++;
|
||||
}
|
||||
else {
|
||||
distances_sq_to_brush[curve_i] = {FLT_MAX, -1};
|
||||
}
|
||||
}
|
||||
});
|
||||
const int valid_curve_count = std::accumulate(
|
||||
valid_curve_count_by_thread.begin(), valid_curve_count_by_thread.end(), 0);
|
||||
|
||||
/* Find a couple of curves that are closest to the brush center. */
|
||||
const int check_curve_count = std::min<int>(8, valid_curve_count);
|
||||
std::partial_sort(distances_sq_to_brush.begin(),
|
||||
distances_sq_to_brush.begin() + check_curve_count,
|
||||
distances_sq_to_brush.end());
|
||||
|
||||
/* Compute the minimum pair-wise distance between the curve roots that are close to the brush
|
||||
* center. */
|
||||
float min_dist_sq_cu = FLT_MAX;
|
||||
for (const int i : IndexRange(check_curve_count)) {
|
||||
const float3 &pos_i = positions_cu[offsets[distances_sq_to_brush[i].second]];
|
||||
for (int j = i + 1; j < check_curve_count; j++) {
|
||||
const float3 &pos_j = positions_cu[offsets[distances_sq_to_brush[j].second]];
|
||||
const float dist_sq_cu = math::distance_squared(pos_i, pos_j);
|
||||
math::min_inplace(min_dist_sq_cu, dist_sq_cu);
|
||||
}
|
||||
}
|
||||
|
||||
const float min_dist_cu = std::sqrt(min_dist_sq_cu);
|
||||
if (min_dist_cu > brush.curves_sculpt_settings->minimum_distance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_density_operation(
|
||||
const BrushStrokeMode brush_mode, const bContext &C, const StrokeExtension &stroke_start)
|
||||
{
|
||||
if (use_add_density_mode(brush_mode, C, stroke_start)) {
|
||||
return std::make_unique<DensityAddOperation>();
|
||||
}
|
||||
return std::make_unique<DensitySubtractOperation>();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
|
@ -66,7 +66,7 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_pinch_operation(const BrushStro
|
|||
std::unique_ptr<CurvesSculptStrokeOperation> new_smooth_operation();
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation();
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_density_operation(
|
||||
const BrushStrokeMode brush_mode, const bContext &C);
|
||||
const BrushStrokeMode brush_mode, const bContext &C, const StrokeExtension &stroke_start);
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_slide_operation();
|
||||
|
||||
struct CurvesBrush3D {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,307 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "curves_sculpt_intern.hh"
|
||||
|
||||
#include "BLI_float4x4.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "PIL_time.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_paint.h"
|
||||
|
||||
#include "DNA_brush_enums.h"
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_curves_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 "WM_api.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 {
|
||||
|
||||
class PinchOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
bool invert_pinch_;
|
||||
Array<float> segment_lengths_cu_;
|
||||
|
||||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
||||
friend struct PinchOperationExecutor;
|
||||
|
||||
public:
|
||||
PinchOperation(const bool invert_pinch) : invert_pinch_(invert_pinch)
|
||||
{
|
||||
}
|
||||
|
||||
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
|
||||
};
|
||||
|
||||
struct PinchOperationExecutor {
|
||||
PinchOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
float brush_radius_base_re_;
|
||||
float brush_radius_factor_;
|
||||
float brush_strength_;
|
||||
|
||||
float invert_factor_;
|
||||
|
||||
float2 brush_pos_re_;
|
||||
|
||||
PinchOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(PinchOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
self_ = &self;
|
||||
|
||||
object_ = CTX_data_active_object(&C);
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
if (curves_->curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
|
||||
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
|
||||
brush_strength_ = BKE_brush_alpha_get(ctx_.scene, brush_);
|
||||
|
||||
invert_factor_ = self_->invert_pinch_ ? -1.0f : 1.0f;
|
||||
|
||||
transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
|
||||
|
||||
point_factors_ = get_point_selection(*curves_id_);
|
||||
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
|
||||
|
||||
brush_pos_re_ = stroke_extension.mouse_position;
|
||||
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
|
||||
brush_->falloff_shape);
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
this->initialize_segment_lengths();
|
||||
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
self_->brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
*ctx_.v3d,
|
||||
*ctx_.rv3d,
|
||||
*object_,
|
||||
brush_pos_re_,
|
||||
brush_radius_base_re_);
|
||||
}
|
||||
}
|
||||
|
||||
Array<bool> changed_curves(curves_->curves_num(), false);
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->pinch_projected_with_symmetry(changed_curves);
|
||||
}
|
||||
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->pinch_spherical_with_symmetry(changed_curves);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
this->restore_segment_lengths(changed_curves);
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void pinch_projected_with_symmetry(MutableSpan<bool> r_changed_curves)
|
||||
{
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->pinch_projected(brush_transform, r_changed_curves);
|
||||
}
|
||||
}
|
||||
|
||||
void pinch_projected(const float4x4 &brush_transform, MutableSpan<bool> r_changed_curves)
|
||||
{
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i];
|
||||
float2 old_pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, old_pos_cu, old_pos_re, projection.values);
|
||||
|
||||
const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_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 t = safe_divide(dist_to_brush_re, brush_radius_base_re_);
|
||||
const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f);
|
||||
const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff *
|
||||
point_factors_[point_i];
|
||||
|
||||
const float2 new_pos_re = math::interpolate(old_pos_re, brush_pos_re_, weight);
|
||||
|
||||
const float3 old_pos_wo = transforms_.curves_to_world * old_pos_cu;
|
||||
float3 new_pos_wo;
|
||||
ED_view3d_win_to_3d(ctx_.v3d, ctx_.region, old_pos_wo, new_pos_re, new_pos_wo);
|
||||
|
||||
const float3 new_pos_cu = transforms_.world_to_curves * new_pos_wo;
|
||||
positions_cu[point_i] = brush_transform * new_pos_cu;
|
||||
r_changed_curves[curve_i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void pinch_spherical_with_symmetry(MutableSpan<bool> r_changed_curves)
|
||||
{
|
||||
float3 brush_pos_wo;
|
||||
ED_view3d_win_to_3d(ctx_.v3d,
|
||||
ctx_.region,
|
||||
transforms_.curves_to_world * self_->brush_3d_.position_cu,
|
||||
brush_pos_re_,
|
||||
brush_pos_wo);
|
||||
const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo;
|
||||
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->pinch_spherical(brush_transform * brush_pos_cu, brush_radius_cu, r_changed_curves);
|
||||
}
|
||||
}
|
||||
|
||||
void pinch_spherical(const float3 &brush_pos_cu,
|
||||
const float brush_radius_cu,
|
||||
MutableSpan<bool> r_changed_curves)
|
||||
{
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float3 old_pos_cu = positions_cu[point_i];
|
||||
|
||||
const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_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 t = safe_divide(dist_to_brush_cu, brush_radius_cu);
|
||||
const float radius_falloff = t * BKE_brush_curve_strength(brush_, t, 1.0f);
|
||||
const float weight = invert_factor_ * 0.1f * brush_strength_ * radius_falloff *
|
||||
point_factors_[point_i];
|
||||
|
||||
const float3 new_pos_cu = math::interpolate(old_pos_cu, brush_pos_cu, weight);
|
||||
positions_cu[point_i] = new_pos_cu;
|
||||
|
||||
r_changed_curves[curve_i] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initialize_segment_lengths()
|
||||
{
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
self_->segment_lengths_cu_.reinitialize(curves_->points_num());
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = curves_->points_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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void restore_segment_lengths(const Span<bool> changed_curves)
|
||||
{
|
||||
const Span<float> expected_lengths_cu = self_->segment_lengths_cu_;
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
|
||||
threading::parallel_for(changed_curves.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
if (!changed_curves[curve_i]) {
|
||||
continue;
|
||||
}
|
||||
const IndexRange points = curves_->points_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 PinchOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
PinchOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_pinch_operation(const BrushStrokeMode brush_mode,
|
||||
const bContext &C)
|
||||
{
|
||||
const Scene &scene = *CTX_data_scene(&C);
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
|
||||
|
||||
const bool invert_pinch = (brush_mode == BRUSH_STROKE_INVERT) !=
|
||||
((brush.flag & BRUSH_DIR_IN) != 0);
|
||||
return std::make_unique<PinchOperation>(invert_pinch);
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
|
@ -0,0 +1,393 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_bvhutils.h"
|
||||
#include "BKE_context.h"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include "BLI_length_parameterize.hh"
|
||||
|
||||
#include "curves_sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
class PuffOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
/** 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 PuffOperationExecutor;
|
||||
|
||||
public:
|
||||
void on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension) override;
|
||||
};
|
||||
|
||||
static float3 compute_surface_point_normal(const MLoopTri &looptri,
|
||||
const float3 &bary_coord,
|
||||
const Span<float3> corner_normals)
|
||||
{
|
||||
const int l0 = looptri.tri[0];
|
||||
const int l1 = looptri.tri[1];
|
||||
const int l2 = looptri.tri[2];
|
||||
|
||||
const float3 &l0_normal = corner_normals[l0];
|
||||
const float3 &l1_normal = corner_normals[l1];
|
||||
const float3 &l2_normal = corner_normals[l2];
|
||||
|
||||
const float3 normal = math::normalize(
|
||||
attribute_math::mix3(bary_coord, l0_normal, l1_normal, l2_normal));
|
||||
return normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 PuffOperationExecutor {
|
||||
PuffOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
float brush_radius_base_re_;
|
||||
float brush_radius_factor_;
|
||||
float brush_strength_;
|
||||
float2 brush_pos_re_;
|
||||
|
||||
eBrushFalloffShape falloff_shape_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
|
||||
Object *surface_ob_ = nullptr;
|
||||
Mesh *surface_ = nullptr;
|
||||
Span<MLoopTri> surface_looptris_;
|
||||
Span<float3> corner_normals_su_;
|
||||
BVHTreeFromMesh surface_bvh_;
|
||||
|
||||
PuffOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(PuffOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
UNUSED_VARS(C, stroke_extension);
|
||||
self_ = &self;
|
||||
|
||||
object_ = CTX_data_active_object(&C);
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
if (curves_->curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
|
||||
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
|
||||
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
brush_pos_re_ = stroke_extension.mouse_position;
|
||||
|
||||
point_factors_ = get_point_selection(*curves_id_);
|
||||
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
|
||||
|
||||
falloff_shape_ = static_cast<eBrushFalloffShape>(brush_->falloff_shape);
|
||||
|
||||
surface_ob_ = curves_id_->surface;
|
||||
surface_ = static_cast<Mesh *>(surface_ob_->data);
|
||||
|
||||
transforms_ = CurvesSculptTransforms(*object_, surface_ob_);
|
||||
|
||||
if (!CustomData_has_layer(&surface_->ldata, CD_NORMAL)) {
|
||||
BKE_mesh_calc_normals_split(surface_);
|
||||
}
|
||||
corner_normals_su_ = {
|
||||
reinterpret_cast<const float3 *>(CustomData_get_layer(&surface_->ldata, CD_NORMAL)),
|
||||
surface_->totloop};
|
||||
|
||||
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
|
||||
|
||||
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
|
||||
BKE_mesh_runtime_looptri_len(surface_)};
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
this->initialize_segment_lengths();
|
||||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
*ctx_.v3d,
|
||||
*ctx_.rv3d,
|
||||
*object_,
|
||||
brush_pos_re_,
|
||||
brush_radius_base_re_);
|
||||
}
|
||||
}
|
||||
|
||||
Array<float> curve_weights(curve_selection_.size(), 0.0f);
|
||||
|
||||
if (falloff_shape_ == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->find_curve_weights_projected_with_symmetry(curve_weights);
|
||||
}
|
||||
else if (falloff_shape_ == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->find_curves_weights_spherical_with_symmetry(curve_weights);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
this->puff(curve_weights);
|
||||
this->restore_segment_lengths();
|
||||
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void find_curve_weights_projected_with_symmetry(MutableSpan<float> r_curve_weights)
|
||||
{
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->find_curve_weights_projected(brush_transform, r_curve_weights);
|
||||
}
|
||||
}
|
||||
|
||||
void find_curve_weights_projected(const float4x4 &brush_transform,
|
||||
MutableSpan<float> r_curve_weights)
|
||||
{
|
||||
Span<float3> positions_cu = curves_->positions();
|
||||
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_selection_i : range) {
|
||||
const int curve_i = curve_selection_[curve_selection_i];
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
const float3 first_pos_cu = brush_transform_inv * positions_cu[points[0]];
|
||||
float2 prev_pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, prev_pos_re, projection.values);
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float3 pos_cu = brush_transform_inv * positions_cu[point_i];
|
||||
float2 pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
|
||||
BLI_SCOPED_DEFER([&]() { prev_pos_re = pos_re; });
|
||||
|
||||
const float dist_to_brush_sq_re = dist_squared_to_line_segment_v2(
|
||||
brush_pos_re_, prev_pos_re, pos_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 = radius_falloff;
|
||||
math::max_inplace(r_curve_weights[curve_selection_i], weight);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void find_curves_weights_spherical_with_symmetry(MutableSpan<float> r_curve_weights)
|
||||
{
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
float3 brush_pos_wo;
|
||||
ED_view3d_win_to_3d(ctx_.v3d,
|
||||
ctx_.region,
|
||||
transforms_.curves_to_world * self_->brush_3d_.position_cu,
|
||||
brush_pos_re_,
|
||||
brush_pos_wo);
|
||||
const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo;
|
||||
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->find_curves_weights_spherical(
|
||||
brush_transform * brush_pos_cu, brush_radius_cu, r_curve_weights);
|
||||
}
|
||||
}
|
||||
|
||||
void find_curves_weights_spherical(const float3 &brush_pos_cu,
|
||||
const float brush_radius_cu,
|
||||
MutableSpan<float> r_curve_weights)
|
||||
{
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_selection_i : range) {
|
||||
const int curve_i = curve_selection_[curve_selection_i];
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float3 &prev_pos_cu = positions_cu[point_i - 1];
|
||||
const float3 &pos_cu = positions_cu[point_i];
|
||||
const float dist_to_brush_sq_cu = dist_squared_to_line_segment_v3(
|
||||
brush_pos_cu, prev_pos_cu, pos_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 = radius_falloff;
|
||||
math::max_inplace(r_curve_weights[curve_selection_i], weight);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void puff(const Span<float> curve_weights)
|
||||
{
|
||||
BLI_assert(curve_weights.size() == curve_selection_.size());
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
Vector<float> accumulated_lengths_cu;
|
||||
for (const int curve_selection_i : range) {
|
||||
const int curve_i = curve_selection_[curve_selection_i];
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
const int first_point_i = points[0];
|
||||
const float3 first_pos_cu = positions_cu[first_point_i];
|
||||
const float3 first_pos_su = transforms_.curves_to_surface * first_pos_cu;
|
||||
|
||||
/* Find the nearest position on the surface. The curve will be aligned to the normal of
|
||||
* that point. */
|
||||
BVHTreeNearest nearest;
|
||||
nearest.dist_sq = FLT_MAX;
|
||||
BLI_bvhtree_find_nearest(surface_bvh_.tree,
|
||||
first_pos_su,
|
||||
&nearest,
|
||||
surface_bvh_.nearest_callback,
|
||||
&surface_bvh_);
|
||||
|
||||
const MLoopTri &looptri = surface_looptris_[nearest.index];
|
||||
const float3 closest_pos_su = nearest.co;
|
||||
const float3 &v0_su = surface_->mvert[surface_->mloop[looptri.tri[0]].v].co;
|
||||
const float3 &v1_su = surface_->mvert[surface_->mloop[looptri.tri[1]].v].co;
|
||||
const float3 &v2_su = surface_->mvert[surface_->mloop[looptri.tri[2]].v].co;
|
||||
float3 bary_coords;
|
||||
interp_weights_tri_v3(bary_coords, v0_su, v1_su, v2_su, closest_pos_su);
|
||||
const float3 normal_su = compute_surface_point_normal(
|
||||
looptri, bary_coords, corner_normals_su_);
|
||||
const float3 normal_cu = math::normalize(transforms_.surface_to_curves_normal * normal_su);
|
||||
|
||||
accumulated_lengths_cu.reinitialize(points.size() - 1);
|
||||
length_parameterize::accumulate_lengths<float3>(
|
||||
positions_cu.slice(points), false, accumulated_lengths_cu);
|
||||
|
||||
/* Align curve to the surface normal while making sure that the curve does not fold up much
|
||||
* in the process (e.g. when the curve was pointing in the opposite direction before). */
|
||||
for (const int i : IndexRange(points.size()).drop_front(1)) {
|
||||
const int point_i = points[i];
|
||||
const float3 old_pos_cu = positions_cu[point_i];
|
||||
|
||||
/* Compute final position of the point. */
|
||||
const float length_param_cu = accumulated_lengths_cu[i - 1];
|
||||
const float3 goal_pos_cu = first_pos_cu + length_param_cu * normal_cu;
|
||||
|
||||
const float weight = 0.01f * brush_strength_ * point_factors_[point_i] *
|
||||
curve_weights[curve_selection_i];
|
||||
float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight);
|
||||
|
||||
/* Make sure the point does not move closer to the root point than it was initially. This
|
||||
* makes the curve kind of "rotate up". */
|
||||
const float old_dist_to_root_cu = math::distance(old_pos_cu, first_pos_cu);
|
||||
const float new_dist_to_root_cu = math::distance(new_pos_cu, first_pos_cu);
|
||||
if (new_dist_to_root_cu < old_dist_to_root_cu) {
|
||||
const float3 offset = math::normalize(new_pos_cu - first_pos_cu);
|
||||
new_pos_cu += (old_dist_to_root_cu - new_dist_to_root_cu) * offset;
|
||||
}
|
||||
|
||||
positions_cu[point_i] = new_pos_cu;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void initialize_segment_lengths()
|
||||
{
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
self_->segment_lengths_cu_.reinitialize(curves_->points_num());
|
||||
threading::parallel_for(curves_->curves_range(), 128, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = curves_->points_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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void restore_segment_lengths()
|
||||
{
|
||||
const Span<float> expected_lengths_cu = self_->segment_lengths_cu_;
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
|
||||
threading::parallel_for(curves_->curves_range(), 256, [&](const IndexRange range) {
|
||||
for (const int curve_i : range) {
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
for (const int segment_i : points.drop_back(1)) {
|
||||
const float3 &p1_cu = positions_cu[segment_i];
|
||||
float3 &p2_cu = positions_cu[segment_i + 1];
|
||||
const float3 direction = math::normalize(p2_cu - p1_cu);
|
||||
const float expected_length_cu = expected_lengths_cu[segment_i];
|
||||
p2_cu = p1_cu + direction * expected_length_cu;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void PuffOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
PuffOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_puff_operation()
|
||||
{
|
||||
return std::make_unique<PuffOperation>();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
|
@ -0,0 +1,310 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "curves_sculpt_intern.hh"
|
||||
|
||||
#include "BLI_float4x4.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_geometry_set.hh"
|
||||
#include "BKE_mesh.h"
|
||||
#include "BKE_mesh_runtime.h"
|
||||
#include "BKE_mesh_sample.hh"
|
||||
#include "BKE_paint.h"
|
||||
|
||||
#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 "UI_interface.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
struct SlideCurveInfo {
|
||||
/** Index of the curve to slide. */
|
||||
int curve_i;
|
||||
/** A weight based on the initial distance to the brush. */
|
||||
float radius_falloff;
|
||||
};
|
||||
|
||||
struct SlideInfo {
|
||||
/** The transform used for the curves below (e.g. for symmetry). */
|
||||
float4x4 brush_transform;
|
||||
Vector<SlideCurveInfo> curves_to_slide;
|
||||
};
|
||||
|
||||
class SlideOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
/** Last mouse position. */
|
||||
float2 brush_pos_last_re_;
|
||||
/** Information about which curves to slide. This is initialized when the brush starts. */
|
||||
Vector<SlideInfo> slide_info_;
|
||||
|
||||
friend struct SlideOperationExecutor;
|
||||
|
||||
public:
|
||||
void on_stroke_extended(const 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 SlideOperationExecutor {
|
||||
SlideOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
float brush_radius_base_re_;
|
||||
float brush_radius_factor_;
|
||||
float brush_strength_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
Object *surface_ob_ = nullptr;
|
||||
Mesh *surface_ = nullptr;
|
||||
Span<MLoopTri> surface_looptris_;
|
||||
VArray_Span<float2> surface_uv_map_;
|
||||
|
||||
VArray<float> curve_factors_;
|
||||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
|
||||
float2 brush_pos_prev_re_;
|
||||
float2 brush_pos_re_;
|
||||
float2 brush_pos_diff_re_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
|
||||
BVHTreeFromMesh surface_bvh_;
|
||||
|
||||
SlideOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(SlideOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
UNUSED_VARS(C, stroke_extension);
|
||||
self_ = &self;
|
||||
|
||||
object_ = CTX_data_active_object(&C);
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
if (curves_id_->surface == nullptr || curves_id_->surface->type != OB_MESH) {
|
||||
return;
|
||||
}
|
||||
if (curves_->curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
|
||||
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
|
||||
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
|
||||
curve_factors_ = get_curves_selection(*curves_id_);
|
||||
point_factors_ = get_point_selection(*curves_id_);
|
||||
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
|
||||
|
||||
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_;
|
||||
BLI_SCOPED_DEFER([&]() { self_->brush_pos_last_re_ = brush_pos_re_; });
|
||||
|
||||
transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
|
||||
|
||||
surface_ob_ = curves_id_->surface;
|
||||
surface_ = static_cast<Mesh *>(surface_ob_->data);
|
||||
|
||||
BKE_bvhtree_from_mesh_get(&surface_bvh_, surface_, BVHTREE_FROM_LOOPTRI, 2);
|
||||
BLI_SCOPED_DEFER([&]() { free_bvhtree_from_mesh(&surface_bvh_); });
|
||||
|
||||
surface_looptris_ = {BKE_mesh_runtime_looptri_ensure(surface_),
|
||||
BKE_mesh_runtime_looptri_len(surface_)};
|
||||
|
||||
if (curves_id_->surface_uv_map != nullptr) {
|
||||
MeshComponent surface_component;
|
||||
surface_component.replace(surface_, GeometryOwnershipType::ReadOnly);
|
||||
surface_uv_map_ = surface_component
|
||||
.attribute_try_get_for_read(curves_id_->surface_uv_map,
|
||||
ATTR_DOMAIN_CORNER)
|
||||
.typed<float2>();
|
||||
}
|
||||
|
||||
if (stroke_extension.is_first) {
|
||||
const Vector<float4x4> brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : brush_transforms) {
|
||||
this->detect_curves_to_slide(brush_transform);
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->slide_projected();
|
||||
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void detect_curves_to_slide(const float4x4 &brush_transform)
|
||||
{
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
const Span<float3> positions_cu = curves_->positions();
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
self_->slide_info_.append({brush_transform});
|
||||
Vector<SlideCurveInfo> &curves_to_slide = self_->slide_info_.last().curves_to_slide;
|
||||
|
||||
/* Find curves in brush radius that should be moved. */
|
||||
for (const int curve_i : curve_selection_) {
|
||||
const int first_point_i = curves_->offsets()[curve_i];
|
||||
const float3 &first_pos_cu = brush_transform_inv * positions_cu[first_point_i];
|
||||
|
||||
float2 first_pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, first_pos_cu, first_pos_re, projection.values);
|
||||
|
||||
const float dist_to_brush_sq_re = math::distance_squared(first_pos_re, brush_pos_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);
|
||||
curves_to_slide.append({curve_i, radius_falloff});
|
||||
}
|
||||
}
|
||||
|
||||
void slide_projected()
|
||||
{
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
|
||||
MutableSpan<float2> surface_uv_coords;
|
||||
if (!surface_uv_map_.is_empty()) {
|
||||
surface_uv_coords = curves_->surface_uv_coords_for_write();
|
||||
}
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
for (const SlideInfo &slide_info : self_->slide_info_) {
|
||||
const float4x4 &brush_transform = slide_info.brush_transform;
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
const Span<SlideCurveInfo> curves_to_slide = slide_info.curves_to_slide;
|
||||
|
||||
threading::parallel_for(curves_to_slide.index_range(), 256, [&](const IndexRange range) {
|
||||
for (const SlideCurveInfo &curve_slide_info : curves_to_slide.slice(range)) {
|
||||
const int curve_i = curve_slide_info.curve_i;
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
const int first_point_i = points.first();
|
||||
const float3 old_first_pos_cu = brush_transform_inv * positions_cu[first_point_i];
|
||||
|
||||
float2 old_first_pos_re;
|
||||
ED_view3d_project_float_v2_m4(
|
||||
ctx_.region, old_first_pos_cu, old_first_pos_re, projection.values);
|
||||
const float first_point_weight = brush_strength_ * curve_slide_info.radius_falloff;
|
||||
|
||||
/* Slide root position in region space and then project it back onto the surface. */
|
||||
const float2 new_first_pos_re = old_first_pos_re +
|
||||
first_point_weight * brush_pos_diff_re_;
|
||||
|
||||
float3 ray_start_wo, ray_end_wo;
|
||||
ED_view3d_win_to_segment_clipped(ctx_.depsgraph,
|
||||
ctx_.region,
|
||||
ctx_.v3d,
|
||||
new_first_pos_re,
|
||||
ray_start_wo,
|
||||
ray_end_wo,
|
||||
true);
|
||||
const float3 ray_start_su = transforms_.world_to_surface * ray_start_wo;
|
||||
const float3 ray_end_su = transforms_.world_to_surface * ray_end_wo;
|
||||
|
||||
const float3 ray_direction_su = math::normalize(ray_end_su - ray_start_su);
|
||||
BVHTreeRayHit hit;
|
||||
hit.dist = FLT_MAX;
|
||||
hit.index = -1;
|
||||
BLI_bvhtree_ray_cast(surface_bvh_.tree,
|
||||
ray_start_su,
|
||||
ray_direction_su,
|
||||
0.0f,
|
||||
&hit,
|
||||
surface_bvh_.raycast_callback,
|
||||
&surface_bvh_);
|
||||
if (hit.index == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int looptri_index = hit.index;
|
||||
const float3 attached_pos_su = hit.co;
|
||||
|
||||
const float3 attached_pos_cu = transforms_.surface_to_curves * attached_pos_su;
|
||||
const float3 pos_offset_cu = brush_transform * (attached_pos_cu - old_first_pos_cu);
|
||||
|
||||
/* Update positions. The first point doesn't have an additional weight here, because then
|
||||
* it wouldn't be attached to the surface anymore. */
|
||||
positions_cu[first_point_i] += pos_offset_cu;
|
||||
for (const int point_i : points.drop_front(1)) {
|
||||
const float weight = point_factors_[point_i];
|
||||
positions_cu[point_i] += weight * pos_offset_cu;
|
||||
}
|
||||
|
||||
/* Update surface attachment information if necessary. */
|
||||
if (!surface_uv_map_.is_empty()) {
|
||||
const MLoopTri &looptri = surface_looptris_[looptri_index];
|
||||
const float3 bary_coord = bke::mesh_surface_sample::compute_bary_coord_in_triangle(
|
||||
*surface_, looptri, attached_pos_su);
|
||||
const float2 &uv0 = surface_uv_map_[looptri.tri[0]];
|
||||
const float2 &uv1 = surface_uv_map_[looptri.tri[1]];
|
||||
const float2 &uv2 = surface_uv_map_[looptri.tri[2]];
|
||||
const float2 uv = attribute_math::mix3(bary_coord, uv0, uv1, uv2);
|
||||
surface_uv_coords[curve_i] = uv;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void SlideOperation::on_stroke_extended(const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
SlideOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_slide_operation()
|
||||
{
|
||||
return std::make_unique<SlideOperation>();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
|
@ -0,0 +1,259 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_brush.h"
|
||||
#include "BKE_context.h"
|
||||
|
||||
#include "ED_screen.h"
|
||||
#include "ED_view3d.h"
|
||||
|
||||
#include "DEG_depsgraph.h"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
|
||||
#include "curves_sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
class SmoothOperation : public CurvesSculptStrokeOperation {
|
||||
private:
|
||||
/** Only used when a 3D brush is used. */
|
||||
CurvesBrush3D brush_3d_;
|
||||
|
||||
friend struct SmoothOperationExecutor;
|
||||
|
||||
public:
|
||||
void on_stroke_extended(const 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 SmoothOperationExecutor {
|
||||
SmoothOperation *self_ = nullptr;
|
||||
CurvesSculptCommonContext ctx_;
|
||||
|
||||
Object *object_ = nullptr;
|
||||
Curves *curves_id_ = nullptr;
|
||||
CurvesGeometry *curves_ = nullptr;
|
||||
|
||||
VArray<float> point_factors_;
|
||||
Vector<int64_t> selected_curve_indices_;
|
||||
IndexMask curve_selection_;
|
||||
|
||||
const CurvesSculpt *curves_sculpt_ = nullptr;
|
||||
const Brush *brush_ = nullptr;
|
||||
float brush_radius_base_re_;
|
||||
float brush_radius_factor_;
|
||||
float brush_strength_;
|
||||
float2 brush_pos_re_;
|
||||
|
||||
CurvesSculptTransforms transforms_;
|
||||
|
||||
SmoothOperationExecutor(const bContext &C) : ctx_(C)
|
||||
{
|
||||
}
|
||||
|
||||
void execute(SmoothOperation &self, const bContext &C, const StrokeExtension &stroke_extension)
|
||||
{
|
||||
UNUSED_VARS(C, stroke_extension);
|
||||
self_ = &self;
|
||||
|
||||
object_ = CTX_data_active_object(&C);
|
||||
curves_id_ = static_cast<Curves *>(object_->data);
|
||||
curves_ = &CurvesGeometry::wrap(curves_id_->geometry);
|
||||
if (curves_->curves_num() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
curves_sculpt_ = ctx_.scene->toolsettings->curves_sculpt;
|
||||
brush_ = BKE_paint_brush_for_read(&curves_sculpt_->paint);
|
||||
brush_radius_base_re_ = BKE_brush_size_get(ctx_.scene, brush_);
|
||||
brush_radius_factor_ = brush_radius_factor(*brush_, stroke_extension);
|
||||
brush_strength_ = brush_strength_get(*ctx_.scene, *brush_, stroke_extension);
|
||||
brush_pos_re_ = stroke_extension.mouse_position;
|
||||
|
||||
point_factors_ = get_point_selection(*curves_id_);
|
||||
curve_selection_ = retrieve_selected_curves(*curves_id_, selected_curve_indices_);
|
||||
transforms_ = CurvesSculptTransforms(*object_, curves_id_->surface);
|
||||
|
||||
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
|
||||
brush_->falloff_shape);
|
||||
if (stroke_extension.is_first) {
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
self.brush_3d_ = *sample_curves_3d_brush(*ctx_.depsgraph,
|
||||
*ctx_.region,
|
||||
*ctx_.v3d,
|
||||
*ctx_.rv3d,
|
||||
*object_,
|
||||
brush_pos_re_,
|
||||
brush_radius_base_re_);
|
||||
}
|
||||
}
|
||||
|
||||
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
||||
this->smooth_projected_with_symmetry();
|
||||
}
|
||||
else if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
||||
this->smooth_spherical_with_symmetry();
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
curves_->tag_positions_changed();
|
||||
DEG_id_tag_update(&curves_id_->id, ID_RECALC_GEOMETRY);
|
||||
WM_main_add_notifier(NC_GEOM | ND_DATA, &curves_id_->id);
|
||||
ED_region_tag_redraw(ctx_.region);
|
||||
}
|
||||
|
||||
void smooth_projected_with_symmetry()
|
||||
{
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->smooth_projected(brush_transform);
|
||||
}
|
||||
}
|
||||
|
||||
void smooth_projected(const float4x4 &brush_transform)
|
||||
{
|
||||
const float4x4 brush_transform_inv = brush_transform.inverted();
|
||||
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
const float brush_radius_re = brush_radius_base_re_ * brush_radius_factor_;
|
||||
const float brush_radius_sq_re = pow2f(brush_radius_re);
|
||||
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
Vector<float2> old_curve_positions_re;
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
|
||||
/* Find position of control points in screen space. */
|
||||
old_curve_positions_re.clear();
|
||||
old_curve_positions_re.reserve(points.size());
|
||||
for (const int point_i : points) {
|
||||
const float3 &pos_cu = brush_transform_inv * positions_cu[point_i];
|
||||
float2 pos_re;
|
||||
ED_view3d_project_float_v2_m4(ctx_.region, pos_cu, pos_re, projection.values);
|
||||
old_curve_positions_re.append_unchecked(pos_re);
|
||||
}
|
||||
for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) {
|
||||
const int point_i = points[i];
|
||||
const float2 &old_pos_re = old_curve_positions_re[i];
|
||||
const float dist_to_brush_sq_re = math::distance_squared(old_pos_re, brush_pos_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);
|
||||
/* Used to make the brush easier to use. Otherwise a strength of 1 would be way too
|
||||
* large. */
|
||||
const float weight_factor = 0.1f;
|
||||
const float weight = weight_factor * brush_strength_ * radius_falloff *
|
||||
point_factors_[point_i];
|
||||
|
||||
/* Move points towards the middle of their neighbors. */
|
||||
const float2 &old_pos_prev_re = old_curve_positions_re[i - 1];
|
||||
const float2 &old_pos_next_re = old_curve_positions_re[i + 1];
|
||||
const float2 goal_pos_re = math::midpoint(old_pos_prev_re, old_pos_next_re);
|
||||
const float2 new_pos_re = math::interpolate(old_pos_re, goal_pos_re, weight);
|
||||
const float3 old_pos_cu = brush_transform_inv * positions_cu[point_i];
|
||||
float3 new_pos_wo;
|
||||
ED_view3d_win_to_3d(ctx_.v3d,
|
||||
ctx_.region,
|
||||
transforms_.curves_to_world * old_pos_cu,
|
||||
new_pos_re,
|
||||
new_pos_wo);
|
||||
const float3 new_pos_cu = brush_transform * (transforms_.world_to_curves * new_pos_wo);
|
||||
positions_cu[point_i] = new_pos_cu;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void smooth_spherical_with_symmetry()
|
||||
{
|
||||
float4x4 projection;
|
||||
ED_view3d_ob_project_mat_get(ctx_.rv3d, object_, projection.values);
|
||||
|
||||
float3 brush_pos_wo;
|
||||
ED_view3d_win_to_3d(ctx_.v3d,
|
||||
ctx_.region,
|
||||
transforms_.curves_to_world * self_->brush_3d_.position_cu,
|
||||
brush_pos_re_,
|
||||
brush_pos_wo);
|
||||
const float3 brush_pos_cu = transforms_.world_to_curves * brush_pos_wo;
|
||||
const float brush_radius_cu = self_->brush_3d_.radius_cu * brush_radius_factor_;
|
||||
|
||||
const Vector<float4x4> symmetry_brush_transforms = get_symmetry_brush_transforms(
|
||||
eCurvesSymmetryType(curves_id_->symmetry));
|
||||
for (const float4x4 &brush_transform : symmetry_brush_transforms) {
|
||||
this->smooth_spherical(brush_transform * brush_pos_cu, brush_radius_cu);
|
||||
}
|
||||
}
|
||||
|
||||
void smooth_spherical(const float3 &brush_pos_cu, const float brush_radius_cu)
|
||||
{
|
||||
MutableSpan<float3> positions_cu = curves_->positions_for_write();
|
||||
const float brush_radius_sq_cu = pow2f(brush_radius_cu);
|
||||
|
||||
threading::parallel_for(curve_selection_.index_range(), 256, [&](const IndexRange range) {
|
||||
Vector<float3> old_curve_positions_cu;
|
||||
for (const int curve_i : curve_selection_.slice(range)) {
|
||||
const IndexRange points = curves_->points_for_curve(curve_i);
|
||||
/* Remember original positions so that we don't smooth based on already smoothed points
|
||||
* below. */
|
||||
old_curve_positions_cu.clear();
|
||||
old_curve_positions_cu.extend(positions_cu.slice(points));
|
||||
for (const int i : IndexRange(points.size()).drop_front(1).drop_back(1)) {
|
||||
const int point_i = points[i];
|
||||
const float3 &old_pos_cu = old_curve_positions_cu[i];
|
||||
const float dist_to_brush_sq_cu = math::distance_squared(old_pos_cu, brush_pos_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);
|
||||
/* Used to make the brush easier to use. Otherwise a strength of 1 would be way too
|
||||
* large. */
|
||||
const float weight_factor = 0.1f;
|
||||
const float weight = weight_factor * brush_strength_ * radius_falloff *
|
||||
point_factors_[point_i];
|
||||
|
||||
/* Move points towards the middle of their neighbors. */
|
||||
const float3 &old_pos_prev_cu = old_curve_positions_cu[i - 1];
|
||||
const float3 &old_pos_next_cu = old_curve_positions_cu[i + 1];
|
||||
const float3 goal_pos_cu = math::midpoint(old_pos_prev_cu, old_pos_next_cu);
|
||||
const float3 new_pos_cu = math::interpolate(old_pos_cu, goal_pos_cu, weight);
|
||||
positions_cu[point_i] = new_pos_cu;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void SmoothOperation::on_stroke_extended(const bContext &C,
|
||||
const StrokeExtension &stroke_extension)
|
||||
{
|
||||
SmoothOperationExecutor executor{C};
|
||||
executor.execute(*this, C, stroke_extension);
|
||||
}
|
||||
|
||||
std::unique_ptr<CurvesSculptStrokeOperation> new_smooth_operation()
|
||||
{
|
||||
return std::make_unique<SmoothOperation>();
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
|
@ -995,7 +995,7 @@ static void stroke_done(bContext *C, wmOperator *op, PaintStroke *stroke)
|
|||
|
||||
static bool curves_sculpt_brush_uses_spacing(const eBrushCurvesSculptTool tool)
|
||||
{
|
||||
return ELEM(tool, CURVES_SCULPT_TOOL_ADD);
|
||||
return ELEM(tool, CURVES_SCULPT_TOOL_ADD, CURVES_SCULPT_TOOL_DENSITY);
|
||||
}
|
||||
|
||||
bool paint_space_stroke_enabled(Brush *br, ePaintMode mode)
|
||||
|
|
|
@ -469,6 +469,11 @@ typedef enum eBrushCurvesSculptTool {
|
|||
CURVES_SCULPT_TOOL_ADD = 3,
|
||||
CURVES_SCULPT_TOOL_GROW_SHRINK = 4,
|
||||
CURVES_SCULPT_TOOL_SELECTION_PAINT = 5,
|
||||
CURVES_SCULPT_TOOL_PINCH = 6,
|
||||
CURVES_SCULPT_TOOL_SMOOTH = 7,
|
||||
CURVES_SCULPT_TOOL_PUFF = 8,
|
||||
CURVES_SCULPT_TOOL_DENSITY = 9,
|
||||
CURVES_SCULPT_TOOL_SLIDE = 10,
|
||||
} eBrushCurvesSculptTool;
|
||||
|
||||
/** When #BRUSH_ACCUMULATE is used */
|
||||
|
@ -622,6 +627,12 @@ typedef enum eBrushCurvesSculptFlag {
|
|||
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT = (1 << 4),
|
||||
} eBrushCurvesSculptFlag;
|
||||
|
||||
typedef enum eBrushCurvesSculptDensityMode {
|
||||
BRUSH_CURVES_SCULPT_DENSITY_MODE_AUTO = 0,
|
||||
BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD = 1,
|
||||
BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE = 2,
|
||||
} eBrushCurvesSculptDensityMode;
|
||||
|
||||
#define MAX_BRUSH_PIXEL_RADIUS 500
|
||||
#define GP_MAX_BRUSH_PIXEL_RADIUS 1000
|
||||
|
||||
|
|
|
@ -148,6 +148,13 @@ typedef struct BrushCurvesSculptSettings {
|
|||
float minimum_length;
|
||||
/** Length of newly added curves when it is not interpolated from other curves. */
|
||||
float curve_length;
|
||||
/** Minimum distance between curve root points used by the Density brush. */
|
||||
float minimum_distance;
|
||||
/** How often the Density brush tries to add a new curve. */
|
||||
int density_add_attempts;
|
||||
/** #eBrushCurvesSculptDensityMode. */
|
||||
uint8_t density_mode;
|
||||
char _pad[7];
|
||||
} BrushCurvesSculptSettings;
|
||||
|
||||
typedef struct Brush {
|
||||
|
|
|
@ -251,6 +251,11 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = {
|
|||
{CURVES_SCULPT_TOOL_ADD, "ADD", ICON_BRUSH_CURVES_ADD, "Add Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_BRUSH_CURVES_GROW_SHRINK, "Grow / Shrink Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_SELECTION_PAINT, "SELECTION_PAINT", ICON_BRUSH_PAINT_SELECT, "Paint Selection", ""},
|
||||
{CURVES_SCULPT_TOOL_PINCH, "PINCH", ICON_BRUSH_CURVES_PINCH, "Pinch Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_SMOOTH, "SMOOTH", ICON_BRUSH_CURVES_SMOOTH, "Smooth Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_PUFF, "PUFF", ICON_BRUSH_CURVES_PUFF, "Puff Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_DENSITY, "DENSITY", ICON_BRUSH_CURVES_DENSITY, "Density Curves", ""},
|
||||
{CURVES_SCULPT_TOOL_SLIDE, "SLIDE", ICON_BRUSH_CURVES_SLIDE, "Slide Curves", ""},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
/* clang-format on */
|
||||
|
@ -889,6 +894,7 @@ static const EnumPropertyItem *rna_Brush_direction_itemf(bContext *C,
|
|||
switch (me->curves_sculpt_tool) {
|
||||
case CURVES_SCULPT_TOOL_GROW_SHRINK:
|
||||
case CURVES_SCULPT_TOOL_SELECTION_PAINT:
|
||||
case CURVES_SCULPT_TOOL_PINCH:
|
||||
return prop_direction_items;
|
||||
default:
|
||||
return DummyRNA_DEFAULT_items;
|
||||
|
@ -1949,6 +1955,26 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
|
|||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
static const EnumPropertyItem density_mode_items[] = {
|
||||
{BRUSH_CURVES_SCULPT_DENSITY_MODE_AUTO,
|
||||
"AUTO",
|
||||
ICON_AUTO,
|
||||
"Auto",
|
||||
"Either add or remove curves depending on the minimum distance of the curves under the "
|
||||
"cursor"},
|
||||
{BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD,
|
||||
"ADD",
|
||||
ICON_ADD,
|
||||
"Add",
|
||||
"Add new curves between existing curves, taking the minimum distance into account"},
|
||||
{BRUSH_CURVES_SCULPT_DENSITY_MODE_REMOVE,
|
||||
"REMOVE",
|
||||
ICON_REMOVE,
|
||||
"Remove",
|
||||
"Remove curves whose root points are too close"},
|
||||
{0, NULL, 0, NULL, NULL},
|
||||
};
|
||||
|
||||
srna = RNA_def_struct(brna, "BrushCurvesSculptSettings", NULL);
|
||||
RNA_def_struct_sdna(srna, "BrushCurvesSculptSettings");
|
||||
RNA_def_struct_ui_text(srna, "Curves Sculpt Brush Settings", "");
|
||||
|
@ -1997,6 +2023,22 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
|
|||
prop,
|
||||
"Curve Length",
|
||||
"Length of newly added curves when it is not interpolated from other curves");
|
||||
|
||||
prop = RNA_def_property(srna, "minimum_distance", PROP_FLOAT, PROP_DISTANCE);
|
||||
RNA_def_property_range(prop, 0.0f, FLT_MAX);
|
||||
RNA_def_property_ui_range(prop, 0.0, 1000.0f, 0.001, 2);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Minimum Distance", "Goal distance between curve roots for the Density brush");
|
||||
|
||||
prop = RNA_def_property(srna, "density_add_attempts", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_range(prop, 0, INT32_MAX);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Density Add Attempts", "How many times the Density brush tries to add a new curve");
|
||||
|
||||
prop = RNA_def_property(srna, "density_mode", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, density_mode_items);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Density Mode", "Determines whether the brush adds or removes curves");
|
||||
}
|
||||
|
||||
static void rna_def_brush(BlenderRNA *brna)
|
||||
|
|
Loading…
Reference in New Issue