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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/sculpt_paint')
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c41
-rw-r--r--source/blender/editors/sculpt_paint/paint_image.c4
-rw-r--r--source/blender/editors/sculpt_paint/paint_image_proj.c27
-rw-r--r--source/blender/editors/sculpt_paint/paint_mask.c604
-rw-r--r--source/blender/editors/sculpt_paint/paint_stroke.c26
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c68
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_boundary.c419
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_cloth.c372
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_face_set.c51
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_color.c2
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_filter_mesh.c280
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h75
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_pose.c5
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_smooth.c125
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_transform.c8
15 files changed, 1347 insertions, 760 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index 88998d5063d..ee514fa745c 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -566,7 +566,7 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
if (load_tex(brush, vc, zoom, col, primary)) {
GPU_color_mask(true, true, true, true);
- GPU_depth_test(false);
+ GPU_depth_test(GPU_DEPTH_NONE);
if (mtex->brush_map_mode == MTEX_MAP_MODE_VIEW) {
GPU_matrix_push();
@@ -634,8 +634,7 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
uint texCoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
/* Premultiplied alpha blending. */
- GPU_blend_set_func(GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA_PREMULT);
immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR);
@@ -670,8 +669,6 @@ static bool paint_draw_tex_overlay(UnifiedPaintSettings *ups,
GPU_texture_unbind(texture);
- GPU_blend_set_func(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA);
-
if (ELEM(mtex->brush_map_mode, MTEX_MAP_MODE_STENCIL, MTEX_MAP_MODE_VIEW)) {
GPU_matrix_pop();
}
@@ -696,7 +693,7 @@ static bool paint_draw_cursor_overlay(
float center[2];
GPU_color_mask(true, true, true, true);
- GPU_depth_test(false);
+ GPU_depth_test(GPU_DEPTH_NONE);
if (ups->draw_anchored) {
copy_v2_v2(center, ups->anchored_initial_mouse);
@@ -729,8 +726,7 @@ static bool paint_draw_cursor_overlay(
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
uint texCoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- GPU_blend_set_func(GPU_ONE, GPU_ONE_MINUS_SRC_ALPHA);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA_PREMULT);
immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_COLOR);
@@ -757,8 +753,6 @@ static bool paint_draw_cursor_overlay(
immUnbindProgram();
- GPU_blend_set_func(GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA);
-
if (do_pop) {
GPU_matrix_pop();
}
@@ -781,7 +775,8 @@ static bool paint_draw_alpha_overlay(UnifiedPaintSettings *ups,
bool alpha_overlay_active = false;
ePaintOverlayControlFlags flags = BKE_paint_get_overlay_flags();
- gpuPushAttr(GPU_DEPTH_BUFFER_BIT | GPU_BLEND_BIT);
+ eGPUBlend blend_state = GPU_blend_get();
+ eGPUDepthTest depth_test = GPU_depth_test_get();
/* Translate to region. */
GPU_matrix_push();
@@ -811,7 +806,8 @@ static bool paint_draw_alpha_overlay(UnifiedPaintSettings *ups,
}
GPU_matrix_pop();
- gpuPopAttr();
+ GPU_blend(blend_state);
+ GPU_depth_test(depth_test);
return alpha_overlay_active;
}
@@ -922,7 +918,7 @@ static void paint_draw_curve_cursor(Brush *brush, ViewContext *vc)
PaintCurvePoint *cp = pc->points;
GPU_line_smooth(true);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA);
/* Draw the bezier handles and the curve segment between the current and next point. */
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
@@ -983,7 +979,7 @@ static void paint_draw_curve_cursor(Brush *brush, ViewContext *vc)
draw_rect_point(
pos, selec_col, handle_col, &cp->bez.vec[2][0], 8.0f, cp->bez.f3 || cp->bez.f2);
- GPU_blend(false);
+ GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
immUnbindProgram();
@@ -1151,9 +1147,9 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr,
immUniformColor4f(1.0f, 1.0f, 1.0f, 0.6f);
/* Cursor normally draws on top, but for this part we need depth tests. */
- const bool depth_test = GPU_depth_test_enabled();
+ const eGPUDepthTest depth_test = GPU_depth_test_get();
if (!depth_test) {
- GPU_depth_test(true);
+ GPU_depth_test(GPU_DEPTH_LESS_EQUAL);
}
GPU_line_width(1.0f);
@@ -1167,7 +1163,7 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr,
/* Restore depth test value. */
if (!depth_test) {
- GPU_depth_test(false);
+ GPU_depth_test(GPU_DEPTH_NONE);
}
}
@@ -1542,7 +1538,7 @@ static void paint_cursor_preview_boundary_data_update(PaintCursorContext *pconte
}
ss->boundary_preview = SCULPT_boundary_data_init(
- pcontext->vc.obact, ss->active_vertex_index, pcontext->radius);
+ pcontext->vc.obact, pcontext->brush, ss->active_vertex_index, pcontext->radius);
}
static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *pcontext)
@@ -1762,9 +1758,6 @@ static void paint_cursor_cursor_draw_3d_view_brush_cursor_active(PaintCursorCont
GPU_matrix_pop();
- /* This Cloth brush cursor overlay always works in cursor space. */
- paint_cursor_drawing_setup_cursor_space(pcontext);
-
GPU_matrix_pop_projection();
wmWindowViewport(pcontext->win);
}
@@ -1842,7 +1835,7 @@ static void paint_cursor_update_anchored_location(PaintCursorContext *pcontext)
static void paint_cursor_setup_2D_drawing(PaintCursorContext *pcontext)
{
GPU_line_width(2.0f);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA);
GPU_line_smooth(true);
pcontext->pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
@@ -1852,7 +1845,7 @@ static void paint_cursor_setup_2D_drawing(PaintCursorContext *pcontext)
static void paint_cursor_setup_3D_drawing(PaintCursorContext *pcontext)
{
GPU_line_width(2.0f);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA);
GPU_line_smooth(true);
pcontext->pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
@@ -1862,7 +1855,7 @@ static void paint_cursor_setup_3D_drawing(PaintCursorContext *pcontext)
static void paint_cursor_restore_drawing_state(void)
{
immUnbindProgram();
- GPU_blend(false);
+ GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
diff --git a/source/blender/editors/sculpt_paint/paint_image.c b/source/blender/editors/sculpt_paint/paint_image.c
index 431ab998f62..d2ae6912fc3 100644
--- a/source/blender/editors/sculpt_paint/paint_image.c
+++ b/source/blender/editors/sculpt_paint/paint_image.c
@@ -438,7 +438,7 @@ static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customda
if (pop) {
GPU_line_smooth(true);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA);
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
@@ -467,7 +467,7 @@ static void gradient_draw_line(bContext *UNUSED(C), int x, int y, void *customda
immUnbindProgram();
- GPU_blend(false);
+ GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
}
diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c
index db7de01bee5..456c1f61cb1 100644
--- a/source/blender/editors/sculpt_paint/paint_image_proj.c
+++ b/source/blender/editors/sculpt_paint/paint_image_proj.c
@@ -407,7 +407,6 @@ typedef struct ProjPaintState {
SpinLock *tile_lock;
Mesh *me_eval;
- bool me_eval_free;
int totlooptri_eval;
int totloop_eval;
int totpoly_eval;
@@ -4033,27 +4032,14 @@ static bool proj_paint_state_mesh_eval_init(const bContext *C, ProjPaintState *p
CustomData_MeshMasks cddata_masks = scene_eval->customdata_mask;
cddata_masks.fmask |= CD_MASK_MTFACE;
cddata_masks.lmask |= CD_MASK_MLOOPUV;
-
- /* Workaround for subsurf selection, try the display mesh first */
- if (ps->source == PROJ_SRC_IMAGE_CAM) {
- /* using render mesh, assume only camera was rendered from */
- ps->me_eval = mesh_create_eval_final_render(depsgraph, scene_eval, ob_eval, &cddata_masks);
- ps->me_eval_free = true;
- }
- else {
- if (ps->do_face_sel) {
- cddata_masks.vmask |= CD_MASK_ORIGINDEX;
- cddata_masks.emask |= CD_MASK_ORIGINDEX;
- cddata_masks.pmask |= CD_MASK_ORIGINDEX;
- }
- ps->me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &cddata_masks);
- ps->me_eval_free = false;
+ if (ps->do_face_sel) {
+ cddata_masks.vmask |= CD_MASK_ORIGINDEX;
+ cddata_masks.emask |= CD_MASK_ORIGINDEX;
+ cddata_masks.pmask |= CD_MASK_ORIGINDEX;
}
+ ps->me_eval = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &cddata_masks);
if (!CustomData_has_layer(&ps->me_eval->ldata, CD_MLOOPUV)) {
- if (ps->me_eval_free) {
- BKE_id_free(NULL, ps->me_eval);
- }
ps->me_eval = NULL;
return false;
}
@@ -4636,9 +4622,6 @@ static void project_paint_end(ProjPaintState *ps)
MEM_freeN(ps->cavities);
}
- if (ps->me_eval_free) {
- BKE_id_free(NULL, ps->me_eval);
- }
ps->me_eval = NULL;
}
diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c
index 05ffb80d8a1..ab8b81a8155 100644
--- a/source/blender/editors/sculpt_paint/paint_mask.c
+++ b/source/blender/editors/sculpt_paint/paint_mask.c
@@ -31,6 +31,7 @@
#include "BLI_lasso_2d.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
+#include "BLI_rect.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
@@ -221,392 +222,383 @@ void PAINT_OT_mask_flood_fill(struct wmOperatorType *ot)
1.0f);
}
-/* Box select, operator is VIEW3D_OT_select_box, defined in view3d_select.c. */
+/* Sculpt Gesture Operators. */
-static bool is_effected(float planes[4][4], const float co[3])
-{
- return isect_point_planes_v3(planes, 4, co);
-}
+typedef enum eSculptGestureShapeType {
+ SCULPT_GESTURE_SHAPE_BOX,
+ SCULPT_GESTURE_SHAPE_LASSO,
+} eMaskGesturesShapeType;
-static void flip_plane(float out[4], const float in[4], const char symm)
-{
- if (symm & PAINT_SYMM_X) {
- out[0] = -in[0];
- }
- else {
- out[0] = in[0];
- }
- if (symm & PAINT_SYMM_Y) {
- out[1] = -in[1];
- }
- else {
- out[1] = in[1];
- }
- if (symm & PAINT_SYMM_Z) {
- out[2] = -in[2];
- }
- else {
- out[2] = in[2];
- }
+typedef struct LassoGestureData {
+ float projviewobjmat[4][4];
- out[3] = in[3];
-}
+ rcti boundbox;
+ int width;
-static void mask_box_select_task_cb(void *__restrict userdata,
- const int i,
- const TaskParallelTLS *__restrict UNUSED(tls))
-{
- MaskTaskData *data = userdata;
+ /* 2D bitmap to test if a vertex is affected by the lasso shape. */
+ BLI_bitmap *mask_px;
+} LassoGestureData;
- PBVHNode *node = data->nodes[i];
+typedef struct SculptGestureContext {
+ SculptSession *ss;
+ ViewContext vc;
- const PaintMaskFloodMode mode = data->mode;
- const float value = data->value;
- float(*clip_planes_final)[4] = data->clip_planes_final;
+ /* Enabled and currently active symmetry. */
+ ePaintSymmetryFlags symm;
+ ePaintSymmetryFlags symmpass;
- PBVHVertexIter vi;
- bool any_masked = false;
- bool redraw = false;
+ /* Operation parameters. */
+ eMaskGesturesShapeType shape_type;
+ bool front_faces_only;
- float vertex_normal[3];
+ /* Mask operation parameters. */
+ PaintMaskFloodMode mask_mode;
+ float mask_value;
- BKE_pbvh_vertex_iter_begin(data->pbvh, node, vi, PBVH_ITER_UNIQUE)
- {
- SCULPT_vertex_normal_get(data->ob->sculpt, vi.index, vertex_normal);
- float dot = dot_v3v3(data->view_normal, vertex_normal);
- const bool is_effected_front_face = !(data->front_faces_only && dot < 0.0f);
+ /* View parameters. */
+ float true_view_normal[3];
+ float view_normal[3];
- if (is_effected_front_face && is_effected(clip_planes_final, vi.co)) {
- float prevmask = *vi.mask;
- if (!any_masked) {
- any_masked = true;
+ float true_clip_planes[4][4];
+ float clip_planes[4][4];
- SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_MASK);
+ /* Lasso Gesture. */
+ LassoGestureData lasso;
- if (data->multires) {
- BKE_pbvh_node_mark_normals_update(node);
- }
- }
- mask_flood_fill_set_elem(vi.mask, mode, value);
- if (prevmask != *vi.mask) {
- redraw = true;
- }
- }
- }
- BKE_pbvh_vertex_iter_end;
+ /* Task Callback Data. */
+ PBVHNode **nodes;
+ int totnode;
+} SculptGestureContext;
- if (redraw) {
- BKE_pbvh_node_mark_update_mask(node);
- }
+static void sculpt_gesture_operator_properties(wmOperatorType *ot)
+{
+ RNA_def_boolean(ot->srna,
+ "use_front_faces_only",
+ false,
+ "Front Faces Only",
+ "Affect only faces facing towards the view");
}
-static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op)
+static void sculpt_gesture_context_init_common(bContext *C,
+ wmOperator *op,
+ SculptGestureContext *sgcontext)
{
- ViewContext vc;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
- ED_view3d_viewcontext_init(C, &vc, depsgraph);
-
- Sculpt *sd = vc.scene->toolsettings->sculpt;
- BoundBox bb;
- float clip_planes[4][4];
- float clip_planes_final[4][4];
- ARegion *region = vc.region;
- Object *ob = vc.obact;
- bool multires;
- PBVH *pbvh;
- PBVHNode **nodes;
- int totnode;
- int symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
-
- const PaintMaskFloodMode mode = RNA_enum_get(op->ptr, "mode");
- const float value = RNA_float_get(op->ptr, "value");
- const bool front_faces_only = RNA_boolean_get(op->ptr, "use_front_faces_only");
+ ED_view3d_viewcontext_init(C, &sgcontext->vc, depsgraph);
- rcti rect;
- WM_operator_properties_border_to_rcti(op, &rect);
+ Sculpt *sd = sgcontext->vc.scene->toolsettings->sculpt;
+ Object *ob = sgcontext->vc.obact;
- /* Transform the clip planes in object space. */
- ED_view3d_clipping_calc(&bb, clip_planes, vc.region, vc.obact, &rect);
+ /* Operator properties. */
+ sgcontext->front_faces_only = RNA_boolean_get(op->ptr, "use_front_faces_only");
- BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false);
- pbvh = ob->sculpt->pbvh;
- multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
+ /* SculptSession */
+ sgcontext->ss = ob->sculpt;
- SCULPT_undo_push_begin("Mask box fill");
+ /* Symmetry. */
+ sgcontext->symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
- /* Calculate the view normal in object space. */
+ /* View Normal. */
float mat[3][3];
float view_dir[3] = {0.0f, 0.0f, 1.0f};
- float true_view_normal[3];
- copy_m3_m4(mat, vc.rv3d->viewinv);
+ copy_m3_m4(mat, sgcontext->vc.rv3d->viewinv);
mul_m3_v3(mat, view_dir);
copy_m3_m4(mat, ob->imat);
mul_m3_v3(mat, view_dir);
- normalize_v3_v3(true_view_normal, view_dir);
+ normalize_v3_v3(sgcontext->true_view_normal, view_dir);
+}
- for (int symmpass = 0; symmpass <= symm; symmpass++) {
- if (symmpass == 0 || (symm & symmpass && (symm != 5 || symmpass != 3) &&
- (symm != 6 || (symmpass != 3 && symmpass != 5)))) {
+static void sculpt_gesture_lasso_px_cb(int x, int x_end, int y, void *user_data)
+{
+ SculptGestureContext *mcontext = user_data;
+ LassoGestureData *lasso = &mcontext->lasso;
+ int index = (y * lasso->width) + x;
+ int index_end = (y * lasso->width) + x_end;
+ do {
+ BLI_BITMAP_ENABLE(lasso->mask_px, index);
+ } while (++index != index_end);
+}
- /* Flip the planes symmetrically as needed. */
- for (int j = 0; j < 4; j++) {
- flip_plane(clip_planes_final[j], clip_planes[j], symmpass);
- }
+static SculptGestureContext *sculpt_gesture_init_from_lasso(bContext *C, wmOperator *op)
+{
+ SculptGestureContext *sgcontext = MEM_callocN(sizeof(SculptGestureContext),
+ "sculpt gesture context lasso");
+ sgcontext->shape_type = SCULPT_GESTURE_SHAPE_LASSO;
+
+ sculpt_gesture_context_init_common(C, op, sgcontext);
- PBVHFrustumPlanes frustum = {.planes = clip_planes_final, .num_planes = 4};
- BKE_pbvh_search_gather(pbvh, BKE_pbvh_node_frustum_contain_AABB, &frustum, &nodes, &totnode);
+ int mcoords_len;
+ const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len);
- negate_m4(clip_planes_final);
+ if (!mcoords) {
+ return NULL;
+ }
- MaskTaskData data = {
- .ob = ob,
- .pbvh = pbvh,
- .nodes = nodes,
- .multires = multires,
- .mode = mode,
- .value = value,
- .clip_planes_final = clip_planes_final,
- .front_faces_only = front_faces_only,
- };
+ ED_view3d_ob_project_mat_get(
+ sgcontext->vc.rv3d, sgcontext->vc.obact, sgcontext->lasso.projviewobjmat);
+ BLI_lasso_boundbox(&sgcontext->lasso.boundbox, mcoords, mcoords_len);
+ sgcontext->lasso.width = sgcontext->lasso.boundbox.xmax - sgcontext->lasso.boundbox.xmin;
+ sgcontext->lasso.mask_px = BLI_BITMAP_NEW(
+ sgcontext->lasso.width * (sgcontext->lasso.boundbox.ymax - sgcontext->lasso.boundbox.ymin),
+ __func__);
+
+ BLI_bitmap_draw_2d_poly_v2i_n(sgcontext->lasso.boundbox.xmin,
+ sgcontext->lasso.boundbox.ymin,
+ sgcontext->lasso.boundbox.xmax,
+ sgcontext->lasso.boundbox.ymax,
+ mcoords,
+ mcoords_len,
+ sculpt_gesture_lasso_px_cb,
+ sgcontext);
- flip_v3_v3(data.view_normal, true_view_normal, symmpass);
+ BoundBox bb;
+ ED_view3d_clipping_calc(&bb,
+ sgcontext->true_clip_planes,
+ sgcontext->vc.region,
+ sgcontext->vc.obact,
+ &sgcontext->lasso.boundbox);
+ MEM_freeN((void *)mcoords);
+
+ return sgcontext;
+}
- TaskParallelSettings settings;
- BKE_pbvh_parallel_range_settings(&settings, true, totnode);
- BLI_task_parallel_range(0, totnode, &data, mask_box_select_task_cb, &settings);
+static SculptGestureContext *sculpt_gesture_init_from_box(bContext *C, wmOperator *op)
+{
+ SculptGestureContext *sgcontext = MEM_callocN(sizeof(SculptGestureContext),
+ "sculpt gesture context box");
+ sgcontext->shape_type = SCULPT_GESTURE_SHAPE_BOX;
- if (nodes) {
- MEM_freeN(nodes);
- }
- }
- }
+ sculpt_gesture_context_init_common(C, op, sgcontext);
- if (multires) {
- multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED);
- }
+ rcti rect;
+ WM_operator_properties_border_to_rcti(op, &rect);
- BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask);
+ BoundBox bb;
+ ED_view3d_clipping_calc(
+ &bb, sgcontext->true_clip_planes, sgcontext->vc.region, sgcontext->vc.obact, &rect);
- SCULPT_undo_push_end();
+ return sgcontext;
+}
- ED_region_tag_redraw(region);
+static void sculpt_gesture_context_free(SculptGestureContext *sgcontext)
+{
+ MEM_SAFE_FREE(sgcontext->lasso.mask_px);
+ MEM_SAFE_FREE(sgcontext->nodes);
+ MEM_SAFE_FREE(sgcontext);
+}
- WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+static void flip_plane(float out[4], const float in[4], const char symm)
+{
+ if (symm & PAINT_SYMM_X) {
+ out[0] = -in[0];
+ }
+ else {
+ out[0] = in[0];
+ }
+ if (symm & PAINT_SYMM_Y) {
+ out[1] = -in[1];
+ }
+ else {
+ out[1] = in[1];
+ }
+ if (symm & PAINT_SYMM_Z) {
+ out[2] = -in[2];
+ }
+ else {
+ out[2] = in[2];
+ }
- return true;
+ out[3] = in[3];
}
-typedef struct LassoMaskData {
- struct ViewContext *vc;
- float projviewobjmat[4][4];
- BLI_bitmap *px;
- int width;
- /* Bounding box for scanfilling. */
- rcti rect;
- int symmpass;
+static void sculpt_gesture_flip_for_symmetry_pass(SculptGestureContext *sgcontext,
+ const ePaintSymmetryFlags symmpass)
+{
+ sgcontext->symmpass = symmpass;
+ for (int j = 0; j < 4; j++) {
+ flip_plane(sgcontext->clip_planes[j], sgcontext->true_clip_planes[j], symmpass);
+ }
+ negate_m4(sgcontext->clip_planes);
+ flip_v3_v3(sgcontext->view_normal, sgcontext->true_view_normal, symmpass);
+}
- MaskTaskData task_data;
-} LassoMaskData;
+static void sculpt_gesture_update_effected_nodes(SculptGestureContext *sgcontext)
+{
+ SculptSession *ss = sgcontext->ss;
+ float clip_planes[4][4];
+ copy_m4_m4(clip_planes, sgcontext->clip_planes);
+ negate_m4(clip_planes);
+ PBVHFrustumPlanes frustum = {.planes = clip_planes, .num_planes = 4};
+ BKE_pbvh_search_gather(ss->pbvh,
+ BKE_pbvh_node_frustum_contain_AABB,
+ &frustum,
+ &sgcontext->nodes,
+ &sgcontext->totnode);
+}
-/**
- * Lasso select. This could be defined as part of #VIEW3D_OT_select_lasso,
- * still the shortcuts conflict, so we will use a separate operator.
- */
-static bool is_effected_lasso(LassoMaskData *data, const float co[3])
+static bool sculpt_gesture_is_effected_lasso(SculptGestureContext *sgcontext, const float co[3])
{
float scr_co_f[2];
int scr_co_s[2];
float co_final[3];
- flip_v3_v3(co_final, co, data->symmpass);
+ flip_v3_v3(co_final, co, sgcontext->symmpass);
+
/* First project point to 2d space. */
- ED_view3d_project_float_v2_m4(data->vc->region, co_final, scr_co_f, data->projviewobjmat);
+ ED_view3d_project_float_v2_m4(
+ sgcontext->vc.region, co_final, scr_co_f, sgcontext->lasso.projviewobjmat);
scr_co_s[0] = scr_co_f[0];
scr_co_s[1] = scr_co_f[1];
- /* Clip against screen, because lasso is limited to screen only. */
- if ((scr_co_s[0] < data->rect.xmin) || (scr_co_s[1] < data->rect.ymin) ||
- (scr_co_s[0] >= data->rect.xmax) || (scr_co_s[1] >= data->rect.ymax)) {
+ /* Clip against lasso boundbox. */
+ LassoGestureData *lasso = &sgcontext->lasso;
+ if (!BLI_rcti_isect_pt(&lasso->boundbox, scr_co_s[0], scr_co_s[1])) {
return false;
}
- scr_co_s[0] -= data->rect.xmin;
- scr_co_s[1] -= data->rect.ymin;
+ scr_co_s[0] -= lasso->boundbox.xmin;
+ scr_co_s[1] -= lasso->boundbox.ymin;
- return BLI_BITMAP_TEST_BOOL(data->px, scr_co_s[1] * data->width + scr_co_s[0]);
+ return BLI_BITMAP_TEST_BOOL(lasso->mask_px, scr_co_s[1] * lasso->width + scr_co_s[0]);
}
-static void mask_lasso_px_cb(int x, int x_end, int y, void *user_data)
+static bool sculpt_gesture_is_vertex_effected(SculptGestureContext *sgcontext, PBVHVertexIter *vd)
{
- LassoMaskData *data = user_data;
- int index = (y * data->width) + x;
- int index_end = (y * data->width) + x_end;
- do {
- BLI_BITMAP_ENABLE(data->px, index);
- } while (++index != index_end);
+ float vertex_normal[3];
+ SCULPT_vertex_normal_get(sgcontext->ss, vd->index, vertex_normal);
+ float dot = dot_v3v3(sgcontext->view_normal, vertex_normal);
+ const bool is_effected_front_face = !(sgcontext->front_faces_only && dot < 0.0f);
+
+ if (!is_effected_front_face) {
+ return false;
+ }
+
+ switch (sgcontext->shape_type) {
+ case SCULPT_GESTURE_SHAPE_BOX:
+ return isect_point_planes_v3(sgcontext->clip_planes, 4, vd->co);
+ case SCULPT_GESTURE_SHAPE_LASSO:
+ return sculpt_gesture_is_effected_lasso(sgcontext, vd->co);
+ }
+ return false;
}
-static void mask_gesture_lasso_task_cb(void *__restrict userdata,
+static void mask_gesture_apply_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
{
- LassoMaskData *lasso_data = userdata;
- MaskTaskData *data = &lasso_data->task_data;
-
- PBVHNode *node = data->nodes[i];
+ SculptGestureContext *sgcontext = userdata;
+ Object *ob = sgcontext->vc.obact;
+ PBVHNode *node = sgcontext->nodes[i];
- const PaintMaskFloodMode mode = data->mode;
- const float value = data->value;
+ const bool is_multires = BKE_pbvh_type(sgcontext->ss->pbvh) == PBVH_GRIDS;
- PBVHVertexIter vi;
+ PBVHVertexIter vd;
bool any_masked = false;
+ bool redraw = false;
- float vertex_normal[3];
-
- BKE_pbvh_vertex_iter_begin(data->pbvh, node, vi, PBVH_ITER_UNIQUE)
+ BKE_pbvh_vertex_iter_begin(sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
{
- SCULPT_vertex_normal_get(data->ob->sculpt, vi.index, vertex_normal);
- float dot = dot_v3v3(lasso_data->task_data.view_normal, vertex_normal);
- const bool is_effected_front_face = !(data->front_faces_only && dot < 0.0f);
-
- if (is_effected_front_face && is_effected_lasso(lasso_data, vi.co)) {
+ if (sculpt_gesture_is_vertex_effected(sgcontext, &vd)) {
+ float prevmask = *vd.mask;
if (!any_masked) {
any_masked = true;
- SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_MASK);
+ SCULPT_undo_push_node(ob, node, SCULPT_UNDO_MASK);
- BKE_pbvh_node_mark_redraw(node);
- if (data->multires) {
+ if (is_multires) {
BKE_pbvh_node_mark_normals_update(node);
}
}
-
- mask_flood_fill_set_elem(vi.mask, mode, value);
+ mask_flood_fill_set_elem(vd.mask, sgcontext->mask_mode, sgcontext->mask_value);
+ if (prevmask != *vd.mask) {
+ redraw = true;
+ }
}
}
BKE_pbvh_vertex_iter_end;
+
+ if (redraw) {
+ BKE_pbvh_node_mark_update_mask(node);
+ }
}
-static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op)
+static void sculpt_gesture_apply(bContext *C, SculptGestureContext *mcontext)
{
- int mcoords_len;
- const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len);
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ BKE_sculpt_update_object_for_edit(depsgraph, mcontext->vc.obact, false, true, false);
- if (mcoords) {
- Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
- float clip_planes[4][4], clip_planes_final[4][4];
- BoundBox bb;
- Object *ob;
- ViewContext vc;
- LassoMaskData data;
- Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
- int symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
- PBVH *pbvh;
- PBVHNode **nodes;
- int totnode;
- bool multires;
- PaintMaskFloodMode mode = RNA_enum_get(op->ptr, "mode");
- float value = RNA_float_get(op->ptr, "value");
- const bool front_faces_only = RNA_boolean_get(op->ptr, "use_front_faces_only");
-
- /* Calculations of individual vertices are done in 2D screen space to diminish the amount of
- * calculations done. Bounding box PBVH collision is not computed against enclosing rectangle
- * of lasso. */
- ED_view3d_viewcontext_init(C, &vc, depsgraph);
-
- /* Lasso data calculations. */
- data.vc = &vc;
- ob = vc.obact;
- ED_view3d_ob_project_mat_get(vc.rv3d, ob, data.projviewobjmat);
-
- BLI_lasso_boundbox(&data.rect, mcoords, mcoords_len);
- data.width = data.rect.xmax - data.rect.xmin;
- data.px = BLI_BITMAP_NEW(data.width * (data.rect.ymax - data.rect.ymin), __func__);
-
- BLI_bitmap_draw_2d_poly_v2i_n(data.rect.xmin,
- data.rect.ymin,
- data.rect.xmax,
- data.rect.ymax,
- mcoords,
- mcoords_len,
- mask_lasso_px_cb,
- &data);
-
- ED_view3d_clipping_calc(&bb, clip_planes, vc.region, vc.obact, &data.rect);
-
- BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false);
- pbvh = ob->sculpt->pbvh;
- multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
-
- SCULPT_undo_push_begin("Mask lasso fill");
-
- /* Calculate the view normal in object space. */
- float mat[3][3];
- float view_dir[3] = {0.0f, 0.0f, 1.0f};
- float true_view_normal[3];
- copy_m3_m4(mat, vc.rv3d->viewinv);
- mul_m3_v3(mat, view_dir);
- copy_m3_m4(mat, ob->imat);
- mul_m3_v3(mat, view_dir);
- normalize_v3_v3(true_view_normal, view_dir);
-
- for (int symmpass = 0; symmpass <= symm; symmpass++) {
- if ((symmpass == 0) || (symm & symmpass && (symm != 5 || symmpass != 3) &&
- (symm != 6 || (symmpass != 3 && symmpass != 5)))) {
-
- /* Flip the planes symmetrically as needed. */
- for (int j = 0; j < 4; j++) {
- flip_plane(clip_planes_final[j], clip_planes[j], symmpass);
- }
+ SCULPT_undo_push_begin("Sculpt Gesture Apply");
- flip_v3_v3(data.task_data.view_normal, true_view_normal, symmpass);
+ for (ePaintSymmetryFlags symmpass = 0; symmpass <= mcontext->symm; symmpass++) {
+ if (SCULPT_is_symmetry_iteration_valid(symmpass, mcontext->symm)) {
+ sculpt_gesture_flip_for_symmetry_pass(mcontext, symmpass);
+ sculpt_gesture_update_effected_nodes(mcontext);
- data.symmpass = symmpass;
-
- /* Gather nodes inside lasso's enclosing rectangle
- * (should greatly help with bigger meshes). */
- PBVHFrustumPlanes frustum = {.planes = clip_planes_final, .num_planes = 4};
- BKE_pbvh_search_gather(
- pbvh, BKE_pbvh_node_frustum_contain_AABB, &frustum, &nodes, &totnode);
-
- negate_m4(clip_planes_final);
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, mcontext->totnode);
+ BLI_task_parallel_range(
+ 0, mcontext->totnode, mcontext, mask_gesture_apply_task_cb, &settings);
- data.task_data.ob = ob;
- data.task_data.pbvh = pbvh;
- data.task_data.nodes = nodes;
- data.task_data.multires = multires;
- data.task_data.mode = mode;
- data.task_data.value = value;
- data.task_data.front_faces_only = front_faces_only;
+ MEM_SAFE_FREE(mcontext->nodes);
+ }
+ }
- TaskParallelSettings settings;
- BKE_pbvh_parallel_range_settings(&settings, true, totnode);
- BLI_task_parallel_range(0, totnode, &data, mask_gesture_lasso_task_cb, &settings);
+ if (BKE_pbvh_type(mcontext->ss->pbvh) == PBVH_GRIDS) {
+ multires_mark_as_modified(depsgraph, mcontext->vc.obact, MULTIRES_COORDS_MODIFIED);
+ }
- if (nodes) {
- MEM_freeN(nodes);
- }
- }
- }
+ BKE_pbvh_update_vertex_data(mcontext->ss->pbvh, PBVH_UpdateMask);
- if (multires) {
- multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED);
- }
+ SCULPT_undo_push_end();
- BKE_pbvh_update_vertex_data(pbvh, PBVH_UpdateMask);
+ ED_region_tag_redraw(mcontext->vc.region);
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, mcontext->vc.obact);
+}
- SCULPT_undo_push_end();
+static void sculpt_gesture_init_mask_properties(SculptGestureContext *sgcontext, wmOperator *op)
+{
+ sgcontext->mask_mode = RNA_enum_get(op->ptr, "mode");
+ sgcontext->mask_value = RNA_float_get(op->ptr, "value");
+}
- ED_region_tag_redraw(vc.region);
- MEM_freeN((void *)mcoords);
- MEM_freeN(data.px);
+static void paint_mask_gesture_operator_properties(wmOperatorType *ot)
+{
+ RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", NULL);
+ RNA_def_float(
+ ot->srna,
+ "value",
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ "Value",
+ "Mask level to use when mode is 'Value'; zero means no masking and one is fully masked",
+ 0.0f,
+ 1.0f);
+}
- WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op)
+{
+ SculptGestureContext *sgcontext = sculpt_gesture_init_from_box(C, op);
+ if (!sgcontext) {
+ return OPERATOR_CANCELLED;
+ }
+ sculpt_gesture_init_mask_properties(sgcontext, op);
+ sculpt_gesture_apply(C, sgcontext);
+ sculpt_gesture_context_free(sgcontext);
+ return OPERATOR_FINISHED;
+}
- return OPERATOR_FINISHED;
+static int paint_mask_gesture_lasso_exec(bContext *C, wmOperator *op)
+{
+ SculptGestureContext *sgcontext = sculpt_gesture_init_from_lasso(C, op);
+ if (!sgcontext) {
+ return OPERATOR_CANCELLED;
}
- return OPERATOR_PASS_THROUGH;
+ sculpt_gesture_init_mask_properties(sgcontext, op);
+ sculpt_gesture_apply(C, sgcontext);
+ sculpt_gesture_context_free(sgcontext);
+ return OPERATOR_FINISHED;
}
void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot)
@@ -625,23 +617,9 @@ void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot)
/* Properties. */
WM_operator_properties_gesture_lasso(ot);
+ sculpt_gesture_operator_properties(ot);
- RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", NULL);
- RNA_def_float(
- ot->srna,
- "value",
- 1.0f,
- 0.0f,
- 1.0f,
- "Value",
- "Mask level to use when mode is 'Value'; zero means no masking and one is fully masked",
- 0.0f,
- 1.0f);
- RNA_def_boolean(ot->srna,
- "use_front_faces_only",
- false,
- "Front Faces Only",
- "Affect only faces facing towards the view");
+ paint_mask_gesture_operator_properties(ot);
}
void PAINT_OT_mask_box_gesture(wmOperatorType *ot)
@@ -660,21 +638,7 @@ void PAINT_OT_mask_box_gesture(wmOperatorType *ot)
/* Properties. */
WM_operator_properties_border(ot);
+ sculpt_gesture_operator_properties(ot);
- RNA_def_enum(ot->srna, "mode", mode_items, PAINT_MASK_FLOOD_VALUE, "Mode", NULL);
- RNA_def_float(
- ot->srna,
- "value",
- 1.0f,
- 0.0f,
- 1.0f,
- "Value",
- "Mask level to use when mode is 'Value'; zero means no masking and one is fully masked",
- 0.0f,
- 1.0f);
- RNA_def_boolean(ot->srna,
- "use_front_faces_only",
- false,
- "Front Faces Only",
- "Affect only faces facing towards the view");
+ paint_mask_gesture_operator_properties(ot);
}
diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c
index 90b0f017bd6..e709224f370 100644
--- a/source/blender/editors/sculpt_paint/paint_stroke.c
+++ b/source/blender/editors/sculpt_paint/paint_stroke.c
@@ -143,7 +143,7 @@ static void paint_draw_smooth_cursor(bContext *C, int x, int y, void *customdata
if (stroke && brush) {
GPU_line_smooth(true);
- GPU_blend(true);
+ GPU_blend(GPU_BLEND_ALPHA);
ARegion *region = stroke->vc.region;
@@ -161,7 +161,7 @@ static void paint_draw_smooth_cursor(bContext *C, int x, int y, void *customdata
immUnbindProgram();
- GPU_blend(false);
+ GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
}
@@ -691,6 +691,14 @@ static float paint_space_stroke_spacing(bContext *C,
spacing = spacing * (1.5f - spacing_pressure);
}
+ if (SCULPT_is_cloth_deform_brush(brush)) {
+ /* The spacing in tools that use the cloth solver should not be affected by the brush radius to
+ * avoid affecting the simulation update rate when changing the radius of the brush.
+ With a value of 100 and the brush default of 10 for spacing, a simulation step runs every 2
+ pixels movement of the cursor. */
+ size_clamp = 100.0f;
+ }
+
/* stroke system is used for 2d paint too, so we need to account for
* the fact that brush can be scaled there. */
spacing *= stroke->zoom_2d;
@@ -997,7 +1005,19 @@ static void stroke_done(bContext *C, wmOperator *op)
/* Returns zero if the stroke dots should not be spaced, non-zero otherwise */
bool paint_space_stroke_enabled(Brush *br, ePaintMode mode)
{
- return (br->flag & BRUSH_SPACE) && paint_supports_dynamic_size(br, mode);
+ if ((br->flag & BRUSH_SPACE) == 0) {
+ return false;
+ }
+
+ if (br->sculpt_tool == SCULPT_TOOL_CLOTH || SCULPT_is_cloth_deform_brush(br)) {
+ /* The Cloth Brush is a special case for stroke spacing. Even if it has grab modes which do
+ * not support dynamic size, stroke spacing needs to be enabled so it is possible to control
+ * whether the simulation runs constantly or only when the brush moves when using the cloth
+ * grab brushes. */
+ return true;
+ }
+
+ return paint_supports_dynamic_size(br, mode);
}
static bool sculpt_is_grab_tool(Brush *br)
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index d68b1226b40..7a066f35f23 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -275,6 +275,19 @@ void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3])
SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal);
}
+float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss,
+ const int deform_target,
+ PBVHVertexIter *iter)
+{
+ switch (deform_target) {
+ case BRUSH_DEFORM_TARGET_GEOMETRY:
+ return iter->co;
+ case BRUSH_DEFORM_TARGET_CLOTH_SIM:
+ return ss->cache->cloth_sim->deformation_pos[iter->index];
+ }
+ return iter->co;
+}
+
/* Sculpt Face Sets and Visibility. */
int SCULPT_active_face_set_get(SculptSession *ss)
@@ -2260,7 +2273,7 @@ static float brush_strength(const Sculpt *sd,
case SCULPT_TOOL_DISPLACEMENT_ERASER:
return alpha * pressure * overlap * feather;
case SCULPT_TOOL_CLOTH:
- if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
+ if (ELEM(brush->cloth_deform_type, BRUSH_CLOTH_DEFORM_GRAB, BRUSH_CLOTH_DEFORM_SNAKE_HOOK)) {
/* Grab deform uses the same falloff as a regular grab brush. */
return root_alpha * feather;
}
@@ -2331,7 +2344,7 @@ static float brush_strength(const Sculpt *sd,
}
case SCULPT_TOOL_SMOOTH:
- return alpha * pressure * feather;
+ return flip * alpha * pressure * feather;
case SCULPT_TOOL_PINCH:
if (flip > 0.0f) {
@@ -5690,6 +5703,16 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
SCULPT_pose_brush_init(sd, ob, ss, brush);
}
+ if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
+ if (!ss->cache->cloth_sim) {
+ ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create(ss, brush, 1.0f, 0.0f, false);
+ SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
+ SCULPT_cloth_brush_build_nodes_constraints(
+ sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX);
+ }
+ SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
+ }
+
bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN;
/* Apply one type of brush action. */
@@ -5828,6 +5851,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
do_gravity(sd, ob, nodes, totnode, sd->gravity_factor);
}
+ if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
+ if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
+ SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
+ }
+ }
+
MEM_SAFE_FREE(nodes);
/* Update average stroke position. */
@@ -6365,14 +6394,15 @@ void SCULPT_cache_free(StrokeCache *cache)
MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp);
MEM_SAFE_FREE(cache->layer_displacement_factor);
MEM_SAFE_FREE(cache->prev_colors);
+ MEM_SAFE_FREE(cache->detail_directions);
if (cache->pose_ik_chain) {
SCULPT_pose_ik_chain_free(cache->pose_ik_chain);
}
for (int i = 0; i < PAINT_SYMM_AREAS; i++) {
- if (cache->bdata[i]) {
- SCULPT_boundary_data_free(cache->bdata[i]);
+ if (cache->boundaries[i]) {
+ SCULPT_boundary_data_free(cache->boundaries[i]);
}
}
@@ -6604,13 +6634,19 @@ static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, flo
* generally used to create grab deformations. */
static bool sculpt_needs_delta_from_anchored_origin(Brush *brush)
{
- return ELEM(brush->sculpt_tool,
- SCULPT_TOOL_GRAB,
- SCULPT_TOOL_POSE,
- SCULPT_TOOL_BOUNDARY,
- SCULPT_TOOL_THUMB,
- SCULPT_TOOL_ELASTIC_DEFORM) ||
- SCULPT_is_cloth_deform_brush(brush);
+ if (ELEM(brush->sculpt_tool,
+ SCULPT_TOOL_GRAB,
+ SCULPT_TOOL_POSE,
+ SCULPT_TOOL_BOUNDARY,
+ SCULPT_TOOL_THUMB,
+ SCULPT_TOOL_ELASTIC_DEFORM)) {
+ return true;
+ }
+ if (brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
+ brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
+ return true;
+ }
+ return false;
}
/* In these brushes the grab delta is calculated from the previous stroke location, which is used
@@ -6618,7 +6654,7 @@ static bool sculpt_needs_delta_from_anchored_origin(Brush *brush)
static bool sculpt_needs_delta_for_tip_orientation(Brush *brush)
{
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
- return !SCULPT_is_cloth_deform_brush(brush);
+ return brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK;
}
return ELEM(brush->sculpt_tool,
SCULPT_TOOL_CLAY_STRIPS,
@@ -6664,7 +6700,9 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
copy_v3_v3(cache->orig_grab_location, cache->true_location);
}
}
- else if (tool == SCULPT_TOOL_SNAKE_HOOK) {
+ else if (tool == SCULPT_TOOL_SNAKE_HOOK ||
+ (tool == SCULPT_TOOL_CLOTH &&
+ brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK)) {
add_v3_v3(cache->true_location, cache->grab_delta);
}
@@ -7125,6 +7163,7 @@ bool SCULPT_cursor_geometry_info_update(bContext *C,
/* Update the active vertex of the SculptSession. */
ss->active_vertex_index = srd.active_vertex_index;
+ SCULPT_vertex_random_access_ensure(ss);
copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss));
switch (BKE_pbvh_type(ss->pbvh)) {
@@ -7306,7 +7345,8 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
need_mask = true;
}
- if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
+ if (brush->sculpt_tool == SCULPT_TOOL_CLOTH ||
+ brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
need_mask = true;
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c
index c86e922a347..5e01e034715 100644
--- a/source/blender/editors/sculpt_paint/sculpt_boundary.c
+++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c
@@ -130,34 +130,39 @@ static int sculpt_boundary_get_closest_boundary_vertex(SculptSession *ss,
* deformations usually need in the boundary. */
static int BOUNDARY_INDICES_BLOCK_SIZE = 300;
-static void sculpt_boundary_index_add(SculptBoundary *bdata,
+static void sculpt_boundary_index_add(SculptBoundary *boundary,
const int new_index,
+ const float distance,
GSet *included_vertices)
{
- bdata->vertices[bdata->num_vertices] = new_index;
+ boundary->vertices[boundary->num_vertices] = new_index;
+ if (boundary->distance) {
+ boundary->distance[new_index] = distance;
+ }
if (included_vertices) {
BLI_gset_add(included_vertices, POINTER_FROM_INT(new_index));
}
- bdata->num_vertices++;
- if (bdata->num_vertices >= bdata->vertices_capacity) {
- bdata->vertices_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
- bdata->vertices = MEM_reallocN_id(
- bdata->vertices, bdata->vertices_capacity * sizeof(int), "boundary indices");
+ boundary->num_vertices++;
+ if (boundary->num_vertices >= boundary->vertices_capacity) {
+ boundary->vertices_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
+ boundary->vertices = MEM_reallocN_id(
+ boundary->vertices, boundary->vertices_capacity * sizeof(int), "boundary indices");
}
};
-static void sculpt_boundary_preview_edge_add(SculptBoundary *bdata, const int v1, const int v2)
+static void sculpt_boundary_preview_edge_add(SculptBoundary *boundary, const int v1, const int v2)
{
- bdata->edges[bdata->num_edges].v1 = v1;
- bdata->edges[bdata->num_edges].v2 = v2;
- bdata->num_edges++;
+ boundary->edges[boundary->num_edges].v1 = v1;
+ boundary->edges[boundary->num_edges].v2 = v2;
+ boundary->num_edges++;
- if (bdata->num_edges >= bdata->edges_capacity) {
- bdata->edges_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
- bdata->edges = MEM_reallocN_id(
- bdata->edges, bdata->edges_capacity * sizeof(SculptBoundaryPreviewEdge), "boundary edges");
+ if (boundary->num_edges >= boundary->edges_capacity) {
+ boundary->edges_capacity += BOUNDARY_INDICES_BLOCK_SIZE;
+ boundary->edges = MEM_reallocN_id(boundary->edges,
+ boundary->edges_capacity * sizeof(SculptBoundaryPreviewEdge),
+ "boundary edges");
}
};
@@ -199,7 +204,7 @@ static bool sculpt_boundary_is_vertex_in_editable_boundary(SculptSession *ss,
*/
typedef struct BoundaryFloodFillData {
- SculptBoundary *bdata;
+ SculptBoundary *boundary;
GSet *included_vertices;
EdgeSet *preview_edges;
@@ -211,11 +216,16 @@ static bool boundary_floodfill_cb(
SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata)
{
BoundaryFloodFillData *data = userdata;
- SculptBoundary *bdata = data->bdata;
+ SculptBoundary *boundary = data->boundary;
if (SCULPT_vertex_is_boundary(ss, to_v)) {
- sculpt_boundary_index_add(bdata, to_v, data->included_vertices);
+ const float edge_len = len_v3v3(SCULPT_vertex_co_get(ss, from_v),
+ SCULPT_vertex_co_get(ss, to_v));
+ const float distance_boundary_to_dst = boundary->distance ?
+ boundary->distance[from_v] + edge_len :
+ 0.0f;
+ sculpt_boundary_index_add(boundary, to_v, distance_boundary_to_dst, data->included_vertices);
if (!is_duplicate) {
- sculpt_boundary_preview_edge_add(bdata, from_v, to_v);
+ sculpt_boundary_preview_edge_add(boundary, from_v, to_v);
}
return sculpt_boundary_is_vertex_in_editable_boundary(ss, to_v);
}
@@ -223,26 +233,32 @@ static bool boundary_floodfill_cb(
}
static void sculpt_boundary_indices_init(SculptSession *ss,
- SculptBoundary *bdata,
+ SculptBoundary *boundary,
+ const bool init_boundary_distances,
const int initial_boundary_index)
{
- bdata->vertices = MEM_malloc_arrayN(
+ const int totvert = SCULPT_vertex_count_get(ss);
+ boundary->vertices = MEM_malloc_arrayN(
BOUNDARY_INDICES_BLOCK_SIZE, sizeof(int), "boundary indices");
- bdata->edges = MEM_malloc_arrayN(
+ if (init_boundary_distances) {
+ boundary->distance = MEM_calloc_arrayN(totvert, sizeof(float), "boundary distances");
+ }
+ boundary->edges = MEM_malloc_arrayN(
BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge), "boundary edges");
GSet *included_vertices = BLI_gset_int_new_ex("included vertices", BOUNDARY_INDICES_BLOCK_SIZE);
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
- bdata->initial_vertex = initial_boundary_index;
- copy_v3_v3(bdata->initial_vertex_position, SCULPT_vertex_co_get(ss, bdata->initial_vertex));
- sculpt_boundary_index_add(bdata, initial_boundary_index, included_vertices);
+ boundary->initial_vertex = initial_boundary_index;
+ copy_v3_v3(boundary->initial_vertex_position,
+ SCULPT_vertex_co_get(ss, boundary->initial_vertex));
+ sculpt_boundary_index_add(boundary, initial_boundary_index, 0.0f, included_vertices);
SCULPT_floodfill_add_initial(&flood, initial_boundary_index);
BoundaryFloodFillData fdata = {
- .bdata = bdata,
+ .boundary = boundary,
.included_vertices = included_vertices,
.last_visited_vertex = BOUNDARY_VERTEX_NONE,
@@ -258,8 +274,8 @@ static void sculpt_boundary_indices_init(SculptSession *ss,
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, fdata.last_visited_vertex, ni) {
if (BLI_gset_haskey(included_vertices, POINTER_FROM_INT(ni.index)) &&
sculpt_boundary_is_vertex_in_editable_boundary(ss, ni.index)) {
- sculpt_boundary_preview_edge_add(bdata, fdata.last_visited_vertex, ni.index);
- bdata->forms_loop = true;
+ sculpt_boundary_preview_edge_add(boundary, fdata.last_visited_vertex, ni.index);
+ boundary->forms_loop = true;
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
@@ -275,7 +291,7 @@ static void sculpt_boundary_indices_init(SculptSession *ss,
* the closest one.
*/
static void sculpt_boundary_edit_data_init(SculptSession *ss,
- SculptBoundary *bdata,
+ SculptBoundary *boundary,
const int initial_vertex,
const float radius)
{
@@ -283,12 +299,12 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
const bool has_duplicates = BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS;
- bdata->edit_info = MEM_malloc_arrayN(
+ boundary->edit_info = MEM_malloc_arrayN(
totvert, sizeof(SculptBoundaryEditInfo), "Boundary edit info");
for (int i = 0; i < totvert; i++) {
- bdata->edit_info[i].original_vertex = BOUNDARY_VERTEX_NONE;
- bdata->edit_info[i].num_propagation_steps = BOUNDARY_STEPS_NONE;
+ boundary->edit_info[i].original_vertex = BOUNDARY_VERTEX_NONE;
+ boundary->edit_info[i].num_propagation_steps = BOUNDARY_STEPS_NONE;
}
GSQueue *current_iteration = BLI_gsqueue_new(sizeof(int));
@@ -297,23 +313,23 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
/* Initialized the first iteration with the vertices already in the boundary. This is propagation
* step 0. */
BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices");
- for (int i = 0; i < bdata->num_vertices; i++) {
- bdata->edit_info[bdata->vertices[i]].original_vertex = bdata->vertices[i];
- bdata->edit_info[bdata->vertices[i]].num_propagation_steps = 0;
+ for (int i = 0; i < boundary->num_vertices; i++) {
+ boundary->edit_info[boundary->vertices[i]].original_vertex = boundary->vertices[i];
+ boundary->edit_info[boundary->vertices[i]].num_propagation_steps = 0;
/* This ensures that all duplicate vertices in the boundary have the same original_vertex
* index, so the deformation for them will be the same. */
if (has_duplicates) {
SculptVertexNeighborIter ni_duplis;
- SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, bdata->vertices[i], ni_duplis) {
+ SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, boundary->vertices[i], ni_duplis) {
if (ni_duplis.is_duplicate) {
- bdata->edit_info[ni_duplis.index].original_vertex = bdata->vertices[i];
+ boundary->edit_info[ni_duplis.index].original_vertex = boundary->vertices[i];
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis);
}
- BLI_gsqueue_push(current_iteration, &bdata->vertices[i]);
+ BLI_gsqueue_push(current_iteration, &boundary->vertices[i]);
}
int num_propagation_steps = 0;
@@ -323,7 +339,7 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
/* This steps is further away from the boundary than the brush radius, so stop adding more
* steps. */
if (accum_distance > radius) {
- bdata->max_propagation_steps = num_propagation_steps;
+ boundary->max_propagation_steps = num_propagation_steps;
break;
}
@@ -333,19 +349,20 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
SculptVertexNeighborIter ni;
SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) {
- if (bdata->edit_info[ni.index].num_propagation_steps == BOUNDARY_STEPS_NONE) {
- bdata->edit_info[ni.index].original_vertex = bdata->edit_info[from_v].original_vertex;
+ if (boundary->edit_info[ni.index].num_propagation_steps == BOUNDARY_STEPS_NONE) {
+ boundary->edit_info[ni.index].original_vertex =
+ boundary->edit_info[from_v].original_vertex;
BLI_BITMAP_ENABLE(visited_vertices, ni.index);
if (ni.is_duplicate) {
/* Grids duplicates handling. */
- bdata->edit_info[ni.index].num_propagation_steps =
- bdata->edit_info[from_v].num_propagation_steps;
+ boundary->edit_info[ni.index].num_propagation_steps =
+ boundary->edit_info[from_v].num_propagation_steps;
}
else {
- bdata->edit_info[ni.index].num_propagation_steps =
- bdata->edit_info[from_v].num_propagation_steps + 1;
+ boundary->edit_info[ni.index].num_propagation_steps =
+ boundary->edit_info[from_v].num_propagation_steps + 1;
BLI_gsqueue_push(next_iteration, &ni.index);
@@ -357,10 +374,10 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
SculptVertexNeighborIter ni_duplis;
SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, ni.index, ni_duplis) {
if (ni_duplis.is_duplicate) {
- bdata->edit_info[ni_duplis.index].original_vertex =
- bdata->edit_info[from_v].original_vertex;
- bdata->edit_info[ni_duplis.index].num_propagation_steps =
- bdata->edit_info[from_v].num_propagation_steps + 1;
+ boundary->edit_info[ni_duplis.index].original_vertex =
+ boundary->edit_info[from_v].original_vertex;
+ boundary->edit_info[ni_duplis.index].num_propagation_steps =
+ boundary->edit_info[from_v].num_propagation_steps + 1;
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni_duplis);
@@ -368,9 +385,9 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
/* Check the distance using the vertex that was propagated from the initial vertex that
* was used to initialize the boundary. */
- if (bdata->edit_info[from_v].original_vertex == initial_vertex) {
- bdata->pivot_vertex = ni.index;
- copy_v3_v3(bdata->initial_pivot_position, SCULPT_vertex_co_get(ss, ni.index));
+ if (boundary->edit_info[from_v].original_vertex == initial_vertex) {
+ boundary->pivot_vertex = ni.index;
+ copy_v3_v3(boundary->initial_pivot_position, SCULPT_vertex_co_get(ss, ni.index));
accum_distance += len_v3v3(SCULPT_vertex_co_get(ss, from_v),
SCULPT_vertex_co_get(ss, ni.index));
}
@@ -406,23 +423,67 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
* on the brush curve and its propagation steps. The falloff goes from the boundary into the mesh.
*/
static void sculpt_boundary_falloff_factor_init(SculptSession *ss,
- SculptBoundary *bdata,
- Brush *brush)
+ SculptBoundary *boundary,
+ Brush *brush,
+ const float radius)
{
const int totvert = SCULPT_vertex_count_get(ss);
BKE_curvemapping_init(brush->curve);
for (int i = 0; i < totvert; i++) {
- if (bdata->edit_info[i].num_propagation_steps != -1) {
- bdata->edit_info[i].strength_factor = BKE_brush_curve_strength(
- brush, bdata->edit_info[i].num_propagation_steps, bdata->max_propagation_steps);
+ if (boundary->edit_info[i].num_propagation_steps != -1) {
+ boundary->edit_info[i].strength_factor = BKE_brush_curve_strength(
+ brush, boundary->edit_info[i].num_propagation_steps, boundary->max_propagation_steps);
+ }
+
+ if (boundary->edit_info[i].original_vertex == boundary->initial_vertex) {
+ /* All vertices that are propagated from the original vertex won't be affected by the
+ * boundary falloff, so there is no need to calculate anything else. */
+ continue;
+ }
+
+ if (!boundary->distance) {
+ /* There are falloff modes that do not require to modify the previously calculated falloff
+ * based on boundary distances. */
+ continue;
+ }
+
+ const float boundary_distance = boundary->distance[boundary->edit_info[i].original_vertex];
+ float falloff_distance = 0.0f;
+ float direction = 1.0f;
+
+ switch (brush->boundary_falloff_type) {
+ case BRUSH_BOUNDARY_FALLOFF_RADIUS:
+ falloff_distance = boundary_distance;
+ break;
+ case BRUSH_BOUNDARY_FALLOFF_LOOP: {
+ const int div = boundary_distance / radius;
+ const float mod = fmodf(boundary_distance, radius);
+ falloff_distance = div % 2 == 0 ? mod : radius - mod;
+ } break;
+ case BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT: {
+ const int div = boundary_distance / radius;
+ const float mod = fmodf(boundary_distance, radius);
+ falloff_distance = div % 2 == 0 ? mod : radius - mod;
+ /* Inverts the faloff in the intervals 1 2 5 6 9 10 ... */
+ if (((div - 1) & 2) == 0) {
+ direction = -1.0f;
+ }
+ } break;
+ case BRUSH_BOUNDARY_FALLOFF_CONSTANT:
+ /* For constant falloff distances are not allocated, so this should never happen. */
+ BLI_assert(false);
}
+
+ boundary->edit_info[i].strength_factor *= direction * BKE_brush_curve_strength(
+ brush, falloff_distance, radius);
}
}
/* Main function to get SculptBoundary data both for brush deformation and viewport preview. Can
* return NULL if there is no boundary from the given vertex using the given radius. */
SculptBoundary *SCULPT_boundary_data_init(Object *object,
+ Brush *brush,
const int initial_vertex,
const float radius)
{
@@ -444,111 +505,118 @@ SculptBoundary *SCULPT_boundary_data_init(Object *object,
return NULL;
}
- SculptBoundary *bdata = MEM_callocN(sizeof(SculptBoundary), "Boundary edit data");
+ SculptBoundary *boundary = MEM_callocN(sizeof(SculptBoundary), "Boundary edit data");
+
+ const bool init_boundary_distances = brush->boundary_falloff_type !=
+ BRUSH_BOUNDARY_FALLOFF_CONSTANT;
+ sculpt_boundary_indices_init(ss, boundary, init_boundary_distances, boundary_initial_vertex);
- sculpt_boundary_indices_init(ss, bdata, boundary_initial_vertex);
- sculpt_boundary_edit_data_init(ss, bdata, boundary_initial_vertex, radius);
+ const float boundary_radius = radius * (1.0f + brush->boundary_offset);
+ sculpt_boundary_edit_data_init(ss, boundary, boundary_initial_vertex, boundary_radius);
- return bdata;
+ return boundary;
}
-void SCULPT_boundary_data_free(SculptBoundary *bdata)
+void SCULPT_boundary_data_free(SculptBoundary *boundary)
{
- MEM_SAFE_FREE(bdata->vertices);
- MEM_SAFE_FREE(bdata->edit_info);
- MEM_SAFE_FREE(bdata->bend.pivot_positions);
- MEM_SAFE_FREE(bdata->bend.pivot_rotation_axis);
- MEM_SAFE_FREE(bdata->slide.directions);
- MEM_SAFE_FREE(bdata);
+ MEM_SAFE_FREE(boundary->vertices);
+ MEM_SAFE_FREE(boundary->distance);
+ MEM_SAFE_FREE(boundary->edit_info);
+ MEM_SAFE_FREE(boundary->bend.pivot_positions);
+ MEM_SAFE_FREE(boundary->bend.pivot_rotation_axis);
+ MEM_SAFE_FREE(boundary->slide.directions);
+ MEM_SAFE_FREE(boundary);
}
/* These functions initialize the required vectors for the desired deformation using the
* SculptBoundaryEditInfo. They calculate the data using the vertices that have the
* max_propagation_steps value and them this data is copied to the rest of the vertices using the
* original vertex index. */
-static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *bdata)
+static void sculpt_boundary_bend_data_init(SculptSession *ss, SculptBoundary *boundary)
{
const int totvert = SCULPT_vertex_count_get(ss);
- bdata->bend.pivot_rotation_axis = MEM_calloc_arrayN(
+ boundary->bend.pivot_rotation_axis = MEM_calloc_arrayN(
totvert, 3 * sizeof(float), "pivot rotation axis");
- bdata->bend.pivot_positions = MEM_calloc_arrayN(totvert, 3 * sizeof(float), "pivot positions");
+ boundary->bend.pivot_positions = MEM_calloc_arrayN(
+ totvert, 3 * sizeof(float), "pivot positions");
for (int i = 0; i < totvert; i++) {
- if (bdata->edit_info[i].num_propagation_steps == bdata->max_propagation_steps) {
+ if (boundary->edit_info[i].num_propagation_steps == boundary->max_propagation_steps) {
float dir[3];
float normal[3];
SCULPT_vertex_normal_get(ss, i, normal);
sub_v3_v3v3(dir,
- SCULPT_vertex_co_get(ss, bdata->edit_info[i].original_vertex),
+ SCULPT_vertex_co_get(ss, boundary->edit_info[i].original_vertex),
SCULPT_vertex_co_get(ss, i));
cross_v3_v3v3(
- bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex], dir, normal);
- normalize_v3(bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex]);
- copy_v3_v3(bdata->bend.pivot_positions[bdata->edit_info[i].original_vertex],
+ boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex], dir, normal);
+ normalize_v3(boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex]);
+ copy_v3_v3(boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex],
SCULPT_vertex_co_get(ss, i));
}
}
for (int i = 0; i < totvert; i++) {
- if (bdata->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
- copy_v3_v3(bdata->bend.pivot_positions[i],
- bdata->bend.pivot_positions[bdata->edit_info[i].original_vertex]);
- copy_v3_v3(bdata->bend.pivot_rotation_axis[i],
- bdata->bend.pivot_rotation_axis[bdata->edit_info[i].original_vertex]);
+ if (boundary->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
+ copy_v3_v3(boundary->bend.pivot_positions[i],
+ boundary->bend.pivot_positions[boundary->edit_info[i].original_vertex]);
+ copy_v3_v3(boundary->bend.pivot_rotation_axis[i],
+ boundary->bend.pivot_rotation_axis[boundary->edit_info[i].original_vertex]);
}
}
}
-static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *bdata)
+static void sculpt_boundary_slide_data_init(SculptSession *ss, SculptBoundary *boundary)
{
const int totvert = SCULPT_vertex_count_get(ss);
- bdata->slide.directions = MEM_calloc_arrayN(totvert, 3 * sizeof(float), "slide directions");
+ boundary->slide.directions = MEM_calloc_arrayN(totvert, 3 * sizeof(float), "slide directions");
for (int i = 0; i < totvert; i++) {
- if (bdata->edit_info[i].num_propagation_steps == bdata->max_propagation_steps) {
- sub_v3_v3v3(bdata->slide.directions[bdata->edit_info[i].original_vertex],
- SCULPT_vertex_co_get(ss, bdata->edit_info[i].original_vertex),
+ if (boundary->edit_info[i].num_propagation_steps == boundary->max_propagation_steps) {
+ sub_v3_v3v3(boundary->slide.directions[boundary->edit_info[i].original_vertex],
+ SCULPT_vertex_co_get(ss, boundary->edit_info[i].original_vertex),
SCULPT_vertex_co_get(ss, i));
- normalize_v3(bdata->slide.directions[bdata->edit_info[i].original_vertex]);
+ normalize_v3(boundary->slide.directions[boundary->edit_info[i].original_vertex]);
}
}
for (int i = 0; i < totvert; i++) {
- if (bdata->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
- copy_v3_v3(bdata->slide.directions[i],
- bdata->slide.directions[bdata->edit_info[i].original_vertex]);
+ if (boundary->edit_info[i].num_propagation_steps != BOUNDARY_STEPS_NONE) {
+ copy_v3_v3(boundary->slide.directions[i],
+ boundary->slide.directions[boundary->edit_info[i].original_vertex]);
}
}
}
-static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *bdata)
+static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *boundary)
{
- zero_v3(bdata->twist.pivot_position);
- float(*poly_verts)[3] = MEM_malloc_arrayN(bdata->num_vertices, sizeof(float) * 3, "poly verts");
- for (int i = 0; i < bdata->num_vertices; i++) {
- add_v3_v3(bdata->twist.pivot_position, SCULPT_vertex_co_get(ss, bdata->vertices[i]));
- copy_v3_v3(poly_verts[i], SCULPT_vertex_co_get(ss, bdata->vertices[i]));
+ zero_v3(boundary->twist.pivot_position);
+ float(*poly_verts)[3] = MEM_malloc_arrayN(
+ boundary->num_vertices, sizeof(float) * 3, "poly verts");
+ for (int i = 0; i < boundary->num_vertices; i++) {
+ add_v3_v3(boundary->twist.pivot_position, SCULPT_vertex_co_get(ss, boundary->vertices[i]));
+ copy_v3_v3(poly_verts[i], SCULPT_vertex_co_get(ss, boundary->vertices[i]));
}
- mul_v3_fl(bdata->twist.pivot_position, 1.0f / bdata->num_vertices);
- if (bdata->forms_loop) {
- normal_poly_v3(bdata->twist.rotation_axis, poly_verts, bdata->num_vertices);
+ mul_v3_fl(boundary->twist.pivot_position, 1.0f / boundary->num_vertices);
+ if (boundary->forms_loop) {
+ normal_poly_v3(boundary->twist.rotation_axis, poly_verts, boundary->num_vertices);
}
else {
- sub_v3_v3v3(bdata->twist.rotation_axis,
- SCULPT_vertex_co_get(ss, bdata->pivot_vertex),
- SCULPT_vertex_co_get(ss, bdata->initial_vertex));
- normalize_v3(bdata->twist.rotation_axis);
+ sub_v3_v3v3(boundary->twist.rotation_axis,
+ SCULPT_vertex_co_get(ss, boundary->pivot_vertex),
+ SCULPT_vertex_co_get(ss, boundary->initial_vertex));
+ normalize_v3(boundary->twist.rotation_axis);
}
MEM_freeN(poly_verts);
}
static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss,
- SculptBoundary *bdata)
+ SculptBoundary *boundary)
{
float plane[4];
float pos[3];
float normal[3];
- sub_v3_v3v3(normal, ss->cache->initial_location, bdata->initial_pivot_position);
+ sub_v3_v3v3(normal, ss->cache->initial_location, boundary->initial_pivot_position);
normalize_v3(normal);
plane_from_point_normal_v3(plane, ss->cache->initial_location, normal);
add_v3_v3v3(pos, ss->cache->initial_location, ss->cache->grab_delta_symmetry);
@@ -563,7 +631,9 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const int symm_area = ss->cache->mirror_symmetry_pass;
- SculptBoundary *bdata = ss->cache->bdata[symm_area];
+ SculptBoundary *boundary = ss->cache->boundaries[symm_area];
+ const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+ const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@@ -571,7 +641,7 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
- const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, boundary);
float angle_factor = disp / ss->cache->radius;
/* Angle Snapping when inverting the brush. */
if (ss->cache->invert) {
@@ -582,16 +652,20 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
- if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ if (boundary->edit_info[vd.index].num_propagation_steps != -1) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
- const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
- float t_orig_co[3];
- sub_v3_v3v3(t_orig_co, orig_data.co, bdata->bend.pivot_positions[vd.index]);
- rotate_v3_v3v3fl(vd.co,
- t_orig_co,
- bdata->bend.pivot_rotation_axis[vd.index],
- angle * bdata->edit_info[vd.index].strength_factor * mask);
- add_v3_v3(vd.co, bdata->bend.pivot_positions[vd.index]);
+ if (SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float t_orig_co[3];
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ sub_v3_v3v3(t_orig_co, orig_data.co, boundary->bend.pivot_positions[vd.index]);
+ rotate_v3_v3v3fl(target_co,
+ t_orig_co,
+ boundary->bend.pivot_rotation_axis[vd.index],
+ angle * boundary->edit_info[vd.index].strength_factor * mask);
+ add_v3_v3(target_co, boundary->bend.pivot_positions[vd.index]);
+ }
}
if (vd.mvert) {
@@ -608,7 +682,9 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const int symm_area = ss->cache->mirror_symmetry_pass;
- SculptBoundary *bdata = ss->cache->bdata[symm_area];
+ SculptBoundary *boundary = ss->cache->boundaries[symm_area];
+ const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+ const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@@ -616,18 +692,22 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata,
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
- const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, boundary);
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
- if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ if (boundary->edit_info[vd.index].num_propagation_steps != -1) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
- const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
- madd_v3_v3v3fl(vd.co,
- orig_data.co,
- bdata->slide.directions[vd.index],
- bdata->edit_info[vd.index].strength_factor * disp * mask * strength);
+ if (SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ madd_v3_v3v3fl(target_co,
+ orig_data.co,
+ boundary->slide.directions[vd.index],
+ boundary->edit_info[vd.index].strength_factor * disp * mask * strength);
+ }
}
if (vd.mvert) {
@@ -644,7 +724,9 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const int symm_area = ss->cache->mirror_symmetry_pass;
- SculptBoundary *bdata = ss->cache->bdata[symm_area];
+ SculptBoundary *boundary = ss->cache->boundaries[symm_area];
+ const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+ const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@@ -652,20 +734,24 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata,
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
- const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ const float disp = sculpt_boundary_displacement_from_grab_delta_get(ss, boundary);
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
- if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ if (boundary->edit_info[vd.index].num_propagation_steps != -1) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
- const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
- float normal[3];
- normal_short_to_float_v3(normal, orig_data.no);
- madd_v3_v3v3fl(vd.co,
- orig_data.co,
- normal,
- bdata->edit_info[vd.index].strength_factor * disp * mask * strength);
+ if (SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float normal[3];
+ normal_short_to_float_v3(normal, orig_data.no);
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ madd_v3_v3v3fl(target_co,
+ orig_data.co,
+ normal,
+ boundary->edit_info[vd.index].strength_factor * disp * mask * strength);
+ }
}
if (vd.mvert) {
@@ -682,7 +768,9 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const int symm_area = ss->cache->mirror_symmetry_pass;
- SculptBoundary *bdata = ss->cache->bdata[symm_area];
+ SculptBoundary *boundary = ss->cache->boundaries[symm_area];
+ const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+ const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@@ -693,13 +781,17 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
- if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
+ if (boundary->edit_info[vd.index].num_propagation_steps != -1) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
- const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
- madd_v3_v3v3fl(vd.co,
- orig_data.co,
- ss->cache->grab_delta_symmetry,
- bdata->edit_info[vd.index].strength_factor * mask * strength);
+ if (SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ madd_v3_v3v3fl(target_co,
+ orig_data.co,
+ ss->cache->grab_delta_symmetry,
+ boundary->edit_info[vd.index].strength_factor * mask * strength);
+ }
}
if (vd.mvert) {
@@ -716,7 +808,9 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const int symm_area = ss->cache->mirror_symmetry_pass;
- SculptBoundary *bdata = ss->cache->bdata[symm_area];
+ SculptBoundary *boundary = ss->cache->boundaries[symm_area];
+ const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
+ const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@@ -724,7 +818,7 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
- const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, bdata);
+ const float disp = strength * sculpt_boundary_displacement_from_grab_delta_get(ss, boundary);
float angle_factor = disp / ss->cache->radius;
/* Angle Snapping when inverting the brush. */
if (ss->cache->invert) {
@@ -735,16 +829,20 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
- if (bdata->edit_info[vd.index].num_propagation_steps != -1) {
- const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ if (boundary->edit_info[vd.index].num_propagation_steps != -1) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
- float t_orig_co[3];
- sub_v3_v3v3(t_orig_co, orig_data.co, bdata->twist.pivot_position);
- rotate_v3_v3v3fl(vd.co,
- t_orig_co,
- bdata->twist.rotation_axis,
- angle * mask * bdata->edit_info[vd.index].strength_factor);
- add_v3_v3(vd.co, bdata->twist.pivot_position);
+ if (SCULPT_check_vertex_pivot_symmetry(
+ orig_data.co, boundary->initial_vertex_position, symm)) {
+ const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
+ float t_orig_co[3];
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ sub_v3_v3v3(t_orig_co, orig_data.co, boundary->twist.pivot_position);
+ rotate_v3_v3v3fl(target_co,
+ t_orig_co,
+ boundary->twist.rotation_axis,
+ angle * mask * boundary->edit_info[vd.index].strength_factor);
+ add_v3_v3(target_co, boundary->twist.pivot_position);
+ }
}
if (vd.mvert) {
@@ -774,20 +872,20 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
sd, ob, location, ss->cache->radius_squared, false);
}
- ss->cache->bdata[symm_area] = SCULPT_boundary_data_init(
- ob, initial_vertex, ss->cache->initial_radius);
+ ss->cache->boundaries[symm_area] = SCULPT_boundary_data_init(
+ ob, brush, initial_vertex, ss->cache->initial_radius);
- if (ss->cache->bdata[symm_area]) {
+ if (ss->cache->boundaries[symm_area]) {
switch (brush->boundary_deform_type) {
case BRUSH_BOUNDARY_DEFORM_BEND:
- sculpt_boundary_bend_data_init(ss, ss->cache->bdata[symm_area]);
+ sculpt_boundary_bend_data_init(ss, ss->cache->boundaries[symm_area]);
break;
case BRUSH_BOUNDARY_DEFORM_EXPAND:
- sculpt_boundary_slide_data_init(ss, ss->cache->bdata[symm_area]);
+ sculpt_boundary_slide_data_init(ss, ss->cache->boundaries[symm_area]);
break;
case BRUSH_BOUNDARY_DEFORM_TWIST:
- sculpt_boundary_twist_data_init(ss, ss->cache->bdata[symm_area]);
+ sculpt_boundary_twist_data_init(ss, ss->cache->boundaries[symm_area]);
break;
case BRUSH_BOUNDARY_DEFORM_INFLATE:
case BRUSH_BOUNDARY_DEFORM_GRAB:
@@ -795,12 +893,13 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
break;
}
- sculpt_boundary_falloff_factor_init(ss, ss->cache->bdata[symm_area], brush);
+ sculpt_boundary_falloff_factor_init(
+ ss, ss->cache->boundaries[symm_area], brush, ss->cache->initial_radius);
}
}
/* No active boundary under the cursor. */
- if (!ss->cache->bdata[symm_area]) {
+ if (!ss->cache->boundaries[symm_area]) {
return;
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c
index 2637cb45906..c3666c8aaad 100644
--- a/source/blender/editors/sculpt_paint/sculpt_cloth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c
@@ -134,6 +134,7 @@ static float cloth_brush_simulation_falloff_get(const Brush *brush,
#define CLOTH_SIMULATION_ITERATIONS 5
#define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024
#define CLOTH_SIMULATION_TIME_STEP 0.01f
+#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.35f
static bool cloth_brush_sim_has_length_constraint(SculptClothSimulation *cloth_sim,
const int v1,
@@ -168,6 +169,8 @@ static void cloth_brush_add_length_constraint(SculptSession *ss,
length_constraint->elem_position_a = cloth_sim->pos[v1];
length_constraint->elem_position_b = cloth_sim->pos[v2];
+ length_constraint->type = SCULPT_CLOTH_CONSTRAINT_STRUCTURAL;
+
if (use_persistent) {
length_constraint->length = len_v3v3(SCULPT_vertex_persistent_co_get(ss, v1),
SCULPT_vertex_persistent_co_get(ss, v2));
@@ -200,6 +203,8 @@ static void cloth_brush_add_softbody_constraint(SculptClothSimulation *cloth_sim
length_constraint->elem_position_a = cloth_sim->pos[v];
length_constraint->elem_position_b = cloth_sim->init_pos[v];
+ length_constraint->type = SCULPT_CLOTH_CONSTRAINT_SOFTBODY;
+
length_constraint->length = 0.0f;
length_constraint->strength = strength;
@@ -219,6 +224,8 @@ static void cloth_brush_add_deformation_constraint(SculptClothSimulation *cloth_
length_constraint->elem_index_a = v;
length_constraint->elem_index_b = v;
+ length_constraint->type = SCULPT_CLOTH_CONSTRAINT_DEFORMATION;
+
length_constraint->elem_position_a = cloth_sim->pos[v];
length_constraint->elem_position_b = cloth_sim->deformation_pos[v];
@@ -297,15 +304,34 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
}
}
- if (cloth_is_deform_brush && len_squared < radius_squared) {
- const float fade = BKE_brush_curve_strength(brush, sqrtf(len_squared), ss->cache->radius);
- cloth_brush_add_deformation_constraint(data->cloth_sim, vd.index, fade);
+ if (brush && brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
+ /* The cloth brush works by applying forces in most of its modes, but some of them require
+ * deformation coordinates to make the simulation stable. */
+ if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB && len_squared < radius_squared) {
+ /* When the grab brush brush is used as part of the cloth brush, deformation constraints
+ * are created with different strengths and only inside the radius of the brush. */
+ const float fade = BKE_brush_curve_strength(brush, sqrtf(len_squared), ss->cache->radius);
+ cloth_brush_add_deformation_constraint(data->cloth_sim, vd.index, fade);
+ }
+ else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) {
+ /* Cloth Snake Hook creates deformation constraint with fixed strength because the strength
+ * is controlled per iteration using cloth_sim->deformation_strength. */
+ cloth_brush_add_deformation_constraint(
+ data->cloth_sim, vd.index, CLOTH_DEFORMATION_TARGET_STRENGTH);
+ }
+ }
+ else if (data->cloth_sim->deformation_pos) {
+ /* Any other tool that target the cloth simulation handle the falloff in
+ * their own code when modifying the deformation coordinates of the simulation, so
+ * deformation constraints are created with a fixed strength for all vertices. */
+ cloth_brush_add_deformation_constraint(
+ data->cloth_sim, vd.index, CLOTH_DEFORMATION_TARGET_STRENGTH);
}
if (pin_simulation_boundary) {
const float sim_falloff = cloth_brush_simulation_falloff_get(
brush, ss->cache->initial_radius, ss->cache->location, vd.co);
- /* Vertex is inside the area of the simulation without any falloff aplied. */
+ /* Vertex is inside the area of the simulation without any falloff applied. */
if (sim_falloff < 1.0f) {
/* Create constraints with more strength the closer the vertex is to the simulation
* boundary. */
@@ -383,7 +409,7 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
brush, ss->cache->radius, ss->cache->initial_location, cloth_sim->init_pos[vd.index]);
float current_vertex_location[3];
- if (SCULPT_is_cloth_deform_brush(brush)) {
+ if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
copy_v3_v3(current_vertex_location, orig_data.co);
}
@@ -442,6 +468,12 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata,
fade);
zero_v3(force);
break;
+ case BRUSH_CLOTH_DEFORM_SNAKE_HOOK:
+ copy_v3_v3(cloth_sim->deformation_pos[vd.index], cloth_sim->pos[vd.index]);
+ madd_v3_v3fl(cloth_sim->deformation_pos[vd.index], ss->cache->grab_delta_symmetry, fade);
+ cloth_sim->deformation_strength[vd.index] = fade;
+ zero_v3(force);
+ break;
case BRUSH_CLOTH_DEFORM_PINCH_POINT:
if (use_falloff_plane) {
float distance = dist_signed_to_plane_v3(vd.co, deform_plane);
@@ -505,49 +537,6 @@ static ListBase *cloth_brush_collider_cache_create(Depsgraph *depsgraph)
return cache;
}
-static SculptClothSimulation *cloth_brush_simulation_create(SculptSession *ss,
- Brush *brush,
- const float cloth_mass,
- const float cloth_damping,
- const bool use_collisions)
-{
- const int totverts = SCULPT_vertex_count_get(ss);
- SculptClothSimulation *cloth_sim;
-
- cloth_sim = MEM_callocN(sizeof(SculptClothSimulation), "cloth constraints");
-
- cloth_sim->length_constraints = MEM_callocN(sizeof(SculptClothLengthConstraint) *
- CLOTH_LENGTH_CONSTRAINTS_BLOCK,
- "cloth length constraints");
- cloth_sim->capacity_length_constraints = CLOTH_LENGTH_CONSTRAINTS_BLOCK;
-
- cloth_sim->acceleration = MEM_calloc_arrayN(
- totverts, sizeof(float[3]), "cloth sim acceleration");
- cloth_sim->pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim pos");
- cloth_sim->prev_pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim prev pos");
- cloth_sim->last_iteration_pos = MEM_calloc_arrayN(
- totverts, sizeof(float[3]), "cloth sim last iteration pos");
- cloth_sim->init_pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim init pos");
- cloth_sim->length_constraint_tweak = MEM_calloc_arrayN(
- totverts, sizeof(float), "cloth sim length tweak");
-
- /* Brush can be NULL for tools that need the solver but don't rely on constraint to deformation
- * positions. */
- if (brush && SCULPT_is_cloth_deform_brush(brush)) {
- cloth_sim->deformation_pos = MEM_calloc_arrayN(
- totverts, sizeof(float[3]), "cloth sim deformation positions");
- }
-
- cloth_sim->mass = cloth_mass;
- cloth_sim->damping = cloth_damping;
-
- if (use_collisions) {
- cloth_sim->collider_list = cloth_brush_collider_cache_create(ss->depsgraph);
- }
-
- return cloth_sim;
-}
-
typedef struct ClothBrushCollision {
CollisionModifierData *col_data;
struct IsectRayPrecalc isect_precalc;
@@ -699,43 +688,6 @@ static void do_cloth_brush_solve_simulation_task_cb_ex(
BKE_pbvh_vertex_iter_end;
}
-static void cloth_brush_build_nodes_constraints(
- Sculpt *sd,
- Object *ob,
- PBVHNode **nodes,
- int totnode,
- SculptClothSimulation *cloth_sim,
- /* Cannot be const, because it is assigned to a non-const variable.
- * NOLINTNEXTLINE: readability-non-const-parameter. */
- float initial_location[3],
- const float radius)
-{
- Brush *brush = BKE_paint_brush(&sd->paint);
-
- /* TODO: Multi-threaded needs to be disabled for this task until implementing the optimization of
- * storing the constraints per node. */
- /* Currently all constrains are added to the same global array which can't be accessed from
- * different threads. */
- TaskParallelSettings settings;
- BKE_pbvh_parallel_range_settings(&settings, false, totnode);
-
- cloth_sim->created_length_constraints = BLI_edgeset_new("created length constraints");
-
- SculptThreadedTaskData build_constraints_data = {
- .sd = sd,
- .ob = ob,
- .brush = brush,
- .nodes = nodes,
- .cloth_sim = cloth_sim,
- .cloth_sim_initial_location = initial_location,
- .cloth_sim_radius = radius,
- };
- BLI_task_parallel_range(
- 0, totnode, &build_constraints_data, do_cloth_brush_build_constraints_task_cb_ex, &settings);
-
- BLI_edgeset_free(cloth_sim->created_length_constraints);
-}
-
static void cloth_brush_satisfy_constraints(SculptSession *ss,
Brush *brush,
SculptClothSimulation *cloth_sim)
@@ -784,19 +736,27 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss,
cloth_sim->init_pos[v2]) :
1.0f;
+ float deformation_strength = 1.0f;
+ if (constraint->type == SCULPT_CLOTH_CONSTRAINT_DEFORMATION) {
+ deformation_strength = (cloth_sim->deformation_strength[v1] +
+ cloth_sim->deformation_strength[v2]) *
+ 0.5f;
+ }
+
madd_v3_v3fl(cloth_sim->pos[v1],
correction_vector_half,
- 1.0f * mask_v1 * sim_factor_v1 * constraint->strength);
+ 1.0f * mask_v1 * sim_factor_v1 * constraint->strength * deformation_strength);
if (v1 != v2) {
madd_v3_v3fl(cloth_sim->pos[v2],
correction_vector_half,
- -1.0f * mask_v2 * sim_factor_v2 * constraint->strength);
+ -1.0f * mask_v2 * sim_factor_v2 * constraint->strength *
+ deformation_strength);
}
}
}
}
-static void cloth_brush_do_simulation_step(
+void SCULPT_cloth_brush_do_simulation_step(
Sculpt *sd, Object *ob, SculptClothSimulation *cloth_sim, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
@@ -890,6 +850,15 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod
}
}
+ if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) {
+ /* Set the deformation strength to 0. Snake hook will initialize the strength in the required
+ * area. */
+ const int totverts = SCULPT_vertex_count_get(ss);
+ for (int i = 0; i < totverts; i++) {
+ ss->cache->cloth_sim->deformation_strength[i] = 0.0f;
+ }
+ }
+
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(
@@ -897,13 +866,116 @@ static void cloth_brush_apply_brush_foces(Sculpt *sd, Object *ob, PBVHNode **nod
}
/* Public functions. */
+SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss,
+ Brush *brush,
+ const float cloth_mass,
+ const float cloth_damping,
+ const bool use_collisions)
+{
+ const int totverts = SCULPT_vertex_count_get(ss);
+ SculptClothSimulation *cloth_sim;
+
+ cloth_sim = MEM_callocN(sizeof(SculptClothSimulation), "cloth constraints");
+
+ cloth_sim->length_constraints = MEM_callocN(sizeof(SculptClothLengthConstraint) *
+ CLOTH_LENGTH_CONSTRAINTS_BLOCK,
+ "cloth length constraints");
+ cloth_sim->capacity_length_constraints = CLOTH_LENGTH_CONSTRAINTS_BLOCK;
+
+ cloth_sim->acceleration = MEM_calloc_arrayN(
+ totverts, sizeof(float[3]), "cloth sim acceleration");
+ cloth_sim->pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim pos");
+ cloth_sim->prev_pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim prev pos");
+ cloth_sim->last_iteration_pos = MEM_calloc_arrayN(
+ totverts, sizeof(float[3]), "cloth sim last iteration pos");
+ cloth_sim->init_pos = MEM_calloc_arrayN(totverts, sizeof(float[3]), "cloth sim init pos");
+ cloth_sim->length_constraint_tweak = MEM_calloc_arrayN(
+ totverts, sizeof(float), "cloth sim length tweak");
+
+ /* Brush can be NULL for tools that need the solver but don't rely on constraint to deformation
+ * positions. */
+ if (brush && SCULPT_is_cloth_deform_brush(brush)) {
+ cloth_sim->deformation_pos = MEM_calloc_arrayN(
+ totverts, sizeof(float[3]), "cloth sim deformation positions");
+ cloth_sim->deformation_strength = MEM_calloc_arrayN(
+ totverts, sizeof(float), "cloth sim deformation strength");
+ }
+
+ cloth_sim->mass = cloth_mass;
+ cloth_sim->damping = cloth_damping;
+
+ if (use_collisions) {
+ cloth_sim->collider_list = cloth_brush_collider_cache_create(ss->depsgraph);
+ }
+
+ return cloth_sim;
+}
+
+void SCULPT_cloth_brush_build_nodes_constraints(
+ Sculpt *sd,
+ Object *ob,
+ PBVHNode **nodes,
+ int totnode,
+ SculptClothSimulation *cloth_sim,
+ /* Cannot be const, because it is assigned to a non-const variable.
+ * NOLINTNEXTLINE: readability-non-const-parameter. */
+ float initial_location[3],
+ const float radius)
+{
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ /* TODO: Multi-threaded needs to be disabled for this task until implementing the optimization of
+ * storing the constraints per node. */
+ /* Currently all constrains are added to the same global array which can't be accessed from
+ * different threads. */
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, false, totnode);
+
+ cloth_sim->created_length_constraints = BLI_edgeset_new("created length constraints");
+
+ SculptThreadedTaskData build_constraints_data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .cloth_sim = cloth_sim,
+ .cloth_sim_initial_location = initial_location,
+ .cloth_sim_radius = radius,
+ };
+ BLI_task_parallel_range(
+ 0, totnode, &build_constraints_data, do_cloth_brush_build_constraints_task_cb_ex, &settings);
+
+ BLI_edgeset_free(cloth_sim->created_length_constraints);
+}
+
+void SCULPT_cloth_brush_simulation_init(SculptSession *ss, SculptClothSimulation *cloth_sim)
+{
+ const int totverts = SCULPT_vertex_count_get(ss);
+ const bool has_deformation_pos = cloth_sim->deformation_pos != NULL;
+ for (int i = 0; i < totverts; i++) {
+ copy_v3_v3(cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, i));
+ copy_v3_v3(cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, i));
+ copy_v3_v3(cloth_sim->prev_pos[i], SCULPT_vertex_co_get(ss, i));
+ if (has_deformation_pos) {
+ copy_v3_v3(cloth_sim->deformation_pos[i], SCULPT_vertex_co_get(ss, i));
+ cloth_sim->deformation_strength[i] = 1.0f;
+ }
+ }
+}
+
+void SCULPT_cloth_brush_store_simulation_state(SculptSession *ss, SculptClothSimulation *cloth_sim)
+{
+ const int totverts = SCULPT_vertex_count_get(ss);
+ for (int i = 0; i < totverts; i++) {
+ copy_v3_v3(cloth_sim->pos[i], SCULPT_vertex_co_get(ss, i));
+ }
+}
/* Main Brush Function. */
void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
- const int totverts = SCULPT_vertex_count_get(ss);
/* In the first brush step of each symmetry pass, build the constraints for the vertices in all
* nodes inside the simulation's limits. */
@@ -914,42 +986,32 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
/* The simulation structure only needs to be created on the first symmetry pass. */
if (SCULPT_stroke_is_first_brush_step(ss->cache) || !ss->cache->cloth_sim) {
- const bool is_cloth_deform_brush = SCULPT_is_cloth_deform_brush(brush);
- ss->cache->cloth_sim = cloth_brush_simulation_create(
+ ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create(
ss,
brush,
brush->cloth_mass,
brush->cloth_damping,
(brush->flag2 & BRUSH_CLOTH_USE_COLLISION));
- for (int i = 0; i < totverts; i++) {
- copy_v3_v3(ss->cache->cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, i));
- copy_v3_v3(ss->cache->cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, i));
- copy_v3_v3(ss->cache->cloth_sim->prev_pos[i], SCULPT_vertex_co_get(ss, i));
- if (is_cloth_deform_brush) {
- copy_v3_v3(ss->cache->cloth_sim->deformation_pos[i], SCULPT_vertex_co_get(ss, i));
- }
- }
+ SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
}
/* Build the constraints. */
const float radius = ss->cache->initial_radius;
const float limit = radius + (radius * brush->cloth_sim_limit);
- cloth_brush_build_nodes_constraints(
+ SCULPT_cloth_brush_build_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, limit);
return;
}
/* Store the initial state in the simulation. */
- for (int i = 0; i < totverts; i++) {
- copy_v3_v3(ss->cache->cloth_sim->pos[i], SCULPT_vertex_co_get(ss, i));
- }
+ SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
/* Apply forces to the vertices. */
cloth_brush_apply_brush_foces(sd, ob, nodes, totnode);
/* Update and write the simulation to the nodes. */
- cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
+ SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
}
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
@@ -962,6 +1024,7 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
MEM_SAFE_FREE(cloth_sim->length_constraint_tweak);
MEM_SAFE_FREE(cloth_sim->deformation_pos);
MEM_SAFE_FREE(cloth_sim->init_pos);
+ MEM_SAFE_FREE(cloth_sim->deformation_strength);
if (cloth_sim->collider_list) {
BKE_collider_cache_free(&cloth_sim->collider_list);
}
@@ -1043,11 +1106,39 @@ static EnumPropertyItem prop_cloth_filter_type[] = {
{CLOTH_FILTER_GRAVITY, "GRAVITY", 0, "Gravity", "Applies gravity to the simulation"},
{CLOTH_FILTER_INFLATE, "INFLATE", 0, "Inflate", "Inflates the cloth"},
{CLOTH_FILTER_EXPAND, "EXPAND", 0, "Expand", "Expands the cloth's dimensions"},
- {CLOTH_FILTER_PINCH,
- "PINCH",
+ {CLOTH_FILTER_PINCH, "PINCH", 0, "Pinch", "Pulls the cloth to the cursor's start position"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+static EnumPropertyItem prop_cloth_filter_orientation_items[] = {
+ {SCULPT_FILTER_ORIENTATION_LOCAL,
+ "LOCAL",
+ 0,
+ "Local",
+ "Use the local axis to limit the force and set the gravity direction"},
+ {SCULPT_FILTER_ORIENTATION_WORLD,
+ "WORLD",
+ 0,
+ "World",
+ "Use the global axis to limit the force and set the gravity direction"},
+ {SCULPT_FILTER_ORIENTATION_VIEW,
+ "VIEW",
0,
- "Pinch",
- "Pinches the cloth to the point were the cursor was when the filter started"},
+ "View",
+ "Use the view axis to limit the force and set the gravity direction"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+typedef enum eClothFilterForceAxis {
+ CLOTH_FILTER_FORCE_X = 1 << 0,
+ CLOTH_FILTER_FORCE_Y = 1 << 1,
+ CLOTH_FILTER_FORCE_Z = 1 << 2,
+} eClothFilterForceAxis;
+
+static EnumPropertyItem prop_cloth_filter_force_axis_items[] = {
+ {CLOTH_FILTER_FORCE_X, "X", 0, "X", "Apply force in the X axis"},
+ {CLOTH_FILTER_FORCE_Y, "Y", 0, "Y", "Apply force in the Y axis"},
+ {CLOTH_FILTER_FORCE_Z, "Z", 0, "Z", "Apply force in the Z axis"},
{0, NULL, 0, NULL, NULL},
};
@@ -1087,7 +1178,15 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata,
switch (filter_type) {
case CLOTH_FILTER_GRAVITY:
- force[2] = -data->filter_strength * fade;
+ if (ss->filter_cache->orientation == SCULPT_FILTER_ORIENTATION_VIEW) {
+ /* When using the view orientation apply gravity in the -Y axis, this way objects will
+ * fall down instead of backwards. */
+ force[1] = -data->filter_strength * fade;
+ }
+ else {
+ force[2] = -data->filter_strength * fade;
+ }
+ SCULPT_filter_to_object_space(force, ss->filter_cache);
break;
case CLOTH_FILTER_INFLATE: {
float normal[3];
@@ -1105,6 +1204,14 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata,
break;
}
+ SCULPT_filter_to_orientation_space(force, ss->filter_cache);
+ for (int axis = 0; axis < 3; axis++) {
+ if (!ss->filter_cache->enabled_force_axis[axis]) {
+ force[axis] = 0.0f;
+ }
+ }
+ SCULPT_filter_to_object_space(force, ss->filter_cache);
+
add_v3_v3(force, sculpt_gravity);
cloth_brush_apply_force_to_vertex(ss, cloth_sim, force, vd.index);
@@ -1161,7 +1268,7 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent
0, ss->filter_cache->totnode, &data, cloth_filter_apply_forces_task_cb, &settings);
/* Update and write the simulation to the nodes. */
- cloth_brush_do_simulation_step(
+ SCULPT_cloth_brush_do_simulation_step(
sd, ob, ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
if (ss->deform_modifiers_active || ss->shapekey_active) {
@@ -1191,31 +1298,26 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_undo_push_begin("Cloth filter");
- SCULPT_filter_cache_init(ob, sd, SCULPT_UNDO_COORDS);
+ SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
const float cloth_mass = RNA_float_get(op->ptr, "cloth_mass");
const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping");
const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions");
- ss->filter_cache->cloth_sim = cloth_brush_simulation_create(
+ ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create(
ss, NULL, cloth_mass, cloth_damping, use_collisions);
copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss));
- const int totverts = SCULPT_vertex_count_get(ss);
- for (int i = 0; i < totverts; i++) {
- copy_v3_v3(ss->filter_cache->cloth_sim->last_iteration_pos[i], SCULPT_vertex_co_get(ss, i));
- copy_v3_v3(ss->filter_cache->cloth_sim->prev_pos[i], SCULPT_vertex_co_get(ss, i));
- copy_v3_v3(ss->filter_cache->cloth_sim->init_pos[i], SCULPT_vertex_co_get(ss, i));
- }
+ SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim);
float origin[3] = {0.0f, 0.0f, 0.0f};
- cloth_brush_build_nodes_constraints(sd,
- ob,
- ss->filter_cache->nodes,
- ss->filter_cache->totnode,
- ss->filter_cache->cloth_sim,
- origin,
- FLT_MAX);
+ SCULPT_cloth_brush_build_nodes_constraints(sd,
+ ob,
+ ss->filter_cache->nodes,
+ ss->filter_cache->totnode,
+ ss->filter_cache->cloth_sim,
+ origin,
+ FLT_MAX);
const bool use_face_sets = RNA_boolean_get(op->ptr, "use_face_sets");
if (use_face_sets) {
@@ -1225,6 +1327,14 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent
ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
}
+ const int force_axis = RNA_enum_get(op->ptr, "force_axis");
+ ss->filter_cache->enabled_force_axis[0] = force_axis & CLOTH_FILTER_FORCE_X;
+ ss->filter_cache->enabled_force_axis[1] = force_axis & CLOTH_FILTER_FORCE_Y;
+ ss->filter_cache->enabled_force_axis[2] = force_axis & CLOTH_FILTER_FORCE_Z;
+
+ SculptFilterOrientation orientation = RNA_enum_get(op->ptr, "orientation");
+ ss->filter_cache->orientation = orientation;
+
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
@@ -1252,6 +1362,18 @@ void SCULPT_OT_cloth_filter(struct wmOperatorType *ot)
"Operation that is going to be applied to the mesh");
RNA_def_float(
ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter Strength", -10.0f, 10.0f);
+ RNA_def_enum_flag(ot->srna,
+ "force_axis",
+ prop_cloth_filter_force_axis_items,
+ CLOTH_FILTER_FORCE_X | CLOTH_FILTER_FORCE_Y | CLOTH_FILTER_FORCE_Z,
+ "Force axis",
+ "Apply the force in the selected axis");
+ RNA_def_enum(ot->srna,
+ "orientation",
+ prop_cloth_filter_orientation_items,
+ SCULPT_FILTER_ORIENTATION_LOCAL,
+ "Orientation",
+ "Orientation of the axis to limit the filter force");
RNA_def_float(ot->srna,
"cloth_mass",
1.0f,
diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.c b/source/blender/editors/sculpt_paint/sculpt_face_set.c
index 2afa3556dd9..b9265380a35 100644
--- a/source/blender/editors/sculpt_paint/sculpt_face_set.c
+++ b/source/blender/editors/sculpt_paint/sculpt_face_set.c
@@ -71,6 +71,37 @@
#include <math.h>
#include <stdlib.h>
+/* Utils. */
+int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh)
+{
+ int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS);
+ if (!face_sets) {
+ return SCULPT_FACE_SET_NONE;
+ }
+
+ int next_face_set_id = 0;
+ for (int i = 0; i < mesh->totpoly; i++) {
+ next_face_set_id = max_ii(next_face_set_id, abs(face_sets[i]));
+ }
+ next_face_set_id++;
+
+ return next_face_set_id;
+}
+
+void ED_sculpt_face_sets_initialize_none_to_id(struct Mesh *mesh, const int new_id)
+{
+ int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS);
+ if (!face_sets) {
+ return;
+ }
+
+ for (int i = 0; i < mesh->totpoly; i++) {
+ if (face_sets[i] == SCULPT_FACE_SET_NONE) {
+ face_sets[i] = new_id;
+ }
+ }
+}
+
/* Draw Face Sets Brush. */
static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata,
@@ -901,6 +932,25 @@ static int sculpt_face_sets_change_visibility_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
+static int sculpt_face_sets_change_visibility_invoke(bContext *C,
+ wmOperator *op,
+ const wmEvent *event)
+{
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+
+ /* Update the active vertex and Face Set using the cursor position to avoid relying on the paint
+ * cursor updates. */
+ SculptCursorGeometryInfo sgi;
+ float mouse[2];
+ mouse[0] = event->mval[0];
+ mouse[1] = event->mval[1];
+ SCULPT_vertex_random_access_ensure(ss);
+ SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
+
+ return sculpt_face_sets_change_visibility_exec(C, op);
+}
+
void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot)
{
/* Identifiers. */
@@ -910,6 +960,7 @@ void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot)
/* Api callbacks. */
ot->exec = sculpt_face_sets_change_visibility_exec;
+ ot->invoke = sculpt_face_sets_change_visibility_invoke;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
index 576536cac03..c5acf736f3e 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c
@@ -289,7 +289,7 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
}
- SCULPT_filter_cache_init(ob, sd, SCULPT_UNDO_COLOR);
+ SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COLOR);
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
index f9ae91fce7f..619a1b975b6 100644
--- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
+++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c
@@ -50,6 +50,7 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
+#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
@@ -63,6 +64,39 @@
#include <math.h>
#include <stdlib.h>
+/* Filter orientation utils. */
+void SCULPT_filter_to_orientation_space(float r_v[3], struct FilterCache *filter_cache)
+{
+ switch (filter_cache->orientation) {
+ case SCULPT_FILTER_ORIENTATION_LOCAL:
+ /* Do nothing, Sculpt Mode already works in object space. */
+ break;
+ case SCULPT_FILTER_ORIENTATION_WORLD:
+ mul_mat3_m4_v3(filter_cache->obmat, r_v);
+ break;
+ case SCULPT_FILTER_ORIENTATION_VIEW:
+ mul_mat3_m4_v3(filter_cache->obmat, r_v);
+ mul_mat3_m4_v3(filter_cache->viewmat, r_v);
+ break;
+ }
+}
+
+void SCULPT_filter_to_object_space(float r_v[3], struct FilterCache *filter_cache)
+{
+ switch (filter_cache->orientation) {
+ case SCULPT_FILTER_ORIENTATION_LOCAL:
+ /* Do nothing, Sculpt Mode already works in object space. */
+ break;
+ case SCULPT_FILTER_ORIENTATION_WORLD:
+ mul_mat3_m4_v3(filter_cache->obmat_inv, r_v);
+ break;
+ case SCULPT_FILTER_ORIENTATION_VIEW:
+ mul_mat3_m4_v3(filter_cache->viewmat_inv, r_v);
+ mul_mat3_m4_v3(filter_cache->obmat_inv, r_v);
+ break;
+ }
+}
+
static void filter_cache_init_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
@@ -73,7 +107,7 @@ static void filter_cache_init_task_cb(void *__restrict userdata,
SCULPT_undo_push_node(data->ob, node, data->filter_undo_type);
}
-void SCULPT_filter_cache_init(Object *ob, Sculpt *sd, const int undo_type)
+void SCULPT_filter_cache_init(bContext *C, Object *ob, Sculpt *sd, const int undo_type)
{
SculptSession *ss = ob->sculpt;
PBVH *pbvh = ob->sculpt->pbvh;
@@ -117,6 +151,16 @@ void SCULPT_filter_cache_init(Object *ob, Sculpt *sd, const int undo_type)
BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode);
BLI_task_parallel_range(
0, ss->filter_cache->totnode, &data, filter_cache_init_task_cb, &settings);
+
+ /* Setup orientation matrices. */
+ copy_m4_m4(ss->filter_cache->obmat, ob->obmat);
+ invert_m4_m4(ss->filter_cache->obmat_inv, ob->obmat);
+
+ Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
+ ViewContext vc;
+ ED_view3d_viewcontext_init(C, &vc, depsgraph);
+ copy_m4_m4(ss->filter_cache->viewmat, vc.rv3d->viewmat);
+ copy_m4_m4(ss->filter_cache->viewmat_inv, vc.rv3d->viewinv);
}
void SCULPT_filter_cache_free(SculptSession *ss)
@@ -132,11 +176,12 @@ void SCULPT_filter_cache_free(SculptSession *ss)
MEM_SAFE_FREE(ss->filter_cache->automask);
MEM_SAFE_FREE(ss->filter_cache->surface_smooth_laplacian_disp);
MEM_SAFE_FREE(ss->filter_cache->sharpen_factor);
- MEM_SAFE_FREE(ss->filter_cache->sharpen_detail_directions);
+ MEM_SAFE_FREE(ss->filter_cache->detail_directions);
+ MEM_SAFE_FREE(ss->filter_cache->limit_surface_co);
MEM_SAFE_FREE(ss->filter_cache);
}
-typedef enum eSculptMeshFilterTypes {
+typedef enum eSculptMeshFilterType {
MESH_FILTER_SMOOTH = 0,
MESH_FILTER_SCALE = 1,
MESH_FILTER_INFLATE = 2,
@@ -146,7 +191,9 @@ typedef enum eSculptMeshFilterTypes {
MESH_FILTER_RELAX_FACE_SETS = 6,
MESH_FILTER_SURFACE_SMOOTH = 7,
MESH_FILTER_SHARPEN = 8,
-} eSculptMeshFilterTypes;
+ MESH_FILTER_ENHANCE_DETAILS = 9,
+ MESH_FILTER_ERASE_DISPLACEMENT = 10,
+} eSculptMeshFilterType;
static EnumPropertyItem prop_mesh_filter_types[] = {
{MESH_FILTER_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth mesh"},
@@ -166,6 +213,16 @@ static EnumPropertyItem prop_mesh_filter_types[] = {
"Surface Smooth",
"Smooth the surface of the mesh, preserving the volume"},
{MESH_FILTER_SHARPEN, "SHARPEN", 0, "Sharpen", "Sharpen the cavities of the mesh"},
+ {MESH_FILTER_ENHANCE_DETAILS,
+ "ENHANCE_DETAILS",
+ 0,
+ "Enhance Details",
+ "Enhance the high frequency surface detail"},
+ {MESH_FILTER_ERASE_DISPLACEMENT,
+ "ERASE_DISCPLACEMENT",
+ 0,
+ "Erase Displacement",
+ "Deletes the displacement of the Multires Modifier"},
{0, NULL, 0, NULL, NULL},
};
@@ -182,13 +239,33 @@ static EnumPropertyItem prop_mesh_filter_deform_axis_items[] = {
{0, NULL, 0, NULL, NULL},
};
-static bool sculpt_mesh_filter_needs_pmap(int filter_type, bool use_face_sets)
+static EnumPropertyItem prop_mesh_filter_orientation_items[] = {
+ {SCULPT_FILTER_ORIENTATION_LOCAL,
+ "LOCAL",
+ 0,
+ "Local",
+ "Use the local axis to limit the displacement"},
+ {SCULPT_FILTER_ORIENTATION_WORLD,
+ "WORLD",
+ 0,
+ "World",
+ "Use the global axis to limit the displacement"},
+ {SCULPT_FILTER_ORIENTATION_VIEW,
+ "VIEW",
+ 0,
+ "View",
+ "Use the view axis to limit the displacement"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+static bool sculpt_mesh_filter_needs_pmap(eSculptMeshFilterType filter_type, bool use_face_sets)
{
return use_face_sets || ELEM(filter_type,
MESH_FILTER_SMOOTH,
MESH_FILTER_RELAX,
MESH_FILTER_RELAX_FACE_SETS,
MESH_FILTER_SURFACE_SMOOTH,
+ MESH_FILTER_ENHANCE_DETAILS,
MESH_FILTER_SHARPEN);
}
@@ -200,7 +277,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
PBVHNode *node = data->nodes[i];
- const int filter_type = data->filter_type;
+ const eSculptMeshFilterType filter_type = data->filter_type;
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]);
@@ -306,7 +383,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
const uint *hash_co = (const uint *)orig_co;
const uint hash = BLI_hash_int_2d(hash_co[0], hash_co[1]) ^
BLI_hash_int_2d(hash_co[2], ss->filter_cache->random_seed);
- mul_v3_fl(normal, hash * (1.0f / 0xFFFFFFFF) - 0.5f);
+ mul_v3_fl(normal, hash * (1.0f / (float)0xFFFFFFFF) - 0.5f);
mul_v3_v3fl(disp, normal, fade);
break;
}
@@ -362,7 +439,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
if (ss->filter_cache->sharpen_intensify_detail_strength > 0.0f) {
float detail_strength[3];
normal_short_to_float_v3(detail_strength, orig_data.no);
- copy_v3_v3(detail_strength, ss->filter_cache->sharpen_detail_directions[vd.index]);
+ copy_v3_v3(detail_strength, ss->filter_cache->detail_directions[vd.index]);
madd_v3_v3fl(disp,
detail_strength,
-ss->filter_cache->sharpen_intensify_detail_strength *
@@ -370,13 +447,25 @@ static void mesh_filter_task_cb(void *__restrict userdata,
}
break;
}
+
+ case MESH_FILTER_ENHANCE_DETAILS: {
+ mul_v3_v3fl(disp, ss->filter_cache->detail_directions[vd.index], -fabsf(fade));
+ } break;
+ case MESH_FILTER_ERASE_DISPLACEMENT: {
+ fade = clamp_f(fade, 0.0f, 1.0f);
+ sub_v3_v3v3(disp, ss->filter_cache->limit_surface_co[vd.index], orig_co);
+ mul_v3_fl(disp, fade);
+ break;
+ }
}
+ SCULPT_filter_to_orientation_space(disp, ss->filter_cache);
for (int it = 0; it < 3; it++) {
if (!ss->filter_cache->enabled_axis[it]) {
disp[it] = 0.0f;
}
}
+ SCULPT_filter_to_object_space(disp, ss->filter_cache);
if (ELEM(filter_type, MESH_FILTER_SURFACE_SMOOTH, MESH_FILTER_SHARPEN)) {
madd_v3_v3v3fl(final_pos, vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
@@ -394,32 +483,83 @@ static void mesh_filter_task_cb(void *__restrict userdata,
BKE_pbvh_node_mark_update(node);
}
-static void mesh_filter_sharpen_init_factors(SculptSession *ss)
+static void mesh_filter_enhance_details_init_directions(SculptSession *ss)
{
const int totvert = SCULPT_vertex_count_get(ss);
+ FilterCache *filter_cache = ss->filter_cache;
+
+ filter_cache->detail_directions = MEM_malloc_arrayN(
+ totvert, sizeof(float[3]), "detail directions");
for (int i = 0; i < totvert; i++) {
float avg[3];
SCULPT_neighbor_coords_average(ss, avg, i);
- sub_v3_v3v3(ss->filter_cache->sharpen_detail_directions[i], avg, SCULPT_vertex_co_get(ss, i));
- ss->filter_cache->sharpen_factor[i] = len_v3(ss->filter_cache->sharpen_detail_directions[i]);
+ sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i));
+ }
+}
+
+static void mesh_filter_surface_smooth_init(SculptSession *ss,
+ const float shape_preservation,
+ const float current_vertex_displacement)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ FilterCache *filter_cache = ss->filter_cache;
+
+ filter_cache->surface_smooth_laplacian_disp = MEM_malloc_arrayN(
+ totvert, sizeof(float[3]), "surface smooth displacement");
+ filter_cache->surface_smooth_shape_preservation = shape_preservation;
+ filter_cache->surface_smooth_current_vertex = current_vertex_displacement;
+}
+
+static void mesh_filter_init_limit_surface_co(SculptSession *ss)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ FilterCache *filter_cache = ss->filter_cache;
+
+ filter_cache->limit_surface_co = MEM_malloc_arrayN(
+ sizeof(float[3]), totvert, "limit surface co");
+ for (int i = 0; i < totvert; i++) {
+ SCULPT_vertex_limit_surface_get(ss, i, filter_cache->limit_surface_co[i]);
+ }
+}
+
+static void mesh_filter_sharpen_init(SculptSession *ss,
+ const float smooth_ratio,
+ const float intensify_detail_strength,
+ const int curvature_smooth_iterations)
+{
+ const int totvert = SCULPT_vertex_count_get(ss);
+ FilterCache *filter_cache = ss->filter_cache;
+
+ filter_cache->sharpen_smooth_ratio = smooth_ratio;
+ filter_cache->sharpen_intensify_detail_strength = intensify_detail_strength;
+ filter_cache->sharpen_curvature_smooth_iterations = curvature_smooth_iterations;
+ filter_cache->sharpen_factor = MEM_malloc_arrayN(sizeof(float), totvert, "sharpen factor");
+ filter_cache->detail_directions = MEM_malloc_arrayN(
+ totvert, sizeof(float[3]), "sharpen detail direction");
+
+ for (int i = 0; i < totvert; i++) {
+ float avg[3];
+ SCULPT_neighbor_coords_average(ss, avg, i);
+ sub_v3_v3v3(filter_cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i));
+ filter_cache->sharpen_factor[i] = len_v3(filter_cache->detail_directions[i]);
}
float max_factor = 0.0f;
for (int i = 0; i < totvert; i++) {
- if (ss->filter_cache->sharpen_factor[i] > max_factor) {
- max_factor = ss->filter_cache->sharpen_factor[i];
+ if (filter_cache->sharpen_factor[i] > max_factor) {
+ max_factor = filter_cache->sharpen_factor[i];
}
}
max_factor = 1.0f / max_factor;
for (int i = 0; i < totvert; i++) {
- ss->filter_cache->sharpen_factor[i] *= max_factor;
- ss->filter_cache->sharpen_factor[i] = 1.0f - pow2f(1.0f - ss->filter_cache->sharpen_factor[i]);
+ filter_cache->sharpen_factor[i] *= max_factor;
+ filter_cache->sharpen_factor[i] = 1.0f - pow2f(1.0f - filter_cache->sharpen_factor[i]);
}
/* Smooth the calculated factors and directions to remove high frecuency detail. */
for (int smooth_iterations = 0;
- smooth_iterations < ss->filter_cache->sharpen_curvature_smooth_iterations;
+ smooth_iterations < filter_cache->sharpen_curvature_smooth_iterations;
smooth_iterations++) {
for (int i = 0; i < totvert; i++) {
float direction_avg[3] = {0.0f, 0.0f, 0.0f};
@@ -428,15 +568,15 @@ static void mesh_filter_sharpen_init_factors(SculptSession *ss)
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) {
- add_v3_v3(direction_avg, ss->filter_cache->sharpen_detail_directions[ni.index]);
- sharpen_avg += ss->filter_cache->sharpen_factor[ni.index];
+ add_v3_v3(direction_avg, filter_cache->detail_directions[ni.index]);
+ sharpen_avg += filter_cache->sharpen_factor[ni.index];
total++;
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
if (total > 0) {
- mul_v3_v3fl(ss->filter_cache->sharpen_detail_directions[i], direction_avg, 1.0f / total);
- ss->filter_cache->sharpen_factor[i] = sharpen_avg / total;
+ mul_v3_v3fl(filter_cache->detail_directions[i], direction_avg, 1.0f / total);
+ filter_cache->sharpen_factor[i] = sharpen_avg / total;
}
}
}
@@ -481,7 +621,7 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
- int filter_type = RNA_enum_get(op->ptr, "type");
+ eSculptMeshFilterType filter_type = RNA_enum_get(op->ptr, "type");
float filter_strength = RNA_float_get(op->ptr, "strength");
const bool use_face_sets = RNA_boolean_get(op->ptr, "use_face_sets");
@@ -545,17 +685,21 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
Object *ob = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
- int filter_type = RNA_enum_get(op->ptr, "type");
SculptSession *ss = ob->sculpt;
- PBVH *pbvh = ob->sculpt->pbvh;
- int deform_axis = RNA_enum_get(op->ptr, "deform_axis");
+ const eMeshFilterDeformAxis deform_axis = RNA_enum_get(op->ptr, "deform_axis");
+ const eSculptMeshFilterType filter_type = RNA_enum_get(op->ptr, "type");
+ const bool use_face_sets = RNA_boolean_get(op->ptr, "use_face_sets");
+ const bool needs_topology_info = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
+
if (deform_axis == 0) {
+ /* All axis are disabled, so the filter is not going to produce any deformation. */
return OPERATOR_CANCELLED;
}
- if (RNA_boolean_get(op->ptr, "use_face_sets")) {
- /* Update the active vertex */
+ if (use_face_sets) {
+ /* Update the active face set manually as the paint cursor is not enabled when using the Mesh
+ * Filter Tool. */
float mouse[2];
SculptCursorGeometryInfo sgi;
mouse[0] = event->mval[0];
@@ -563,63 +707,57 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false);
}
- const bool use_face_sets = RNA_boolean_get(op->ptr, "use_face_sets");
-
SCULPT_vertex_random_access_ensure(ss);
-
- const bool needs_topology_info = sculpt_mesh_filter_needs_pmap(filter_type, use_face_sets);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_topology_info, false, false);
if (needs_topology_info) {
SCULPT_boundary_info_ensure(ob);
}
- const int totvert = SCULPT_vertex_count_get(ss);
- if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_topology_info && !ob->sculpt->pmap) {
- return OPERATOR_CANCELLED;
- }
-
- SCULPT_undo_push_begin("Mesh filter");
-
- if (ELEM(filter_type, MESH_FILTER_RELAX, MESH_FILTER_RELAX_FACE_SETS)) {
- SCULPT_boundary_info_ensure(ob);
- }
-
- SCULPT_filter_cache_init(ob, sd, SCULPT_UNDO_COORDS);
-
- if (use_face_sets) {
- ss->filter_cache->active_face_set = SCULPT_active_face_set_get(ss);
- }
- else {
- ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
- }
-
- if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SURFACE_SMOOTH) {
- ss->filter_cache->surface_smooth_laplacian_disp = MEM_mallocN(sizeof(float[3]) * totvert,
- "surface smooth disp");
- ss->filter_cache->surface_smooth_shape_preservation = RNA_float_get(
- op->ptr, "surface_smooth_shape_preservation");
- ss->filter_cache->surface_smooth_current_vertex = RNA_float_get(
- op->ptr, "surface_smooth_current_vertex");
- }
+ SCULPT_undo_push_begin("Mesh Filter");
- if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SHARPEN) {
- ss->filter_cache->sharpen_smooth_ratio = RNA_float_get(op->ptr, "sharpen_smooth_ratio");
- ss->filter_cache->sharpen_intensify_detail_strength = RNA_float_get(
- op->ptr, "sharpen_intensify_detail_strength");
- ss->filter_cache->sharpen_curvature_smooth_iterations = RNA_int_get(
- op->ptr, "sharpen_curvature_smooth_iterations");
+ SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
- ss->filter_cache->sharpen_factor = MEM_mallocN(sizeof(float) * totvert, "sharpen factor");
- ss->filter_cache->sharpen_detail_directions = MEM_malloc_arrayN(
- totvert, sizeof(float[3]), "sharpen detail direction");
+ FilterCache *filter_cache = ss->filter_cache;
+ filter_cache->active_face_set = use_face_sets ? SCULPT_active_face_set_get(ss) :
+ SCULPT_FACE_SET_NONE;
- mesh_filter_sharpen_init_factors(ss);
+ switch (filter_type) {
+ case MESH_FILTER_SURFACE_SMOOTH: {
+ const float shape_preservation = RNA_float_get(op->ptr, "surface_smooth_shape_preservation");
+ const float current_vertex_displacement = RNA_float_get(op->ptr,
+ "surface_smooth_current_vertex");
+ mesh_filter_surface_smooth_init(ss, shape_preservation, current_vertex_displacement);
+ break;
+ }
+ case MESH_FILTER_SHARPEN: {
+ const float smooth_ratio = RNA_float_get(op->ptr, "sharpen_smooth_ratio");
+ const float intensify_detail_strength = RNA_float_get(op->ptr,
+ "sharpen_intensify_detail_strength");
+ const int curvature_smooth_iterations = RNA_int_get(op->ptr,
+ "sharpen_curvature_smooth_iterations");
+ mesh_filter_sharpen_init(
+ ss, smooth_ratio, intensify_detail_strength, curvature_smooth_iterations);
+ break;
+ }
+ case MESH_FILTER_ENHANCE_DETAILS: {
+ mesh_filter_enhance_details_init_directions(ss);
+ break;
+ }
+ case MESH_FILTER_ERASE_DISPLACEMENT: {
+ mesh_filter_init_limit_surface_co(ss);
+ break;
+ }
+ default:
+ break;
}
ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
+ SculptFilterOrientation orientation = RNA_enum_get(op->ptr, "orientation");
+ ss->filter_cache->orientation = orientation;
+
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
@@ -653,6 +791,12 @@ void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
MESH_FILTER_DEFORM_X | MESH_FILTER_DEFORM_Y | MESH_FILTER_DEFORM_Z,
"Deform axis",
"Apply the deformation in the selected axis");
+ RNA_def_enum(ot->srna,
+ "orientation",
+ prop_mesh_filter_orientation_items,
+ SCULPT_FILTER_ORIENTATION_LOCAL,
+ "Orientation",
+ "Orientation of the axis to limit the filter displacement");
ot->prop = RNA_def_boolean(ot->srna,
"use_face_sets",
false,
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index b11a7005fb5..47a375a2318 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -100,10 +100,16 @@ const float *SCULPT_vertex_color_get(SculptSession *ss, int index);
const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, int index);
void SCULPT_vertex_persistent_normal_get(SculptSession *ss, int index, float no[3]);
-/* Returs the info of the limit surface when Multires is available, otherwise it returns the
+/* Returns the info of the limit surface when Multires is available, otherwise it returns the
* current coordinate of the vertex. */
void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3]);
+/* Returns the pointer to the coordinates that should be edited from a brush tool iterator
+ * depending on the given deformation target. */
+float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss,
+ const int deform_target,
+ PBVHVertexIter *iter);
+
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
typedef struct SculptVertexNeighborIter {
/* Storage */
@@ -337,7 +343,7 @@ float *SCULPT_boundary_automasking_init(Object *ob,
float *automask_factor);
/* Filters. */
-void SCULPT_filter_cache_init(Object *ob, Sculpt *sd, const int undo_type);
+void SCULPT_filter_cache_init(struct bContext *C, Object *ob, Sculpt *sd, const int undo_type);
void SCULPT_filter_cache_free(SculptSession *ss);
void SCULPT_mask_filter_smooth_apply(
@@ -350,8 +356,33 @@ void SCULPT_do_cloth_brush(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode);
+
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim);
+struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct SculptSession *ss,
+ struct Brush *brush,
+ const float cloth_mass,
+ const float cloth_damping,
+ const bool use_collisions);
+void SCULPT_cloth_brush_simulation_init(struct SculptSession *ss,
+ struct SculptClothSimulation *cloth_sim);
+void SCULPT_cloth_brush_store_simulation_state(struct SculptSession *ss,
+ struct SculptClothSimulation *cloth_sim);
+
+void SCULPT_cloth_brush_do_simulation_step(struct Sculpt *sd,
+ struct Object *ob,
+ struct SculptClothSimulation *cloth_sim,
+ struct PBVHNode **nodes,
+ int totnode);
+
+void SCULPT_cloth_brush_build_nodes_constraints(struct Sculpt *sd,
+ struct Object *ob,
+ struct PBVHNode **nodes,
+ int totnode,
+ struct SculptClothSimulation *cloth_sim,
+ float initial_location[3],
+ const float radius);
+
void SCULPT_cloth_simulation_limits_draw(const uint gpuattr,
const struct Brush *brush,
const float location[3],
@@ -367,8 +398,13 @@ void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
BLI_INLINE bool SCULPT_is_cloth_deform_brush(const Brush *brush)
{
- return brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
- brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB;
+ return (brush->sculpt_tool == SCULPT_TOOL_CLOTH && ELEM(brush->cloth_deform_type,
+ BRUSH_CLOTH_DEFORM_GRAB,
+ BRUSH_CLOTH_DEFORM_SNAKE_HOOK)) ||
+ /* All brushes that are not the cloth brush deform the simulation using softbody
+ constriants instead of applying forces. */
+ (brush->sculpt_tool != SCULPT_TOOL_CLOTH &&
+ brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM);
}
/* Pose Brush. */
@@ -398,9 +434,10 @@ void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
/* Boundary Brush. */
struct SculptBoundary *SCULPT_boundary_data_init(Object *object,
+ Brush *brush,
const int initial_vertex,
const float radius);
-void SCULPT_boundary_data_free(struct SculptBoundary *bdata);
+void SCULPT_boundary_data_free(struct SculptBoundary *boundary);
void SCULPT_do_boundary_brush(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
@@ -864,6 +901,9 @@ typedef struct StrokeCache {
/* Pose brush */
struct SculptPoseIKChain *pose_ik_chain;
+ /* Enhance Details. */
+ float (*detail_directions)[3];
+
/* Clay Thumb brush */
/* Angle of the front tilting plane of the brush to simulate clay accumulation. */
float clay_thumb_front_angle;
@@ -879,7 +919,7 @@ typedef struct StrokeCache {
float true_initial_normal[3];
/* Boundary brush */
- struct SculptBoundary *bdata[PAINT_SYMM_AREAS];
+ struct SculptBoundary *boundaries[PAINT_SYMM_AREAS];
/* Surface Smooth Brush */
/* Stores the displacement produced by the laplacian step of HC smooth. */
@@ -919,8 +959,19 @@ typedef struct StrokeCache {
} StrokeCache;
+/* Sculpt Filters */
+typedef enum SculptFilterOrientation {
+ SCULPT_FILTER_ORIENTATION_LOCAL = 0,
+ SCULPT_FILTER_ORIENTATION_WORLD = 1,
+ SCULPT_FILTER_ORIENTATION_VIEW = 2,
+} SculptFilterOrientation;
+
+void SCULPT_filter_to_orientation_space(float r_v[3], struct FilterCache *filter_cache);
+void SCULPT_filter_to_object_space(float r_v[3], struct FilterCache *filter_cache);
+
typedef struct FilterCache {
bool enabled_axis[3];
+ bool enabled_force_axis[3];
int random_seed;
/* Used for alternating between filter operations in filters that need to apply different ones to
@@ -937,7 +988,17 @@ typedef struct FilterCache {
float sharpen_intensify_detail_strength;
int sharpen_curvature_smooth_iterations;
float *sharpen_factor;
- float (*sharpen_detail_directions)[3];
+ float (*detail_directions)[3];
+
+ /* Filter orientaiton. */
+ SculptFilterOrientation orientation;
+ float obmat[4][4];
+ float obmat_inv[4][4];
+ float viewmat[4][4];
+ float viewmat_inv[4][4];
+
+ /* Displacement eraser. */
+ float (*limit_surface_co)[3];
/* unmasked nodes */
PBVHNode **nodes;
diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.c b/source/blender/editors/sculpt_paint/sculpt_pose.c
index 4d41d069155..e53e33c1186 100644
--- a/source/blender/editors/sculpt_paint/sculpt_pose.c
+++ b/source/blender/editors/sculpt_paint/sculpt_pose.c
@@ -165,6 +165,7 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
SculptPoseIKChainSegment *segments = ik_chain->segments;
+ const Brush *brush = data->brush;
PBVHVertexIter vd;
float disp[3], new_co[3];
@@ -206,7 +207,9 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata,
/* Apply the accumulated displacement to the vertex. */
add_v3_v3v3(final_pos, orig_data.co, total_disp);
- copy_v3_v3(vd.co, final_pos);
+
+ float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
+ copy_v3_v3(target_co, final_pos);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c
index 2b93298ac4a..87ee7480c92 100644
--- a/source/blender/editors/sculpt_paint/sculpt_smooth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c
@@ -66,25 +66,40 @@ void SCULPT_neighbor_coords_average_interior(SculptSession *ss, float result[3],
{
float avg[3] = {0.0f, 0.0f, 0.0f};
int total = 0;
-
- if (SCULPT_vertex_is_boundary(ss, index)) {
- copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
- return;
- }
+ int neighbor_count = 0;
+ const bool is_boundary = SCULPT_vertex_is_boundary(ss, index);
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
- add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
- total++;
+ neighbor_count++;
+ if (is_boundary) {
+ /* Boundary vertices use only other boundary vertices. */
+ if (SCULPT_vertex_is_boundary(ss, ni.index)) {
+ add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
+ total++;
+ }
+ }
+ else {
+ /* Interior vertices use all neighbors. */
+ add_v3_v3(avg, SCULPT_vertex_co_get(ss, ni.index));
+ total++;
+ }
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
- if (total > 0) {
- mul_v3_v3fl(result, avg, 1.0f / total);
+ /* Do not modify corner vertices. */
+ if (neighbor_count <= 2) {
+ copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
+ return;
}
- else {
+
+ /* Avoid division by 0 when there are no neighbors. */
+ if (total == 0) {
copy_v3_v3(result, SCULPT_vertex_co_get(ss, index));
+ return;
}
+
+ mul_v3_v3fl(result, avg, 1.0f / total);
}
/* For bmesh: Average surrounding verts based on an orthogonality measure.
@@ -195,6 +210,85 @@ void SCULPT_neighbor_color_average(SculptSession *ss, float result[4], int index
}
}
+static void do_enhance_details_brush_task_cb_ex(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ Sculpt *sd = data->sd;
+ const Brush *brush = data->brush;
+
+ PBVHVertexIter vd;
+
+ float bstrength = ss->cache->bstrength;
+ CLAMP(bstrength, -1.0f, 1.0f);
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+ ss, &test, data->brush->falloff_shape);
+
+ const int thread_id = BLI_task_parallel_thread_id(tls);
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ const float fade = bstrength * SCULPT_brush_strength_factor(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ thread_id);
+
+ float disp[3];
+ madd_v3_v3v3fl(disp, vd.co, ss->cache->detail_directions[vd.index], fade);
+ SCULPT_clip(sd, ss, vd.co, disp);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void SCULPT_enhance_details_brush(Sculpt *sd,
+ Object *ob,
+ PBVHNode **nodes,
+ const int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ SCULPT_vertex_random_access_ensure(ss);
+ SCULPT_boundary_info_ensure(ob);
+
+ if (SCULPT_stroke_is_first_brush_step(ss->cache)) {
+ const int totvert = SCULPT_vertex_count_get(ss);
+ ss->cache->detail_directions = MEM_malloc_arrayN(
+ totvert, 3 * sizeof(float), "details directions");
+
+ for (int i = 0; i < totvert; i++) {
+ float avg[3];
+ SCULPT_neighbor_coords_average(ss, avg, i);
+ sub_v3_v3v3(ss->cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, i));
+ }
+ }
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ };
+
+ TaskParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, true, totnode);
+ BLI_task_parallel_range(0, totnode, &data, do_enhance_details_brush_task_cb_ex, &settings);
+}
+
static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
@@ -237,7 +331,7 @@ static void do_smooth_brush_task_cb_ex(void *__restrict userdata,
}
else {
float avg[3], val[3];
- SCULPT_neighbor_coords_average(ss, avg, vd.index);
+ SCULPT_neighbor_coords_average_interior(ss, avg, vd.index);
sub_v3_v3v3(val, avg, vd.co);
madd_v3_v3v3fl(val, vd.co, val, fade);
SCULPT_clip(sd, ss, vd.co, val);
@@ -300,7 +394,14 @@ void SCULPT_smooth(Sculpt *sd,
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
- SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
+ if (ss->cache->bstrength <= 0.0f) {
+ /* Invert mode, intensify details. */
+ SCULPT_enhance_details_brush(sd, ob, nodes, totnode);
+ }
+ else {
+ /* Regular mode, smooth. */
+ SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
+ }
}
/* HC Smooth Algorithm. */
diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.c b/source/blender/editors/sculpt_paint/sculpt_transform.c
index 4c54a0465b9..b52b04eba3a 100644
--- a/source/blender/editors/sculpt_paint/sculpt_transform.c
+++ b/source/blender/editors/sculpt_paint/sculpt_transform.c
@@ -76,7 +76,7 @@ void ED_sculpt_init_transform(struct bContext *C)
ss->pivot_rot[3] = 1.0f;
SCULPT_vertex_random_access_ensure(ss);
- SCULPT_filter_cache_init(ob, sd, SCULPT_UNDO_COORDS);
+ SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
}
static void sculpt_transform_task_cb(void *__restrict userdata,
@@ -326,6 +326,12 @@ static int sculpt_set_pivot_position_exec(bContext *C, wmOperator *op)
MEM_SAFE_FREE(nodes);
}
+ /* Update the viewport navigation rotation origin. */
+ UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings;
+ copy_v3_v3(ups->average_stroke_accum, ss->pivot_pos);
+ ups->average_stroke_counter = 1;
+ ups->last_stroke_valid = true;
+
ED_region_tag_redraw(region);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);