From 598bb9065c8765b8950d069058e501fb51f55a4e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 9 Nov 2022 19:27:41 -0600 Subject: Cleanup: Move sculpt.c to C++ --- source/blender/blenkernel/BKE_paint.h | 1 + source/blender/blenkernel/BKE_pbvh.h | 2 + source/blender/editors/include/ED_sculpt.h | 2 +- source/blender/editors/sculpt_paint/CMakeLists.txt | 2 +- .../blender/editors/sculpt_paint/paint_vertex.cc | 78 +- source/blender/editors/sculpt_paint/sculpt.c | 6198 -------------------- source/blender/editors/sculpt_paint/sculpt.cc | 6193 +++++++++++++++++++ .../blender/editors/sculpt_paint/sculpt_intern.h | 10 +- source/blender/makesdna/DNA_scene_types.h | 1 + 9 files changed, 6251 insertions(+), 6236 deletions(-) delete mode 100644 source/blender/editors/sculpt_paint/sculpt.c create mode 100644 source/blender/editors/sculpt_paint/sculpt.cc diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 9fc4aa5307d..434255b2d9c 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -119,6 +119,7 @@ typedef enum ePaintSymmetryAreas { PAINT_SYMM_AREA_Y = (1 << 1), PAINT_SYMM_AREA_Z = (1 << 2), } ePaintSymmetryAreas; +ENUM_OPERATORS(ePaintSymmetryAreas, PAINT_SYMM_AREA_Z); #define PAINT_SYMM_AREAS 8 diff --git a/source/blender/blenkernel/BKE_pbvh.h b/source/blender/blenkernel/BKE_pbvh.h index b375d69b61c..4badd1bc269 100644 --- a/source/blender/blenkernel/BKE_pbvh.h +++ b/source/blender/blenkernel/BKE_pbvh.h @@ -416,6 +416,8 @@ typedef enum { PBVH_Subdivide = 1, PBVH_Collapse = 2, } PBVHTopologyUpdateMode; +ENUM_OPERATORS(PBVHTopologyUpdateMode, PBVH_Collapse); + /** * Collapse short edges, subdivide long edges. */ diff --git a/source/blender/editors/include/ED_sculpt.h b/source/blender/editors/include/ED_sculpt.h index 1c1ce41ef7a..bc4e3b88586 100644 --- a/source/blender/editors/include/ED_sculpt.h +++ b/source/blender/editors/include/ED_sculpt.h @@ -22,7 +22,7 @@ struct wmMsgSubscribeValue; struct wmRegionMessageSubscribeParams; struct wmOperator; -/* sculpt.c */ +/* sculpt.cc */ void ED_operatortypes_sculpt(void); void ED_sculpt_redraw_planes_get(float planes[4][4], struct ARegion *region, struct Object *ob); diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 2709ac3fd91..b29fc0e9e7d 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -60,7 +60,7 @@ set(SRC paint_vertex_proj.c paint_vertex_weight_ops.c paint_vertex_weight_utils.c - sculpt.c + sculpt.cc sculpt_automasking.cc sculpt_boundary.c sculpt_brush_types.c diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 8e790ac435e..8758d3fa83f 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -1162,7 +1162,7 @@ static void do_weight_paint_vertex( } } -/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.c) */ +/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.cc) */ static void vertex_paint_init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, @@ -2388,7 +2388,7 @@ static void wpaint_do_paint(bContext *C, WeightPaintInfo *wpi, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis, const int i, const float angle) @@ -2415,7 +2415,7 @@ static void wpaint_do_radial_symmetry(bContext *C, WeightPaintInfo *wpi, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis) { for (int i = 1; i < wp->radial_symm[axis - 'X']; i++) { @@ -2424,7 +2424,7 @@ static void wpaint_do_radial_symmetry(bContext *C, } } -/* near duplicate of: sculpt.c's, +/* near duplicate of: sculpt.cc's, * 'do_symmetrical_brush_actions' and 'vpaint_do_symmetrical_brush_actions'. */ static void wpaint_do_symmetrical_brush_actions( bContext *C, Object *ob, VPaint *wp, Sculpt *sd, WPaintData *wpd, WeightPaintInfo *wpi) @@ -2437,11 +2437,11 @@ static void wpaint_do_symmetrical_brush_actions( int i = 0; /* initial stroke */ - cache->mirror_symmetry_pass = 0; - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Y'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Z'); + cache->mirror_symmetry_pass = ePaintSymmetryFlags(0); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Y'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Z'); cache->symmetry = symm; @@ -2456,21 +2456,22 @@ static void wpaint_do_symmetrical_brush_actions( * X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ for (i = 1; i <= symm; i++) { if (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))) { - cache->mirror_symmetry_pass = i; + const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm; cache->radial_symmetry_pass = 0; - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); + SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); if (i & (1 << 0)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X'); } if (i & (1 << 1)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y'); } if (i & (1 << 2)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z'); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z'); } } } @@ -3738,7 +3739,7 @@ static void vpaint_do_paint(bContext *C, Object *ob, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis, const int i, const float angle) @@ -3769,7 +3770,7 @@ static void vpaint_do_radial_symmetry(bContext *C, Object *ob, Mesh *me, Brush *brush, - const char symm, + const ePaintSymmetryFlags symm, const int axis) { for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) { @@ -3778,7 +3779,7 @@ static void vpaint_do_radial_symmetry(bContext *C, } } -/* near duplicate of: sculpt.c's, +/* near duplicate of: sculpt.cc's, * 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */ template static void vpaint_do_symmetrical_brush_actions( @@ -3792,11 +3793,15 @@ static void vpaint_do_symmetrical_brush_actions( int i = 0; /* initial stroke */ - cache->mirror_symmetry_pass = 0; - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + const ePaintSymmetryFlags initial_symm = ePaintSymmetryFlags(0); + cache->mirror_symmetry_pass = ePaintSymmetryFlags(0); + vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, initial_symm, 'X', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'X'); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'Y'); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, initial_symm, 'Z'); cache->symmetry = symm; @@ -3804,21 +3809,28 @@ static void vpaint_do_symmetrical_brush_actions( * X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ for (i = 1; i <= symm; i++) { if (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))) { - cache->mirror_symmetry_pass = i; + const ePaintSymmetryFlags symm_pass = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm_pass; cache->radial_symmetry_pass = 0; - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); + SCULPT_cache_calc_brushdata_symm(cache, symm_pass, 0, 0); if (i & (1 << 0)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'X', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'X'); } if (i & (1 << 1)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Y', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Y'); } if (i & (1 << 2)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + vpaint_do_paint( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Z', 0, 0); + vpaint_do_radial_symmetry( + C, sd, vp, vpd, ob, me, brush, symm_pass, 'Z'); } } } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c deleted file mode 100644 index 3477285814e..00000000000 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ /dev/null @@ -1,6198 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2006 by Nicholas Bishop. All rights reserved. */ - -/** \file - * \ingroup edsculpt - * Implements the Sculpt Mode tools. - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_blenlib.h" -#include "BLI_dial_2d.h" -#include "BLI_ghash.h" -#include "BLI_gsqueue.h" -#include "BLI_math.h" -#include "BLI_task.h" -#include "BLI_utildefines.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_attribute.h" -#include "BKE_brush.h" -#include "BKE_ccg.h" -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_image.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_modifier.h" -#include "BKE_multires.h" -#include "BKE_object.h" -#include "BKE_paint.h" -#include "BKE_pbvh.h" -#include "BKE_report.h" -#include "BKE_scene.h" -#include "BKE_subdiv_ccg.h" -#include "BKE_subsurf.h" - -#include "NOD_texture.h" - -#include "DEG_depsgraph.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_paint.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 "bmesh.h" - -#include -#include -#include - -/* -------------------------------------------------------------------- */ -/** \name Sculpt PBVH Abstraction API - * - * This is read-only, for writing use PBVH vertex iterators. There vd.index matches - * the indices used here. - * - * For multi-resolution, the same vertex in multiple grids is counted multiple times, with - * different index for each grid. - * \{ */ - -void SCULPT_vertex_random_access_ensure(SculptSession *ss) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); - } -} - -int SCULPT_vertex_count_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return ss->totvert; - case PBVH_BMESH: - return BM_mesh_elem_count(BKE_pbvh_get_bmesh(ss->pbvh), BM_VERT); - case PBVH_GRIDS: - return BKE_pbvh_get_grid_num_verts(ss->pbvh); - } - - return 0; -} - -const float *SCULPT_vertex_co_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (ss->shapekey_active || ss->deform_modifiers_active) { - const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[vertex.i].co; - } - return ss->mvert[vertex.i].co; - } - case PBVH_BMESH: - return ((BMVert *)vertex.i)->co; - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - return CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); - } - } - return NULL; -} - -bool SCULPT_has_loop_colors(const Object *ob) -{ - Mesh *me = BKE_object_get_original_mesh(ob); - const CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); - - return layer && BKE_id_attribute_domain(&me->id, layer) == ATTR_DOMAIN_CORNER; -} - -bool SCULPT_has_colors(const SculptSession *ss) -{ - return ss->vcol || ss->mcol; -} - -void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) -{ - BKE_pbvh_vertex_color_get(ss->pbvh, vertex, r_color); -} - -void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]) -{ - BKE_pbvh_vertex_color_set(ss->pbvh, vertex, color); -} - -void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - const float(*vert_normals)[3] = BKE_pbvh_get_vert_normals(ss->pbvh); - copy_v3_v3(no, vert_normals[vertex.i]); - break; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - copy_v3_v3(no, v->no); - break; - } - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - copy_v3_v3(no, CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index))); - break; - } - } -} - -const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) -{ - if (ss->attrs.persistent_co) { - return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); - } - - return SCULPT_vertex_co_get(ss, vertex); -} - -const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex) -{ - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { - /* Always grab active shape key if the sculpt happens on shapekey. */ - if (ss->shapekey_active) { - const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); - return mverts[vertex.i].co; - } - - /* Sculpting on the base mesh. */ - return ss->mvert[vertex.i].co; - } - - /* Everything else, such as sculpting on multires. */ - return SCULPT_vertex_co_get(ss, vertex); -} - -void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_BMESH: - copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); - break; - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - - SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - BKE_subdiv_ccg_eval_limit_point(ss->subdiv_ccg, &coord, r_co); - break; - } - } -} - -void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) -{ - if (ss->attrs.persistent_no) { - copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); - return; - } - SCULPT_vertex_normal_get(ss, vertex, no); -} - -float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return ss->vmask ? ss->vmask[vertex.i] : 0.0f; - case PBVH_BMESH: { - BMVert *v; - int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); - - v = (BMVert *)vertex.i; - return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; - } - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; - return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); - } - } - - return 0.0f; -} - -PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) -{ - if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { - return ss->active_vertex; - } - - return BKE_pbvh_make_vref(PBVH_REF_NONE); -} - -const float *SCULPT_active_vertex_co_get(SculptSession *ss) -{ - return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss)); -} - -void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) -{ - SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal); -} - -MVert *SCULPT_mesh_deformed_mverts_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - if (ss->shapekey_active || ss->deform_modifiers_active) { - return BKE_pbvh_get_verts(ss->pbvh); - } - return ss->mvert; - case PBVH_BMESH: - case PBVH_GRIDS: - return NULL; - } - return NULL; -} - -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; -} - -char SCULPT_mesh_symmetry_xyz_get(Object *object) -{ - const Mesh *mesh = BKE_mesh_from_object(object); - return mesh->symmetry; -} - -/* Sculpt Face Sets and Visibility. */ - -int SCULPT_active_face_set_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - return ss->face_sets[ss->active_face_index]; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, - ss->active_grid_index); - return ss->face_sets[face_index]; - } - case PBVH_BMESH: - return SCULPT_FACE_SET_NONE; - } - return SCULPT_FACE_SET_NONE; -} - -void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); - hide_vert[vertex.i] = visible; - break; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - BM_elem_flag_set(v, BM_ELEM_HIDDEN, !visible); - break; - } - case PBVH_GRIDS: - break; - } -} - -bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - const bool *hide_vert = BKE_pbvh_get_vert_hide(ss->pbvh); - return hide_vert == NULL || !hide_vert[vertex.i]; - } - case PBVH_BMESH: - return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - BLI_bitmap **grid_hidden = BKE_pbvh_get_grid_visibility(ss->pbvh); - if (grid_hidden && grid_hidden[grid_index]) { - return !BLI_BITMAP_TEST(grid_hidden[grid_index], vertex_index); - } - } - } - return true; -} - -void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible) -{ - BLI_assert(ss->face_sets != NULL); - BLI_assert(ss->hide_poly != NULL); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] != face_set) { - continue; - } - ss->hide_poly[i] = !visible; - } - break; - case PBVH_BMESH: - break; - } -} - -void SCULPT_face_visibility_all_invert(SculptSession *ss) -{ - BLI_assert(ss->face_sets != NULL); - BLI_assert(ss->hide_poly != NULL); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - for (int i = 0; i < ss->totfaces; i++) { - ss->hide_poly[i] = !ss->hide_poly[i]; - } - break; - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BM_elem_flag_toggle(f, BM_ELEM_HIDDEN); - } - break; - } - } -} - -void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: - BLI_assert(ss->hide_poly != NULL); - memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); - break; - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); - } - break; - } - } -} - -bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->hide_poly) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < ss->pmap[vertex.i].count; j++) { - if (!ss->hide_poly[vert_map->indices[j]]) { - return true; - } - } - return false; - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: - return true; - } - return true; -} - -bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->hide_poly) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < vert_map->count; j++) { - if (ss->hide_poly[vert_map->indices[j]]) { - return false; - } - } - return true; - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - BMEdge *e = v->e; - - if (!e) { - return true; - } - - do { - BMLoop *l = e->l; - - if (!l) { - continue; - } - - do { - if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { - return false; - } - } while ((l = l->radial_next) != e->l); - } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); - - return true; - } - case PBVH_GRIDS: { - if (!ss->hide_poly) { - return true; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return !ss->hide_poly[face_index]; - } - } - return true; -} - -void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - BLI_assert(ss->face_sets != NULL); - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int j = 0; j < vert_map->count; j++) { - const int poly_index = vert_map->indices[j]; - if (ss->hide_poly && ss->hide_poly[poly_index]) { - /* Skip hidden faces connected to the vertex. */ - continue; - } - ss->face_sets[poly_index] = face_set; - } - break; - } - case PBVH_BMESH: - break; - case PBVH_GRIDS: { - BLI_assert(ss->face_sets != NULL); - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - if (ss->hide_poly && ss->hide_poly[face_index]) { - /* Skip the vertex if it's in a hidden face. */ - return; - } - ss->face_sets[face_index] = face_set; - break; - } - } -} - -int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - int face_set = 0; - for (int i = 0; i < vert_map->count; i++) { - if (ss->face_sets[vert_map->indices[i]] > face_set) { - face_set = abs(ss->face_sets[vert_map->indices[i]]); - } - } - return face_set; - } - case PBVH_BMESH: - return 0; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return SCULPT_FACE_SET_NONE; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return ss->face_sets[face_index]; - } - } - return 0; -} - -bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!ss->face_sets) { - return face_set == SCULPT_FACE_SET_NONE; - } - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - for (int i = 0; i < vert_map->count; i++) { - if (ss->face_sets[vert_map->indices[i]] == face_set) { - return true; - } - } - return false; - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return face_set == SCULPT_FACE_SET_NONE; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); - return ss->face_sets[face_index] == face_set; - } - } - return true; -} - -void SCULPT_visibility_sync_all_from_faces(Object *ob) -{ - SculptSession *ss = ob->sculpt; - Mesh *mesh = BKE_object_get_original_mesh(ob); - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - /* We may have adjusted the ".hide_poly" attribute, now make the hide status attributes for - * vertices and edges consistent. */ - BKE_mesh_flush_hidden_from_polys(mesh); - BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); - break; - } - case PBVH_GRIDS: { - /* In addition to making the hide status of the base mesh consistent, we also have to - * propagate the status to the Multires grids. */ - BKE_mesh_flush_hidden_from_polys(mesh); - BKE_sculpt_sync_face_visibility_to_grids(mesh, ss->subdiv_ccg); - break; - } - case PBVH_BMESH: { - BMIter iter; - BMFace *f; - - /* Hide all verts and edges attached to faces.*/ - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BMLoop *l = f->l_first; - do { - BM_elem_flag_enable(l->v, BM_ELEM_HIDDEN); - BM_elem_flag_enable(l->e, BM_ELEM_HIDDEN); - } while ((l = l->next) != f->l_first); - } - - /* Unhide verts and edges attached to visible faces. */ - BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - continue; - } - - BMLoop *l = f->l_first; - do { - BM_elem_flag_disable(l->v, BM_ELEM_HIDDEN); - BM_elem_flag_disable(l->e, BM_ELEM_HIDDEN); - } while ((l = l->next) != f->l_first); - } - break; - } - } -} - -static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) -{ - if (!ss->face_sets) { - return true; - } - const MeshElemMap *vert_map = &ss->pmap[index]; - int face_set = -1; - for (int i = 0; i < vert_map->count; i++) { - if (face_set == -1) { - face_set = ss->face_sets[vert_map->indices[i]]; - } - else { - if (ss->face_sets[vert_map->indices[i]] != face_set) { - return false; - } - } - } - return true; -} - -/** - * Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2 - * in the base mesh are equal. - */ -static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss, int v1, int v2) -{ - const MeshElemMap *vert_map = &ss->pmap[v1]; - int p1 = -1, p2 = -1; - for (int i = 0; i < vert_map->count; i++) { - const MPoly *p = &ss->mpoly[vert_map->indices[i]]; - for (int l = 0; l < p->totloop; l++) { - const MLoop *loop = &ss->mloop[p->loopstart + l]; - if (loop->v == v2) { - if (p1 == -1) { - p1 = vert_map->indices[i]; - break; - } - - if (p2 == -1) { - p2 = vert_map->indices[i]; - break; - } - } - } - } - - if (p1 != -1 && p2 != -1) { - return abs(ss->face_sets[p1]) == (ss->face_sets[p2]); - } - return true; -} - -bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - return sculpt_check_unique_face_set_in_base_mesh(ss, vertex.i); - } - case PBVH_BMESH: - return true; - case PBVH_GRIDS: { - if (!ss->face_sets) { - return true; - } - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - const SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - int v1, v2; - const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( - ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); - switch (adjacency) { - case SUBDIV_CCG_ADJACENT_VERTEX: - return sculpt_check_unique_face_set_in_base_mesh(ss, v1); - case SUBDIV_CCG_ADJACENT_EDGE: - return sculpt_check_unique_face_set_for_edge_in_base_mesh(ss, v1, v2); - case SUBDIV_CCG_ADJACENT_NONE: - return true; - } - } - } - return false; -} - -int SCULPT_face_set_next_available_get(SculptSession *ss) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - case PBVH_GRIDS: { - if (!ss->face_sets) { - return 0; - } - int next_face_set = 0; - for (int i = 0; i < ss->totfaces; i++) { - if (ss->face_sets[i] > next_face_set) { - next_face_set = ss->face_sets[i]; - } - } - next_face_set++; - return next_face_set; - } - case PBVH_BMESH: - return 0; - } - return 0; -} - -/* Sculpt Neighbor Iterators */ - -#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 - -static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, - PBVHVertRef neighbor, - int neighbor_index) -{ - for (int i = 0; i < iter->size; i++) { - if (iter->neighbors[i].i == neighbor.i) { - return; - } - } - - if (iter->size >= iter->capacity) { - iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - - if (iter->neighbors == iter->neighbors_fixed) { - iter->neighbors = MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array"); - memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); - } - else { - iter->neighbors = MEM_reallocN_id( - iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array"); - } - - if (iter->neighbor_indices == iter->neighbor_indices_fixed) { - iter->neighbor_indices = MEM_mallocN(iter->capacity * sizeof(int), "neighbor array"); - memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); - } - else { - iter->neighbor_indices = MEM_reallocN_id( - iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array"); - } - } - - iter->neighbors[iter->size] = neighbor; - iter->neighbor_indices[iter->size] = neighbor_index; - iter->size++; -} - -static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) -{ - BMVert *v = (BMVert *)vertex.i; - BMIter liter; - BMLoop *l; - iter->size = 0; - iter->num_duplicates = 0; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { - const BMVert *adj_v[2] = {l->prev->v, l->next->v}; - for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { - const BMVert *v_other = adj_v[i]; - if (v_other != v) { - sculpt_vertex_neighbor_add( - iter, BKE_pbvh_make_vref((intptr_t)v_other), BM_elem_index_get(v_other)); - } - } - } -} - -static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, - PBVHVertRef vertex, - SculptVertexNeighborIter *iter) -{ - const MeshElemMap *vert_map = &ss->pmap[vertex.i]; - iter->size = 0; - iter->num_duplicates = 0; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - for (int i = 0; i < vert_map->count; i++) { - if (ss->hide_poly && ss->hide_poly[vert_map->indices[i]]) { - /* Skip connectivity from hidden faces. */ - continue; - } - const MPoly *p = &ss->mpoly[vert_map->indices[i]]; - int f_adj_v[2]; - if (poly_get_adj_loops_from_vert(p, ss->mloop, vertex.i, f_adj_v) != -1) { - for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { - if (f_adj_v[j] != vertex.i) { - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); - } - } - } - } - - if (ss->fake_neighbors.use_fake_neighbors) { - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - sculpt_vertex_neighbor_add( - iter, - BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), - ss->fake_neighbors.fake_neighbor_index[vertex.i]); - } - } -} - -static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, - const PBVHVertRef vertex, - const bool include_duplicates, - SculptVertexNeighborIter *iter) -{ - /* TODO: optimize this. We could fill #SculptVertexNeighborIter directly, - * maybe provide coordinate and mask pointers directly rather than converting - * back and forth between #CCGElem and global index. */ - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - - SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - - SubdivCCGNeighbors neighbors; - BKE_subdiv_ccg_neighbor_coords_get(ss->subdiv_ccg, &coord, include_duplicates, &neighbors); - - iter->size = 0; - iter->num_duplicates = neighbors.num_duplicates; - iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; - iter->neighbors = iter->neighbors_fixed; - iter->neighbor_indices = iter->neighbor_indices_fixed; - - for (int i = 0; i < neighbors.size; i++) { - int v = neighbors.coords[i].grid_index * key->grid_area + - neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; - - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); - } - - if (ss->fake_neighbors.use_fake_neighbors) { - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { - int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; - sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); - } - } - - if (neighbors.coords != neighbors.coords_fixed) { - MEM_freeN(neighbors.coords); - } -} - -void SCULPT_vertex_neighbors_get(SculptSession *ss, - const PBVHVertRef vertex, - const bool include_duplicates, - SculptVertexNeighborIter *iter) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - sculpt_vertex_neighbors_get_faces(ss, vertex, iter); - return; - case PBVH_BMESH: - sculpt_vertex_neighbors_get_bmesh(vertex, iter); - return; - case PBVH_GRIDS: - sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); - return; - } -} - -static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, const int index) -{ - BLI_assert(ss->vertex_info.boundary); - return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); -} - -bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: { - if (!SCULPT_vertex_all_faces_visible_get(ss, vertex)) { - return true; - } - return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); - } - case PBVH_BMESH: { - BMVert *v = (BMVert *)vertex.i; - return BM_vert_is_boundary(v); - } - - case PBVH_GRIDS: { - const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); - const int grid_index = vertex.i / key->grid_area; - const int vertex_index = vertex.i - grid_index * key->grid_area; - const SubdivCCGCoord coord = {.grid_index = grid_index, - .x = vertex_index % key->grid_size, - .y = vertex_index / key->grid_size}; - int v1, v2; - const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( - ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); - switch (adjacency) { - case SUBDIV_CCG_ADJACENT_VERTEX: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1); - case SUBDIV_CCG_ADJACENT_EDGE: - return sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && - sculpt_check_boundary_vertex_in_base_mesh(ss, v2); - case SUBDIV_CCG_ADJACENT_NONE: - return false; - } - } - } - - return false; -} - -/* Utilities */ - -bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache) -{ - return cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && - cache->tile_pass == 0; -} - -bool SCULPT_stroke_is_first_brush_step(StrokeCache *cache) -{ - return cache->first_time && cache->mirror_symmetry_pass == 0 && - cache->radial_symmetry_pass == 0 && cache->tile_pass == 0; -} - -bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(StrokeCache *cache) -{ - return cache->first_time; -} - -bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm) -{ - bool is_in_symmetry_area = true; - for (int i = 0; i < 3; i++) { - char symm_it = 1 << i; - if (symm & symm_it) { - if (pco[i] == 0.0f) { - if (vco[i] > 0.0f) { - is_in_symmetry_area = false; - } - } - if (vco[i] * pco[i] < 0.0f) { - is_in_symmetry_area = false; - } - } - } - return is_in_symmetry_area; -} - -typedef struct NearestVertexTLSData { - PBVHVertRef nearest_vertex; - float nearest_vertex_distance_squared; -} NearestVertexTLSData; - -static void do_nearest_vertex_get_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - NearestVertexTLSData *nvtd = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); - if (distance_squared < nvtd->nearest_vertex_distance_squared && - distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex = vd.vertex; - nvtd->nearest_vertex_distance_squared = distance_squared; - } - } - BKE_pbvh_vertex_iter_end; -} - -static void nearest_vertex_get_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - NearestVertexTLSData *join = chunk_join; - NearestVertexTLSData *nvtd = chunk; - if (join->nearest_vertex.i == PBVH_REF_NONE) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } - else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } -} - -PBVHVertRef SCULPT_nearest_vertex_get( - Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - int totnode; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = max_distance * max_distance, - .original = use_original, - .center = co, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); - if (totnode == 0) { - return BKE_pbvh_make_vref(PBVH_REF_NONE); - } - - SculptThreadedTaskData task_data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .max_distance_squared = max_distance * max_distance, - }; - - copy_v3_v3(task_data.nearest_vertex_search_co, co); - NearestVertexTLSData nvtd; - nvtd.nearest_vertex.i = PBVH_REF_NONE; - nvtd.nearest_vertex_distance_squared = FLT_MAX; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = nearest_vertex_get_reduce; - settings.userdata_chunk = &nvtd; - settings.userdata_chunk_size = sizeof(NearestVertexTLSData); - BLI_task_parallel_range(0, totnode, &task_data, do_nearest_vertex_get_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - return nvtd.nearest_vertex; -} - -bool SCULPT_is_symmetry_iteration_valid(char i, char symm) -{ - return i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))); -} - -bool SCULPT_is_vertex_inside_brush_radius_symm(const float vertex[3], - const float br_co[3], - float radius, - char symm) -{ - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - float location[3]; - flip_v3_v3(location, br_co, (char)i); - if (len_squared_v3v3(location, vertex) < radius * radius) { - return true; - } - } - return false; -} - -void SCULPT_tag_update_overlays(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - ED_region_tag_redraw(region); - - Object *ob = CTX_data_active_object(C); - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); - - RegionView3D *rv3d = CTX_wm_region_view3d(C); - if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Flood Fill API - * - * Iterate over connected vertices, starting from one or more initial vertices. - * \{ */ - -void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood) -{ - int vertex_count = SCULPT_vertex_count_get(ss); - SCULPT_vertex_random_access_ensure(ss); - - flood->queue = BLI_gsqueue_new(sizeof(intptr_t)); - flood->visited_verts = BLI_BITMAP_NEW(vertex_count, "visited verts"); -} - -void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex) -{ - BLI_gsqueue_push(flood->queue, &vertex); -} - -void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) -{ - BLI_gsqueue_push(flood->queue, &vertex); - BLI_BITMAP_ENABLE(flood->visited_verts, vertex.i); -} - -void SCULPT_floodfill_add_initial_with_symmetry(Sculpt *sd, - Object *ob, - SculptSession *ss, - SculptFloodFill *flood, - PBVHVertRef vertex, - float radius) -{ - /* Add active vertex and symmetric vertices to the queue. */ - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - PBVHVertRef v = {PBVH_REF_NONE}; - - if (i == 0) { - v = vertex; - } - else if (radius > 0.0f) { - float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; - float location[3]; - flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), i); - v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); - } - - if (v.i != PBVH_REF_NONE) { - SCULPT_floodfill_add_initial(flood, v); - } - } -} - -void SCULPT_floodfill_add_active( - Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, float radius) -{ - /* Add active vertex and symmetric vertices to the queue. */ - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - for (char i = 0; i <= symm; ++i) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - - PBVHVertRef v = {PBVH_REF_NONE}; - - if (i == 0) { - v = SCULPT_active_vertex_get(ss); - } - else if (radius > 0.0f) { - float location[3]; - flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), i); - v = SCULPT_nearest_vertex_get(sd, ob, location, radius, false); - } - - if (v.i != PBVH_REF_NONE) { - SCULPT_floodfill_add_initial(flood, v); - } - } -} - -void SCULPT_floodfill_execute(SculptSession *ss, - SculptFloodFill *flood, - bool (*func)(SculptSession *ss, - PBVHVertRef from_v, - PBVHVertRef to_v, - bool is_duplicate, - void *userdata), - void *userdata) -{ - while (!BLI_gsqueue_is_empty(flood->queue)) { - PBVHVertRef from_v; - - BLI_gsqueue_pop(flood->queue, &from_v); - SculptVertexNeighborIter ni; - SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { - const PBVHVertRef to_v = ni.vertex; - int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - - if (BLI_BITMAP_TEST(flood->visited_verts, to_v_i)) { - continue; - } - - if (!SCULPT_vertex_visible_get(ss, to_v)) { - continue; - } - - BLI_BITMAP_ENABLE(flood->visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, to_v)); - - if (func(ss, from_v, to_v, ni.is_duplicate, userdata)) { - BLI_gsqueue_push(flood->queue, &to_v); - } - } - SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); - } -} - -void SCULPT_floodfill_free(SculptFloodFill *flood) -{ - MEM_SAFE_FREE(flood->visited_verts); - BLI_gsqueue_free(flood->queue); - flood->queue = NULL; -} - -/** \} */ - -static bool sculpt_tool_has_cube_tip(const char sculpt_tool) -{ - return ELEM( - sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT, SCULPT_TOOL_MULTIPLANE_SCRAPE); -} - -/* -------------------------------------------------------------------- */ -/** \name Tool Capabilities - * - * Avoid duplicate checks, internal logic only, - * share logic with #rna_def_sculpt_capabilities where possible. - * \{ */ - -static bool sculpt_tool_needs_original(const char sculpt_tool) -{ - return ELEM(sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); -} - -static bool sculpt_tool_is_proxy_used(const char sculpt_tool) -{ - return ELEM(sculpt_tool, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_POSE, - SCULPT_TOOL_DISPLACEMENT_SMEAR, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR, - SCULPT_TOOL_DRAW_FACE_SETS); -} - -static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush) -{ - return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(brush->sculpt_tool) && - (brush->topology_rake_factor > 0.0f) && (ss->bm != NULL); -} - -/** - * Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action - */ -static int sculpt_brush_needs_normal(const SculptSession *ss, Sculpt *sd, const Brush *brush) -{ - return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && - (ss->cache->normal_weight > 0.0f)) || - SCULPT_automasking_needs_normal(ss, sd, brush) || - ELEM(brush->sculpt_tool, - SCULPT_TOOL_BLOB, - SCULPT_TOOL_CREASE, - SCULPT_TOOL_DRAW, - SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_LAYER, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_THUMB) || - - (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) || - sculpt_brush_use_topology_rake(ss, brush); -} - -static bool sculpt_brush_needs_rake_rotation(const Brush *brush) -{ - return SCULPT_TOOL_HAS_RAKE(brush->sculpt_tool) && (brush->rake_factor != 0.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Init/Update - * \{ */ - -typedef enum StrokeFlags { - CLIP_X = 1, - CLIP_Y = 2, - CLIP_Z = 4, -} StrokeFlags; - -void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, SculptUndoNode *unode) -{ - SculptSession *ss = ob->sculpt; - BMesh *bm = ss->bm; - - memset(data, 0, sizeof(*data)); - data->unode = unode; - - if (bm) { - data->bm_log = ss->bm_log; - } - else { - data->coords = data->unode->co; - data->normals = data->unode->no; - data->vmasks = data->unode->mask; - data->colors = data->unode->col; - } -} - -void SCULPT_orig_vert_data_init(SculptOrigVertData *data, - Object *ob, - PBVHNode *node, - SculptUndoType type) -{ - SculptUndoNode *unode; - unode = SCULPT_undo_push_node(ob, node, type); - SCULPT_orig_vert_data_unode_init(data, ob, unode); -} - -void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) -{ - if (orig_data->unode->type == SCULPT_UNDO_COORDS) { - if (orig_data->bm_log) { - BM_log_original_vert_data(orig_data->bm_log, iter->bm_vert, &orig_data->co, &orig_data->no); - } - else { - orig_data->co = orig_data->coords[iter->i]; - orig_data->no = orig_data->normals[iter->i]; - } - } - else if (orig_data->unode->type == SCULPT_UNDO_COLOR) { - orig_data->col = orig_data->colors[iter->i]; - } - else if (orig_data->unode->type == SCULPT_UNDO_MASK) { - if (orig_data->bm_log) { - orig_data->mask = BM_log_original_mask(orig_data->bm_log, iter->bm_vert); - } - else { - orig_data->mask = orig_data->vmasks[iter->i]; - } - } -} - -static void sculpt_rake_data_update(struct SculptRakeData *srd, const float co[3]) -{ - float rake_dist = len_v3v3(srd->follow_co, co); - if (rake_dist > srd->follow_dist) { - interp_v3_v3v3(srd->follow_co, srd->follow_co, co, rake_dist - srd->follow_dist); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Dynamic Topology - * \{ */ - -bool SCULPT_stroke_is_dynamic_topology(const SculptSession *ss, const Brush *brush) -{ - return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && - - (!ss->cache || (!ss->cache->alt_smooth)) && - - /* Requires mesh restore, which doesn't work with - * dynamic-topology. */ - !(brush->flag & BRUSH_ANCHORED) && !(brush->flag & BRUSH_DRAG_DOT) && - - SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Paint Mesh - * \{ */ - -static void paint_mesh_restore_co_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - SculptUndoNode *unode; - SculptUndoType type; - - switch (data->brush->sculpt_tool) { - case SCULPT_TOOL_MASK: - type = SCULPT_UNDO_MASK; - break; - case SCULPT_TOOL_PAINT: - case SCULPT_TOOL_SMEAR: - type = SCULPT_UNDO_COLOR; - break; - default: - type = SCULPT_UNDO_COORDS; - break; - } - - if (ss->bm) { - unode = SCULPT_undo_push_node(data->ob, data->nodes[n], type); - } - else { - unode = SCULPT_undo_get_node(data->nodes[n], type); - } - - if (!unode) { - return; - } - - switch (type) { - case SCULPT_UNDO_MASK: - BKE_pbvh_node_mark_update_mask(data->nodes[n]); - break; - case SCULPT_UNDO_COLOR: - BKE_pbvh_node_mark_update_color(data->nodes[n]); - break; - case SCULPT_UNDO_COORDS: - BKE_pbvh_node_mark_update(data->nodes[n]); - break; - default: - break; - } - - PBVHVertexIter vd; - SculptOrigVertData orig_data; - - SCULPT_orig_vert_data_unode_init(&orig_data, data->ob, unode); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - SCULPT_orig_vert_data_update(&orig_data, &vd); - - if (orig_data.unode->type == SCULPT_UNDO_COORDS) { - copy_v3_v3(vd.co, orig_data.co); - if (vd.no) { - copy_v3_v3(vd.no, orig_data.no); - } - else { - copy_v3_v3(vd.fno, orig_data.no); - } - if (vd.mvert) { - BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); - } - } - else if (orig_data.unode->type == SCULPT_UNDO_MASK) { - *vd.mask = orig_data.mask; - } - else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { - SCULPT_vertex_color_set(ss, vd.vertex, orig_data.col); - } - } - BKE_pbvh_vertex_iter_end; -} - -static void paint_mesh_restore_co(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - PBVHNode **nodes; - int totnode; - - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - /** - * Disable multi-threading when dynamic-topology is enabled. Otherwise, - * new entries might be inserted by #SCULPT_undo_push_node() into the #GHash - * used internally by #BM_log_original_vert_co() by a different thread. See T33787. - */ - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true && !ss->bm, totnode); - BLI_task_parallel_range(0, totnode, &data, paint_mesh_restore_co_task_cb, &settings); - - BKE_pbvh_node_color_buffer_free(ss->pbvh); - - MEM_SAFE_FREE(nodes); -} - -/*** BVH Tree ***/ - -static void sculpt_extend_redraw_rect_previous(Object *ob, rcti *rect) -{ - /* Expand redraw \a rect with redraw \a rect from previous step to - * prevent partial-redraw issues caused by fast strokes. This is - * needed here (not in sculpt_flush_update) as it was before - * because redraw rectangle should be the same in both of - * optimized PBVH draw function and 3d view redraw, if not -- some - * mesh parts could disappear from screen (sergey). */ - SculptSession *ss = ob->sculpt; - - if (!ss->cache) { - return; - } - - if (BLI_rcti_is_empty(&ss->cache->previous_r)) { - return; - } - - BLI_rcti_union(rect, &ss->cache->previous_r); -} - -bool SCULPT_get_redraw_rect(ARegion *region, RegionView3D *rv3d, Object *ob, rcti *rect) -{ - PBVH *pbvh = ob->sculpt->pbvh; - float bb_min[3], bb_max[3]; - - if (!pbvh) { - return false; - } - - BKE_pbvh_redraw_BB(pbvh, bb_min, bb_max); - - /* Convert 3D bounding box to screen space. */ - if (!paint_convert_bb_to_rect(rect, bb_min, bb_max, region, rv3d, ob)) { - return false; - } - - return true; -} - -void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *region, Object *ob) -{ - PBVH *pbvh = ob->sculpt->pbvh; - /* Copy here, original will be used below. */ - rcti rect = ob->sculpt->cache->current_r; - - sculpt_extend_redraw_rect_previous(ob, &rect); - - paint_calc_redraw_planes(planes, region, ob, &rect); - - /* We will draw this \a rect, so now we can set it as the previous partial \a rect. - * Note that we don't update with the union of previous/current (\a rect), only with - * the current. Thus we avoid the rectangle needlessly growing to include - * all the stroke area. */ - ob->sculpt->cache->previous_r = ob->sculpt->cache->current_r; - - /* Clear redraw flag from nodes. */ - if (pbvh) { - BKE_pbvh_update_bounds(pbvh, PBVH_UpdateRedraw); - } -} - -/************************ Brush Testing *******************/ - -void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) -{ - RegionView3D *rv3d = ss->cache ? ss->cache->vc->rv3d : ss->rv3d; - View3D *v3d = ss->cache ? ss->cache->vc->v3d : ss->v3d; - - test->radius_squared = ss->cache ? ss->cache->radius_squared : - ss->cursor_radius * ss->cursor_radius; - test->radius = sqrtf(test->radius_squared); - - if (ss->cache) { - copy_v3_v3(test->location, ss->cache->location); - test->mirror_symmetry_pass = ss->cache->mirror_symmetry_pass; - test->radial_symmetry_pass = ss->cache->radial_symmetry_pass; - copy_m4_m4(test->symm_rot_mat_inv, ss->cache->symm_rot_mat_inv); - } - else { - copy_v3_v3(test->location, ss->cursor_location); - test->mirror_symmetry_pass = 0; - test->radial_symmetry_pass = 0; - unit_m4(test->symm_rot_mat_inv); - } - - /* Just for initialize. */ - test->dist = 0.0f; - - /* Only for 2D projection. */ - zero_v4(test->plane_view); - zero_v4(test->plane_tool); - - if (RV3D_CLIPPING_ENABLED(v3d, rv3d)) { - test->clip_rv3d = rv3d; - } - else { - test->clip_rv3d = NULL; - } -} - -BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const float co[3]) -{ - RegionView3D *rv3d = test->clip_rv3d; - if (!rv3d) { - return false; - } - float symm_co[3]; - flip_v3_v3(symm_co, co, test->mirror_symmetry_pass); - if (test->radial_symmetry_pass) { - mul_m4_v3(test->symm_rot_mat_inv, symm_co); - } - return ED_view3d_clipping_test(rv3d, symm_co, true); -} - -bool SCULPT_brush_test_sphere(SculptBrushTest *test, const float co[3]) -{ - float distsq = len_squared_v3v3(co, test->location); - - if (distsq > test->radius_squared) { - return false; - } - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - test->dist = sqrtf(distsq); - return true; -} - -bool SCULPT_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]) -{ - float distsq = len_squared_v3v3(co, test->location); - - if (distsq > test->radius_squared) { - return false; - } - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - test->dist = distsq; - return true; -} - -bool SCULPT_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]) -{ - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - return len_squared_v3v3(co, test->location) <= test->radius_squared; -} - -bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]) -{ - float co_proj[3]; - closest_to_plane_normalized_v3(co_proj, test->plane_view, co); - float distsq = len_squared_v3v3(co_proj, test->location); - - if (distsq > test->radius_squared) { - return false; - } - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - test->dist = distsq; - return true; -} - -bool SCULPT_brush_test_cube(SculptBrushTest *test, - const float co[3], - const float local[4][4], - const float roundness) -{ - float side = 1.0f; - float local_co[3]; - - if (sculpt_brush_test_clipping(test, co)) { - return false; - } - - mul_v3_m4v3(local_co, local, co); - - local_co[0] = fabsf(local_co[0]); - local_co[1] = fabsf(local_co[1]); - local_co[2] = fabsf(local_co[2]); - - /* Keep the square and circular brush tips the same size. */ - side += (1.0f - side) * roundness; - - const float hardness = 1.0f - roundness; - const float constant_side = hardness * side; - const float falloff_side = roundness * side; - - if (!(local_co[0] <= side && local_co[1] <= side && local_co[2] <= side)) { - /* Outside the square. */ - return false; - } - if (min_ff(local_co[0], local_co[1]) > constant_side) { - /* Corner, distance to the center of the corner circle. */ - float r_point[3]; - copy_v3_fl(r_point, constant_side); - test->dist = len_v2v2(r_point, local_co) / falloff_side; - return true; - } - if (max_ff(local_co[0], local_co[1]) > constant_side) { - /* Side, distance to the square XY axis. */ - test->dist = (max_ff(local_co[0], local_co[1]) - constant_side) / falloff_side; - return true; - } - - /* Inside the square, constant distance. */ - test->dist = 0.0f; - return true; -} - -SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, - SculptBrushTest *test, - char falloff_shape) -{ - SCULPT_brush_test_init(ss, test); - SculptBrushTestFn sculpt_brush_test_sq_fn; - if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - sculpt_brush_test_sq_fn = SCULPT_brush_test_sphere_sq; - } - else { - /* PAINT_FALLOFF_SHAPE_TUBE */ - plane_from_point_normal_v3(test->plane_view, test->location, ss->cache->view_normal); - sculpt_brush_test_sq_fn = SCULPT_brush_test_circle_sq; - } - return sculpt_brush_test_sq_fn; -} - -const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, - char falloff_shape) -{ - if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - return ss->cache->sculpt_normal_symm; - } - /* PAINT_FALLOFF_SHAPE_TUBE */ - return ss->cache->view_normal; -} - -static float frontface(const Brush *br, - const float sculpt_normal[3], - const float no[3], - const float fno[3]) -{ - if (!(br->flag & BRUSH_FRONTFACE)) { - return 1.0f; - } - - float dot; - if (no) { - dot = dot_v3v3(no, sculpt_normal); - } - else { - dot = dot_v3v3(fno, sculpt_normal); - } - return dot > 0.0f ? dot : 0.0f; -} - -#if 0 - -static bool sculpt_brush_test_cyl(SculptBrushTest *test, - float co[3], - float location[3], - const float area_no[3]) -{ - if (sculpt_brush_test_sphere_fast(test, co)) { - float t1[3], t2[3], t3[3], dist; - - sub_v3_v3v3(t1, location, co); - sub_v3_v3v3(t2, x2, location); - - cross_v3_v3v3(t3, area_no, t1); - - dist = len_v3(t3) / len_v3(t2); - - test->dist = dist; - - return true; - } - - return false; -} - -#endif - -/* ===== Sculpting ===== - */ - -static float calc_overlap(StrokeCache *cache, const char symm, const char axis, const float angle) -{ - float mirror[3]; - float distsq; - - flip_v3_v3(mirror, cache->true_location, symm); - - if (axis != 0) { - float mat[3][3]; - axis_angle_to_mat3_single(mat, axis, angle); - mul_m3_v3(mat, mirror); - } - - distsq = len_squared_v3v3(mirror, cache->true_location); - - if (distsq <= 4.0f * (cache->radius_squared)) { - return (2.0f * (cache->radius) - sqrtf(distsq)) / (2.0f * (cache->radius)); - } - return 0.0f; -} - -static float calc_radial_symmetry_feather(Sculpt *sd, - StrokeCache *cache, - const char symm, - const char axis) -{ - float overlap = 0.0f; - - for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { - const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; - overlap += calc_overlap(cache, symm, axis, angle); - } - - return overlap; -} - -static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache) -{ - if (!(sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) { - return 1.0f; - } - float overlap; - const int symm = cache->symmetry; - - overlap = 0.0f; - for (int i = 0; i <= symm; i++) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - - overlap += calc_overlap(cache, i, 0, 0); - - overlap += calc_radial_symmetry_feather(sd, cache, i, 'X'); - overlap += calc_radial_symmetry_feather(sd, cache, i, 'Y'); - overlap += calc_radial_symmetry_feather(sd, cache, i, 'Z'); - } - return 1.0f / overlap; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Calculate Normal and Center - * - * Calculate geometry surrounding the brush center. - * (optionally using original coordinates). - * - * Functions are: - * - #SCULPT_calc_area_center - * - #SCULPT_calc_area_normal - * - #SCULPT_calc_area_normal_and_center - * - * \note These are all _very_ similar, when changing one, check others. - * \{ */ - -typedef struct AreaNormalCenterTLSData { - /* 0 = towards view, 1 = flipped */ - float area_cos[2][3]; - float area_nos[2][3]; - int count_no[2]; - int count_co[2]; -} AreaNormalCenterTLSData; - -static void calc_area_normal_and_center_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - AreaNormalCenterTLSData *anctd = tls->userdata_chunk; - const bool use_area_nos = data->use_area_nos; - const bool use_area_cos = data->use_area_cos; - - PBVHVertexIter vd; - SculptUndoNode *unode = NULL; - - bool use_original = false; - bool normal_test_r, area_test_r; - - if (ss->cache && ss->cache->original) { - unode = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - use_original = (unode->co || unode->bm_entry); - } - - SculptBrushTest normal_test; - SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &normal_test, data->brush->falloff_shape); - - /* Update the test radius to sample the normal using the normal radius of the brush. */ - if (data->brush->ob_mode == OB_MODE_SCULPT) { - float test_radius = sqrtf(normal_test.radius_squared); - test_radius *= data->brush->normal_radius_factor; - normal_test.radius = test_radius; - normal_test.radius_squared = test_radius * test_radius; - } - - SculptBrushTest area_test; - SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &area_test, data->brush->falloff_shape); - - if (data->brush->ob_mode == OB_MODE_SCULPT) { - float test_radius = sqrtf(area_test.radius_squared); - /* Layer brush produces artifacts with normal and area radius */ - /* Enable area radius control only on Scrape for now */ - if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && - data->brush->area_radius_factor > 0.0f) { - test_radius *= data->brush->area_radius_factor; - if (ss->cache && data->brush->flag2 & BRUSH_AREA_RADIUS_PRESSURE) { - test_radius *= ss->cache->pressure; - } - } - else { - test_radius *= data->brush->normal_radius_factor; - } - area_test.radius = test_radius; - area_test.radius_squared = test_radius * test_radius; - } - - /* When the mesh is edited we can't rely on original coords - * (original mesh may not even have verts in brush radius). */ - if (use_original && data->has_bm_orco) { - float(*orco_coords)[3]; - int(*orco_tris)[3]; - int orco_tris_num; - - BKE_pbvh_node_get_bm_orco_data(data->nodes[n], &orco_tris, &orco_tris_num, &orco_coords, NULL); - - for (int i = 0; i < orco_tris_num; i++) { - const float *co_tri[3] = { - orco_coords[orco_tris[i][0]], - orco_coords[orco_tris[i][1]], - orco_coords[orco_tris[i][2]], - }; - float co[3]; - - closest_on_tri_to_point_v3(co, normal_test.location, UNPACK3(co_tri)); - - normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); - area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); - - if (!normal_test_r && !area_test_r) { - continue; - } - - float no[3]; - int flip_index; - - normal_tri_v3(no, UNPACK3(co_tri)); - - flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); - if (use_area_cos && area_test_r) { - /* Weight the coordinates towards the center. */ - float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); - const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - - float disp[3]; - sub_v3_v3v3(disp, co, area_test.location); - mul_v3_fl(disp, 1.0f - afactor); - add_v3_v3v3(co, area_test.location, disp); - add_v3_v3(anctd->area_cos[flip_index], co); - - anctd->count_co[flip_index] += 1; - } - if (use_area_nos && normal_test_r) { - /* Weight the normals towards the center. */ - float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); - const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - mul_v3_fl(no, nfactor); - - add_v3_v3(anctd->area_nos[flip_index], no); - anctd->count_no[flip_index] += 1; - } - } - } - else { - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float co[3]; - - /* For bm_vert only. */ - float no_s[3]; - - if (use_original) { - if (unode->bm_entry) { - const float *temp_co; - const float *temp_no_s; - BM_log_original_vert_data(ss->bm_log, vd.bm_vert, &temp_co, &temp_no_s); - copy_v3_v3(co, temp_co); - copy_v3_v3(no_s, temp_no_s); - } - else { - copy_v3_v3(co, unode->co[vd.i]); - copy_v3_v3(no_s, unode->no[vd.i]); - } - } - else { - copy_v3_v3(co, vd.co); - } - - normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); - area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); - - if (!normal_test_r && !area_test_r) { - continue; - } - - float no[3]; - int flip_index; - - data->any_vertex_sampled = true; - - if (use_original) { - copy_v3_v3(no, no_s); - } - else { - if (vd.no) { - copy_v3_v3(no, vd.no); - } - else { - copy_v3_v3(no, vd.fno); - } - } - - flip_index = (dot_v3v3(ss->cache ? ss->cache->view_normal : ss->cursor_view_normal, no) <= - 0.0f); - - if (use_area_cos && area_test_r) { - /* Weight the coordinates towards the center. */ - float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); - const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - - float disp[3]; - sub_v3_v3v3(disp, co, area_test.location); - mul_v3_fl(disp, 1.0f - afactor); - add_v3_v3v3(co, area_test.location, disp); - - add_v3_v3(anctd->area_cos[flip_index], co); - anctd->count_co[flip_index] += 1; - } - if (use_area_nos && normal_test_r) { - /* Weight the normals towards the center. */ - float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); - const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); - mul_v3_fl(no, nfactor); - - add_v3_v3(anctd->area_nos[flip_index], no); - anctd->count_no[flip_index] += 1; - } - } - BKE_pbvh_vertex_iter_end; - } -} - -static void calc_area_normal_and_center_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - AreaNormalCenterTLSData *join = chunk_join; - AreaNormalCenterTLSData *anctd = chunk; - - /* For flatten center. */ - add_v3_v3(join->area_cos[0], anctd->area_cos[0]); - add_v3_v3(join->area_cos[1], anctd->area_cos[1]); - - /* For area normal. */ - add_v3_v3(join->area_nos[0], anctd->area_nos[0]); - add_v3_v3(join->area_nos[1], anctd->area_nos[1]); - - /* Weights. */ - add_v2_v2_int(join->count_no, anctd->count_no); - add_v2_v2_int(join->count_co, anctd->count_co); -} - -void SCULPT_calc_area_center( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - int n; - - /* Intentionally set 'sd' to NULL since we share logic with vertex paint. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_cos = true, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For flatten center. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { - if (anctd.count_co[n] == 0) { - continue; - } - - mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); - break; - } - - if (n == 2) { - zero_v3(r_area_co); - } - - if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { - if (ss->cache) { - copy_v3_v3(r_area_co, ss->cache->location); - } - } -} - -void SCULPT_calc_area_normal( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, r_area_no); -} - -bool SCULPT_pbvh_calc_area_normal(const Brush *brush, - Object *ob, - PBVHNode **nodes, - int totnode, - bool use_threading, - float r_area_no[3]) -{ - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - - /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_nos = true, - .any_vertex_sampled = false, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, use_threading, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For area normal. */ - for (int i = 0; i < ARRAY_SIZE(anctd.area_nos); i++) { - if (normalize_v3_v3(r_area_no, anctd.area_nos[i]) != 0.0f) { - break; - } - } - - return data.any_vertex_sampled; -} - -void SCULPT_calc_area_normal_and_center( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); - int n; - - /* Intentionally set 'sd' to NULL since this is used for vertex paint too. */ - SculptThreadedTaskData data = { - .sd = NULL, - .ob = ob, - .brush = brush, - .nodes = nodes, - .totnode = totnode, - .has_bm_orco = has_bm_orco, - .use_area_cos = true, - .use_area_nos = true, - }; - - AreaNormalCenterTLSData anctd = {{{0}}}; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = calc_area_normal_and_center_reduce; - settings.userdata_chunk = &anctd; - settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); - BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); - - /* For flatten center. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { - if (anctd.count_co[n] == 0) { - continue; - } - - mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); - break; - } - - if (n == 2) { - zero_v3(r_area_co); - } - - if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { - if (ss->cache) { - copy_v3_v3(r_area_co, ss->cache->location); - } - } - - /* For area normal. */ - for (n = 0; n < ARRAY_SIZE(anctd.area_nos); n++) { - if (normalize_v3_v3(r_area_no, anctd.area_nos[n]) != 0.0f) { - break; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Generic Brush Utilities - * \{ */ - -/** - * Return modified brush strength. Includes the direction of the brush, positive - * values pull vertices, negative values push. Uses tablet pressure and a - * special multiplier found experimentally to scale the strength factor. - */ -static float brush_strength(const Sculpt *sd, - const StrokeCache *cache, - const float feather, - const UnifiedPaintSettings *ups, - const PaintModeSettings *UNUSED(paint_mode_settings)) -{ - const Scene *scene = cache->vc->scene; - const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); - - /* Primary strength input; square it to make lower values more sensitive. */ - const float root_alpha = BKE_brush_alpha_get(scene, brush); - const float alpha = root_alpha * root_alpha; - const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f; - const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f; - const float pen_flip = cache->pen_flip ? -1.0f : 1.0f; - const float invert = cache->invert ? -1.0f : 1.0f; - float overlap = ups->overlap_factor; - /* Spacing is integer percentage of radius, divide by 50 to get - * normalized diameter. */ - - float flip = dir * invert * pen_flip; - if (brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - flip = 1.0f; - } - - /* Pressure final value after being tweaked depending on the brush. */ - float final_pressure; - - switch (brush->sculpt_tool) { - case SCULPT_TOOL_CLAY: - final_pressure = pow4f(pressure); - overlap = (1.0f + overlap) / 2.0f; - return 0.25f * alpha * flip * final_pressure * overlap * feather; - case SCULPT_TOOL_DRAW: - case SCULPT_TOOL_DRAW_SHARP: - case SCULPT_TOOL_LAYER: - return alpha * flip * pressure * overlap * feather; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_CLOTH: - if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { - /* Grab deform uses the same falloff as a regular grab brush. */ - return root_alpha * feather; - } - else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) { - return root_alpha * feather * pressure * overlap; - } - else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) { - /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over - * the same vertices. */ - return 0.1f * alpha * flip * pressure * overlap * feather; - } - else { - /* Multiply by 10 by default to get a larger range of strength depending on the size of the - * brush and object. */ - return 10.0f * alpha * flip * pressure * overlap * feather; - } - case SCULPT_TOOL_DRAW_FACE_SETS: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_SLIDE_RELAX: - return alpha * pressure * overlap * feather * 2.0f; - case SCULPT_TOOL_PAINT: - final_pressure = pressure * pressure; - return final_pressure * overlap * feather; - case SCULPT_TOOL_SMEAR: - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - return alpha * pressure * overlap * feather; - case SCULPT_TOOL_CLAY_STRIPS: - /* Clay Strips needs less strength to compensate the curve. */ - final_pressure = powf(pressure, 1.5f); - return alpha * flip * final_pressure * overlap * feather * 0.3f; - case SCULPT_TOOL_CLAY_THUMB: - final_pressure = pressure * pressure; - return alpha * flip * final_pressure * overlap * feather * 1.3f; - - case SCULPT_TOOL_MASK: - overlap = (1.0f + overlap) / 2.0f; - switch ((BrushMaskTool)brush->mask_tool) { - case BRUSH_MASK_DRAW: - return alpha * flip * pressure * overlap * feather; - case BRUSH_MASK_SMOOTH: - return alpha * pressure * feather; - } - BLI_assert_msg(0, "Not supposed to happen"); - return 0.0f; - - case SCULPT_TOOL_CREASE: - case SCULPT_TOOL_BLOB: - return alpha * flip * pressure * overlap * feather; - - case SCULPT_TOOL_INFLATE: - if (flip > 0.0f) { - return 0.250f * alpha * flip * pressure * overlap * feather; - } - else { - return 0.125f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - overlap = (1.0f + overlap) / 2.0f; - return alpha * flip * pressure * overlap * feather; - - case SCULPT_TOOL_FILL: - case SCULPT_TOOL_SCRAPE: - case SCULPT_TOOL_FLATTEN: - if (flip > 0.0f) { - overlap = (1.0f + overlap) / 2.0f; - return alpha * flip * pressure * overlap * feather; - } - else { - /* Reduce strength for DEEPEN, PEAKS, and CONTRAST. */ - return 0.5f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_SMOOTH: - return flip * alpha * pressure * feather; - - case SCULPT_TOOL_PINCH: - if (flip > 0.0f) { - return alpha * flip * pressure * overlap * feather; - } - else { - return 0.25f * alpha * flip * pressure * overlap * feather; - } - - case SCULPT_TOOL_NUDGE: - overlap = (1.0f + overlap) / 2.0f; - return alpha * pressure * overlap * feather; - - case SCULPT_TOOL_THUMB: - return alpha * pressure * feather; - - case SCULPT_TOOL_SNAKE_HOOK: - return root_alpha * feather; - - case SCULPT_TOOL_GRAB: - return root_alpha * feather; - - case SCULPT_TOOL_ROTATE: - return alpha * pressure * feather; - - case SCULPT_TOOL_ELASTIC_DEFORM: - case SCULPT_TOOL_POSE: - case SCULPT_TOOL_BOUNDARY: - return root_alpha * feather; - - default: - return 0.0f; - } -} - -float SCULPT_brush_strength_factor(SculptSession *ss, - const Brush *br, - const float brush_point[3], - float len, - const float vno[3], - const float fno[3], - float mask, - const PBVHVertRef vertex, - const int thread_id, - AutomaskingNodeData *automask_data) -{ - StrokeCache *cache = ss->cache; - const Scene *scene = cache->vc->scene; - const MTex *mtex = BKE_brush_mask_texture_get(br, OB_MODE_SCULPT); - float avg = 1.0f; - float rgba[4]; - float point[3]; - - sub_v3_v3v3(point, brush_point, cache->plane_offset); - - if (!mtex->tex) { - avg = 1.0f; - } - else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { - /* Get strength by feeding the vertex location directly into a texture. */ - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point, rgba, 0, ss->tex_pool); - } - else { - float symm_point[3], point_2d[2]; - /* Quite warnings. */ - float x = 0.0f, y = 0.0f; - - /* If the active area is being applied for symmetry, flip it - * across the symmetry axis and rotate it back to the original - * position in order to project it. This insures that the - * brush texture will be oriented correctly. */ - if (cache->radial_symmetry_pass) { - mul_m4_v3(cache->symm_rot_mat_inv, point); - } - flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); - - ED_view3d_project_float_v2_m4(cache->vc->region, symm_point, point_2d, cache->projection_mat); - - /* Still no symmetry supported for other paint modes. - * Sculpt does it DIY. */ - if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { - /* Similar to fixed mode, but projects from brush angle - * rather than view direction. */ - - mul_m4_v3(cache->brush_local_mat, symm_point); - - x = symm_point[0]; - y = symm_point[1]; - - x *= mtex->size[0]; - y *= mtex->size[1]; - - x += mtex->ofs[0]; - y += mtex->ofs[1]; - - avg = paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id); - - avg += br->texture_sample_bias; - } - else { - const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; - avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); - } - } - - /* Hardness. */ - float final_len = len; - const float hardness = cache->paint_brush.hardness; - float p = len / cache->radius; - if (p < hardness) { - final_len = 0.0f; - } - else if (hardness == 1.0f) { - final_len = cache->radius; - } - else { - p = (p - hardness) / (1.0f - hardness); - final_len = p * cache->radius; - } - - /* Falloff curve. */ - avg *= BKE_brush_curve_strength(br, final_len, cache->radius); - avg *= frontface(br, cache->view_normal, vno, fno); - - /* Paint mask. */ - avg *= 1.0f - mask; - - /* Auto-masking. */ - avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex, automask_data); - - return avg; -} - -bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v) -{ - SculptSearchSphereData *data = data_v; - const float *center; - float nearest[3]; - if (data->center) { - center = data->center; - } - else { - center = data->ss->cache ? data->ss->cache->location : data->ss->cursor_location; - } - float t[3], bb_min[3], bb_max[3]; - - if (data->ignore_fully_ineffective) { - if (BKE_pbvh_node_fully_hidden_get(node)) { - return false; - } - if (BKE_pbvh_node_fully_masked_get(node)) { - return false; - } - } - - if (data->original) { - BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); - } - else { - BKE_pbvh_node_get_BB(node, bb_min, bb_max); - } - - for (int i = 0; i < 3; i++) { - if (bb_min[i] > center[i]) { - nearest[i] = bb_min[i]; - } - else if (bb_max[i] < center[i]) { - nearest[i] = bb_max[i]; - } - else { - nearest[i] = center[i]; - } - } - - sub_v3_v3v3(t, center, nearest); - - return len_squared_v3(t) < data->radius_squared; -} - -bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v) -{ - SculptSearchCircleData *data = data_v; - float bb_min[3], bb_max[3]; - - if (data->ignore_fully_ineffective) { - if (BKE_pbvh_node_fully_masked_get(node)) { - return false; - } - } - - if (data->original) { - BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); - } - else { - BKE_pbvh_node_get_BB(node, bb_min, bb_min); - } - - float dummy_co[3], dummy_depth; - const float dist_sq = dist_squared_ray_to_aabb_v3( - data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth); - - /* Seems like debug code. - * Maybe this function can just return true if the node is not fully masked. */ - return dist_sq < data->radius_squared || true; -} - -void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3]) -{ - for (int i = 0; i < 3; i++) { - if (sd->flags & (SCULPT_LOCK_X << i)) { - continue; - } - - bool do_clip = false; - float co_clip[3]; - if (ss->cache && (ss->cache->flag & (CLIP_X << i))) { - /* Take possible mirror object into account. */ - mul_v3_m4v3(co_clip, ss->cache->clip_mirror_mtx, co); - - if (fabsf(co_clip[i]) <= ss->cache->clip_tolerance[i]) { - co_clip[i] = 0.0f; - float imtx[4][4]; - invert_m4_m4(imtx, ss->cache->clip_mirror_mtx); - mul_m4_v3(imtx, co_clip); - do_clip = true; - } - } - - if (do_clip) { - co[i] = co_clip[i]; - } - else { - co[i] = val[i]; - } - } -} - -static PBVHNode **sculpt_pbvh_gather_cursor_update(Object *ob, - Sculpt *sd, - bool use_original, - int *r_totnode) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cursor_radius, - .original = use_original, - .ignore_fully_ineffective = false, - .center = NULL, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); - return nodes; -} - -static PBVHNode **sculpt_pbvh_gather_generic(Object *ob, - Sculpt *sd, - const Brush *brush, - bool use_original, - float radius_scale, - int *r_totnode) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - - /* Build a list of all nodes that are potentially within the cursor or brush's area of influence. - */ - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = square_f(ss->cache->radius * radius_scale), - .original = use_original, - .ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK, - .center = NULL, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); - } - else { - struct DistRayAABB_Precalc dist_ray_to_aabb_precalc; - dist_squared_ray_to_aabb_v3_precalc( - &dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); - SculptSearchCircleData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cache ? square_f(ss->cache->radius * radius_scale) : - ss->cursor_radius, - .original = use_original, - .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc, - .ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK, - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode); - } - return nodes; -} - -/* Calculate primary direction of movement for many brushes. */ -static void calc_sculpt_normal( - Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - const SculptSession *ss = ob->sculpt; - - 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(sd, ob, nodes, totnode, r_area_no); - break; - - default: - break; - } -} - -static void update_sculpt_normal(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) -{ - const Brush *brush = BKE_paint_brush(&sd->paint); - StrokeCache *cache = ob->sculpt->cache; - /* Grab brush does not update the sculpt normal during a stroke. */ - const bool update_normal = - !(brush->flag & BRUSH_ORIGINAL_NORMAL) && !(brush->sculpt_tool == SCULPT_TOOL_GRAB) && - !(brush->sculpt_tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && - !(brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) && - !(brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f); - - if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && - (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal)) { - calc_sculpt_normal(sd, ob, nodes, totnode, cache->sculpt_normal); - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(cache->sculpt_normal, cache->sculpt_normal, cache->view_normal); - normalize_v3(cache->sculpt_normal); - } - copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); - } - else { - copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); - flip_v3(cache->sculpt_normal_symm, cache->mirror_symmetry_pass); - mul_m4_v3(cache->symm_rot_mat, cache->sculpt_normal_symm); - } -} - -static void calc_local_y(ViewContext *vc, const float center[3], float y[3]) -{ - Object *ob = vc->obact; - float loc[3]; - const float xy_delta[2] = {0.0f, 1.0f}; - - mul_v3_m4v3(loc, ob->world_to_object, center); - const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc); - - ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y); - normalize_v3(y); - - add_v3_v3(y, ob->loc); - mul_m4_v3(ob->world_to_object, y); -} - -static void calc_brush_local_mat(const Brush *brush, Object *ob, float local_mat[4][4]) -{ - const StrokeCache *cache = ob->sculpt->cache; - float tmat[4][4]; - float mat[4][4]; - float scale[4][4]; - float angle, v[3]; - float up[3]; - - /* Ensure `ob->world_to_object` is up to date. */ - invert_m4_m4(ob->world_to_object, ob->object_to_world); - - /* Initialize last column of matrix. */ - mat[0][3] = 0.0f; - mat[1][3] = 0.0f; - mat[2][3] = 0.0f; - mat[3][3] = 1.0f; - - /* Get view's up vector in object-space. */ - calc_local_y(cache->vc, cache->location, up); - - /* Calculate the X axis of the local matrix. */ - cross_v3_v3v3(v, up, cache->sculpt_normal); - /* Apply rotation (user angle, rake, etc.) to X axis. */ - angle = brush->mtex.rot - cache->special_rotation; - rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle); - - /* Get other axes. */ - cross_v3_v3v3(mat[1], cache->sculpt_normal, mat[0]); - copy_v3_v3(mat[2], cache->sculpt_normal); - - /* Set location. */ - copy_v3_v3(mat[3], cache->location); - - /* Scale by brush radius. */ - normalize_m4(mat); - scale_m4_fl(scale, cache->radius); - mul_m4_m4m4(tmat, mat, scale); - - /* Return inverse (for converting from model-space coords to local area coords). */ - invert_m4_m4(local_mat, tmat); -} - -#define SCULPT_TILT_SENSITIVITY 0.7f -void SCULPT_tilt_apply_to_normal(float r_normal[3], StrokeCache *cache, const float tilt_strength) -{ - if (!U.experimental.use_sculpt_tools_tilt) { - return; - } - const float rot_max = M_PI_2 * tilt_strength * SCULPT_TILT_SENSITIVITY; - mul_v3_mat3_m4v3(r_normal, cache->vc->obact->object_to_world, r_normal); - float normal_tilt_y[3]; - rotate_v3_v3v3fl(normal_tilt_y, r_normal, cache->vc->rv3d->viewinv[0], cache->y_tilt * rot_max); - float normal_tilt_xy[3]; - rotate_v3_v3v3fl( - normal_tilt_xy, normal_tilt_y, cache->vc->rv3d->viewinv[1], cache->x_tilt * rot_max); - mul_v3_mat3_m4v3(r_normal, cache->vc->obact->world_to_object, normal_tilt_xy); - normalize_v3(r_normal); -} - -void SCULPT_tilt_effective_normal_get(const SculptSession *ss, const Brush *brush, float r_no[3]) -{ - copy_v3_v3(r_no, ss->cache->sculpt_normal_symm); - SCULPT_tilt_apply_to_normal(r_no, ss->cache, brush->tilt_strength_factor); -} - -static void update_brush_local_mat(Sculpt *sd, Object *ob) -{ - StrokeCache *cache = ob->sculpt->cache; - - if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) { - calc_brush_local_mat(BKE_paint_brush(&sd->paint), ob, cache->brush_local_mat); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Texture painting - * \{ */ - -static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, - const Brush *brush, - Object *ob) -{ - if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { - Image *image; - ImageUser *image_user; - return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); - } - - return false; -} - -static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, - SculptSession *ss, - Object *ob) -{ - BLI_assert(ob->type == OB_MESH); - Mesh *mesh = (Mesh *)ob->data; - - Image *image; - ImageUser *image_user; - if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { - return; - } - - BKE_pbvh_build_pixels(ss->pbvh, mesh, image, image_user); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Generic Brush Plane & Symmetry Utilities - * \{ */ - -typedef struct { - SculptSession *ss; - const float *ray_start; - const float *ray_normal; - bool hit; - float depth; - bool original; - - PBVHVertRef active_vertex; - float *face_normal; - - int active_face_grid_index; - - struct IsectRayPrecalc isect_precalc; -} SculptRaycastData; - -typedef struct { - SculptSession *ss; - const float *ray_start, *ray_normal; - bool hit; - float depth; - float dist_sq_to_ray; - bool original; -} SculptFindNearestToRayData; - -ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) -{ - ePaintSymmetryAreas symm_area = PAINT_SYMM_AREA_DEFAULT; - if (co[0] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_X; - } - if (co[1] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_Y; - } - if (co[2] < 0.0f) { - symm_area |= PAINT_SYMM_AREA_Z; - } - return symm_area; -} - -void SCULPT_flip_v3_by_symm_area(float v[3], - const ePaintSymmetryFlags symm, - const ePaintSymmetryAreas symmarea, - const float pivot[3]) -{ - for (int i = 0; i < 3; i++) { - ePaintSymmetryFlags symm_it = 1 << i; - if (!(symm & symm_it)) { - continue; - } - if (symmarea & symm_it) { - flip_v3(v, symm_it); - } - if (pivot[i] < 0.0f) { - flip_v3(v, symm_it); - } - } -} - -void SCULPT_flip_quat_by_symm_area(float quat[4], - const ePaintSymmetryFlags symm, - const ePaintSymmetryAreas symmarea, - const float pivot[3]) -{ - for (int i = 0; i < 3; i++) { - ePaintSymmetryFlags symm_it = 1 << i; - if (!(symm & symm_it)) { - continue; - } - if (symmarea & symm_it) { - flip_qt(quat, symm_it); - } - if (pivot[i] < 0.0f) { - flip_qt(quat, symm_it); - } - } -} - -void SCULPT_calc_brush_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); - - zero_v3(r_area_co); - zero_v3(r_area_no); - - 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); - } -} - -int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3]) -{ - return (!(brush->flag & BRUSH_PLANE_TRIM) || - (dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared)); -} - -int SCULPT_plane_point_side(const float co[3], const float plane[4]) -{ - float d = plane_point_side_v3(plane, co); - return d <= 0.0f; -} - -float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - float rv = brush->plane_offset; - - if (brush->flag & BRUSH_OFFSET_PRESSURE) { - rv *= ss->cache->pressure; - } - - return rv; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Gravity Brush - * \{ */ - -static void do_gravity_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 *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; - } - 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.vertex, - thread_id, - NULL); - - mul_v3_v3fl(proxy[vd.i], offset, fade); - - if (vd.mvert) { - BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); - } - } - BKE_pbvh_vertex_iter_end; -} - -static void do_gravity(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float bstrength) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - float offset[3]; - float gravity_vector[3]; - - mul_v3_v3fl(gravity_vector, ss->cache->gravity_direction, -ss->cache->radius_squared); - - /* Offset with as much as possible factored in already. */ - mul_v3_v3v3(offset, gravity_vector, ss->cache->scale); - mul_v3_fl(offset, bstrength); - - /* 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_gravity_task_cb_ex, &settings); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Sculpt Brush Utilities - * \{ */ - -void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) -{ - Mesh *me = (Mesh *)ob->data; - float(*ofs)[3] = NULL; - int a; - const int kb_act_idx = ob->shapenr - 1; - KeyBlock *currkey; - - /* For relative keys editing of base should update other keys. */ - if (BKE_keyblock_is_basis(me->key, kb_act_idx)) { - ofs = BKE_keyblock_convert_to_vertcos(ob, kb); - - /* Calculate key coord offsets (from previous location). */ - for (a = 0; a < me->totvert; a++) { - sub_v3_v3v3(ofs[a], vertCos[a], ofs[a]); - } - - /* Apply offsets on other keys. */ - for (currkey = me->key->block.first; currkey; currkey = currkey->next) { - if ((currkey != kb) && (currkey->relative == kb_act_idx)) { - BKE_keyblock_update_from_offset(ob, currkey, ofs); - } - } - - MEM_freeN(ofs); - } - - /* Modifying of basis key should update mesh. */ - if (kb == me->key->refkey) { - MVert *verts = BKE_mesh_verts_for_write(me); - - for (a = 0; a < me->totvert; a++) { - copy_v3_v3(verts[a].co, vertCos[a]); - } - BKE_mesh_tag_coords_changed(me); - } - - /* Apply new coords on active key block, no need to re-allocate kb->data here! */ - BKE_keyblock_update_from_vertcos(ob, kb, vertCos); -} - -/* NOTE: we do the topology update before any brush actions to avoid - * issues with the proxies. The size of the proxy can't change, so - * topology must be updated first. */ -static void sculpt_topology_update(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *UNUSED(ups), - PaintModeSettings *UNUSED(paint_mode_settings)) -{ - SculptSession *ss = ob->sculpt; - - int n, totnode; - /* Build a list of all nodes that are potentially within the brush's area of influence. */ - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - ss->cache->original; - const float radius_scale = 1.25f; - PBVHNode **nodes = sculpt_pbvh_gather_generic( - ob, sd, brush, use_original, radius_scale, &totnode); - - /* Only act if some verts are inside the brush area. */ - if (totnode == 0) { - return; - } - - /* Free index based vertex info as it will become invalid after modifying the topology during the - * stroke. */ - MEM_SAFE_FREE(ss->vertex_info.boundary); - MEM_SAFE_FREE(ss->vertex_info.connected_component); - - PBVHTopologyUpdateMode mode = 0; - float location[3]; - - if (!(sd->flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) { - if (sd->flags & SCULPT_DYNTOPO_SUBDIVIDE) { - mode |= PBVH_Subdivide; - } - - if ((sd->flags & SCULPT_DYNTOPO_COLLAPSE) || (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY)) { - mode |= PBVH_Collapse; - } - } - - for (n = 0; n < totnode; n++) { - SCULPT_undo_push_node(ob, - nodes[n], - brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK : - SCULPT_UNDO_COORDS); - BKE_pbvh_node_mark_update(nodes[n]); - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_node_mark_topology_update(nodes[n]); - BKE_pbvh_bmesh_node_save_orig(ss->bm, ss->bm_log, nodes[n], false); - } - } - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_update_topology(ss->pbvh, - mode, - ss->cache->location, - ss->cache->view_normal, - ss->cache->radius, - (brush->flag & BRUSH_FRONTFACE) != 0, - (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); - } - - MEM_SAFE_FREE(nodes); - - /* Update average stroke position. */ - copy_v3_v3(location, ss->cache->true_location); - mul_m4_v3(ob->object_to_world, location); -} - -static void do_brush_action_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - - bool need_coords = ss->cache->supports_gravity; - - /* Face Sets modifications do a single undo push */ - if (data->brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) { - BKE_pbvh_node_mark_redraw(data->nodes[n]); - /* Draw face sets in smooth mode moves the vertices. */ - if (ss->cache->alt_smooth) { - need_coords = true; - } - } - else if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); - BKE_pbvh_node_mark_update_mask(data->nodes[n]); - } - else if (SCULPT_tool_is_paint(data->brush->sculpt_tool)) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); - BKE_pbvh_node_mark_update_color(data->nodes[n]); - } - else { - need_coords = true; - } - - if (need_coords) { - SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); - BKE_pbvh_node_mark_update(data->nodes[n]); - } -} - -static void do_brush_action(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings) -{ - SculptSession *ss = ob->sculpt; - int totnode; - PBVHNode **nodes; - - /* Check for unsupported features. */ - PBVHType type = BKE_pbvh_type(ss->pbvh); - - if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { - if (type != PBVH_FACES) { - return; - } - - BKE_pbvh_ensure_node_loops(ss->pbvh); - } - - /* Build a list of all nodes that are potentially within the brush's area of influence */ - - if (SCULPT_tool_needs_all_pbvh_nodes(brush)) { - /* These brushes need to update all nodes as they are not constrained by the brush radius */ - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - } - else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { - nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode); - } - else { - const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : - ss->cache->original; - float radius_scale = 1.0f; - - /* Corners of square brushes can go outside the brush radius. */ - if (sculpt_tool_has_cube_tip(brush->sculpt_tool)) { - radius_scale = M_SQRT2; - } - - /* With these options enabled not all required nodes are inside the original brush radius, so - * the brush can produce artifacts in some situations. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) { - radius_scale = 2.0f; - } - nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); - } - const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob); - if (use_pixels) { - sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); - } - - /* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the - * vertices and uses regular coords undo. */ - /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type - * and the number of nodes under the brush influence. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && - SCULPT_stroke_is_first_brush_step(ss->cache) && !ss->cache->alt_smooth) { - - /* Dynamic-topology does not support Face Sets data, so it can't store/restore it from undo. */ - /* TODO(pablodp606): This check should be done in the undo code and not here, but the rest of - * the sculpt code is not checking for unsupported undo types that may return a null node. */ - if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { - SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_FACE_SETS); - } - - if (ss->cache->invert) { - /* When inverting the brush, pick the paint face mask ID from the mesh. */ - ss->cache->paint_face_set = SCULPT_active_face_set_get(ss); - } - else { - /* By default create a new Face Sets. */ - ss->cache->paint_face_set = SCULPT_face_set_next_available_get(ss); - } - } - - /* For anchored brushes with spherical falloff, we start off with zero radius, thus we have no - * PBVH nodes on the first brush step. */ - if (totnode || - ((brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) && (brush->flag & BRUSH_ANCHORED))) { - if (SCULPT_stroke_is_first_brush_step(ss->cache)) { - /* Initialize auto-masking cache. */ - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); - ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash( - ob, ss->cache->automasking); - } - /* Initialize surface smooth cache. */ - if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && - (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE)) { - BLI_assert(ss->cache->surface_smooth_laplacian_disp == NULL); - ss->cache->surface_smooth_laplacian_disp = MEM_callocN( - sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b"); - } - } - } - - /* Only act if some verts are inside the brush area. */ - if (totnode == 0) { - return; - } - float location[3]; - - if (!use_pixels) { - SculptThreadedTaskData task_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, &task_data, do_brush_action_task_cb, &settings); - } - - if (sculpt_brush_needs_normal(ss, sd, brush)) { - update_sculpt_normal(sd, ob, nodes, totnode); - } - - if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA) { - update_brush_local_mat(sd, ob); - } - - if (brush->sculpt_tool == SCULPT_TOOL_POSE && SCULPT_stroke_is_first_brush_step(ss->cache)) { - 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( - ob, 1.0f, 0.0f, 0.0f, false, true); - SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); - } - SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); - SCULPT_cloth_brush_ensure_nodes_constraints( - sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX); - } - - bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN; - - /* Apply one type of brush action. */ - switch (brush->sculpt_tool) { - case SCULPT_TOOL_DRAW: - SCULPT_do_draw_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SMOOTH: - if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { - SCULPT_do_smooth_brush(sd, ob, nodes, totnode); - } - else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { - SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode); - } - break; - case SCULPT_TOOL_CREASE: - SCULPT_do_crease_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_BLOB: - SCULPT_do_crease_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_PINCH: - SCULPT_do_pinch_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_INFLATE: - SCULPT_do_inflate_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_GRAB: - SCULPT_do_grab_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_ROTATE: - SCULPT_do_rotate_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SNAKE_HOOK: - SCULPT_do_snake_hook_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_NUDGE: - SCULPT_do_nudge_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_THUMB: - SCULPT_do_thumb_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_LAYER: - SCULPT_do_layer_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_FLATTEN: - SCULPT_do_flatten_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLAY: - SCULPT_do_clay_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLAY_STRIPS: - SCULPT_do_clay_strips_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLAY_THUMB: - SCULPT_do_clay_thumb_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_FILL: - if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - SCULPT_do_scrape_brush(sd, ob, nodes, totnode); - } - else { - SCULPT_do_fill_brush(sd, ob, nodes, totnode); - } - break; - case SCULPT_TOOL_SCRAPE: - if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { - SCULPT_do_fill_brush(sd, ob, nodes, totnode); - } - else { - SCULPT_do_scrape_brush(sd, ob, nodes, totnode); - } - break; - case SCULPT_TOOL_MASK: - SCULPT_do_mask_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_POSE: - SCULPT_do_pose_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DRAW_SHARP: - SCULPT_do_draw_sharp_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_ELASTIC_DEFORM: - SCULPT_do_elastic_deform_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SLIDE_RELAX: - SCULPT_do_slide_relax_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_BOUNDARY: - SCULPT_do_boundary_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_CLOTH: - SCULPT_do_cloth_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DRAW_FACE_SETS: - SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - SCULPT_do_displacement_eraser_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_PAINT: - SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode); - break; - case SCULPT_TOOL_SMEAR: - SCULPT_do_smear_brush(sd, ob, nodes, totnode); - break; - } - - if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) && - brush->autosmooth_factor > 0) { - if (brush->flag & BRUSH_INVERSE_SMOOTH_PRESSURE) { - SCULPT_smooth( - sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false); - } - else { - SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false); - } - } - - if (sculpt_brush_use_topology_rake(ss, brush)) { - SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); - } - - if (!SCULPT_tool_can_reuse_automask(brush->sculpt_tool) || - (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) { - /* Clear cavity mask cache. */ - ss->last_automasking_settings_hash = 0; - } - - /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */ - if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_BOUNDARY)) { - 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_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode); - SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode); - } - } - - MEM_SAFE_FREE(nodes); - - /* Update average stroke position. */ - copy_v3_v3(location, ss->cache->true_location); - mul_m4_v3(ob->object_to_world, location); - - add_v3_v3(ups->average_stroke_accum, location); - ups->average_stroke_counter++; - /* Update last stroke position. */ - ups->last_stroke_valid = true; -} - -/* Flush displacement from deformed PBVH vertex to original mesh. */ -static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd) -{ - SculptSession *ss = ob->sculpt; - Mesh *me = ob->data; - float disp[3], newco[3]; - int index = vd->vert_indices[vd->i]; - - sub_v3_v3v3(disp, vd->co, ss->deform_cos[index]); - mul_m3_v3(ss->deform_imats[index], disp); - add_v3_v3v3(newco, disp, ss->orig_cos[index]); - - copy_v3_v3(ss->deform_cos[index], vd->co); - copy_v3_v3(ss->orig_cos[index], newco); - - MVert *verts = BKE_mesh_verts_for_write(me); - if (!ss->shapekey_active) { - copy_v3_v3(verts[index].co, newco); - } -} - -static void sculpt_combine_proxies_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Sculpt *sd = data->sd; - Object *ob = data->ob; - const bool use_orco = data->use_proxies_orco; - - PBVHVertexIter vd; - PBVHProxyNode *proxies; - int proxy_count; - float(*orco)[3] = NULL; - - if (use_orco && !ss->bm) { - orco = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS)->co; - } - - BKE_pbvh_node_get_proxies(data->nodes[n], &proxies, &proxy_count); - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - float val[3]; - - if (use_orco) { - if (ss->bm) { - copy_v3_v3(val, BM_log_original_vert_co(ss->bm_log, vd.bm_vert)); - } - else { - copy_v3_v3(val, orco[vd.i]); - } - } - else { - copy_v3_v3(val, vd.co); - } - - for (int p = 0; p < proxy_count; p++) { - add_v3_v3(val, proxies[p].co[vd.i]); - } - - SCULPT_clip(sd, ss, vd.co, val); - - if (ss->deform_modifiers_active) { - sculpt_flush_pbvhvert_deform(ob, &vd); - } - } - BKE_pbvh_vertex_iter_end; - - BKE_pbvh_node_free_proxies(data->nodes[n]); -} - -static void sculpt_combine_proxies(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - PBVHNode **nodes; - int totnode; - - if (!ss->cache->supports_gravity && sculpt_tool_is_proxy_used(brush->sculpt_tool)) { - /* First line is tools that don't support proxies. */ - return; - } - - /* First line is tools that don't support proxies. */ - const bool use_orco = ELEM(brush->sculpt_tool, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ROTATE, - SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_POSE); - - BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .use_proxies_orco = use_orco, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); - MEM_SAFE_FREE(nodes); -} - -void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes; - int totnode; - - BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .use_proxies_orco = false, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); - - MEM_SAFE_FREE(nodes); -} - -/** - * Copy the modified vertices from the #PBVH to the active key. - */ -static void sculpt_update_keyblock(Object *ob) -{ - SculptSession *ss = ob->sculpt; - float(*vertCos)[3]; - - /* Key-block update happens after handling deformation caused by modifiers, - * so ss->orig_cos would be updated with new stroke. */ - if (ss->orig_cos) { - vertCos = ss->orig_cos; - } - else { - vertCos = BKE_pbvh_vert_coords_alloc(ss->pbvh); - } - - if (!vertCos) { - return; - } - - SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); - - if (vertCos != ss->orig_cos) { - MEM_freeN(vertCos); - } -} - -static void SCULPT_flush_stroke_deform_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - Object *ob = data->ob; - float(*vertCos)[3] = data->vertCos; - - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - sculpt_flush_pbvhvert_deform(ob, &vd); - - if (!vertCos) { - continue; - } - - int index = vd.vert_indices[vd.i]; - copy_v3_v3(vertCos[index], ss->orig_cos[index]); - } - BKE_pbvh_vertex_iter_end; -} - -void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (is_proxy_used && ss->deform_modifiers_active) { - /* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed - * deformation to original base. */ - - int totnode; - Mesh *me = (Mesh *)ob->data; - PBVHNode **nodes; - float(*vertCos)[3] = NULL; - - if (ss->shapekey_active) { - vertCos = MEM_mallocN(sizeof(*vertCos) * me->totvert, "flushStrokeDeofrm keyVerts"); - - /* Mesh could have isolated verts which wouldn't be in BVH, to deal with this we copy old - * coordinates over new ones and then update coordinates for all vertices from BVH. */ - memcpy(vertCos, ss->orig_cos, sizeof(*vertCos) * me->totvert); - } - - BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); - - SculptThreadedTaskData data = { - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .vertCos = vertCos, - }; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, SCULPT_flush_stroke_deform_task_cb, &settings); - - if (vertCos) { - SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); - MEM_freeN(vertCos); - } - - MEM_SAFE_FREE(nodes); - } - else if (ss->shapekey_active) { - sculpt_update_keyblock(ob); - } -} - -void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, - const char symm, - const char axis, - const float angle) -{ - flip_v3_v3(cache->location, cache->true_location, symm); - flip_v3_v3(cache->last_location, cache->true_last_location, symm); - flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm); - flip_v3_v3(cache->view_normal, cache->true_view_normal, symm); - - flip_v3_v3(cache->initial_location, cache->true_initial_location, symm); - flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm); - - /* XXX This reduces the length of the grab delta if it approaches the line of symmetry - * XXX However, a different approach appears to be needed. */ -#if 0 - if (sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER) { - float frac = 1.0f / max_overlap_count(sd); - float reduce = (feather - frac) / (1.0f - frac); - - printf("feather: %f frac: %f reduce: %f\n", feather, frac, reduce); - - if (frac < 1.0f) { - mul_v3_fl(cache->grab_delta_symmetry, reduce); - } - } -#endif - - unit_m4(cache->symm_rot_mat); - unit_m4(cache->symm_rot_mat_inv); - zero_v3(cache->plane_offset); - - /* Expects XYZ. */ - if (axis) { - rotate_m4(cache->symm_rot_mat, axis, angle); - rotate_m4(cache->symm_rot_mat_inv, axis, -angle); - } - - mul_m4_v3(cache->symm_rot_mat, cache->location); - mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); - - if (cache->supports_gravity) { - flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); - mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); - } - - if (cache->is_rake_rotation_valid) { - flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); - } -} - -typedef void (*BrushActionFunc)(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings); - -static void do_tiled(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings, - BrushActionFunc action) -{ - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const float radius = cache->radius; - const BoundBox *bb = BKE_object_boundbox_get(ob); - const float *bbMin = bb->vec[0]; - const float *bbMax = bb->vec[6]; - const float *step = sd->paint.tile_offset; - - /* These are integer locations, for real location: multiply with step and add orgLoc. - * So 0,0,0 is at orgLoc. */ - int start[3]; - int end[3]; - int cur[3]; - - /* Position of the "prototype" stroke for tiling. */ - float orgLoc[3]; - float original_initial_location[3]; - copy_v3_v3(orgLoc, cache->location); - copy_v3_v3(original_initial_location, cache->initial_location); - - for (int dim = 0; dim < 3; dim++) { - if ((sd->paint.symmetry_flags & (PAINT_TILE_X << dim)) && step[dim] > 0) { - start[dim] = (bbMin[dim] - orgLoc[dim] - radius) / step[dim]; - end[dim] = (bbMax[dim] - orgLoc[dim] + radius) / step[dim]; - } - else { - start[dim] = end[dim] = 0; - } - } - - /* First do the "un-tiled" position to initialize the stroke for this location. */ - cache->tile_pass = 0; - action(sd, ob, brush, ups, paint_mode_settings); - - /* Now do it for all the tiles. */ - copy_v3_v3_int(cur, start); - for (cur[0] = start[0]; cur[0] <= end[0]; cur[0]++) { - for (cur[1] = start[1]; cur[1] <= end[1]; cur[1]++) { - for (cur[2] = start[2]; cur[2] <= end[2]; cur[2]++) { - if (!cur[0] && !cur[1] && !cur[2]) { - /* Skip tile at orgLoc, this was already handled before all others. */ - continue; - } - - ++cache->tile_pass; - - for (int dim = 0; dim < 3; dim++) { - cache->location[dim] = cur[dim] * step[dim] + orgLoc[dim]; - cache->plane_offset[dim] = cur[dim] * step[dim]; - cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; - } - action(sd, ob, brush, ups, paint_mode_settings); - } - } - } -} - -static void do_radial_symmetry(Sculpt *sd, - Object *ob, - Brush *brush, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings, - BrushActionFunc action, - const char symm, - const int axis, - const float UNUSED(feather)) -{ - SculptSession *ss = ob->sculpt; - - for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { - const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; - ss->cache->radial_symmetry_pass = i; - SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); - do_tiled(sd, ob, brush, ups, paint_mode_settings, action); - } -} - -/** - * Noise texture gives different values for the same input coord; this - * can tear a multi-resolution mesh during sculpting so do a stitch in this case. - */ -static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - if (ss->multires.active && mtex->tex && mtex->tex->type == TEX_NOISE) { - multires_stitch_grids(ob); - } -} - -static void do_symmetrical_brush_actions(Sculpt *sd, - Object *ob, - BrushActionFunc action, - UnifiedPaintSettings *ups, - PaintModeSettings *paint_mode_settings) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const char symm = SCULPT_mesh_symmetry_xyz_get(ob); - - float feather = calc_symmetry_feather(sd, ss->cache); - - cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings); - cache->symmetry = symm; - - /* `symm` is a bit combination of XYZ - - * 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ - for (int i = 0; i <= symm; i++) { - if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { - continue; - } - cache->mirror_symmetry_pass = i; - cache->radial_symmetry_pass = 0; - - SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); - do_tiled(sd, ob, brush, ups, paint_mode_settings, action); - - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'X', feather); - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Y', feather); - do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, i, 'Z', feather); - } -} - -bool SCULPT_mode_poll(bContext *C) -{ - Object *ob = CTX_data_active_object(C); - return ob && ob->mode & OB_MODE_SCULPT; -} - -bool SCULPT_mode_poll_view3d(bContext *C) -{ - return (SCULPT_mode_poll(C) && CTX_wm_region_view3d(C)); -} - -bool SCULPT_poll_view3d(bContext *C) -{ - return (SCULPT_poll(C) && CTX_wm_region_view3d(C)); -} - -bool SCULPT_poll(bContext *C) -{ - return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); -} - -static const char *sculpt_tool_name(Sculpt *sd) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - - switch ((eBrushSculptTool)brush->sculpt_tool) { - case SCULPT_TOOL_DRAW: - return "Draw Brush"; - case SCULPT_TOOL_SMOOTH: - return "Smooth Brush"; - case SCULPT_TOOL_CREASE: - return "Crease Brush"; - case SCULPT_TOOL_BLOB: - return "Blob Brush"; - case SCULPT_TOOL_PINCH: - return "Pinch Brush"; - case SCULPT_TOOL_INFLATE: - return "Inflate Brush"; - case SCULPT_TOOL_GRAB: - return "Grab Brush"; - case SCULPT_TOOL_NUDGE: - return "Nudge Brush"; - case SCULPT_TOOL_THUMB: - return "Thumb Brush"; - case SCULPT_TOOL_LAYER: - return "Layer Brush"; - case SCULPT_TOOL_FLATTEN: - return "Flatten Brush"; - case SCULPT_TOOL_CLAY: - return "Clay Brush"; - case SCULPT_TOOL_CLAY_STRIPS: - return "Clay Strips Brush"; - case SCULPT_TOOL_CLAY_THUMB: - return "Clay Thumb Brush"; - case SCULPT_TOOL_FILL: - return "Fill Brush"; - case SCULPT_TOOL_SCRAPE: - return "Scrape Brush"; - case SCULPT_TOOL_SNAKE_HOOK: - return "Snake Hook Brush"; - case SCULPT_TOOL_ROTATE: - return "Rotate Brush"; - case SCULPT_TOOL_MASK: - return "Mask Brush"; - case SCULPT_TOOL_SIMPLIFY: - return "Simplify Brush"; - case SCULPT_TOOL_DRAW_SHARP: - return "Draw Sharp Brush"; - case SCULPT_TOOL_ELASTIC_DEFORM: - return "Elastic Deform Brush"; - case SCULPT_TOOL_POSE: - return "Pose Brush"; - case SCULPT_TOOL_MULTIPLANE_SCRAPE: - return "Multi-plane Scrape Brush"; - case SCULPT_TOOL_SLIDE_RELAX: - return "Slide/Relax Brush"; - case SCULPT_TOOL_BOUNDARY: - return "Boundary Brush"; - case SCULPT_TOOL_CLOTH: - return "Cloth Brush"; - case SCULPT_TOOL_DRAW_FACE_SETS: - return "Draw Face Sets"; - case SCULPT_TOOL_DISPLACEMENT_ERASER: - return "Multires Displacement Eraser"; - case SCULPT_TOOL_DISPLACEMENT_SMEAR: - return "Multires Displacement Smear"; - case SCULPT_TOOL_PAINT: - return "Paint Brush"; - case SCULPT_TOOL_SMEAR: - return "Smear Brush"; - } - - return "Sculpting"; -} - -/* Operator for applying a stroke (various attributes including mouse path) - * using the current brush. */ - -void SCULPT_cache_free(StrokeCache *cache) -{ - MEM_SAFE_FREE(cache->dial); - 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); - MEM_SAFE_FREE(cache->prev_displacement); - MEM_SAFE_FREE(cache->limit_surface_co); - MEM_SAFE_FREE(cache->prev_colors_vpaint); - - 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->boundaries[i]) { - SCULPT_boundary_data_free(cache->boundaries[i]); - } - } - - if (cache->cloth_sim) { - SCULPT_cloth_simulation_free(cache->cloth_sim); - } - - MEM_freeN(cache); -} - -/* Initialize mirror modifier clipping. */ -static void sculpt_init_mirror_clipping(Object *ob, SculptSession *ss) -{ - ModifierData *md; - - unit_m4(ss->cache->clip_mirror_mtx); - - for (md = ob->modifiers.first; md; md = md->next) { - if (!(md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime))) { - continue; - } - MirrorModifierData *mmd = (MirrorModifierData *)md; - - if (!(mmd->flag & MOD_MIR_CLIPPING)) { - continue; - } - /* Check each axis for mirroring. */ - for (int i = 0; i < 3; i++) { - if (!(mmd->flag & (MOD_MIR_AXIS_X << i))) { - continue; - } - /* Enable sculpt clipping. */ - ss->cache->flag |= CLIP_X << i; - - /* Update the clip tolerance. */ - if (mmd->tolerance > ss->cache->clip_tolerance[i]) { - ss->cache->clip_tolerance[i] = mmd->tolerance; - } - - /* Store matrix for mirror object clipping. */ - if (mmd->mirror_ob) { - float imtx_mirror_ob[4][4]; - invert_m4_m4(imtx_mirror_ob, mmd->mirror_ob->object_to_world); - mul_m4_m4m4(ss->cache->clip_mirror_mtx, imtx_mirror_ob, ob->object_to_world); - } - } - } -} - -static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) -{ - Scene *scene = CTX_data_scene(C); - Brush *brush = paint->brush; - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - cache->saved_mask_brush_tool = brush->mask_tool; - brush->mask_tool = BRUSH_MASK_SMOOTH; - } - else if (ELEM(brush->sculpt_tool, - SCULPT_TOOL_SLIDE_RELAX, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR)) { - /* Do nothing, this tool has its own smooth mode. */ - } - else { - int cur_brush_size = BKE_brush_size_get(scene, brush); - - BLI_strncpy(cache->saved_active_brush_name, - brush->id.name + 2, - sizeof(cache->saved_active_brush_name)); - - /* Switch to the smooth brush. */ - brush = BKE_paint_toolslots_brush_get(paint, SCULPT_TOOL_SMOOTH); - if (brush) { - BKE_paint_brush_set(paint, brush); - cache->saved_smooth_size = BKE_brush_size_get(scene, brush); - BKE_brush_size_set(scene, brush, cur_brush_size); - BKE_curvemapping_init(brush->curve); - } - } -} - -static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) -{ - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - Brush *brush = BKE_paint_brush(paint); - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - brush->mask_tool = cache->saved_mask_brush_tool; - } - else if (ELEM(brush->sculpt_tool, - SCULPT_TOOL_SLIDE_RELAX, - SCULPT_TOOL_DRAW_FACE_SETS, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_SMEAR)) { - /* Do nothing. */ - } - else { - /* Try to switch back to the saved/previous brush. */ - BKE_brush_size_set(scene, brush, cache->saved_smooth_size); - brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, cache->saved_active_brush_name); - if (brush) { - BKE_paint_brush_set(paint, brush); - } - } -} - -/* Initialize the stroke cache invariants from operator properties. */ -static void sculpt_update_cache_invariants( - bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mval[2]) -{ - StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - UnifiedPaintSettings *ups = &tool_settings->unified_paint_settings; - Brush *brush = BKE_paint_brush(&sd->paint); - ViewContext *vc = paint_stroke_view_context(op->customdata); - Object *ob = CTX_data_active_object(C); - float mat[3][3]; - float viewDir[3] = {0.0f, 0.0f, 1.0f}; - float max_scale; - int mode; - - ss->cache = cache; - - /* Set scaling adjustment. */ - max_scale = 0.0f; - for (int i = 0; i < 3; i++) { - max_scale = max_ff(max_scale, fabsf(ob->scale[i])); - } - cache->scale[0] = max_scale / ob->scale[0]; - cache->scale[1] = max_scale / ob->scale[1]; - cache->scale[2] = max_scale / ob->scale[2]; - - cache->plane_trim_squared = brush->plane_trim * brush->plane_trim; - - cache->flag = 0; - - sculpt_init_mirror_clipping(ob, ss); - - /* Initial mouse location. */ - if (mval) { - copy_v2_v2(cache->initial_mouse, mval); - } - else { - zero_v2(cache->initial_mouse); - } - - copy_v3_v3(cache->initial_location, ss->cursor_location); - copy_v3_v3(cache->true_initial_location, ss->cursor_location); - - copy_v3_v3(cache->initial_normal, ss->cursor_normal); - copy_v3_v3(cache->true_initial_normal, ss->cursor_normal); - - mode = RNA_enum_get(op->ptr, "mode"); - cache->invert = mode == BRUSH_STROKE_INVERT; - cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH; - cache->normal_weight = brush->normal_weight; - - /* Interpret invert as following normal, for grab brushes. */ - if (SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool)) { - if (cache->invert) { - cache->invert = false; - cache->normal_weight = (cache->normal_weight == 0.0f); - } - } - - /* Not very nice, but with current events system implementation - * we can't handle brush appearance inversion hotkey separately (sergey). */ - if (cache->invert) { - ups->draw_inverted = true; - } - else { - ups->draw_inverted = false; - } - - /* Alt-Smooth. */ - if (cache->alt_smooth) { - smooth_brush_toggle_on(C, &sd->paint, cache); - /* Refresh the brush pointer in case we switched brush in the toggle function. */ - brush = BKE_paint_brush(&sd->paint); - } - - copy_v2_v2(cache->mouse, cache->initial_mouse); - copy_v2_v2(cache->mouse_event, cache->initial_mouse); - copy_v2_v2(ups->tex_mouse, cache->initial_mouse); - - /* Truly temporary data that isn't stored in properties. */ - cache->vc = vc; - cache->brush = brush; - - /* Cache projection matrix. */ - ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); - - invert_m4_m4(ob->world_to_object, ob->object_to_world); - copy_m3_m4(mat, cache->vc->rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->world_to_object); - mul_m3_v3(mat, viewDir); - normalize_v3_v3(cache->true_view_normal, viewDir); - - cache->supports_gravity = (!ELEM(brush->sculpt_tool, - SCULPT_TOOL_MASK, - SCULPT_TOOL_SMOOTH, - SCULPT_TOOL_SIMPLIFY, - SCULPT_TOOL_DISPLACEMENT_SMEAR, - SCULPT_TOOL_DISPLACEMENT_ERASER) && - (sd->gravity_factor > 0.0f)); - /* Get gravity vector in world space. */ - if (cache->supports_gravity) { - if (sd->gravity_object) { - Object *gravity_object = sd->gravity_object; - - copy_v3_v3(cache->true_gravity_direction, gravity_object->object_to_world[2]); - } - else { - cache->true_gravity_direction[0] = cache->true_gravity_direction[1] = 0.0f; - cache->true_gravity_direction[2] = 1.0f; - } - - /* Transform to sculpted object space. */ - mul_m3_v3(mat, cache->true_gravity_direction); - normalize_v3(cache->true_gravity_direction); - } - - /* Make copies of the mesh vertex locations and normals for some tools. */ - if (brush->flag & BRUSH_ANCHORED) { - cache->original = true; - } - - if (SCULPT_automasking_needs_original(sd, brush)) { - cache->original = true; - } - - /* Draw sharp does not need the original coordinates to produce the accumulate effect, so it - * should work the opposite way. */ - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { - cache->original = true; - } - - if (SCULPT_TOOL_HAS_ACCUMULATE(brush->sculpt_tool)) { - if (!(brush->flag & BRUSH_ACCUMULATE)) { - cache->original = true; - if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { - cache->original = false; - } - } - } - - /* Original coordinates require the sculpt undo system, which isn't used - * for image brushes. It's also not necessary, just disable it. */ - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - cache->original = false; - } - - cache->first_time = true; - -#define PIXEL_INPUT_THRESHHOLD 5 - if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { - cache->dial = BLI_dial_init(cache->initial_mouse, PIXEL_INPUT_THRESHHOLD); - } - -#undef PIXEL_INPUT_THRESHHOLD -} - -static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size) -{ - switch (brush->sculpt_tool) { - case SCULPT_TOOL_CLAY: - return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure)); - case SCULPT_TOOL_CLAY_STRIPS: - return max_ff(initial_size * 0.30f, initial_size * powf(cache->pressure, 1.5f)); - case SCULPT_TOOL_CLAY_THUMB: { - float clay_stabilized_pressure = SCULPT_clay_thumb_get_stabilized_pressure(cache); - return initial_size * clay_stabilized_pressure; - } - default: - return initial_size * cache->pressure; - } -} - -/* In these brushes the grab delta is calculated always from the initial stroke location, which is - * generally used to create grab deformations. */ -static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) -{ - if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && (brush->flag & BRUSH_ANCHORED)) { - return true; - } - - 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 - * to calculate to orientate the brush tip and deformation towards the stroke direction. */ -static bool sculpt_needs_delta_for_tip_orientation(Brush *brush) -{ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { - return brush->cloth_deform_type != BRUSH_CLOTH_DEFORM_GRAB; - } - return ELEM(brush->sculpt_tool, - SCULPT_TOOL_CLAY_STRIPS, - SCULPT_TOOL_PINCH, - SCULPT_TOOL_MULTIPLANE_SCRAPE, - SCULPT_TOOL_CLAY_THUMB, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_SNAKE_HOOK); -} - -static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Brush *brush) -{ - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - const float mval[2] = { - cache->mouse_event[0], - cache->mouse_event[1], - }; - int tool = brush->sculpt_tool; - - if (!ELEM(tool, - SCULPT_TOOL_PAINT, - SCULPT_TOOL_GRAB, - SCULPT_TOOL_ELASTIC_DEFORM, - SCULPT_TOOL_CLOTH, - SCULPT_TOOL_NUDGE, - SCULPT_TOOL_CLAY_STRIPS, - SCULPT_TOOL_PINCH, - SCULPT_TOOL_MULTIPLANE_SCRAPE, - SCULPT_TOOL_CLAY_THUMB, - SCULPT_TOOL_SNAKE_HOOK, - SCULPT_TOOL_POSE, - SCULPT_TOOL_BOUNDARY, - SCULPT_TOOL_SMEAR, - SCULPT_TOOL_THUMB) && - !sculpt_brush_use_topology_rake(ss, brush)) { - return; - } - float grab_location[3], imat[4][4], delta[3], loc[3]; - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { - copy_v3_v3(cache->orig_grab_location, - SCULPT_vertex_co_for_grab_active_get(ss, SCULPT_active_vertex_get(ss))); - } - else { - copy_v3_v3(cache->orig_grab_location, cache->true_location); - } - } - 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); - } - - /* Compute 3d coordinate at same z from original location + mval. */ - mul_v3_m4v3(loc, ob->object_to_world, cache->orig_grab_location); - ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mval, grab_location); - - /* Compute delta to move verts by. */ - if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (sculpt_needs_delta_from_anchored_origin(brush)) { - sub_v3_v3v3(delta, grab_location, cache->old_grab_location); - invert_m4_m4(imat, ob->object_to_world); - mul_mat3_m4_v3(imat, delta); - add_v3_v3(cache->grab_delta, delta); - } - else if (sculpt_needs_delta_for_tip_orientation(brush)) { - if (brush->flag & BRUSH_ANCHORED) { - float orig[3]; - mul_v3_m4v3(orig, ob->object_to_world, cache->orig_grab_location); - sub_v3_v3v3(cache->grab_delta, grab_location, orig); - } - else { - sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); - } - invert_m4_m4(imat, ob->object_to_world); - mul_mat3_m4_v3(imat, cache->grab_delta); - } - else { - /* Use for 'Brush.topology_rake_factor'. */ - sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); - } - } - else { - zero_v3(cache->grab_delta); - } - - if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { - project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss->cache->true_view_normal); - } - - copy_v3_v3(cache->old_grab_location, grab_location); - - if (tool == SCULPT_TOOL_GRAB) { - if (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { - copy_v3_v3(cache->anchored_location, cache->orig_grab_location); - } - else { - copy_v3_v3(cache->anchored_location, cache->true_location); - } - } - else if (tool == SCULPT_TOOL_ELASTIC_DEFORM || SCULPT_is_cloth_deform_brush(brush)) { - copy_v3_v3(cache->anchored_location, cache->true_location); - } - else if (tool == SCULPT_TOOL_THUMB) { - copy_v3_v3(cache->anchored_location, cache->orig_grab_location); - } - - if (sculpt_needs_delta_from_anchored_origin(brush)) { - /* Location stays the same for finding vertices in brush radius. */ - copy_v3_v3(cache->true_location, cache->orig_grab_location); - - ups->draw_anchored = true; - copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); - ups->anchored_size = ups->pixel_radius; - } - - /* Handle 'rake' */ - cache->is_rake_rotation_valid = false; - - invert_m4_m4(imat, ob->object_to_world); - mul_mat3_m4_v3(imat, grab_location); - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - copy_v3_v3(cache->rake_data.follow_co, grab_location); - } - - if (!sculpt_brush_needs_rake_rotation(brush)) { - return; - } - cache->rake_data.follow_dist = cache->radius * SCULPT_RAKE_BRUSH_FACTOR; - - if (!is_zero_v3(cache->grab_delta)) { - const float eps = 0.00001f; - - float v1[3], v2[3]; - - copy_v3_v3(v1, cache->rake_data.follow_co); - copy_v3_v3(v2, cache->rake_data.follow_co); - sub_v3_v3(v2, cache->grab_delta); - - sub_v3_v3(v1, grab_location); - sub_v3_v3(v2, grab_location); - - if ((normalize_v3(v2) > eps) && (normalize_v3(v1) > eps) && (len_squared_v3v3(v1, v2) > eps)) { - const float rake_dist_sq = len_squared_v3v3(cache->rake_data.follow_co, grab_location); - const float rake_fade = (rake_dist_sq > square_f(cache->rake_data.follow_dist)) ? - 1.0f : - sqrtf(rake_dist_sq) / cache->rake_data.follow_dist; - - float axis[3], angle; - float tquat[4]; - - rotation_between_vecs_to_quat(tquat, v1, v2); - - /* Use axis-angle to scale rotation since the factor may be above 1. */ - quat_to_axis_angle(axis, &angle, tquat); - normalize_v3(axis); - - angle *= brush->rake_factor * rake_fade; - axis_angle_normalized_to_quat(cache->rake_rotation, axis, angle); - cache->is_rake_rotation_valid = true; - } - } - sculpt_rake_data_update(&cache->rake_data, grab_location); -} - -static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush) -{ - cache->paint_brush.hardness = brush->hardness; - if (brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) { - cache->paint_brush.hardness *= brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.flow = brush->flow; - if (brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE) { - cache->paint_brush.flow *= brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.wet_mix = brush->wet_mix; - if (brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE) { - cache->paint_brush.wet_mix *= brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - - /* This makes wet mix more sensible in higher values, which allows to create brushes that have - * a wider pressure range were they only blend colors without applying too much of the brush - * color. */ - cache->paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache->paint_brush.wet_mix); - } - - cache->paint_brush.wet_persistence = brush->wet_persistence; - if (brush->paint_flags & BRUSH_PAINT_WET_PERSISTENCE_PRESSURE) { - cache->paint_brush.wet_persistence = brush->paint_flags & - BRUSH_PAINT_WET_PERSISTENCE_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } - - cache->paint_brush.density = brush->density; - if (brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE) { - cache->paint_brush.density = brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE_INVERT ? - 1.0f - cache->pressure : - cache->pressure; - } -} - -/* Initialize the stroke cache variants from operator properties. */ -static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr) -{ - Scene *scene = CTX_data_scene(C); - UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; - SculptSession *ss = ob->sculpt; - StrokeCache *cache = ss->cache; - Brush *brush = BKE_paint_brush(&sd->paint); - - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || - !((brush->flag & BRUSH_ANCHORED) || (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK) || - (brush->sculpt_tool == SCULPT_TOOL_ROTATE) || SCULPT_is_cloth_deform_brush(brush))) { - RNA_float_get_array(ptr, "location", cache->true_location); - } - - cache->pen_flip = RNA_boolean_get(ptr, "pen_flip"); - RNA_float_get_array(ptr, "mouse", cache->mouse); - RNA_float_get_array(ptr, "mouse_event", cache->mouse_event); - - /* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab, - * thumb). They depends on initial state and brush coord/pressure/etc. - * It's more an events design issue, which doesn't split coordinate/pressure/angle changing - * events. We should avoid this after events system re-design. */ - if (paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT) || cache->first_time) { - cache->pressure = RNA_float_get(ptr, "pressure"); - } - - cache->x_tilt = RNA_float_get(ptr, "x_tilt"); - cache->y_tilt = RNA_float_get(ptr, "y_tilt"); - - /* Truly temporary data that isn't stored in properties. */ - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - if (!BKE_brush_use_locked_size(scene, brush)) { - cache->initial_radius = paint_calc_object_space_radius( - cache->vc, cache->true_location, BKE_brush_size_get(scene, brush)); - BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius); - } - else { - cache->initial_radius = BKE_brush_unprojected_radius_get(scene, brush); - } - } - - /* Clay stabilized pressure. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLAY_THUMB) { - if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { - for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { - ss->cache->clay_pressure_stabilizer[i] = 0.0f; - } - ss->cache->clay_pressure_stabilizer_index = 0; - } - else { - cache->clay_pressure_stabilizer[cache->clay_pressure_stabilizer_index] = cache->pressure; - cache->clay_pressure_stabilizer_index += 1; - if (cache->clay_pressure_stabilizer_index >= SCULPT_CLAY_STABILIZER_LEN) { - cache->clay_pressure_stabilizer_index = 0; - } - } - } - - if (BKE_brush_use_size_pressure(brush) && - paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) { - cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); - cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( - brush, cache, ups->initial_pixel_radius); - } - else { - cache->radius = cache->initial_radius; - cache->dyntopo_pixel_radius = ups->initial_pixel_radius; - } - - sculpt_update_cache_paint_variants(cache, brush); - - cache->radius_squared = cache->radius * cache->radius; - - if (brush->flag & BRUSH_ANCHORED) { - /* True location has been calculated as part of the stroke system already here. */ - if (brush->flag & BRUSH_EDGE_TO_EDGE) { - RNA_float_get_array(ptr, "location", cache->true_location); - } - - cache->radius = paint_calc_object_space_radius( - cache->vc, cache->true_location, ups->pixel_radius); - cache->radius_squared = cache->radius * cache->radius; - - copy_v3_v3(cache->anchored_location, cache->true_location); - } - - sculpt_update_brush_delta(ups, ob, brush); - - if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { - cache->vertex_rotation = -BLI_dial_angle(cache->dial, cache->mouse) * cache->bstrength; - - ups->draw_anchored = true; - copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); - copy_v3_v3(cache->anchored_location, cache->true_location); - ups->anchored_size = ups->pixel_radius; - } - - cache->special_rotation = ups->brush_rotation; - - cache->iteration_count++; -} - -/* Returns true if any of the smoothing modes are active (currently - * one of smooth brush, autosmooth, mask smooth, or shift-key - * smooth). */ -static bool sculpt_needs_connectivity_info(const Sculpt *sd, - const Brush *brush, - SculptSession *ss, - int stroke_mode) -{ - if (ss && ss->pbvh && SCULPT_is_automasking_enabled(sd, ss, brush)) { - return true; - } - return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) || - (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) || - ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) || - (brush->sculpt_tool == SCULPT_TOOL_POSE) || - (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) || - (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) || - SCULPT_tool_is_paint(brush->sculpt_tool) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || - (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || - (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || - (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) || - (brush->sculpt_tool == SCULPT_TOOL_PAINT)); -} - -void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) -{ - SculptSession *ss = ob->sculpt; - RegionView3D *rv3d = CTX_wm_region_view3d(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - - bool need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, 0); - if (ss->shapekey_active || ss->deform_modifiers_active || - (!BKE_sculptsession_use_pbvh_draw(ob, rv3d) && need_pmap)) { - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - BKE_sculpt_update_object_for_edit( - depsgraph, ob, need_pmap, false, SCULPT_tool_is_paint(brush->sculpt_tool)); - } -} - -static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) -{ - if (BKE_pbvh_node_get_tmin(node) >= *tmin) { - return; - } - SculptRaycastData *srd = data_v; - float(*origco)[3] = NULL; - bool use_origco = false; - - if (srd->original && srd->ss->cache) { - if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { - use_origco = true; - } - else { - /* Intersect with coordinates from before we started stroke. */ - SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); - origco = (unode) ? unode->co : NULL; - use_origco = origco ? true : false; - } - } - - if (BKE_pbvh_node_raycast(srd->ss->pbvh, - node, - origco, - use_origco, - srd->ray_start, - srd->ray_normal, - &srd->isect_precalc, - &srd->depth, - &srd->active_vertex, - &srd->active_face_grid_index, - srd->face_normal)) { - srd->hit = true; - *tmin = srd->depth; - } -} - -static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) -{ - if (BKE_pbvh_node_get_tmin(node) >= *tmin) { - return; - } - SculptFindNearestToRayData *srd = data_v; - float(*origco)[3] = NULL; - bool use_origco = false; - - if (srd->original && srd->ss->cache) { - if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { - use_origco = true; - } - else { - /* Intersect with coordinates from before we started stroke. */ - SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); - origco = (unode) ? unode->co : NULL; - use_origco = origco ? true : false; - } - } - - if (BKE_pbvh_node_find_nearest_to_ray(srd->ss->pbvh, - node, - origco, - use_origco, - srd->ray_start, - srd->ray_normal, - &srd->depth, - &srd->dist_sq_to_ray)) { - srd->hit = true; - *tmin = srd->dist_sq_to_ray; - } -} - -float SCULPT_raycast_init(ViewContext *vc, - const float mval[2], - float ray_start[3], - float ray_end[3], - float ray_normal[3], - bool original) -{ - float obimat[4][4]; - float dist; - Object *ob = vc->obact; - RegionView3D *rv3d = vc->region->regiondata; - View3D *v3d = vc->v3d; - - /* TODO: what if the segment is totally clipped? (return == 0). */ - ED_view3d_win_to_segment_clipped( - vc->depsgraph, vc->region, vc->v3d, mval, ray_start, ray_end, true); - - invert_m4_m4(obimat, ob->object_to_world); - mul_m4_v3(obimat, ray_start); - mul_m4_v3(obimat, ray_end); - - sub_v3_v3v3(ray_normal, ray_end, ray_start); - dist = normalize_v3(ray_normal); - - if ((rv3d->is_persp == false) && - /* If the ray is clipped, don't adjust its start/end. */ - !RV3D_CLIPPING_ENABLED(v3d, rv3d)) { - BKE_pbvh_raycast_project_ray_root(ob->sculpt->pbvh, original, ray_start, ray_end, ray_normal); - - /* rRecalculate the normal. */ - sub_v3_v3v3(ray_normal, ray_end, ray_start); - dist = normalize_v3(ray_normal); - } - - return dist; -} - -bool SCULPT_cursor_geometry_info_update(bContext *C, - SculptCursorGeometryInfo *out, - const float mval[2], - bool use_sampled_normal) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Scene *scene = CTX_data_scene(C); - Sculpt *sd = scene->toolsettings->sculpt; - Object *ob; - SculptSession *ss; - ViewContext vc; - const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); - float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3], sampled_normal[3], - mat[3][3]; - float viewDir[3] = {0.0f, 0.0f, 1.0f}; - int totnode; - bool original = false; - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - ob = vc.obact; - ss = ob->sculpt; - - if (!ss->pbvh) { - zero_v3(out->location); - zero_v3(out->normal); - zero_v3(out->active_vertex_co); - return false; - } - - /* PBVH raycast to get active vertex and face normal. */ - depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); - SCULPT_stroke_modifiers_check(C, ob, brush); - - SculptRaycastData srd = { - .original = original, - .ss = ob->sculpt, - .hit = false, - .ray_start = ray_start, - .ray_normal = ray_normal, - .depth = depth, - .face_normal = face_normal, - }; - isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - - /* Cursor is not over the mesh, return default values. */ - if (!srd.hit) { - zero_v3(out->location); - zero_v3(out->normal); - zero_v3(out->active_vertex_co); - return false; - } - - /* Update the active vertex of the SculptSession. */ - ss->active_vertex = srd.active_vertex; - 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)) { - case PBVH_FACES: - ss->active_face_index = srd.active_face_grid_index; - ss->active_grid_index = 0; - break; - case PBVH_GRIDS: - ss->active_face_index = 0; - ss->active_grid_index = srd.active_face_grid_index; - break; - case PBVH_BMESH: - ss->active_face_index = 0; - ss->active_grid_index = 0; - break; - } - - copy_v3_v3(out->location, ray_normal); - mul_v3_fl(out->location, srd.depth); - add_v3_v3(out->location, ray_start); - - /* Option to return the face normal directly for performance o accuracy reasons. */ - if (!use_sampled_normal) { - copy_v3_v3(out->normal, srd.face_normal); - return srd.hit; - } - - /* Sampled normal calculation. */ - float radius; - - /* Update cursor data in SculptSession. */ - invert_m4_m4(ob->world_to_object, ob->object_to_world); - copy_m3_m4(mat, vc.rv3d->viewinv); - mul_m3_v3(mat, viewDir); - copy_m3_m4(mat, ob->world_to_object); - mul_m3_v3(mat, viewDir); - normalize_v3_v3(ss->cursor_view_normal, viewDir); - copy_v3_v3(ss->cursor_normal, srd.face_normal); - copy_v3_v3(ss->cursor_location, out->location); - ss->rv3d = vc.rv3d; - ss->v3d = vc.v3d; - - if (!BKE_brush_use_locked_size(scene, brush)) { - radius = paint_calc_object_space_radius(&vc, out->location, BKE_brush_size_get(scene, brush)); - } - else { - radius = BKE_brush_unprojected_radius_get(scene, brush); - } - ss->cursor_radius = radius; - - PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); - - /* In case there are no nodes under the cursor, return the face normal. */ - if (!totnode) { - MEM_SAFE_FREE(nodes); - copy_v3_v3(out->normal, srd.face_normal); - return true; - } - - /* Calculate the sampled normal. */ - if (SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) { - copy_v3_v3(out->normal, sampled_normal); - copy_v3_v3(ss->cursor_sampled_normal, sampled_normal); - } - else { - /* Use face normal when there are no vertices to sample inside the cursor radius. */ - copy_v3_v3(out->normal, srd.face_normal); - } - MEM_SAFE_FREE(nodes); - return true; -} - -bool SCULPT_stroke_get_location(bContext *C, - float out[3], - const float mval[2], - bool force_original) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob; - SculptSession *ss; - StrokeCache *cache; - float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3]; - bool original; - ViewContext vc; - - ED_view3d_viewcontext_init(C, &vc, depsgraph); - - ob = vc.obact; - - ss = ob->sculpt; - cache = ss->cache; - original = force_original || ((cache) ? cache->original : false); - - const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); - - SCULPT_stroke_modifiers_check(C, ob, brush); - - depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); - - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BM_mesh_elem_table_ensure(ss->bm, BM_VERT); - BM_mesh_elem_index_ensure(ss->bm, BM_VERT); - } - - bool hit = false; - { - SculptRaycastData srd; - srd.ss = ob->sculpt; - srd.ray_start = ray_start; - srd.ray_normal = ray_normal; - srd.hit = false; - srd.depth = depth; - srd.original = original; - srd.face_normal = face_normal; - isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); - - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - if (srd.hit) { - hit = true; - copy_v3_v3(out, ray_normal); - mul_v3_fl(out, srd.depth); - add_v3_v3(out, ray_start); - } - } - - if (hit) { - return hit; - } - - if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { - return hit; - } - - SculptFindNearestToRayData srd = { - .original = original, - .ss = ob->sculpt, - .hit = false, - .ray_start = ray_start, - .ray_normal = ray_normal, - .depth = FLT_MAX, - .dist_sq_to_ray = FLT_MAX, - }; - BKE_pbvh_find_nearest_to_ray( - ss->pbvh, sculpt_find_nearest_to_ray_cb, &srd, ray_start, ray_normal, srd.original); - if (srd.hit) { - hit = true; - copy_v3_v3(out, ray_normal); - mul_v3_fl(out, srd.depth); - add_v3_v3(out, ray_start); - } - - return hit; -} - -static void sculpt_brush_init_tex(Sculpt *sd, SculptSession *ss) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - /* Init mtex nodes. */ - if (mtex->tex && mtex->tex->nodetree) { - /* Has internal flag to detect it only does it once. */ - ntreeTexBeginExecTree(mtex->tex->nodetree); - } - - if (ss->tex_pool == NULL) { - ss->tex_pool = BKE_image_pool_new(); - } -} - -static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - Sculpt *sd = tool_settings->sculpt; - SculptSession *ss = CTX_data_active_object(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - int mode = RNA_enum_get(op->ptr, "mode"); - bool need_pmap, needs_colors; - bool need_mask = false; - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - need_mask = true; - } - - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || - brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { - need_mask = true; - } - - view3d_operator_needs_opengl(C); - sculpt_brush_init_tex(sd, ss); - - need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); - needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob); - - if (needs_colors) { - BKE_sculpt_color_layer_create_if_needed(ob); - } - - /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of - * earlier steps modifying the data. */ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - BKE_sculpt_update_object_for_edit( - depsgraph, ob, need_pmap, need_mask, SCULPT_tool_is_paint(brush->sculpt_tool)); - - ED_paint_tool_update_sticky_shading_color(C, ob); -} - -static void sculpt_restore_mesh(Sculpt *sd, Object *ob) -{ - SculptSession *ss = ob->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - /* For the cloth brush it makes more sense to not restore the mesh state to keep running the - * simulation from the previous state. */ - if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { - return; - } - - /* Restore the mesh before continuing with anchored stroke. */ - if ((brush->flag & BRUSH_ANCHORED) || - (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM) && - BKE_brush_use_size_pressure(brush)) || - (brush->flag & BRUSH_DRAG_DOT)) { - - SculptUndoNode *unode = SCULPT_undo_get_first_node(); - if (unode && unode->type == SCULPT_UNDO_FACE_SETS) { - for (int i = 0; i < ss->totfaces; i++) { - ss->face_sets[i] = unode->face_sets[i]; - } - } - - paint_mesh_restore_co(sd, ob); - - if (ss->cache) { - MEM_SAFE_FREE(ss->cache->layer_displacement_factor); - } - } -} - -void SCULPT_update_object_bounding_box(Object *ob) -{ - if (ob->runtime.bb) { - float bb_min[3], bb_max[3]; - - BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); - BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); - } -} - -void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) -{ - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - ARegion *region = CTX_wm_region(C); - MultiresModifierData *mmd = ss->multires.modifier; - RegionView3D *rv3d = CTX_wm_region_view3d(C); - - if (rv3d) { - /* Mark for faster 3D viewport redraws. */ - rv3d->rflag |= RV3D_PAINTING; - } - - if (mmd != NULL) { - multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); - } - - if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { - ED_region_tag_redraw(region); - if (update_flags == SCULPT_UPDATE_IMAGE) { - /* Early exit when only need to update the images. We don't want to tag any geometry updates - * that would rebuilt the PBVH. */ - return; - } - } - - DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); - - /* Only current viewport matters, slower update for all viewports will - * be done in sculpt_flush_update_done. */ - if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { - /* Slow update with full dependency graph update and all that comes with it. - * Needed when there are modifiers or full shading in the 3D viewport. */ - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - ED_region_tag_redraw(region); - } - else { - /* Fast path where we just update the BVH nodes that changed, and redraw - * only the part of the 3D viewport where changes happened. */ - rcti r; - - if (update_flags & SCULPT_UPDATE_COORDS) { - BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB); - /* Update the object's bounding box too so that the object - * doesn't get incorrectly clipped during drawing in - * draw_mesh_object(). T33790. */ - SCULPT_update_object_bounding_box(ob); - } - - if (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { - if (ss->cache) { - ss->cache->current_r = r; - } - - /* previous is not set in the current cache else - * the partial rect will always grow */ - sculpt_extend_redraw_rect_previous(ob, &r); - - r.xmin += region->winrct.xmin - 2; - r.xmax += region->winrct.xmin + 2; - r.ymin += region->winrct.ymin - 2; - r.ymax += region->winrct.ymin + 2; - ED_region_tag_redraw_partial(region, &r, true); - } - } -} - -void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags) -{ - /* After we are done drawing the stroke, check if we need to do a more - * expensive depsgraph tag to update geometry. */ - wmWindowManager *wm = CTX_wm_manager(C); - RegionView3D *current_rv3d = CTX_wm_region_view3d(C); - SculptSession *ss = ob->sculpt; - Mesh *mesh = ob->data; - - /* Always needed for linked duplicates. */ - bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); - - if (current_rv3d) { - current_rv3d->rflag &= ~RV3D_PAINTING; - } - - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - bScreen *screen = WM_window_get_active_screen(win); - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - SpaceLink *sl = area->spacedata.first; - if (sl->spacetype != SPACE_VIEW3D) { - continue; - } - - /* Tag all 3D viewports for redraw now that we are done. Others - * viewports did not get a full redraw, and anti-aliasing for the - * current viewport was deactivated. */ - LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - if (region->regiontype == RGN_TYPE_WINDOW) { - RegionView3D *rv3d = region->regiondata; - if (rv3d != current_rv3d) { - need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, rv3d); - } - - ED_region_tag_redraw(region); - } - } - } - - if (update_flags & SCULPT_UPDATE_IMAGE) { - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - SpaceLink *sl = area->spacedata.first; - if (sl->spacetype != SPACE_IMAGE) { - continue; - } - ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); - } - } - } - - if (update_flags & SCULPT_UPDATE_COORDS) { - BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); - - /* Coordinates were modified, so fake neighbors are not longer valid. */ - SCULPT_fake_neighbors_free(ob); - } - - if (update_flags & SCULPT_UPDATE_MASK) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); - } - - if (update_flags & SCULPT_UPDATE_COLOR) { - BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); - } - - BKE_sculpt_attributes_destroy_temporary_stroke(ob); - - if (update_flags & SCULPT_UPDATE_COORDS) { - if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { - BKE_pbvh_bmesh_after_stroke(ss->pbvh); - } - - /* Optimization: if there is locked key and active modifiers present in */ - /* the stack, keyblock is updating at each step. otherwise we could update */ - /* keyblock only when stroke is finished. */ - if (ss->shapekey_active && !ss->deform_modifiers_active) { - sculpt_update_keyblock(ob); - } - } - - if (need_tag) { - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - } -} - -/* Returns whether the mouse/stylus is over the mesh (1) - * or over the background (0). */ -static bool over_mesh(bContext *C, struct wmOperator *UNUSED(op), const float mval[2]) -{ - float co_dummy[3]; - return SCULPT_stroke_get_location(C, co_dummy, mval, false); -} - -static void sculpt_stroke_undo_begin(const bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* Setup the correct undo system. Image painting and sculpting are mutual exclusive. - * Color attributes are part of the sculpting undo system. */ - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - ED_image_undo_push_begin(op->type->name, PAINT_MODE_SCULPT); - } - else { - SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); - } -} - -static void sculpt_stroke_undo_end(const bContext *C, Brush *brush) -{ - Object *ob = CTX_data_active_object(C); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && - SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - ED_image_undo_push_end(); - } - else { - SCULPT_undo_push_end(ob); - } -} - -bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) -{ - switch (BKE_pbvh_type(ss->pbvh)) { - case PBVH_FACES: - return true; - case PBVH_BMESH: - BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode"); - return false; - case PBVH_GRIDS: - BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode"); - return false; - } - - BLI_assert_msg(0, "PBVH corruption, type was invalid."); - - return false; -} - -static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mval[2]) -{ - /* Don't start the stroke until `mval` goes over the mesh. - * NOTE: `mval` will only be null when re-executing the saved stroke. - * We have exception for 'exec' strokes since they may not set `mval`, - * only 'location', see: T52195. */ - if (((op->flag & OP_IS_INVOKE) == 0) || (mval == NULL) || over_mesh(C, op, mval)) { - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the - * canvas it is painting on. (ref. use_sculpt_texture_paint). */ - if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - View3D *v3d = CTX_wm_view3d(C); - if (v3d->shading.type == OB_SOLID) { - v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; - } - } - - ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); - - sculpt_update_cache_invariants(C, sd, ss, op, mval); - - SculptCursorGeometryInfo sgi; - SCULPT_cursor_geometry_info_update(C, &sgi, mval, false); - - sculpt_stroke_undo_begin(C, op); - - SCULPT_stroke_id_next(ob); - ss->cache->stroke_id = ss->stroke_id; - - return true; - } - return false; -} - -static void sculpt_stroke_update_step(bContext *C, - wmOperator *UNUSED(op), - struct PaintStroke *stroke, - PointerRNA *itemptr) -{ - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); - ToolSettings *tool_settings = CTX_data_tool_settings(C); - StrokeCache *cache = ss->cache; - cache->stroke_distance = paint_stroke_distance_get(stroke); - - SCULPT_stroke_modifiers_check(C, ob, brush); - sculpt_update_cache_variants(C, sd, ob, itemptr); - sculpt_restore_mesh(sd, ob); - - if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { - float object_space_constant_detail = 1.0f / (sd->constant_detail * - mat4_to_scale(ob->object_to_world)); - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); - } - else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); - } - else { - BKE_pbvh_bmesh_detail_size_set(ss->pbvh, - (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * - (sd->detail_size * U.pixelsize) / 0.4f); - } - - if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { - do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); - } - - do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); - sculpt_combine_proxies(sd, ob); - - /* Hack to fix noise texture tearing mesh. */ - sculpt_fix_noise_tear(sd, ob); - - /* TODO(sergey): This is not really needed for the solid shading, - * which does use pBVH drawing anyway, but texture and wireframe - * requires this. - * - * Could be optimized later, but currently don't think it's so - * much common scenario. - * - * Same applies to the DEG_id_tag_update() invoked from - * sculpt_flush_update_step(). - */ - if (ss->deform_modifiers_active) { - SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); - } - else if (ss->shapekey_active) { - sculpt_update_keyblock(ob); - } - - ss->cache->first_time = false; - copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); - - /* Cleanup. */ - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); - } - else if (SCULPT_tool_is_paint(brush->sculpt_tool)) { - if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); - } - else { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); - } - } - else { - SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); - } -} - -static void sculpt_brush_exit_tex(Sculpt *sd) -{ - Brush *brush = BKE_paint_brush(&sd->paint); - MTex *mtex = &brush->mtex; - - if (mtex->tex && mtex->tex->nodetree) { - ntreeTexEndExecTree(mtex->tex->nodetree->execdata); - } -} - -static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(stroke)) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - /* Finished. */ - if (!ss->cache) { - sculpt_brush_exit_tex(sd); - return; - } - UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - Brush *brush = BKE_paint_brush(&sd->paint); - BLI_assert(brush == ss->cache->brush); /* const, so we shouldn't change. */ - ups->draw_inverted = false; - - SCULPT_stroke_modifiers_check(C, ob, brush); - - /* Alt-Smooth. */ - if (ss->cache->alt_smooth) { - smooth_brush_toggle_off(C, &sd->paint, ss->cache); - /* Refresh the brush pointer in case we switched brush in the toggle function. */ - brush = BKE_paint_brush(&sd->paint); - } - - if (SCULPT_is_automasking_enabled(sd, ss, brush)) { - SCULPT_automasking_cache_free(ss->cache->automasking); - } - - BKE_pbvh_node_color_buffer_free(ss->pbvh); - SCULPT_cache_free(ss->cache); - ss->cache = NULL; - - sculpt_stroke_undo_end(C, brush); - - if (brush->sculpt_tool == SCULPT_TOOL_MASK) { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); - } - else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { - if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); - } - else { - BKE_sculpt_attributes_destroy_temporary_stroke(ob); - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR); - } - } - else { - SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); - } - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - sculpt_brush_exit_tex(sd); -} - -static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - struct PaintStroke *stroke; - int ignore_background_click; - int retval; - Object *ob = CTX_data_active_object(C); - - /* Test that ob is visible; otherwise we won't be able to get evaluated data - * from the depsgraph. We do this here instead of SCULPT_mode_poll - * to avoid falling through to the translate operator in the - * global view3d keymap. - * - * NOTE: #BKE_object_is_visible_in_viewport is not working here (it returns false - * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. - */ - - if (ob->visibility_flag & OB_HIDE_VIEWPORT) { - return OPERATOR_CANCELLED; - } - - sculpt_brush_stroke_init(C, op); - - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - SculptSession *ss = ob->sculpt; - - if (SCULPT_tool_is_paint(brush->sculpt_tool) && - !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { - return OPERATOR_CANCELLED; - } - if (SCULPT_tool_is_mask(brush->sculpt_tool)) { - MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); - BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), ob, mmd); - } - if (SCULPT_tool_is_face_sets(brush->sculpt_tool)) { - Mesh *mesh = BKE_object_get_original_mesh(ob); - ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); - } - - stroke = paint_stroke_new(C, - op, - SCULPT_stroke_get_location, - sculpt_stroke_test_start, - sculpt_stroke_update_step, - NULL, - sculpt_stroke_done, - event->type); - - op->customdata = stroke; - - /* For tablet rotation. */ - ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); - - if (ignore_background_click && !over_mesh(C, op, (const float[2]){UNPACK2(event->mval)})) { - paint_stroke_free(C, op, op->customdata); - return OPERATOR_PASS_THROUGH; - } - - retval = op->type->modal(C, op, event); - if (ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { - paint_stroke_free(C, op, op->customdata); - return retval; - } - /* Add modal handler. */ - WM_event_add_modal_handler(C, op); - - OPERATOR_RETVAL_CHECK(retval); - BLI_assert(retval == OPERATOR_RUNNING_MODAL); - - return OPERATOR_RUNNING_MODAL; -} - -static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) -{ - sculpt_brush_stroke_init(C, op); - - op->customdata = paint_stroke_new(C, - op, - SCULPT_stroke_get_location, - sculpt_stroke_test_start, - sculpt_stroke_update_step, - NULL, - sculpt_stroke_done, - 0); - - /* Frees op->customdata. */ - paint_stroke_exec(C, op, op->customdata); - - return OPERATOR_FINISHED; -} - -static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) -{ - Object *ob = CTX_data_active_object(C); - SculptSession *ss = ob->sculpt; - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - const Brush *brush = BKE_paint_brush(&sd->paint); - - /* XXX Canceling strokes that way does not work with dynamic topology, - * user will have to do real undo for now. See T46456. */ - if (ss->cache && !SCULPT_stroke_is_dynamic_topology(ss, brush)) { - paint_mesh_restore_co(sd, ob); - } - - paint_stroke_cancel(C, op, op->customdata); - - if (ss->cache) { - SCULPT_cache_free(ss->cache); - ss->cache = NULL; - } - - sculpt_brush_exit_tex(sd); -} - -static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - bool started = op->customdata && paint_stroke_started((struct PaintStroke *)op->customdata); - - int retval = paint_stroke_modal(C, op, event, (struct PaintStroke **)&op->customdata); - - if (!started && ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { - /* Did the stroke never start? If so push a blank sculpt undo - * step to prevent a global undo step (which is triggered by the - * #OPTYPE_UNDO flag in #SCULPT_OT_brush_stroke). - * - * Having blank global undo steps interleaved with sculpt steps - * corrupts the DynTopo undo stack. - * See T101430. - * - * NOTE: simply returning #OPERATOR_CANCELLED was not - * sufficient to prevent this. */ - Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - Brush *brush = BKE_paint_brush(&sd->paint); - - sculpt_stroke_undo_begin(C, op); - sculpt_stroke_undo_end(C, brush); - } - - return retval; -} - -static void sculpt_redo_empty_ui(bContext *UNUSED(C), wmOperator *UNUSED(op)) -{ -} - -void SCULPT_OT_brush_stroke(wmOperatorType *ot) -{ - /* Identifiers. */ - ot->name = "Sculpt"; - ot->idname = "SCULPT_OT_brush_stroke"; - ot->description = "Sculpt a stroke into the geometry"; - - /* API callbacks. */ - ot->invoke = sculpt_brush_stroke_invoke; - ot->modal = sculpt_brush_stroke_modal; - ot->exec = sculpt_brush_stroke_exec; - ot->poll = SCULPT_poll; - ot->cancel = sculpt_brush_stroke_cancel; - ot->ui = sculpt_redo_empty_ui; - - /* Flags (sculpt does own undo? (ton)). */ - ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; - - /* Properties. */ - - paint_stroke_operator_properties(ot); - - RNA_def_boolean(ot->srna, - "ignore_background_click", - 0, - "Ignore Background Click", - "Clicks on the background do not start the stroke"); -} - -/* Fake Neighbors. */ -/* This allows the sculpt tools to work on meshes with multiple connected components as they had - * only one connected component. When initialized and enabled, the sculpt API will return extra - * connectivity neighbors that are not in the real mesh. These neighbors are calculated for each - * vertex using the minimum distance to a vertex that is in a different connected component. */ - -/* The fake neighbors first need to be ensured to be initialized. - * After that tools which needs fake neighbors functionality need to - * temporarily enable it: - * - * void my_awesome_sculpt_tool() { - * SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max); - * SCULPT_fake_neighbors_enable(ob); - * - * ... Logic of the tool ... - * SCULPT_fake_neighbors_disable(ob); - * } - * - * Such approach allows to keep all the connectivity information ready for reuse - * (without having lag prior to every stroke), but also makes it so the affect - * is localized to a specific brushes and tools only. */ - -enum { - SCULPT_TOPOLOGY_ID_NONE, - SCULPT_TOPOLOGY_ID_DEFAULT, -}; - -static int SCULPT_vertex_get_connected_component(SculptSession *ss, PBVHVertRef vertex) -{ - if (ss->vertex_info.connected_component) { - return ss->vertex_info.connected_component[vertex.i]; - } - return SCULPT_TOPOLOGY_ID_DEFAULT; -} - -static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) -{ - const int totvert = SCULPT_vertex_count_get(ss); - ss->fake_neighbors.fake_neighbor_index = MEM_malloc_arrayN( - totvert, sizeof(int), "fake neighbor"); - for (int i = 0; i < totvert; i++) { - ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; - } - - ss->fake_neighbors.current_max_distance = max_dist; -} - -static void SCULPT_fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_b) -{ - int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); - int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); - - if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { - ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; - ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; - } -} - -static void sculpt_pose_fake_neighbors_free(SculptSession *ss) -{ - MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); -} - -typedef struct NearestVertexFakeNeighborTLSData { - PBVHVertRef nearest_vertex; - float nearest_vertex_distance_squared; - int current_topology_id; -} NearestVertexFakeNeighborTLSData; - -static void do_fake_neighbor_search_task_cb(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict tls) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - NearestVertexFakeNeighborTLSData *nvtd = tls->userdata_chunk; - PBVHVertexIter vd; - - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.vertex); - if (vd_topology_id != nvtd->current_topology_id && - ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { - float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); - if (distance_squared < nvtd->nearest_vertex_distance_squared && - distance_squared < data->max_distance_squared) { - nvtd->nearest_vertex = vd.vertex; - nvtd->nearest_vertex_distance_squared = distance_squared; - } - } - } - BKE_pbvh_vertex_iter_end; -} - -static void fake_neighbor_search_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - NearestVertexFakeNeighborTLSData *join = chunk_join; - NearestVertexFakeNeighborTLSData *nvtd = chunk; - if (join->nearest_vertex.i == PBVH_REF_NONE) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } - else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { - join->nearest_vertex = nvtd->nearest_vertex; - join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; - } -} - -static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd, - Object *ob, - const PBVHVertRef vertex, - float max_distance) -{ - SculptSession *ss = ob->sculpt; - PBVHNode **nodes = NULL; - int totnode; - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = max_distance * max_distance, - .original = false, - .center = SCULPT_vertex_co_get(ss, vertex), - }; - BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); - - if (totnode == 0) { - return BKE_pbvh_make_vref(PBVH_REF_NONE); - } - - SculptThreadedTaskData task_data = { - .sd = sd, - .ob = ob, - .nodes = nodes, - .max_distance_squared = max_distance * max_distance, - }; - - copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, vertex)); - - NearestVertexFakeNeighborTLSData nvtd; - nvtd.nearest_vertex.i = -1; - nvtd.nearest_vertex_distance_squared = FLT_MAX; - nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, vertex); - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - settings.func_reduce = fake_neighbor_search_reduce; - settings.userdata_chunk = &nvtd; - settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData); - BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings); - - MEM_SAFE_FREE(nodes); - - return nvtd.nearest_vertex; -} - -typedef struct SculptTopologyIDFloodFillData { - int next_id; -} SculptTopologyIDFloodFillData; - -static bool SCULPT_connected_components_floodfill_cb(SculptSession *ss, - PBVHVertRef from_v, - PBVHVertRef to_v, - bool UNUSED(is_duplicate), - void *userdata) -{ - SculptTopologyIDFloodFillData *data = userdata; - - int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); - int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); - - ss->vertex_info.connected_component[from_v_i] = data->next_id; - ss->vertex_info.connected_component[to_v_i] = data->next_id; - return true; -} - -void SCULPT_connected_components_ensure(Object *ob) -{ - SculptSession *ss = ob->sculpt; - - /* Topology IDs already initialized. They only need to be recalculated when the PBVH is - * rebuild. - */ - if (ss->vertex_info.connected_component) { - return; - } - - const int totvert = SCULPT_vertex_count_get(ss); - ss->vertex_info.connected_component = MEM_malloc_arrayN(totvert, sizeof(int), "topology ID"); - - for (int i = 0; i < totvert; i++) { - ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE; - } - - int next_id = 0; - for (int i = 0; i < totvert; i++) { - PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { - SculptFloodFill flood; - SCULPT_floodfill_init(ss, &flood); - SCULPT_floodfill_add_initial(&flood, vertex); - SculptTopologyIDFloodFillData data; - data.next_id = next_id; - SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); - SCULPT_floodfill_free(&flood); - next_id++; - } - } -} - -void SCULPT_boundary_info_ensure(Object *object) -{ - SculptSession *ss = object->sculpt; - if (ss->vertex_info.boundary) { - return; - } - - Mesh *base_mesh = BKE_mesh_from_object(object); - const MEdge *edges = BKE_mesh_edges(base_mesh); - const MPoly *polys = BKE_mesh_polys(base_mesh); - const MLoop *loops = BKE_mesh_loops(base_mesh); - - ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info"); - int *adjacent_faces_edge_count = MEM_calloc_arrayN( - base_mesh->totedge, sizeof(int), "Adjacent face edge count"); - - for (int p = 0; p < base_mesh->totpoly; p++) { - const MPoly *poly = &polys[p]; - for (int l = 0; l < poly->totloop; l++) { - const MLoop *loop = &loops[l + poly->loopstart]; - adjacent_faces_edge_count[loop->e]++; - } - } - - for (int e = 0; e < base_mesh->totedge; e++) { - if (adjacent_faces_edge_count[e] < 2) { - const MEdge *edge = &edges[e]; - BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true); - BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true); - } - } - - MEM_freeN(adjacent_faces_edge_count); -} - -void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) -{ - SculptSession *ss = ob->sculpt; - const int totvert = SCULPT_vertex_count_get(ss); - - /* Fake neighbors were already initialized with the same distance, so no need to be - * recalculated. - */ - if (ss->fake_neighbors.fake_neighbor_index && - ss->fake_neighbors.current_max_distance == max_dist) { - return; - } - - SCULPT_connected_components_ensure(ob); - SCULPT_fake_neighbor_init(ss, max_dist); - - for (int i = 0; i < totvert; i++) { - const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); - - /* This vertex does not have a fake neighbor yet, search one for it. */ - if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { - const PBVHVertRef to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); - if (to_v.i != PBVH_REF_NONE) { - /* Add the fake neighbor if available. */ - SCULPT_fake_neighbor_add(ss, from_v, to_v); - } - } - } -} - -void SCULPT_fake_neighbors_enable(Object *ob) -{ - SculptSession *ss = ob->sculpt; - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - ss->fake_neighbors.use_fake_neighbors = true; -} - -void SCULPT_fake_neighbors_disable(Object *ob) -{ - SculptSession *ss = ob->sculpt; - BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); - ss->fake_neighbors.use_fake_neighbors = false; -} - -void SCULPT_fake_neighbors_free(Object *ob) -{ - SculptSession *ss = ob->sculpt; - sculpt_pose_fake_neighbors_free(ss); -} - -void SCULPT_automasking_node_begin(Object *ob, - const SculptSession *UNUSED(ss), - AutomaskingCache *automasking, - AutomaskingNodeData *automask_data, - PBVHNode *node) -{ - if (!automasking) { - memset(automask_data, 0, sizeof(*automask_data)); - return; - } - - automask_data->node = node; - automask_data->have_orig_data = automasking->settings.flags & - (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL); - - if (automask_data->have_orig_data) { - SCULPT_orig_vert_data_init(&automask_data->orig_data, ob, node, SCULPT_UNDO_COORDS); - } - else { - memset(&automask_data->orig_data, 0, sizeof(automask_data->orig_data)); - } -} - -void SCULPT_automasking_node_update(SculptSession *UNUSED(ss), - AutomaskingNodeData *automask_data, - PBVHVertexIter *vd) -{ - if (automask_data->have_orig_data) { - SCULPT_orig_vert_data_update(&automask_data->orig_data, vd); - } -} - -bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool original) -{ - float ray_start[3], ray_end[3], ray_normal[3], face_normal[3]; - float co[3]; - - copy_v3_v3(co, SCULPT_vertex_co_get(ss, vertex)); - float mouse[2]; - - ED_view3d_project_float_v2_m4(ss->cache->vc->region, co, mouse, ss->cache->projection_mat); - - int depth = SCULPT_raycast_init(ss->cache->vc, mouse, ray_end, ray_start, ray_normal, original); - - negate_v3(ray_normal); - - copy_v3_v3(ray_start, SCULPT_vertex_co_get(ss, vertex)); - madd_v3_v3fl(ray_start, ray_normal, 0.002); - - SculptRaycastData srd = {0}; - srd.original = original; - srd.ss = ss; - srd.hit = false; - srd.ray_start = ray_start; - srd.ray_normal = ray_normal; - srd.depth = depth; - srd.face_normal = face_normal; - - isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); - BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); - - return srd.hit; -} - -void SCULPT_stroke_id_next(Object *ob) -{ - /* Manually wrap in int32 space to avoid tripping up undefined behavior - * sanitizers. - */ - ob->sculpt->stroke_id = (uchar)(((int)ob->sculpt->stroke_id + 1) & 255); -} - -void SCULPT_stroke_id_ensure(Object *ob) -{ - SculptSession *ss = ob->sculpt; - - if (!ss->attrs.automasking_stroke_id) { - SculptAttributeParams params = {0}; - ss->attrs.automasking_stroke_id = BKE_sculpt_attribute_ensure( - ob, - ATTR_DOMAIN_POINT, - CD_PROP_INT8, - SCULPT_ATTRIBUTE_NAME(automasking_stroke_id), - ¶ms); - } -} - -/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc new file mode 100644 index 00000000000..684fcdbff9e --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -0,0 +1,6193 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2006 by Nicholas Bishop. All rights reserved. */ + +/** \file + * \ingroup edsculpt + * Implements the Sculpt Mode tools. + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_dial_2d.h" +#include "BLI_ghash.h" +#include "BLI_gsqueue.h" +#include "BLI_math.h" +#include "BLI_task.h" +#include "BLI_utildefines.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_attribute.h" +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_image.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_modifier.h" +#include "BKE_multires.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_subdiv_ccg.h" +#include "BKE_subsurf.h" + +#include "NOD_texture.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_paint.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 "bmesh.h" + +/* -------------------------------------------------------------------- */ +/** \name Sculpt PBVH Abstraction API + * + * This is read-only, for writing use PBVH vertex iterators. There vd.index matches + * the indices used here. + * + * For multi-resolution, the same vertex in multiple grids is counted multiple times, with + * different index for each grid. + * \{ */ + +void SCULPT_vertex_random_access_ensure(SculptSession *ss) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + } +} + +int SCULPT_vertex_count_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return ss->totvert; + case PBVH_BMESH: + return BM_mesh_elem_count(BKE_pbvh_get_bmesh(ss->pbvh), BM_VERT); + case PBVH_GRIDS: + return BKE_pbvh_get_grid_num_verts(ss->pbvh); + } + + return 0; +} + +const float *SCULPT_vertex_co_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (ss->shapekey_active || ss->deform_modifiers_active) { + const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); + return mverts[vertex.i].co; + } + return ss->mvert[vertex.i].co; + } + case PBVH_BMESH: + return ((BMVert *)vertex.i)->co; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + return CCG_elem_co(key, CCG_elem_offset(key, elem, vertex_index)); + } + } + return nullptr; +} + +bool SCULPT_has_loop_colors(const Object *ob) +{ + Mesh *me = BKE_object_get_original_mesh(ob); + const CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + + return layer && BKE_id_attribute_domain(&me->id, layer) == ATTR_DOMAIN_CORNER; +} + +bool SCULPT_has_colors(const SculptSession *ss) +{ + return ss->vcol || ss->mcol; +} + +void SCULPT_vertex_color_get(const SculptSession *ss, PBVHVertRef vertex, float r_color[4]) +{ + BKE_pbvh_vertex_color_get(ss->pbvh, vertex, r_color); +} + +void SCULPT_vertex_color_set(SculptSession *ss, PBVHVertRef vertex, const float color[4]) +{ + BKE_pbvh_vertex_color_set(ss->pbvh, vertex, color); +} + +void SCULPT_vertex_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + const float(*vert_normals)[3] = BKE_pbvh_get_vert_normals(ss->pbvh); + copy_v3_v3(no, vert_normals[vertex.i]); + break; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + copy_v3_v3(no, v->no); + break; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + copy_v3_v3(no, CCG_elem_no(key, CCG_elem_offset(key, elem, vertex_index))); + break; + } + } +} + +const float *SCULPT_vertex_persistent_co_get(SculptSession *ss, PBVHVertRef vertex) +{ + if (ss->attrs.persistent_co) { + return (const float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co); + } + + return SCULPT_vertex_co_get(ss, vertex); +} + +const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, PBVHVertRef vertex) +{ + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + /* Always grab active shape key if the sculpt happens on shapekey. */ + if (ss->shapekey_active) { + const MVert *mverts = BKE_pbvh_get_verts(ss->pbvh); + return mverts[vertex.i].co; + } + + /* Sculpting on the base mesh. */ + return ss->mvert[vertex.i].co; + } + + /* Everything else, such as sculpting on multires. */ + return SCULPT_vertex_co_get(ss, vertex); +} + +void SCULPT_vertex_limit_surface_get(SculptSession *ss, PBVHVertRef vertex, float r_co[3]) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_BMESH: + copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, vertex)); + break; + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + BKE_subdiv_ccg_eval_limit_point(ss->subdiv_ccg, &coord, r_co); + break; + } + } +} + +void SCULPT_vertex_persistent_normal_get(SculptSession *ss, PBVHVertRef vertex, float no[3]) +{ + if (ss->attrs.persistent_no) { + copy_v3_v3(no, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no)); + return; + } + SCULPT_vertex_normal_get(ss, vertex, no); +} + +float SCULPT_vertex_mask_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return ss->vmask ? ss->vmask[vertex.i] : 0.0f; + case PBVH_BMESH: { + BMVert *v; + int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); + + v = (BMVert *)vertex.i; + return cd_mask != -1 ? BM_ELEM_CD_GET_FLOAT(v, cd_mask) : 0.0f; + } + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + CCGElem *elem = BKE_pbvh_get_grids(ss->pbvh)[grid_index]; + return *CCG_elem_mask(key, CCG_elem_offset(key, elem, vertex_index)); + } + } + + return 0.0f; +} + +PBVHVertRef SCULPT_active_vertex_get(SculptSession *ss) +{ + if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { + return ss->active_vertex; + } + + return BKE_pbvh_make_vref(PBVH_REF_NONE); +} + +const float *SCULPT_active_vertex_co_get(SculptSession *ss) +{ + return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss)); +} + +void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]) +{ + SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal); +} + +MVert *SCULPT_mesh_deformed_mverts_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + if (ss->shapekey_active || ss->deform_modifiers_active) { + return BKE_pbvh_get_verts(ss->pbvh); + } + return ss->mvert; + case PBVH_BMESH: + case PBVH_GRIDS: + return nullptr; + } + return nullptr; +} + +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; +} + +char SCULPT_mesh_symmetry_xyz_get(Object *object) +{ + const Mesh *mesh = BKE_mesh_from_object(object); + return mesh->symmetry; +} + +/* Sculpt Face Sets and Visibility. */ + +int SCULPT_active_face_set_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + return ss->face_sets[ss->active_face_index]; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, + ss->active_grid_index); + return ss->face_sets[face_index]; + } + case PBVH_BMESH: + return SCULPT_FACE_SET_NONE; + } + return SCULPT_FACE_SET_NONE; +} + +void SCULPT_vertex_visible_set(SculptSession *ss, PBVHVertRef vertex, bool visible) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + bool *hide_vert = BKE_pbvh_get_vert_hide_for_write(ss->pbvh); + hide_vert[vertex.i] = visible; + break; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BM_elem_flag_set(v, BM_ELEM_HIDDEN, !visible); + break; + } + case PBVH_GRIDS: + break; + } +} + +bool SCULPT_vertex_visible_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + const bool *hide_vert = BKE_pbvh_get_vert_hide(ss->pbvh); + return hide_vert == nullptr || !hide_vert[vertex.i]; + } + case PBVH_BMESH: + return !BM_elem_flag_test((BMVert *)vertex.i, BM_ELEM_HIDDEN); + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + BLI_bitmap **grid_hidden = BKE_pbvh_get_grid_visibility(ss->pbvh); + if (grid_hidden && grid_hidden[grid_index]) { + return !BLI_BITMAP_TEST(grid_hidden[grid_index], vertex_index); + } + } + } + return true; +} + +void SCULPT_face_set_visibility_set(SculptSession *ss, int face_set, bool visible) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] != face_set) { + continue; + } + ss->hide_poly[i] = !visible; + } + break; + case PBVH_BMESH: + break; + } +} + +void SCULPT_face_visibility_all_invert(SculptSession *ss) +{ + BLI_assert(ss->face_sets != nullptr); + BLI_assert(ss->hide_poly != nullptr); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + for (int i = 0; i < ss->totfaces; i++) { + ss->hide_poly[i] = !ss->hide_poly[i]; + } + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_elem_flag_toggle(f, BM_ELEM_HIDDEN); + } + break; + } + } +} + +void SCULPT_face_visibility_all_set(SculptSession *ss, bool visible) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: + BLI_assert(ss->hide_poly != nullptr); + memset(ss->hide_poly, !visible, sizeof(bool) * ss->totfaces); + break; + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_HIDDEN, !visible); + } + break; + } + } +} + +bool SCULPT_vertex_any_face_visible_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->hide_poly) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < ss->pmap[vertex.i].count; j++) { + if (!ss->hide_poly[vert_map->indices[j]]) { + return true; + } + } + return false; + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: + return true; + } + return true; +} + +bool SCULPT_vertex_all_faces_visible_get(const SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->hide_poly) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < vert_map->count; j++) { + if (ss->hide_poly[vert_map->indices[j]]) { + return false; + } + } + return true; + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + BMEdge *e = v->e; + + if (!e) { + return true; + } + + do { + BMLoop *l = e->l; + + if (!l) { + continue; + } + + do { + if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { + return false; + } + } while ((l = l->radial_next) != e->l); + } while ((e = BM_DISK_EDGE_NEXT(e, v)) != v->e); + + return true; + } + case PBVH_GRIDS: { + if (!ss->hide_poly) { + return true; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return !ss->hide_poly[face_index]; + } + } + return true; +} + +void SCULPT_vertex_face_set_set(SculptSession *ss, PBVHVertRef vertex, int face_set) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + BLI_assert(ss->face_sets != nullptr); + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int j = 0; j < vert_map->count; j++) { + const int poly_index = vert_map->indices[j]; + if (ss->hide_poly && ss->hide_poly[poly_index]) { + /* Skip hidden faces connected to the vertex. */ + continue; + } + ss->face_sets[poly_index] = face_set; + } + break; + } + case PBVH_BMESH: + break; + case PBVH_GRIDS: { + BLI_assert(ss->face_sets != nullptr); + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + if (ss->hide_poly && ss->hide_poly[face_index]) { + /* Skip the vertex if it's in a hidden face. */ + return; + } + ss->face_sets[face_index] = face_set; + break; + } + } +} + +int SCULPT_vertex_face_set_get(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + int face_set = 0; + for (int i = 0; i < vert_map->count; i++) { + if (ss->face_sets[vert_map->indices[i]] > face_set) { + face_set = abs(ss->face_sets[vert_map->indices[i]]); + } + } + return face_set; + } + case PBVH_BMESH: + return 0; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return SCULPT_FACE_SET_NONE; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return ss->face_sets[face_index]; + } + } + return 0; +} + +bool SCULPT_vertex_has_face_set(SculptSession *ss, PBVHVertRef vertex, int face_set) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + for (int i = 0; i < vert_map->count; i++) { + if (ss->face_sets[vert_map->indices[i]] == face_set) { + return true; + } + } + return false; + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return face_set == SCULPT_FACE_SET_NONE; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); + return ss->face_sets[face_index] == face_set; + } + } + return true; +} + +void SCULPT_visibility_sync_all_from_faces(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + /* We may have adjusted the ".hide_poly" attribute, now make the hide status attributes for + * vertices and edges consistent. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh); + break; + } + case PBVH_GRIDS: { + /* In addition to making the hide status of the base mesh consistent, we also have to + * propagate the status to the Multires grids. */ + BKE_mesh_flush_hidden_from_polys(mesh); + BKE_sculpt_sync_face_visibility_to_grids(mesh, ss->subdiv_ccg); + break; + } + case PBVH_BMESH: { + BMIter iter; + BMFace *f; + + /* Hide all verts and edges attached to faces.*/ + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + BMLoop *l = f->l_first; + do { + BM_elem_flag_enable(l->v, BM_ELEM_HIDDEN); + BM_elem_flag_enable(l->e, BM_ELEM_HIDDEN); + } while ((l = l->next) != f->l_first); + } + + /* Unhide verts and edges attached to visible faces. */ + BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l = f->l_first; + do { + BM_elem_flag_disable(l->v, BM_ELEM_HIDDEN); + BM_elem_flag_disable(l->e, BM_ELEM_HIDDEN); + } while ((l = l->next) != f->l_first); + } + break; + } + } +} + +static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) +{ + if (!ss->face_sets) { + return true; + } + const MeshElemMap *vert_map = &ss->pmap[index]; + int face_set = -1; + for (int i = 0; i < vert_map->count; i++) { + if (face_set == -1) { + face_set = ss->face_sets[vert_map->indices[i]]; + } + else { + if (ss->face_sets[vert_map->indices[i]] != face_set) { + return false; + } + } + } + return true; +} + +/** + * Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2 + * in the base mesh are equal. + */ +static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(SculptSession *ss, int v1, int v2) +{ + const MeshElemMap *vert_map = &ss->pmap[v1]; + int p1 = -1, p2 = -1; + for (int i = 0; i < vert_map->count; i++) { + const MPoly *p = &ss->mpoly[vert_map->indices[i]]; + for (int l = 0; l < p->totloop; l++) { + const MLoop *loop = &ss->mloop[p->loopstart + l]; + if (loop->v == v2) { + if (p1 == -1) { + p1 = vert_map->indices[i]; + break; + } + + if (p2 == -1) { + p2 = vert_map->indices[i]; + break; + } + } + } + } + + if (p1 != -1 && p2 != -1) { + return abs(ss->face_sets[p1]) == (ss->face_sets[p2]); + } + return true; +} + +bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + return sculpt_check_unique_face_set_in_base_mesh(ss, vertex.i); + } + case PBVH_BMESH: + return true; + case PBVH_GRIDS: { + if (!ss->face_sets) { + return true; + } + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + return sculpt_check_unique_face_set_in_base_mesh(ss, v1); + case SUBDIV_CCG_ADJACENT_EDGE: + return sculpt_check_unique_face_set_for_edge_in_base_mesh(ss, v1, v2); + case SUBDIV_CCG_ADJACENT_NONE: + return true; + } + } + } + return false; +} + +int SCULPT_face_set_next_available_get(SculptSession *ss) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + case PBVH_GRIDS: { + if (!ss->face_sets) { + return 0; + } + int next_face_set = 0; + for (int i = 0; i < ss->totfaces; i++) { + if (ss->face_sets[i] > next_face_set) { + next_face_set = ss->face_sets[i]; + } + } + next_face_set++; + return next_face_set; + } + case PBVH_BMESH: + return 0; + } + return 0; +} + +/* Sculpt Neighbor Iterators */ + +#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256 + +static void sculpt_vertex_neighbor_add(SculptVertexNeighborIter *iter, + PBVHVertRef neighbor, + int neighbor_index) +{ + for (int i = 0; i < iter->size; i++) { + if (iter->neighbors[i].i == neighbor.i) { + return; + } + } + + if (iter->size >= iter->capacity) { + iter->capacity += SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + + if (iter->neighbors == iter->neighbors_fixed) { + iter->neighbors = static_cast( + MEM_mallocN(iter->capacity * sizeof(PBVHVertRef), "neighbor array")); + memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(PBVHVertRef) * iter->size); + } + else { + iter->neighbors = static_cast(MEM_reallocN_id( + iter->neighbors, iter->capacity * sizeof(PBVHVertRef), "neighbor array")); + } + + if (iter->neighbor_indices == iter->neighbor_indices_fixed) { + iter->neighbor_indices = static_cast( + MEM_mallocN(iter->capacity * sizeof(int), "neighbor array")); + memcpy(iter->neighbor_indices, iter->neighbor_indices_fixed, sizeof(int) * iter->size); + } + else { + iter->neighbor_indices = static_cast( + MEM_reallocN_id(iter->neighbor_indices, iter->capacity * sizeof(int), "neighbor array")); + } + } + + iter->neighbors[iter->size] = neighbor; + iter->neighbor_indices[iter->size] = neighbor_index; + iter->size++; +} + +static void sculpt_vertex_neighbors_get_bmesh(PBVHVertRef vertex, SculptVertexNeighborIter *iter) +{ + BMVert *v = (BMVert *)vertex.i; + BMIter liter; + BMLoop *l; + iter->size = 0; + iter->num_duplicates = 0; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { + const BMVert *adj_v[2] = {l->prev->v, l->next->v}; + for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { + const BMVert *v_other = adj_v[i]; + if (v_other != v) { + sculpt_vertex_neighbor_add( + iter, BKE_pbvh_make_vref((intptr_t)v_other), BM_elem_index_get(v_other)); + } + } + } +} + +static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, + PBVHVertRef vertex, + SculptVertexNeighborIter *iter) +{ + const MeshElemMap *vert_map = &ss->pmap[vertex.i]; + iter->size = 0; + iter->num_duplicates = 0; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + for (int i = 0; i < vert_map->count; i++) { + if (ss->hide_poly && ss->hide_poly[vert_map->indices[i]]) { + /* Skip connectivity from hidden faces. */ + continue; + } + const MPoly *p = &ss->mpoly[vert_map->indices[i]]; + int f_adj_v[2]; + if (poly_get_adj_loops_from_vert(p, ss->mloop, vertex.i, f_adj_v) != -1) { + for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { + if (f_adj_v[j] != vertex.i) { + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(f_adj_v[j]), f_adj_v[j]); + } + } + } + } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + sculpt_vertex_neighbor_add( + iter, + BKE_pbvh_make_vref(ss->fake_neighbors.fake_neighbor_index[vertex.i]), + ss->fake_neighbors.fake_neighbor_index[vertex.i]); + } + } +} + +static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, + const PBVHVertRef vertex, + const bool include_duplicates, + SculptVertexNeighborIter *iter) +{ + /* TODO: optimize this. We could fill #SculptVertexNeighborIter directly, + * maybe provide coordinate and mask pointers directly rather than converting + * back and forth between #CCGElem and global index. */ + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + + SubdivCCGNeighbors neighbors; + BKE_subdiv_ccg_neighbor_coords_get(ss->subdiv_ccg, &coord, include_duplicates, &neighbors); + + iter->size = 0; + iter->num_duplicates = neighbors.num_duplicates; + iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; + iter->neighbors = iter->neighbors_fixed; + iter->neighbor_indices = iter->neighbor_indices_fixed; + + for (int i = 0; i < neighbors.size; i++) { + int v = neighbors.coords[i].grid_index * key->grid_area + + neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x; + + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + } + + if (ss->fake_neighbors.use_fake_neighbors) { + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + if (ss->fake_neighbors.fake_neighbor_index[vertex.i] != FAKE_NEIGHBOR_NONE) { + int v = ss->fake_neighbors.fake_neighbor_index[vertex.i]; + sculpt_vertex_neighbor_add(iter, BKE_pbvh_make_vref(v), v); + } + } + + if (neighbors.coords != neighbors.coords_fixed) { + MEM_freeN(neighbors.coords); + } +} + +void SCULPT_vertex_neighbors_get(SculptSession *ss, + const PBVHVertRef vertex, + const bool include_duplicates, + SculptVertexNeighborIter *iter) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + sculpt_vertex_neighbors_get_faces(ss, vertex, iter); + return; + case PBVH_BMESH: + sculpt_vertex_neighbors_get_bmesh(vertex, iter); + return; + case PBVH_GRIDS: + sculpt_vertex_neighbors_get_grids(ss, vertex, include_duplicates, iter); + return; + } +} + +static bool sculpt_check_boundary_vertex_in_base_mesh(const SculptSession *ss, const int index) +{ + BLI_assert(ss->vertex_info.boundary); + return BLI_BITMAP_TEST(ss->vertex_info.boundary, index); +} + +bool SCULPT_vertex_is_boundary(const SculptSession *ss, const PBVHVertRef vertex) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + if (!SCULPT_vertex_all_faces_visible_get(ss, vertex)) { + return true; + } + return sculpt_check_boundary_vertex_in_base_mesh(ss, vertex.i); + } + case PBVH_BMESH: { + BMVert *v = (BMVert *)vertex.i; + return BM_vert_is_boundary(v); + } + + case PBVH_GRIDS: { + const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); + const int grid_index = vertex.i / key->grid_area; + const int vertex_index = vertex.i - grid_index * key->grid_area; + SubdivCCGCoord coord{}; + coord.grid_index = grid_index; + coord.x = vertex_index % key->grid_size; + coord.y = vertex_index / key->grid_size; + int v1, v2; + const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get( + ss->subdiv_ccg, &coord, ss->mloop, ss->mpoly, &v1, &v2); + switch (adjacency) { + case SUBDIV_CCG_ADJACENT_VERTEX: + return sculpt_check_boundary_vertex_in_base_mesh(ss, v1); + case SUBDIV_CCG_ADJACENT_EDGE: + return sculpt_check_boundary_vertex_in_base_mesh(ss, v1) && + sculpt_check_boundary_vertex_in_base_mesh(ss, v2); + case SUBDIV_CCG_ADJACENT_NONE: + return false; + } + } + } + + return false; +} + +/* Utilities */ + +bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache) +{ + return cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && + cache->tile_pass == 0; +} + +bool SCULPT_stroke_is_first_brush_step(StrokeCache *cache) +{ + return cache->first_time && cache->mirror_symmetry_pass == 0 && + cache->radial_symmetry_pass == 0 && cache->tile_pass == 0; +} + +bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(StrokeCache *cache) +{ + return cache->first_time; +} + +bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm) +{ + bool is_in_symmetry_area = true; + for (int i = 0; i < 3; i++) { + char symm_it = 1 << i; + if (symm & symm_it) { + if (pco[i] == 0.0f) { + if (vco[i] > 0.0f) { + is_in_symmetry_area = false; + } + } + if (vco[i] * pco[i] < 0.0f) { + is_in_symmetry_area = false; + } + } + } + return is_in_symmetry_area; +} + +struct NearestVertexTLSData { + PBVHVertRef nearest_vertex; + float nearest_vertex_distance_squared; +}; + +static void do_nearest_vertex_get_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + NearestVertexTLSData *nvtd = static_cast(tls->userdata_chunk); + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); + if (distance_squared < nvtd->nearest_vertex_distance_squared && + distance_squared < data->max_distance_squared) { + nvtd->nearest_vertex = vd.vertex; + nvtd->nearest_vertex_distance_squared = distance_squared; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void nearest_vertex_get_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + NearestVertexTLSData *join = static_cast(chunk_join); + NearestVertexTLSData *nvtd = static_cast(chunk); + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } + else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } +} + +PBVHVertRef SCULPT_nearest_vertex_get( + Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + int totnode; + SculptSearchSphereData data{}; + data.sd = sd; + data.radius_squared = max_distance * max_distance; + data.original = use_original; + data.center = co; + + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); + if (totnode == 0) { + return BKE_pbvh_make_vref(PBVH_REF_NONE); + } + + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.nodes = nodes; + task_data.max_distance_squared = max_distance * max_distance; + + copy_v3_v3(task_data.nearest_vertex_search_co, co); + NearestVertexTLSData nvtd; + nvtd.nearest_vertex.i = PBVH_REF_NONE; + nvtd.nearest_vertex_distance_squared = FLT_MAX; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = nearest_vertex_get_reduce; + settings.userdata_chunk = &nvtd; + settings.userdata_chunk_size = sizeof(NearestVertexTLSData); + BLI_task_parallel_range(0, totnode, &task_data, do_nearest_vertex_get_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + return nvtd.nearest_vertex; +} + +bool SCULPT_is_symmetry_iteration_valid(char i, char symm) +{ + return i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || !ELEM(i, 3, 5))); +} + +bool SCULPT_is_vertex_inside_brush_radius_symm(const float vertex[3], + const float br_co[3], + float radius, + char symm) +{ + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + float location[3]; + flip_v3_v3(location, br_co, ePaintSymmetryFlags(i)); + if (len_squared_v3v3(location, vertex) < radius * radius) { + return true; + } + } + return false; +} + +void SCULPT_tag_update_overlays(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + ED_region_tag_redraw(region); + + Object *ob = CTX_data_active_object(C); + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + + RegionView3D *rv3d = CTX_wm_region_view3d(C); + if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Flood Fill API + * + * Iterate over connected vertices, starting from one or more initial vertices. + * \{ */ + +void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood) +{ + int vertex_count = SCULPT_vertex_count_get(ss); + SCULPT_vertex_random_access_ensure(ss); + + flood->queue = BLI_gsqueue_new(sizeof(intptr_t)); + flood->visited_verts = BLI_BITMAP_NEW(vertex_count, "visited verts"); +} + +void SCULPT_floodfill_add_initial(SculptFloodFill *flood, PBVHVertRef vertex) +{ + BLI_gsqueue_push(flood->queue, &vertex); +} + +void SCULPT_floodfill_add_and_skip_initial(SculptFloodFill *flood, PBVHVertRef vertex) +{ + BLI_gsqueue_push(flood->queue, &vertex); + BLI_BITMAP_ENABLE(flood->visited_verts, vertex.i); +} + +void SCULPT_floodfill_add_initial_with_symmetry(Sculpt *sd, + Object *ob, + SculptSession *ss, + SculptFloodFill *flood, + PBVHVertRef vertex, + float radius) +{ + /* Add active vertex and symmetric vertices to the queue. */ + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + PBVHVertRef v = {PBVH_REF_NONE}; + + if (i == 0) { + v = vertex; + } + else if (radius > 0.0f) { + float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; + float location[3]; + flip_v3_v3(location, SCULPT_vertex_co_get(ss, vertex), ePaintSymmetryFlags(i)); + v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); + } + + if (v.i != PBVH_REF_NONE) { + SCULPT_floodfill_add_initial(flood, v); + } + } +} + +void SCULPT_floodfill_add_active( + Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, float radius) +{ + /* Add active vertex and symmetric vertices to the queue. */ + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char i = 0; i <= symm; ++i) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + + PBVHVertRef v = {PBVH_REF_NONE}; + + if (i == 0) { + v = SCULPT_active_vertex_get(ss); + } + else if (radius > 0.0f) { + float location[3]; + flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), ePaintSymmetryFlags(i)); + v = SCULPT_nearest_vertex_get(sd, ob, location, radius, false); + } + + if (v.i != PBVH_REF_NONE) { + SCULPT_floodfill_add_initial(flood, v); + } + } +} + +void SCULPT_floodfill_execute(SculptSession *ss, + SculptFloodFill *flood, + bool (*func)(SculptSession *ss, + PBVHVertRef from_v, + PBVHVertRef to_v, + bool is_duplicate, + void *userdata), + void *userdata) +{ + while (!BLI_gsqueue_is_empty(flood->queue)) { + PBVHVertRef from_v; + + BLI_gsqueue_pop(flood->queue, &from_v); + SculptVertexNeighborIter ni; + SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { + const PBVHVertRef to_v = ni.vertex; + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + if (BLI_BITMAP_TEST(flood->visited_verts, to_v_i)) { + continue; + } + + if (!SCULPT_vertex_visible_get(ss, to_v)) { + continue; + } + + BLI_BITMAP_ENABLE(flood->visited_verts, BKE_pbvh_vertex_to_index(ss->pbvh, to_v)); + + if (func(ss, from_v, to_v, ni.is_duplicate, userdata)) { + BLI_gsqueue_push(flood->queue, &to_v); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } +} + +void SCULPT_floodfill_free(SculptFloodFill *flood) +{ + MEM_SAFE_FREE(flood->visited_verts); + BLI_gsqueue_free(flood->queue); + flood->queue = nullptr; +} + +/** \} */ + +static bool sculpt_tool_has_cube_tip(const char sculpt_tool) +{ + return ELEM( + sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_PAINT, SCULPT_TOOL_MULTIPLANE_SCRAPE); +} + +/* -------------------------------------------------------------------- */ +/** \name Tool Capabilities + * + * Avoid duplicate checks, internal logic only, + * share logic with #rna_def_sculpt_capabilities where possible. + * \{ */ + +static bool sculpt_tool_needs_original(const char sculpt_tool) +{ + return ELEM(sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_POSE); +} + +static bool sculpt_tool_is_proxy_used(const char sculpt_tool) +{ + return ELEM(sculpt_tool, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_POSE, + SCULPT_TOOL_DISPLACEMENT_SMEAR, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR, + SCULPT_TOOL_DRAW_FACE_SETS); +} + +static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush) +{ + return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(brush->sculpt_tool) && + (brush->topology_rake_factor > 0.0f) && (ss->bm != nullptr); +} + +/** + * Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action + */ +static int sculpt_brush_needs_normal(const SculptSession *ss, Sculpt *sd, const Brush *brush) +{ + return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && + (ss->cache->normal_weight > 0.0f)) || + SCULPT_automasking_needs_normal(ss, sd, brush) || + ELEM(brush->sculpt_tool, + SCULPT_TOOL_BLOB, + SCULPT_TOOL_CREASE, + SCULPT_TOOL_DRAW, + SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_LAYER, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_THUMB) || + + (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) || + sculpt_brush_use_topology_rake(ss, brush); +} + +static bool sculpt_brush_needs_rake_rotation(const Brush *brush) +{ + return SCULPT_TOOL_HAS_RAKE(brush->sculpt_tool) && (brush->rake_factor != 0.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Init/Update + * \{ */ + +enum StrokeFlags { + CLIP_X = 1, + CLIP_Y = 2, + CLIP_Z = 4, +}; + +void SCULPT_orig_vert_data_unode_init(SculptOrigVertData *data, Object *ob, SculptUndoNode *unode) +{ + SculptSession *ss = ob->sculpt; + BMesh *bm = ss->bm; + + memset(data, 0, sizeof(*data)); + data->unode = unode; + + if (bm) { + data->bm_log = ss->bm_log; + } + else { + data->coords = data->unode->co; + data->normals = data->unode->no; + data->vmasks = data->unode->mask; + data->colors = data->unode->col; + } +} + +void SCULPT_orig_vert_data_init(SculptOrigVertData *data, + Object *ob, + PBVHNode *node, + SculptUndoType type) +{ + SculptUndoNode *unode; + unode = SCULPT_undo_push_node(ob, node, type); + SCULPT_orig_vert_data_unode_init(data, ob, unode); +} + +void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter) +{ + if (orig_data->unode->type == SCULPT_UNDO_COORDS) { + if (orig_data->bm_log) { + BM_log_original_vert_data(orig_data->bm_log, iter->bm_vert, &orig_data->co, &orig_data->no); + } + else { + orig_data->co = orig_data->coords[iter->i]; + orig_data->no = orig_data->normals[iter->i]; + } + } + else if (orig_data->unode->type == SCULPT_UNDO_COLOR) { + orig_data->col = orig_data->colors[iter->i]; + } + else if (orig_data->unode->type == SCULPT_UNDO_MASK) { + if (orig_data->bm_log) { + orig_data->mask = BM_log_original_mask(orig_data->bm_log, iter->bm_vert); + } + else { + orig_data->mask = orig_data->vmasks[iter->i]; + } + } +} + +static void sculpt_rake_data_update(SculptRakeData *srd, const float co[3]) +{ + float rake_dist = len_v3v3(srd->follow_co, co); + if (rake_dist > srd->follow_dist) { + interp_v3_v3v3(srd->follow_co, srd->follow_co, co, rake_dist - srd->follow_dist); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Dynamic Topology + * \{ */ + +bool SCULPT_stroke_is_dynamic_topology(const SculptSession *ss, const Brush *brush) +{ + return ((BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) && + + (!ss->cache || (!ss->cache->alt_smooth)) && + + /* Requires mesh restore, which doesn't work with + * dynamic-topology. */ + !(brush->flag & BRUSH_ANCHORED) && !(brush->flag & BRUSH_DRAG_DOT) && + + SCULPT_TOOL_HAS_DYNTOPO(brush->sculpt_tool)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Paint Mesh + * \{ */ + +static void paint_mesh_restore_co_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + + SculptUndoNode *unode; + SculptUndoType type; + + switch (data->brush->sculpt_tool) { + case SCULPT_TOOL_MASK: + type = SCULPT_UNDO_MASK; + break; + case SCULPT_TOOL_PAINT: + case SCULPT_TOOL_SMEAR: + type = SCULPT_UNDO_COLOR; + break; + default: + type = SCULPT_UNDO_COORDS; + break; + } + + if (ss->bm) { + unode = SCULPT_undo_push_node(data->ob, data->nodes[n], type); + } + else { + unode = SCULPT_undo_get_node(data->nodes[n], type); + } + + if (!unode) { + return; + } + + switch (type) { + case SCULPT_UNDO_MASK: + BKE_pbvh_node_mark_update_mask(data->nodes[n]); + break; + case SCULPT_UNDO_COLOR: + BKE_pbvh_node_mark_update_color(data->nodes[n]); + break; + case SCULPT_UNDO_COORDS: + BKE_pbvh_node_mark_update(data->nodes[n]); + break; + default: + break; + } + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + + SCULPT_orig_vert_data_unode_init(&orig_data, data->ob, unode); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + if (orig_data.unode->type == SCULPT_UNDO_COORDS) { + copy_v3_v3(vd.co, orig_data.co); + if (vd.no) { + copy_v3_v3(vd.no, orig_data.no); + } + else { + copy_v3_v3(vd.fno, orig_data.no); + } + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + else if (orig_data.unode->type == SCULPT_UNDO_MASK) { + *vd.mask = orig_data.mask; + } + else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { + SCULPT_vertex_color_set(ss, vd.vertex, orig_data.col); + } + } + BKE_pbvh_vertex_iter_end; +} + +static void paint_mesh_restore_co(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + PBVHNode **nodes; + int totnode; + + BKE_pbvh_search_gather(ss->pbvh, nullptr, nullptr, &nodes, &totnode); + + /** + * Disable multi-threading when dynamic-topology is enabled. Otherwise, + * new entries might be inserted by #SCULPT_undo_push_node() into the #GHash + * used internally by #BM_log_original_vert_co() by a different thread. See T33787. + */ + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true && !ss->bm, totnode); + BLI_task_parallel_range(0, totnode, &data, paint_mesh_restore_co_task_cb, &settings); + + BKE_pbvh_node_color_buffer_free(ss->pbvh); + + MEM_SAFE_FREE(nodes); +} + +/*** BVH Tree ***/ + +static void sculpt_extend_redraw_rect_previous(Object *ob, rcti *rect) +{ + /* Expand redraw \a rect with redraw \a rect from previous step to + * prevent partial-redraw issues caused by fast strokes. This is + * needed here (not in sculpt_flush_update) as it was before + * because redraw rectangle should be the same in both of + * optimized PBVH draw function and 3d view redraw, if not -- some + * mesh parts could disappear from screen (sergey). */ + SculptSession *ss = ob->sculpt; + + if (!ss->cache) { + return; + } + + if (BLI_rcti_is_empty(&ss->cache->previous_r)) { + return; + } + + BLI_rcti_union(rect, &ss->cache->previous_r); +} + +bool SCULPT_get_redraw_rect(ARegion *region, RegionView3D *rv3d, Object *ob, rcti *rect) +{ + PBVH *pbvh = ob->sculpt->pbvh; + float bb_min[3], bb_max[3]; + + if (!pbvh) { + return false; + } + + BKE_pbvh_redraw_BB(pbvh, bb_min, bb_max); + + /* Convert 3D bounding box to screen space. */ + if (!paint_convert_bb_to_rect(rect, bb_min, bb_max, region, rv3d, ob)) { + return false; + } + + return true; +} + +void ED_sculpt_redraw_planes_get(float planes[4][4], ARegion *region, Object *ob) +{ + PBVH *pbvh = ob->sculpt->pbvh; + /* Copy here, original will be used below. */ + rcti rect = ob->sculpt->cache->current_r; + + sculpt_extend_redraw_rect_previous(ob, &rect); + + paint_calc_redraw_planes(planes, region, ob, &rect); + + /* We will draw this \a rect, so now we can set it as the previous partial \a rect. + * Note that we don't update with the union of previous/current (\a rect), only with + * the current. Thus we avoid the rectangle needlessly growing to include + * all the stroke area. */ + ob->sculpt->cache->previous_r = ob->sculpt->cache->current_r; + + /* Clear redraw flag from nodes. */ + if (pbvh) { + BKE_pbvh_update_bounds(pbvh, PBVH_UpdateRedraw); + } +} + +/************************ Brush Testing *******************/ + +void SCULPT_brush_test_init(SculptSession *ss, SculptBrushTest *test) +{ + RegionView3D *rv3d = ss->cache ? ss->cache->vc->rv3d : ss->rv3d; + View3D *v3d = ss->cache ? ss->cache->vc->v3d : ss->v3d; + + test->radius_squared = ss->cache ? ss->cache->radius_squared : + ss->cursor_radius * ss->cursor_radius; + test->radius = sqrtf(test->radius_squared); + + if (ss->cache) { + copy_v3_v3(test->location, ss->cache->location); + test->mirror_symmetry_pass = ss->cache->mirror_symmetry_pass; + test->radial_symmetry_pass = ss->cache->radial_symmetry_pass; + copy_m4_m4(test->symm_rot_mat_inv, ss->cache->symm_rot_mat_inv); + } + else { + copy_v3_v3(test->location, ss->cursor_location); + test->mirror_symmetry_pass = ePaintSymmetryFlags(0); + test->radial_symmetry_pass = 0; + unit_m4(test->symm_rot_mat_inv); + } + + /* Just for initialize. */ + test->dist = 0.0f; + + /* Only for 2D projection. */ + zero_v4(test->plane_view); + zero_v4(test->plane_tool); + + if (RV3D_CLIPPING_ENABLED(v3d, rv3d)) { + test->clip_rv3d = rv3d; + } + else { + test->clip_rv3d = nullptr; + } +} + +BLI_INLINE bool sculpt_brush_test_clipping(const SculptBrushTest *test, const float co[3]) +{ + RegionView3D *rv3d = test->clip_rv3d; + if (!rv3d) { + return false; + } + float symm_co[3]; + flip_v3_v3(symm_co, co, test->mirror_symmetry_pass); + if (test->radial_symmetry_pass) { + mul_m4_v3(test->symm_rot_mat_inv, symm_co); + } + return ED_view3d_clipping_test(rv3d, symm_co, true); +} + +bool SCULPT_brush_test_sphere(SculptBrushTest *test, const float co[3]) +{ + float distsq = len_squared_v3v3(co, test->location); + + if (distsq > test->radius_squared) { + return false; + } + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + test->dist = sqrtf(distsq); + return true; +} + +bool SCULPT_brush_test_sphere_sq(SculptBrushTest *test, const float co[3]) +{ + float distsq = len_squared_v3v3(co, test->location); + + if (distsq > test->radius_squared) { + return false; + } + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + test->dist = distsq; + return true; +} + +bool SCULPT_brush_test_sphere_fast(const SculptBrushTest *test, const float co[3]) +{ + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + return len_squared_v3v3(co, test->location) <= test->radius_squared; +} + +bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]) +{ + float co_proj[3]; + closest_to_plane_normalized_v3(co_proj, test->plane_view, co); + float distsq = len_squared_v3v3(co_proj, test->location); + + if (distsq > test->radius_squared) { + return false; + } + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + test->dist = distsq; + return true; +} + +bool SCULPT_brush_test_cube(SculptBrushTest *test, + const float co[3], + const float local[4][4], + const float roundness) +{ + float side = 1.0f; + float local_co[3]; + + if (sculpt_brush_test_clipping(test, co)) { + return false; + } + + mul_v3_m4v3(local_co, local, co); + + local_co[0] = fabsf(local_co[0]); + local_co[1] = fabsf(local_co[1]); + local_co[2] = fabsf(local_co[2]); + + /* Keep the square and circular brush tips the same size. */ + side += (1.0f - side) * roundness; + + const float hardness = 1.0f - roundness; + const float constant_side = hardness * side; + const float falloff_side = roundness * side; + + if (!(local_co[0] <= side && local_co[1] <= side && local_co[2] <= side)) { + /* Outside the square. */ + return false; + } + if (min_ff(local_co[0], local_co[1]) > constant_side) { + /* Corner, distance to the center of the corner circle. */ + float r_point[3]; + copy_v3_fl(r_point, constant_side); + test->dist = len_v2v2(r_point, local_co) / falloff_side; + return true; + } + if (max_ff(local_co[0], local_co[1]) > constant_side) { + /* Side, distance to the square XY axis. */ + test->dist = (max_ff(local_co[0], local_co[1]) - constant_side) / falloff_side; + return true; + } + + /* Inside the square, constant distance. */ + test->dist = 0.0f; + return true; +} + +SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, + SculptBrushTest *test, + char falloff_shape) +{ + SCULPT_brush_test_init(ss, test); + SculptBrushTestFn sculpt_brush_test_sq_fn; + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + sculpt_brush_test_sq_fn = SCULPT_brush_test_sphere_sq; + } + else { + /* PAINT_FALLOFF_SHAPE_TUBE */ + plane_from_point_normal_v3(test->plane_view, test->location, ss->cache->view_normal); + sculpt_brush_test_sq_fn = SCULPT_brush_test_circle_sq; + } + return sculpt_brush_test_sq_fn; +} + +const float *SCULPT_brush_frontface_normal_from_falloff_shape(SculptSession *ss, + char falloff_shape) +{ + if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + return ss->cache->sculpt_normal_symm; + } + /* PAINT_FALLOFF_SHAPE_TUBE */ + return ss->cache->view_normal; +} + +static float frontface(const Brush *br, + const float sculpt_normal[3], + const float no[3], + const float fno[3]) +{ + if (!(br->flag & BRUSH_FRONTFACE)) { + return 1.0f; + } + + float dot; + if (no) { + dot = dot_v3v3(no, sculpt_normal); + } + else { + dot = dot_v3v3(fno, sculpt_normal); + } + return dot > 0.0f ? dot : 0.0f; +} + +#if 0 + +static bool sculpt_brush_test_cyl(SculptBrushTest *test, + float co[3], + float location[3], + const float area_no[3]) +{ + if (sculpt_brush_test_sphere_fast(test, co)) { + float t1[3], t2[3], t3[3], dist; + + sub_v3_v3v3(t1, location, co); + sub_v3_v3v3(t2, x2, location); + + cross_v3_v3v3(t3, area_no, t1); + + dist = len_v3(t3) / len_v3(t2); + + test->dist = dist; + + return true; + } + + return false; +} + +#endif + +/* ===== Sculpting ===== + */ + +static float calc_overlap(StrokeCache *cache, + const ePaintSymmetryFlags symm, + const char axis, + const float angle) +{ + float mirror[3]; + float distsq; + + flip_v3_v3(mirror, cache->true_location, symm); + + if (axis != 0) { + float mat[3][3]; + axis_angle_to_mat3_single(mat, axis, angle); + mul_m3_v3(mat, mirror); + } + + distsq = len_squared_v3v3(mirror, cache->true_location); + + if (distsq <= 4.0f * (cache->radius_squared)) { + return (2.0f * (cache->radius) - sqrtf(distsq)) / (2.0f * (cache->radius)); + } + return 0.0f; +} + +static float calc_radial_symmetry_feather(Sculpt *sd, + StrokeCache *cache, + const ePaintSymmetryFlags symm, + const char axis) +{ + float overlap = 0.0f; + + for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { + const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; + overlap += calc_overlap(cache, symm, axis, angle); + } + + return overlap; +} + +static float calc_symmetry_feather(Sculpt *sd, StrokeCache *cache) +{ + if (!(sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) { + return 1.0f; + } + float overlap; + const int symm = cache->symmetry; + + overlap = 0.0f; + for (int i = 0; i <= symm; i++) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + + overlap += calc_overlap(cache, ePaintSymmetryFlags(i), 0, 0); + + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'X'); + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Y'); + overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Z'); + } + return 1.0f / overlap; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Calculate Normal and Center + * + * Calculate geometry surrounding the brush center. + * (optionally using original coordinates). + * + * Functions are: + * - #SCULPT_calc_area_center + * - #SCULPT_calc_area_normal + * - #SCULPT_calc_area_normal_and_center + * + * \note These are all _very_ similar, when changing one, check others. + * \{ */ + +struct AreaNormalCenterTLSData { + /* 0 = towards view, 1 = flipped */ + float area_cos[2][3]; + float area_nos[2][3]; + int count_no[2]; + int count_co[2]; +}; + +static void calc_area_normal_and_center_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + AreaNormalCenterTLSData *anctd = static_cast(tls->userdata_chunk); + const bool use_area_nos = data->use_area_nos; + const bool use_area_cos = data->use_area_cos; + + PBVHVertexIter vd; + SculptUndoNode *unode = nullptr; + + bool use_original = false; + bool normal_test_r, area_test_r; + + if (ss->cache && ss->cache->original) { + unode = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + use_original = (unode->co || unode->bm_entry); + } + + SculptBrushTest normal_test; + SculptBrushTestFn sculpt_brush_normal_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &normal_test, data->brush->falloff_shape); + + /* Update the test radius to sample the normal using the normal radius of the brush. */ + if (data->brush->ob_mode == OB_MODE_SCULPT) { + float test_radius = sqrtf(normal_test.radius_squared); + test_radius *= data->brush->normal_radius_factor; + normal_test.radius = test_radius; + normal_test.radius_squared = test_radius * test_radius; + } + + SculptBrushTest area_test; + SculptBrushTestFn sculpt_brush_area_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &area_test, data->brush->falloff_shape); + + if (data->brush->ob_mode == OB_MODE_SCULPT) { + float test_radius = sqrtf(area_test.radius_squared); + /* Layer brush produces artifacts with normal and area radius */ + /* Enable area radius control only on Scrape for now */ + if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) && + data->brush->area_radius_factor > 0.0f) { + test_radius *= data->brush->area_radius_factor; + if (ss->cache && data->brush->flag2 & BRUSH_AREA_RADIUS_PRESSURE) { + test_radius *= ss->cache->pressure; + } + } + else { + test_radius *= data->brush->normal_radius_factor; + } + area_test.radius = test_radius; + area_test.radius_squared = test_radius * test_radius; + } + + /* When the mesh is edited we can't rely on original coords + * (original mesh may not even have verts in brush radius). */ + if (use_original && data->has_bm_orco) { + float(*orco_coords)[3]; + int(*orco_tris)[3]; + int orco_tris_num; + + BKE_pbvh_node_get_bm_orco_data( + data->nodes[n], &orco_tris, &orco_tris_num, &orco_coords, nullptr); + + for (int i = 0; i < orco_tris_num; i++) { + const float *co_tri[3] = { + orco_coords[orco_tris[i][0]], + orco_coords[orco_tris[i][1]], + orco_coords[orco_tris[i][2]], + }; + float co[3]; + + closest_on_tri_to_point_v3(co, normal_test.location, UNPACK3(co_tri)); + + normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); + area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); + + if (!normal_test_r && !area_test_r) { + continue; + } + + float no[3]; + int flip_index; + + normal_tri_v3(no, UNPACK3(co_tri)); + + flip_index = (dot_v3v3(ss->cache->view_normal, no) <= 0.0f); + if (use_area_cos && area_test_r) { + /* Weight the coordinates towards the center. */ + float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); + const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + + float disp[3]; + sub_v3_v3v3(disp, co, area_test.location); + mul_v3_fl(disp, 1.0f - afactor); + add_v3_v3v3(co, area_test.location, disp); + add_v3_v3(anctd->area_cos[flip_index], co); + + anctd->count_co[flip_index] += 1; + } + if (use_area_nos && normal_test_r) { + /* Weight the normals towards the center. */ + float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); + const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + mul_v3_fl(no, nfactor); + + add_v3_v3(anctd->area_nos[flip_index], no); + anctd->count_no[flip_index] += 1; + } + } + } + else { + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float co[3]; + + /* For bm_vert only. */ + float no_s[3]; + + if (use_original) { + if (unode->bm_entry) { + const float *temp_co; + const float *temp_no_s; + BM_log_original_vert_data(ss->bm_log, vd.bm_vert, &temp_co, &temp_no_s); + copy_v3_v3(co, temp_co); + copy_v3_v3(no_s, temp_no_s); + } + else { + copy_v3_v3(co, unode->co[vd.i]); + copy_v3_v3(no_s, unode->no[vd.i]); + } + } + else { + copy_v3_v3(co, vd.co); + } + + normal_test_r = sculpt_brush_normal_test_sq_fn(&normal_test, co); + area_test_r = sculpt_brush_area_test_sq_fn(&area_test, co); + + if (!normal_test_r && !area_test_r) { + continue; + } + + float no[3]; + int flip_index; + + data->any_vertex_sampled = true; + + if (use_original) { + copy_v3_v3(no, no_s); + } + else { + if (vd.no) { + copy_v3_v3(no, vd.no); + } + else { + copy_v3_v3(no, vd.fno); + } + } + + flip_index = (dot_v3v3(ss->cache ? ss->cache->view_normal : ss->cursor_view_normal, no) <= + 0.0f); + + if (use_area_cos && area_test_r) { + /* Weight the coordinates towards the center. */ + float p = 1.0f - (sqrtf(area_test.dist) / area_test.radius); + const float afactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + + float disp[3]; + sub_v3_v3v3(disp, co, area_test.location); + mul_v3_fl(disp, 1.0f - afactor); + add_v3_v3v3(co, area_test.location, disp); + + add_v3_v3(anctd->area_cos[flip_index], co); + anctd->count_co[flip_index] += 1; + } + if (use_area_nos && normal_test_r) { + /* Weight the normals towards the center. */ + float p = 1.0f - (sqrtf(normal_test.dist) / normal_test.radius); + const float nfactor = clamp_f(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f); + mul_v3_fl(no, nfactor); + + add_v3_v3(anctd->area_nos[flip_index], no); + anctd->count_no[flip_index] += 1; + } + } + BKE_pbvh_vertex_iter_end; + } +} + +static void calc_area_normal_and_center_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + AreaNormalCenterTLSData *join = static_cast(chunk_join); + AreaNormalCenterTLSData *anctd = static_cast(chunk); + + /* For flatten center. */ + add_v3_v3(join->area_cos[0], anctd->area_cos[0]); + add_v3_v3(join->area_cos[1], anctd->area_cos[1]); + + /* For area normal. */ + add_v3_v3(join->area_nos[0], anctd->area_nos[0]); + add_v3_v3(join->area_nos[1], anctd->area_nos[1]); + + /* Weights. */ + add_v2_v2_int(join->count_no, anctd->count_no); + add_v2_v2_int(join->count_co, anctd->count_co); +} + +void SCULPT_calc_area_center( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_co[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + int n; + + /* Intentionally set 'sd' to nullptr since we share logic with vertex paint. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_cos = true; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For flatten center. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { + if (anctd.count_co[n] == 0) { + continue; + } + + mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); + break; + } + + if (n == 2) { + zero_v3(r_area_co); + } + + if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { + if (ss->cache) { + copy_v3_v3(r_area_co, ss->cache->location); + } + } +} + +void SCULPT_calc_area_normal( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, r_area_no); +} + +bool SCULPT_pbvh_calc_area_normal(const Brush *brush, + Object *ob, + PBVHNode **nodes, + int totnode, + bool use_threading, + float r_area_no[3]) +{ + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + + /* Intentionally set 'sd' to nullptr since this is used for vertex paint too. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_nos = true; + data.any_vertex_sampled = false; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, use_threading, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For area normal. */ + for (int i = 0; i < ARRAY_SIZE(anctd.area_nos); i++) { + if (normalize_v3_v3(r_area_no, anctd.area_nos[i]) != 0.0f) { + break; + } + } + + return data.any_vertex_sampled; +} + +void SCULPT_calc_area_normal_and_center( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + const bool has_bm_orco = ss->bm && SCULPT_stroke_is_dynamic_topology(ss, brush); + int n; + + /* Intentionally set 'sd' to nullptr since this is used for vertex paint too. */ + SculptThreadedTaskData data{}; + data.sd = nullptr; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.totnode = totnode; + data.has_bm_orco = has_bm_orco; + data.use_area_cos = true; + data.use_area_nos = true; + + AreaNormalCenterTLSData anctd = {{{0}}}; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = calc_area_normal_and_center_reduce; + settings.userdata_chunk = &anctd; + settings.userdata_chunk_size = sizeof(AreaNormalCenterTLSData); + BLI_task_parallel_range(0, totnode, &data, calc_area_normal_and_center_task_cb, &settings); + + /* For flatten center. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_cos); n++) { + if (anctd.count_co[n] == 0) { + continue; + } + + mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]); + break; + } + + if (n == 2) { + zero_v3(r_area_co); + } + + if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) { + if (ss->cache) { + copy_v3_v3(r_area_co, ss->cache->location); + } + } + + /* For area normal. */ + for (n = 0; n < ARRAY_SIZE(anctd.area_nos); n++) { + if (normalize_v3_v3(r_area_no, anctd.area_nos[n]) != 0.0f) { + break; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Brush Utilities + * \{ */ + +/** + * Return modified brush strength. Includes the direction of the brush, positive + * values pull vertices, negative values push. Uses tablet pressure and a + * special multiplier found experimentally to scale the strength factor. + */ +static float brush_strength(const Sculpt *sd, + const StrokeCache *cache, + const float feather, + const UnifiedPaintSettings *ups, + const PaintModeSettings * /*paint_mode_settings*/) +{ + const Scene *scene = cache->vc->scene; + const Brush *brush = BKE_paint_brush((Paint *)&sd->paint); + + /* Primary strength input; square it to make lower values more sensitive. */ + const float root_alpha = BKE_brush_alpha_get(scene, brush); + const float alpha = root_alpha * root_alpha; + const float dir = (brush->flag & BRUSH_DIR_IN) ? -1.0f : 1.0f; + const float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f; + const float pen_flip = cache->pen_flip ? -1.0f : 1.0f; + const float invert = cache->invert ? -1.0f : 1.0f; + float overlap = ups->overlap_factor; + /* Spacing is integer percentage of radius, divide by 50 to get + * normalized diameter. */ + + float flip = dir * invert * pen_flip; + if (brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + flip = 1.0f; + } + + /* Pressure final value after being tweaked depending on the brush. */ + float final_pressure; + + switch (brush->sculpt_tool) { + case SCULPT_TOOL_CLAY: + final_pressure = pow4f(pressure); + overlap = (1.0f + overlap) / 2.0f; + return 0.25f * alpha * flip * final_pressure * overlap * feather; + case SCULPT_TOOL_DRAW: + case SCULPT_TOOL_DRAW_SHARP: + case SCULPT_TOOL_LAYER: + return alpha * flip * pressure * overlap * feather; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_CLOTH: + if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) { + /* Grab deform uses the same falloff as a regular grab brush. */ + return root_alpha * feather; + } + else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) { + return root_alpha * feather * pressure * overlap; + } + else if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) { + /* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over + * the same vertices. */ + return 0.1f * alpha * flip * pressure * overlap * feather; + } + else { + /* Multiply by 10 by default to get a larger range of strength depending on the size of the + * brush and object. */ + return 10.0f * alpha * flip * pressure * overlap * feather; + } + case SCULPT_TOOL_DRAW_FACE_SETS: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_SLIDE_RELAX: + return alpha * pressure * overlap * feather * 2.0f; + case SCULPT_TOOL_PAINT: + final_pressure = pressure * pressure; + return final_pressure * overlap * feather; + case SCULPT_TOOL_SMEAR: + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + return alpha * pressure * overlap * feather; + case SCULPT_TOOL_CLAY_STRIPS: + /* Clay Strips needs less strength to compensate the curve. */ + final_pressure = powf(pressure, 1.5f); + return alpha * flip * final_pressure * overlap * feather * 0.3f; + case SCULPT_TOOL_CLAY_THUMB: + final_pressure = pressure * pressure; + return alpha * flip * final_pressure * overlap * feather * 1.3f; + + case SCULPT_TOOL_MASK: + overlap = (1.0f + overlap) / 2.0f; + switch ((BrushMaskTool)brush->mask_tool) { + case BRUSH_MASK_DRAW: + return alpha * flip * pressure * overlap * feather; + case BRUSH_MASK_SMOOTH: + return alpha * pressure * feather; + } + BLI_assert_msg(0, "Not supposed to happen"); + return 0.0f; + + case SCULPT_TOOL_CREASE: + case SCULPT_TOOL_BLOB: + return alpha * flip * pressure * overlap * feather; + + case SCULPT_TOOL_INFLATE: + if (flip > 0.0f) { + return 0.250f * alpha * flip * pressure * overlap * feather; + } + else { + return 0.125f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + overlap = (1.0f + overlap) / 2.0f; + return alpha * flip * pressure * overlap * feather; + + case SCULPT_TOOL_FILL: + case SCULPT_TOOL_SCRAPE: + case SCULPT_TOOL_FLATTEN: + if (flip > 0.0f) { + overlap = (1.0f + overlap) / 2.0f; + return alpha * flip * pressure * overlap * feather; + } + else { + /* Reduce strength for DEEPEN, PEAKS, and CONTRAST. */ + return 0.5f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_SMOOTH: + return flip * alpha * pressure * feather; + + case SCULPT_TOOL_PINCH: + if (flip > 0.0f) { + return alpha * flip * pressure * overlap * feather; + } + else { + return 0.25f * alpha * flip * pressure * overlap * feather; + } + + case SCULPT_TOOL_NUDGE: + overlap = (1.0f + overlap) / 2.0f; + return alpha * pressure * overlap * feather; + + case SCULPT_TOOL_THUMB: + return alpha * pressure * feather; + + case SCULPT_TOOL_SNAKE_HOOK: + return root_alpha * feather; + + case SCULPT_TOOL_GRAB: + return root_alpha * feather; + + case SCULPT_TOOL_ROTATE: + return alpha * pressure * feather; + + case SCULPT_TOOL_ELASTIC_DEFORM: + case SCULPT_TOOL_POSE: + case SCULPT_TOOL_BOUNDARY: + return root_alpha * feather; + + default: + return 0.0f; + } +} + +float SCULPT_brush_strength_factor(SculptSession *ss, + const Brush *br, + const float brush_point[3], + float len, + const float vno[3], + const float fno[3], + float mask, + const PBVHVertRef vertex, + const int thread_id, + AutomaskingNodeData *automask_data) +{ + StrokeCache *cache = ss->cache; + const Scene *scene = cache->vc->scene; + const MTex *mtex = BKE_brush_mask_texture_get(br, OB_MODE_SCULPT); + float avg = 1.0f; + float rgba[4]; + float point[3]; + + sub_v3_v3v3(point, brush_point, cache->plane_offset); + + if (!mtex->tex) { + avg = 1.0f; + } + else if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { + /* Get strength by feeding the vertex location directly into a texture. */ + avg = BKE_brush_sample_tex_3d(scene, br, mtex, point, rgba, 0, ss->tex_pool); + } + else { + float symm_point[3], point_2d[2]; + /* Quite warnings. */ + float x = 0.0f, y = 0.0f; + + /* If the active area is being applied for symmetry, flip it + * across the symmetry axis and rotate it back to the original + * position in order to project it. This insures that the + * brush texture will be oriented correctly. */ + if (cache->radial_symmetry_pass) { + mul_m4_v3(cache->symm_rot_mat_inv, point); + } + flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); + + ED_view3d_project_float_v2_m4(cache->vc->region, symm_point, point_2d, cache->projection_mat); + + /* Still no symmetry supported for other paint modes. + * Sculpt does it DIY. */ + if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) { + /* Similar to fixed mode, but projects from brush angle + * rather than view direction. */ + + mul_m4_v3(cache->brush_local_mat, symm_point); + + x = symm_point[0]; + y = symm_point[1]; + + x *= mtex->size[0]; + y *= mtex->size[1]; + + x += mtex->ofs[0]; + y += mtex->ofs[1]; + + avg = paint_get_tex_pixel(mtex, x, y, ss->tex_pool, thread_id); + + avg += br->texture_sample_bias; + } + else { + const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f}; + avg = BKE_brush_sample_tex_3d(scene, br, mtex, point_3d, rgba, 0, ss->tex_pool); + } + } + + /* Hardness. */ + float final_len = len; + const float hardness = cache->paint_brush.hardness; + float p = len / cache->radius; + if (p < hardness) { + final_len = 0.0f; + } + else if (hardness == 1.0f) { + final_len = cache->radius; + } + else { + p = (p - hardness) / (1.0f - hardness); + final_len = p * cache->radius; + } + + /* Falloff curve. */ + avg *= BKE_brush_curve_strength(br, final_len, cache->radius); + avg *= frontface(br, cache->view_normal, vno, fno); + + /* Paint mask. */ + avg *= 1.0f - mask; + + /* Auto-masking. */ + avg *= SCULPT_automasking_factor_get(cache->automasking, ss, vertex, automask_data); + + return avg; +} + +bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v) +{ + SculptSearchSphereData *data = static_cast(data_v); + const float *center; + float nearest[3]; + if (data->center) { + center = data->center; + } + else { + center = data->ss->cache ? data->ss->cache->location : data->ss->cursor_location; + } + float t[3], bb_min[3], bb_max[3]; + + if (data->ignore_fully_ineffective) { + if (BKE_pbvh_node_fully_hidden_get(node)) { + return false; + } + if (BKE_pbvh_node_fully_masked_get(node)) { + return false; + } + } + + if (data->original) { + BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); + } + else { + BKE_pbvh_node_get_BB(node, bb_min, bb_max); + } + + for (int i = 0; i < 3; i++) { + if (bb_min[i] > center[i]) { + nearest[i] = bb_min[i]; + } + else if (bb_max[i] < center[i]) { + nearest[i] = bb_max[i]; + } + else { + nearest[i] = center[i]; + } + } + + sub_v3_v3v3(t, center, nearest); + + return len_squared_v3(t) < data->radius_squared; +} + +bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v) +{ + SculptSearchCircleData *data = static_cast(data_v); + float bb_min[3], bb_max[3]; + + if (data->ignore_fully_ineffective) { + if (BKE_pbvh_node_fully_masked_get(node)) { + return false; + } + } + + if (data->original) { + BKE_pbvh_node_get_original_BB(node, bb_min, bb_max); + } + else { + BKE_pbvh_node_get_BB(node, bb_min, bb_min); + } + + float dummy_co[3], dummy_depth; + const float dist_sq = dist_squared_ray_to_aabb_v3( + data->dist_ray_to_aabb_precalc, bb_min, bb_max, dummy_co, &dummy_depth); + + /* Seems like debug code. + * Maybe this function can just return true if the node is not fully masked. */ + return dist_sq < data->radius_squared || true; +} + +void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3]) +{ + for (int i = 0; i < 3; i++) { + if (sd->flags & (SCULPT_LOCK_X << i)) { + continue; + } + + bool do_clip = false; + float co_clip[3]; + if (ss->cache && (ss->cache->flag & (CLIP_X << i))) { + /* Take possible mirror object into account. */ + mul_v3_m4v3(co_clip, ss->cache->clip_mirror_mtx, co); + + if (fabsf(co_clip[i]) <= ss->cache->clip_tolerance[i]) { + co_clip[i] = 0.0f; + float imtx[4][4]; + invert_m4_m4(imtx, ss->cache->clip_mirror_mtx); + mul_m4_v3(imtx, co_clip); + do_clip = true; + } + } + + if (do_clip) { + co[i] = co_clip[i]; + } + else { + co[i] = val[i]; + } + } +} + +static PBVHNode **sculpt_pbvh_gather_cursor_update(Object *ob, + Sculpt *sd, + bool use_original, + int *r_totnode) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cursor_radius; + data.original = use_original; + data.ignore_fully_ineffective = false; + data.center = nullptr; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); + return nodes; +} + +static PBVHNode **sculpt_pbvh_gather_generic(Object *ob, + Sculpt *sd, + const Brush *brush, + bool use_original, + float radius_scale, + int *r_totnode) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + + /* Build a list of all nodes that are potentially within the cursor or brush's area of influence. + */ + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = square_f(ss->cache->radius * radius_scale); + data.original = use_original; + data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK; + data.center = nullptr; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); + } + else { + DistRayAABB_Precalc dist_ray_to_aabb_precalc; + dist_squared_ray_to_aabb_v3_precalc( + &dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); + SculptSearchCircleData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cache ? square_f(ss->cache->radius * radius_scale) : + ss->cursor_radius; + data.original = use_original; + data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc; + data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode); + } + return nodes; +} + +/* Calculate primary direction of movement for many brushes. */ +static void calc_sculpt_normal( + Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3]) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + const SculptSession *ss = ob->sculpt; + + 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(sd, ob, nodes, totnode, r_area_no); + break; + + default: + break; + } +} + +static void update_sculpt_normal(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + const Brush *brush = BKE_paint_brush(&sd->paint); + StrokeCache *cache = ob->sculpt->cache; + /* Grab brush does not update the sculpt normal during a stroke. */ + const bool update_normal = + !(brush->flag & BRUSH_ORIGINAL_NORMAL) && !(brush->sculpt_tool == SCULPT_TOOL_GRAB) && + !(brush->sculpt_tool == SCULPT_TOOL_THUMB && !(brush->flag & BRUSH_ANCHORED)) && + !(brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) && + !(brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK && cache->normal_weight > 0.0f); + + if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && + (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal)) { + calc_sculpt_normal(sd, ob, nodes, totnode, cache->sculpt_normal); + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->sculpt_normal, cache->sculpt_normal, cache->view_normal); + normalize_v3(cache->sculpt_normal); + } + copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); + } + else { + copy_v3_v3(cache->sculpt_normal_symm, cache->sculpt_normal); + flip_v3(cache->sculpt_normal_symm, cache->mirror_symmetry_pass); + mul_m4_v3(cache->symm_rot_mat, cache->sculpt_normal_symm); + } +} + +static void calc_local_y(ViewContext *vc, const float center[3], float y[3]) +{ + Object *ob = vc->obact; + float loc[3]; + const float xy_delta[2] = {0.0f, 1.0f}; + + mul_v3_m4v3(loc, ob->world_to_object, center); + const float zfac = ED_view3d_calc_zfac(vc->rv3d, loc); + + ED_view3d_win_to_delta(vc->region, xy_delta, zfac, y); + normalize_v3(y); + + add_v3_v3(y, ob->loc); + mul_m4_v3(ob->world_to_object, y); +} + +static void calc_brush_local_mat(const Brush *brush, Object *ob, float local_mat[4][4]) +{ + const StrokeCache *cache = ob->sculpt->cache; + float tmat[4][4]; + float mat[4][4]; + float scale[4][4]; + float angle, v[3]; + float up[3]; + + /* Ensure `ob->world_to_object` is up to date. */ + invert_m4_m4(ob->world_to_object, ob->object_to_world); + + /* Initialize last column of matrix. */ + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + + /* Get view's up vector in object-space. */ + calc_local_y(cache->vc, cache->location, up); + + /* Calculate the X axis of the local matrix. */ + cross_v3_v3v3(v, up, cache->sculpt_normal); + /* Apply rotation (user angle, rake, etc.) to X axis. */ + angle = brush->mtex.rot - cache->special_rotation; + rotate_v3_v3v3fl(mat[0], v, cache->sculpt_normal, angle); + + /* Get other axes. */ + cross_v3_v3v3(mat[1], cache->sculpt_normal, mat[0]); + copy_v3_v3(mat[2], cache->sculpt_normal); + + /* Set location. */ + copy_v3_v3(mat[3], cache->location); + + /* Scale by brush radius. */ + normalize_m4(mat); + scale_m4_fl(scale, cache->radius); + mul_m4_m4m4(tmat, mat, scale); + + /* Return inverse (for converting from model-space coords to local area coords). */ + invert_m4_m4(local_mat, tmat); +} + +#define SCULPT_TILT_SENSITIVITY 0.7f +void SCULPT_tilt_apply_to_normal(float r_normal[3], StrokeCache *cache, const float tilt_strength) +{ + if (!U.experimental.use_sculpt_tools_tilt) { + return; + } + const float rot_max = M_PI_2 * tilt_strength * SCULPT_TILT_SENSITIVITY; + mul_v3_mat3_m4v3(r_normal, cache->vc->obact->object_to_world, r_normal); + float normal_tilt_y[3]; + rotate_v3_v3v3fl(normal_tilt_y, r_normal, cache->vc->rv3d->viewinv[0], cache->y_tilt * rot_max); + float normal_tilt_xy[3]; + rotate_v3_v3v3fl( + normal_tilt_xy, normal_tilt_y, cache->vc->rv3d->viewinv[1], cache->x_tilt * rot_max); + mul_v3_mat3_m4v3(r_normal, cache->vc->obact->world_to_object, normal_tilt_xy); + normalize_v3(r_normal); +} + +void SCULPT_tilt_effective_normal_get(const SculptSession *ss, const Brush *brush, float r_no[3]) +{ + copy_v3_v3(r_no, ss->cache->sculpt_normal_symm); + SCULPT_tilt_apply_to_normal(r_no, ss->cache, brush->tilt_strength_factor); +} + +static void update_brush_local_mat(Sculpt *sd, Object *ob) +{ + StrokeCache *cache = ob->sculpt->cache; + + if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) { + calc_brush_local_mat(BKE_paint_brush(&sd->paint), ob, cache->brush_local_mat); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Texture painting + * \{ */ + +static bool sculpt_needs_pbvh_pixels(PaintModeSettings *paint_mode_settings, + const Brush *brush, + Object *ob) +{ + if (brush->sculpt_tool == SCULPT_TOOL_PAINT && U.experimental.use_sculpt_texture_paint) { + Image *image; + ImageUser *image_user; + return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user); + } + + return false; +} + +static void sculpt_pbvh_update_pixels(PaintModeSettings *paint_mode_settings, + SculptSession *ss, + Object *ob) +{ + BLI_assert(ob->type == OB_MESH); + Mesh *mesh = (Mesh *)ob->data; + + Image *image; + ImageUser *image_user; + if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) { + return; + } + + BKE_pbvh_build_pixels(ss->pbvh, mesh, image, image_user); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Generic Brush Plane & Symmetry Utilities + * \{ */ + +struct SculptRaycastData { + SculptSession *ss; + const float *ray_start; + const float *ray_normal; + bool hit; + float depth; + bool original; + + PBVHVertRef active_vertex; + float *face_normal; + + int active_face_grid_index; + + IsectRayPrecalc isect_precalc; +}; + +struct SculptFindNearestToRayData { + SculptSession *ss; + const float *ray_start, *ray_normal; + bool hit; + float depth; + float dist_sq_to_ray; + bool original; +}; + +ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3]) +{ + ePaintSymmetryAreas symm_area = ePaintSymmetryAreas(PAINT_SYMM_AREA_DEFAULT); + if (co[0] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_X; + } + if (co[1] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_Y; + } + if (co[2] < 0.0f) { + symm_area |= PAINT_SYMM_AREA_Z; + } + return symm_area; +} + +void SCULPT_flip_v3_by_symm_area(float v[3], + const ePaintSymmetryFlags symm, + const ePaintSymmetryAreas symmarea, + const float pivot[3]) +{ + for (int i = 0; i < 3; i++) { + ePaintSymmetryFlags symm_it = ePaintSymmetryFlags(1 << i); + if (!(symm & symm_it)) { + continue; + } + if (symmarea & symm_it) { + flip_v3(v, symm_it); + } + if (pivot[i] < 0.0f) { + flip_v3(v, symm_it); + } + } +} + +void SCULPT_flip_quat_by_symm_area(float quat[4], + const ePaintSymmetryFlags symm, + const ePaintSymmetryAreas symmarea, + const float pivot[3]) +{ + for (int i = 0; i < 3; i++) { + ePaintSymmetryFlags symm_it = ePaintSymmetryFlags(1 << i); + if (!(symm & symm_it)) { + continue; + } + if (symmarea & symm_it) { + flip_qt(quat, symm_it); + } + if (pivot[i] < 0.0f) { + flip_qt(quat, symm_it); + } + } +} + +void SCULPT_calc_brush_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); + + zero_v3(r_area_co); + zero_v3(r_area_no); + + 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); + } +} + +int SCULPT_plane_trim(const StrokeCache *cache, const Brush *brush, const float val[3]) +{ + return (!(brush->flag & BRUSH_PLANE_TRIM) || + (dot_v3v3(val, val) <= cache->radius_squared * cache->plane_trim_squared)); +} + +int SCULPT_plane_point_side(const float co[3], const float plane[4]) +{ + float d = plane_point_side_v3(plane, co); + return d <= 0.0f; +} + +float SCULPT_brush_plane_offset_get(Sculpt *sd, SculptSession *ss) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + float rv = brush->plane_offset; + + if (brush->flag & BRUSH_OFFSET_PRESSURE) { + rv *= ss->cache->pressure; + } + + return rv; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Gravity Brush + * \{ */ + +static void do_gravity_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + 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; + } + 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.vertex, + thread_id, + nullptr); + + mul_v3_v3fl(proxy[vd.i], offset, fade); + + if (vd.mvert) { + BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex); + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_gravity(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float bstrength) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + float offset[3]; + float gravity_vector[3]; + + mul_v3_v3fl(gravity_vector, ss->cache->gravity_direction, -ss->cache->radius_squared); + + /* Offset with as much as possible factored in already. */ + mul_v3_v3v3(offset, gravity_vector, ss->cache->scale); + mul_v3_fl(offset, bstrength); + + /* Threaded loop over nodes. */ + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.offset = offset; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_gravity_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Sculpt Brush Utilities + * \{ */ + +void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3]) +{ + Mesh *me = (Mesh *)ob->data; + float(*ofs)[3] = nullptr; + int a; + const int kb_act_idx = ob->shapenr - 1; + + /* For relative keys editing of base should update other keys. */ + if (BKE_keyblock_is_basis(me->key, kb_act_idx)) { + ofs = BKE_keyblock_convert_to_vertcos(ob, kb); + + /* Calculate key coord offsets (from previous location). */ + for (a = 0; a < me->totvert; a++) { + sub_v3_v3v3(ofs[a], vertCos[a], ofs[a]); + } + + /* Apply offsets on other keys. */ + LISTBASE_FOREACH (KeyBlock *, currkey, &me->key->block) { + if ((currkey != kb) && (currkey->relative == kb_act_idx)) { + BKE_keyblock_update_from_offset(ob, currkey, ofs); + } + } + + MEM_freeN(ofs); + } + + /* Modifying of basis key should update mesh. */ + if (kb == me->key->refkey) { + MVert *verts = BKE_mesh_verts_for_write(me); + + for (a = 0; a < me->totvert; a++) { + copy_v3_v3(verts[a].co, vertCos[a]); + } + BKE_mesh_tag_coords_changed(me); + } + + /* Apply new coords on active key block, no need to re-allocate kb->data here! */ + BKE_keyblock_update_from_vertcos(ob, kb, vertCos); +} + +/* NOTE: we do the topology update before any brush actions to avoid + * issues with the proxies. The size of the proxy can't change, so + * topology must be updated first. */ +static void sculpt_topology_update(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings * /*ups*/, + PaintModeSettings * /*paint_mode_settings*/) +{ + SculptSession *ss = ob->sculpt; + + int n, totnode; + /* Build a list of all nodes that are potentially within the brush's area of influence. */ + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : + ss->cache->original; + const float radius_scale = 1.25f; + PBVHNode **nodes = sculpt_pbvh_gather_generic( + ob, sd, brush, use_original, radius_scale, &totnode); + + /* Only act if some verts are inside the brush area. */ + if (totnode == 0) { + return; + } + + /* Free index based vertex info as it will become invalid after modifying the topology during the + * stroke. */ + MEM_SAFE_FREE(ss->vertex_info.boundary); + MEM_SAFE_FREE(ss->vertex_info.connected_component); + + PBVHTopologyUpdateMode mode = PBVHTopologyUpdateMode(0); + float location[3]; + + if (!(sd->flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) { + if (sd->flags & SCULPT_DYNTOPO_SUBDIVIDE) { + mode |= PBVH_Subdivide; + } + + if ((sd->flags & SCULPT_DYNTOPO_COLLAPSE) || (brush->sculpt_tool == SCULPT_TOOL_SIMPLIFY)) { + mode |= PBVH_Collapse; + } + } + + for (n = 0; n < totnode; n++) { + SCULPT_undo_push_node(ob, + nodes[n], + brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK : + SCULPT_UNDO_COORDS); + BKE_pbvh_node_mark_update(nodes[n]); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_node_mark_topology_update(nodes[n]); + BKE_pbvh_bmesh_node_save_orig(ss->bm, ss->bm_log, nodes[n], false); + } + } + + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_bmesh_update_topology(ss->pbvh, + mode, + ss->cache->location, + ss->cache->view_normal, + ss->cache->radius, + (brush->flag & BRUSH_FRONTFACE) != 0, + (brush->falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE)); + } + + MEM_SAFE_FREE(nodes); + + /* Update average stroke position. */ + copy_v3_v3(location, ss->cache->true_location); + mul_m4_v3(ob->object_to_world, location); +} + +static void do_brush_action_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + + bool need_coords = ss->cache->supports_gravity; + + /* Face Sets modifications do a single undo push */ + if (data->brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) { + BKE_pbvh_node_mark_redraw(data->nodes[n]); + /* Draw face sets in smooth mode moves the vertices. */ + if (ss->cache->alt_smooth) { + need_coords = true; + } + } + else if (data->brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); + BKE_pbvh_node_mark_update_mask(data->nodes[n]); + } + else if (SCULPT_tool_is_paint(data->brush->sculpt_tool)) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); + BKE_pbvh_node_mark_update_color(data->nodes[n]); + } + else { + need_coords = true; + } + + if (need_coords) { + SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); + BKE_pbvh_node_mark_update(data->nodes[n]); + } +} + +static void do_brush_action(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) +{ + SculptSession *ss = ob->sculpt; + int totnode; + PBVHNode **nodes; + + /* Check for unsupported features. */ + PBVHType type = BKE_pbvh_type(ss->pbvh); + + if (SCULPT_tool_is_paint(brush->sculpt_tool) && SCULPT_has_loop_colors(ob)) { + if (type != PBVH_FACES) { + return; + } + + BKE_pbvh_ensure_node_loops(ss->pbvh); + } + + /* Build a list of all nodes that are potentially within the brush's area of influence */ + + if (SCULPT_tool_needs_all_pbvh_nodes(brush)) { + /* These brushes need to update all nodes as they are not constrained by the brush radius */ + BKE_pbvh_search_gather(ss->pbvh, nullptr, nullptr, &nodes, &totnode); + } + else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + nodes = SCULPT_cloth_brush_affected_nodes_gather(ss, brush, &totnode); + } + else { + const bool use_original = sculpt_tool_needs_original(brush->sculpt_tool) ? true : + ss->cache->original; + float radius_scale = 1.0f; + + /* Corners of square brushes can go outside the brush radius. */ + if (sculpt_tool_has_cube_tip(brush->sculpt_tool)) { + radius_scale = M_SQRT2; + } + + /* With these options enabled not all required nodes are inside the original brush radius, so + * the brush can produce artifacts in some situations. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW && brush->flag & BRUSH_ORIGINAL_NORMAL) { + radius_scale = 2.0f; + } + nodes = sculpt_pbvh_gather_generic(ob, sd, brush, use_original, radius_scale, &totnode); + } + const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob); + if (use_pixels) { + sculpt_pbvh_update_pixels(paint_mode_settings, ss, ob); + } + + /* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the + * vertices and uses regular coords undo. */ + /* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type + * and the number of nodes under the brush influence. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS && + SCULPT_stroke_is_first_brush_step(ss->cache) && !ss->cache->alt_smooth) { + + /* Dynamic-topology does not support Face Sets data, so it can't store/restore it from undo. */ + /* TODO(pablodp606): This check should be done in the undo code and not here, but the rest of + * the sculpt code is not checking for unsupported undo types that may return a null node. */ + if (BKE_pbvh_type(ss->pbvh) != PBVH_BMESH) { + SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_FACE_SETS); + } + + if (ss->cache->invert) { + /* When inverting the brush, pick the paint face mask ID from the mesh. */ + ss->cache->paint_face_set = SCULPT_active_face_set_get(ss); + } + else { + /* By default create a new Face Sets. */ + ss->cache->paint_face_set = SCULPT_face_set_next_available_get(ss); + } + } + + /* For anchored brushes with spherical falloff, we start off with zero radius, thus we have no + * PBVH nodes on the first brush step. */ + if (totnode || + ((brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) && (brush->flag & BRUSH_ANCHORED))) { + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { + /* Initialize auto-masking cache. */ + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); + ss->last_automasking_settings_hash = SCULPT_automasking_settings_hash( + ob, ss->cache->automasking); + } + /* Initialize surface smooth cache. */ + if ((brush->sculpt_tool == SCULPT_TOOL_SMOOTH) && + (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE)) { + BLI_assert(ss->cache->surface_smooth_laplacian_disp == nullptr); + ss->cache->surface_smooth_laplacian_disp = static_cast( + MEM_callocN(sizeof(float[3]) * SCULPT_vertex_count_get(ss), "HC smooth laplacian b")); + } + } + } + + /* Only act if some verts are inside the brush area. */ + if (totnode == 0) { + return; + } + float location[3]; + + if (!use_pixels) { + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.brush = brush; + task_data.nodes = nodes; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &task_data, do_brush_action_task_cb, &settings); + } + + if (sculpt_brush_needs_normal(ss, sd, brush)) { + update_sculpt_normal(sd, ob, nodes, totnode); + } + + if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA) { + update_brush_local_mat(sd, ob); + } + + if (brush->sculpt_tool == SCULPT_TOOL_POSE && SCULPT_stroke_is_first_brush_step(ss->cache)) { + 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( + ob, 1.0f, 0.0f, 0.0f, false, true); + SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim); + } + SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim); + SCULPT_cloth_brush_ensure_nodes_constraints( + sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX); + } + + bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN; + + /* Apply one type of brush action. */ + switch (brush->sculpt_tool) { + case SCULPT_TOOL_DRAW: + SCULPT_do_draw_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SMOOTH: + if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) { + SCULPT_do_smooth_brush(sd, ob, nodes, totnode); + } + else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { + SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_CREASE: + SCULPT_do_crease_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_BLOB: + SCULPT_do_crease_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_PINCH: + SCULPT_do_pinch_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_INFLATE: + SCULPT_do_inflate_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_GRAB: + SCULPT_do_grab_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_ROTATE: + SCULPT_do_rotate_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SNAKE_HOOK: + SCULPT_do_snake_hook_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_NUDGE: + SCULPT_do_nudge_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_THUMB: + SCULPT_do_thumb_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_LAYER: + SCULPT_do_layer_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_FLATTEN: + SCULPT_do_flatten_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLAY: + SCULPT_do_clay_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLAY_STRIPS: + SCULPT_do_clay_strips_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLAY_THUMB: + SCULPT_do_clay_thumb_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_FILL: + if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); + } + else { + SCULPT_do_fill_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_SCRAPE: + if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { + SCULPT_do_fill_brush(sd, ob, nodes, totnode); + } + else { + SCULPT_do_scrape_brush(sd, ob, nodes, totnode); + } + break; + case SCULPT_TOOL_MASK: + SCULPT_do_mask_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_POSE: + SCULPT_do_pose_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DRAW_SHARP: + SCULPT_do_draw_sharp_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_ELASTIC_DEFORM: + SCULPT_do_elastic_deform_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SLIDE_RELAX: + SCULPT_do_slide_relax_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_BOUNDARY: + SCULPT_do_boundary_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_CLOTH: + SCULPT_do_cloth_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DRAW_FACE_SETS: + SCULPT_do_draw_face_sets_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + SCULPT_do_displacement_eraser_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + SCULPT_do_displacement_smear_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_PAINT: + SCULPT_do_paint_brush(paint_mode_settings, sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SMEAR: + SCULPT_do_smear_brush(sd, ob, nodes, totnode); + break; + } + + if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) && + brush->autosmooth_factor > 0) { + if (brush->flag & BRUSH_INVERSE_SMOOTH_PRESSURE) { + SCULPT_smooth( + sd, ob, nodes, totnode, brush->autosmooth_factor * (1.0f - ss->cache->pressure), false); + } + else { + SCULPT_smooth(sd, ob, nodes, totnode, brush->autosmooth_factor, false); + } + } + + if (sculpt_brush_use_topology_rake(ss, brush)) { + SCULPT_bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); + } + + if (!SCULPT_tool_can_reuse_automask(brush->sculpt_tool) || + (ss->cache->supports_gravity && sd->gravity_factor > 0.0f)) { + /* Clear cavity mask cache. */ + ss->last_automasking_settings_hash = 0; + } + + /* The cloth brush adds the gravity as a regular force and it is processed in the solver. */ + if (ss->cache->supports_gravity && !ELEM(brush->sculpt_tool, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_BOUNDARY)) { + 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_sim_activate_nodes(ss->cache->cloth_sim, nodes, totnode); + SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode); + } + } + + MEM_SAFE_FREE(nodes); + + /* Update average stroke position. */ + copy_v3_v3(location, ss->cache->true_location); + mul_m4_v3(ob->object_to_world, location); + + add_v3_v3(ups->average_stroke_accum, location); + ups->average_stroke_counter++; + /* Update last stroke position. */ + ups->last_stroke_valid = true; +} + +/* Flush displacement from deformed PBVH vertex to original mesh. */ +static void sculpt_flush_pbvhvert_deform(Object *ob, PBVHVertexIter *vd) +{ + SculptSession *ss = ob->sculpt; + Mesh *me = static_cast(ob->data); + float disp[3], newco[3]; + int index = vd->vert_indices[vd->i]; + + sub_v3_v3v3(disp, vd->co, ss->deform_cos[index]); + mul_m3_v3(ss->deform_imats[index], disp); + add_v3_v3v3(newco, disp, ss->orig_cos[index]); + + copy_v3_v3(ss->deform_cos[index], vd->co); + copy_v3_v3(ss->orig_cos[index], newco); + + MVert *verts = BKE_mesh_verts_for_write(me); + if (!ss->shapekey_active) { + copy_v3_v3(verts[index].co, newco); + } +} + +static void sculpt_combine_proxies_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + Object *ob = data->ob; + const bool use_orco = data->use_proxies_orco; + + PBVHVertexIter vd; + PBVHProxyNode *proxies; + int proxy_count; + float(*orco)[3] = nullptr; + + if (use_orco && !ss->bm) { + orco = SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS)->co; + } + + BKE_pbvh_node_get_proxies(data->nodes[n], &proxies, &proxy_count); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float val[3]; + + if (use_orco) { + if (ss->bm) { + copy_v3_v3(val, BM_log_original_vert_co(ss->bm_log, vd.bm_vert)); + } + else { + copy_v3_v3(val, orco[vd.i]); + } + } + else { + copy_v3_v3(val, vd.co); + } + + for (int p = 0; p < proxy_count; p++) { + add_v3_v3(val, proxies[p].co[vd.i]); + } + + SCULPT_clip(sd, ss, vd.co, val); + + if (ss->deform_modifiers_active) { + sculpt_flush_pbvhvert_deform(ob, &vd); + } + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_free_proxies(data->nodes[n]); +} + +static void sculpt_combine_proxies(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + PBVHNode **nodes; + int totnode; + + if (!ss->cache->supports_gravity && sculpt_tool_is_proxy_used(brush->sculpt_tool)) { + /* First line is tools that don't support proxies. */ + return; + } + + /* First line is tools that don't support proxies. */ + const bool use_orco = ELEM(brush->sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_POSE); + + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.use_proxies_orco = use_orco; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); + MEM_SAFE_FREE(nodes); +} + +void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes; + int totnode; + + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.nodes = nodes; + data.use_proxies_orco = false; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); + + MEM_SAFE_FREE(nodes); +} + +/** + * Copy the modified vertices from the #PBVH to the active key. + */ +static void sculpt_update_keyblock(Object *ob) +{ + SculptSession *ss = ob->sculpt; + float(*vertCos)[3]; + + /* Key-block update happens after handling deformation caused by modifiers, + * so ss->orig_cos would be updated with new stroke. */ + if (ss->orig_cos) { + vertCos = ss->orig_cos; + } + else { + vertCos = BKE_pbvh_vert_coords_alloc(ss->pbvh); + } + + if (!vertCos) { + return; + } + + SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); + + if (vertCos != ss->orig_cos) { + MEM_freeN(vertCos); + } +} + +static void SCULPT_flush_stroke_deform_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + Object *ob = data->ob; + float(*vertCos)[3] = data->vertCos; + + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + sculpt_flush_pbvhvert_deform(ob, &vd); + + if (!vertCos) { + continue; + } + + int index = vd.vert_indices[vd.i]; + copy_v3_v3(vertCos[index], ss->orig_cos[index]); + } + BKE_pbvh_vertex_iter_end; +} + +void SCULPT_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (is_proxy_used && ss->deform_modifiers_active) { + /* This brushes aren't using proxies, so sculpt_combine_proxies() wouldn't propagate needed + * deformation to original base. */ + + int totnode; + Mesh *me = (Mesh *)ob->data; + PBVHNode **nodes; + float(*vertCos)[3] = nullptr; + + if (ss->shapekey_active) { + vertCos = static_cast( + MEM_mallocN(sizeof(*vertCos) * me->totvert, "flushStrokeDeofrm keyVerts")); + + /* Mesh could have isolated verts which wouldn't be in BVH, to deal with this we copy old + * coordinates over new ones and then update coordinates for all vertices from BVH. */ + memcpy(vertCos, ss->orig_cos, sizeof(*vertCos) * me->totvert); + } + + BKE_pbvh_search_gather(ss->pbvh, nullptr, nullptr, &nodes, &totnode); + + SculptThreadedTaskData data{}; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.vertCos = vertCos; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, SCULPT_flush_stroke_deform_task_cb, &settings); + + if (vertCos) { + SCULPT_vertcos_to_key(ob, ss->shapekey_active, vertCos); + MEM_freeN(vertCos); + } + + MEM_SAFE_FREE(nodes); + } + else if (ss->shapekey_active) { + sculpt_update_keyblock(ob); + } +} + +void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, + const ePaintSymmetryFlags symm, + const char axis, + const float angle) +{ + flip_v3_v3(cache->location, cache->true_location, symm); + flip_v3_v3(cache->last_location, cache->true_last_location, symm); + flip_v3_v3(cache->grab_delta_symmetry, cache->grab_delta, symm); + flip_v3_v3(cache->view_normal, cache->true_view_normal, symm); + + flip_v3_v3(cache->initial_location, cache->true_initial_location, symm); + flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm); + + /* XXX This reduces the length of the grab delta if it approaches the line of symmetry + * XXX However, a different approach appears to be needed. */ +#if 0 + if (sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER) { + float frac = 1.0f / max_overlap_count(sd); + float reduce = (feather - frac) / (1.0f - frac); + + printf("feather: %f frac: %f reduce: %f\n", feather, frac, reduce); + + if (frac < 1.0f) { + mul_v3_fl(cache->grab_delta_symmetry, reduce); + } + } +#endif + + unit_m4(cache->symm_rot_mat); + unit_m4(cache->symm_rot_mat_inv); + zero_v3(cache->plane_offset); + + /* Expects XYZ. */ + if (axis) { + rotate_m4(cache->symm_rot_mat, axis, angle); + rotate_m4(cache->symm_rot_mat_inv, axis, -angle); + } + + mul_m4_v3(cache->symm_rot_mat, cache->location); + mul_m4_v3(cache->symm_rot_mat, cache->grab_delta_symmetry); + + if (cache->supports_gravity) { + flip_v3_v3(cache->gravity_direction, cache->true_gravity_direction, symm); + mul_m4_v3(cache->symm_rot_mat, cache->gravity_direction); + } + + if (cache->is_rake_rotation_valid) { + flip_qt_qt(cache->rake_rotation_symmetry, cache->rake_rotation, symm); + } +} + +using BrushActionFunc = void (*)(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings); + +static void do_tiled(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action) +{ + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const float radius = cache->radius; + const BoundBox *bb = BKE_object_boundbox_get(ob); + const float *bbMin = bb->vec[0]; + const float *bbMax = bb->vec[6]; + const float *step = sd->paint.tile_offset; + + /* These are integer locations, for real location: multiply with step and add orgLoc. + * So 0,0,0 is at orgLoc. */ + int start[3]; + int end[3]; + int cur[3]; + + /* Position of the "prototype" stroke for tiling. */ + float orgLoc[3]; + float original_initial_location[3]; + copy_v3_v3(orgLoc, cache->location); + copy_v3_v3(original_initial_location, cache->initial_location); + + for (int dim = 0; dim < 3; dim++) { + if ((sd->paint.symmetry_flags & (PAINT_TILE_X << dim)) && step[dim] > 0) { + start[dim] = (bbMin[dim] - orgLoc[dim] - radius) / step[dim]; + end[dim] = (bbMax[dim] - orgLoc[dim] + radius) / step[dim]; + } + else { + start[dim] = end[dim] = 0; + } + } + + /* First do the "un-tiled" position to initialize the stroke for this location. */ + cache->tile_pass = 0; + action(sd, ob, brush, ups, paint_mode_settings); + + /* Now do it for all the tiles. */ + copy_v3_v3_int(cur, start); + for (cur[0] = start[0]; cur[0] <= end[0]; cur[0]++) { + for (cur[1] = start[1]; cur[1] <= end[1]; cur[1]++) { + for (cur[2] = start[2]; cur[2] <= end[2]; cur[2]++) { + if (!cur[0] && !cur[1] && !cur[2]) { + /* Skip tile at orgLoc, this was already handled before all others. */ + continue; + } + + ++cache->tile_pass; + + for (int dim = 0; dim < 3; dim++) { + cache->location[dim] = cur[dim] * step[dim] + orgLoc[dim]; + cache->plane_offset[dim] = cur[dim] * step[dim]; + cache->initial_location[dim] = cur[dim] * step[dim] + original_initial_location[dim]; + } + action(sd, ob, brush, ups, paint_mode_settings); + } + } + } +} + +static void do_radial_symmetry(Sculpt *sd, + Object *ob, + Brush *brush, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings, + BrushActionFunc action, + const ePaintSymmetryFlags symm, + const int axis, + const float /*feather*/) +{ + SculptSession *ss = ob->sculpt; + + for (int i = 1; i < sd->radial_symm[axis - 'X']; i++) { + const float angle = 2.0f * M_PI * i / sd->radial_symm[axis - 'X']; + ss->cache->radial_symmetry_pass = i; + SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); + } +} + +/** + * Noise texture gives different values for the same input coord; this + * can tear a multi-resolution mesh during sculpting so do a stitch in this case. + */ +static void sculpt_fix_noise_tear(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; + + if (ss->multires.active && mtex->tex && mtex->tex->type == TEX_NOISE) { + multires_stitch_grids(ob); + } +} + +static void do_symmetrical_brush_actions(Sculpt *sd, + Object *ob, + BrushActionFunc action, + UnifiedPaintSettings *ups, + PaintModeSettings *paint_mode_settings) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + + float feather = calc_symmetry_feather(sd, ss->cache); + + cache->bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings); + cache->symmetry = symm; + + /* `symm` is a bit combination of XYZ - + * 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ + for (int i = 0; i <= symm; i++) { + if (!SCULPT_is_symmetry_iteration_valid(i, symm)) { + continue; + } + const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm; + cache->radial_symmetry_pass = 0; + + SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); + do_tiled(sd, ob, brush, ups, paint_mode_settings, action); + + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'X', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Y', feather); + do_radial_symmetry(sd, ob, brush, ups, paint_mode_settings, action, symm, 'Z', feather); + } +} + +bool SCULPT_mode_poll(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + return ob && ob->mode & OB_MODE_SCULPT; +} + +bool SCULPT_mode_poll_view3d(bContext *C) +{ + return (SCULPT_mode_poll(C) && CTX_wm_region_view3d(C)); +} + +bool SCULPT_poll_view3d(bContext *C) +{ + return (SCULPT_poll(C) && CTX_wm_region_view3d(C)); +} + +bool SCULPT_poll(bContext *C) +{ + return SCULPT_mode_poll(C) && PAINT_brush_tool_poll(C); +} + +static const char *sculpt_tool_name(Sculpt *sd) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + switch ((eBrushSculptTool)brush->sculpt_tool) { + case SCULPT_TOOL_DRAW: + return "Draw Brush"; + case SCULPT_TOOL_SMOOTH: + return "Smooth Brush"; + case SCULPT_TOOL_CREASE: + return "Crease Brush"; + case SCULPT_TOOL_BLOB: + return "Blob Brush"; + case SCULPT_TOOL_PINCH: + return "Pinch Brush"; + case SCULPT_TOOL_INFLATE: + return "Inflate Brush"; + case SCULPT_TOOL_GRAB: + return "Grab Brush"; + case SCULPT_TOOL_NUDGE: + return "Nudge Brush"; + case SCULPT_TOOL_THUMB: + return "Thumb Brush"; + case SCULPT_TOOL_LAYER: + return "Layer Brush"; + case SCULPT_TOOL_FLATTEN: + return "Flatten Brush"; + case SCULPT_TOOL_CLAY: + return "Clay Brush"; + case SCULPT_TOOL_CLAY_STRIPS: + return "Clay Strips Brush"; + case SCULPT_TOOL_CLAY_THUMB: + return "Clay Thumb Brush"; + case SCULPT_TOOL_FILL: + return "Fill Brush"; + case SCULPT_TOOL_SCRAPE: + return "Scrape Brush"; + case SCULPT_TOOL_SNAKE_HOOK: + return "Snake Hook Brush"; + case SCULPT_TOOL_ROTATE: + return "Rotate Brush"; + case SCULPT_TOOL_MASK: + return "Mask Brush"; + case SCULPT_TOOL_SIMPLIFY: + return "Simplify Brush"; + case SCULPT_TOOL_DRAW_SHARP: + return "Draw Sharp Brush"; + case SCULPT_TOOL_ELASTIC_DEFORM: + return "Elastic Deform Brush"; + case SCULPT_TOOL_POSE: + return "Pose Brush"; + case SCULPT_TOOL_MULTIPLANE_SCRAPE: + return "Multi-plane Scrape Brush"; + case SCULPT_TOOL_SLIDE_RELAX: + return "Slide/Relax Brush"; + case SCULPT_TOOL_BOUNDARY: + return "Boundary Brush"; + case SCULPT_TOOL_CLOTH: + return "Cloth Brush"; + case SCULPT_TOOL_DRAW_FACE_SETS: + return "Draw Face Sets"; + case SCULPT_TOOL_DISPLACEMENT_ERASER: + return "Multires Displacement Eraser"; + case SCULPT_TOOL_DISPLACEMENT_SMEAR: + return "Multires Displacement Smear"; + case SCULPT_TOOL_PAINT: + return "Paint Brush"; + case SCULPT_TOOL_SMEAR: + return "Smear Brush"; + } + + return "Sculpting"; +} + +/* Operator for applying a stroke (various attributes including mouse path) + * using the current brush. */ + +void SCULPT_cache_free(StrokeCache *cache) +{ + MEM_SAFE_FREE(cache->dial); + 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); + MEM_SAFE_FREE(cache->prev_displacement); + MEM_SAFE_FREE(cache->limit_surface_co); + MEM_SAFE_FREE(cache->prev_colors_vpaint); + + 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->boundaries[i]) { + SCULPT_boundary_data_free(cache->boundaries[i]); + } + } + + if (cache->cloth_sim) { + SCULPT_cloth_simulation_free(cache->cloth_sim); + } + + MEM_freeN(cache); +} + +/* Initialize mirror modifier clipping. */ +static void sculpt_init_mirror_clipping(Object *ob, SculptSession *ss) +{ + unit_m4(ss->cache->clip_mirror_mtx); + + LISTBASE_FOREACH (ModifierData *, md, &ob->modifiers) { + if (!(md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime))) { + continue; + } + MirrorModifierData *mmd = (MirrorModifierData *)md; + + if (!(mmd->flag & MOD_MIR_CLIPPING)) { + continue; + } + /* Check each axis for mirroring. */ + for (int i = 0; i < 3; i++) { + if (!(mmd->flag & (MOD_MIR_AXIS_X << i))) { + continue; + } + /* Enable sculpt clipping. */ + ss->cache->flag |= CLIP_X << i; + + /* Update the clip tolerance. */ + if (mmd->tolerance > ss->cache->clip_tolerance[i]) { + ss->cache->clip_tolerance[i] = mmd->tolerance; + } + + /* Store matrix for mirror object clipping. */ + if (mmd->mirror_ob) { + float imtx_mirror_ob[4][4]; + invert_m4_m4(imtx_mirror_ob, mmd->mirror_ob->object_to_world); + mul_m4_m4m4(ss->cache->clip_mirror_mtx, imtx_mirror_ob, ob->object_to_world); + } + } + } +} + +static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) +{ + Scene *scene = CTX_data_scene(C); + Brush *brush = paint->brush; + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + cache->saved_mask_brush_tool = brush->mask_tool; + brush->mask_tool = BRUSH_MASK_SMOOTH; + } + else if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_SLIDE_RELAX, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR)) { + /* Do nothing, this tool has its own smooth mode. */ + } + else { + int cur_brush_size = BKE_brush_size_get(scene, brush); + + BLI_strncpy(cache->saved_active_brush_name, + brush->id.name + 2, + sizeof(cache->saved_active_brush_name)); + + /* Switch to the smooth brush. */ + brush = BKE_paint_toolslots_brush_get(paint, SCULPT_TOOL_SMOOTH); + if (brush) { + BKE_paint_brush_set(paint, brush); + cache->saved_smooth_size = BKE_brush_size_get(scene, brush); + BKE_brush_size_set(scene, brush, cur_brush_size); + BKE_curvemapping_init(brush->curve); + } + } +} + +static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + Brush *brush = BKE_paint_brush(paint); + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + brush->mask_tool = cache->saved_mask_brush_tool; + } + else if (ELEM(brush->sculpt_tool, + SCULPT_TOOL_SLIDE_RELAX, + SCULPT_TOOL_DRAW_FACE_SETS, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_SMEAR)) { + /* Do nothing. */ + } + else { + /* Try to switch back to the saved/previous brush. */ + BKE_brush_size_set(scene, brush, cache->saved_smooth_size); + brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, cache->saved_active_brush_name); + if (brush) { + BKE_paint_brush_set(paint, brush); + } + } +} + +/* Initialize the stroke cache invariants from operator properties. */ +static void sculpt_update_cache_invariants( + bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mval[2]) +{ + StrokeCache *cache = static_cast( + MEM_callocN(sizeof(StrokeCache), "stroke cache")); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + UnifiedPaintSettings *ups = &tool_settings->unified_paint_settings; + Brush *brush = BKE_paint_brush(&sd->paint); + ViewContext *vc = paint_stroke_view_context(static_cast(op->customdata)); + Object *ob = CTX_data_active_object(C); + float mat[3][3]; + float viewDir[3] = {0.0f, 0.0f, 1.0f}; + float max_scale; + int mode; + + ss->cache = cache; + + /* Set scaling adjustment. */ + max_scale = 0.0f; + for (int i = 0; i < 3; i++) { + max_scale = max_ff(max_scale, fabsf(ob->scale[i])); + } + cache->scale[0] = max_scale / ob->scale[0]; + cache->scale[1] = max_scale / ob->scale[1]; + cache->scale[2] = max_scale / ob->scale[2]; + + cache->plane_trim_squared = brush->plane_trim * brush->plane_trim; + + cache->flag = 0; + + sculpt_init_mirror_clipping(ob, ss); + + /* Initial mouse location. */ + if (mval) { + copy_v2_v2(cache->initial_mouse, mval); + } + else { + zero_v2(cache->initial_mouse); + } + + copy_v3_v3(cache->initial_location, ss->cursor_location); + copy_v3_v3(cache->true_initial_location, ss->cursor_location); + + copy_v3_v3(cache->initial_normal, ss->cursor_normal); + copy_v3_v3(cache->true_initial_normal, ss->cursor_normal); + + mode = RNA_enum_get(op->ptr, "mode"); + cache->invert = mode == BRUSH_STROKE_INVERT; + cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH; + cache->normal_weight = brush->normal_weight; + + /* Interpret invert as following normal, for grab brushes. */ + if (SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool)) { + if (cache->invert) { + cache->invert = false; + cache->normal_weight = (cache->normal_weight == 0.0f); + } + } + + /* Not very nice, but with current events system implementation + * we can't handle brush appearance inversion hotkey separately (sergey). */ + if (cache->invert) { + ups->draw_inverted = true; + } + else { + ups->draw_inverted = false; + } + + /* Alt-Smooth. */ + if (cache->alt_smooth) { + smooth_brush_toggle_on(C, &sd->paint, cache); + /* Refresh the brush pointer in case we switched brush in the toggle function. */ + brush = BKE_paint_brush(&sd->paint); + } + + copy_v2_v2(cache->mouse, cache->initial_mouse); + copy_v2_v2(cache->mouse_event, cache->initial_mouse); + copy_v2_v2(ups->tex_mouse, cache->initial_mouse); + + /* Truly temporary data that isn't stored in properties. */ + cache->vc = vc; + cache->brush = brush; + + /* Cache projection matrix. */ + ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat); + + invert_m4_m4(ob->world_to_object, ob->object_to_world); + copy_m3_m4(mat, cache->vc->rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->world_to_object); + mul_m3_v3(mat, viewDir); + normalize_v3_v3(cache->true_view_normal, viewDir); + + cache->supports_gravity = (!ELEM(brush->sculpt_tool, + SCULPT_TOOL_MASK, + SCULPT_TOOL_SMOOTH, + SCULPT_TOOL_SIMPLIFY, + SCULPT_TOOL_DISPLACEMENT_SMEAR, + SCULPT_TOOL_DISPLACEMENT_ERASER) && + (sd->gravity_factor > 0.0f)); + /* Get gravity vector in world space. */ + if (cache->supports_gravity) { + if (sd->gravity_object) { + Object *gravity_object = sd->gravity_object; + + copy_v3_v3(cache->true_gravity_direction, gravity_object->object_to_world[2]); + } + else { + cache->true_gravity_direction[0] = cache->true_gravity_direction[1] = 0.0f; + cache->true_gravity_direction[2] = 1.0f; + } + + /* Transform to sculpted object space. */ + mul_m3_v3(mat, cache->true_gravity_direction); + normalize_v3(cache->true_gravity_direction); + } + + /* Make copies of the mesh vertex locations and normals for some tools. */ + if (brush->flag & BRUSH_ANCHORED) { + cache->original = true; + } + + if (SCULPT_automasking_needs_original(sd, brush)) { + cache->original = true; + } + + /* Draw sharp does not need the original coordinates to produce the accumulate effect, so it + * should work the opposite way. */ + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { + cache->original = true; + } + + if (SCULPT_TOOL_HAS_ACCUMULATE(brush->sculpt_tool)) { + if (!(brush->flag & BRUSH_ACCUMULATE)) { + cache->original = true; + if (brush->sculpt_tool == SCULPT_TOOL_DRAW_SHARP) { + cache->original = false; + } + } + } + + /* Original coordinates require the sculpt undo system, which isn't used + * for image brushes. It's also not necessary, just disable it. */ + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + cache->original = false; + } + + cache->first_time = true; + +#define PIXEL_INPUT_THRESHHOLD 5 + if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { + cache->dial = BLI_dial_init(cache->initial_mouse, PIXEL_INPUT_THRESHHOLD); + } + +#undef PIXEL_INPUT_THRESHHOLD +} + +static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size) +{ + switch (brush->sculpt_tool) { + case SCULPT_TOOL_CLAY: + return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure)); + case SCULPT_TOOL_CLAY_STRIPS: + return max_ff(initial_size * 0.30f, initial_size * powf(cache->pressure, 1.5f)); + case SCULPT_TOOL_CLAY_THUMB: { + float clay_stabilized_pressure = SCULPT_clay_thumb_get_stabilized_pressure(cache); + return initial_size * clay_stabilized_pressure; + } + default: + return initial_size * cache->pressure; + } +} + +/* In these brushes the grab delta is calculated always from the initial stroke location, which is + * generally used to create grab deformations. */ +static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) +{ + if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && (brush->flag & BRUSH_ANCHORED)) { + return true; + } + + 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 + * to calculate to orientate the brush tip and deformation towards the stroke direction. */ +static bool sculpt_needs_delta_for_tip_orientation(Brush *brush) +{ + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + return brush->cloth_deform_type != BRUSH_CLOTH_DEFORM_GRAB; + } + return ELEM(brush->sculpt_tool, + SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_PINCH, + SCULPT_TOOL_MULTIPLANE_SCRAPE, + SCULPT_TOOL_CLAY_THUMB, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_SNAKE_HOOK); +} + +static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Brush *brush) +{ + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + const float mval[2] = { + cache->mouse_event[0], + cache->mouse_event[1], + }; + int tool = brush->sculpt_tool; + + if (!ELEM(tool, + SCULPT_TOOL_PAINT, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_CLOTH, + SCULPT_TOOL_NUDGE, + SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_PINCH, + SCULPT_TOOL_MULTIPLANE_SCRAPE, + SCULPT_TOOL_CLAY_THUMB, + SCULPT_TOOL_SNAKE_HOOK, + SCULPT_TOOL_POSE, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_SMEAR, + SCULPT_TOOL_THUMB) && + !sculpt_brush_use_topology_rake(ss, brush)) { + return; + } + float grab_location[3], imat[4][4], delta[3], loc[3]; + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->orig_grab_location, + SCULPT_vertex_co_for_grab_active_get(ss, SCULPT_active_vertex_get(ss))); + } + else { + copy_v3_v3(cache->orig_grab_location, cache->true_location); + } + } + 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); + } + + /* Compute 3d coordinate at same z from original location + mval. */ + mul_v3_m4v3(loc, ob->object_to_world, cache->orig_grab_location); + ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mval, grab_location); + + /* Compute delta to move verts by. */ + if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (sculpt_needs_delta_from_anchored_origin(brush)) { + sub_v3_v3v3(delta, grab_location, cache->old_grab_location); + invert_m4_m4(imat, ob->object_to_world); + mul_mat3_m4_v3(imat, delta); + add_v3_v3(cache->grab_delta, delta); + } + else if (sculpt_needs_delta_for_tip_orientation(brush)) { + if (brush->flag & BRUSH_ANCHORED) { + float orig[3]; + mul_v3_m4v3(orig, ob->object_to_world, cache->orig_grab_location); + sub_v3_v3v3(cache->grab_delta, grab_location, orig); + } + else { + sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); + } + invert_m4_m4(imat, ob->object_to_world); + mul_mat3_m4_v3(imat, cache->grab_delta); + } + else { + /* Use for 'Brush.topology_rake_factor'. */ + sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); + } + } + else { + zero_v3(cache->grab_delta); + } + + if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { + project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss->cache->true_view_normal); + } + + copy_v3_v3(cache->old_grab_location, grab_location); + + if (tool == SCULPT_TOOL_GRAB) { + if (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->anchored_location, cache->orig_grab_location); + } + else { + copy_v3_v3(cache->anchored_location, cache->true_location); + } + } + else if (tool == SCULPT_TOOL_ELASTIC_DEFORM || SCULPT_is_cloth_deform_brush(brush)) { + copy_v3_v3(cache->anchored_location, cache->true_location); + } + else if (tool == SCULPT_TOOL_THUMB) { + copy_v3_v3(cache->anchored_location, cache->orig_grab_location); + } + + if (sculpt_needs_delta_from_anchored_origin(brush)) { + /* Location stays the same for finding vertices in brush radius. */ + copy_v3_v3(cache->true_location, cache->orig_grab_location); + + ups->draw_anchored = true; + copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); + ups->anchored_size = ups->pixel_radius; + } + + /* Handle 'rake' */ + cache->is_rake_rotation_valid = false; + + invert_m4_m4(imat, ob->object_to_world); + mul_mat3_m4_v3(imat, grab_location); + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + copy_v3_v3(cache->rake_data.follow_co, grab_location); + } + + if (!sculpt_brush_needs_rake_rotation(brush)) { + return; + } + cache->rake_data.follow_dist = cache->radius * SCULPT_RAKE_BRUSH_FACTOR; + + if (!is_zero_v3(cache->grab_delta)) { + const float eps = 0.00001f; + + float v1[3], v2[3]; + + copy_v3_v3(v1, cache->rake_data.follow_co); + copy_v3_v3(v2, cache->rake_data.follow_co); + sub_v3_v3(v2, cache->grab_delta); + + sub_v3_v3(v1, grab_location); + sub_v3_v3(v2, grab_location); + + if ((normalize_v3(v2) > eps) && (normalize_v3(v1) > eps) && (len_squared_v3v3(v1, v2) > eps)) { + const float rake_dist_sq = len_squared_v3v3(cache->rake_data.follow_co, grab_location); + const float rake_fade = (rake_dist_sq > square_f(cache->rake_data.follow_dist)) ? + 1.0f : + sqrtf(rake_dist_sq) / cache->rake_data.follow_dist; + + float axis[3], angle; + float tquat[4]; + + rotation_between_vecs_to_quat(tquat, v1, v2); + + /* Use axis-angle to scale rotation since the factor may be above 1. */ + quat_to_axis_angle(axis, &angle, tquat); + normalize_v3(axis); + + angle *= brush->rake_factor * rake_fade; + axis_angle_normalized_to_quat(cache->rake_rotation, axis, angle); + cache->is_rake_rotation_valid = true; + } + } + sculpt_rake_data_update(&cache->rake_data, grab_location); +} + +static void sculpt_update_cache_paint_variants(StrokeCache *cache, const Brush *brush) +{ + cache->paint_brush.hardness = brush->hardness; + if (brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) { + cache->paint_brush.hardness *= brush->paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; + } + + cache->paint_brush.flow = brush->flow; + if (brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE) { + cache->paint_brush.flow *= brush->paint_flags & BRUSH_PAINT_FLOW_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; + } + + cache->paint_brush.wet_mix = brush->wet_mix; + if (brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE) { + cache->paint_brush.wet_mix *= brush->paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; + + /* This makes wet mix more sensible in higher values, which allows to create brushes that have + * a wider pressure range were they only blend colors without applying too much of the brush + * color. */ + cache->paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache->paint_brush.wet_mix); + } + + cache->paint_brush.wet_persistence = brush->wet_persistence; + if (brush->paint_flags & BRUSH_PAINT_WET_PERSISTENCE_PRESSURE) { + cache->paint_brush.wet_persistence = brush->paint_flags & + BRUSH_PAINT_WET_PERSISTENCE_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; + } + + cache->paint_brush.density = brush->density; + if (brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE) { + cache->paint_brush.density = brush->paint_flags & BRUSH_PAINT_DENSITY_PRESSURE_INVERT ? + 1.0f - cache->pressure : + cache->pressure; + } +} + +/* Initialize the stroke cache variants from operator properties. */ +static void sculpt_update_cache_variants(bContext *C, Sculpt *sd, Object *ob, PointerRNA *ptr) +{ + Scene *scene = CTX_data_scene(C); + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + Brush *brush = BKE_paint_brush(&sd->paint); + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || + !((brush->flag & BRUSH_ANCHORED) || (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK) || + (brush->sculpt_tool == SCULPT_TOOL_ROTATE) || SCULPT_is_cloth_deform_brush(brush))) { + RNA_float_get_array(ptr, "location", cache->true_location); + } + + cache->pen_flip = RNA_boolean_get(ptr, "pen_flip"); + RNA_float_get_array(ptr, "mouse", cache->mouse); + RNA_float_get_array(ptr, "mouse_event", cache->mouse_event); + + /* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab, + * thumb). They depends on initial state and brush coord/pressure/etc. + * It's more an events design issue, which doesn't split coordinate/pressure/angle changing + * events. We should avoid this after events system re-design. */ + if (paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT) || cache->first_time) { + cache->pressure = RNA_float_get(ptr, "pressure"); + } + + cache->x_tilt = RNA_float_get(ptr, "x_tilt"); + cache->y_tilt = RNA_float_get(ptr, "y_tilt"); + + /* Truly temporary data that isn't stored in properties. */ + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + if (!BKE_brush_use_locked_size(scene, brush)) { + cache->initial_radius = paint_calc_object_space_radius( + cache->vc, cache->true_location, BKE_brush_size_get(scene, brush)); + BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius); + } + else { + cache->initial_radius = BKE_brush_unprojected_radius_get(scene, brush); + } + } + + /* Clay stabilized pressure. */ + if (brush->sculpt_tool == SCULPT_TOOL_CLAY_THUMB) { + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { + ss->cache->clay_pressure_stabilizer[i] = 0.0f; + } + ss->cache->clay_pressure_stabilizer_index = 0; + } + else { + cache->clay_pressure_stabilizer[cache->clay_pressure_stabilizer_index] = cache->pressure; + cache->clay_pressure_stabilizer_index += 1; + if (cache->clay_pressure_stabilizer_index >= SCULPT_CLAY_STABILIZER_LEN) { + cache->clay_pressure_stabilizer_index = 0; + } + } + } + + if (BKE_brush_use_size_pressure(brush) && + paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) { + cache->radius = sculpt_brush_dynamic_size_get(brush, cache, cache->initial_radius); + cache->dyntopo_pixel_radius = sculpt_brush_dynamic_size_get( + brush, cache, ups->initial_pixel_radius); + } + else { + cache->radius = cache->initial_radius; + cache->dyntopo_pixel_radius = ups->initial_pixel_radius; + } + + sculpt_update_cache_paint_variants(cache, brush); + + cache->radius_squared = cache->radius * cache->radius; + + if (brush->flag & BRUSH_ANCHORED) { + /* True location has been calculated as part of the stroke system already here. */ + if (brush->flag & BRUSH_EDGE_TO_EDGE) { + RNA_float_get_array(ptr, "location", cache->true_location); + } + + cache->radius = paint_calc_object_space_radius( + cache->vc, cache->true_location, ups->pixel_radius); + cache->radius_squared = cache->radius * cache->radius; + + copy_v3_v3(cache->anchored_location, cache->true_location); + } + + sculpt_update_brush_delta(ups, ob, brush); + + if (brush->sculpt_tool == SCULPT_TOOL_ROTATE) { + cache->vertex_rotation = -BLI_dial_angle(cache->dial, cache->mouse) * cache->bstrength; + + ups->draw_anchored = true; + copy_v2_v2(ups->anchored_initial_mouse, cache->initial_mouse); + copy_v3_v3(cache->anchored_location, cache->true_location); + ups->anchored_size = ups->pixel_radius; + } + + cache->special_rotation = ups->brush_rotation; + + cache->iteration_count++; +} + +/* Returns true if any of the smoothing modes are active (currently + * one of smooth brush, autosmooth, mask smooth, or shift-key + * smooth). */ +static bool sculpt_needs_connectivity_info(const Sculpt *sd, + const Brush *brush, + SculptSession *ss, + int stroke_mode) +{ + if (ss && ss->pbvh && SCULPT_is_automasking_enabled(sd, ss, brush)) { + return true; + } + return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) || + (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) || + ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) || + (brush->sculpt_tool == SCULPT_TOOL_POSE) || + (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) || + (brush->sculpt_tool == SCULPT_TOOL_SLIDE_RELAX) || + SCULPT_tool_is_paint(brush->sculpt_tool) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || + (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || + (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS) || + (brush->sculpt_tool == SCULPT_TOOL_DISPLACEMENT_SMEAR) || + (brush->sculpt_tool == SCULPT_TOOL_PAINT)); +} + +void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) +{ + SculptSession *ss = ob->sculpt; + RegionView3D *rv3d = CTX_wm_region_view3d(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + bool need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, 0); + if (ss->shapekey_active || ss->deform_modifiers_active || + (!BKE_sculptsession_use_pbvh_draw(ob, rv3d) && need_pmap)) { + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + BKE_sculpt_update_object_for_edit( + depsgraph, ob, need_pmap, false, SCULPT_tool_is_paint(brush->sculpt_tool)); + } +} + +static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) +{ + if (BKE_pbvh_node_get_tmin(node) >= *tmin) { + return; + } + SculptRaycastData *srd = static_cast(data_v); + float(*origco)[3] = nullptr; + bool use_origco = false; + + if (srd->original && srd->ss->cache) { + if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { + use_origco = true; + } + else { + /* Intersect with coordinates from before we started stroke. */ + SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); + origco = (unode) ? unode->co : nullptr; + use_origco = origco ? true : false; + } + } + + if (BKE_pbvh_node_raycast(srd->ss->pbvh, + node, + origco, + use_origco, + srd->ray_start, + srd->ray_normal, + &srd->isect_precalc, + &srd->depth, + &srd->active_vertex, + &srd->active_face_grid_index, + srd->face_normal)) { + srd->hit = true; + *tmin = srd->depth; + } +} + +static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) +{ + if (BKE_pbvh_node_get_tmin(node) >= *tmin) { + return; + } + SculptFindNearestToRayData *srd = static_cast(data_v); + float(*origco)[3] = nullptr; + bool use_origco = false; + + if (srd->original && srd->ss->cache) { + if (BKE_pbvh_type(srd->ss->pbvh) == PBVH_BMESH) { + use_origco = true; + } + else { + /* Intersect with coordinates from before we started stroke. */ + SculptUndoNode *unode = SCULPT_undo_get_node(node, SCULPT_UNDO_COORDS); + origco = (unode) ? unode->co : nullptr; + use_origco = origco ? true : false; + } + } + + if (BKE_pbvh_node_find_nearest_to_ray(srd->ss->pbvh, + node, + origco, + use_origco, + srd->ray_start, + srd->ray_normal, + &srd->depth, + &srd->dist_sq_to_ray)) { + srd->hit = true; + *tmin = srd->dist_sq_to_ray; + } +} + +float SCULPT_raycast_init(ViewContext *vc, + const float mval[2], + float ray_start[3], + float ray_end[3], + float ray_normal[3], + bool original) +{ + float obimat[4][4]; + float dist; + Object *ob = vc->obact; + RegionView3D *rv3d = static_cast(vc->region->regiondata); + View3D *v3d = vc->v3d; + + /* TODO: what if the segment is totally clipped? (return == 0). */ + ED_view3d_win_to_segment_clipped( + vc->depsgraph, vc->region, vc->v3d, mval, ray_start, ray_end, true); + + invert_m4_m4(obimat, ob->object_to_world); + mul_m4_v3(obimat, ray_start); + mul_m4_v3(obimat, ray_end); + + sub_v3_v3v3(ray_normal, ray_end, ray_start); + dist = normalize_v3(ray_normal); + + if ((rv3d->is_persp == false) && + /* If the ray is clipped, don't adjust its start/end. */ + !RV3D_CLIPPING_ENABLED(v3d, rv3d)) { + BKE_pbvh_raycast_project_ray_root(ob->sculpt->pbvh, original, ray_start, ray_end, ray_normal); + + /* rRecalculate the normal. */ + sub_v3_v3v3(ray_normal, ray_end, ray_start); + dist = normalize_v3(ray_normal); + } + + return dist; +} + +bool SCULPT_cursor_geometry_info_update(bContext *C, + SculptCursorGeometryInfo *out, + const float mval[2], + bool use_sampled_normal) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Scene *scene = CTX_data_scene(C); + Sculpt *sd = scene->toolsettings->sculpt; + Object *ob; + SculptSession *ss; + ViewContext vc; + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3], sampled_normal[3], + mat[3][3]; + float viewDir[3] = {0.0f, 0.0f, 1.0f}; + int totnode; + bool original = false; + + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + ob = vc.obact; + ss = ob->sculpt; + + if (!ss->pbvh) { + zero_v3(out->location); + zero_v3(out->normal); + zero_v3(out->active_vertex_co); + return false; + } + + /* PBVH raycast to get active vertex and face normal. */ + depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); + SCULPT_stroke_modifiers_check(C, ob, brush); + + SculptRaycastData srd{}; + srd.original = original; + srd.ss = ob->sculpt; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = depth; + srd.face_normal = face_normal; + + isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); + + /* Cursor is not over the mesh, return default values. */ + if (!srd.hit) { + zero_v3(out->location); + zero_v3(out->normal); + zero_v3(out->active_vertex_co); + return false; + } + + /* Update the active vertex of the SculptSession. */ + ss->active_vertex = srd.active_vertex; + 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)) { + case PBVH_FACES: + ss->active_face_index = srd.active_face_grid_index; + ss->active_grid_index = 0; + break; + case PBVH_GRIDS: + ss->active_face_index = 0; + ss->active_grid_index = srd.active_face_grid_index; + break; + case PBVH_BMESH: + ss->active_face_index = 0; + ss->active_grid_index = 0; + break; + } + + copy_v3_v3(out->location, ray_normal); + mul_v3_fl(out->location, srd.depth); + add_v3_v3(out->location, ray_start); + + /* Option to return the face normal directly for performance o accuracy reasons. */ + if (!use_sampled_normal) { + copy_v3_v3(out->normal, srd.face_normal); + return srd.hit; + } + + /* Sampled normal calculation. */ + float radius; + + /* Update cursor data in SculptSession. */ + invert_m4_m4(ob->world_to_object, ob->object_to_world); + copy_m3_m4(mat, vc.rv3d->viewinv); + mul_m3_v3(mat, viewDir); + copy_m3_m4(mat, ob->world_to_object); + mul_m3_v3(mat, viewDir); + normalize_v3_v3(ss->cursor_view_normal, viewDir); + copy_v3_v3(ss->cursor_normal, srd.face_normal); + copy_v3_v3(ss->cursor_location, out->location); + ss->rv3d = vc.rv3d; + ss->v3d = vc.v3d; + + if (!BKE_brush_use_locked_size(scene, brush)) { + radius = paint_calc_object_space_radius(&vc, out->location, BKE_brush_size_get(scene, brush)); + } + else { + radius = BKE_brush_unprojected_radius_get(scene, brush); + } + ss->cursor_radius = radius; + + PBVHNode **nodes = sculpt_pbvh_gather_cursor_update(ob, sd, original, &totnode); + + /* In case there are no nodes under the cursor, return the face normal. */ + if (!totnode) { + MEM_SAFE_FREE(nodes); + copy_v3_v3(out->normal, srd.face_normal); + return true; + } + + /* Calculate the sampled normal. */ + if (SCULPT_pbvh_calc_area_normal(brush, ob, nodes, totnode, true, sampled_normal)) { + copy_v3_v3(out->normal, sampled_normal); + copy_v3_v3(ss->cursor_sampled_normal, sampled_normal); + } + else { + /* Use face normal when there are no vertices to sample inside the cursor radius. */ + copy_v3_v3(out->normal, srd.face_normal); + } + MEM_SAFE_FREE(nodes); + return true; +} + +bool SCULPT_stroke_get_location(bContext *C, + float out[3], + const float mval[2], + bool force_original) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob; + SculptSession *ss; + StrokeCache *cache; + float ray_start[3], ray_end[3], ray_normal[3], depth, face_normal[3]; + bool original; + ViewContext vc; + + ED_view3d_viewcontext_init(C, &vc, depsgraph); + + ob = vc.obact; + + ss = ob->sculpt; + cache = ss->cache; + original = force_original || ((cache) ? cache->original : false); + + const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C)); + + SCULPT_stroke_modifiers_check(C, ob, brush); + + depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BM_mesh_elem_table_ensure(ss->bm, BM_VERT); + BM_mesh_elem_index_ensure(ss->bm, BM_VERT); + } + + bool hit = false; + { + SculptRaycastData srd; + srd.ss = ob->sculpt; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.hit = false; + srd.depth = depth; + srd.original = original; + srd.face_normal = face_normal; + isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); + + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); + } + } + + if (hit) { + return hit; + } + + if (!ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { + return hit; + } + + SculptFindNearestToRayData srd{}; + srd.original = original; + srd.ss = ob->sculpt; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = FLT_MAX; + srd.dist_sq_to_ray = FLT_MAX; + + BKE_pbvh_find_nearest_to_ray( + ss->pbvh, sculpt_find_nearest_to_ray_cb, &srd, ray_start, ray_normal, srd.original); + if (srd.hit) { + hit = true; + copy_v3_v3(out, ray_normal); + mul_v3_fl(out, srd.depth); + add_v3_v3(out, ray_start); + } + + return hit; +} + +static void sculpt_brush_init_tex(Sculpt *sd, SculptSession *ss) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; + + /* Init mtex nodes. */ + if (mtex->tex && mtex->tex->nodetree) { + /* Has internal flag to detect it only does it once. */ + ntreeTexBeginExecTree(mtex->tex->nodetree); + } + + if (ss->tex_pool == nullptr) { + ss->tex_pool = BKE_image_pool_new(); + } +} + +static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + Sculpt *sd = tool_settings->sculpt; + SculptSession *ss = CTX_data_active_object(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + int mode = RNA_enum_get(op->ptr, "mode"); + bool need_pmap, needs_colors; + bool need_mask = false; + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + need_mask = true; + } + + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH || + brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) { + need_mask = true; + } + + view3d_operator_needs_opengl(C); + sculpt_brush_init_tex(sd, ss); + + need_pmap = sculpt_needs_connectivity_info(sd, brush, ss, mode); + needs_colors = SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob); + + if (needs_colors) { + BKE_sculpt_color_layer_create_if_needed(ob); + } + + /* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of + * earlier steps modifying the data. */ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + BKE_sculpt_update_object_for_edit( + depsgraph, ob, need_pmap, need_mask, SCULPT_tool_is_paint(brush->sculpt_tool)); + + ED_paint_tool_update_sticky_shading_color(C, ob); +} + +static void sculpt_restore_mesh(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + /* For the cloth brush it makes more sense to not restore the mesh state to keep running the + * simulation from the previous state. */ + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { + return; + } + + /* Restore the mesh before continuing with anchored stroke. */ + if ((brush->flag & BRUSH_ANCHORED) || + (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM) && + BKE_brush_use_size_pressure(brush)) || + (brush->flag & BRUSH_DRAG_DOT)) { + + SculptUndoNode *unode = SCULPT_undo_get_first_node(); + if (unode && unode->type == SCULPT_UNDO_FACE_SETS) { + for (int i = 0; i < ss->totfaces; i++) { + ss->face_sets[i] = unode->face_sets[i]; + } + } + + paint_mesh_restore_co(sd, ob); + + if (ss->cache) { + MEM_SAFE_FREE(ss->cache->layer_displacement_factor); + } + } +} + +void SCULPT_update_object_bounding_box(Object *ob) +{ + if (ob->runtime.bb) { + float bb_min[3], bb_max[3]; + + BKE_pbvh_bounding_box(ob->sculpt->pbvh, bb_min, bb_max); + BKE_boundbox_init_from_minmax(ob->runtime.bb, bb_min, bb_max); + } +} + +void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + ARegion *region = CTX_wm_region(C); + MultiresModifierData *mmd = ss->multires.modifier; + RegionView3D *rv3d = CTX_wm_region_view3d(C); + + if (rv3d) { + /* Mark for faster 3D viewport redraws. */ + rv3d->rflag |= RV3D_PAINTING; + } + + if (mmd != nullptr) { + multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED); + } + + if ((update_flags & SCULPT_UPDATE_IMAGE) != 0) { + ED_region_tag_redraw(region); + if (update_flags == SCULPT_UPDATE_IMAGE) { + /* Early exit when only need to update the images. We don't want to tag any geometry updates + * that would rebuilt the PBVH. */ + return; + } + } + + DEG_id_tag_update(&ob->id, ID_RECALC_SHADING); + + /* Only current viewport matters, slower update for all viewports will + * be done in sculpt_flush_update_done. */ + if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { + /* Slow update with full dependency graph update and all that comes with it. + * Needed when there are modifiers or full shading in the 3D viewport. */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + ED_region_tag_redraw(region); + } + else { + /* Fast path where we just update the BVH nodes that changed, and redraw + * only the part of the 3D viewport where changes happened. */ + rcti r; + + if (update_flags & SCULPT_UPDATE_COORDS) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB); + /* Update the object's bounding box too so that the object + * doesn't get incorrectly clipped during drawing in + * draw_mesh_object(). T33790. */ + SCULPT_update_object_bounding_box(ob); + } + + if (SCULPT_get_redraw_rect(region, CTX_wm_region_view3d(C), ob, &r)) { + if (ss->cache) { + ss->cache->current_r = r; + } + + /* previous is not set in the current cache else + * the partial rect will always grow */ + sculpt_extend_redraw_rect_previous(ob, &r); + + r.xmin += region->winrct.xmin - 2; + r.xmax += region->winrct.xmin + 2; + r.ymin += region->winrct.ymin - 2; + r.ymax += region->winrct.ymin + 2; + ED_region_tag_redraw_partial(region, &r, true); + } + } +} + +void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType update_flags) +{ + /* After we are done drawing the stroke, check if we need to do a more + * expensive depsgraph tag to update geometry. */ + wmWindowManager *wm = CTX_wm_manager(C); + RegionView3D *current_rv3d = CTX_wm_region_view3d(C); + SculptSession *ss = ob->sculpt; + Mesh *mesh = static_cast(ob->data); + + /* Always needed for linked duplicates. */ + bool need_tag = (ID_REAL_USERS(&mesh->id) > 1); + + if (current_rv3d) { + current_rv3d->rflag &= ~RV3D_PAINTING; + } + + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype != SPACE_VIEW3D) { + continue; + } + + /* Tag all 3D viewports for redraw now that we are done. Others + * viewports did not get a full redraw, and anti-aliasing for the + * current viewport was deactivated. */ + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (region->regiontype == RGN_TYPE_WINDOW) { + RegionView3D *rv3d = static_cast(region->regiondata); + if (rv3d != current_rv3d) { + need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, rv3d); + } + + ED_region_tag_redraw(region); + } + } + } + + if (update_flags & SCULPT_UPDATE_IMAGE) { + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = static_cast(area->spacedata.first); + if (sl->spacetype != SPACE_IMAGE) { + continue; + } + ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW); + } + } + } + + if (update_flags & SCULPT_UPDATE_COORDS) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB); + + /* Coordinates were modified, so fake neighbors are not longer valid. */ + SCULPT_fake_neighbors_free(ob); + } + + if (update_flags & SCULPT_UPDATE_MASK) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); + } + + if (update_flags & SCULPT_UPDATE_COLOR) { + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateColor); + } + + BKE_sculpt_attributes_destroy_temporary_stroke(ob); + + if (update_flags & SCULPT_UPDATE_COORDS) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) { + BKE_pbvh_bmesh_after_stroke(ss->pbvh); + } + + /* Optimization: if there is locked key and active modifiers present in */ + /* the stack, keyblock is updating at each step. otherwise we could update */ + /* keyblock only when stroke is finished. */ + if (ss->shapekey_active && !ss->deform_modifiers_active) { + sculpt_update_keyblock(ob); + } + } + + if (need_tag) { + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + } +} + +/* Returns whether the mouse/stylus is over the mesh (1) + * or over the background (0). */ +static bool over_mesh(bContext *C, wmOperator * /*op*/, const float mval[2]) +{ + float co_dummy[3]; + return SCULPT_stroke_get_location(C, co_dummy, mval, false); +} + +static void sculpt_stroke_undo_begin(const bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* Setup the correct undo system. Image painting and sculpting are mutual exclusive. + * Color attributes are part of the sculpting undo system. */ + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + ED_image_undo_push_begin(op->type->name, PAINT_MODE_SCULPT); + } + else { + SCULPT_undo_push_begin_ex(ob, sculpt_tool_name(sd)); + } +} + +static void sculpt_stroke_undo_end(const bContext *C, Brush *brush) +{ + Object *ob = CTX_data_active_object(C); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + if (brush && brush->sculpt_tool == SCULPT_TOOL_PAINT && + SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + ED_image_undo_push_end(); + } + else { + SCULPT_undo_push_end(ob); + } +} + +bool SCULPT_handles_colors_report(SculptSession *ss, ReportList *reports) +{ + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: + return true; + case PBVH_BMESH: + BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode"); + return false; + case PBVH_GRIDS: + BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode"); + return false; + } + + BLI_assert_msg(0, "PBVH corruption, type was invalid."); + + return false; +} + +static bool sculpt_stroke_test_start(bContext *C, wmOperator *op, const float mval[2]) +{ + /* Don't start the stroke until `mval` goes over the mesh. + * NOTE: `mval` will only be null when re-executing the saved stroke. + * We have exception for 'exec' strokes since they may not set `mval`, + * only 'location', see: T52195. */ + if (((op->flag & OP_IS_INVOKE) == 0) || (mval == nullptr) || over_mesh(C, op, mval)) { + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* NOTE: This should be removed when paint mode is available. Paint mode can force based on the + * canvas it is painting on. (ref. use_sculpt_texture_paint). */ + if (brush && SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + View3D *v3d = CTX_wm_view3d(C); + if (v3d->shading.type == OB_SOLID) { + v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR; + } + } + + ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); + + sculpt_update_cache_invariants(C, sd, ss, op, mval); + + SculptCursorGeometryInfo sgi; + SCULPT_cursor_geometry_info_update(C, &sgi, mval, false); + + sculpt_stroke_undo_begin(C, op); + + SCULPT_stroke_id_next(ob); + ss->cache->stroke_id = ss->stroke_id; + + return true; + } + return false; +} + +static void sculpt_stroke_update_step(bContext *C, + wmOperator * /*op*/, + PaintStroke *stroke, + PointerRNA *itemptr) +{ + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + const Brush *brush = BKE_paint_brush(&sd->paint); + ToolSettings *tool_settings = CTX_data_tool_settings(C); + StrokeCache *cache = ss->cache; + cache->stroke_distance = paint_stroke_distance_get(stroke); + + SCULPT_stroke_modifiers_check(C, ob, brush); + sculpt_update_cache_variants(C, sd, ob, itemptr); + sculpt_restore_mesh(sd, ob); + + if (sd->flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) { + float object_space_constant_detail = 1.0f / (sd->constant_detail * + mat4_to_scale(ob->object_to_world)); + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, object_space_constant_detail); + } + else if (sd->flags & SCULPT_DYNTOPO_DETAIL_BRUSH) { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, ss->cache->radius * sd->detail_percent / 100.0f); + } + else { + BKE_pbvh_bmesh_detail_size_set(ss->pbvh, + (ss->cache->radius / ss->cache->dyntopo_pixel_radius) * + (sd->detail_size * U.pixelsize) / 0.4f); + } + + if (SCULPT_stroke_is_dynamic_topology(ss, brush)) { + do_symmetrical_brush_actions(sd, ob, sculpt_topology_update, ups, &tool_settings->paint_mode); + } + + do_symmetrical_brush_actions(sd, ob, do_brush_action, ups, &tool_settings->paint_mode); + sculpt_combine_proxies(sd, ob); + + /* Hack to fix noise texture tearing mesh. */ + sculpt_fix_noise_tear(sd, ob); + + /* TODO(sergey): This is not really needed for the solid shading, + * which does use pBVH drawing anyway, but texture and wireframe + * requires this. + * + * Could be optimized later, but currently don't think it's so + * much common scenario. + * + * Same applies to the DEG_id_tag_update() invoked from + * sculpt_flush_update_step(). + */ + if (ss->deform_modifiers_active) { + SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool)); + } + else if (ss->shapekey_active) { + sculpt_update_keyblock(ob); + } + + ss->cache->first_time = false; + copy_v3_v3(ss->cache->true_last_location, ss->cache->true_location); + + /* Cleanup. */ + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_MASK); + } + else if (SCULPT_tool_is_paint(brush->sculpt_tool)) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_step(C, SCULPT_UPDATE_IMAGE); + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COLOR); + } + } + else { + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + } +} + +static void sculpt_brush_exit_tex(Sculpt *sd) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + MTex *mtex = &brush->mtex; + + if (mtex->tex && mtex->tex->nodetree) { + ntreeTexEndExecTree(mtex->tex->nodetree->execdata); + } +} + +static void sculpt_stroke_done(const bContext *C, PaintStroke * /*stroke*/) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + ToolSettings *tool_settings = CTX_data_tool_settings(C); + + /* Finished. */ + if (!ss->cache) { + sculpt_brush_exit_tex(sd); + return; + } + UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; + Brush *brush = BKE_paint_brush(&sd->paint); + BLI_assert(brush == ss->cache->brush); /* const, so we shouldn't change. */ + ups->draw_inverted = false; + + SCULPT_stroke_modifiers_check(C, ob, brush); + + /* Alt-Smooth. */ + if (ss->cache->alt_smooth) { + smooth_brush_toggle_off(C, &sd->paint, ss->cache); + /* Refresh the brush pointer in case we switched brush in the toggle function. */ + brush = BKE_paint_brush(&sd->paint); + } + + if (SCULPT_is_automasking_enabled(sd, ss, brush)) { + SCULPT_automasking_cache_free(ss->cache->automasking); + } + + BKE_pbvh_node_color_buffer_free(ss->pbvh); + SCULPT_cache_free(ss->cache); + ss->cache = nullptr; + + sculpt_stroke_undo_end(C, brush); + + if (brush->sculpt_tool == SCULPT_TOOL_MASK) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + } + else if (brush->sculpt_tool == SCULPT_TOOL_PAINT) { + if (SCULPT_use_image_paint_brush(&tool_settings->paint_mode, ob)) { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_IMAGE); + } + else { + BKE_sculpt_attributes_destroy_temporary_stroke(ob); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COLOR); + } + } + else { + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + } + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + sculpt_brush_exit_tex(sd); +} + +static int sculpt_brush_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + PaintStroke *stroke; + int ignore_background_click; + int retval; + Object *ob = CTX_data_active_object(C); + + /* Test that ob is visible; otherwise we won't be able to get evaluated data + * from the depsgraph. We do this here instead of SCULPT_mode_poll + * to avoid falling through to the translate operator in the + * global view3d keymap. + * + * NOTE: #BKE_object_is_visible_in_viewport is not working here (it returns false + * if the object is in local view); instead, test for OB_HIDE_VIEWPORT directly. + */ + + if (ob->visibility_flag & OB_HIDE_VIEWPORT) { + return OPERATOR_CANCELLED; + } + + sculpt_brush_stroke_init(C, op); + + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + SculptSession *ss = ob->sculpt; + + if (SCULPT_tool_is_paint(brush->sculpt_tool) && + !SCULPT_handles_colors_report(ob->sculpt, op->reports)) { + return OPERATOR_CANCELLED; + } + if (SCULPT_tool_is_mask(brush->sculpt_tool)) { + MultiresModifierData *mmd = BKE_sculpt_multires_active(ss->scene, ob); + BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), ob, mmd); + } + if (SCULPT_tool_is_face_sets(brush->sculpt_tool)) { + Mesh *mesh = BKE_object_get_original_mesh(ob); + ss->face_sets = BKE_sculpt_face_sets_ensure(mesh); + } + + stroke = paint_stroke_new(C, + op, + SCULPT_stroke_get_location, + sculpt_stroke_test_start, + sculpt_stroke_update_step, + nullptr, + sculpt_stroke_done, + event->type); + + op->customdata = stroke; + + /* For tablet rotation. */ + ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click"); + const float mval[2] = {float(event->mval[0]), float(event->mval[1])}; + if (ignore_background_click && !over_mesh(C, op, mval)) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return OPERATOR_PASS_THROUGH; + } + + retval = op->type->modal(C, op, event); + if (ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { + paint_stroke_free(C, op, static_cast(op->customdata)); + return retval; + } + /* Add modal handler. */ + WM_event_add_modal_handler(C, op); + + OPERATOR_RETVAL_CHECK(retval); + BLI_assert(retval == OPERATOR_RUNNING_MODAL); + + return OPERATOR_RUNNING_MODAL; +} + +static int sculpt_brush_stroke_exec(bContext *C, wmOperator *op) +{ + sculpt_brush_stroke_init(C, op); + + op->customdata = paint_stroke_new(C, + op, + SCULPT_stroke_get_location, + sculpt_stroke_test_start, + sculpt_stroke_update_step, + nullptr, + sculpt_stroke_done, + 0); + + /* Frees op->customdata. */ + paint_stroke_exec(C, op, static_cast(op->customdata)); + + return OPERATOR_FINISHED; +} + +static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + const Brush *brush = BKE_paint_brush(&sd->paint); + + /* XXX Canceling strokes that way does not work with dynamic topology, + * user will have to do real undo for now. See T46456. */ + if (ss->cache && !SCULPT_stroke_is_dynamic_topology(ss, brush)) { + paint_mesh_restore_co(sd, ob); + } + + paint_stroke_cancel(C, op, static_cast(op->customdata)); + + if (ss->cache) { + SCULPT_cache_free(ss->cache); + ss->cache = nullptr; + } + + sculpt_brush_exit_tex(sd); +} + +static int sculpt_brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + bool started = op->customdata && paint_stroke_started((PaintStroke *)op->customdata); + + int retval = paint_stroke_modal(C, op, event, (PaintStroke **)&op->customdata); + + if (!started && ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) { + /* Did the stroke never start? If so push a blank sculpt undo + * step to prevent a global undo step (which is triggered by the + * #OPTYPE_UNDO flag in #SCULPT_OT_brush_stroke). + * + * Having blank global undo steps interleaved with sculpt steps + * corrupts the DynTopo undo stack. + * See T101430. + * + * NOTE: simply returning #OPERATOR_CANCELLED was not + * sufficient to prevent this. */ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + sculpt_stroke_undo_begin(C, op); + sculpt_stroke_undo_end(C, brush); + } + + return retval; +} + +static void sculpt_redo_empty_ui(bContext * /*C*/, wmOperator * /*op*/) +{ +} + +void SCULPT_OT_brush_stroke(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Sculpt"; + ot->idname = "SCULPT_OT_brush_stroke"; + ot->description = "Sculpt a stroke into the geometry"; + + /* API callbacks. */ + ot->invoke = sculpt_brush_stroke_invoke; + ot->modal = sculpt_brush_stroke_modal; + ot->exec = sculpt_brush_stroke_exec; + ot->poll = SCULPT_poll; + ot->cancel = sculpt_brush_stroke_cancel; + ot->ui = sculpt_redo_empty_ui; + + /* Flags (sculpt does own undo? (ton)). */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; + + /* Properties. */ + + paint_stroke_operator_properties(ot); + + RNA_def_boolean(ot->srna, + "ignore_background_click", + 0, + "Ignore Background Click", + "Clicks on the background do not start the stroke"); +} + +/* Fake Neighbors. */ +/* This allows the sculpt tools to work on meshes with multiple connected components as they had + * only one connected component. When initialized and enabled, the sculpt API will return extra + * connectivity neighbors that are not in the real mesh. These neighbors are calculated for each + * vertex using the minimum distance to a vertex that is in a different connected component. */ + +/* The fake neighbors first need to be ensured to be initialized. + * After that tools which needs fake neighbors functionality need to + * temporarily enable it: + * + * void my_awesome_sculpt_tool() { + * SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max); + * SCULPT_fake_neighbors_enable(ob); + * + * ... Logic of the tool ... + * SCULPT_fake_neighbors_disable(ob); + * } + * + * Such approach allows to keep all the connectivity information ready for reuse + * (without having lag prior to every stroke), but also makes it so the affect + * is localized to a specific brushes and tools only. */ + +enum { + SCULPT_TOPOLOGY_ID_NONE, + SCULPT_TOPOLOGY_ID_DEFAULT, +}; + +static int SCULPT_vertex_get_connected_component(SculptSession *ss, PBVHVertRef vertex) +{ + if (ss->vertex_info.connected_component) { + return ss->vertex_info.connected_component[vertex.i]; + } + return SCULPT_TOPOLOGY_ID_DEFAULT; +} + +static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist) +{ + const int totvert = SCULPT_vertex_count_get(ss); + ss->fake_neighbors.fake_neighbor_index = static_cast( + MEM_malloc_arrayN(totvert, sizeof(int), "fake neighbor")); + for (int i = 0; i < totvert; i++) { + ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE; + } + + ss->fake_neighbors.current_max_distance = max_dist; +} + +static void SCULPT_fake_neighbor_add(SculptSession *ss, PBVHVertRef v_a, PBVHVertRef v_b) +{ + int v_index_a = BKE_pbvh_vertex_to_index(ss->pbvh, v_a); + int v_index_b = BKE_pbvh_vertex_to_index(ss->pbvh, v_b); + + if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) { + ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b; + ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a; + } +} + +static void sculpt_pose_fake_neighbors_free(SculptSession *ss) +{ + MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index); +} + +struct NearestVertexFakeNeighborTLSData { + PBVHVertRef nearest_vertex; + float nearest_vertex_distance_squared; + int current_topology_id; +}; + +static void do_fake_neighbor_search_task_cb(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = static_cast(userdata); + SculptSession *ss = data->ob->sculpt; + NearestVertexFakeNeighborTLSData *nvtd = static_cast( + tls->userdata_chunk); + PBVHVertexIter vd; + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.vertex); + if (vd_topology_id != nvtd->current_topology_id && + ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) { + float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co); + if (distance_squared < nvtd->nearest_vertex_distance_squared && + distance_squared < data->max_distance_squared) { + nvtd->nearest_vertex = vd.vertex; + nvtd->nearest_vertex_distance_squared = distance_squared; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void fake_neighbor_search_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + NearestVertexFakeNeighborTLSData *join = static_cast( + chunk_join); + NearestVertexFakeNeighborTLSData *nvtd = static_cast(chunk); + if (join->nearest_vertex.i == PBVH_REF_NONE) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } + else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) { + join->nearest_vertex = nvtd->nearest_vertex; + join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; + } +} + +static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd, + Object *ob, + const PBVHVertRef vertex, + float max_distance) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes = nullptr; + int totnode; + SculptSearchSphereData data{}; + data.ss = ss; + data.sd = sd; + data.radius_squared = max_distance * max_distance; + data.original = false; + data.center = SCULPT_vertex_co_get(ss, vertex); + + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); + + if (totnode == 0) { + return BKE_pbvh_make_vref(PBVH_REF_NONE); + } + + SculptThreadedTaskData task_data{}; + task_data.sd = sd; + task_data.ob = ob; + task_data.nodes = nodes; + task_data.max_distance_squared = max_distance * max_distance; + + copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, vertex)); + + NearestVertexFakeNeighborTLSData nvtd; + nvtd.nearest_vertex.i = -1; + nvtd.nearest_vertex_distance_squared = FLT_MAX; + nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, vertex); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + settings.func_reduce = fake_neighbor_search_reduce; + settings.userdata_chunk = &nvtd; + settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData); + BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings); + + MEM_SAFE_FREE(nodes); + + return nvtd.nearest_vertex; +} + +struct SculptTopologyIDFloodFillData { + int next_id; +}; + +static bool SCULPT_connected_components_floodfill_cb( + SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool /*is_duplicate*/, void *userdata) +{ + SculptTopologyIDFloodFillData *data = static_cast(userdata); + + int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v); + int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v); + + ss->vertex_info.connected_component[from_v_i] = data->next_id; + ss->vertex_info.connected_component[to_v_i] = data->next_id; + return true; +} + +void SCULPT_connected_components_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + /* Topology IDs already initialized. They only need to be recalculated when the PBVH is + * rebuild. + */ + if (ss->vertex_info.connected_component) { + return; + } + + const int totvert = SCULPT_vertex_count_get(ss); + ss->vertex_info.connected_component = static_cast( + MEM_malloc_arrayN(totvert, sizeof(int), "topology ID")); + + for (int i = 0; i < totvert; i++) { + ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE; + } + + int next_id = 0; + for (int i = 0; i < totvert; i++) { + PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { + SculptFloodFill flood; + SCULPT_floodfill_init(ss, &flood); + SCULPT_floodfill_add_initial(&flood, vertex); + SculptTopologyIDFloodFillData data; + data.next_id = next_id; + SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data); + SCULPT_floodfill_free(&flood); + next_id++; + } + } +} + +void SCULPT_boundary_info_ensure(Object *object) +{ + SculptSession *ss = object->sculpt; + if (ss->vertex_info.boundary) { + return; + } + + Mesh *base_mesh = BKE_mesh_from_object(object); + const MEdge *edges = BKE_mesh_edges(base_mesh); + const MPoly *polys = BKE_mesh_polys(base_mesh); + const MLoop *loops = BKE_mesh_loops(base_mesh); + + ss->vertex_info.boundary = BLI_BITMAP_NEW(base_mesh->totvert, "Boundary info"); + int *adjacent_faces_edge_count = static_cast( + MEM_calloc_arrayN(base_mesh->totedge, sizeof(int), "Adjacent face edge count")); + + for (int p = 0; p < base_mesh->totpoly; p++) { + const MPoly *poly = &polys[p]; + for (int l = 0; l < poly->totloop; l++) { + const MLoop *loop = &loops[l + poly->loopstart]; + adjacent_faces_edge_count[loop->e]++; + } + } + + for (int e = 0; e < base_mesh->totedge; e++) { + if (adjacent_faces_edge_count[e] < 2) { + const MEdge *edge = &edges[e]; + BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v1, true); + BLI_BITMAP_SET(ss->vertex_info.boundary, edge->v2, true); + } + } + + MEM_freeN(adjacent_faces_edge_count); +} + +void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + + /* Fake neighbors were already initialized with the same distance, so no need to be + * recalculated. + */ + if (ss->fake_neighbors.fake_neighbor_index && + ss->fake_neighbors.current_max_distance == max_dist) { + return; + } + + SCULPT_connected_components_ensure(ob); + SCULPT_fake_neighbor_init(ss, max_dist); + + for (int i = 0; i < totvert; i++) { + const PBVHVertRef from_v = BKE_pbvh_index_to_vertex(ss->pbvh, i); + + /* This vertex does not have a fake neighbor yet, search one for it. */ + if (ss->fake_neighbors.fake_neighbor_index[i] == FAKE_NEIGHBOR_NONE) { + const PBVHVertRef to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); + if (to_v.i != PBVH_REF_NONE) { + /* Add the fake neighbor if available. */ + SCULPT_fake_neighbor_add(ss, from_v, to_v); + } + } + } +} + +void SCULPT_fake_neighbors_enable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + ss->fake_neighbors.use_fake_neighbors = true; +} + +void SCULPT_fake_neighbors_disable(Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->fake_neighbors.fake_neighbor_index != nullptr); + ss->fake_neighbors.use_fake_neighbors = false; +} + +void SCULPT_fake_neighbors_free(Object *ob) +{ + SculptSession *ss = ob->sculpt; + sculpt_pose_fake_neighbors_free(ss); +} + +void SCULPT_automasking_node_begin(Object *ob, + const SculptSession * /*ss*/, + AutomaskingCache *automasking, + AutomaskingNodeData *automask_data, + PBVHNode *node) +{ + if (!automasking) { + memset(automask_data, 0, sizeof(*automask_data)); + return; + } + + automask_data->node = node; + automask_data->have_orig_data = automasking->settings.flags & + (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL); + + if (automask_data->have_orig_data) { + SCULPT_orig_vert_data_init(&automask_data->orig_data, ob, node, SCULPT_UNDO_COORDS); + } + else { + memset(&automask_data->orig_data, 0, sizeof(automask_data->orig_data)); + } +} + +void SCULPT_automasking_node_update(SculptSession * /*ss*/, + AutomaskingNodeData *automask_data, + PBVHVertexIter *vd) +{ + if (automask_data->have_orig_data) { + SCULPT_orig_vert_data_update(&automask_data->orig_data, vd); + } +} + +bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool original) +{ + float ray_start[3], ray_end[3], ray_normal[3], face_normal[3]; + float co[3]; + + copy_v3_v3(co, SCULPT_vertex_co_get(ss, vertex)); + float mouse[2]; + + ED_view3d_project_float_v2_m4(ss->cache->vc->region, co, mouse, ss->cache->projection_mat); + + int depth = SCULPT_raycast_init(ss->cache->vc, mouse, ray_end, ray_start, ray_normal, original); + + negate_v3(ray_normal); + + copy_v3_v3(ray_start, SCULPT_vertex_co_get(ss, vertex)); + madd_v3_v3fl(ray_start, ray_normal, 0.002); + + SculptRaycastData srd = {0}; + srd.original = original; + srd.ss = ss; + srd.hit = false; + srd.ray_start = ray_start; + srd.ray_normal = ray_normal; + srd.depth = depth; + srd.face_normal = face_normal; + + isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); + BKE_pbvh_raycast(ss->pbvh, sculpt_raycast_cb, &srd, ray_start, ray_normal, srd.original); + + return srd.hit; +} + +void SCULPT_stroke_id_next(Object *ob) +{ + /* Manually wrap in int32 space to avoid tripping up undefined behavior + * sanitizers. + */ + ob->sculpt->stroke_id = uchar((int(ob->sculpt->stroke_id) + 1) & 255); +} + +void SCULPT_stroke_id_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + + if (!ss->attrs.automasking_stroke_id) { + SculptAttributeParams params = {0}; + ss->attrs.automasking_stroke_id = BKE_sculpt_attribute_ensure( + ob, + ATTR_DOMAIN_POINT, + CD_PROP_INT8, + SCULPT_ATTRIBUTE_NAME(automasking_stroke_id), + ¶ms); + } +} + +/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index bf47b64d176..852b3c2719a 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -341,7 +341,7 @@ typedef struct SculptBrushTest { float radius; float location[3]; float dist; - int mirror_symmetry_pass; + ePaintSymmetryFlags mirror_symmetry_pass; int radial_symmetry_pass; float symm_rot_mat_inv[4][4]; @@ -556,7 +556,8 @@ typedef struct StrokeCache { /* Symmetry index between 0 and 7 bit combo 0 is Brush only; * 1 is X mirror; 2 is Y mirror; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */ int symmetry; - int mirror_symmetry_pass; /* The symmetry pass we are currently on between 0 and 7. */ + ePaintSymmetryFlags + mirror_symmetry_pass; /* The symmetry pass we are currently on between 0 and 7. */ float true_view_normal[3]; float view_normal[3]; @@ -1526,7 +1527,10 @@ bool SCULPT_pbvh_calc_area_normal(const struct Brush *brush, * Flip all the edit-data across the axis/axes specified by \a symm. * Used to calculate multiple modifications to the mesh when symmetry is enabled. */ -void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, char symm, char axis, float angle); +void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, + ePaintSymmetryFlags symm, + char axis, + float angle); void SCULPT_cache_free(StrokeCache *cache); /* -------------------------------------------------------------------- */ diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 8d02b274c65..274b2094ee7 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -2302,6 +2302,7 @@ typedef enum ePaintSymmetryFlags { PAINT_TILE_Y = (1 << 5), PAINT_TILE_Z = (1 << 6), } ePaintSymmetryFlags; +ENUM_OPERATORS(ePaintSymmetryFlags, PAINT_TILE_Z); #define PAINT_SYMM_AXIS_ALL (PAINT_SYMM_X | PAINT_SYMM_Y | PAINT_SYMM_Z) -- cgit v1.2.3