diff options
author | Campbell Barton <campbell@blender.org> | 2022-01-07 06:22:02 +0300 |
---|---|---|
committer | Campbell Barton <campbell@blender.org> | 2022-01-07 06:28:49 +0300 |
commit | f48164b5ea7f41d639e511aee5d336657ebf3106 (patch) | |
tree | af405d9294dfe2369bab8295c4ec91832a23becd /source/blender/editors/sculpt_paint/sculpt_brush_types.c | |
parent | 3d3bc748849834ef74563deb603ab43859cffeeb (diff) |
Cleanup: rename sculpt_brushes.c -> sculpt_brush_types.c
This better differentiates sculpt brush types with brush data-blocks,
since the same sculpt brush type may be used for many brushes.
Diffstat (limited to 'source/blender/editors/sculpt_paint/sculpt_brush_types.c')
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_brush_types.c | 2847 |
1 files changed, 2847 insertions, 0 deletions
diff --git a/source/blender/editors/sculpt_paint/sculpt_brush_types.c b/source/blender/editors/sculpt_paint/sculpt_brush_types.c new file mode 100644 index 00000000000..8842d93410c --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_brush_types.c @@ -0,0 +1,2847 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2006 by Nicholas Bishop + * All rights reserved. + * Implements the Sculpt Mode tools + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_math_color.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "PIL_time.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_image.h" +#include "BKE_kelvinlet.h" +#include "BKE_key.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_mesh_mirror.h" +#include "BKE_modifier.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_particle.h" +#include "BKE_pbvh.h" +#include "BKE_pointcache.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_subdiv_ccg.h" +#include "BKE_subsurf.h" + +#include "DEG_depsgraph.h" + +#include "IMB_colormanagement.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +/* -------------------------------------------------------------------- */ +/** \name SculptProjectVector + * + * Fast-path for #project_plane_v3_v3v3 + * \{ */ + +typedef struct SculptProjectVector { + float plane[3]; + float len_sq; + float len_sq_inv_neg; + bool is_valid; + +} SculptProjectVector; + +static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip) +{ + float d = plane_point_side_v3(plane, co); + if (flip) { + d = -d; + } + return d <= 0.0f; +} + +/** + * \param plane: Direction, can be any length. + */ +static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3]) +{ + copy_v3_v3(spvc->plane, plane); + spvc->len_sq = len_squared_v3(spvc->plane); + spvc->is_valid = (spvc->len_sq > FLT_EPSILON); + spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f; +} + +/** + * Calculate the projection. + */ +static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3]) +{ +#if 0 + project_plane_v3_v3v3(r_vec, vec, spvc->plane); +#else + /* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */ + madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg); +#endif +} + +static void calc_sculpt_plane( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) && + (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || + !(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) { + switch (brush->sculpt_plane) { + case SCULPT_DISP_DIR_VIEW: + copy_v3_v3(r_area_no, ss->cache->true_view_normal); + break; + + case SCULPT_DISP_DIR_X: + ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f); + break; + + case SCULPT_DISP_DIR_Y: + ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f); + break; + + case SCULPT_DISP_DIR_Z: + ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); + break; + + case SCULPT_DISP_DIR_AREA: + SCULPT_calc_area_normal_and_center(sd, ob, nodes, totnode, r_area_no, r_area_co); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(r_area_no, r_area_no, ss->cache->view_normal); + normalize_v3(r_area_no); + } + break; + + default: + break; + } + + /* For flatten center. */ + /* Flatten center has not been calculated yet if we are not using the area normal. */ + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA) { + SCULPT_calc_area_center(sd, ob, nodes, totnode, r_area_co); + } + + /* For area normal. */ + if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && + (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + copy_v3_v3(r_area_no, ss->cache->sculpt_normal); + } + else { + copy_v3_v3(ss->cache->sculpt_normal, r_area_no); + } + + /* For flatten center. */ + if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && + (brush->flag & BRUSH_ORIGINAL_PLANE)) { + copy_v3_v3(r_area_co, ss->cache->last_center); + } + else { + copy_v3_v3(ss->cache->last_center, r_area_co); + } + } + else { + /* For area normal. */ + copy_v3_v3(r_area_no, ss->cache->sculpt_normal); + + /* For flatten center. */ + copy_v3_v3(r_area_co, ss->cache->last_center); + + /* For area normal. */ + flip_v3(r_area_no, ss->cache->mirror_symmetry_pass); + + /* For flatten center. */ + flip_v3(r_area_co, ss->cache->mirror_symmetry_pass); + + /* For area normal. */ + mul_m4_v3(ss->cache->symm_rot_mat, r_area_no); + + /* For flatten center. */ + mul_m4_v3(ss->cache->symm_rot_mat, r_area_co); + + /* Shift the plane for the current tile. */ + add_v3_v3(r_area_co, ss->cache->plane_offset); + } +} + +static void sculpt_rake_rotate(const SculptSession *ss, + const float sculpt_co[3], + const float v_co[3], + float factor, + float r_delta[3]) +{ + float vec_rot[3]; + +#if 0 + /* lerp */ + sub_v3_v3v3(vec_rot, v_co, sculpt_co); + mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot); + add_v3_v3(vec_rot, sculpt_co); + sub_v3_v3v3(r_delta, vec_rot, v_co); + mul_v3_fl(r_delta, factor); +#else + /* slerp */ + float q_interp[4]; + sub_v3_v3v3(vec_rot, v_co, sculpt_co); + + copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry); + pow_qt_fl_normalized(q_interp, factor); + mul_qt_v3(q_interp, vec_rot); + + add_v3_v3(vec_rot, sculpt_co); + sub_v3_v3v3(r_delta, vec_rot, v_co); +#endif +} + +/** + * Align the grab delta to the brush normal. + * + * \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`. + */ +static void sculpt_project_v3_normal_align(SculptSession *ss, + const float normal_weight, + float grab_delta[3]) +{ + /* Signed to support grabbing in (to make a hole) as well as out. */ + const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta); + + /* This scale effectively projects the offset so dragging follows the cursor, + * as the normal points towards the view, the scale increases. */ + float len_view_scale; + { + float view_aligned_normal[3]; + project_plane_v3_v3v3( + view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal); + len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm)); + len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f; + } + + mul_v3_fl(grab_delta, 1.0f - normal_weight); + madd_v3_v3fl( + grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Draw Brush + * \{ */ + +static void do_draw_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *offset = data->offset; + + PBVHVertexIter vd; + float(*proxy)[3]; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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)) { + continue; + } + /* Offset vertex. */ + const float fade = 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); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + const float bstrength = ss->cache->bstrength; + + /* Offset with as much as possible factored in already. */ + float effective_normal[3]; + SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); + mul_v3_v3fl(offset, effective_normal, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise + * initialize before threads so they can do curve mapping. */ + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .offset = offset, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings); +} + +/** \} */ + +static void do_fill_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + + 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); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + float offset = SCULPT_brush_plane_offset_get(sd, ss); + + float displace; + + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings); +} + +static void do_scrape_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + if (SCULPT_plane_point_side(vd.co, test.plane_tool)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + + 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); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + float offset = SCULPT_brush_plane_offset_get(sd, ss); + + float displace; + + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = -radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Clay Thumb Brush + * \{ */ + +static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*mat)[4] = data->mat; + const float *area_no_sp = data->area_no_sp; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = data->clay_strength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + float plane_tilt[4]; + float normal_tilt[3]; + float imat[4][4]; + + invert_m4_m4(imat, mat); + rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle)); + + /* Plane aligned to the geometry normal (back part of the brush). */ + plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); + /* Tilted plane (front part of the brush). */ + plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + float local_co[3]; + mul_v3_m4v3(local_co, mat, vd.co); + float intr[3], intr_tilt[3]; + float val[3]; + + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co); + + /* Mix the deformation of the aligned and the tilted plane based on the brush space vertex + * coordinates. */ + /* We can also control the mix with a curve if it produces noticeable artifacts in the center + * of the brush. */ + const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f; + interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix); + sub_v3_v3v3(val, intr_tilt, 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); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +float SCULPT_clay_thumb_get_stabilized_pressure(StrokeCache *cache) +{ + float final_pressure = 0.0f; + for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { + final_pressure += cache->clay_pressure_stabilizer[i]; + } + return final_pressure / SCULPT_CLAY_STABILIZER_LEN; +} + +void SCULPT_do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + const float offset = SCULPT_brush_plane_offset_get(sd, ss); + const float displace = radius * (0.25f + offset); + + /* Sampled geometry normal and area center. */ + float area_no_sp[3]; + float area_no[3]; + float area_co[3]; + + float temp[3]; + float mat[4][4]; + float scale[4][4]; + float tmat[4][4]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); + + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); + } + else { + copy_v3_v3(area_no, area_no_sp); + } + + /* Delay the first daub because grab delta is not setup. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + ss->cache->clay_thumb_front_angle = 0.0f; + return; + } + + /* Simulate the clay accumulation by increasing the plane angle as more samples are added to the + * stroke. */ + if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { + ss->cache->clay_thumb_front_angle += 0.8f; + ss->cache->clay_thumb_front_angle = clamp_f(ss->cache->clay_thumb_front_angle, 0.0f, 60.0f); + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + /* Displace the brush planes. */ + copy_v3_v3(area_co, ss->cache->location); + mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + /* Initialize brush local-space matrix. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], ss->cache->location); + mat[3][3] = 1.0f; + normalize_m4(mat); + + /* Scale brush local space matrix. */ + scale_m4_fl(scale, ss->cache->radius); + mul_m4_m4m4(tmat, mat, scale); + invert_m4_m4(mat, tmat); + + float clay_strength = ss->cache->bstrength * + SCULPT_clay_thumb_get_stabilized_pressure(ss->cache); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no_sp = area_no_sp, + .area_co = ss->cache->location, + .mat = mat, + .clay_strength = clay_strength, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings); +} + +/** \} */ + +static void do_flatten_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + float intr[3]; + float val[3]; + + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + + sub_v3_v3v3(val, intr, vd.co); + + if (SCULPT_plane_trim(ss->cache, brush, val)) { + 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); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = ss->cache->radius; + + float area_no[3]; + float area_co[3]; + + float offset = SCULPT_brush_plane_offset_get(sd, ss); + float displace; + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); + + displace = radius * offset; + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Clay Brush + * \{ */ + +typedef struct ClaySampleData { + float plane_dist[2]; +} ClaySampleData; + +static void calc_clay_surface_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + ClaySampleData *csd = tls->userdata_chunk; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + float plane[4]; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + + /* Apply the brush normal radius to the test before sampling. */ + float test_radius = sqrtf(test.radius_squared); + test_radius *= brush->normal_radius_factor; + test.radius_squared = test_radius * test_radius; + plane_from_point_normal_v3(plane, area_co, area_no); + + if (is_zero_v4(plane)) { + return; + } + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float plane_dist = dist_signed_to_plane_v3(vd.co, plane); + float plane_dist_abs = fabsf(plane_dist); + if (plane_dist > 0.0f) { + csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs); + } + else { + csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs); + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata), + void *__restrict chunk_join, + void *__restrict chunk) +{ + ClaySampleData *join = chunk_join; + ClaySampleData *csd = chunk; + join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]); + join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]); +} + +static void do_clay_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *area_no = data->area_no; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = fabsf(ss->cache->bstrength); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + plane_from_point_normal_v3(test.plane_tool, area_co, area_no); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + + sub_v3_v3v3(val, intr, 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); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const float radius = fabsf(ss->cache->radius); + const float initial_radius = fabsf(ss->cache->initial_radius); + bool flip = ss->cache->bstrength < 0.0f; + + float offset = SCULPT_brush_plane_offset_get(sd, ss); + float displace; + + float area_no[3]; + float area_co[3]; + float temp[3]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); + + SculptThreadedTaskData sample_data = { + .sd = NULL, + .ob = ob, + .brush = brush, + .nodes = nodes, + .totnode = totnode, + .area_no = area_no, + .area_co = ss->cache->location, + }; + + ClaySampleData csd = {{0}}; + + TaskParallelSettings sample_settings; + BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode); + sample_settings.func_reduce = calc_clay_surface_reduce; + sample_settings.userdata_chunk = &csd; + sample_settings.userdata_chunk_size = sizeof(ClaySampleData); + + BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings); + + float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]); + d_offset = min_ff(radius, d_offset); + d_offset = d_offset / radius; + d_offset = 1.0f - d_offset; + displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f))); + if (flip) { + displace = -displace; + } + + mul_v3_v3v3(temp, area_no, ss->cache->scale); + mul_v3_fl(temp, displace); + copy_v3_v3(area_co, ss->cache->location); + add_v3_v3(area_co, temp); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no = area_no, + .area_co = area_co, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings); +} + +static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*mat)[4] = data->mat; + const float *area_no_sp = data->area_no_sp; + const float *area_co = data->area_co; + + PBVHVertexIter vd; + SculptBrushTest test; + float(*proxy)[3]; + const bool flip = (ss->cache->bstrength < 0.0f); + const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + SCULPT_brush_test_init(ss, &test); + plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); + 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_cube(&test, vd.co, mat, brush->tip_roundness)) { + continue; + } + + if (!plane_point_side_flip(vd.co, test.plane_tool, flip)) { + continue; + } + + float intr[3]; + float val[3]; + closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); + sub_v3_v3v3(val, intr, vd.co); + + if (!SCULPT_plane_trim(ss->cache, brush, val)) { + continue; + } + /* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */ + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + vd.co, + ss->cache->radius * test.dist, + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], val, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + const bool flip = (ss->cache->bstrength < 0.0f); + const float radius = flip ? -ss->cache->radius : ss->cache->radius; + const float offset = SCULPT_brush_plane_offset_get(sd, ss); + const float displace = radius * (0.18f + offset); + + /* The sculpt-plane normal (whatever its set to). */ + float area_no_sp[3]; + + /* Geometry normal */ + float area_no[3]; + float area_co[3]; + + float temp[3]; + float mat[4][4]; + float scale[4][4]; + float tmat[4][4]; + + SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); + SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor); + + if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { + SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); + } + else { + copy_v3_v3(area_no, area_no_sp); + } + + /* Delay the first daub because grab delta is not setup. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); + mul_v3_fl(temp, displace); + add_v3_v3(area_co, temp); + + /* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform the + * vertices. When in Add mode, vertices that are below the plane and inside the cube are move + * towards the plane. In this situation, there may be cases where a vertex is outside the cube + * but below the plane, so won't be deformed, causing artifacts. In order to prevent these + * artifacts, this displaces the test cube space in relation to the plane in order to + * deform more vertices that may be below it. */ + /* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were set + * by doing multiple tests using the default "Clay Strips" brush preset. */ + float area_co_displaced[3]; + madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f); + + /* Initialize brush local-space matrix. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], area_co_displaced); + mat[3][3] = 1.0f; + normalize_m4(mat); + + /* Scale brush local space matrix. */ + scale_m4_fl(scale, ss->cache->radius); + mul_m4_m4m4(tmat, mat, scale); + + /* Deform the local space in Z to scale the test cube. As the test cube does not have falloff in + * Z this does not produce artifacts in the falloff cube and allows to deform extra vertices + * during big deformation while keeping the surface as uniform as possible. */ + mul_v3_fl(tmat[2], 1.25f); + + invert_m4_m4(mat, tmat); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no_sp = area_no_sp, + .area_co = area_co, + .mat = mat, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings); +} + +static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + SculptProjectVector *spvc = data->spvc; + const float *grab_delta = data->grab_delta; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + const bool do_rake_rotation = ss->cache->is_rake_rotation_valid; + const bool do_pinch = (brush->crease_pinch_factor != 0.5f); + const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) * + (len_v3(grab_delta) / ss->cache->radius)) : + 0.0f; + + const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + KelvinletParams params; + BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!do_elastic && !sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float fade; + if (do_elastic) { + fade = 1.0f; + } + else { + 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); + } + + mul_v3_v3fl(proxy[vd.i], grab_delta, fade); + + /* Negative pinch will inflate, helps maintain volume. */ + if (do_pinch) { + float delta_pinch_init[3], delta_pinch[3]; + + sub_v3_v3v3(delta_pinch, vd.co, test.location); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal); + } + + /* Important to calculate based on the grabbed location + * (intentionally ignore fade here). */ + add_v3_v3(delta_pinch, grab_delta); + + sculpt_project_v3(spvc, delta_pinch, delta_pinch); + + copy_v3_v3(delta_pinch_init, delta_pinch); + + float pinch_fade = pinch * fade; + /* When reducing, scale reduction back by how close to the center we are, + * so we don't pinch into nothingness. */ + if (pinch > 0.0f) { + /* Square to have even less impact for close vertices. */ + pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius)); + } + mul_v3_fl(delta_pinch, 1.0f + pinch_fade); + sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch); + add_v3_v3(proxy[vd.i], delta_pinch); + } + + if (do_rake_rotation) { + float delta_rotate[3]; + sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate); + add_v3_v3(proxy[vd.i], delta_rotate); + } + + if (do_elastic) { + float disp[3]; + BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]); + mul_v3_fl(disp, bstrength * 20.0f); + if (vd.mask) { + mul_v3_fl(disp, 1.0f - *vd.mask); + } + mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + copy_v3_v3(proxy[vd.i], disp); + } + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + const float bstrength = ss->cache->bstrength; + float grab_delta[3]; + + SculptProjectVector spvc; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (bstrength < 0.0f) { + negate_v3(grab_delta); + } + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + /* Optionally pinch while painting. */ + if (brush->crease_pinch_factor != 0.5f) { + sculpt_project_v3_cache_init(&spvc, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .spvc = &spvc, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings); +} + +static void do_thumb_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *cono = data->cono; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], cono, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + float tmp[3], cono[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); + cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .cono = cono, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings); +} + +static void do_rotate_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float angle = data->angle; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + float vec[3], rot[3][3]; + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + sub_v3_v3v3(vec, orig_data.co, ss->cache->location); + axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade); + mul_v3_m3v3(proxy[vd.i], rot, vec); + add_v3_v3(proxy[vd.i], ss->cache->location); + sub_v3_v3(proxy[vd.i], orig_data.co); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1}; + const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass]; + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .angle = angle, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings); +} + +static void do_layer_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; + + const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + const float bstrength = ss->cache->bstrength; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = 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); + + const int vi = vd.index; + float *disp_factor; + if (use_persistent_base) { + disp_factor = &ss->persistent_base[vi].disp; + } + else { + disp_factor = &ss->cache->layer_displacement_factor[vi]; + } + + /* When using persistent base, the layer brush (holding Control) invert mode resets the + * height of the layer to 0. This makes possible to clean edges of previously added layers + * on top of the base. */ + /* The main direction of the layers is inverted using the regular brush strength with the + * brush direction property. */ + if (use_persistent_base && ss->cache->invert) { + (*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) * + ((*disp_factor) > 0.0f ? -1.0f : 1.0f); + } + else { + (*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor)); + } + if (vd.mask) { + const float clamp_mask = 1.0f - *vd.mask; + *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); + } + else { + *disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f); + } + + float final_co[3]; + float normal[3]; + + if (use_persistent_base) { + SCULPT_vertex_persistent_normal_get(ss, vi, normal); + mul_v3_fl(normal, brush->height); + madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor); + } + else { + normal_short_to_float_v3(normal, orig_data.no); + mul_v3_fl(normal, brush->height); + madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor); + } + + float vdisp[3]; + sub_v3_v3v3(vdisp, final_co, vd.co); + mul_v3_fl(vdisp, fabsf(fade)); + add_v3_v3v3(final_co, vd.co, vdisp); + + SCULPT_clip(sd, ss, vd.co, final_co); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (ss->cache->layer_displacement_factor == NULL) { + ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss), + "layer displacement factor"); + } + + 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_layer_brush_task_cb_ex, &settings); +} + +static void do_inflate_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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)) { + continue; + } + 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 val[3]; + + if (vd.fno) { + copy_v3_v3(val, vd.fno); + } + else { + normal_short_to_float_v3(val, vd.no); + } + + mul_v3_fl(val, fade * ss->cache->radius); + mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + 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_inflate_brush_task_cb_ex, &settings); +} + +static void do_nudge_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *cono = data->cono; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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)) { + continue; + } + 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); + + mul_v3_v3fl(proxy[vd.i], cono, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + float tmp[3], cono[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); + cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .cono = cono, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Crease & Blob Brush + * \{ */ + +/** + * Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB' + */ +static void do_crease_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + SculptProjectVector *spvc = data->spvc; + const float flippedbstrength = data->flippedbstrength; + const float *offset = data->offset; + + PBVHVertexIter vd; + float(*proxy)[3]; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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)) { + continue; + } + /* Offset vertex. */ + const float fade = 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 val1[3]; + float val2[3]; + + /* First we pinch. */ + sub_v3_v3v3(val1, test.location, vd.co); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(val1, val1, ss->cache->view_normal); + } + + mul_v3_fl(val1, fade * flippedbstrength); + + sculpt_project_v3(spvc, val1, val1); + + /* Then we draw. */ + mul_v3_v3fl(val2, offset, fade); + + add_v3_v3v3(proxy[vd.i], val1, val2); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + const Scene *scene = ss->cache->vc->scene; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + float bstrength = ss->cache->bstrength; + float flippedbstrength, crease_correction; + float brush_alpha; + + SculptProjectVector spvc; + + /* Offset with as much as possible factored in already. */ + mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* We divide out the squared alpha and multiply by the squared crease + * to give us the pinch strength. */ + crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor; + brush_alpha = BKE_brush_alpha_get(scene, brush); + if (brush_alpha > 0.0f) { + crease_correction /= brush_alpha * brush_alpha; + } + + /* We always want crease to pinch or blob to relax even when draw is negative. */ + flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength : + crease_correction * bstrength; + + if (brush->sculpt_tool == SCULPT_TOOL_BLOB) { + flippedbstrength *= -1.0f; + } + + /* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single + * point. Without this we get a 'flat' surface surrounding the pinch. */ + sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .spvc = &spvc, + .offset = offset, + .flippedbstrength = flippedbstrength, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings); +} + +static void do_pinch_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + float(*stroke_xz)[3] = data->stroke_xz; + + PBVHVertexIter vd; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + float x_object_space[3]; + float z_object_space[3]; + copy_v3_v3(x_object_space, stroke_xz[0]); + copy_v3_v3(z_object_space, stroke_xz[1]); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + 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_center[3]; + float x_disp[3]; + float z_disp[3]; + /* Calculate displacement from the vertex to the brush center. */ + sub_v3_v3v3(disp_center, test.location, vd.co); + + /* Project the displacement into the X vector (aligned to the stroke). */ + mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space)); + + /* Project the displacement into the Z vector (aligned to the surface normal). */ + mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space)); + + /* Add the two projected vectors to calculate the final displacement. + * The Y component is removed. */ + add_v3_v3v3(disp_center, x_disp, z_disp); + + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal); + } + mul_v3_v3fl(proxy[vd.i], disp_center, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + float area_no[3]; + float area_co[3]; + + float mat[4][4]; + calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co); + + /* delay the first daub because grab delta is not setup */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + if (is_zero_v3(ss->cache->grab_delta_symmetry)) { + return; + } + + /* Initialize `mat`. */ + cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); + mat[0][3] = 0.0f; + cross_v3_v3v3(mat[1], area_no, mat[0]); + mat[1][3] = 0.0f; + copy_v3_v3(mat[2], area_no); + mat[2][3] = 0.0f; + copy_v3_v3(mat[3], ss->cache->location); + mat[3][3] = 1.0f; + normalize_m4(mat); + + float stroke_xz[2][3]; + normalize_v3_v3(stroke_xz[0], mat[0]); + normalize_v3_v3(stroke_xz[1], mat[2]); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .stroke_xz = stroke_xz, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings); +} + +static void do_grab_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *grab_delta = data->grab_delta; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + if (grab_silhouette) { + float silhouette_test_dir[3]; + normalize_v3_v3(silhouette_test_dir, grab_delta); + if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) { + mul_v3_fl(silhouette_test_dir, -1.0f); + } + float vno[3]; + normal_short_to_float_v3(vno, orig_data.no); + fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f); + } + + mul_v3_v3fl(proxy[vd.i], grab_delta, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings); +} + +static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *grab_delta = data->grab_delta; + const float *location = ss->cache->location; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + const float bstrength = ss->cache->bstrength; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + float dir; + if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) { + dir = 1.0f; + } + else { + dir = -1.0f; + } + + if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) { + int symm = ss->cache->mirror_symmetry_pass; + if (ELEM(symm, 1, 2, 4, 7)) { + dir = -dir; + } + } + + KelvinletParams params; + float force = len_v3(grab_delta) * dir * bstrength; + BKE_kelvinlet_init_params( + ¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + float final_disp[3]; + switch (brush->elastic_deform_type) { + case BRUSH_ELASTIC_DEFORM_GRAB: + BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: { + BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + } + case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: { + BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta); + mul_v3_fl(final_disp, bstrength * 20.0f); + break; + } + case BRUSH_ELASTIC_DEFORM_SCALE: + BKE_kelvinlet_scale( + final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); + break; + case BRUSH_ELASTIC_DEFORM_TWIST: + BKE_kelvinlet_twist( + final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); + break; + } + + if (vd.mask) { + mul_v3_fl(final_disp, 1.0f - *vd.mask); + } + + mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); + + copy_v3_v3(proxy[vd.i], final_disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + if (ss->cache->normal_weight > 0.0f) { + sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); + } + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .grab_delta = grab_delta, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings); +} +/** \} */ + +static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float *offset = data->offset; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + /* Offset vertex. */ + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float offset[3]; + const float bstrength = ss->cache->bstrength; + + /* Offset with as much as possible factored in already. */ + float effective_normal[3]; + SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); + mul_v3_v3fl(offset, effective_normal, ss->cache->radius); + mul_v3_v3(offset, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise + * initialize before threads so they can do curve mapping. */ + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .offset = offset, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings); +} + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Topology Brush + * \{ */ + +static void do_topology_slide_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + float(*proxy)[3]; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + float current_disp[3]; + float current_disp_norm[3]; + float final_disp[3] = {0.0f, 0.0f, 0.0f}; + + switch (brush->slide_deform_type) { + case BRUSH_SLIDE_DEFORM_DRAG: + sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); + break; + case BRUSH_SLIDE_DEFORM_PINCH: + sub_v3_v3v3(current_disp, ss->cache->location, vd.co); + break; + case BRUSH_SLIDE_DEFORM_EXPAND: + sub_v3_v3v3(current_disp, vd.co, ss->cache->location); + break; + } + + normalize_v3_v3(current_disp_norm, current_disp); + mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vertex_disp[3]; + float vertex_disp_norm[3]; + sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); + normalize_v3_v3(vertex_disp_norm, vertex_disp); + if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { + madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + mul_v3_v3fl(proxy[vd.i], final_disp, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_relax_vertex(SculptSession *ss, + PBVHVertexIter *vd, + float factor, + bool filter_boundary_face_sets, + float *r_final_pos) +{ + float smooth_pos[3]; + float final_disp[3]; + float boundary_normal[3]; + int avg_count = 0; + int neighbor_count = 0; + zero_v3(smooth_pos); + zero_v3(boundary_normal); + const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { + neighbor_count++; + if (!filter_boundary_face_sets || + (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) { + + /* When the vertex to relax is boundary, use only connected boundary vertices for the average + * position. */ + if (is_boundary) { + if (!SCULPT_vertex_is_boundary(ss, ni.index)) { + continue; + } + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + avg_count++; + + /* Calculate a normal for the constraint plane using the edges of the boundary. */ + float to_neighbor[3]; + sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co); + normalize_v3(to_neighbor); + add_v3_v3(boundary_normal, to_neighbor); + } + else { + add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); + avg_count++; + } + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + /* Don't modify corner vertices. */ + if (neighbor_count <= 2) { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + if (avg_count > 0) { + mul_v3_fl(smooth_pos, 1.0f / avg_count); + } + else { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + float plane[4]; + float smooth_closest_plane[3]; + float vno[3]; + + if (is_boundary && avg_count == 2) { + normalize_v3_v3(vno, boundary_normal); + } + else { + SCULPT_vertex_normal_get(ss, vd->index, vno); + } + + if (is_zero_v3(vno)) { + copy_v3_v3(r_final_pos, vd->co); + return; + } + + plane_from_point_normal_v3(plane, vd->co, vno); + closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos); + sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co); + + mul_v3_fl(final_disp, factor); + add_v3_v3v3(r_final_pos, vd->co, final_disp); +} + +static void do_topology_relax_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]); + + 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) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + orig_data.co, + sqrtf(test.dist), + orig_data.no, + NULL, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co); + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + return; + } + + BKE_curvemapping_init(brush->curve); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + if (ss->cache->alt_smooth) { + SCULPT_boundary_info_ensure(ob); + for (int i = 0; i < 4; i++) { + BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings); + } + } + else { + BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings); + } +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Multires Displacement Eraser Brush + * \{ */ + +static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); + + float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; + + 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); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + 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 limit_co[3]; + float disp[3]; + SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co); + sub_v3_v3v3(disp, limit_co, vd.co); + mul_v3_v3fl(proxy[vd.i], disp, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + BKE_curvemapping_init(brush->curve); + + /* Threaded loop over nodes. */ + 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_displacement_eraser_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Multires Displacement Smear Brush + * \{ */ + +static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = clamp_f(ss->cache->bstrength, 0.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); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + 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 current_disp[3]; + float current_disp_norm[3]; + float interp_limit_surface_disp[3]; + + copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]); + + switch (brush->smear_deform_type) { + case BRUSH_SMEAR_DEFORM_DRAG: + sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); + break; + case BRUSH_SMEAR_DEFORM_PINCH: + sub_v3_v3v3(current_disp, ss->cache->location, vd.co); + break; + case BRUSH_SMEAR_DEFORM_EXPAND: + sub_v3_v3v3(current_disp, vd.co, ss->cache->location); + break; + } + + normalize_v3_v3(current_disp_norm, current_disp); + mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); + + float weights_accum = 1.0f; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vertex_disp[3]; + float vertex_disp_norm[3]; + float neighbor_limit_co[3]; + SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co); + sub_v3_v3v3(vertex_disp, + ss->cache->limit_surface_co[ni.index], + ss->cache->limit_surface_co[vd.index]); + const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index]; + normalize_v3_v3(vertex_disp_norm, vertex_disp); + + if (dot_v3v3(current_disp_norm, vertex_disp_norm) >= 0.0f) { + continue; + } + + const float disp_interp = clamp_f( + -dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f); + madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp); + weights_accum += disp_interp; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum); + + float new_co[3]; + add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp); + interp_v3_v3v3(vd.co, vd.co, new_co, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_displacement_smear_store_prev_disp_task_cb_ex( + void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + sub_v3_v3v3(ss->cache->prev_displacement[vd.index], + SCULPT_vertex_co_get(ss, vd.index), + ss->cache->limit_surface_co[vd.index]); + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + + BKE_curvemapping_init(brush->curve); + + const int totvert = SCULPT_vertex_count_get(ss); + if (!ss->cache->prev_displacement) { + ss->cache->prev_displacement = MEM_malloc_arrayN( + totvert, sizeof(float[3]), "prev displacement"); + ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co"); + for (int i = 0; i < totvert; i++) { + SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]); + sub_v3_v3v3(ss->cache->prev_displacement[i], + SCULPT_vertex_co_get(ss, i), + ss->cache->limit_surface_co[i]); + } + } + /* Threaded loop over nodes. */ + 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_displacement_smear_store_prev_disp_task_cb_ex, &settings); + BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Topology Rake (Shared Utility) + * \{ */ + +static void do_topology_rake_bmesh_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; + + float direction[3]; + copy_v3_v3(direction, ss->cache->grab_delta_symmetry); + + float tmp[3]; + mul_v3_v3fl( + tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction)); + sub_v3_v3(direction, tmp); + normalize_v3(direction); + + /* Cancel if there's no grab data. */ + if (is_zero_v3(direction)) { + return; + } + + const float bstrength = clamp_f(data->strength, 0.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); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = + bstrength * + SCULPT_brush_strength_factor( + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) * + ss->cache->pressure; + + float avg[3], val[3]; + + SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert); + + sub_v3_v3v3(val, avg, vd.co); + + madd_v3_v3v3fl(val, vd.co, val, fade); + + SCULPT_clip(sd, ss, vd.co, val); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_bmesh_topology_rake( + Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + const float strength = clamp_f(bstrength, 0.0f, 1.0f); + + /* Interactions increase both strength and quality. */ + const int iterations = 3; + + int iteration; + const int count = iterations * strength + 1; + const float factor = iterations * strength / count; + + for (iteration = 0; iteration <= count; iteration++) { + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .strength = factor, + }; + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + + BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Mask Brush + * \{ */ + +static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + 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)) { + continue; + } + + const float fade = SCULPT_brush_strength_factor( + ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id); + + if (bstrength > 0.0f) { + (*vd.mask) += fade * bstrength * (1.0f - *vd.mask); + } + else { + (*vd.mask) += fade * bstrength * (*vd.mask); + } + *vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + BKE_pbvh_vertex_iter_end; + } +} + +void SCULPT_do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + /* Threaded loop over nodes. */ + 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_mask_brush_draw_task_cb_ex, &settings); +} + +void SCULPT_do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + switch ((BrushMaskTool)brush->mask_tool) { + case BRUSH_MASK_DRAW: + SCULPT_do_mask_brush_draw(sd, ob, nodes, totnode); + break; + case BRUSH_MASK_SMOOTH: + SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true); + break; + } +} + +/** \} */ |