/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2006 by Nicholas Bishop * All rights reserved. * Implements the Sculpt Mode tools */ /** \file * \ingroup edsculpt */ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_dial_2d.h" #include "BLI_ghash.h" #include "BLI_gsqueue.h" #include "BLI_hash.h" #include "BLI_math.h" #include "BLI_math_color_blend.h" #include "BLI_task.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "PIL_time.h" #include "DNA_brush_types.h" #include "DNA_customdata_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "BKE_brush.h" #include "BKE_ccg.h" #include "BKE_colortools.h" #include "BKE_context.h" #include "BKE_image.h" #include "BKE_kelvinlet.h" #include "BKE_key.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_mirror.h" #include "BKE_modifier.h" #include "BKE_multires.h" #include "BKE_node.h" #include "BKE_object.h" #include "BKE_paint.h" #include "BKE_particle.h" #include "BKE_pbvh.h" #include "BKE_pointcache.h" #include "BKE_report.h" #include "BKE_scene.h" #include "BKE_screen.h" #include "BKE_subdiv_ccg.h" #include "BKE_subsurf.h" #include "DEG_depsgraph.h" #include "IMB_colormanagement.h" #include "GPU_immediate.h" #include "GPU_immediate_util.h" #include "GPU_matrix.h" #include "GPU_state.h" #include "WM_api.h" #include "WM_message.h" #include "WM_toolsystem.h" #include "WM_types.h" #include "ED_object.h" #include "ED_screen.h" #include "ED_sculpt.h" #include "ED_space_api.h" #include "ED_view3d.h" #include "paint_intern.h" #include "sculpt_intern.h" #include "RNA_access.h" #include "RNA_define.h" #include "UI_interface.h" #include "UI_resources.h" #include "bmesh.h" #include "bmesh_tools.h" #include #include #include /* 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_vertices(ss->pbvh); } return 0; } const float *SCULPT_vertex_co_get(SculptSession *ss, int index) { 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[index].co; } return ss->mvert[index].co; } case PBVH_BMESH: return BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->co; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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; } const float *SCULPT_vertex_color_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: if (ss->vcol) { return ss->vcol[index].color; } break; case PBVH_BMESH: case PBVH_GRIDS: break; } return NULL; } void SCULPT_vertex_normal_get(SculptSession *ss, int index, float no[3]) { 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); normal_short_to_float_v3(no, mverts[index].no); } else { normal_short_to_float_v3(no, ss->mvert[index].no); } break; } case PBVH_BMESH: copy_v3_v3(no, BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->no); break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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, int index) { if (ss->persistent_base) { return ss->persistent_base[index].co; } return SCULPT_vertex_co_get(ss, index); } const float *SCULPT_vertex_co_for_grab_active_get(SculptSession *ss, int index) { /* 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[index].co; } /* Sculpting on the base mesh. */ if (ss->mvert) { return ss->mvert[index].co; } /* Everything else, such as sculpting on multires. */ return SCULPT_vertex_co_get(ss, index); } void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, 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, index)); break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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, int index, float no[3]) { if (ss->persistent_base) { copy_v3_v3(no, ss->persistent_base[index].no); return; } SCULPT_vertex_normal_get(ss, index, no); } float SCULPT_vertex_mask_get(SculptSession *ss, int index) { BMVert *v; float *mask; switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: return ss->vmask[index]; case PBVH_BMESH: v = BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index); mask = BM_ELEM_CD_GET_VOID_P(v, CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK)); return *mask; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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; } int SCULPT_active_vertex_get(SculptSession *ss) { if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) { return ss->active_vertex_index; } return 0; } 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: return ss->face_sets[ss->active_face_index]; case PBVH_GRIDS: { 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, int index, bool visible) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: SET_FLAG_FROM_TEST(ss->mvert[index].flag, !visible, ME_HIDE); ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE; break; case PBVH_BMESH: BM_elem_flag_set(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN, !visible); break; case PBVH_GRIDS: break; } } bool SCULPT_vertex_visible_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: return !(ss->mvert[index].flag & ME_HIDE); case PBVH_BMESH: return !BM_elem_flag_test(BM_vert_at_index(ss->bm, index), BM_ELEM_HIDDEN); case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: for (int i = 0; i < ss->totfaces; i++) { if (abs(ss->face_sets[i]) == face_set) { if (visible) { ss->face_sets[i] = abs(ss->face_sets[i]); } else { ss->face_sets[i] = -abs(ss->face_sets[i]); } } } break; case PBVH_BMESH: break; } } void SCULPT_face_sets_visibility_invert(SculptSession *ss) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: for (int i = 0; i < ss->totfaces; i++) { ss->face_sets[i] *= -1; } break; case PBVH_BMESH: break; } } void SCULPT_face_sets_visibility_all_set(SculptSession *ss, bool visible) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: case PBVH_GRIDS: for (int i = 0; i < ss->totfaces; i++) { /* This can run on geometry without a face set assigned, so its ID sign can't be changed to * modify the visibility. Force that geometry to the ID 1 to enable changing the visibility * here. */ if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { ss->face_sets[i] = 1; } if (visible) { ss->face_sets[i] = abs(ss->face_sets[i]); } else { ss->face_sets[i] = -abs(ss->face_sets[i]); } } break; case PBVH_BMESH: break; } } bool SCULPT_vertex_any_face_set_visible_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { MeshElemMap *vert_map = &ss->pmap[index]; for (int j = 0; j < ss->pmap[index].count; j++) { if (ss->face_sets[vert_map->indices[j]] > 0) { return true; } } return false; } case PBVH_BMESH: return true; case PBVH_GRIDS: return true; } return true; } bool SCULPT_vertex_all_face_sets_visible_get(const SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { MeshElemMap *vert_map = &ss->pmap[index]; for (int j = 0; j < ss->pmap[index].count; j++) { if (ss->face_sets[vert_map->indices[j]] < 0) { return false; } } return true; } case PBVH_BMESH: return true; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / 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] > 0; } } return true; } void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { MeshElemMap *vert_map = &ss->pmap[index]; for (int j = 0; j < ss->pmap[index].count; j++) { if (ss->face_sets[vert_map->indices[j]] > 0) { ss->face_sets[vert_map->indices[j]] = abs(face_set); } } } break; case PBVH_BMESH: break; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int face_index = BKE_subdiv_ccg_grid_to_face_index(ss->subdiv_ccg, grid_index); if (ss->face_sets[face_index] > 0) { ss->face_sets[face_index] = abs(face_set); } } break; } } int SCULPT_vertex_face_set_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { MeshElemMap *vert_map = &ss->pmap[index]; int face_set = 0; for (int i = 0; i < ss->pmap[index].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: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / 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, int index, int face_set) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { MeshElemMap *vert_map = &ss->pmap[index]; for (int i = 0; i < ss->pmap[index].count; i++) { if (ss->face_sets[vert_map->indices[i]] == face_set) { return true; } } return false; } case PBVH_BMESH: return true; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / 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_face_sets_to_vertices(Object *ob) { SculptSession *ss = ob->sculpt; Mesh *mesh = BKE_object_get_original_mesh(ob); switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { BKE_sculpt_sync_face_sets_visibility_to_base_mesh(mesh); break; } case PBVH_GRIDS: { BKE_sculpt_sync_face_sets_visibility_to_base_mesh(mesh); BKE_sculpt_sync_face_sets_visibility_to_grids(mesh, ss->subdiv_ccg); break; } case PBVH_BMESH: break; } } static void UNUSED_FUNCTION(sculpt_visibility_sync_vertex_to_face_sets)(SculptSession *ss, int index) { MeshElemMap *vert_map = &ss->pmap[index]; const bool visible = SCULPT_vertex_visible_get(ss, index); for (int i = 0; i < ss->pmap[index].count; i++) { if (visible) { ss->face_sets[vert_map->indices[i]] = abs(ss->face_sets[vert_map->indices[i]]); } else { ss->face_sets[vert_map->indices[i]] = -abs(ss->face_sets[vert_map->indices[i]]); } } ss->mvert[index].flag |= ME_VERT_PBVH_UPDATE; } void SCULPT_visibility_sync_all_vertex_to_face_sets(SculptSession *ss) { if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { for (int i = 0; i < ss->totfaces; i++) { MPoly *poly = &ss->mpoly[i]; bool poly_visible = true; for (int l = 0; l < poly->totloop; l++) { MLoop *loop = &ss->mloop[poly->loopstart + l]; if (!SCULPT_vertex_visible_get(ss, (int)loop->v)) { poly_visible = false; } } if (poly_visible) { ss->face_sets[i] = abs(ss->face_sets[i]); } else { ss->face_sets[i] = -abs(ss->face_sets[i]); } } } } static bool sculpt_check_unique_face_set_in_base_mesh(SculptSession *ss, int index) { MeshElemMap *vert_map = &ss->pmap[index]; int face_set = -1; for (int i = 0; i < ss->pmap[index].count; i++) { if (face_set == -1) { face_set = abs(ss->face_sets[vert_map->indices[i]]); } else { if (abs(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) { MeshElemMap *vert_map = &ss->pmap[v1]; int p1 = -1, p2 = -1; for (int i = 0; i < ss->pmap[v1].count; i++) { MPoly *p = &ss->mpoly[vert_map->indices[i]]; for (int l = 0; l < p->totloop; l++) { 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, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { return sculpt_check_unique_face_set_in_base_mesh(ss, index); } case PBVH_BMESH: return false; case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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: { int next_face_set = 0; for (int i = 0; i < ss->totfaces; i++) { if (abs(ss->face_sets[i]) > next_face_set) { next_face_set = abs(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, int neighbor_index) { for (int i = 0; i < iter->size; i++) { if (iter->neighbors[i] == neighbor_index) { 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(int), "neighbor array"); memcpy(iter->neighbors, iter->neighbors_fixed, sizeof(int) * iter->size); } else { iter->neighbors = MEM_reallocN_id( iter->neighbors, iter->capacity * sizeof(int), "neighbor array"); } } iter->neighbors[iter->size] = neighbor_index; iter->size++; } static void sculpt_vertex_neighbors_get_bmesh(SculptSession *ss, int index, SculptVertexNeighborIter *iter) { BMVert *v = BM_vert_at_index(ss->bm, index); BMIter liter; BMLoop *l; iter->size = 0; iter->num_duplicates = 0; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_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 (BM_elem_index_get(v_other) != (int)index) { sculpt_vertex_neighbor_add(iter, BM_elem_index_get(v_other)); } } } } static void sculpt_vertex_neighbors_get_faces(SculptSession *ss, int index, SculptVertexNeighborIter *iter) { MeshElemMap *vert_map = &ss->pmap[index]; iter->size = 0; iter->num_duplicates = 0; iter->capacity = SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY; iter->neighbors = iter->neighbors_fixed; for (int i = 0; i < ss->pmap[index].count; i++) { const MPoly *p = &ss->mpoly[vert_map->indices[i]]; uint f_adj_v[2]; if (poly_get_adj_loops_from_vert(p, ss->mloop, index, f_adj_v) != -1) { for (int j = 0; j < ARRAY_SIZE(f_adj_v); j += 1) { if (f_adj_v[j] != index) { sculpt_vertex_neighbor_add(iter, 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[index] != FAKE_NEIGHBOR_NONE) { sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); } } } static void sculpt_vertex_neighbors_get_grids(SculptSession *ss, const int index, 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 = index / key->grid_area; const int vertex_index = index - 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; for (int i = 0; i < neighbors.size; i++) { sculpt_vertex_neighbor_add(iter, neighbors.coords[i].grid_index * key->grid_area + neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x); } if (ss->fake_neighbors.use_fake_neighbors) { BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL); if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) { sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]); } } if (neighbors.coords != neighbors.coords_fixed) { MEM_freeN(neighbors.coords); } } void SCULPT_vertex_neighbors_get(SculptSession *ss, const int index, const bool include_duplicates, SculptVertexNeighborIter *iter) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: sculpt_vertex_neighbors_get_faces(ss, index, iter); return; case PBVH_BMESH: sculpt_vertex_neighbors_get_bmesh(ss, index, iter); return; case PBVH_GRIDS: sculpt_vertex_neighbors_get_grids(ss, index, 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 int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: { if (!SCULPT_vertex_all_face_sets_visible_get(ss, index)) { return true; } return sculpt_check_boundary_vertex_in_base_mesh(ss, index); } case PBVH_BMESH: { BMVert *v = BM_vert_at_index(ss->bm, index); return BM_vert_is_boundary(v); } case PBVH_GRIDS: { const CCGKey *key = BKE_pbvh_get_grid_key(ss->pbvh); const int grid_index = index / key->grid_area; const int vertex_index = index - 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 */ /** * Returns true when the step belongs to the stroke that is directly performed by the brush and * not by one of the symmetry passes. */ bool SCULPT_stroke_is_main_symmetry_pass(StrokeCache *cache) { return cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0 && cache->tile_pass == 0; } /** * Return true only once per stroke on the first symmetry pass, regardless of the symmetry passes * enabled. * * This should be used for functionality that needs to be computed once per stroke of a particular * tool (allocating memory, updating random seeds...). */ 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; } /** * Returns true on the first brush step of each symmetry pass. */ 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 { int nearest_vertex_index; 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_index = vd.index; 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_index == -1) { join->nearest_vertex_index = nvtd->nearest_vertex_index; 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_index = nvtd->nearest_vertex_index; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } } int 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 -1; } 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_index = -1; 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_index; } bool SCULPT_is_symmetry_iteration_valid(char i, char symm) { return i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))); } /* Checks if a vertex is inside the brush radius from any of its mirrored axis. */ 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)) { 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); View3D *v3d = CTX_wm_view3d(C); if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } } /* 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(int)); flood->visited_vertices = BLI_BITMAP_NEW(vertex_count, "visited vertices"); } void SCULPT_floodfill_add_initial(SculptFloodFill *flood, int index) { BLI_gsqueue_push(flood->queue, &index); } void SCULPT_floodfill_add_initial_with_symmetry( Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, int index, 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)) { int v = -1; if (i == 0) { v = index; } 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, index), i); v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); } if (v != -1) { 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)) { int v = -1; if (i == 0) { v = SCULPT_active_vertex_get(ss); } else if (radius > 0.0f) { float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius; float location[3]; flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), i); v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false); } if (v != -1) { SCULPT_floodfill_add_initial(flood, v); } } } } void SCULPT_floodfill_execute( SculptSession *ss, SculptFloodFill *flood, bool (*func)(SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata), void *userdata) { while (!BLI_gsqueue_is_empty(flood->queue)) { int from_v; BLI_gsqueue_pop(flood->queue, &from_v); SculptVertexNeighborIter ni; SCULPT_VERTEX_DUPLICATES_AND_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { const int to_v = ni.index; if (!BLI_BITMAP_TEST(flood->visited_vertices, to_v) && SCULPT_vertex_visible_get(ss, to_v)) { BLI_BITMAP_ENABLE(flood->visited_vertices, 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_vertices); BLI_gsqueue_free(flood->queue); flood->queue = NULL; } /* -------------------------------------------------------------------- */ /** \name Tool Capabilities * * Avoid duplicate checks, internal logic only, * share logic with #rna_def_sculpt_capabilities where possible. * * \{ */ /* Check if there are any active modifiers in stack. * Used for flushing updates at enter/exit sculpt mode. */ static bool sculpt_has_active_modifiers(Scene *scene, Object *ob) { ModifierData *md; VirtualModifierData virtualModifierData; md = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData); /* Exception for shape keys because we can edit those. */ for (; md; md = md->next) { if (BKE_modifier_is_enabled(scene, md, eModifierMode_Realtime)) { return true; } } return false; } 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, const Brush *brush) { return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && (ss->cache->normal_weight > 0.0f)) || 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); } typedef enum StrokeFlags { CLIP_X = 1, CLIP_Y = 2, CLIP_Z = 4, } StrokeFlags; /** * Initialize a #SculptOrigVertData for accessing original vertex data; * handles #BMesh, #Mesh, and multi-resolution. */ 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; } } /** * Initialize a #SculptOrigVertData for accessing original vertex data; * handles #BMesh, #Mesh, and multi-resolution. */ void SCULPT_orig_vert_data_init(SculptOrigVertData *data, Object *ob, PBVHNode *node) { SculptUndoNode *unode; unode = SCULPT_undo_push_node(ob, node, SCULPT_UNDO_COORDS); SCULPT_orig_vert_data_unode_init(data, ob, unode); } /** * Update a #SculptOrigVertData for a particular vertex from the PBVH iterator. */ 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); } } static void sculpt_rake_rotate(const SculptSession *ss, const float sculpt_co[3], const float v_co[3], float factor, float r_delta[3]) { float vec_rot[3]; #if 0 /* lerp */ sub_v3_v3v3(vec_rot, v_co, sculpt_co); mul_qt_v3(ss->cache->rake_rotation_symmetry, vec_rot); add_v3_v3(vec_rot, sculpt_co); sub_v3_v3v3(r_delta, vec_rot, v_co); mul_v3_fl(r_delta, factor); #else /* slerp */ float q_interp[4]; sub_v3_v3v3(vec_rot, v_co, sculpt_co); copy_qt_qt(q_interp, ss->cache->rake_rotation_symmetry); pow_qt_fl_normalized(q_interp, factor); mul_qt_v3(q_interp, vec_rot); add_v3_v3(vec_rot, sculpt_co); sub_v3_v3v3(r_delta, vec_rot, v_co); #endif } /** * Align the grab delta to the brush normal. * * \param grab_delta: Typically from `ss->cache->grab_delta_symmetry`. */ static void sculpt_project_v3_normal_align(SculptSession *ss, const float normal_weight, float grab_delta[3]) { /* Signed to support grabbing in (to make a hole) as well as out. */ const float len_signed = dot_v3v3(ss->cache->sculpt_normal_symm, grab_delta); /* This scale effectively projects the offset so dragging follows the cursor, * as the normal points towards the view, the scale increases. */ float len_view_scale; { float view_aligned_normal[3]; project_plane_v3_v3v3( view_aligned_normal, ss->cache->sculpt_normal_symm, ss->cache->view_normal); len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss->cache->sculpt_normal_symm)); len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f; } mul_v3_fl(grab_delta, 1.0f - normal_weight); madd_v3_v3fl( grab_delta, ss->cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale); } /* -------------------------------------------------------------------- */ /** \name SculptProjectVector * * Fast-path for #project_plane_v3_v3v3 * * \{ */ typedef struct SculptProjectVector { float plane[3]; float len_sq; float len_sq_inv_neg; bool is_valid; } SculptProjectVector; /** * \param plane: Direction, can be any length. */ static void sculpt_project_v3_cache_init(SculptProjectVector *spvc, const float plane[3]) { copy_v3_v3(spvc->plane, plane); spvc->len_sq = len_squared_v3(spvc->plane); spvc->is_valid = (spvc->len_sq > FLT_EPSILON); spvc->len_sq_inv_neg = (spvc->is_valid) ? -1.0f / spvc->len_sq : 0.0f; } /** * Calculate the projection. */ static void sculpt_project_v3(const SculptProjectVector *spvc, const float vec[3], float r_vec[3]) { #if 0 project_plane_v3_v3v3(r_vec, vec, spvc->plane); #else /* inline the projection, cache `-1.0 / dot_v3_v3(v_proj, v_proj)` */ madd_v3_v3fl(r_vec, spvc->plane, dot_v3v3(vec, spvc->plane) * spvc->len_sq_inv_neg); #endif } /** \} */ /**********************************************************************/ /* Returns true if the stroke will use dynamic topology, false * otherwise. * * Factors: some brushes like grab cannot do dynamic topology. * Others, like smooth, are better without. * Same goes for alt-key smoothing. */ 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)); } /*** 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 = (data->brush->sculpt_tool == SCULPT_TOOL_MASK ? SCULPT_UNDO_MASK : SCULPT_UNDO_COORDS); if (ss->bm) { unode = SCULPT_undo_push_node(data->ob, data->nodes[n], type); } else { unode = SCULPT_undo_get_node(data->nodes[n]); } if (unode) { 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_short(vd.no, orig_data.no); } else { normal_short_to_float_v3(vd.fno, orig_data.no); } } else if (orig_data.unode->type == SCULPT_UNDO_MASK) { *vd.mask = orig_data.mask; } else if (orig_data.unode->type == SCULPT_UNDO_COLOR) { copy_v4_v4(vd.col, orig_data.col); } if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } BKE_pbvh_vertex_iter_end; BKE_pbvh_node_mark_update(data->nodes[n]); } } 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) { if (!BLI_rcti_is_empty(&ss->cache->previous_r)) { BLI_rcti_union(rect, &ss->cache->previous_r); } } } /* Get a screen-space rectangle of the modified area. */ 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) { if (sculpt_brush_test_clipping(test, co)) { return false; } test->dist = sqrtf(distsq); return true; } return false; } 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) { if (sculpt_brush_test_clipping(test, co)) { return false; } test->dist = distsq; return true; } return false; } 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) { if (sculpt_brush_test_clipping(test, co)) { return false; } test->dist = distsq; return true; } return false; } bool SCULPT_brush_test_cube(SculptBrushTest *test, const float co[3], const float local[4][4], const float roundness) { float side = M_SQRT1_2; 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) { /* Corner, distance to the center of the corner circle. */ if (min_ff(local_co[0], local_co[1]) > constant_side) { float r_point[3]; copy_v3_fl(r_point, constant_side); test->dist = len_v2v2(r_point, local_co) / falloff_side; return true; } /* Side, distance to the square XY axis. */ if (max_ff(local_co[0], local_co[1]) > constant_side) { 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; } /* Outside the square. */ return false; } 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 short no[3], const float fno[3]) { if (br->flag & BRUSH_FRONTFACE) { float dot; if (no) { float tmp[3]; normal_short_to_float_v3(tmp, no); dot = dot_v3v3(tmp, sculpt_normal); } else { dot = dot_v3v3(fno, sculpt_normal); } return dot > 0.0f ? dot : 0.0f; } return 1.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 void flip_v3(float v[3], const ePaintSymmetryFlags symm) { flip_v3_v3(v, v, symm); } static void flip_qt(float quat[3], const ePaintSymmetryFlags symm) { flip_qt_qt(quat, quat, symm); } 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) { float overlap; const int symm = cache->symmetry; overlap = 0.0f; for (int i = 0; i <= symm; i++) { if (i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5)))) { 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; } return 1.0f; } /* -------------------------------------------------------------------- */ /** \name Calculate Normal and Center * * Calculate geometry surrounding the brush center. * (optionally using original coordinates). * * Functions are: * - #calc_area_center * - #calc_area_normal * - #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); 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) { 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. */ short no_s[3]; if (use_original) { if (unode->bm_entry) { const float *temp_co; const short *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_short(no_s, temp_no_s); } else { copy_v3_v3(co, unode->co[vd.i]); copy_v3_v3_short(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) { float no[3]; int flip_index; data->any_vertex_sampled = true; if (use_original) { normal_short_to_float_v3(no, no_s); } else { if (vd.no) { normal_short_to_float_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); } static void 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) { 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); } /* Expose 'calc_area_normal' externally. */ 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; } /* This calculates flatten center and area normal together, * amortizing the memory bandwidth and loop overhead to calculate both at the same time. */ static void 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) { 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; } } } /** \} */ /** * 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 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(!"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; } } /* Return a multiplier for brush strength on a particular vertex. */ float SCULPT_brush_strength_factor(SculptSession *ss, const Brush *br, const float brush_point[3], const float len, const short vno[3], const float fno[3], const float mask, const int vertex_index, const int thread_id) { StrokeCache *cache = ss->cache; const Scene *scene = cache->vc->scene; const MTex *mtex = &br->mtex; 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, point, rgba, 0, ss->tex_pool); } else if (ss->texcache) { 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. */ flip_v3_v3(symm_point, point, cache->mirror_symmetry_pass); if (cache->radial_symmetry_pass) { mul_m4_v3(cache->symm_rot_mat_inv, symm_point); } 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 *= br->mtex.size[0]; y *= br->mtex.size[1]; x += br->mtex.ofs[0]; y += br->mtex.ofs[1]; avg = paint_get_tex_pixel(&br->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, 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_index); return avg; } /* Test AABB against sphere. */ 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; } /* 2D projection (distance to line). */ 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; } /** * Handles clipping against a mirror modifier and #SCULPT_LOCK_X/Y/Z axis flags. */ 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; } if (ss->cache && (ss->cache->flag & (CLIP_X << i)) && (fabsf(co[i]) <= ss->cache->clip_tolerance[i])) { co[i] = 0.0f; } 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_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], mval_f[2] = {0.0f, 1.0f}; float zfac; mul_v3_m4v3(loc, ob->imat, center); zfac = ED_view3d_calc_zfac(vc->rv3d, loc, NULL); ED_view3d_win_to_delta(vc->region, mval_f, y, zfac); normalize_v3(y); add_v3_v3(y, ob->loc); mul_m4_v3(ob->imat, 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->imat` is up to date. */ invert_m4_m4(ob->imat, ob->obmat); /* 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->obmat, 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->imat, 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); } } typedef struct { SculptSession *ss; const float *ray_start; const float *ray_normal; bool hit; float depth; bool original; int active_vertex_index; 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; static void do_topology_rake_bmesh_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; const Brush *brush = data->brush; float direction[3]; copy_v3_v3(direction, ss->cache->grab_delta_symmetry); float tmp[3]; mul_v3_v3fl( tmp, ss->cache->sculpt_normal_symm, dot_v3v3(ss->cache->sculpt_normal_symm, direction)); sub_v3_v3(direction, tmp); normalize_v3(direction); /* Cancel if there's no grab data. */ if (is_zero_v3(direction)) { return; } const float bstrength = clamp_f(data->strength, 0.0f, 1.0f); SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * SCULPT_brush_strength_factor( ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, *vd.mask, vd.index, thread_id) * ss->cache->pressure; float avg[3], val[3]; SCULPT_bmesh_four_neighbor_average(avg, direction, vd.bm_vert); sub_v3_v3v3(val, avg, vd.co); madd_v3_v3v3fl(val, vd.co, val, fade); SCULPT_clip(sd, ss, vd.co, val); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void bmesh_topology_rake( Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) { Brush *brush = BKE_paint_brush(&sd->paint); const float strength = clamp_f(bstrength, 0.0f, 1.0f); /* Interactions increase both strength and quality. */ const int iterations = 3; int iteration; const int count = iterations * strength + 1; const float factor = iterations * strength / count; for (iteration = 0; iteration <= count; iteration++) { SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .strength = factor, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_topology_rake_bmesh_task_cb_ex, &settings); } } static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float bstrength = ss->cache->bstrength; PBVHVertexIter vd; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = SCULPT_brush_strength_factor( ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, thread_id); if (bstrength > 0.0f) { (*vd.mask) += fade * bstrength * (1.0f - *vd.mask); } else { (*vd.mask) += fade * bstrength * (*vd.mask); } *vd.mask = clamp_f(*vd.mask, 0.0f, 1.0f); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } BKE_pbvh_vertex_iter_end; } } static void do_mask_brush_draw(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { Brush *brush = BKE_paint_brush(&sd->paint); /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_mask_brush_draw_task_cb_ex, &settings); } static void do_mask_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); switch ((BrushMaskTool)brush->mask_tool) { case BRUSH_MASK_DRAW: do_mask_brush_draw(sd, ob, nodes, totnode); break; case BRUSH_MASK_SMOOTH: SCULPT_smooth(sd, ob, nodes, totnode, ss->cache->bstrength, true); break; } } /* -------------------------------------------------------------------- */ /** \name Sculpt Multires Displacement Eraser Brush * \{ */ static void do_displacement_eraser_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float limit_co[3]; float disp[3]; SCULPT_vertex_limit_surface_get(ss, vd.index, limit_co); sub_v3_v3v3(disp, limit_co, vd.co); mul_v3_v3fl(proxy[vd.i], disp, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { Brush *brush = BKE_paint_brush(&sd->paint); BKE_curvemapping_init(brush->curve); /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_displacement_eraser_brush_task_cb_ex, &settings); } /** \} */ /** \name Sculpt Multires Displacement Smear Brush * \{ */ static void do_displacement_smear_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (!sculpt_brush_test_sq_fn(&test, vd.co)) { continue; } const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float current_disp[3]; float current_disp_norm[3]; float interp_limit_surface_disp[3]; copy_v3_v3(interp_limit_surface_disp, ss->cache->prev_displacement[vd.index]); switch (brush->smear_deform_type) { case BRUSH_SMEAR_DEFORM_DRAG: sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); break; case BRUSH_SMEAR_DEFORM_PINCH: sub_v3_v3v3(current_disp, ss->cache->location, vd.co); break; case BRUSH_SMEAR_DEFORM_EXPAND: sub_v3_v3v3(current_disp, vd.co, ss->cache->location); break; } normalize_v3_v3(current_disp_norm, current_disp); mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); float weights_accum = 1.0f; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { float vertex_disp[3]; float vertex_disp_norm[3]; float neighbor_limit_co[3]; SCULPT_vertex_limit_surface_get(ss, ni.index, neighbor_limit_co); sub_v3_v3v3(vertex_disp, ss->cache->limit_surface_co[ni.index], ss->cache->limit_surface_co[vd.index]); const float *neighbor_limit_surface_disp = ss->cache->prev_displacement[ni.index]; normalize_v3_v3(vertex_disp_norm, vertex_disp); if (dot_v3v3(current_disp_norm, vertex_disp_norm) < 0.0f) { const float disp_interp = clamp_f( -dot_v3v3(current_disp_norm, vertex_disp_norm), 0.0f, 1.0f); madd_v3_v3fl(interp_limit_surface_disp, neighbor_limit_surface_disp, disp_interp); weights_accum += disp_interp; } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); mul_v3_fl(interp_limit_surface_disp, 1.0f / weights_accum); float new_co[3]; add_v3_v3v3(new_co, ss->cache->limit_surface_co[vd.index], interp_limit_surface_disp); interp_v3_v3v3(vd.co, vd.co, new_co, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } BKE_pbvh_vertex_iter_end; } static void do_displacement_smear_store_prev_disp_task_cb_ex( void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { sub_v3_v3v3(ss->cache->prev_displacement[vd.index], SCULPT_vertex_co_get(ss, vd.index), ss->cache->limit_surface_co[vd.index]); } BKE_pbvh_vertex_iter_end; } static void do_displacement_smear_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; BKE_curvemapping_init(brush->curve); const int totvert = SCULPT_vertex_count_get(ss); if (!ss->cache->prev_displacement) { ss->cache->prev_displacement = MEM_malloc_arrayN( totvert, sizeof(float[3]), "prev displacement"); ss->cache->limit_surface_co = MEM_malloc_arrayN(totvert, sizeof(float[3]), "limit surface co"); for (int i = 0; i < totvert; i++) { SCULPT_vertex_limit_surface_get(ss, i, ss->cache->limit_surface_co[i]); sub_v3_v3v3(ss->cache->prev_displacement[i], SCULPT_vertex_co_get(ss, i), ss->cache->limit_surface_co[i]); } } /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range( 0, totnode, &data, do_displacement_smear_store_prev_disp_task_cb_ex, &settings); BLI_task_parallel_range(0, totnode, &data, do_displacement_smear_brush_task_cb_ex, &settings); } /** \} */ static void do_draw_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *offset = data->offset; PBVHVertexIter vd; float(*proxy)[3]; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* Offset vertex. */ const float fade = SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_draw_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float offset[3]; const float bstrength = ss->cache->bstrength; /* Offset with as much as possible factored in already. */ float effective_normal[3]; SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); mul_v3_v3fl(offset, effective_normal, ss->cache->radius); mul_v3_v3(offset, ss->cache->scale); mul_v3_fl(offset, bstrength); /* XXX - this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise * initialize before threads so they can do curve mapping. */ BKE_curvemapping_init(brush->curve); /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .offset = offset, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_draw_brush_task_cb_ex, &settings); } static void do_draw_sharp_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *offset = data->offset; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { /* Offset vertex. */ const float fade = SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float offset[3]; const float bstrength = ss->cache->bstrength; /* Offset with as much as possible factored in already. */ float effective_normal[3]; SCULPT_tilt_effective_normal_get(ss, brush, effective_normal); mul_v3_v3fl(offset, effective_normal, ss->cache->radius); mul_v3_v3(offset, ss->cache->scale); mul_v3_fl(offset, bstrength); /* XXX - this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise * initialize before threads so they can do curve mapping. */ BKE_curvemapping_init(brush->curve); /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .offset = offset, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_draw_sharp_brush_task_cb_ex, &settings); } /* -------------------------------------------------------------------- */ /** \name Sculpt Topology Brush * \{ */ static void do_topology_slide_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float current_disp[3]; float current_disp_norm[3]; float final_disp[3] = {0.0f, 0.0f, 0.0f}; switch (brush->slide_deform_type) { case BRUSH_SLIDE_DEFORM_DRAG: sub_v3_v3v3(current_disp, ss->cache->location, ss->cache->last_location); break; case BRUSH_SLIDE_DEFORM_PINCH: sub_v3_v3v3(current_disp, ss->cache->location, vd.co); break; case BRUSH_SLIDE_DEFORM_EXPAND: sub_v3_v3v3(current_disp, vd.co, ss->cache->location); break; } normalize_v3_v3(current_disp_norm, current_disp); mul_v3_v3fl(current_disp, current_disp_norm, ss->cache->bstrength); SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { float vertex_disp[3]; float vertex_disp_norm[3]; sub_v3_v3v3(vertex_disp, SCULPT_vertex_co_get(ss, ni.index), vd.co); normalize_v3_v3(vertex_disp_norm, vertex_disp); if (dot_v3v3(current_disp_norm, vertex_disp_norm) > 0.0f) { madd_v3_v3fl(final_disp, vertex_disp_norm, dot_v3v3(current_disp, vertex_disp)); } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); mul_v3_v3fl(proxy[vd.i], final_disp, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } void SCULPT_relax_vertex(SculptSession *ss, PBVHVertexIter *vd, float factor, bool filter_boundary_face_sets, float *r_final_pos) { float smooth_pos[3]; float final_disp[3]; float boundary_normal[3]; int avg_count = 0; int neighbor_count = 0; zero_v3(smooth_pos); zero_v3(boundary_normal); const bool is_boundary = SCULPT_vertex_is_boundary(ss, vd->index); SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) { neighbor_count++; if (!filter_boundary_face_sets || (filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) { /* When the vertex to relax is boundary, use only connected boundary vertices for the average * position. */ if (is_boundary) { if (SCULPT_vertex_is_boundary(ss, ni.index)) { add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); avg_count++; /* Calculate a normal for the constraint plane using the edges of the boundary. */ float to_neighbor[3]; sub_v3_v3v3(to_neighbor, SCULPT_vertex_co_get(ss, ni.index), vd->co); normalize_v3(to_neighbor); add_v3_v3(boundary_normal, to_neighbor); } } else { add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index)); avg_count++; } } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); /* Don't modify corner vertices. */ if (neighbor_count <= 2) { copy_v3_v3(r_final_pos, vd->co); return; } if (avg_count > 0) { mul_v3_fl(smooth_pos, 1.0f / avg_count); } else { copy_v3_v3(r_final_pos, vd->co); return; } float plane[4]; float smooth_closest_plane[3]; float vno[3]; if (is_boundary && avg_count == 2) { normalize_v3_v3(vno, boundary_normal); } else { SCULPT_vertex_normal_get(ss, vd->index, vno); } if (is_zero_v3(vno)) { copy_v3_v3(r_final_pos, vd->co); return; } plane_from_point_normal_v3(plane, vd->co, vno); closest_to_plane_v3(smooth_closest_plane, plane, smooth_pos); sub_v3_v3v3(final_disp, smooth_closest_plane, vd->co); mul_v3_fl(final_disp, factor); add_v3_v3v3(r_final_pos, vd->co, final_disp); } static void do_topology_relax_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float bstrength = ss->cache->bstrength; PBVHVertexIter vd; SculptOrigVertData orig_data; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n]); SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); SCULPT_relax_vertex(ss, &vd, fade * bstrength, false, vd.co); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_slide_relax_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { return; } BKE_curvemapping_init(brush->curve); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); if (ss->cache->alt_smooth) { SCULPT_boundary_info_ensure(ob); for (int i = 0; i < 4; i++) { BLI_task_parallel_range(0, totnode, &data, do_topology_relax_task_cb_ex, &settings); } } else { BLI_task_parallel_range(0, totnode, &data, do_topology_slide_task_cb_ex, &settings); } } static void calc_sculpt_plane( Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode, float r_area_no[3], float r_area_co[3]) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); if (SCULPT_stroke_is_main_symmetry_pass(ss->cache) && (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache) || !(brush->flag & BRUSH_ORIGINAL_PLANE) || !(brush->flag & BRUSH_ORIGINAL_NORMAL))) { switch (brush->sculpt_plane) { case SCULPT_DISP_DIR_VIEW: copy_v3_v3(r_area_no, ss->cache->true_view_normal); break; case SCULPT_DISP_DIR_X: ARRAY_SET_ITEMS(r_area_no, 1.0f, 0.0f, 0.0f); break; case SCULPT_DISP_DIR_Y: ARRAY_SET_ITEMS(r_area_no, 0.0f, 1.0f, 0.0f); break; case SCULPT_DISP_DIR_Z: ARRAY_SET_ITEMS(r_area_no, 0.0f, 0.0f, 1.0f); break; case SCULPT_DISP_DIR_AREA: 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) { 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); } } /** \} */ /** * Used for 'SCULPT_TOOL_CREASE' and 'SCULPT_TOOL_BLOB' */ static void do_crease_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; SculptProjectVector *spvc = data->spvc; const float flippedbstrength = data->flippedbstrength; const float *offset = data->offset; PBVHVertexIter vd; float(*proxy)[3]; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { /* Offset vertex. */ const float fade = SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float val1[3]; float val2[3]; /* First we pinch. */ sub_v3_v3v3(val1, test.location, vd.co); if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { project_plane_v3_v3v3(val1, val1, ss->cache->view_normal); } mul_v3_fl(val1, fade * flippedbstrength); sculpt_project_v3(spvc, val1, val1); /* Then we draw. */ mul_v3_v3fl(val2, offset, fade); add_v3_v3v3(proxy[vd.i], val1, val2); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_crease_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; const Scene *scene = ss->cache->vc->scene; Brush *brush = BKE_paint_brush(&sd->paint); float offset[3]; float bstrength = ss->cache->bstrength; float flippedbstrength, crease_correction; float brush_alpha; SculptProjectVector spvc; /* Offset with as much as possible factored in already. */ mul_v3_v3fl(offset, ss->cache->sculpt_normal_symm, ss->cache->radius); mul_v3_v3(offset, ss->cache->scale); mul_v3_fl(offset, bstrength); /* We divide out the squared alpha and multiply by the squared crease * to give us the pinch strength. */ crease_correction = brush->crease_pinch_factor * brush->crease_pinch_factor; brush_alpha = BKE_brush_alpha_get(scene, brush); if (brush_alpha > 0.0f) { crease_correction /= brush_alpha * brush_alpha; } /* We always want crease to pinch or blob to relax even when draw is negative. */ flippedbstrength = (bstrength < 0.0f) ? -crease_correction * bstrength : crease_correction * bstrength; if (brush->sculpt_tool == SCULPT_TOOL_BLOB) { flippedbstrength *= -1.0f; } /* Use surface normal for 'spvc', so the vertices are pinched towards a line instead of a single * point. Without this we get a 'flat' surface surrounding the pinch. */ sculpt_project_v3_cache_init(&spvc, ss->cache->sculpt_normal_symm); /* Threaded loop over nodes. */ SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .spvc = &spvc, .offset = offset, .flippedbstrength = flippedbstrength, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_crease_brush_task_cb_ex, &settings); } static void do_pinch_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; float(*stroke_xz)[3] = data->stroke_xz; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); float x_object_space[3]; float z_object_space[3]; copy_v3_v3(x_object_space, stroke_xz[0]); copy_v3_v3(z_object_space, stroke_xz[1]); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float disp_center[3]; float x_disp[3]; float z_disp[3]; /* Calculate displacement from the vertex to the brush center. */ sub_v3_v3v3(disp_center, test.location, vd.co); /* Project the displacement into the X vector (aligned to the stroke). */ mul_v3_v3fl(x_disp, x_object_space, dot_v3v3(disp_center, x_object_space)); /* Project the displacement into the Z vector (aligned to the surface normal). */ mul_v3_v3fl(z_disp, z_object_space, dot_v3v3(disp_center, z_object_space)); /* Add the two projected vectors to calculate the final displacement. * The Y component is removed. */ add_v3_v3v3(disp_center, x_disp, z_disp); if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { project_plane_v3_v3v3(disp_center, disp_center, ss->cache->view_normal); } mul_v3_v3fl(proxy[vd.i], disp_center, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_pinch_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float area_no[3]; float area_co[3]; float mat[4][4]; calc_sculpt_plane(sd, ob, nodes, totnode, area_no, area_co); /* delay the first daub because grab delta is not setup */ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { return; } if (is_zero_v3(ss->cache->grab_delta_symmetry)) { return; } /* Initialize `mat`. */ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); mat[0][3] = 0.0f; cross_v3_v3v3(mat[1], area_no, mat[0]); mat[1][3] = 0.0f; copy_v3_v3(mat[2], area_no); mat[2][3] = 0.0f; copy_v3_v3(mat[3], ss->cache->location); mat[3][3] = 1.0f; normalize_m4(mat); float stroke_xz[2][3]; normalize_v3_v3(stroke_xz[0], mat[0]); normalize_v3_v3(stroke_xz[1], mat[2]); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .stroke_xz = stroke_xz, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_pinch_brush_task_cb_ex, &settings); } static void do_grab_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *grab_delta = data->grab_delta; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); if (grab_silhouette) { float silhouette_test_dir[3]; normalize_v3_v3(silhouette_test_dir, grab_delta); if (dot_v3v3(ss->cache->initial_normal, ss->cache->grab_delta_symmetry) < 0.0f) { mul_v3_fl(silhouette_test_dir, -1.0f); } float vno[3]; normal_short_to_float_v3(vno, orig_data.no); fade *= max_ff(dot_v3v3(vno, silhouette_test_dir), 0.0f); } mul_v3_v3fl(proxy[vd.i], grab_delta, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float grab_delta[3]; copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); if (ss->cache->normal_weight > 0.0f) { sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); } SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .grab_delta = grab_delta, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_grab_brush_task_cb_ex, &settings); } static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *grab_delta = data->grab_delta; const float *location = ss->cache->location; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; float dir; if (ss->cache->mouse[0] > ss->cache->initial_mouse[0]) { dir = 1.0f; } else { dir = -1.0f; } if (brush->elastic_deform_type == BRUSH_ELASTIC_DEFORM_TWIST) { int symm = ss->cache->mirror_symmetry_pass; if (ELEM(symm, 1, 2, 4, 7)) { dir = -dir; } } KelvinletParams params; float force = len_v3(grab_delta) * dir * bstrength; BKE_kelvinlet_init_params( ¶ms, ss->cache->radius, force, 1.0f, brush->elastic_deform_volume_preservation); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); float final_disp[3]; switch (brush->elastic_deform_type) { case BRUSH_ELASTIC_DEFORM_GRAB: BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta); mul_v3_fl(final_disp, bstrength * 20.0f); break; case BRUSH_ELASTIC_DEFORM_GRAB_BISCALE: { BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_data.co, location, grab_delta); mul_v3_fl(final_disp, bstrength * 20.0f); break; } case BRUSH_ELASTIC_DEFORM_GRAB_TRISCALE: { BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_data.co, location, grab_delta); mul_v3_fl(final_disp, bstrength * 20.0f); break; } case BRUSH_ELASTIC_DEFORM_SCALE: BKE_kelvinlet_scale( final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); break; case BRUSH_ELASTIC_DEFORM_TWIST: BKE_kelvinlet_twist( final_disp, ¶ms, orig_data.co, location, ss->cache->sculpt_normal_symm); break; } if (vd.mask) { mul_v3_fl(final_disp, 1.0f - *vd.mask); } mul_v3_fl(final_disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); copy_v3_v3(proxy[vd.i], final_disp); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } BKE_pbvh_vertex_iter_end; } static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float grab_delta[3]; copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); if (ss->cache->normal_weight > 0.0f) { sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); } SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .grab_delta = grab_delta, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings); } 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) { 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[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) { 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: 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) { calc_area_center(sd, ob, nodes, totnode, r_area_co); } /* For area normal. */ if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && (brush->flag & BRUSH_ORIGINAL_NORMAL)) { copy_v3_v3(r_area_no, ss->cache->sculpt_normal); } else { copy_v3_v3(ss->cache->sculpt_normal, r_area_no); } /* For flatten center. */ if ((!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) && (brush->flag & BRUSH_ORIGINAL_PLANE)) { copy_v3_v3(r_area_co, ss->cache->last_center); } else { copy_v3_v3(ss->cache->last_center, r_area_co); } } else { /* For area normal. */ copy_v3_v3(r_area_no, ss->cache->sculpt_normal); /* For flatten center. */ copy_v3_v3(r_area_co, ss->cache->last_center); /* For area normal. */ flip_v3(r_area_no, ss->cache->mirror_symmetry_pass); /* For flatten center. */ flip_v3(r_area_co, ss->cache->mirror_symmetry_pass); /* For area normal. */ mul_m4_v3(ss->cache->symm_rot_mat, r_area_no); /* For flatten center. */ mul_m4_v3(ss->cache->symm_rot_mat, r_area_co); /* Shift the plane for the current tile. */ add_v3_v3(r_area_co, ss->cache->plane_offset); } } static void do_nudge_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *cono = data->cono; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_nudge_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float grab_delta[3]; float tmp[3], cono[3]; copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .cono = cono, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_nudge_brush_task_cb_ex, &settings); } static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; SculptProjectVector *spvc = data->spvc; const float *grab_delta = data->grab_delta; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; const bool do_rake_rotation = ss->cache->is_rake_rotation_valid; const bool do_pinch = (brush->crease_pinch_factor != 0.5f); const float pinch = do_pinch ? (2.0f * (0.5f - brush->crease_pinch_factor) * (len_v3(grab_delta) / ss->cache->radius)) : 0.0f; const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); KelvinletParams params; BKE_kelvinlet_init_params(¶ms, ss->cache->radius, bstrength, 1.0f, 0.4f); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (do_elastic || sculpt_brush_test_sq_fn(&test, vd.co)) { float fade; if (do_elastic) { fade = 1.0f; } else { fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); } mul_v3_v3fl(proxy[vd.i], grab_delta, fade); /* Negative pinch will inflate, helps maintain volume. */ if (do_pinch) { float delta_pinch_init[3], delta_pinch[3]; sub_v3_v3v3(delta_pinch, vd.co, test.location); if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) { project_plane_v3_v3v3(delta_pinch, delta_pinch, ss->cache->true_view_normal); } /* Important to calculate based on the grabbed location * (intentionally ignore fade here). */ add_v3_v3(delta_pinch, grab_delta); sculpt_project_v3(spvc, delta_pinch, delta_pinch); copy_v3_v3(delta_pinch_init, delta_pinch); float pinch_fade = pinch * fade; /* When reducing, scale reduction back by how close to the center we are, * so we don't pinch into nothingness. */ if (pinch > 0.0f) { /* Square to have even less impact for close vertices. */ pinch_fade *= pow2f(min_ff(1.0f, len_v3(delta_pinch) / ss->cache->radius)); } mul_v3_fl(delta_pinch, 1.0f + pinch_fade); sub_v3_v3v3(delta_pinch, delta_pinch_init, delta_pinch); add_v3_v3(proxy[vd.i], delta_pinch); } if (do_rake_rotation) { float delta_rotate[3]; sculpt_rake_rotate(ss, test.location, vd.co, fade, delta_rotate); add_v3_v3(proxy[vd.i], delta_rotate); } if (do_elastic) { float disp[3]; BKE_kelvinlet_grab_triscale(disp, ¶ms, vd.co, ss->cache->location, proxy[vd.i]); mul_v3_fl(disp, bstrength * 20.0f); if (vd.mask) { mul_v3_fl(disp, 1.0f - *vd.mask); } mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index)); copy_v3_v3(proxy[vd.i], disp); } if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_snake_hook_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float bstrength = ss->cache->bstrength; float grab_delta[3]; SculptProjectVector spvc; copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); if (bstrength < 0.0f) { negate_v3(grab_delta); } if (ss->cache->normal_weight > 0.0f) { sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); } /* Optionally pinch while painting. */ if (brush->crease_pinch_factor != 0.5f) { sculpt_project_v3_cache_init(&spvc, grab_delta); } SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .spvc = &spvc, .grab_delta = grab_delta, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_snake_hook_brush_task_cb_ex, &settings); } static void do_thumb_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *cono = data->cono; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], cono, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); float grab_delta[3]; float tmp[3], cono[3]; copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); cross_v3_v3v3(tmp, ss->cache->sculpt_normal_symm, grab_delta); cross_v3_v3v3(cono, tmp, ss->cache->sculpt_normal_symm); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .cono = cono, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_thumb_brush_task_cb_ex, &settings); } static void do_rotate_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float angle = data->angle; PBVHVertexIter vd; SculptOrigVertData orig_data; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { float vec[3], rot[3][3]; const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, orig_data.co, sqrtf(test.dist), orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); sub_v3_v3v3(vec, orig_data.co, ss->cache->location); axis_angle_normalized_to_mat3(rot, ss->cache->sculpt_normal_symm, angle * fade); mul_v3_m3v3(proxy[vd.i], rot, vec); add_v3_v3(proxy[vd.i], ss->cache->location); sub_v3_v3(proxy[vd.i], orig_data.co); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_rotate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); static const int flip[8] = {1, -1, -1, 1, -1, 1, 1, -1}; const float angle = ss->cache->vertex_rotation * flip[ss->cache->mirror_symmetry_pass]; SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .angle = angle, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_rotate_brush_task_cb_ex, &settings); } static void do_layer_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; Sculpt *sd = data->sd; const Brush *brush = data->brush; const bool use_persistent_base = ss->persistent_base && brush->flag & BRUSH_PERSISTENT; PBVHVertexIter vd; SculptOrigVertData orig_data; const float bstrength = ss->cache->bstrength; SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); if (sculpt_brush_test_sq_fn(&test, orig_data.co)) { const float fade = SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); const int vi = vd.index; float *disp_factor; if (use_persistent_base) { disp_factor = &ss->persistent_base[vi].disp; } else { disp_factor = &ss->cache->layer_displacement_factor[vi]; } /* When using persistent base, the layer brush (holding Control) invert mode resets the * height of the layer to 0. This makes possible to clean edges of previously added layers * on top of the base. */ /* The main direction of the layers is inverted using the regular brush strength with the * brush direction property. */ if (use_persistent_base && ss->cache->invert) { (*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) * ((*disp_factor) > 0.0f ? -1.0f : 1.0f); } else { (*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor)); } if (vd.mask) { const float clamp_mask = 1.0f - *vd.mask; *disp_factor = clamp_f(*disp_factor, -clamp_mask, clamp_mask); } else { *disp_factor = clamp_f(*disp_factor, -1.0f, 1.0f); } float final_co[3]; float normal[3]; if (use_persistent_base) { SCULPT_vertex_persistent_normal_get(ss, vi, normal); mul_v3_fl(normal, brush->height); madd_v3_v3v3fl(final_co, SCULPT_vertex_persistent_co_get(ss, vi), normal, *disp_factor); } else { normal_short_to_float_v3(normal, orig_data.no); mul_v3_fl(normal, brush->height); madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor); } float vdisp[3]; sub_v3_v3v3(vdisp, final_co, vd.co); mul_v3_fl(vdisp, fabsf(fade)); add_v3_v3v3(final_co, vd.co, vdisp); SCULPT_clip(sd, ss, vd.co, final_co); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); if (ss->cache->layer_displacement_factor == NULL) { ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss), "layer displacement factor"); } SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings); } static void do_inflate_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); float val[3]; if (vd.fno) { copy_v3_v3(val, vd.fno); } else { normal_short_to_float_v3(val, vd.no); } mul_v3_fl(val, fade * ss->cache->radius); mul_v3_v3v3(proxy[vd.i], val, ss->cache->scale); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_inflate_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { Brush *brush = BKE_paint_brush(&sd->paint); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_inflate_brush_task_cb_ex, &settings); } 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))); } static bool plane_point_side_flip(const float co[3], const float plane[4], const bool flip) { float d = plane_point_side_v3(plane, co); if (flip) { d = -d; } return d <= 0.0f; } 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; } static void do_flatten_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); plane_from_point_normal_v3(test.plane_tool, area_co, area_no); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (SCULPT_plane_trim(ss->cache, brush, val)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } } BKE_pbvh_vertex_iter_end; } static void do_flatten_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float radius = ss->cache->radius; float area_no[3]; float area_co[3]; float offset = SCULPT_brush_plane_offset_get(sd, ss); float displace; float temp[3]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); displace = radius * offset; mul_v3_v3v3(temp, area_no, ss->cache->scale); mul_v3_fl(temp, displace); add_v3_v3(area_co, temp); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no = area_no, .area_co = area_co, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_flatten_brush_task_cb_ex, &settings); } /* -------------------------------------------------------------------- */ /** \name Sculpt Clay Brush * \{ */ typedef struct ClaySampleData { float plane_dist[2]; } ClaySampleData; static void calc_clay_surface_task_cb(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; ClaySampleData *csd = tls->userdata_chunk; const float *area_no = data->area_no; const float *area_co = data->area_co; float plane[4]; PBVHVertexIter vd; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, brush->falloff_shape); /* Apply the brush normal radius to the test before sampling. */ float test_radius = sqrtf(test.radius_squared); test_radius *= brush->normal_radius_factor; test.radius_squared = test_radius * test_radius; plane_from_point_normal_v3(plane, area_co, area_no); if (is_zero_v4(plane)) { return; } BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { float plane_dist = dist_signed_to_plane_v3(vd.co, plane); float plane_dist_abs = fabsf(plane_dist); if (plane_dist > 0.0f) { csd->plane_dist[0] = MIN2(csd->plane_dist[0], plane_dist_abs); } else { csd->plane_dist[1] = MIN2(csd->plane_dist[1], plane_dist_abs); } } BKE_pbvh_vertex_iter_end; } } static void calc_clay_surface_reduce(const void *__restrict UNUSED(userdata), void *__restrict chunk_join, void *__restrict chunk) { ClaySampleData *join = chunk_join; ClaySampleData *csd = chunk; join->plane_dist[0] = MIN2(csd->plane_dist[0], join->plane_dist[0]); join->plane_dist[1] = MIN2(csd->plane_dist[1], join->plane_dist[1]); } static void do_clay_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = fabsf(ss->cache->bstrength); proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); plane_from_point_normal_v3(test.plane_tool, area_co, area_no); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float radius = fabsf(ss->cache->radius); const float initial_radius = fabsf(ss->cache->initial_radius); bool flip = ss->cache->bstrength < 0.0f; float offset = SCULPT_brush_plane_offset_get(sd, ss); float displace; float area_no[3]; float area_co[3]; float temp[3]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); SculptThreadedTaskData sample_data = { .sd = NULL, .ob = ob, .brush = brush, .nodes = nodes, .totnode = totnode, .area_no = area_no, .area_co = ss->cache->location, }; ClaySampleData csd = {{0}}; TaskParallelSettings sample_settings; BKE_pbvh_parallel_range_settings(&sample_settings, true, totnode); sample_settings.func_reduce = calc_clay_surface_reduce; sample_settings.userdata_chunk = &csd; sample_settings.userdata_chunk_size = sizeof(ClaySampleData); BLI_task_parallel_range(0, totnode, &sample_data, calc_clay_surface_task_cb, &sample_settings); float d_offset = (csd.plane_dist[0] + csd.plane_dist[1]); d_offset = min_ff(radius, d_offset); d_offset = d_offset / radius; d_offset = 1.0f - d_offset; displace = fabsf(initial_radius * (0.25f + offset + (d_offset * 0.15f))); if (flip) { displace = -displace; } mul_v3_v3v3(temp, area_no, ss->cache->scale); mul_v3_fl(temp, displace); copy_v3_v3(area_co, ss->cache->location); add_v3_v3(area_co, temp); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no = area_no, .area_co = area_co, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings); } static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; float(*mat)[4] = data->mat; const float *area_no_sp = data->area_no_sp; const float *area_co = data->area_co; PBVHVertexIter vd; SculptBrushTest test; float(*proxy)[3]; const bool flip = (ss->cache->bstrength < 0.0f); const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SCULPT_brush_test_init(ss, &test); plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); const int thread_id = BLI_task_parallel_thread_id(tls); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (SCULPT_brush_test_cube(&test, vd.co, mat, brush->tip_roundness)) { if (plane_point_side_flip(vd.co, test.plane_tool, flip)) { float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (SCULPT_plane_trim(ss->cache, brush, val)) { /* The normal from the vertices is ignored, it causes glitch with planes, see: T44390. */ const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, ss->cache->radius * test.dist, vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } } } BKE_pbvh_vertex_iter_end; } static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const bool flip = (ss->cache->bstrength < 0.0f); const float radius = flip ? -ss->cache->radius : ss->cache->radius; const float offset = SCULPT_brush_plane_offset_get(sd, ss); const float displace = radius * (0.18f + offset); /* The sculpt-plane normal (whatever its set to). */ float area_no_sp[3]; /* Geometry normal */ float area_no[3]; float area_co[3]; float temp[3]; float mat[4][4]; float scale[4][4]; float tmat[4][4]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); SCULPT_tilt_apply_to_normal(area_no_sp, ss->cache, brush->tilt_strength_factor); if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); } else { copy_v3_v3(area_no, area_no_sp); } /* Delay the first daub because grab delta is not setup. */ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { return; } if (is_zero_v3(ss->cache->grab_delta_symmetry)) { return; } mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); mul_v3_fl(temp, displace); add_v3_v3(area_co, temp); /* Clay Strips uses a cube test with falloff in the XY axis (not in Z) and a plane to deform the * vertices. When in Add mode, vertices that are below the plane and inside the cube are move * towards the plane. In this situation, there may be cases where a vertex is outside the cube * but below the plane, so won't be deformed, causing artifacts. In order to prevent these * artifacts, this displaces the test cube space in relation to the plane in order to * deform more vertices that may be below it. */ /* The 0.7 and 1.25 factors are arbitrary and don't have any relation between them, they were set * by doing multiple tests using the default "Clay Strips" brush preset. */ float area_co_displaced[3]; madd_v3_v3v3fl(area_co_displaced, area_co, area_no, -radius * 0.7f); /* Initialize brush local-space matrix. */ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); mat[0][3] = 0.0f; cross_v3_v3v3(mat[1], area_no, mat[0]); mat[1][3] = 0.0f; copy_v3_v3(mat[2], area_no); mat[2][3] = 0.0f; copy_v3_v3(mat[3], area_co_displaced); mat[3][3] = 1.0f; normalize_m4(mat); /* Scale brush local space matrix. */ scale_m4_fl(scale, ss->cache->radius); mul_m4_m4m4(tmat, mat, scale); /* Deform the local space in Z to scale the test cube. As the test cube does not have falloff in * Z this does not produce artifacts in the falloff cube and allows to deform extra vertices * during big deformation while keeping the surface as uniform as possible. */ mul_v3_fl(tmat[2], 1.25f); invert_m4_m4(mat, tmat); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no_sp = area_no_sp, .area_co = area_co, .mat = mat, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings); } static void do_fill_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); plane_from_point_normal_v3(test.plane_tool, area_co, area_no); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { if (SCULPT_plane_point_side(vd.co, test.plane_tool)) { float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (SCULPT_plane_trim(ss->cache, brush, val)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } } } BKE_pbvh_vertex_iter_end; } static void do_fill_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float radius = ss->cache->radius; float area_no[3]; float area_co[3]; float offset = SCULPT_brush_plane_offset_get(sd, ss); float displace; float temp[3]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); displace = radius * offset; mul_v3_v3v3(temp, area_no, ss->cache->scale); mul_v3_fl(temp, displace); add_v3_v3(area_co, temp); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no = area_no, .area_co = area_co, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_fill_brush_task_cb_ex, &settings); } static void do_scrape_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; const float *area_no = data->area_no; const float *area_co = data->area_co; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = ss->cache->bstrength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); plane_from_point_normal_v3(test.plane_tool, area_co, area_no); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { if (!SCULPT_plane_point_side(vd.co, test.plane_tool)) { float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); sub_v3_v3v3(val, intr, vd.co); if (SCULPT_plane_trim(ss->cache, brush, val)) { const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } } } BKE_pbvh_vertex_iter_end; } static void do_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float radius = ss->cache->radius; float area_no[3]; float area_co[3]; float offset = SCULPT_brush_plane_offset_get(sd, ss); float displace; float temp[3]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no, area_co); SCULPT_tilt_apply_to_normal(area_no, ss->cache, brush->tilt_strength_factor); displace = -radius * offset; mul_v3_v3v3(temp, area_no, ss->cache->scale); mul_v3_fl(temp, displace); add_v3_v3(area_co, temp); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no = area_no, .area_co = area_co, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_scrape_brush_task_cb_ex, &settings); } /* -------------------------------------------------------------------- */ /** \name Sculpt Clay Thumb Brush * \{ */ static void do_clay_thumb_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; const Brush *brush = data->brush; float(*mat)[4] = data->mat; const float *area_no_sp = data->area_no_sp; const float *area_co = data->area_co; PBVHVertexIter vd; float(*proxy)[3]; const float bstrength = data->clay_strength; proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co; SculptBrushTest test; SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( ss, &test, data->brush->falloff_shape); const int thread_id = BLI_task_parallel_thread_id(tls); float plane_tilt[4]; float normal_tilt[3]; float imat[4][4]; invert_m4_m4(imat, mat); rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss->cache->clay_thumb_front_angle)); /* Plane aligned to the geometry normal (back part of the brush). */ plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp); /* Tilted plane (front part of the brush). */ plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt); BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { if (sculpt_brush_test_sq_fn(&test, vd.co)) { float local_co[3]; mul_v3_m4v3(local_co, mat, vd.co); float intr[3], intr_tilt[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co); /* Mix the deformation of the aligned and the tilted plane based on the brush space vertex * coordinates. */ /* We can also control the mix with a curve if it produces noticeable artifacts in the center * of the brush. */ const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f; interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix); sub_v3_v3v3(val, intr_tilt, vd.co); const float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], val, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } static float sculpt_clay_thumb_get_stabilized_pressure(StrokeCache *cache) { float final_pressure = 0.0f; for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) { final_pressure += cache->clay_pressure_stabilizer[i]; } return final_pressure / SCULPT_CLAY_STABILIZER_LEN; } static void do_clay_thumb_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); const float radius = ss->cache->radius; const float offset = SCULPT_brush_plane_offset_get(sd, ss); const float displace = radius * (0.25f + offset); /* Sampled geometry normal and area center. */ float area_no_sp[3]; float area_no[3]; float area_co[3]; float temp[3]; float mat[4][4]; float scale[4][4]; float tmat[4][4]; SCULPT_calc_brush_plane(sd, ob, nodes, totnode, area_no_sp, area_co); if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) { SCULPT_calc_area_normal(sd, ob, nodes, totnode, area_no); } else { copy_v3_v3(area_no, area_no_sp); } /* Delay the first daub because grab delta is not setup. */ if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { ss->cache->clay_thumb_front_angle = 0.0f; return; } /* Simulate the clay accumulation by increasing the plane angle as more samples are added to the * stroke. */ if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { ss->cache->clay_thumb_front_angle += 0.8f; ss->cache->clay_thumb_front_angle = clamp_f(ss->cache->clay_thumb_front_angle, 0.0f, 60.0f); } if (is_zero_v3(ss->cache->grab_delta_symmetry)) { return; } /* Displace the brush planes. */ copy_v3_v3(area_co, ss->cache->location); mul_v3_v3v3(temp, area_no_sp, ss->cache->scale); mul_v3_fl(temp, displace); add_v3_v3(area_co, temp); /* Initialize brush local-space matrix. */ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry); mat[0][3] = 0.0f; cross_v3_v3v3(mat[1], area_no, mat[0]); mat[1][3] = 0.0f; copy_v3_v3(mat[2], area_no); mat[2][3] = 0.0f; copy_v3_v3(mat[3], ss->cache->location); mat[3][3] = 1.0f; normalize_m4(mat); /* Scale brush local space matrix. */ scale_m4_fl(scale, ss->cache->radius); mul_m4_m4m4(tmat, mat, scale); invert_m4_m4(mat, tmat); float clay_strength = ss->cache->bstrength * sculpt_clay_thumb_get_stabilized_pressure(ss->cache); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, .area_no_sp = area_no_sp, .area_co = ss->cache->location, .mat = mat, .clay_strength = clay_strength, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_clay_thumb_brush_task_cb_ex, &settings); } /** \} */ static void do_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)) { const float fade = SCULPT_brush_strength_factor(ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, vd.mask ? *vd.mask : 0.0f, vd.index, thread_id); mul_v3_v3fl(proxy[vd.i], offset, fade); if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; } 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); } 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 *mvert = me->mvert; for (a = 0; a < me->totvert; a++, mvert++) { copy_v3_v3(mvert->co, vertCos[a]); } BKE_mesh_calc_normals(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)) { 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) { 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, nodes[n]); } } 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->obmat, 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; /* 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) { SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COORDS); BKE_pbvh_node_mark_update(data->nodes[n]); } } 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 (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_COLOR); BKE_pbvh_node_mark_update_color(data->nodes[n]); } else { 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) { SculptSession *ss = ob->sculpt; int totnode; PBVHNode **nodes; /* Check for unsupported features. */ PBVHType type = BKE_pbvh_type(ss->pbvh); if (brush->sculpt_tool == SCULPT_TOOL_PAINT && type != PBVH_FACES) { return; } if (brush->sculpt_tool == SCULPT_TOOL_SMEAR && type != PBVH_FACES) { return; } /* 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; /* 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); } /* 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); } } /* Initialize automasking cache. 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)) { if (SCULPT_is_automasking_enabled(sd, ss, brush)) { ss->cache->automasking = SCULPT_automasking_cache_init(sd, brush, ob); } } } /* Only act if some verts are inside the brush area. */ if (totnode) { float location[3]; 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, 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( ss, 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: 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: do_crease_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_BLOB: do_crease_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PINCH: do_pinch_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_INFLATE: do_inflate_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_GRAB: do_grab_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_ROTATE: do_rotate_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_SNAKE_HOOK: do_snake_hook_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_NUDGE: do_nudge_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_THUMB: do_thumb_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_LAYER: do_layer_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_FLATTEN: do_flatten_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_CLAY: do_clay_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_CLAY_STRIPS: 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: do_clay_thumb_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_FILL: if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { do_scrape_brush(sd, ob, nodes, totnode); } else { do_fill_brush(sd, ob, nodes, totnode); } break; case SCULPT_TOOL_SCRAPE: if (invert && brush->flag & BRUSH_INVERT_TO_SCRAPE_FILL) { do_fill_brush(sd, ob, nodes, totnode); } else { do_scrape_brush(sd, ob, nodes, totnode); } break; case SCULPT_TOOL_MASK: 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: do_draw_sharp_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_ELASTIC_DEFORM: do_elastic_deform_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_SLIDE_RELAX: 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: do_displacement_eraser_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_DISPLACEMENT_SMEAR: do_displacement_smear_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_PAINT: SCULPT_do_paint_brush(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)) { bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); } /* 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->obmat, 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); if (!ss->shapekey_active) { copy_v3_v3(me->mvert[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; /* These brushes start from original coordinates. */ const bool use_orco = ELEM(data->brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_BOUNDARY, SCULPT_TOOL_POSE); 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; BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); /* First line is tools that don't support proxies. */ if (ss->cache->supports_gravity || (sculpt_tool_is_proxy_used(brush->sculpt_tool) == false)) { SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, 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) { 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) { int index = vd.vert_indices[vd.i]; copy_v3_v3(vertCos[index], ss->orig_cos[index]); } } BKE_pbvh_vertex_iter_end; } /* Flush displacement from deformed PBVH to original layer. */ 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); /* Modifiers could depend on mesh normals, so we should update them. * Note, then if sculpting happens on locked key, normals should be re-calculate after applying * coords from key-block on base mesh. */ BKE_mesh_calc_normals(me); } else if (ss->shapekey_active) { sculpt_update_keyblock(ob); } } /** * 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, 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); static void do_tiled( Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, BrushActionFunc action) { SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; const float radius = cache->radius; 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 "untiled" position to initialize the stroke for this location. */ cache->tile_pass = 0; action(sd, ob, brush, ups); /* 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); } } } } static void do_radial_symmetry(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSettings *ups, 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, 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) { 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); 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 (i == 0 || (symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5)))) { 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, action); do_radial_symmetry(sd, ob, brush, ups, action, i, 'X', feather); do_radial_symmetry(sd, ob, brush, ups, action, i, 'Y', feather); do_radial_symmetry(sd, ob, brush, ups, action, i, 'Z', feather); } } } static void sculpt_update_tex(const Scene *scene, Sculpt *sd, SculptSession *ss) { Brush *brush = BKE_paint_brush(&sd->paint); const int radius = BKE_brush_size_get(scene, brush); if (ss->texcache) { MEM_freeN(ss->texcache); ss->texcache = NULL; } if (ss->tex_pool) { BKE_image_pool_free(ss->tex_pool); ss->tex_pool = NULL; } /* Need to allocate a bigger buffer for bigger brush size. */ ss->texcache_side = 2 * radius; if (!ss->texcache || ss->texcache_side > ss->texcache_actual) { ss->texcache = BKE_brush_gen_texture_cache(brush, radius, false); ss->texcache_actual = ss->texcache_side; ss->tex_pool = BKE_image_pool_new(); } } bool SCULPT_mode_poll(bContext *C) { Object *ob = CTX_data_active_object(C); return ob && ob->mode & OB_MODE_SCULPT; } bool SCULPT_vertex_colors_poll(bContext *C) { if (!U.experimental.use_sculpt_vertex_colors) { return false; } return SCULPT_mode_poll(C); } 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_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); 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; for (md = ob->modifiers.first; md; md = md->next) { if (md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime)) { MirrorModifierData *mmd = (MirrorModifierData *)md; if (mmd->flag & MOD_MIR_CLIPPING) { /* Check each axis for mirroring. */ for (int i = 0; i < 3; i++) { if (mmd->flag & (MOD_MIR_AXIS_X << i)) { /* 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; } } } } } } } /* Initialize the stroke cache invariants from operator properties. */ static void sculpt_update_cache_invariants( bContext *C, Sculpt *sd, SculptSession *ss, wmOperator *op, const float mouse[2]) { StrokeCache *cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->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 (mouse) { copy_v2_v2(cache->initial_mouse, mouse); } 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) { 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 { Paint *p = &sd->paint; Brush *br; int size = BKE_brush_size_get(scene, brush); BLI_strncpy(cache->saved_active_brush_name, brush->id.name + 2, sizeof(cache->saved_active_brush_name)); br = (Brush *)BKE_libblock_find_name(bmain, ID_BR, "Smooth"); if (br) { BKE_paint_brush_set(p, br); brush = br; cache->saved_smooth_size = BKE_brush_size_get(scene, brush); BKE_brush_size_set(scene, brush, size); BKE_curvemapping_init(brush->curve); } } } 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->imat, ob->obmat); copy_m3_m4(mat, cache->vc->rv3d->viewinv); mul_m3_v3(mat, viewDir); copy_m3_m4(mat, ob->imat); 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->obmat[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; } /* 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; } } } 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 (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 mouse[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_THUMB) || sculpt_brush_use_topology_rake(ss, brush)) { 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 + mouse. */ mul_v3_m4v3(loc, ob->obmat, cache->orig_grab_location); ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mouse, 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->obmat); 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->obmat, 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->obmat); 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->obmat); 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)) { 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) || (brush->sculpt_tool == SCULPT_TOOL_CLOTH) || (brush->sculpt_tool == SCULPT_TOOL_SMEAR) || (brush->sculpt_tool == SCULPT_TOOL_DRAW_FACE_SETS)); } void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) { SculptSession *ss = ob->sculpt; View3D *v3d = CTX_wm_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, v3d) && need_pmap)) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); BKE_sculpt_update_object_for_edit(depsgraph, ob, need_pmap, false, false); } } static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) { if (BKE_pbvh_node_get_tmin(node) < *tmin) { 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); 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_index, &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) { 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); 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 mouse[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, mouse, ray_start, ray_end, true); invert_m4_m4(obimat, ob->obmat); 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; } /* Gets the normal, location and active vertex location of the geometry under the cursor. This also * updates the active vertex and cursor related data of the SculptSession using the mouse position */ bool SCULPT_cursor_geometry_info_update(bContext *C, SculptCursorGeometryInfo *out, const float mouse[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, mouse, 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_index = srd.active_vertex_index; SCULPT_vertex_random_access_ensure(ss); copy_v3_v3(out->active_vertex_co, SCULPT_active_vertex_co_get(ss)); switch (BKE_pbvh_type(ss->pbvh)) { 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->imat, ob->obmat); copy_m3_m4(mat, vc.rv3d->viewinv); mul_m3_v3(mat, viewDir); copy_m3_m4(mat, ob->imat); 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; } /* Do a raycast in the tree to find the 3d brush location * (This allows us to ignore the GL depth buffer) * Returns 0 if the ray doesn't hit the mesh, non-zero otherwise. */ bool SCULPT_stroke_get_location(bContext *C, float out[3], const float mouse[2]) { 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 = (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, mouse, 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) { if (ELEM(brush->falloff_shape, PAINT_FALLOFF_SHAPE_TUBE)) { 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(const Scene *scene, 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); } /* TODO: Shouldn't really have to do this at the start of every stroke, but sculpt would need * some sort of notification when changes are made to the texture. */ sculpt_update_tex(scene, sd, ss); } static void sculpt_brush_stroke_init(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->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 is_smooth, 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(scene, sd, ss); is_smooth = sculpt_needs_connectivity_info(sd, brush, ss, mode); needs_colors = ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR); 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, is_smooth, need_mask, needs_colors); } 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) || ((brush->sculpt_tool == SCULPT_TOOL_GRAB || brush->sculpt_tool == 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); } } } /* Copy the PBVH bounding box into the object's bounding box. */ 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; View3D *v3d = CTX_wm_view3d(C); 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); } 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, v3d)) { /* 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); View3D *current_v3d = CTX_wm_view3d(C); RegionView3D *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 (rv3d) { 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) { View3D *v3d = (View3D *)sl; if (v3d != current_v3d) { need_tag |= !BKE_sculptsession_use_pbvh_draw(ob, v3d); } /* 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) { ED_region_tag_redraw(region); } } } } } 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); } 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), float x, float y) { float mouse[2], co[3]; mouse[0] = x; mouse[1] = y; return SCULPT_stroke_get_location(C, co, mouse); } static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) { /* Don't start the stroke until mouse goes over the mesh. * note: mouse will only be null when re-executing the saved stroke. * We have exception for 'exec' strokes since they may not set 'mouse', * only 'location', see: T52195. */ if (((op->flag & OP_IS_INVOKE) == 0) || (mouse == NULL) || over_mesh(C, op, mouse[0], mouse[1])) { Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; ED_view3d_init_mats_rv3d(ob, CTX_wm_region_view3d(C)); sculpt_update_cache_invariants(C, sd, ss, op, mouse); SCULPT_undo_push_begin(ob, sculpt_tool_name(sd)); return true; } return false; } static void sculpt_stroke_update_step(bContext *C, struct PaintStroke *UNUSED(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); 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->obmat)); 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); } do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); 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 (ELEM(brush->sculpt_tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)) { 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)) { Main *bmain = CTX_data_main(C); Object *ob = CTX_data_active_object(C); Scene *scene = CTX_data_scene(C); SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; /* Finished. */ if (ss->cache) { 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) { if (brush->sculpt_tool == SCULPT_TOOL_MASK) { brush->mask_tool = ss->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 { BKE_brush_size_set(scene, brush, ss->cache->saved_smooth_size); brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, ss->cache->saved_active_brush_name); if (brush) { BKE_paint_brush_set(&sd->paint, brush); } } } 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_undo_push_end(); if (brush->sculpt_tool == SCULPT_TOOL_MASK) { SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); } 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; sculpt_brush_stroke_init(C, op); 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, event->x, event->y)) { paint_stroke_free(C, op); return OPERATOR_PASS_THROUGH; } if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { paint_stroke_free(C, op); return OPERATOR_FINISHED; } /* 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); 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); if (ss->cache) { SCULPT_cache_free(ss->cache); ss->cache = NULL; } sculpt_brush_exit_tex(sd); } static 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 = paint_stroke_modal; ot->exec = sculpt_brush_stroke_exec; ot->poll = SCULPT_poll; ot->cancel = sculpt_brush_stroke_cancel; /* Flags (sculpt does own undo? (ton)). */ ot->flag = OPTYPE_BLOCKING; /* 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"); } /* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */ static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op)) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; if (ss) { SCULPT_vertex_random_access_ensure(ss); BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); MEM_SAFE_FREE(ss->persistent_base); const int totvert = SCULPT_vertex_count_get(ss); ss->persistent_base = MEM_mallocN(sizeof(SculptPersistentBase) * totvert, "layer persistent base"); for (int i = 0; i < totvert; i++) { copy_v3_v3(ss->persistent_base[i].co, SCULPT_vertex_co_get(ss, i)); SCULPT_vertex_normal_get(ss, i, ss->persistent_base[i].no); ss->persistent_base[i].disp = 0.0f; } } return OPERATOR_FINISHED; } static void SCULPT_OT_set_persistent_base(wmOperatorType *ot) { /* Identifiers. */ ot->name = "Set Persistent Base"; ot->idname = "SCULPT_OT_set_persistent_base"; ot->description = "Reset the copy of the mesh that is being sculpted on"; /* API callbacks. */ ot->exec = sculpt_set_persistent_base_exec; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /************************* SCULPT_OT_optimize *************************/ static int sculpt_optimize_exec(bContext *C, wmOperator *UNUSED(op)) { Object *ob = CTX_data_active_object(C); SCULPT_pbvh_clear(ob); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); return OPERATOR_FINISHED; } /* The BVH gets less optimal more quickly with dynamic topology than * regular sculpting. There is no doubt more clever stuff we can do to * optimize it on the fly, but for now this gives the user a nicer way * to recalculate it than toggling modes. */ static void SCULPT_OT_optimize(wmOperatorType *ot) { /* Identifiers. */ ot->name = "Rebuild BVH"; ot->idname = "SCULPT_OT_optimize"; ot->description = "Recalculate the sculpt BVH to improve performance"; /* API callbacks. */ ot->exec = sculpt_optimize_exec; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /********************* Dynamic topology symmetrize ********************/ static bool sculpt_no_multires_poll(bContext *C) { Object *ob = CTX_data_active_object(C); if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) { return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS; } return false; } static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C); const Sculpt *sd = CTX_data_tool_settings(C)->sculpt; SculptSession *ss = ob->sculpt; PBVH *pbvh = ss->pbvh; if (!pbvh) { return OPERATOR_CANCELLED; } switch (BKE_pbvh_type(pbvh)) { case PBVH_BMESH: /* Dyntopo Symmetrize. */ /* To simplify undo for symmetrize, all BMesh elements are logged * as deleted, then after symmetrize operation all BMesh elements * are logged as added (as opposed to attempting to store just the * parts that symmetrize modifies). */ SCULPT_undo_push_begin(ob, "Dynamic topology symmetrize"); SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_SYMMETRIZE); BM_log_before_all_removed(ss->bm, ss->bm_log); BM_mesh_toolflags_set(ss->bm, true); /* Symmetrize and re-triangulate. */ BMO_op_callf(ss->bm, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), "symmetrize input=%avef direction=%i dist=%f", sd->symmetrize_direction, 0.00001f); SCULPT_dynamic_topology_triangulate(ss->bm); /* Bisect operator flags edges (keep tags clean for edge queue). */ BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false); BM_mesh_toolflags_set(ss->bm, false); /* Finish undo. */ BM_log_all_added(ss->bm, ss->bm_log); SCULPT_undo_push_end(); break; case PBVH_FACES: /* Mesh Symmetrize. */ ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize"); Mesh *mesh = ob->data; Mesh *mesh_mirror; MirrorModifierData mmd = {{0}}; int axis = 0; mmd.flag = 0; mmd.tolerance = RNA_float_get(op->ptr, "merge_tolerance"); switch (sd->symmetrize_direction) { case BMO_SYMMETRIZE_NEGATIVE_X: axis = 0; mmd.flag |= MOD_MIR_AXIS_X | MOD_MIR_BISECT_AXIS_X | MOD_MIR_BISECT_FLIP_AXIS_X; break; case BMO_SYMMETRIZE_NEGATIVE_Y: axis = 1; mmd.flag |= MOD_MIR_AXIS_Y | MOD_MIR_BISECT_AXIS_Y | MOD_MIR_BISECT_FLIP_AXIS_Y; break; case BMO_SYMMETRIZE_NEGATIVE_Z: axis = 2; mmd.flag |= MOD_MIR_AXIS_Z | MOD_MIR_BISECT_AXIS_Z | MOD_MIR_BISECT_FLIP_AXIS_Z; break; case BMO_SYMMETRIZE_POSITIVE_X: axis = 0; mmd.flag |= MOD_MIR_AXIS_X | MOD_MIR_BISECT_AXIS_X; break; case BMO_SYMMETRIZE_POSITIVE_Y: axis = 1; mmd.flag |= MOD_MIR_AXIS_Y | MOD_MIR_BISECT_AXIS_Y; break; case BMO_SYMMETRIZE_POSITIVE_Z: axis = 2; mmd.flag |= MOD_MIR_AXIS_Z | MOD_MIR_BISECT_AXIS_Z; break; } mesh_mirror = BKE_mesh_mirror_apply_mirror_on_axis(&mmd, NULL, ob, mesh, axis); if (mesh_mirror) { BKE_mesh_nomain_to_mesh(mesh_mirror, mesh, ob, &CD_MASK_MESH, true); } ED_sculpt_undo_geometry_end(ob); BKE_mesh_calc_normals(ob->data); BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); break; case PBVH_GRIDS: return OPERATOR_CANCELLED; } /* Redraw. */ SCULPT_pbvh_clear(ob); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); return OPERATOR_FINISHED; } static void SCULPT_OT_symmetrize(wmOperatorType *ot) { /* Identifiers. */ ot->name = "Symmetrize"; ot->idname = "SCULPT_OT_symmetrize"; ot->description = "Symmetrize the topology modifications"; /* API callbacks. */ ot->exec = sculpt_symmetrize_exec; ot->poll = sculpt_no_multires_poll; RNA_def_float(ot->srna, "merge_tolerance", 0.001f, 0.0f, FLT_MAX, "Merge Distance", "Distance within which symmetrical vertices are merged", 0.0f, 1.0f); } /**** Toggle operator for turning sculpt mode on or off ****/ /** \warning Expects a fully evaluated depsgraph. */ static void sculpt_init_session(Depsgraph *depsgraph, Scene *scene, Object *ob) { /* Create persistent sculpt mode data. */ BKE_sculpt_toolsettings_data_ensure(scene); ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); ob->sculpt->mode_type = OB_MODE_SCULPT; BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); /* Here we can detect geometry that was just added to Sculpt Mode as it has the * SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */ /* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not * initialized, which is used is some operators that modify the mesh topology to preform certain * actions in the new polys. After these operations are finished, all polys should have a valid * face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their visibility * correctly. */ /* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new * objects, like moving the transform pivot position to the new area or masking existing * geometry. */ SculptSession *ss = ob->sculpt; const int new_face_set = SCULPT_face_set_next_available_get(ss); for (int i = 0; i < ss->totfaces; i++) { if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) { ss->face_sets[i] = new_face_set; } } /* Update the Face Sets visibility with the vertex visibility changes that may have been done * outside Sculpt Mode */ Mesh *mesh = ob->data; BKE_sculpt_face_sets_ensure_from_base_mesh_visibility(mesh); } static int ed_object_sculptmode_flush_recalc_flag(Scene *scene, Object *ob, MultiresModifierData *mmd) { int flush_recalc = 0; /* Multires in sculpt mode could have different from object mode subdivision level. */ flush_recalc |= mmd && mmd->sculptlvl != mmd->lvl; /* If object has got active modifiers, its dm could be different in sculpt mode. */ flush_recalc |= sculpt_has_active_modifiers(scene, ob); return flush_recalc; } void ED_object_sculptmode_enter_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, const bool force_dyntopo, ReportList *reports) { const int mode_flag = OB_MODE_SCULPT; Mesh *me = BKE_mesh_from_object(ob); /* Enter sculpt mode. */ ob->mode |= mode_flag; MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd); if (flush_recalc) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); BKE_scene_graph_evaluated_ensure(depsgraph, bmain); } /* Create sculpt mode session data. */ if (ob->sculpt) { BKE_sculptsession_free(ob); } /* Copy the current mesh visibility to the Face Sets. */ BKE_sculpt_face_sets_ensure_from_base_mesh_visibility(me); sculpt_init_session(depsgraph, scene, ob); /* Mask layer is required. */ if (mmd) { /* XXX, we could attempt to support adding mask data mid-sculpt mode (with multi-res) * but this ends up being quite tricky (and slow). */ BKE_sculpt_mask_layers_ensure(ob, mmd); } if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f && fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f)) { BKE_report( reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable"); } else if (is_negative_m4(ob->obmat)) { BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable"); } Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT); BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT); paint_cursor_start(paint, SCULPT_mode_poll_view3d); /* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes, * As long as no data was added that is not supported. */ if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { const char *message_unsupported = NULL; if (me->totloop != me->totpoly * 3) { message_unsupported = TIP_("non-triangle face"); } else if (mmd != NULL) { message_unsupported = TIP_("multi-res modifier"); } else { enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob); if (flag == 0) { /* pass */ } else if (flag & DYNTOPO_WARN_VDATA) { message_unsupported = TIP_("vertex data"); } else if (flag & DYNTOPO_WARN_EDATA) { message_unsupported = TIP_("edge data"); } else if (flag & DYNTOPO_WARN_LDATA) { message_unsupported = TIP_("face data"); } else if (flag & DYNTOPO_WARN_MODIFIER) { message_unsupported = TIP_("constructive modifier"); } else { BLI_assert(0); } } if ((message_unsupported == NULL) || force_dyntopo) { /* Needed because we may be entering this mode before the undo system loads. */ wmWindowManager *wm = bmain->wm.first; bool has_undo = wm->undo_stack != NULL; /* Undo push is needed to prevent memory leak. */ if (has_undo) { SCULPT_undo_push_begin(ob, "Dynamic topology enable"); } SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, scene, ob); if (has_undo) { SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_DYNTOPO_BEGIN); SCULPT_undo_push_end(); } } else { BKE_reportf( reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported); me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY; } } /* Flush object mode. */ DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); } void ED_object_sculptmode_enter(struct bContext *C, Depsgraph *depsgraph, ReportList *reports) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = OBACT(view_layer); ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports); } void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) { const int mode_flag = OB_MODE_SCULPT; Mesh *me = BKE_mesh_from_object(ob); multires_flush_sculpt_updates(ob); /* Not needed for now. */ #if 0 MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob); const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd); #endif /* Always for now, so leaving sculpt mode always ensures scene is in * a consistent state. */ if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) { /* Dynamic topology must be disabled before exiting sculpt * mode to ensure the undo stack stays in a consistent * state. */ sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob); /* Store so we know to re-enable when entering sculpt mode. */ me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; } /* Leave sculpt mode. */ ob->mode &= ~mode_flag; BKE_sculptsession_free(ob); paint_cursor_delete_textures(); /* Never leave derived meshes behind. */ BKE_object_free_derived_caches(ob); /* Flush object mode. */ DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE); } void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = OBACT(view_layer); ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); } static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op) { struct wmMsgBus *mbus = CTX_wm_message_bus(C); Main *bmain = CTX_data_main(C); Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C); Scene *scene = CTX_data_scene(C); ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); Object *ob = OBACT(view_layer); const int mode_flag = OB_MODE_SCULPT; const bool is_mode_set = (ob->mode & mode_flag) != 0; if (!is_mode_set) { if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { return OPERATOR_CANCELLED; } } if (is_mode_set) { ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob); } else { if (depsgraph) { depsgraph = CTX_data_ensure_evaluated_depsgraph(C); } ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports); BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint); if (ob->mode & mode_flag) { Mesh *me = ob->data; /* Dyntopo adds its own undo step. */ if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) { /* Without this the memfile undo step is used, * while it works it causes lag when undoing the first undo step, see T71564. */ wmWindowManager *wm = CTX_wm_manager(C); if (wm->op_undo_depth <= 1) { SCULPT_undo_push_begin(ob, op->type->name); } } } } WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); WM_toolsystem_update_from_context_view3d(C); return OPERATOR_FINISHED; } static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot) { /* Identifiers. */ ot->name = "Sculpt Mode"; ot->idname = "SCULPT_OT_sculptmode_toggle"; ot->description = "Toggle sculpt mode in 3D view"; /* API callbacks. */ ot->exec = sculpt_mode_toggle_exec; ot->poll = ED_operator_object_active_editable_mesh; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob = CTX_data_active_object(C); ss->preview_vert_index_count = 0; int totpoints = 0; /* This function is called from the cursor drawing code, so the PBVH may not be build yet. */ if (!ss->pbvh) { return; } if (!ss->deform_modifiers_active) { return; } if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { return; } BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); if (!ss->pmap) { return; } float brush_co[3]; copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss)); BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_vertices"); /* Assuming an average of 6 edges per vertex in a triangulated mesh. */ const int max_preview_vertices = SCULPT_vertex_count_get(ss) * 3 * 2; if (ss->preview_vert_index_list == NULL) { ss->preview_vert_index_list = MEM_callocN(max_preview_vertices * sizeof(int), "preview lines"); } GSQueue *not_visited_vertices = BLI_gsqueue_new(sizeof(int)); int active_v = SCULPT_active_vertex_get(ss); BLI_gsqueue_push(not_visited_vertices, &active_v); while (!BLI_gsqueue_is_empty(not_visited_vertices)) { int from_v; BLI_gsqueue_pop(not_visited_vertices, &from_v); SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) { if (totpoints + (ni.size * 2) < max_preview_vertices) { int to_v = ni.index; ss->preview_vert_index_list[totpoints] = from_v; totpoints++; ss->preview_vert_index_list[totpoints] = to_v; totpoints++; if (!BLI_BITMAP_TEST(visited_vertices, to_v)) { BLI_BITMAP_ENABLE(visited_vertices, to_v); const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v); if (len_squared_v3v3(brush_co, co) < radius * radius) { BLI_gsqueue_push(not_visited_vertices, &to_v); } } } } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); } BLI_gsqueue_free(not_visited_vertices); MEM_freeN(visited_vertices); ss->preview_vert_index_count = totpoints; } static int vertex_to_loop_colors_exec(bContext *C, wmOperator *UNUSED(op)) { Object *ob = CTX_data_active_object(C); ID *data; data = ob->data; if (data && ID_IS_LINKED(data)) { return OPERATOR_CANCELLED; } if (ob->type != OB_MESH) { return OPERATOR_CANCELLED; } Mesh *mesh = ob->data; const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); if (mloopcol_layer_n == -1) { return OPERATOR_CANCELLED; } MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); if (MPropCol_layer_n == -1) { return OPERATOR_CANCELLED; } MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); for (int i = 0; i < mesh->totpoly; i++) { MPoly *c_poly = &polys[i]; for (int j = 0; j < c_poly->totloop; j++) { int loop_index = c_poly->loopstart + j; MLoop *c_loop = &loops[c_poly->loopstart + j]; loopcols[loop_index].r = (char)(vertcols[c_loop->v].color[0] * 255); loopcols[loop_index].g = (char)(vertcols[c_loop->v].color[1] * 255); loopcols[loop_index].b = (char)(vertcols[c_loop->v].color[2] * 255); loopcols[loop_index].a = (char)(vertcols[c_loop->v].color[3] * 255); } } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); return OPERATOR_FINISHED; } static void SCULPT_OT_vertex_to_loop_colors(wmOperatorType *ot) { /* identifiers */ ot->name = "Sculpt Vertex Color to Vertex Color"; ot->description = "Copy the Sculpt Vertex Color to a regular color layer"; ot->idname = "SCULPT_OT_vertex_to_loop_colors"; /* api callbacks */ ot->poll = SCULPT_vertex_colors_poll; ot->exec = vertex_to_loop_colors_exec; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } static int loop_to_vertex_colors_exec(bContext *C, wmOperator *UNUSED(op)) { Object *ob = CTX_data_active_object(C); ID *data; data = ob->data; if (data && ID_IS_LINKED(data)) { return OPERATOR_CANCELLED; } if (ob->type != OB_MESH) { return OPERATOR_CANCELLED; } Mesh *mesh = ob->data; const int mloopcol_layer_n = CustomData_get_active_layer(&mesh->ldata, CD_MLOOPCOL); if (mloopcol_layer_n == -1) { return OPERATOR_CANCELLED; } MLoopCol *loopcols = CustomData_get_layer_n(&mesh->ldata, CD_MLOOPCOL, mloopcol_layer_n); const int MPropCol_layer_n = CustomData_get_active_layer(&mesh->vdata, CD_PROP_COLOR); if (MPropCol_layer_n == -1) { return OPERATOR_CANCELLED; } MPropCol *vertcols = CustomData_get_layer_n(&mesh->vdata, CD_PROP_COLOR, MPropCol_layer_n); MLoop *loops = CustomData_get_layer(&mesh->ldata, CD_MLOOP); MPoly *polys = CustomData_get_layer(&mesh->pdata, CD_MPOLY); for (int i = 0; i < mesh->totpoly; i++) { MPoly *c_poly = &polys[i]; for (int j = 0; j < c_poly->totloop; j++) { int loop_index = c_poly->loopstart + j; MLoop *c_loop = &loops[c_poly->loopstart + j]; vertcols[c_loop->v].color[0] = (loopcols[loop_index].r / 255.0f); vertcols[c_loop->v].color[1] = (loopcols[loop_index].g / 255.0f); vertcols[c_loop->v].color[2] = (loopcols[loop_index].b / 255.0f); vertcols[c_loop->v].color[3] = (loopcols[loop_index].a / 255.0f); } } DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); return OPERATOR_FINISHED; } static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) { /* identifiers */ ot->name = "Vertex Color to Sculpt Vertex Color"; ot->description = "Copy the active loop color layer to the vertex color"; ot->idname = "SCULPT_OT_loop_to_vertex_colors"; /* api callbacks */ ot->poll = SCULPT_vertex_colors_poll; ot->exec = loop_to_vertex_colors_exec; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } static int sculpt_sample_color_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(e)) { Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Scene *scene = CTX_data_scene(C); Object *ob = CTX_data_active_object(C); Brush *brush = BKE_paint_brush(&sd->paint); SculptSession *ss = ob->sculpt; int active_vertex = SCULPT_active_vertex_get(ss); const float *active_vertex_color = SCULPT_vertex_color_get(ss, active_vertex); if (!active_vertex_color) { return OPERATOR_CANCELLED; } float color_srgb[3]; copy_v3_v3(color_srgb, active_vertex_color); IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb); BKE_brush_color_set(scene, brush, color_srgb); WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); return OPERATOR_FINISHED; } static void SCULPT_OT_sample_color(wmOperatorType *ot) { /* identifiers */ ot->name = "Sample Color"; ot->idname = "SCULPT_OT_sample_color"; ot->description = "Sample the vertex color of the active vertex"; /* api callbacks */ ot->invoke = sculpt_sample_color_invoke; ot->poll = SCULPT_vertex_colors_poll; ot->flag = OPTYPE_REGISTER; } /* 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, int index) { if (ss->vertex_info.connected_component) { return ss->vertex_info.connected_component[index]; } 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, int v_index_a, int v_index_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 { int nearest_vertex_index; 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.index); 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_index = vd.index; 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_index == -1) { join->nearest_vertex_index = nvtd->nearest_vertex_index; 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_index = nvtd->nearest_vertex_index; join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared; } } static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, 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, index), }; BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode); if (totnode == 0) { return -1; } 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, index)); NearestVertexFakeNeighborTLSData nvtd; nvtd.nearest_vertex_index = -1; nvtd.nearest_vertex_distance_squared = FLT_MAX; nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, index); 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_index; } typedef struct SculptTopologyIDFloodFillData { int next_id; } SculptTopologyIDFloodFillData; static bool SCULPT_connected_components_floodfill_cb( SculptSession *ss, int from_v, int to_v, bool UNUSED(is_duplicate), void *userdata) { SculptTopologyIDFloodFillData *data = userdata; ss->vertex_info.connected_component[from_v] = data->next_id; ss->vertex_info.connected_component[to_v] = data->next_id; return true; } static 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++) { if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) { SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); SCULPT_floodfill_add_initial(&flood, i); 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); 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++) { MPoly *poly = &base_mesh->mpoly[p]; for (int l = 0; l < poly->totloop; l++) { MLoop *loop = &base_mesh->mloop[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) { MEdge *edge = &base_mesh->medge[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 int from_v = i; /* This vertex does not have a fake neighbor yet, seach one for it. */ if (ss->fake_neighbors.fake_neighbor_index[from_v] == FAKE_NEIGHBOR_NONE) { const int to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist); if (to_v != -1) { /* 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); } /** * #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the * mask based on the difference between two colors (the active color and the color of any other * vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active * color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer * falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between * masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going * to be. */ #define MASK_BY_COLOR_SLOPE 0.25f static float sculpt_mask_by_color_delta_get(const float *color_a, const float *color_b, const float threshold, const bool invert) { float len = len_v3v3(color_a, color_b); /* Normalize len to the (0, 1) range. */ len = len / M_SQRT3; if (len < threshold - MASK_BY_COLOR_SLOPE) { len = 1.0f; } else if (len >= threshold) { len = 0.0f; } else { len = (-len + threshold) / MASK_BY_COLOR_SLOPE; } if (invert) { return 1.0f - len; } return len; } static float sculpt_mask_by_color_final_mask_get(const float current_mask, const float new_mask, const bool invert, const bool preserve_mask) { if (preserve_mask) { if (invert) { return min_ff(current_mask, new_mask); } return max_ff(current_mask, new_mask); } return new_mask; } typedef struct MaskByColorContiguousFloodFillData { float threshold; bool invert; float *new_mask; float initial_color[3]; } MaskByColorContiguousFloodFillData; static void do_mask_by_color_contiguous_update_nodes_cb( void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); bool update_node = false; const bool invert = data->mask_by_color_invert; const bool preserve_mask = data->mask_by_color_preserve_mask; PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { const float current_mask = *vd.mask; const float new_mask = data->mask_by_color_floodfill[vd.index]; *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); if (current_mask != *vd.mask) { update_node = true; if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; if (update_node) { BKE_pbvh_node_mark_redraw(data->nodes[n]); } } static bool sculpt_mask_by_color_contiguous_floodfill_cb( SculptSession *ss, int from_v, int to_v, bool is_duplicate, void *userdata) { MaskByColorContiguousFloodFillData *data = userdata; const float *current_color = SCULPT_vertex_color_get(ss, to_v); float new_vertex_mask = sculpt_mask_by_color_delta_get( current_color, data->initial_color, data->threshold, data->invert); data->new_mask[to_v] = new_vertex_mask; if (is_duplicate) { data->new_mask[to_v] = data->new_mask[from_v]; } float len = len_v3v3(current_color, data->initial_color); len = len / M_SQRT3; return len <= data->threshold; } static void sculpt_mask_by_color_contiguous(Object *object, const int vertex, const float threshold, const bool invert, const bool preserve_mask) { SculptSession *ss = object->sculpt; const int totvert = SCULPT_vertex_count_get(ss); float *new_mask = MEM_calloc_arrayN(totvert, sizeof(float), "new mask"); if (invert) { for (int i = 0; i < totvert; i++) { new_mask[i] = 1.0f; } } SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); SCULPT_floodfill_add_initial(&flood, vertex); MaskByColorContiguousFloodFillData ffd; ffd.threshold = threshold; ffd.invert = invert; ffd.new_mask = new_mask; copy_v3_v3(ffd.initial_color, SCULPT_vertex_color_get(ss, vertex)); SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill_cb, &ffd); SCULPT_floodfill_free(&flood); int totnode; PBVHNode **nodes; BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); SculptThreadedTaskData data = { .ob = object, .nodes = nodes, .mask_by_color_floodfill = new_mask, .mask_by_color_vertex = vertex, .mask_by_color_threshold = threshold, .mask_by_color_invert = invert, .mask_by_color_preserve_mask = preserve_mask, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range( 0, totnode, &data, do_mask_by_color_contiguous_update_nodes_cb, &settings); MEM_SAFE_FREE(nodes); MEM_freeN(new_mask); } static void do_mask_by_color_task_cb(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_MASK); bool update_node = false; const float threshold = data->mask_by_color_threshold; const bool invert = data->mask_by_color_invert; const bool preserve_mask = data->mask_by_color_preserve_mask; const float *active_color = SCULPT_vertex_color_get(ss, data->mask_by_color_vertex); PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { const float current_mask = *vd.mask; const float new_mask = sculpt_mask_by_color_delta_get(active_color, vd.col, threshold, invert); *vd.mask = sculpt_mask_by_color_final_mask_get(current_mask, new_mask, invert, preserve_mask); if (current_mask != *vd.mask) { update_node = true; if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; } } } BKE_pbvh_vertex_iter_end; if (update_node) { BKE_pbvh_node_mark_redraw(data->nodes[n]); } } static void sculpt_mask_by_color_full_mesh(Object *object, const int vertex, const float threshold, const bool invert, const bool preserve_mask) { SculptSession *ss = object->sculpt; int totnode; PBVHNode **nodes; BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); SculptThreadedTaskData data = { .ob = object, .nodes = nodes, .mask_by_color_vertex = vertex, .mask_by_color_threshold = threshold, .mask_by_color_invert = invert, .mask_by_color_preserve_mask = preserve_mask, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, do_mask_by_color_task_cb, &settings); MEM_SAFE_FREE(nodes); } static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Object *ob = CTX_data_active_object(C); SculptSession *ss = ob->sculpt; BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); /* Color data is not available in Multires. */ if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { return OPERATOR_CANCELLED; } if (!ss->vcol) { return OPERATOR_CANCELLED; } SCULPT_vertex_random_access_ensure(ss); /* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move, * so it needs to be updated here. */ SculptCursorGeometryInfo sgi; float mouse[2]; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); SCULPT_undo_push_begin(ob, "Mask by color"); const int active_vertex = SCULPT_active_vertex_get(ss); const float threshold = RNA_float_get(op->ptr, "threshold"); const bool invert = RNA_boolean_get(op->ptr, "invert"); const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask"); if (RNA_boolean_get(op->ptr, "contiguous")) { sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask); } else { sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask); } BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateMask); SCULPT_undo_push_end(); SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); return OPERATOR_FINISHED; } static void SCULPT_OT_mask_by_color(wmOperatorType *ot) { /* identifiers */ ot->name = "Mask By Color"; ot->idname = "SCULPT_OT_mask_by_color"; ot->description = "Creates a mask based on the sculpt vertex colors"; /* api callbacks */ ot->invoke = sculpt_mask_by_color_invoke; ot->poll = SCULPT_vertex_colors_poll; ot->flag = OPTYPE_REGISTER; ot->prop = RNA_def_boolean( ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas"); ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask"); ot->prop = RNA_def_boolean( ot->srna, "preserve_previous_mask", false, "Preserve Previous Mask", "Preserve the previous mask and add or subtract the new one generated by the colors"); RNA_def_float(ot->srna, "threshold", 0.35f, 0.0f, 1.0f, "Threshold", "How much changes in color affect the mask generation", 0.0f, 1.0f); } /* -------------------------------------------------------------------- */ /** \name Dyntopo Detail Size Edit Operator * \{ */ /* Defines how much the mouse movement will modify the detail size value. */ #define DETAIL_SIZE_DELTA_SPEED 0.08f #define DETAIL_SIZE_DELTA_ACCURATE_SPEED 0.004f typedef struct DyntopoDetailSizeEditCustomData { void *draw_handle; Object *active_object; float init_mval[2]; float accurate_mval[2]; float outline_col[4]; bool accurate_mode; bool sample_mode; float init_detail_size; float accurate_detail_size; float detail_size; float radius; float preview_tri[3][3]; float gizmo_mat[4][4]; } DyntopoDetailSizeEditCustomData; static void dyntopo_detail_size_parallel_lines_draw(uint pos3d, DyntopoDetailSizeEditCustomData *cd, const float start_co[3], const float end_co[3], bool flip, const float angle) { float object_space_constant_detail = 1.0f / (cd->detail_size * mat4_to_scale(cd->active_object->obmat)); /* The constant detail represents the maximum edge length allowed before subdividing it. If the * triangle grid preview is created with this value it will represent an ideal mesh density where * all edges have the exact maximum length, which never happens in practice. As the minimum edge * length for dyntopo is 0.4 * max_edge_length, this adjust the detail size to the average * between max and min edge length so the preview is more accurate. */ object_space_constant_detail *= 0.7f; const float total_len = len_v3v3(cd->preview_tri[0], cd->preview_tri[1]); const int tot_lines = (int)(total_len / object_space_constant_detail) + 1; const float tot_lines_fl = total_len / object_space_constant_detail; float spacing_disp[3]; sub_v3_v3v3(spacing_disp, end_co, start_co); normalize_v3(spacing_disp); float line_disp[3]; rotate_v2_v2fl(line_disp, spacing_disp, DEG2RAD(angle)); mul_v3_fl(spacing_disp, total_len / tot_lines_fl); immBegin(GPU_PRIM_LINES, (uint)tot_lines * 2); for (int i = 0; i < tot_lines; i++) { float line_length; if (flip) { line_length = total_len * ((float)i / (float)tot_lines_fl); } else { line_length = total_len * (1.0f - ((float)i / (float)tot_lines_fl)); } float line_start[3]; copy_v3_v3(line_start, start_co); madd_v3_v3v3fl(line_start, line_start, spacing_disp, i); float line_end[3]; madd_v3_v3v3fl(line_end, line_start, line_disp, line_length); immVertex3fv(pos3d, line_start); immVertex3fv(pos3d, line_end); } immEnd(); } static void dyntopo_detail_size_edit_draw(const bContext *UNUSED(C), ARegion *UNUSED(ar), void *arg) { DyntopoDetailSizeEditCustomData *cd = arg; GPU_blend(GPU_BLEND_ALPHA); GPU_line_smooth(true); uint pos3d = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); GPU_matrix_push(); GPU_matrix_mul(cd->gizmo_mat); /* Draw Cursor */ immUniformColor4fv(cd->outline_col); GPU_line_width(3.0f); imm_draw_circle_wire_3d(pos3d, 0, 0, cd->radius, 80); /* Draw Triangle. */ immUniformColor4f(0.9f, 0.9f, 0.9f, 0.8f); immBegin(GPU_PRIM_LINES, 6); immVertex3fv(pos3d, cd->preview_tri[0]); immVertex3fv(pos3d, cd->preview_tri[1]); immVertex3fv(pos3d, cd->preview_tri[1]); immVertex3fv(pos3d, cd->preview_tri[2]); immVertex3fv(pos3d, cd->preview_tri[2]); immVertex3fv(pos3d, cd->preview_tri[0]); immEnd(); /* Draw Grid */ GPU_line_width(1.0f); dyntopo_detail_size_parallel_lines_draw( pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], false, 60.0f); dyntopo_detail_size_parallel_lines_draw( pos3d, cd, cd->preview_tri[0], cd->preview_tri[1], true, 120.0f); dyntopo_detail_size_parallel_lines_draw( pos3d, cd, cd->preview_tri[0], cd->preview_tri[2], false, -60.0f); immUnbindProgram(); GPU_matrix_pop(); GPU_blend(GPU_BLEND_NONE); GPU_line_smooth(false); } static void dyntopo_detail_size_edit_cancel(bContext *C, wmOperator *op) { Object *active_object = CTX_data_active_object(C); SculptSession *ss = active_object->sculpt; ARegion *region = CTX_wm_region(C); DyntopoDetailSizeEditCustomData *cd = op->customdata; ED_region_draw_cb_exit(region->type, cd->draw_handle); ss->draw_faded_cursor = false; MEM_freeN(op->customdata); ED_workspace_status_text(C, NULL); } static void dyntopo_detail_size_sample_from_surface(Object *ob, DyntopoDetailSizeEditCustomData *cd) { SculptSession *ss = ob->sculpt; const int active_vertex = SCULPT_active_vertex_get(ss); float len_accum = 0; int num_neighbors = 0; SculptVertexNeighborIter ni; SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, active_vertex, ni) { len_accum += len_v3v3(SCULPT_vertex_co_get(ss, active_vertex), SCULPT_vertex_co_get(ss, ni.index)); num_neighbors++; } SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); if (num_neighbors > 0) { const float avg_edge_len = len_accum / num_neighbors; /* Use 0.7 as the average of min and max dyntopo edge length. */ const float detail_size = 0.7f / (avg_edge_len * mat4_to_scale(cd->active_object->obmat)); cd->detail_size = clamp_f(detail_size, 1.0f, 500.0f); } } static void dyntopo_detail_size_update_from_mouse_delta(DyntopoDetailSizeEditCustomData *cd, const wmEvent *event) { const float mval[2] = {event->mval[0], event->mval[1]}; float detail_size_delta; if (cd->accurate_mode) { detail_size_delta = mval[0] - cd->accurate_mval[0]; cd->detail_size = cd->accurate_detail_size + detail_size_delta * DETAIL_SIZE_DELTA_ACCURATE_SPEED; } else { detail_size_delta = mval[0] - cd->init_mval[0]; cd->detail_size = cd->init_detail_size + detail_size_delta * DETAIL_SIZE_DELTA_SPEED; } if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_PRESS) { cd->accurate_mode = true; copy_v2_v2(cd->accurate_mval, mval); cd->accurate_detail_size = cd->detail_size; } if (event->type == EVT_LEFTSHIFTKEY && event->val == KM_RELEASE) { cd->accurate_mode = false; cd->accurate_detail_size = 0.0f; } cd->detail_size = clamp_f(cd->detail_size, 1.0f, 500.0f); } static int dyntopo_detail_size_edit_modal(bContext *C, wmOperator *op, const wmEvent *event) { Object *active_object = CTX_data_active_object(C); SculptSession *ss = active_object->sculpt; ARegion *region = CTX_wm_region(C); DyntopoDetailSizeEditCustomData *cd = op->customdata; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; /* Cancel modal operator */ if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) || (event->type == RIGHTMOUSE && event->val == KM_PRESS)) { dyntopo_detail_size_edit_cancel(C, op); ED_region_tag_redraw(region); return OPERATOR_FINISHED; } /* Finish modal operator */ if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) || (event->type == EVT_RETKEY && event->val == KM_PRESS) || (event->type == EVT_PADENTER && event->val == KM_PRESS)) { ED_region_draw_cb_exit(region->type, cd->draw_handle); sd->constant_detail = cd->detail_size; ss->draw_faded_cursor = false; MEM_freeN(op->customdata); ED_region_tag_redraw(region); ED_workspace_status_text(C, NULL); return OPERATOR_FINISHED; } ED_region_tag_redraw(region); if (event->type == EVT_LEFTCTRLKEY && event->val == KM_PRESS) { cd->sample_mode = true; } if (event->type == EVT_LEFTCTRLKEY && event->val == KM_RELEASE) { cd->sample_mode = false; } /* Sample mode sets the detail size sampling the average edge length under the surface. */ if (cd->sample_mode) { dyntopo_detail_size_sample_from_surface(active_object, cd); return OPERATOR_RUNNING_MODAL; } /* Regular mode, changes the detail size by moving the cursor. */ dyntopo_detail_size_update_from_mouse_delta(cd, event); return OPERATOR_RUNNING_MODAL; } static int dyntopo_detail_size_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) { ARegion *region = CTX_wm_region(C); Object *active_object = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; Brush *brush = BKE_paint_brush(&sd->paint); DyntopoDetailSizeEditCustomData *cd = MEM_callocN(sizeof(DyntopoDetailSizeEditCustomData), "Dyntopo Detail Size Edit OP Custom Data"); /* Initial operator Custom Data setup. */ cd->draw_handle = ED_region_draw_cb_activate( region->type, dyntopo_detail_size_edit_draw, cd, REGION_DRAW_POST_VIEW); cd->active_object = active_object; cd->init_mval[0] = event->mval[0]; cd->init_mval[1] = event->mval[1]; cd->detail_size = sd->constant_detail; cd->init_detail_size = sd->constant_detail; copy_v4_v4(cd->outline_col, brush->add_col); op->customdata = cd; SculptSession *ss = active_object->sculpt; cd->radius = ss->cursor_radius; /* Generates the matrix to position the gizmo in the surface of the mesh using the same location * and orientation as the brush cursor. */ float cursor_trans[4][4], cursor_rot[4][4]; const float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f}; float quat[4]; copy_m4_m4(cursor_trans, active_object->obmat); translate_m4( cursor_trans, ss->cursor_location[0], ss->cursor_location[1], ss->cursor_location[2]); float cursor_normal[3]; if (!is_zero_v3(ss->cursor_sampled_normal)) { copy_v3_v3(cursor_normal, ss->cursor_sampled_normal); } else { copy_v3_v3(cursor_normal, ss->cursor_normal); } rotation_between_vecs_to_quat(quat, z_axis, cursor_normal); quat_to_mat4(cursor_rot, quat); copy_m4_m4(cd->gizmo_mat, cursor_trans); mul_m4_m4_post(cd->gizmo_mat, cursor_rot); /* Initize the position of the triangle vertices. */ const float y_axis[3] = {0.0f, cd->radius, 0.0f}; for (int i = 0; i < 3; i++) { zero_v3(cd->preview_tri[i]); rotate_v2_v2fl(cd->preview_tri[i], y_axis, DEG2RAD(120.0f * i)); } SCULPT_vertex_random_access_ensure(ss); WM_event_add_modal_handler(C, op); ED_region_tag_redraw(region); ss->draw_faded_cursor = true; const char *status_str = TIP_( "Move the mouse to change the dyntopo detail size. LMB: confirm size, ESC/RMB: cancel"); ED_workspace_status_text(C, status_str); return OPERATOR_RUNNING_MODAL; } static bool dyntopo_detail_size_edit_poll(bContext *C) { Object *ob = CTX_data_active_object(C); Sculpt *sd = CTX_data_tool_settings(C)->sculpt; return SCULPT_mode_poll(C) && ob->sculpt->bm && (sd->flags & SCULPT_DYNTOPO_DETAIL_CONSTANT); } static void SCULPT_OT_dyntopo_detail_size_edit(wmOperatorType *ot) { /* identifiers */ ot->name = "Edit Dyntopo Detail Size"; ot->description = "Modify the constant detail size of dyntopo interactively"; ot->idname = "SCULPT_OT_dyntopo_detail_size_edit"; /* api callbacks */ ot->poll = dyntopo_detail_size_edit_poll; ot->invoke = dyntopo_detail_size_edit_invoke; ot->modal = dyntopo_detail_size_edit_modal; ot->cancel = dyntopo_detail_size_edit_cancel; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } void ED_operatortypes_sculpt(void) { WM_operatortype_append(SCULPT_OT_brush_stroke); WM_operatortype_append(SCULPT_OT_sculptmode_toggle); WM_operatortype_append(SCULPT_OT_set_persistent_base); WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle); WM_operatortype_append(SCULPT_OT_optimize); WM_operatortype_append(SCULPT_OT_symmetrize); WM_operatortype_append(SCULPT_OT_detail_flood_fill); WM_operatortype_append(SCULPT_OT_sample_detail_size); WM_operatortype_append(SCULPT_OT_set_detail_size); WM_operatortype_append(SCULPT_OT_mesh_filter); WM_operatortype_append(SCULPT_OT_mask_filter); WM_operatortype_append(SCULPT_OT_dirty_mask); WM_operatortype_append(SCULPT_OT_mask_expand); WM_operatortype_append(SCULPT_OT_set_pivot_position); WM_operatortype_append(SCULPT_OT_face_sets_create); WM_operatortype_append(SCULPT_OT_face_sets_change_visibility); WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors); WM_operatortype_append(SCULPT_OT_face_sets_init); WM_operatortype_append(SCULPT_OT_cloth_filter); WM_operatortype_append(SCULPT_OT_face_sets_edit); WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture); WM_operatortype_append(SCULPT_OT_face_set_box_gesture); WM_operatortype_append(SCULPT_OT_trim_box_gesture); WM_operatortype_append(SCULPT_OT_trim_lasso_gesture); WM_operatortype_append(SCULPT_OT_project_line_gesture); WM_operatortype_append(SCULPT_OT_sample_color); WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors); WM_operatortype_append(SCULPT_OT_vertex_to_loop_colors); WM_operatortype_append(SCULPT_OT_color_filter); WM_operatortype_append(SCULPT_OT_mask_by_color); WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit); }