diff options
Diffstat (limited to 'source/blender/editors')
31 files changed, 6203 insertions, 122 deletions
diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 35bf295a678..7ca120fa95a 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -46,6 +46,7 @@ set(SRC editmesh_extrude_screw.c editmesh_extrude_spin.c editmesh_extrude_spin_gizmo.c + editmesh_fair.c editmesh_inset.c editmesh_intersect.c editmesh_knife.c diff --git a/source/blender/editors/mesh/editmesh_fair.c b/source/blender/editors/mesh/editmesh_fair.c new file mode 100644 index 00000000000..7f43043176e --- /dev/null +++ b/source/blender/editors/mesh/editmesh_fair.c @@ -0,0 +1,169 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edmesh + * + * Interactive editmesh knife tool. + */ + +#ifdef _MSC_VER +# define _USE_MATH_DEFINES +#endif + +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" + +#include "BLI_alloca.h" +#include "BLI_array.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_smallhash.h" +#include "BLI_string.h" + +#include "BLT_translation.h" + +#include "BKE_bvhutils.h" +#include "BKE_context.h" +#include "BKE_editmesh.h" +#include "BKE_editmesh_bvh.h" +#include "BKE_layer.h" +#include "BKE_mesh_fair.h" +#include "BKE_report.h" + +#include "ED_mesh.h" +#include "ED_screen.h" +#include "ED_space_api.h" +#include "ED_view3d.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "DNA_object_types.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "mesh_intern.h" /* own include */ + +static EnumPropertyItem prop_edit_mesh_fair_selection_mode_items[] = { + { + MESH_FAIRING_DEPTH_POSITION, + "POSITION", + 0, + "Position", + "Fair positions", + }, + { + MESH_FAIRING_DEPTH_TANGENCY, + "TANGENCY", + 0, + "Tangency", + "Fair tangency", + }, + /* + { + MESH_FAIRING_DEPTH_CURVATURE, + "CURVATURE", + 0, + "Curvature", + "Fair curvature", + }, + */ + {0, NULL, 0, NULL, NULL}, +}; + +static int edbm_fair_vertices_exec(bContext *C, wmOperator *op) +{ + const int mode = RNA_enum_get(op->ptr, "mode"); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + if ((em->bm->totvertsel == 0)) { + continue; + } + + BMesh *bm = em->bm; + BMVert *v; + BMIter iter; + int i; + bool *fairing_mask = MEM_calloc_arrayN(bm->totvert, sizeof(bool), "fairing mask"); + BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) { + if (!BM_elem_flag_test(v, BM_ELEM_SELECT)) { + continue; + } + if (BM_vert_is_boundary(v)) { + continue; + } + if (!BM_vert_is_manifold(v)) { + continue; + } + fairing_mask[i] = true; + } + BKE_bmesh_prefair_and_fair_vertices(bm, fairing_mask, mode); + MEM_freeN(fairing_mask); + + EDBM_mesh_normals_update(em); + EDBM_update(obedit->data, + &(const struct EDBMUpdate_Params){ + .calc_looptri = false, + .calc_normals = true, + .is_destructive = true, + }); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_fair_vertices(wmOperatorType *ot) +{ + /* description */ + ot->name = "Fair Vertices"; + ot->idname = "MESH_OT_fair_vertices"; + ot->description = "Create a smooth as possible geometry patch"; + + /* callbacks */ + ot->exec = edbm_fair_vertices_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, + "mode", + prop_edit_mesh_fair_selection_mode_items, + MESH_FAIRING_DEPTH_POSITION, + "Mode", + ""); +} diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 03c99e40d1e..8dbca8a4ea8 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -268,6 +268,9 @@ void MESH_OT_paint_mask_extract(struct wmOperatorType *ot); void MESH_OT_face_set_extract(struct wmOperatorType *ot); void MESH_OT_paint_mask_slice(struct wmOperatorType *ot); +/* *** editmesh_fair.c *** */ +void MESH_OT_fair_vertices(struct wmOperatorType *ot); + struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf); #if defined(WITH_FREESTYLE) diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index 94823b92c44..d98e03405a4 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -175,6 +175,8 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_bevel); + WM_operatortype_append(MESH_OT_fair_vertices); + WM_operatortype_append(MESH_OT_bridge_edge_loops); WM_operatortype_append(MESH_OT_inset); WM_operatortype_append(MESH_OT_offset_edge_loops); diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c index 2c58ef02486..da38f7a627c 100644 --- a/source/blender/editors/object/object_modes.c +++ b/source/blender/editors/object/object_modes.c @@ -21,11 +21,17 @@ * actual mode switching logic is per-object type. */ +#include "MEM_guardedalloc.h" + #include "DNA_gpencil_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_workspace_types.h" +#include "PIL_time.h" + #include "BLI_kdopbvh.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -38,6 +44,8 @@ #include "BKE_gpencil_modifier.h" #include "BKE_layer.h" #include "BKE_main.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_types.h" #include "BKE_modifier.h" #include "BKE_object.h" #include "BKE_paint.h" @@ -50,6 +58,7 @@ #include "RNA_access.h" #include "RNA_define.h" +#include "RNA_enum_types.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" @@ -57,10 +66,13 @@ #include "ED_armature.h" #include "ED_gpencil.h" #include "ED_screen.h" +#include "ED_space_api.h" #include "ED_transform_snap_object_context.h" #include "ED_undo.h" #include "ED_view3d.h" +#include "UI_resources.h" + #include "WM_toolsystem.h" #include "ED_object.h" /* own include */ @@ -408,7 +420,7 @@ bool ED_object_mode_generic_has_data(struct Depsgraph *depsgraph, const struct O /** \} */ -/* -------------------------------------------------------------------- */ + /* -------------------------------------------------------------------- */ /** \name Transfer Mode * * Enters the same mode of the current active object in another object, @@ -588,4 +600,4 @@ void OBJECT_OT_transfer_mode(wmOperatorType *ot) "Flash the target object when transferring the mode"); } -/** \} */ +/** \} */
\ No newline at end of file diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index 75269dffec8..2d535b12b08 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -38,6 +38,7 @@ #include "DNA_light_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" #include "DNA_meta_types.h" #include "DNA_object_types.h" #include "DNA_particle_types.h" @@ -63,6 +64,7 @@ #include "BKE_constraint.h" #include "BKE_context.h" #include "BKE_curve.h" +#include "BKE_customdata.h" #include "BKE_displist.h" #include "BKE_editmesh.h" #include "BKE_fcurve.h" @@ -85,9 +87,11 @@ #include "BKE_modifier.h" #include "BKE_node.h" #include "BKE_object.h" +#include "BKE_paint.h" #include "BKE_pointcloud.h" #include "BKE_report.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "BKE_speaker.h" #include "BKE_texture.h" #include "BKE_volume.h" @@ -113,6 +117,7 @@ #include "ED_mesh.h" #include "ED_object.h" #include "ED_screen.h" +#include "ED_sculpt.h" #include "ED_view3d.h" #include "object_intern.h" @@ -2753,6 +2758,63 @@ char *ED_object_ot_drop_named_material_tooltip(bContext *C, return result; } +static void drop_named_material_face_set_slots_update(bContext *C, + Object *ob, + const wmEvent *event) +{ + Main *bmain = CTX_data_main(C); + Mesh *mesh = BKE_mesh_from_object(ob); + + bScreen *screen = CTX_wm_screen(C); + ARegion *region = BKE_screen_find_main_region_at_xy(screen, SPACE_VIEW3D, event->x, event->y); + + const float mval[2] = {event->x - region->winrct.xmin, event->y - region->winrct.ymin}; + const int face_set_id = ED_sculpt_face_sets_active_update_and_get(C, ob, mval); + + int *face_sets = CustomData_get_layer(&mesh->pdata, CD_SCULPT_FACE_SETS); + + short face_set_nr = -1; + for (int i = 0; i < mesh->totpoly; i++) { + if (face_sets[i] != face_set_id) { + continue; + } + face_set_nr = mesh->mpoly[i].mat_nr; + break; + } + + bool create_new_slot = false; + for (int i = 0; i < mesh->totpoly; i++) { + if (face_sets[i] == face_set_id) { + if (mesh->mpoly[i].mat_nr != face_set_nr) { + create_new_slot = true; + break; + } + } + else { + if (mesh->mpoly[i].mat_nr == face_set_nr) { + create_new_slot = true; + break; + } + } + } + + if (create_new_slot) { + BKE_object_material_slot_add(bmain, ob); + } + else { + ob->actcol = face_set_nr + 1; + } + + const short active_mat_slot = ob->actcol; + const short material_nr = active_mat_slot - 1; + for (int i = 0; i < mesh->totpoly; i++) { + if (face_sets[i] != face_set_id) { + continue; + } + mesh->mpoly[i].mat_nr = material_nr; + } +} + static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Main *bmain = CTX_data_main(C); @@ -2769,6 +2831,10 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_CANCELLED; } + if (ob->mode == OB_MODE_SCULPT) { + drop_named_material_face_set_slots_update(C, ob, event); + } + BKE_object_material_assign(CTX_data_main(C), ob, ma, mat_slot, BKE_MAT_ASSIGN_USERPREF); DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); @@ -2780,6 +2846,25 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_FINISHED; } +bool ED_operator_drop_material_poll(bContext *C) +{ + Scene *scene = CTX_data_scene(C); + Object *obact = CTX_data_active_object(C); + + if (scene == NULL || ID_IS_LINKED(scene)) { + return false; + } + if (CTX_data_edit_object(C)) { + return false; + } + + if (obact && !ELEM(obact->mode, OB_MODE_OBJECT, OB_MODE_SCULPT)) { + return false; + } + + return true; +} + /* used for dropbox */ /* assigns to object under cursor, only first material slot */ void OBJECT_OT_drop_named_material(wmOperatorType *ot) @@ -2790,7 +2875,7 @@ void OBJECT_OT_drop_named_material(wmOperatorType *ot) /* api callbacks */ ot->invoke = drop_named_material_invoke; - ot->poll = ED_operator_objectmode; + ot->poll = ED_operator_drop_material_poll; /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc index d56cb3c7548..e859b8fa85a 100644 --- a/source/blender/editors/object/object_remesh.cc +++ b/source/blender/editors/object/object_remesh.cc @@ -171,7 +171,8 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) BKE_mesh_calc_normals(new_mesh); if (mesh->flag & ME_REMESH_REPROJECT_VOLUME || mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK || - mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) { + mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS || + mesh->flag & ME_REMESH_REPROJECT_MATERIALS) { BKE_mesh_runtime_clear_geometry(mesh); } @@ -187,11 +188,19 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) BKE_remesh_reproject_sculpt_face_sets(new_mesh, mesh); } + if (mesh->flag & ME_REMESH_REPROJECT_MATERIALS) { + BKE_remesh_reproject_materials(new_mesh, mesh); + } + if (mesh->flag & ME_REMESH_REPROJECT_VERTEX_COLORS) { BKE_mesh_runtime_clear_geometry(mesh); BKE_remesh_reproject_vertex_paint(new_mesh, mesh); } + if (ob->mode == OB_MODE_SCULPT) { + BKE_mesh_remesh_sculpt_array_update(ob, new_mesh, mesh); + } + BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true); if (smooth_normals) { diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 3b668a1bd4c..c8c88e9d412 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -56,6 +56,7 @@ set(SRC paint_vertex_weight_ops.c paint_vertex_weight_utils.c sculpt.c + sculpt_array.c sculpt_automasking.c sculpt_boundary.c sculpt_cloth.c @@ -63,15 +64,19 @@ set(SRC sculpt_dyntopo.c sculpt_expand.c sculpt_face_set.c + sculpt_face_set_topology.c sculpt_filter_color.c sculpt_filter_mask.c sculpt_filter_mesh.c sculpt_geodesic.c + sculpt_gradient.c sculpt_mask_expand.c sculpt_mask_init.c sculpt_multiplane_scrape.c sculpt_paint_color.c sculpt_pose.c + sculpt_poly_loop.c + sculpt_symmetrize.c sculpt_smooth.c sculpt_transform.c sculpt_undo.c diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index ab2b2f4b16b..100ba5baf3b 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -37,6 +37,7 @@ #include "DNA_space_types.h" #include "DNA_userdef_types.h" #include "DNA_view3d_types.h" +#include "DNA_mesh_types.h" #include "BKE_brush.h" #include "BKE_colortools.h" @@ -1383,7 +1384,7 @@ static void paint_cursor_sculpt_session_update_and_init(PaintCursorContext *pcon pcontext->prev_active_vertex_index = ss->active_vertex_index; if (!ups->stroke_active) { pcontext->is_cursor_over_mesh = SCULPT_cursor_geometry_info_update( - C, &gi, mouse, (pcontext->brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE)); + C, &gi, mouse, (pcontext->brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE), false); copy_v3_v3(pcontext->location, gi.location); copy_v3_v3(pcontext->normal, gi.normal); } @@ -1464,6 +1465,103 @@ static void paint_draw_3D_view_inactive_brush_cursor(PaintCursorContext *pcontex 80); } +static void sculpt_cursor_draw_active_face_set_color_set(PaintCursorContext *pcontext) { + + SculptSession *ss = pcontext->ss; + + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return; + } + + const int active_face_set = SCULPT_active_face_set_get(ss); + uchar color[4] = {UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX}; + Object *ob = CTX_data_active_object(pcontext->C); + Mesh *mesh = ob->data; + if (active_face_set != mesh->face_sets_color_default) { + BKE_paint_face_set_overlay_color_get(active_face_set, mesh->face_sets_color_seed, color); + color[3] = UCHAR_MAX; + } + else { + color[3] /= 2; + } + + + immUniformColor4ubv(color); +} + +static void sculpt_cursor_draw_3D_face_set_preview(PaintCursorContext *pcontext) +{ + + SculptSession *ss = pcontext->ss; + + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return; + } + + GPU_line_width(1.0f); + sculpt_cursor_draw_active_face_set_color_set(pcontext); + + MPoly *poly = &ss->mpoly[ss->active_face_index]; + MLoop *loops = ss->mloop; + const int totpoints = poly->totloop; + + /* + immBegin(GPU_PRIM_LINE_STRIP, totpoints + 1); + for (int i = 0; i < totpoints; i++) { + float co[3]; + copy_v3_v3(co, SCULPT_vertex_co_get(ss, loops[poly->loopstart + i].v)); + immVertex3fv(pcontext->pos, co); + } + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart].v)); + immEnd(); + */ + + + /* + int v_in_poly = 0; + for (int i = 0; i < totpoints; i++) { + if (ss->active_vertex_index == loops[poly->loopstart + i].v) { + v_in_poly = i; + } + } + const int next_v = v_in_poly == poly->totloop - 1? 0 : v_in_poly + 1; + const int prev_v = v_in_poly == 0? poly->totloop - 1 : v_in_poly - 1; + + + immBegin(GPU_PRIM_LINES, 4); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex_index)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + next_v].v)); + + + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ss->active_vertex_index)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, loops[poly->loopstart + prev_v].v)); + + immEnd(); + */ + + if (!ss->pmap) { + return; + } + + int total = 0; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex_index, ni) { + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + + + + immBegin(GPU_PRIM_LINES, total * 2); + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, ss->active_vertex_index, ni) { + immVertex3fv(pcontext->pos, SCULPT_active_vertex_co_get(ss)); + immVertex3fv(pcontext->pos, SCULPT_vertex_co_get(ss, ni.index)); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + immEnd(); +} + static void paint_cursor_update_object_space_radius(PaintCursorContext *pcontext) { if (!BKE_brush_use_locked_size(pcontext->scene, pcontext->brush)) { @@ -1609,6 +1707,7 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * } if (len_v3v3(active_vertex_co, pcontext->location) < pcontext->radius) { immUniformColor3fvAlpha(pcontext->outline_col, pcontext->outline_alpha); + sculpt_cursor_draw_active_face_set_color_set(pcontext); cursor_draw_point_with_symmetry(pcontext->pos, pcontext->region, active_vertex_co, @@ -1654,6 +1753,13 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * 2); } + + /* Transform Pivot. */ + if (pcontext->paint && pcontext->paint->flags & PAINT_SCULPT_SHOW_PIVOT) { + cursor_draw_point_screen_space( + pcontext->pos, pcontext->region, pcontext->ss->pivot_pos, pcontext->vc.obact->obmat, 2); + } + if (is_brush_tool && brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { paint_cursor_preview_boundary_data_update(pcontext, update_previews); paint_cursor_preview_boundary_data_pivot_draw(pcontext); @@ -1692,6 +1798,9 @@ static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext * SCULPT_boundary_pivot_line_preview_draw(pcontext->pos, pcontext->ss); } + /* Face Set Preview. */ + sculpt_cursor_draw_3D_face_set_preview(pcontext); + GPU_matrix_pop(); /* Drawing Cursor overlays in Paint Cursor space (as additional info on top of the brush cursor) @@ -1776,6 +1885,11 @@ static void paint_cursor_cursor_draw_3d_view_brush_cursor_active(PaintCursorCont pcontext->pos, brush, ss, pcontext->outline_col, pcontext->outline_alpha); } + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + /* Draw Array Path. */ + SCULPT_array_path_draw(pcontext->pos, brush, ss); + } + if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) { if (brush->cloth_force_falloff_type == BRUSH_CLOTH_FORCE_FALLOFF_PLANE) { SCULPT_cloth_plane_falloff_preview_draw( diff --git a/source/blender/editors/sculpt_paint/paint_mask.c b/source/blender/editors/sculpt_paint/paint_mask.c index e65d6ce2d48..a0f4bac2e7c 100644 --- a/source/blender/editors/sculpt_paint/paint_mask.c +++ b/source/blender/editors/sculpt_paint/paint_mask.c @@ -44,6 +44,7 @@ #include "BKE_context.h" #include "BKE_lib_id.h" #include "BKE_mesh.h" +#include "BKE_mesh_fair.h" #include "BKE_multires.h" #include "BKE_paint.h" #include "BKE_pbvh.h" @@ -946,6 +947,24 @@ static EnumPropertyItem prop_trim_orientation_types[] = { {0, NULL, 0, NULL, NULL}, }; +typedef enum eSculptTrimLocationType { + SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE, + SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME, +} eSculptTrimLocationType; +static EnumPropertyItem prop_trim_location_types[] = { + {SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE, + "DEPTH_SURFACE", + 0, + "Surface", + "Use the surface under the cursor to locate the trimming shape"}, + {SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME, + "DEPTH_VOLUME", + 0, + "Volume", + "Use the volume of the mesh to locate the trimming shape in the center of the volume"}, + {0, NULL, 0, NULL, NULL}, +}; + typedef struct SculptGestureTrimOperation { SculptGestureOperation op; @@ -959,6 +978,7 @@ typedef struct SculptGestureTrimOperation { eSculptTrimOperationType mode; eSculptTrimOrientationType orientation; + eSculptTrimLocationType location; } SculptGestureTrimOperation; static void sculpt_gesture_trim_normals_update(SculptGestureContext *sgcontext) @@ -1053,8 +1073,19 @@ static void sculpt_gesture_trim_calculate_depth(SculptGestureContext *sgcontext) if (trim_operation->use_cursor_depth) { float world_space_gesture_initial_location[3]; - mul_v3_m4v3( - world_space_gesture_initial_location, vc->obact->obmat, ss->gesture_initial_location); + + switch (trim_operation->location) { + case SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE: { + mul_v3_m4v3( + world_space_gesture_initial_location, vc->obact->obmat, ss->gesture_initial_location); + + } break; + case SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME: { + float center_co[3]; + mid_v3_v3v3(center_co, ss->gesture_initial_location, ss->gesture_initial_back_location); + mul_v3_m4v3(world_space_gesture_initial_location, vc->obact->obmat, center_co); + } break; + } float mid_point_depth; if (trim_operation->orientation == SCULPT_GESTURE_TRIM_ORIENTATION_VIEW) { @@ -1067,6 +1098,17 @@ static void sculpt_gesture_trim_calculate_depth(SculptGestureContext *sgcontext) /* When using normal orientation, if the stroke started over the mesh, position the mid point * at 0 distance from the shape plane. This positions the trimming shape half inside of the * surface. */ + if (SCULPT_GESTURE_TRIM_LOCATION_DEPTH_VOLUME) { + mid_point_depth = ss->gesture_initial_hit ? + dist_signed_to_plane_v3(world_space_gesture_initial_location, + shape_plane) : + (trim_operation->depth_back + trim_operation->depth_front) * 0.5f; + } + else { + mid_point_depth = ss->gesture_initial_hit ? + 0.0f : + (trim_operation->depth_back + trim_operation->depth_front) * 0.5f; + } mid_point_depth = ss->gesture_initial_hit ? 0.0f : (trim_operation->depth_back + trim_operation->depth_front) * 0.5f; @@ -1358,6 +1400,7 @@ static void sculpt_gesture_init_trim_properties(SculptGestureContext *sgcontext, trim_operation->mode = RNA_enum_get(op->ptr, "trim_mode"); trim_operation->use_cursor_depth = RNA_boolean_get(op->ptr, "use_cursor_depth"); trim_operation->orientation = RNA_enum_get(op->ptr, "trim_orientation"); + trim_operation->location = RNA_enum_get(op->ptr, "trim_location"); /* If the cursor was not over the mesh, force the orientation to view. */ if (!sgcontext->ss->gesture_initial_hit) { @@ -1385,18 +1428,52 @@ static void sculpt_trim_gesture_operator_properties(wmOperatorType *ot) SCULPT_GESTURE_TRIM_ORIENTATION_VIEW, "Shape Orientation", NULL); + + RNA_def_enum(ot->srna, + "trim_location", + prop_trim_location_types, + SCULPT_GESTURE_TRIM_LOCATION_DEPTH_SURFACE, + "Shape Location", + NULL); } /* Project Gesture Operation. */ +typedef enum eSculptProjectDeformationMode { + SCULPT_GESTURE_PROJECT_DEFORM_PROJECT, + SCULPT_GESTURE_PROJECT_DEFORM_FAIR, +} eSculptProjectDeformationMode; + +static EnumPropertyItem prop_project_deformation_mode_types[] = { + {SCULPT_GESTURE_PROJECT_DEFORM_PROJECT, + "PROJECT", + 0, + "Project to Plane", + "Project the affected geometry into the gesture plane"}, + {SCULPT_GESTURE_PROJECT_DEFORM_FAIR, + "FAIR", + 0, + "Fair Positions", + "Use position fairing in the affected area"}, + {0, NULL, 0, NULL, NULL}, +}; typedef struct SculptGestureProjectOperation { SculptGestureOperation operation; + eSculptProjectDeformationMode deformation_mode; + bool *fairing_mask; } SculptGestureProjectOperation; static void sculpt_gesture_project_begin(bContext *C, SculptGestureContext *sgcontext) { + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - BKE_sculpt_update_object_for_edit(depsgraph, sgcontext->vc.obact, false, false, false); + BKE_sculpt_update_object_for_edit(depsgraph, sgcontext->vc.obact, true, false, false); + + if (project_operation->deformation_mode == SCULPT_GESTURE_PROJECT_DEFORM_FAIR) { + const int totvert = SCULPT_vertex_count_get(sgcontext->ss); + project_operation->fairing_mask = MEM_calloc_arrayN(totvert, sizeof(bool), "fairing mask"); + } } static void project_line_gesture_apply_task_cb(void *__restrict userdata, @@ -1439,27 +1516,138 @@ static void project_line_gesture_apply_task_cb(void *__restrict userdata, } } +static void project_gesture_tag_fairing_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptGestureContext *sgcontext = userdata; + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; + + PBVHNode *node = sgcontext->nodes[i]; + PBVHVertexIter vd; + bool any_updated = false; + + SCULPT_undo_push_node(sgcontext->vc.obact, node, SCULPT_UNDO_COORDS); + + BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (!sculpt_gesture_is_vertex_effected(sgcontext, &vd)) { + continue; + } + project_operation->fairing_mask[vd.index] = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + any_updated = true; + } + BKE_pbvh_vertex_iter_end; + + if (any_updated) { + BKE_pbvh_node_mark_update(node); + } +} + +static void project_gesture_project_fairing_boundary_task_cb( + void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptGestureContext *sgcontext = userdata; + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; + SculptSession *ss = sgcontext->ss; + + PBVHNode *node = sgcontext->nodes[i]; + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (sgcontext->ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + bool project_vertex = false; + bool vertex_fairing_mask = project_operation->fairing_mask[vd.index]; + + if (!project_operation->fairing_mask[vd.index]) { + // continue; + } + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + if (project_operation->fairing_mask[ni.index] != vertex_fairing_mask) { + project_vertex = true; + break; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (!project_vertex) { + continue; + } + + closest_to_plane_v3(vd.co, sgcontext->line.plane, vd.co); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + static void sculpt_gesture_project_apply_for_symmetry_pass(bContext *UNUSED(C), SculptGestureContext *sgcontext) { + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, sgcontext->totnode); - switch (sgcontext->shape_type) { - case SCULPT_GESTURE_SHAPE_LINE: + switch (project_operation->deformation_mode) { + case SCULPT_GESTURE_PROJECT_DEFORM_PROJECT: + BLI_assert(sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE); BLI_task_parallel_range( 0, sgcontext->totnode, sgcontext, project_line_gesture_apply_task_cb, &settings); break; - case SCULPT_GESTURE_SHAPE_LASSO: - case SCULPT_GESTURE_SHAPE_BOX: - /* Gesture shape projection not implemented yet. */ - BLI_assert(false); + case SCULPT_GESTURE_PROJECT_DEFORM_FAIR: + BLI_task_parallel_range( + 0, sgcontext->totnode, sgcontext, project_gesture_tag_fairing_task_cb, &settings); + if (sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE) { + /* TODO: this needs to loop over all nodes to avoid artifacts. */ + /* + BLI_task_parallel_range(0, + sgcontext->totnode, + sgcontext, + project_gesture_project_fairing_boundary_task_cb, + &settings); + */ + } break; } } +static void sculpt_gesture_fairing_apply(SculptGestureContext *sgcontext) +{ + SculptSession *ss = sgcontext->vc.obact->sculpt; + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + Mesh *mesh = sgcontext->vc.obact->data; + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + BKE_mesh_prefair_and_fair_vertices( + mesh, mvert, project_operation->fairing_mask, MESH_FAIRING_DEPTH_POSITION); + } break; + case PBVH_BMESH: { + BKE_bmesh_prefair_and_fair_vertices( + ss->bm, project_operation->fairing_mask, MESH_FAIRING_DEPTH_POSITION); + } break; + case PBVH_GRIDS: + BLI_assert(false); + } +} + static void sculpt_gesture_project_end(bContext *C, SculptGestureContext *sgcontext) { + SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) + sgcontext->operation; + if (project_operation->deformation_mode == SCULPT_GESTURE_PROJECT_DEFORM_FAIR) { + sculpt_gesture_fairing_apply(sgcontext); + MEM_SAFE_FREE(project_operation->fairing_mask); + } + SculptSession *ss = sgcontext->ss; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; if (ss->deform_modifiers_active || ss->shapekey_active) { @@ -1470,20 +1658,38 @@ static void sculpt_gesture_project_end(bContext *C, SculptGestureContext *sgcont SCULPT_flush_update_done(C, sgcontext->vc.obact, SCULPT_UPDATE_COORDS); } -static void sculpt_gesture_init_project_properties(SculptGestureContext *sgcontext, - wmOperator *UNUSED(op)) +static void sculpt_gesture_init_project_properties(SculptGestureContext *sgcontext, wmOperator *op) { - sgcontext->operation = MEM_callocN(sizeof(SculptGestureFaceSetOperation), "Project Operation"); + sgcontext->operation = MEM_callocN(sizeof(SculptGestureProjectOperation), "Project Operation"); SculptGestureProjectOperation *project_operation = (SculptGestureProjectOperation *) sgcontext->operation; + if (sgcontext->shape_type == SCULPT_GESTURE_SHAPE_LINE) { + project_operation->deformation_mode = RNA_enum_get(op->ptr, "deformation_mode"); + } + else { + /* All gesture shapes that are not a line need to be deformed by fairing as they can't be + * projected to a plane. */ + project_operation->deformation_mode = SCULPT_GESTURE_PROJECT_DEFORM_FAIR; + } + project_operation->operation.sculpt_gesture_begin = sculpt_gesture_project_begin; project_operation->operation.sculpt_gesture_apply_for_symmetry_pass = sculpt_gesture_project_apply_for_symmetry_pass; project_operation->operation.sculpt_gesture_end = sculpt_gesture_project_end; } +static void sculpt_project_gesture_operator_properties(wmOperatorType *ot) +{ + RNA_def_enum(ot->srna, + "deformation_mode", + prop_project_deformation_mode_types, + SCULPT_GESTURE_PROJECT_DEFORM_PROJECT, + "Deformation mode", + NULL); +} + static int paint_mask_gesture_box_exec(bContext *C, wmOperator *op) { SculptGestureContext *sgcontext = sculpt_gesture_init_from_box(C, op); @@ -1577,7 +1783,7 @@ static int sculpt_trim_gesture_box_invoke(bContext *C, wmOperator *op, const wmE SculptCursorGeometryInfo sgi; float mouse[2] = {event->mval[0], event->mval[1]}; SCULPT_vertex_random_access_ensure(ss); - ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); if (ss->gesture_initial_hit) { copy_v3_v3(ss->gesture_initial_location, sgi.location); copy_v3_v3(ss->gesture_initial_normal, sgi.normal); @@ -1618,9 +1824,10 @@ static int sculpt_trim_gesture_lasso_invoke(bContext *C, wmOperator *op, const w SculptCursorGeometryInfo sgi; float mouse[2] = {event->mval[0], event->mval[1]}; SCULPT_vertex_random_access_ensure(ss); - ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + ss->gesture_initial_hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, true); if (ss->gesture_initial_hit) { copy_v3_v3(ss->gesture_initial_location, sgi.location); + copy_v3_v3(ss->gesture_initial_back_location, sgi.back_location); copy_v3_v3(ss->gesture_initial_normal, sgi.normal); } @@ -1639,6 +1846,44 @@ static int project_gesture_line_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static int project_gesture_lasso_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* Fairing operations are not supported in Multires. */ + return OPERATOR_CANCELLED; + } + + SculptGestureContext *sgcontext = sculpt_gesture_init_from_lasso(C, op); + if (!sgcontext) { + return OPERATOR_CANCELLED; + } + sculpt_gesture_init_project_properties(sgcontext, op); + sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_context_free(sgcontext); + return OPERATOR_FINISHED; +} + +static int project_gesture_box_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + /* Fairing operations are not supported in Multires. */ + return OPERATOR_CANCELLED; + } + + SculptGestureContext *sgcontext = sculpt_gesture_init_from_box(C, op); + if (!sgcontext) { + return OPERATOR_CANCELLED; + } + sculpt_gesture_init_project_properties(sgcontext, op); + sculpt_gesture_apply(C, sgcontext); + sculpt_gesture_context_free(sgcontext); + return OPERATOR_FINISHED; +} + void PAINT_OT_mask_lasso_gesture(wmOperatorType *ot) { ot->name = "Mask Lasso Gesture"; @@ -1799,4 +2044,48 @@ void SCULPT_OT_project_line_gesture(wmOperatorType *ot) /* Properties. */ WM_operator_properties_gesture_straightline(ot, WM_CURSOR_EDIT); sculpt_gesture_operator_properties(ot); + + sculpt_project_gesture_operator_properties(ot); +} + +void SCULPT_OT_project_lasso_gesture(wmOperatorType *ot) +{ + ot->name = "Project Lasso Gesture"; + ot->idname = "SCULPT_OT_project_lasso_gesture"; + ot->description = "Project by fairing the geometry to the curve defined by the lasso gesture"; + + ot->invoke = WM_gesture_lasso_invoke; + ot->modal = WM_gesture_lasso_modal; + ot->exec = project_gesture_lasso_exec; + + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; + + /* Properties. */ + WM_operator_properties_gesture_lasso(ot); + sculpt_gesture_operator_properties(ot); + + sculpt_project_gesture_operator_properties(ot); +} + +void SCULPT_OT_project_box_gesture(wmOperatorType *ot) +{ + ot->name = "Project Box Gesture"; + ot->idname = "SCULPT_OT_project_box_gesture"; + ot->description = "Project by fairing the geometry to the box defined by the gesture"; + + ot->invoke = WM_gesture_box_invoke; + ot->modal = WM_gesture_box_modal; + ot->exec = project_gesture_box_exec; + + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; + + /* Properties. */ + WM_operator_properties_border(ot); + sculpt_gesture_operator_properties(ot); + + sculpt_project_gesture_operator_properties(ot); } diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index de01bc3a474..938b7acd74b 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -227,6 +227,7 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode) case PAINT_MODE_SCULPT: if (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, + SCULPT_TOOL_ARRAY, SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_POSE, SCULPT_TOOL_BOUNDARY, @@ -269,6 +270,7 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m case PAINT_MODE_SCULPT: if (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, + SCULPT_TOOL_ARRAY, SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB, SCULPT_TOOL_SNAKE_HOOK, @@ -679,7 +681,12 @@ static float paint_space_stroke_spacing(bContext *C, ePaintMode mode = BKE_paintmode_get_active_from_context(C); Brush *brush = BKE_paint_brush(paint); float size_clamp = 0.0f; - float size = BKE_brush_size_get(scene, stroke->brush) * size_pressure; + float final_size_pressure = size_pressure; + if (brush->pressure_size_curve) { + BKE_curvemapping_init(brush->pressure_size_curve); + final_size_pressure = BKE_curvemapping_evaluateF(brush->pressure_size_curve, 0, size_pressure); + } + float size = BKE_brush_size_get(scene, stroke->brush) * final_size_pressure; if (paint_stroke_use_scene_spacing(brush, mode)) { if (!BKE_brush_use_locked_size(scene, brush)) { float last_object_space_position[3]; @@ -688,7 +695,7 @@ static float paint_space_stroke_spacing(bContext *C, size_clamp = paint_calc_object_space_radius(&stroke->vc, last_object_space_position, size); } else { - size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * size_pressure; + size_clamp = BKE_brush_unprojected_radius_get(scene, brush) * final_size_pressure; } } else { @@ -788,6 +795,7 @@ static float paint_space_stroke_spacing_variable(bContext *C, /* average spacing */ float last_spacing = paint_space_stroke_spacing( C, scene, stroke, last_size_pressure, pressure); + float new_spacing = paint_space_stroke_spacing(C, scene, stroke, new_size_pressure, pressure); return 0.5f * (last_spacing + new_spacing); @@ -1037,6 +1045,7 @@ static bool sculpt_is_grab_tool(Brush *br) SCULPT_TOOL_POSE, SCULPT_TOOL_BOUNDARY, SCULPT_TOOL_THUMB, + SCULPT_TOOL_ARRAY, SCULPT_TOOL_ROTATE, SCULPT_TOOL_SNAKE_HOOK); } @@ -1050,6 +1059,9 @@ bool paint_supports_dynamic_size(Brush *br, ePaintMode mode) switch (mode) { case PAINT_MODE_SCULPT: + if (br->sculpt_tool == SCULPT_TOOL_ARRAY) { + return true; + } if (sculpt_is_grab_tool(br)) { return false; } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 7bde864e73f..d9afafd16d9 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -29,6 +29,7 @@ #include "BLI_ghash.h" #include "BLI_gsqueue.h" #include "BLI_hash.h" +#include "BLI_linklist_stack.h" #include "BLI_math.h" #include "BLI_math_color_blend.h" #include "BLI_task.h" @@ -56,6 +57,7 @@ #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_mesh.h" +#include "BKE_mesh_fair.h" #include "BKE_mesh_mapping.h" #include "BKE_mesh_mirror.h" #include "BKE_modifier.h" @@ -73,9 +75,17 @@ #include "BKE_subsurf.h" #include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" #include "IMB_colormanagement.h" +#include "GPU_batch.h" +#include "GPU_batch_presets.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" @@ -84,6 +94,8 @@ #include "ED_object.h" #include "ED_screen.h" #include "ED_sculpt.h" +#include "ED_space_api.h" +#include "ED_transform_snap_object_context.h" #include "ED_view3d.h" #include "paint_intern.h" #include "sculpt_intern.h" @@ -226,6 +238,10 @@ 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: + if (ss->limit_surface) { + copy_v3_v3(r_co, ss->limit_surface[index]); + break; + } copy_v3_v3(r_co, SCULPT_vertex_co_get(ss, index)); break; case PBVH_GRIDS: { @@ -511,6 +527,31 @@ void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set) } } +void SCULPT_vertex_face_set_increase(SculptSession *ss, int index, const int increase) +{ + 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]] += increase; + } + } + } 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] += increase; + } + + } break; + } +} + int SCULPT_vertex_face_set_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { @@ -1079,6 +1120,7 @@ void SCULPT_tag_update_overlays(bContext *C) View3D *v3d = CTX_wm_view3d(C); if (!BKE_sculptsession_use_pbvh_draw(ob, v3d)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } } @@ -1214,6 +1256,7 @@ static bool sculpt_tool_needs_original(const char sculpt_tool) SCULPT_TOOL_ELASTIC_DEFORM, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_FAIRING, SCULPT_TOOL_POSE); } @@ -1222,12 +1265,17 @@ static bool sculpt_tool_is_proxy_used(const char sculpt_tool) return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, + SCULPT_TOOL_FAIRING, + SCULPT_TOOL_SCENE_PROJECT, SCULPT_TOOL_POSE, + SCULPT_TOOL_ARRAY, + SCULPT_TOOL_TWIST, SCULPT_TOOL_DISPLACEMENT_SMEAR, SCULPT_TOOL_BOUNDARY, SCULPT_TOOL_CLOTH, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR, + SCULPT_TOOL_SYMMETRIZE, SCULPT_TOOL_DRAW_FACE_SETS); } @@ -1250,6 +1298,7 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, const Brush *brush SCULPT_TOOL_CREASE, SCULPT_TOOL_DRAW, SCULPT_TOOL_DRAW_SHARP, + SCULPT_TOOL_SCENE_PROJECT, SCULPT_TOOL_CLOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_NUDGE, @@ -2324,7 +2373,6 @@ static float brush_strength(const Sculpt *sd, 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; @@ -2336,20 +2384,31 @@ static float brush_strength(const Sculpt *sd, flip = 1.0f; } + float pressure = BKE_brush_use_alpha_pressure(brush) ? cache->pressure : 1.0f; + if (brush->pressure_strength_curve) { + } + BKE_curvemapping_init(brush->pressure_strength_curve); + pressure = BKE_brush_use_alpha_pressure(brush) && brush->pressure_strength_curve ? + BKE_curvemapping_evaluateF(brush->pressure_strength_curve, 0, cache->pressure) : + pressure; /* 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); + // final_pressure = pow4f(pressure); overlap = (1.0f + overlap) / 2.0f; - return 0.25f * alpha * flip * final_pressure * overlap * feather; + return 0.25f * alpha * flip * pressure * overlap * feather; case SCULPT_TOOL_DRAW: case SCULPT_TOOL_DRAW_SHARP: case SCULPT_TOOL_LAYER: + case SCULPT_TOOL_SYMMETRIZE: return alpha * flip * pressure * overlap * feather; case SCULPT_TOOL_DISPLACEMENT_ERASER: return alpha * pressure * overlap * feather; + case SCULPT_TOOL_FAIRING: + case SCULPT_TOOL_SCENE_PROJECT: + 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. */ @@ -2380,11 +2439,13 @@ static float brush_strength(const Sculpt *sd, 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; + // final_pressure = powf(pressure, 1.5f); + return alpha * flip * pressure * overlap * feather * 0.3f; + case SCULPT_TOOL_TWIST: + return alpha * flip * pressure * overlap * feather * 0.3f; case SCULPT_TOOL_CLAY_THUMB: - final_pressure = pressure * pressure; - return alpha * flip * final_pressure * overlap * feather * 1.3f; + // final_pressure = pressure * pressure; + return alpha * flip * pressure * overlap * feather * 1.3f; case SCULPT_TOOL_MASK: overlap = (1.0f + overlap) / 2.0f; @@ -2425,8 +2486,13 @@ static float brush_strength(const Sculpt *sd, return 0.5f * alpha * flip * pressure * overlap * feather; } - case SCULPT_TOOL_SMOOTH: - return flip * alpha * pressure * feather; + case SCULPT_TOOL_SMOOTH: { + const float smooth_strength_base = flip * pressure * feather; + if (cache->alt_smooth) { + return smooth_strength_base * sd->smooth_strength_factor; + } + return smooth_strength_base * alpha; + } case SCULPT_TOOL_PINCH: if (flip > 0.0f) { @@ -2449,6 +2515,10 @@ static float brush_strength(const Sculpt *sd, case SCULPT_TOOL_GRAB: return root_alpha * feather; + case SCULPT_TOOL_ARRAY: + //return root_alpha * feather; + return alpha * pressure ; + case SCULPT_TOOL_ROTATE: return alpha * pressure * feather; @@ -2871,9 +2941,18 @@ typedef struct { const float *ray_start; const float *ray_normal; bool hit; + int hit_count; + bool back_hit; float depth; bool original; + /* Depth of the second raycast hit. */ + float back_depth; + + /* When the back depth is not needed, this can be set to false to avoid traversing unnecesary + * nodes. */ + bool use_back_depth; + int active_vertex_index; float *face_normal; @@ -3117,6 +3196,162 @@ static void do_displacement_eraser_brush(Sculpt *sd, Object *ob, PBVHNode **node /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Sculpt Multires Displacement Eraser Brush + * \{ */ + +static void do_fairing_brush_tag_store_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + PBVHVertexIter vd; + + const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); + const Brush *brush = data->brush; + + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + if (SCULPT_vertex_is_boundary(ss, vd.index)) { + continue; + } + + const float fade = bstrength * SCULPT_brush_strength_factor(ss, + brush, + ss->cache->prefairing_co[vd.index], + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + if (fade == 0.0f) { + continue; + } + + ss->cache->fairing_fade[vd.index] = max_ff(fade, ss->cache->fairing_fade[vd.index]); + ss->cache->fairing_mask[vd.index] = true; + } + BKE_pbvh_vertex_iter_end; +} + +static void do_fairing_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + const int totvert = SCULPT_vertex_count_get(ss); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return; + } + + if (!ss->cache->fairing_mask) { + ss->cache->fairing_mask = MEM_malloc_arrayN(totvert, sizeof(bool), "fairing_mask"); + ss->cache->fairing_fade = MEM_malloc_arrayN(totvert, sizeof(float), "fairing_fade"); + ss->cache->prefairing_co = MEM_malloc_arrayN(totvert, sizeof(float) * 3, "prefairing_co"); + } + + if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { + for (int i = 0; i < totvert; i++) { + ss->cache->fairing_mask[i] = false; + ss->cache->fairing_fade[i] = 0.0f; + copy_v3_v3(ss->cache->prefairing_co[i], SCULPT_vertex_co_get(ss, i)); + } + } + + SCULPT_boundary_info_ensure(ob); + + /* 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_fairing_brush_tag_store_task_cb_ex, &settings); +} + +static void do_fairing_brush_displace_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) { + if (!ss->cache->fairing_mask[vd.index]) { + continue; + } + float disp[3]; + sub_v3_v3v3(disp, vd.co, ss->cache->prefairing_co[vd.index]); + mul_v3_fl(disp, ss->cache->fairing_fade[vd.index]); + copy_v3_v3(vd.co, ss->cache->prefairing_co[vd.index]); + add_v3_v3(vd.co, disp); + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void sculpt_fairing_brush_exec_fairing_for_cache(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(BKE_pbvh_type(ss->pbvh) != PBVH_GRIDS); + BLI_assert(ss->cache); + Brush *brush = BKE_paint_brush(&sd->paint); + Mesh *mesh = ob->data; + + if (!ss->cache->fairing_mask) { + return; + } + + switch (BKE_pbvh_type(ss->pbvh)) { + case PBVH_FACES: { + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + BKE_mesh_prefair_and_fair_vertices( + mesh, mvert, ss->cache->fairing_mask, MESH_FAIRING_DEPTH_TANGENCY); + } break; + case PBVH_BMESH: { + BKE_bmesh_prefair_and_fair_vertices( + ss->bm, ss->cache->fairing_mask, MESH_FAIRING_DEPTH_TANGENCY); + } break; + case PBVH_GRIDS: + BLI_assert(false); + } + + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + + 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_fairing_brush_displace_task_cb_ex, &settings); + MEM_freeN(nodes); +} + +/** \} */ + /** \name Sculpt Multires Displacement Smear Brush * \{ */ @@ -3414,6 +3649,237 @@ static void do_draw_sharp_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int to } /* -------------------------------------------------------------------- */ +/** \name Sculpt Scene Project Brush + * \{ */ + +static void sculpt_stroke_cache_snap_context_init(bContext *C, Object *ob) +{ + SculptSession *ss = ob->sculpt; + StrokeCache *cache = ss->cache; + + if (ss->cache && ss->cache->snap_context) { + return; + } + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + View3D *v3d = CTX_wm_view3d(C); + + cache->snap_context = ED_transform_snap_object_context_create_view3d(scene, 0, region, v3d); + cache->depsgraph = depsgraph; +} + +static void sculpt_scene_project_view_ray_init(Object *ob, + const int vertex_index, + float r_ray_normal[3], + float r_ray_origin[3]) +{ + SculptSession *ss = ob->sculpt; + float world_space_vertex_co[3]; + mul_v3_m4v3(world_space_vertex_co, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index)); + if (ss->cache->vc->rv3d->is_persp) { + sub_v3_v3v3(r_ray_normal, world_space_vertex_co, ss->cache->view_origin); + normalize_v3(r_ray_normal); + copy_v3_v3(r_ray_origin, ss->cache->view_origin); + } + else { + mul_v3_mat3_m4v3(r_ray_normal, ob->obmat, ss->cache->view_normal); + sub_v3_v3v3(r_ray_origin, world_space_vertex_co, r_ray_normal); + } +} + +static void sculpt_scene_project_vertex_normal_ray_init(Object *ob, + const int vertex_index, + const float original_normal[3], + float r_ray_normal[3], + float r_ray_origin[3]) +{ + SculptSession *ss = ob->sculpt; + mul_v3_m4v3(r_ray_normal, ob->obmat, original_normal); + normalize_v3(r_ray_normal); + + mul_v3_m4v3(r_ray_origin, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index)); +} + +static void sculpt_scene_project_brush_normal_ray_init(Object *ob, + const int vertex_index, + float r_ray_normal[3], + float r_ray_origin[3]) +{ + SculptSession *ss = ob->sculpt; + mul_v3_m4v3(r_ray_origin, ob->obmat, SCULPT_vertex_co_get(ss, vertex_index)); + mul_v3_m4v3(r_ray_normal, ob->obmat, ss->cache->sculpt_normal); + normalize_v3(r_ray_normal); +} + +static bool sculpt_scene_project_raycast(SculptSession *ss, + const float ray_normal[3], + const float ray_origin[3], + const bool use_both_directions, + float r_loc[3]) +{ + float hit_co[2][3]; + float hit_len_squared[2]; + bool any_hit = false; + bool hit = false; + hit = ED_transform_snap_object_project_ray(ss->cache->snap_context, + ss->cache->depsgraph, + &(const struct SnapObjectParams){ + .snap_select = SNAP_NOT_ACTIVE, + }, + ray_origin, + ray_normal, + NULL, + hit_co[0], + NULL); + if (hit) { + hit_len_squared[0] = len_squared_v3v3(hit_co[0], ray_origin); + any_hit |= hit; + } + else { + hit_len_squared[0] = FLT_MAX; + } + + if (!use_both_directions) { + copy_v3_v3(r_loc, hit_co[0]); + return any_hit; + } + + float ray_normal_flip[3]; + mul_v3_v3fl(ray_normal_flip, ray_normal, -1.0f); + + hit = ED_transform_snap_object_project_ray(ss->cache->snap_context, + ss->cache->depsgraph, + &(const struct SnapObjectParams){ + .snap_select = SNAP_NOT_ACTIVE, + }, + ray_origin, + ray_normal_flip, + NULL, + hit_co[1], + NULL); + if (hit) { + hit_len_squared[1] = len_squared_v3v3(hit_co[1], ray_origin); + any_hit |= hit; + } + else { + hit_len_squared[1] = FLT_MAX; + } + + if (hit_len_squared[0] <= hit_len_squared[1]) { + copy_v3_v3(r_loc, hit_co[0]); + } + else { + copy_v3_v3(r_loc, hit_co[1]); + } + return any_hit; +} + +static void do_scene_project_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + + const float bstrength = clamp_f(ss->cache->bstrength, 0.0f, 1.0f); + const Brush *brush = data->brush; + + const int thread_id = BLI_task_parallel_thread_id(tls); + + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + 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; + } + + SCULPT_orig_vert_data_update(&orig_data, &vd); + + 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); + + if (fade == 0.0f) { + continue; + } + + float ray_normal[3]; + float ray_origin[3]; + bool use_both_directions = false; + switch (brush->scene_project_direction_type) { + case BRUSH_SCENE_PROJECT_DIRECTION_VIEW: + sculpt_scene_project_view_ray_init(data->ob, vd.index, ray_normal, ray_origin); + break; + case BRUSH_SCENE_PROJECT_DIRECTION_VERTEX_NORMAL: { + float normal[3]; + normal_short_to_float_v3(normal, orig_data.no); + sculpt_scene_project_vertex_normal_ray_init( + data->ob, vd.index, normal, ray_normal, ray_origin); + use_both_directions = true; + } break; + case BRUSH_SCENE_PROJECT_DIRECTION_BRUSH_NORMAL: + sculpt_scene_project_brush_normal_ray_init(data->ob, vd.index, ray_normal, ray_origin); + use_both_directions = true; + break; + } + + float world_space_hit_co[3]; + float hit_co[3]; + const bool hit = sculpt_scene_project_raycast( + ss, ray_normal, ray_origin, use_both_directions, world_space_hit_co); + if (!hit) { + continue; + } + + mul_v3_m4v3(hit_co, data->ob->imat, world_space_hit_co); + + float disp[3]; + sub_v3_v3v3(disp, hit_co, vd.co); + mul_v3_fl(disp, fade); + add_v3_v3(vd.co, disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_scene_project_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + + SCULPT_vertex_random_access_ensure(ob->sculpt); + + /* 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_scene_project_brush_task_cb_ex, &settings); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Sculpt Topology Brush * \{ */ @@ -3991,6 +4457,7 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata, const int thread_id = BLI_task_parallel_thread_id(tls); const bool grab_silhouette = brush->flag2 & BRUSH_GRAB_SILHOUETTE; + const bool use_geodesic_dists = brush->flag2 & BRUSH_USE_SURFACE_FALLOFF; BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { SCULPT_orig_vert_data_update(&orig_data, &vd); @@ -3998,10 +4465,19 @@ static void do_grab_brush_task_cb_ex(void *__restrict userdata, if (!sculpt_brush_test_sq_fn(&test, orig_data.co)) { continue; } + + float dist; + if (use_geodesic_dists) { + dist = ss->cache->geodesic_dists[ss->cache->mirror_symmetry_pass][vd.index]; + } + else { + dist = sqrtf(test.dist); + } + float fade = bstrength * SCULPT_brush_strength_factor(ss, brush, orig_data.co, - sqrtf(test.dist), + dist, orig_data.no, NULL, vd.mask ? *vd.mask : 0.0f, @@ -4040,6 +4516,17 @@ static void do_grab_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); } + if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) { + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + const int symm_pass = ss->cache->mirror_symmetry_pass; + float location[3]; + flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), symm_pass); + int v = SCULPT_nearest_vertex_get(sd, ob, location, ss->cache->radius, false); + ss->cache->geodesic_dists[symm_pass] = SCULPT_geodesic_from_vertex( + ob, v, ss->cache->initial_radius); + } + } + SculptThreadedTaskData data = { .sd = sd, .ob = ob, @@ -4096,28 +4583,46 @@ static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, 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]; + + float orig_co[3]; + if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) { + const float geodesic_dist = + ss->cache->geodesic_dists[ss->cache->mirror_symmetry_pass][vd.index]; + + if (geodesic_dist == FLT_MAX) { + continue; + } + + float disp[3]; + sub_v3_v3v3(disp, orig_data.co, ss->cache->initial_location); + normalize_v3(disp); + mul_v3_fl(disp, geodesic_dist); + add_v3_v3v3(orig_co, ss->cache->initial_location, disp); + } + else { + copy_v3_v3(orig_co, orig_data.co); + } + switch (brush->elastic_deform_type) { case BRUSH_ELASTIC_DEFORM_GRAB: - BKE_kelvinlet_grab(final_disp, ¶ms, orig_data.co, location, grab_delta); + BKE_kelvinlet_grab(final_disp, ¶ms, orig_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); + BKE_kelvinlet_grab_biscale(final_disp, ¶ms, orig_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); + BKE_kelvinlet_grab_triscale(final_disp, ¶ms, orig_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); + BKE_kelvinlet_scale(final_disp, ¶ms, orig_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); + BKE_kelvinlet_twist(final_disp, ¶ms, orig_co, location, ss->cache->sculpt_normal_symm); break; } @@ -4148,6 +4653,16 @@ static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in sculpt_project_v3_normal_align(ss, ss->cache->normal_weight, grab_delta); } + if (brush->flag2 & BRUSH_USE_SURFACE_FALLOFF) { + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + const int symm_pass = ss->cache->mirror_symmetry_pass; + float location[3]; + flip_v3_v3(location, SCULPT_active_vertex_co_get(ss), symm_pass); + int v = SCULPT_nearest_vertex_get(sd, ob, location, ss->cache->initial_radius, false); + ss->cache->geodesic_dists[symm_pass] = SCULPT_geodesic_from_vertex(ob, v, FLT_MAX); + } + } + SculptThreadedTaskData data = { .sd = sd, .ob = ob, @@ -5187,6 +5702,12 @@ static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata, continue; } + float vertex_no[3]; + SCULPT_vertex_normal_get(ss, vd.index, vertex_no); + if (dot_v3v3(area_no_sp, vertex_no) <= -0.1f) { + continue; + } + float intr[3]; float val[3]; closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co); @@ -5225,6 +5746,8 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t const float offset = SCULPT_brush_plane_offset_get(sd, ss); const float displace = radius * (0.18f + offset); + SCULPT_vertex_random_access_ensure(ss); + /* The sculpt-plane normal (whatever its set to). */ float area_no_sp[3]; @@ -5308,6 +5831,294 @@ static void do_clay_strips_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int t BLI_task_parallel_range(0, totnode, &data, do_clay_strips_brush_task_cb_ex, &settings); } + +/****** Twist Brush **********/ + +static void do_twist_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; + const bool flip = (ss->cache->bstrength < 0.0f); + const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; + + 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 stroke_direction[3]; + float stroke_line[2][3]; + normalize_v3_v3(stroke_direction, ss->cache->grab_delta_symmetry); + copy_v3_v3(stroke_line[0], ss->cache->location); + add_v3_v3v3(stroke_line[1], stroke_line[0], stroke_direction); + + + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + + 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, vd.co)) { + continue; + } + const float fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + sqrtf(test.dist), + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + if (fade == 0.0f) { + continue; + } + + float local_vert_co[3]; + float rotation_axis[3] = {0.0, 1.0, 0.0}; + float origin[3] = {0.0, 0.0, 0.0f}; + float vertex_in_line[3]; + float scaled_mat[4][4]; + float scaled_mat_inv[4][4]; + + + + copy_m4_m4(scaled_mat, mat); + invert_m4(scaled_mat); + mul_v3_fl(scaled_mat[2], 0.7f * fade * (1.0f - bstrength)); + invert_m4(scaled_mat); + + invert_m4_m4(scaled_mat_inv, scaled_mat); + + + mul_v3_m4v3(local_vert_co, scaled_mat, vd.co); + closest_to_line_v3(vertex_in_line, local_vert_co, rotation_axis, origin); + float p_to_rotate[3]; + sub_v3_v3v3(p_to_rotate, local_vert_co, vertex_in_line); + float p_rotated[3]; + rotate_v3_v3v3fl(p_rotated, p_to_rotate, rotation_axis, 2.0f * bstrength * fade); + add_v3_v3(p_rotated, vertex_in_line); + mul_v3_m4v3(p_rotated, scaled_mat_inv, p_rotated); + + + float disp[3]; + sub_v3_v3v3(disp, p_rotated, vd.co); + mul_v3_fl(disp, bstrength * fade); + add_v3_v3(vd.co, disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_twist_brush_post_smooth_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; + const bool flip = (ss->cache->bstrength < 0.0f); + const float bstrength = flip ? -ss->cache->bstrength : ss->cache->bstrength; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + + float local_vert_co[3]; + float scaled_mat[4][4]; + copy_m4_m4(scaled_mat, mat); + invert_m4(scaled_mat); + invert_m4(scaled_mat); + mul_v3_m4v3(local_vert_co, scaled_mat, vd.co); + + const float brush_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 smooth_fade = SCULPT_brush_strength_factor(ss, + brush, + vd.co, + local_vert_co[0], + vd.no, + vd.fno, + vd.mask ? *vd.mask : 0.0f, + vd.index, + thread_id); + + if (brush_fade == 0.0f) { + //continue; + } + + if (smooth_fade == 0.0f) { + //continue; + } + + + smooth_fade = 1.0f - min_ff(fabsf(local_vert_co[0]), 1.0f); + smooth_fade = pow3f(smooth_fade); + + float rotation_axis[3] = {0.0, 1.0, 0.0}; + float origin[3] = {0.0, 0.0, 0.0f}; + float vertex_in_line[3]; + float scaled_mat_inv[4][4]; + + float avg[3]; + float val[3]; + float disp[3]; + +/* + SCULPT_neighbor_coords_average(ss, avg, vd.index); + + sub_v3_v3v3(disp, avg, vd.co); + mul_v3_fl(disp, 1.0f - smooth_fade); + add_v3_v3(vd.co, disp); + */ + + float final_co[3]; + SCULPT_relax_vertex(ss, &vd, clamp_f(smooth_fade, 0.0f, 1.0f), false, final_co); + + sub_v3_v3v3(disp, final_co, vd.co); + add_v3_v3(vd.co, disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + + +static void do_twist_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); + + + SCULPT_vertex_random_access_ensure(ss); + SCULPT_boundary_info_ensure(ob); + + /* 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); + */ + + /* 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); + mat[3][3] = 1.0f; + normalize_m4(mat); + + /* Scale brush local space matrix. */ + scale_m4_fl(scale, ss->cache->radius * 0.5f); + mul_m4_m4m4(tmat, mat, scale); + + /* Scale rotation space. */ + //mul_v3_fl(tmat[2], 0.5f); + + float twist_mat[4][4]; + invert_m4_m4(twist_mat, tmat); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .area_no_sp = area_no_sp, + .area_co = area_co, + .mat = twist_mat, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, do_twist_brush_task_cb_ex, &settings); + + scale_m4_fl(scale, ss->cache->radius); + mul_m4_m4m4(tmat, mat, scale); + float smooth_mat[4][4]; + invert_m4_m4(smooth_mat, tmat); + data.mat = smooth_mat; + + for (int i = 0; i < 2; i++) { + BLI_task_parallel_range(0, totnode, &data, do_twist_brush_post_smooth_task_cb_ex, &settings); + } +} + + static void do_fill_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) @@ -5817,6 +6628,7 @@ static void sculpt_topology_update(Sculpt *sd, /* Free index based vertex info as it will become invalid after modifying the topology during the * stroke. */ MEM_SAFE_FREE(ss->vertex_info.boundary); + MEM_SAFE_FREE(ss->vertex_info.symmetrize_map); MEM_SAFE_FREE(ss->vertex_info.connected_component); PBVHTopologyUpdateMode mode = 0; @@ -5878,6 +6690,9 @@ static void do_brush_action_task_cb(void *__restrict userdata, BKE_pbvh_node_mark_update(data->nodes[n]); } } + else if (data->brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + /* Do nothing, array brush does a single geometry undo push. */ + } 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]); @@ -5908,6 +6723,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe return; } + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY && 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)) { @@ -5996,7 +6815,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe 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); + ss, 1.0f, 1.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); @@ -6018,6 +6837,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) { SCULPT_do_surface_smooth_brush(sd, ob, nodes, totnode); } + else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_DIRECTIONAL) { + SCULPT_do_directional_smooth_brush(sd, ob, nodes, totnode); + } + else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_UNIFORM_WEIGHTS) { + SCULPT_do_uniform_weights_smooth_brush(sd, ob, nodes, totnode); + } break; case SCULPT_TOOL_CREASE: do_crease_brush(sd, ob, nodes, totnode); @@ -6058,6 +6883,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe case SCULPT_TOOL_CLAY_STRIPS: do_clay_strips_brush(sd, ob, nodes, totnode); break; + case SCULPT_TOOL_TWIST: + do_twist_brush(sd, ob, nodes, totnode); + break; case SCULPT_TOOL_MULTIPLANE_SCRAPE: SCULPT_do_multiplane_scrape_brush(sd, ob, nodes, totnode); break; @@ -6116,6 +6944,18 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe case SCULPT_TOOL_SMEAR: SCULPT_do_smear_brush(sd, ob, nodes, totnode); break; + case SCULPT_TOOL_FAIRING: + do_fairing_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SCENE_PROJECT: + do_scene_project_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_SYMMETRIZE: + SCULPT_do_symmetrize_brush(sd, ob, nodes, totnode); + break; + case SCULPT_TOOL_ARRAY: + SCULPT_do_array_brush(sd, ob, nodes, totnode); + break; } if (!ELEM(brush->sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_MASK) && @@ -6188,15 +7028,7 @@ static void sculpt_combine_proxies_task_cb(void *__restrict 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); + const bool use_orco = data->use_proxies_orco; PBVHVertexIter vd; PBVHProxyNode *proxies; @@ -6228,7 +7060,14 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata, add_v3_v3(val, proxies[p].co[vd.i]); } - SCULPT_clip(sd, ss, vd.co, val); + if (ss->filter_cache && ss->filter_cache->cloth_sim) { + /* When there is a simulation running in the filter cache that was created by a tool, combine + * the proxies into the simulation instead of directly into the mesh. */ + SCULPT_clip(sd, ss, ss->filter_cache->cloth_sim->pos[vd.index], val); + } + else { + SCULPT_clip(sd, ss, vd.co, val); + } if (ss->deform_modifiers_active) { sculpt_flush_pbvhvert_deform(ob, &vd); @@ -6252,16 +7091,47 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob) } BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + + const bool use_orco = ELEM(brush->sculpt_tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_ROTATE, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_ELASTIC_DEFORM, + + SCULPT_TOOL_POSE); SculptThreadedTaskData data = { .sd = sd, .ob = ob, .brush = brush, .nodes = nodes, + .use_proxies_orco = use_orco, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); + MEM_SAFE_FREE(nodes); +} + +void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob) +{ + SculptSession *ss = ob->sculpt; + PBVHNode **nodes; + int totnode; + + BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode); + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + .use_proxies_orco = false, }; TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings); + MEM_SAFE_FREE(nodes); } @@ -6384,6 +7254,7 @@ void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, 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->view_origin, cache->true_view_origin, symm); flip_v3_v3(cache->initial_location, cache->true_initial_location, symm); flip_v3_v3(cache->initial_normal, cache->true_initial_normal, symm); @@ -6674,6 +7545,16 @@ static const char *sculpt_tool_name(Sculpt *sd) return "Paint Brush"; case SCULPT_TOOL_SMEAR: return "Smear Brush"; + case SCULPT_TOOL_FAIRING: + return "Fairing Brush"; + case SCULPT_TOOL_SCENE_PROJECT: + return "Scene Project"; + case SCULPT_TOOL_SYMMETRIZE: + return "Symmetrize Brush"; + case SCULPT_TOOL_TWIST: + return "Clay Strips Brush"; + case SCULPT_TOOL_ARRAY: + return "Array Brush"; } return "Sculpting"; @@ -6690,9 +7571,16 @@ void SCULPT_cache_free(StrokeCache *cache) MEM_SAFE_FREE(cache->layer_displacement_factor); MEM_SAFE_FREE(cache->prev_colors); MEM_SAFE_FREE(cache->detail_directions); + MEM_SAFE_FREE(cache->prefairing_co); + MEM_SAFE_FREE(cache->fairing_fade); + MEM_SAFE_FREE(cache->fairing_mask); MEM_SAFE_FREE(cache->prev_displacement); MEM_SAFE_FREE(cache->limit_surface_co); + if (cache->snap_context) { + ED_transform_snap_object_context_destroy(cache->snap_context); + } + if (cache->pose_ik_chain) { SCULPT_pose_ik_chain_free(cache->pose_ik_chain); } @@ -6701,6 +7589,9 @@ void SCULPT_cache_free(StrokeCache *cache) if (cache->boundaries[i]) { SCULPT_boundary_data_free(cache->boundaries[i]); } + if (cache->geodesic_dists[i]) { + MEM_SAFE_FREE(cache->geodesic_dists[i]); + } } if (cache->cloth_sim) { @@ -6862,6 +7753,8 @@ static void sculpt_update_cache_invariants( mul_m3_v3(mat, viewDir); normalize_v3_v3(cache->true_view_normal, viewDir); + copy_v3_v3(cache->true_view_origin, cache->vc->rv3d->viewinv[3]); + cache->supports_gravity = (!ELEM(brush->sculpt_tool, SCULPT_TOOL_MASK, SCULPT_TOOL_SMOOTH, @@ -6918,6 +7811,11 @@ static void sculpt_update_cache_invariants( static float sculpt_brush_dynamic_size_get(Brush *brush, StrokeCache *cache, float initial_size) { + if (brush->pressure_size_curve) { + return initial_size * + BKE_curvemapping_evaluateF(brush->pressure_size_curve, 0, cache->pressure); + } + switch (brush->sculpt_tool) { case SCULPT_TOOL_CLAY: return max_ff(initial_size * 0.20f, initial_size * pow3f(cache->pressure)); @@ -6940,6 +7838,7 @@ static bool sculpt_needs_delta_from_anchored_origin(Brush *brush) SCULPT_TOOL_GRAB, SCULPT_TOOL_POSE, SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_ARRAY, SCULPT_TOOL_THUMB, SCULPT_TOOL_ELASTIC_DEFORM)) { return true; @@ -6960,6 +7859,7 @@ static bool sculpt_needs_delta_for_tip_orientation(Brush *brush) } return ELEM(brush->sculpt_tool, SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_TWIST, SCULPT_TOOL_PINCH, SCULPT_TOOL_MULTIPLANE_SCRAPE, SCULPT_TOOL_CLAY_THUMB, @@ -6984,12 +7884,14 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru SCULPT_TOOL_CLOTH, SCULPT_TOOL_NUDGE, SCULPT_TOOL_CLAY_STRIPS, + SCULPT_TOOL_TWIST, SCULPT_TOOL_PINCH, SCULPT_TOOL_MULTIPLANE_SCRAPE, SCULPT_TOOL_CLAY_THUMB, SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_POSE, SCULPT_TOOL_BOUNDARY, + SCULPT_TOOL_ARRAY, SCULPT_TOOL_THUMB) && !sculpt_brush_use_topology_rake(ss, brush)) { return; @@ -7287,6 +8189,8 @@ static bool sculpt_needs_connectivity_info(const Sculpt *sd, ((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_FAIRING) || + (brush->sculpt_tool == SCULPT_TOOL_TWIST) || (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) || @@ -7309,10 +8213,11 @@ void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *b static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) { - if (BKE_pbvh_node_get_tmin(node) >= *tmin) { + SculptRaycastData *srd = data_v; + if (!srd->use_back_depth && BKE_pbvh_node_get_tmin(node) >= *tmin) { return; } - SculptRaycastData *srd = data_v; + float(*origco)[3] = NULL; bool use_origco = false; @@ -7335,13 +8240,19 @@ static void sculpt_raycast_cb(PBVHNode *node, void *data_v, float *tmin) srd->ray_start, srd->ray_normal, &srd->isect_precalc, + &srd->hit_count, &srd->depth, + &srd->back_depth, &srd->active_vertex_index, &srd->active_face_grid_index, srd->face_normal)) { srd->hit = true; *tmin = srd->depth; } + + if (srd->hit_count >= 2) { + srd->back_hit = true; + } } static void sculpt_find_nearest_to_ray_cb(PBVHNode *node, void *data_v, float *tmin) @@ -7421,7 +8332,8 @@ float SCULPT_raycast_init(ViewContext *vc, bool SCULPT_cursor_geometry_info_update(bContext *C, SculptCursorGeometryInfo *out, const float mouse[2], - bool use_sampled_normal) + bool use_sampled_normal, + bool use_back_depth) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); Scene *scene = CTX_data_scene(C); @@ -7451,14 +8363,19 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, /* 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); + float back_depth = depth; SculptRaycastData srd = { .original = original, .ss = ob->sculpt, .hit = false, + .back_hit = false, .ray_start = ray_start, .ray_normal = ray_normal, .depth = depth, + .back_depth = back_depth, + .hit_count = 0, + .use_back_depth = use_back_depth, .face_normal = face_normal, }; isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal); @@ -7496,6 +8413,17 @@ bool SCULPT_cursor_geometry_info_update(bContext *C, mul_v3_fl(out->location, srd.depth); add_v3_v3(out->location, ray_start); + if (use_back_depth) { + copy_v3_v3(out->back_location, ray_normal); + if (srd.back_hit) { + mul_v3_fl(out->back_location, srd.back_depth); + } + else { + mul_v3_fl(out->back_location, srd.depth); + } + add_v3_v3(out->back_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); @@ -7752,6 +8680,12 @@ void SCULPT_flush_update_step(bContext *C, SculptUpdateType update_flags) /* 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); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateBB); + SCULPT_update_object_bounding_box(ob); + } ED_region_tag_redraw(region); } else { @@ -7885,9 +8819,14 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f sculpt_update_cache_invariants(C, sd, ss, op, mouse); SculptCursorGeometryInfo sgi; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); SCULPT_undo_push_begin(ob, sculpt_tool_name(sd)); + + Brush *brush = BKE_paint_brush(&sd->paint); + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY); + } return true; } @@ -7904,6 +8843,10 @@ static void sculpt_stroke_update_step(bContext *C, SculptSession *ss = ob->sculpt; const Brush *brush = BKE_paint_brush(&sd->paint); + if (brush->sculpt_tool == SCULPT_TOOL_SCENE_PROJECT) { + sculpt_stroke_cache_snap_context_init(C, ob); + } + SCULPT_stroke_modifiers_check(C, ob, brush); sculpt_update_cache_variants(C, sd, ob, itemptr); sculpt_restore_mesh(sd, ob); @@ -7926,8 +8869,30 @@ static void sculpt_stroke_update_step(bContext *C, } do_symmetrical_brush_actions(sd, ob, do_brush_action, ups); + + if (ss->needs_pbvh_rebuild) { + /* The mesh was modified, rebuild the PBVH. */ + BKE_particlesystem_reset_all(ob); + BKE_ptcache_object_reset(CTX_data_scene(C), ob, PTCACHE_RESET_OUTDATED); + + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + BKE_scene_graph_update_tagged(CTX_data_ensure_evaluated_depsgraph(C), CTX_data_main(C)); + SCULPT_pbvh_clear(ob); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + SCULPT_tag_update_overlays(C); + } + ss->needs_pbvh_rebuild = false; + } + sculpt_combine_proxies(sd, ob); + if (brush->sculpt_tool == SCULPT_TOOL_FAIRING) { + sculpt_fairing_brush_exec_fairing_for_cache(sd, ob); + } + /* Hack to fix noise texture tearing mesh. */ sculpt_fix_noise_tear(sd, ob); @@ -8022,6 +8987,11 @@ static void sculpt_stroke_done(const bContext *C, struct PaintStroke *UNUSED(str SCULPT_cache_free(ss->cache); ss->cache = NULL; + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + SCULPT_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY); + SCULPT_array_datalayers_free(ob); + } + SCULPT_undo_push_end(); if (brush->sculpt_tool == SCULPT_TOOL_MASK) { @@ -8774,31 +9744,171 @@ static void SCULPT_OT_loop_to_vertex_colors(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } + +#define SAMPLE_COLOR_PREVIEW_SIZE 60 +#define SAMPLE_COLOR_OFFSET_X -15 +#define SAMPLE_COLOR_OFFSET_Y -15 +typedef struct SampleColorCustomData { + void *draw_handle; + Object *active_object; + + float mval[2]; + + float initial_color[4]; + float sampled_color[4]; +} SampleColorCustomData; + +static void sculpt_sample_color_draw(const bContext *UNUSED(C), + ARegion *UNUSED(ar), + void *arg) +{ + SampleColorCustomData *sccd = (SampleColorCustomData *)arg; + GPU_line_width(2.0f); + GPU_line_smooth(true); + uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + const float origin_x = sccd->mval[0] + SAMPLE_COLOR_OFFSET_X; + const float origin_y = sccd->mval[1] + SAMPLE_COLOR_OFFSET_Y; + + + immUniformColor3fvAlpha(sccd->sampled_color, 1.0f); + immRectf(pos, origin_x, origin_y, origin_x - SAMPLE_COLOR_PREVIEW_SIZE, origin_y - SAMPLE_COLOR_PREVIEW_SIZE); + + immUniformColor3fvAlpha(sccd->initial_color, 1.0f); + immRectf(pos, origin_x - SAMPLE_COLOR_PREVIEW_SIZE, origin_y, origin_x - 2.0f * SAMPLE_COLOR_PREVIEW_SIZE, origin_y - SAMPLE_COLOR_PREVIEW_SIZE); + + + immUnbindProgram(); + GPU_line_smooth(false); +} + +static bool sculpt_sample_color_update_from_base(bContext *C, const wmEvent *event, SampleColorCustomData *sccd) { + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Base *base_sample = ED_view3d_give_base_under_cursor(C, event->mval); + if (base_sample == NULL) { + return false; + } + + Object *object_sample = base_sample->object; + if (object_sample->type != OB_MESH) { + return false; + } + + Object *ob_eval = DEG_get_evaluated_object(depsgraph, object_sample); + Mesh *me_eval = BKE_object_get_evaluated_mesh(ob_eval); + MPropCol *vcol = CustomData_get_layer(&me_eval->vdata, CD_PROP_COLOR); + + if (!vcol) { + return false; + } + + ARegion *region = CTX_wm_region(C); + Scene *scene = CTX_data_scene(C); + float global_loc[3]; + if (!ED_view3d_autodist_simple(region, event->mval, global_loc, 0, NULL)) { + return false; + } + + float object_loc[3]; + mul_v3_m4v3(object_loc, ob_eval->imat, global_loc); + + + BVHTreeFromMesh bvh; + BKE_bvhtree_from_mesh_get(&bvh, me_eval, BVHTREE_FROM_VERTS, 2); + BVHTreeNearest nearest; + nearest.index = -1; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest(bvh.tree, object_loc, &nearest, bvh.nearest_callback, &bvh); + if (nearest.index == -1) { + return false; + } + free_bvhtree_from_mesh(&bvh); + + copy_v4_v4(sccd->sampled_color, vcol[nearest.index].color); + IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color); + return true; +} + +static int sculpt_sample_color_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Scene *scene = CTX_data_scene(C); + Brush *brush = BKE_paint_brush(&sd->paint); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + SampleColorCustomData *sccd = (SampleColorCustomData *)op->customdata; + + /* Finish operation on release. */ + if (event->val == KM_RELEASE) { + float color_srgb[3]; + copy_v3_v3(color_srgb, sccd->sampled_color); + BKE_brush_color_set(scene, brush, sccd->sampled_color); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + ED_region_draw_cb_exit(region->type, sccd->draw_handle); + ED_region_tag_redraw(region); + MEM_freeN(sccd); + ss->draw_faded_cursor = false; + return OPERATOR_FINISHED; + } + + SculptCursorGeometryInfo sgi; + sccd->mval[0] = event->mval[0]; + sccd->mval[1] = event->mval[1]; + + + const bool over_mesh = SCULPT_cursor_geometry_info_update(C, &sgi, sccd->mval, false, false); + if (over_mesh) { + const int active_vertex = SCULPT_active_vertex_get(ss); + copy_v4_v4(sccd->sampled_color, SCULPT_vertex_color_get(ss, active_vertex)); + IMB_colormanagement_scene_linear_to_srgb_v3(sccd->sampled_color); + } + else { + sculpt_sample_color_update_from_base(C, event, sccd); + } + + ss->draw_faded_cursor = true; + ED_region_tag_redraw(region); + + return OPERATOR_RUNNING_MODAL; +} + static int sculpt_sample_color_invoke(bContext *C, - wmOperator *UNUSED(op), + wmOperator *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); + ARegion *region = CTX_wm_region(C); 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) { + + if (!ss->vcol) { 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); + SampleColorCustomData *sccd = MEM_callocN(sizeof(SampleColorCustomData), "Sample Color Custom Data"); + const int active_vertex = SCULPT_active_vertex_get(ss); + copy_v4_v4(sccd->sampled_color, SCULPT_vertex_color_get(ss, active_vertex)); + copy_v4_v4(sccd->initial_color, BKE_brush_color_get(scene, brush)); - WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush); + sccd->draw_handle = ED_region_draw_cb_activate( + region->type, sculpt_sample_color_draw, sccd, REGION_DRAW_POST_PIXEL); - return OPERATOR_FINISHED; + op->customdata = sccd; + + WM_event_add_modal_handler(C, op); + ED_region_tag_redraw(region); + + return OPERATOR_RUNNING_MODAL; } + + + static void SCULPT_OT_sample_color(wmOperatorType *ot) { /* identifiers */ @@ -8808,6 +9918,7 @@ static void SCULPT_OT_sample_color(wmOperatorType *ot) /* api callbacks */ ot->invoke = sculpt_sample_color_invoke; + ot->modal = sculpt_sample_color_modal; ot->poll = SCULPT_vertex_colors_poll; ot->flag = OPTYPE_REGISTER; @@ -9336,7 +10447,7 @@ static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEven float mouse[2]; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); SCULPT_undo_push_begin(ob, "Mask by color"); @@ -9395,11 +10506,82 @@ static void SCULPT_OT_mask_by_color(wmOperatorType *ot) 1.0f); } +static int sculpt_reset_brushes_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + + LISTBASE_FOREACH (Brush *, br, &bmain->brushes) { + if (br->ob_mode != OB_MODE_SCULPT) { + continue; + } + BKE_brush_sculpt_reset(br); + WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, br); + } + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_reset_brushes(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Reset Sculpt Brushes"; + ot->idname = "SCULPT_OT_reset_brushes"; + ot->description = "Resets all sculpt brushes to their default value"; + + /* API callbacks. */ + ot->exec = sculpt_reset_brushes_exec; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; +} + +static int sculpt_set_limit_surface_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Object *ob = CTX_data_active_object(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + SculptSession *ss = ob->sculpt; + + if (!ss) { + return OPERATOR_FINISHED; + } + + SCULPT_vertex_random_access_ensure(ss); + + MEM_SAFE_FREE(ss->limit_surface); + + const int totvert = SCULPT_vertex_count_get(ss); + ss->limit_surface = MEM_mallocN(sizeof(float) * 3 * totvert, + "limit surface"); + + for (int i = 0; i < totvert; i++) { + SCULPT_neighbor_coords_average(ss, ss->limit_surface[i], i); + } + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_set_limit_surface(wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Set Limit Surface"; + ot->idname = "SCULPT_OT_set_limit_surface"; + ot->description = "Calculates and stores a limit surface from the current mesh"; + + /* API callbacks. */ + ot->exec = sculpt_set_limit_surface_exec; + ot->poll = SCULPT_mode_poll; + + 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_set_limit_surface); WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle); WM_operatortype_append(SCULPT_OT_optimize); WM_operatortype_append(SCULPT_OT_symmetrize); @@ -9414,7 +10596,6 @@ void ED_operatortypes_sculpt(void) 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); @@ -9422,6 +10603,8 @@ void ED_operatortypes_sculpt(void) 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_project_lasso_gesture); + WM_operatortype_append(SCULPT_OT_project_box_gesture); WM_operatortype_append(SCULPT_OT_sample_color); WM_operatortype_append(SCULPT_OT_loop_to_vertex_colors); @@ -9430,6 +10613,10 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_mask_by_color); WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit); WM_operatortype_append(SCULPT_OT_mask_init); + WM_operatortype_append(SCULPT_OT_face_sets_init); + WM_operatortype_append(SCULPT_OT_reset_brushes); + WM_operatortype_append(SCULPT_OT_ipmask_filter); + WM_operatortype_append(SCULPT_OT_face_set_by_topology); WM_operatortype_append(SCULPT_OT_expand); } diff --git a/source/blender/editors/sculpt_paint/sculpt_array.c b/source/blender/editors/sculpt_paint/sculpt_array.c new file mode 100644 index 00000000000..d93f61168ea --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_array.c @@ -0,0 +1,982 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_hash.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_multires.h" +#include "BKE_lib_id.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "ED_sculpt.h" + +#include "bmesh.h" +#include "bmesh_tools.h" + +#include <math.h> +#include <stdlib.h> + + +static const char array_symmetry_pass_cd_name[] = "v_symmetry_pass"; +static const char array_instance_cd_name[] = "v_array_instance"; + +#define ARRAY_INSTANCE_ORIGINAL -1 + +static void sculpt_vertex_array_data_get(SculptArray *array, const int vertex, int *r_copy, int *r_symmetry_pass) { + if (!array->copy_index) { + printf("NO ARRAY COPY\n"); + *r_copy = ARRAY_INSTANCE_ORIGINAL; + *r_symmetry_pass = 0; + return; + } + *r_copy = array->copy_index[vertex]; + *r_symmetry_pass = array->symmetry_pass[vertex]; +} + + +static void sculpt_array_datalayers_add(Mesh *mesh) { + int *v_array_instance = CustomData_add_layer_named(&mesh->vdata, + CD_PROP_INT32, + CD_CALLOC, + NULL, + mesh->totvert, + array_instance_cd_name); + for (int i = 0; i < mesh->totvert; i++) { + v_array_instance[i] = ARRAY_INSTANCE_ORIGINAL; + } + + CustomData_add_layer_named(&mesh->vdata, + CD_PROP_INT32, + CD_CALLOC, + NULL, + mesh->totvert, + array_symmetry_pass_cd_name); +} + +void SCULPT_array_datalayers_free(Object *ob) { + Mesh *mesh = BKE_object_get_original_mesh(ob); + int v_layer_index = CustomData_get_named_layer_index(&mesh->vdata, CD_PROP_INT32, array_instance_cd_name); + if (v_layer_index != -1) { + CustomData_free_layer(&mesh->vdata, CD_PROP_INT32, mesh->totvert, v_layer_index); + } + + v_layer_index = CustomData_get_named_layer_index(&mesh->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name); + if (v_layer_index != -1) { + CustomData_free_layer(&mesh->vdata, CD_PROP_INT32, mesh->totvert, v_layer_index); + } +} + +const float source_geometry_threshold = 0.5f; + +static BMesh *sculpt_array_source_build(Object *ob, Brush *brush, SculptArray *array) { + Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob); + + BMesh *srcbm; + const BMAllocTemplate allocsizea = BMALLOC_TEMPLATE_FROM_ME(sculpt_mesh); + srcbm = BM_mesh_create(&allocsizea, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(srcbm, + sculpt_mesh, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + + BM_mesh_elem_table_ensure(srcbm, BM_VERT); + BM_mesh_elem_index_ensure(srcbm, BM_VERT); + + int vert_count = 0; + zero_v3(array->source_origin); + + SculptSession *ss = ob->sculpt; + for (int i = 0; i < srcbm->totvert; i++) { + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, i); + const float mask = 1.0f - SCULPT_vertex_mask_get(ss, i); + const float influence = mask * automask; + BMVert *vert = BM_vert_at_index(srcbm, i); + if (influence >= source_geometry_threshold) { + vert_count++; + add_v3_v3(array->source_origin, vert->co); + continue; + } + BM_elem_flag_set(vert, BM_ELEM_TAG, true); + } + + + if (vert_count == 0) { + return srcbm; + } + + mul_v3_fl(array->source_origin, 1.0f / vert_count); + + /* TODO(pablodp606): Handle individual Face Sets for Face Set automasking. */ + BM_mesh_delete_hflag_context(srcbm, BM_ELEM_TAG, DEL_VERTS); + + const bool fill_holes = brush->flag2 & BRUSH_ARRAY_FILL_HOLES; + if (fill_holes) { + BM_mesh_elem_hflag_disable_all(srcbm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + BM_mesh_elem_hflag_enable_all(srcbm, BM_EDGE, BM_ELEM_TAG, false); + BM_mesh_edgenet(srcbm, false, true); + BM_mesh_normals_update(srcbm); + BMO_op_callf(srcbm, + (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), + "triangulate faces=%hf quad_method=%i ngon_method=%i", + BM_ELEM_TAG, + 0, + 0); + + BM_mesh_elem_hflag_enable_all(srcbm, BM_FACE, BM_ELEM_TAG, false); + BMO_op_callf(srcbm, + (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), + "recalc_face_normals faces=%hf", + BM_ELEM_TAG); + BM_mesh_elem_hflag_disable_all(srcbm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + } + + return srcbm; +} + +void sculpt_array_source_datalayer_update(BMesh *bm, const int symm_pass, const int copy_index) { + const int cd_array_instance_index = CustomData_get_named_layer_index( + &bm->vdata, CD_PROP_INT32, array_instance_cd_name); + const int cd_array_instance_offset = CustomData_get_n_offset( + &bm->vdata, CD_PROP_INT32, cd_array_instance_index); + + const int cd_array_symm_pass_index = CustomData_get_named_layer_index( + &bm->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name); + const int cd_array_symm_pass_offset = CustomData_get_n_offset( + &bm->vdata, CD_PROP_INT32, cd_array_symm_pass_index); + + BM_mesh_elem_table_ensure(bm, BM_VERT); + BM_mesh_elem_index_ensure(bm, BM_VERT); + + BMVert *v; + BMIter iter; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_ELEM_CD_SET_INT(v, cd_array_instance_offset, copy_index); + BM_ELEM_CD_SET_INT(v, cd_array_symm_pass_offset, symm_pass); + } +} + +static void sculpt_array_final_mesh_write(Object *ob, BMesh *final_mesh) { + SculptSession *ss = ob->sculpt; + Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob); + Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(final_mesh, NULL, sculpt_mesh); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + BKE_mesh_nomain_to_mesh(result, ob->data, ob, &CD_MASK_MESH, true); + BKE_id_free(NULL, result); + BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + + const int next_face_set_id = ED_sculpt_face_sets_find_next_available_id(ob->data); + ED_sculpt_face_sets_initialize_none_to_id(ob->data, next_face_set_id); + + ss->needs_pbvh_rebuild = true; +} + +static void sculpt_array_ensure_geometry_indices(Object *ob, SculptArray *array) { + Mesh *mesh = BKE_object_get_original_mesh(ob); + + if (array->copy_index) { + + return; + } + + +printf("ALLOCATION COPY INDEX\n"); + + array->copy_index = MEM_malloc_arrayN(mesh->totvert, sizeof(int), "array copy index"); + array->symmetry_pass = MEM_malloc_arrayN(mesh->totvert, sizeof(int), "array symmetry pass index"); + + int *cd_array_instance = CustomData_get_layer_named( + &mesh->vdata, CD_PROP_INT32, array_instance_cd_name); + + int *cd_array_symm_pass = CustomData_get_layer_named( + &mesh->vdata, CD_PROP_INT32, array_symmetry_pass_cd_name); + + if (!cd_array_instance) { + printf("ERROR 1"); + } + + for (int i = 0; i < mesh->totvert; i++) { + array->copy_index[i] = cd_array_instance[i]; + array->symmetry_pass[i] = cd_array_symm_pass[i]; + } + + SCULPT_array_datalayers_free(ob); +} + +static void sculpt_array_mesh_build(Sculpt *sd, Object *ob, SculptArray *array) { + Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob); + Brush *brush = BKE_paint_brush(&sd->paint); + sculpt_array_datalayers_add(sculpt_mesh); + + BMesh *srcbm = sculpt_array_source_build(ob, brush, array); + + BMesh *destbm; + const BMAllocTemplate allocsizeb = BMALLOC_TEMPLATE_FROM_ME(sculpt_mesh); + destbm = BM_mesh_create(&allocsizeb, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + BM_mesh_bm_from_me(destbm, + sculpt_mesh, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + + + BM_mesh_elem_toolflags_ensure(destbm); + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char symm_it = 0; symm_it <= symm; symm_it++) { + if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { + continue; + } + + for (int copy_index = 0; copy_index < array->num_copies; copy_index++) { + sculpt_array_source_datalayer_update(srcbm, symm_it, copy_index); + + BM_mesh_copy_init_customdata(destbm, srcbm, &bm_mesh_allocsize_default); + const int opflag = (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE); + BMO_op_callf(srcbm, opflag, "duplicate geom=%avef dest=%p", destbm); + } + } + + sculpt_array_final_mesh_write(ob, destbm); + + BM_mesh_free(srcbm); + BM_mesh_free(destbm); + + +} + +static SculptArray *sculpt_array_cache_create(Object *ob, eBrushArrayDeformType deform_type, const int num_copies) { + + SculptArray *array = MEM_callocN(sizeof(SculptArray), "Sculpt Array"); + array->num_copies = num_copies; + + array->mode = deform_type; + + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char symm_it = 0; symm_it <= symm; symm_it++) { + if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { + continue; + } + array->copies[symm_it] = MEM_malloc_arrayN(num_copies, sizeof(SculptArrayCopy), "Sculpt array copies"); + } + return array; +} + +static void sculpt_array_cache_free(SculptArray *array) { + return; + for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) { + MEM_SAFE_FREE(array->copies[symm_pass]); + } + MEM_freeN(array->copy_index); + MEM_freeN(array->symmetry_pass); + MEM_freeN(array); +} + +static void sculpt_array_init(Object *ob, Brush *brush, SculptArray *array) { + SculptSession *ss = ob->sculpt; + + /* TODO: add options. */ + copy_v3_v3(array->normal, ss->cache->view_normal); + array->radial_angle = 2.0f * M_PI; + + for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) { + if (array->copies[symm_pass] == NULL) { + continue; + } + for (int copy_index = 0; copy_index < array->num_copies; copy_index++) { + SculptArrayCopy *copy = &array->copies[symm_pass][copy_index]; + unit_m4(copy->mat); + copy->symm_pass = symm_pass; + copy->index = copy_index; + float symm_location[3]; + flip_v3_v3(symm_location, ss->cache->location, symm_pass); + copy_v3_v3(copy->origin, ss->cache->location); + } + } +} + + +static void sculpt_array_position_in_path_search(float *r_position, float *r_direction, float *r_scale, SculptArray *array, const int index) { + const float path_length = array->path.points[array->path.tot_points-1].length; + const float step_distance = path_length / (float)array->num_copies; + const float copy_distance = step_distance * (index + 1); + + + if (array->path.tot_points == 1) { + zero_v3(r_position); + if (r_direction) { + zero_v3(r_direction); + } + if (r_scale) { + *r_scale = 1.0f; + } + return; + } + + for (int i = 1; i < array->path.tot_points; i++) { + ScultpArrayPathPoint *path_point = &array->path.points[i]; + if (copy_distance >= path_point->length) { + continue; + } + ScultpArrayPathPoint *prev_path_point = &array->path.points[i - 1]; + + const float remaining_dist = copy_distance - prev_path_point->length; + const float segment_length = path_point->length - prev_path_point->length; + const float interp_factor = remaining_dist / segment_length; + interp_v3_v3v3(r_position, prev_path_point->co, path_point->co, interp_factor); + if (r_direction) { + if (i == array->path.tot_points - 1) { + copy_v3_v3(r_direction, prev_path_point->direction); + } + else { + copy_v3_v3(r_direction, path_point->direction); + } + } + if (r_scale) { + const float s = 1.0f - interp_factor; + *r_scale = s * prev_path_point->strength + interp_factor * path_point->strength; + } + return; + } + + ScultpArrayPathPoint *last_path_point = &array->path.points[array->path.tot_points - 1]; + copy_v3_v3(r_position, last_path_point->co); + if (r_direction) { + ScultpArrayPathPoint *prev_path_point = &array->path.points[array->path.tot_points - 2]; + copy_v3_v3(r_direction, prev_path_point->direction); + } + if (r_scale) { + ScultpArrayPathPoint *prev_path_point = &array->path.points[array->path.tot_points - 2]; + *r_scale = prev_path_point->strength; + } +} + +static void scultp_array_basis_from_direction(float r_mat[4][4], SculptArray *array, const float direction[3]) { + float direction_normalized[3]; + normalize_v3_v3(direction_normalized, direction); + copy_v3_v3(r_mat[0], direction_normalized); + cross_v3_v3v3(r_mat[2], r_mat[0], array->normal); + cross_v3_v3v3(r_mat[1], r_mat[0], r_mat[2]); + normalize_v3(r_mat[0]); + normalize_v3(r_mat[1]); + normalize_v3(r_mat[2]); +} + +static float *sculpt_array_delta_from_path(SculptArray *array) { + return array->path.points[array->path.tot_points - 1].co; +} + +static void sculpt_array_update_copy(StrokeCache *cache, SculptArray *array, SculptArrayCopy *copy, Brush *brush) { + + float copy_position[3]; + unit_m4(copy->mat); + + float scale = 1.0f; + float direction[3]; + + eBrushArrayDeformType array_type = brush->array_deform_type; + float delta[3]; + copy_v3_v3(delta, sculpt_array_delta_from_path(array)); + + switch (array_type) + { + case BRUSH_ARRAY_DEFORM_LINEAR: { + const float fade = ((float)copy->index + 1.0f) / (float)(array->num_copies); + mul_v3_v3fl(copy->mat[3], delta, fade); + normalize_v3_v3(direction, delta); + scale = cache->bstrength; + } + break; + + case BRUSH_ARRAY_DEFORM_RADIAL: { + float pos[3]; + const float fade = ((float)copy->index + 1.0f) / (float)(array->num_copies); + copy_v3_v3(pos, delta); + rotate_v3_v3v3fl(copy->mat[3], pos, array->normal, fade * array->radial_angle); + copy_v3_v3(direction, copy->mat[3]); + //sub_v3_v3v3(direction, copy->mat[3], array->source_origin); + scale = cache->bstrength; + } + break; + case BRUSH_ARRAY_DEFORM_PATH: + sculpt_array_position_in_path_search(copy->mat[3], direction, &scale, array, copy->index); + break; + } + + + if (!(brush->flag2 & BRUSH_ARRAY_LOCK_ORIENTATION)) { + scultp_array_basis_from_direction(copy->mat, array, direction); + } + + + /* + copy->mat[3][0] += (BLI_hash_int_01(copy->index) * 2.0f - 0.5f) * cache->radius; + copy->mat[3][1] += (BLI_hash_int_01(copy->index + 1) * 2.0f - 0.5f) * cache->radius; + copy->mat[3][2] += (BLI_hash_int_01(copy->index + 2) * 2.0f - 0.5f) * cache->radius; + */ + + mul_v3_fl(copy->mat[0], scale); + mul_v3_fl(copy->mat[1], scale); + mul_v3_fl(copy->mat[2], scale); + + /* + copy->mat[0][0] = scale; + copy->mat[1][1] = scale; + copy->mat[2][2] = scale; + */ + +} + +static void sculpt_array_update(Object *ob, Brush *brush, SculptArray *array) { + SculptSession *ss = ob->sculpt; + + /* Main symmetry pass. */ + for (int copy_index = 0; copy_index < array->num_copies; copy_index++) { + SculptArrayCopy *copy = &array->copies[0][copy_index]; + unit_m4(copy->mat); + sculpt_array_update_copy(ss->cache, array, copy, brush); + } + + for (int symm_pass = 1; symm_pass < PAINT_SYMM_AREAS; symm_pass++) { + if (array->copies[symm_pass] == NULL) { + continue; + } + + float symm_orig[3]; + flip_v3_v3(symm_orig, array->source_origin, symm_pass); + + for (int copy_index = 0; copy_index < array->num_copies; copy_index++) { + SculptArrayCopy *copy = &array->copies[symm_pass][copy_index]; + SculptArrayCopy *main_copy = &array->copies[0][copy_index]; + unit_m4(copy->mat); + for (int m = 0; m < 4; m++) { + flip_v3_v3(copy->mat[m],main_copy->mat[m], symm_pass); + } + } + } + + for (int symm_pass = 0; symm_pass < PAINT_SYMM_AREAS; symm_pass++) { + + if (array->copies[symm_pass] == NULL) { + continue; + } + for (int copy_index = 0; copy_index < array->num_copies; copy_index++) { + SculptArrayCopy *copy = &array->copies[symm_pass][copy_index]; + invert_m4_m4(copy->imat, copy->mat); + } + } + +} + +static void do_array_deform_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + SculptArray *array = ss->array; + + Mesh *mesh = BKE_object_get_original_mesh(data->ob); + + bool any_modified = false; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + int array_index = ARRAY_INSTANCE_ORIGINAL; + int array_symm_pass = 0; + sculpt_vertex_array_data_get(array, vd.index, &array_index, &array_symm_pass); + + if (array_index == ARRAY_INSTANCE_ORIGINAL) { + continue; + } + + SculptArrayCopy *copy = &array->copies[array_symm_pass][array_index]; + + float co[3]; + copy_v3_v3(co, array->orco[vd.index]); + mul_v3_m4v3(co, array->source_imat, array->orco[vd.index]); + mul_v3_m4v3(co, copy->mat, co); + float source_origin_symm[3]; + flip_v3_v3(source_origin_symm, array->source_origin, array_symm_pass); + add_v3_v3v3(vd.co, co, source_origin_symm); + + any_modified = true; + + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (any_modified) { + BKE_pbvh_node_mark_update(data->nodes[n]); + } +} + +static void sculpt_array_deform(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { + /* Threaded loop over nodes. */ + SculptSession *ss = ob->sculpt; + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range( + 0, totnode, &data, do_array_deform_task_cb_ex, &settings); +} + + +static void do_array_smooth_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + SculptArray *array = ss->array; + + Mesh *mesh = BKE_object_get_original_mesh(data->ob); + + bool any_modified = false; + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + int array_index = ARRAY_INSTANCE_ORIGINAL; + int array_symm_pass = 0; + sculpt_vertex_array_data_get(array, vd.index, &array_index, &array_symm_pass); + + const float fade = array->smooth_strength[vd.index]; + + if (fade == 0.0f) { + continue; + } + + float smooth_co[3]; + SCULPT_neighbor_coords_average(ss, smooth_co, vd.index); + float disp[3]; + sub_v3_v3v3(disp, smooth_co, vd.co); + mul_v3_fl(disp, fade); + add_v3_v3(vd.co, disp); + + +/* + if (array_index == ARRAY_INSTANCE_ORIGINAL) { + continue; + } + + bool do_smooth = false; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + int neighbor_array_index = ARRAY_INSTANCE_ORIGINAL; + int neighbor_symm_pass = 0; + sculpt_vertex_array_data_get(array, ni.index, &neighbor_array_index,&neighbor_symm_pass); + if (neighbor_array_index != array_index) { + do_smooth = true; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + if (!do_smooth) { + continue; + } + */ + + + + any_modified = true; + + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (any_modified) { + BKE_pbvh_node_mark_update(data->nodes[n]); + } +} + +static void sculpt_array_smooth(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { + + + + /* Threaded loop over nodes. */ + SculptSession *ss = ob->sculpt; + SculptArray *array = ss->array; + + if (!array) { + return; + } + + if (!array->smooth_strength) { + return; + } + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, totnode); + BLI_task_parallel_range( + 0, totnode, &data, do_array_smooth_task_cb_ex, &settings); +} + +static void sculpt_array_ensure_original_coordinates(Object *ob, SculptArray *array){ + SculptSession *ss = ob->sculpt; + Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob); + const int totvert = SCULPT_vertex_count_get(ss); + + if (array->orco) { + return; + } + + array->orco = MEM_malloc_arrayN(sculpt_mesh->totvert, sizeof(float) * 3, "array orco"); + for (int i = 0; i < totvert; i++) { + copy_v3_v3(array->orco[i], SCULPT_vertex_co_get(ss, i)); + } +} + +static void sculpt_array_ensure_base_transform(Sculpt *sd, Object *ob, SculptArray *array){ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + Mesh *sculpt_mesh = BKE_object_get_original_mesh(ob); + const int totvert = SCULPT_vertex_count_get(ss); + + if (array->source_mat_valid) { + return; + } + + unit_m4(array->source_mat); + + if (brush->flag2 & BRUSH_ARRAY_LOCK_ORIENTATION) { + unit_m4(array->source_mat); + copy_v3_v3(array->source_mat[3], array->source_origin); + invert_m4_m4(array->source_imat, array->source_mat); + array->source_mat_valid = true; + return; + } + + if (is_zero_v3(ss->cache->grab_delta)) { + return; + } + + scultp_array_basis_from_direction(array->source_mat, array, ss->cache->grab_delta); + copy_v3_v3(array->source_mat[3], array->source_origin); + invert_m4_m4(array->source_imat, array->source_mat); + + array->source_mat_valid = true; + return; +} + +static void sculpt_array_path_point_update(SculptArray *array, const int path_point_index) { + if (path_point_index == 0) { + return; + } + + const int prev_path_point_index = path_point_index - 1; + + ScultpArrayPathPoint *path_point = &array->path.points[path_point_index]; + ScultpArrayPathPoint *prev_path_point = &array->path.points[prev_path_point_index]; + + if (len_v3v3(prev_path_point->co, path_point->co) <= 0.0001f) { + return; + } + sub_v3_v3v3(prev_path_point->direction, path_point->co, prev_path_point->co); + path_point->length = prev_path_point->length + normalize_v3(prev_path_point->direction); +} + + +static void sculpt_array_stroke_sample_add(Object *ob, SculptArray *array) { + SculptSession *ss = ob->sculpt; + + if (!array->path.points) { + array->path.points = MEM_malloc_arrayN(9999, sizeof(ScultpArrayPathPoint), "Array Path"); + } + + const int current_point_index = array->path.tot_points; + const int prev_point_index = current_point_index - 1; + + ScultpArrayPathPoint *path_point = &array->path.points[current_point_index]; + + //add_v3_v3v3(path_point->co, ss->cache->orig_grab_location, ss->cache->grab_delta); + copy_v3_v3(path_point->co, ss->cache->grab_delta); + path_point->strength = ss->cache->bstrength; + + if (current_point_index == 0) { + /* First point of the path. */ + path_point->length = 0.0f; + } + else { + ScultpArrayPathPoint *prev_path_point = &array->path.points[prev_point_index]; + if (len_v3v3(prev_path_point->co, path_point->co) <= 0.0001f) { + return; + } + sub_v3_v3v3(prev_path_point->direction, path_point->co, prev_path_point->co); + path_point->length = prev_path_point->length + normalize_v3(prev_path_point->direction); + } + + array->path.tot_points++; +} + +void SCULPT_do_array_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + + if (ss->cache->invert) { + if (!ss->array) { + return; + } + + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { + SculptArray *array = ss->array; + const int totvert = SCULPT_vertex_count_get(ss); + + + /* Rebuild smooth strength cache. */ + MEM_SAFE_FREE(array->smooth_strength); + array->smooth_strength = MEM_calloc_arrayN(sizeof(float), totvert, "smooth_strength"); + + + for (int i = 0; i < totvert; i++) { + int array_index = ARRAY_INSTANCE_ORIGINAL; + int array_symm_pass = 0; + sculpt_vertex_array_data_get(array, i, &array_index, &array_symm_pass); + + if (array_index == ARRAY_INSTANCE_ORIGINAL) { + continue; + } + + /* TODO: this can be cached. */ + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + int neighbor_array_index = ARRAY_INSTANCE_ORIGINAL; + int neighbor_symm_pass = 0; + sculpt_vertex_array_data_get(array, ni.index, &neighbor_array_index,&neighbor_symm_pass); + if (neighbor_array_index != array_index) { + array->smooth_strength[i] = 1.0f; + break; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } + + for(int smooth_iterations = 0; smooth_iterations < 4; smooth_iterations++) { + for (int i = 0; i < totvert; i++) { + float avg = array->smooth_strength[i]; + int count = 1; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, i, ni) { + avg += array->smooth_strength[ni.index]; + count++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + array->smooth_strength[i] = avg / count; + } + } + + + + + /* Update Array Path Orco. */ + for (int i = 0; i < array->path.tot_points; i++) { + ScultpArrayPathPoint *point = &array->path.points[i]; + copy_v3_v3(point->orco, point->co); + } + array->initial_radial_angle = array->radial_angle; + + /* Update Geometry Orco. */ + for (int i = 0; i < totvert; i++) { + int array_index = ARRAY_INSTANCE_ORIGINAL; + int array_symm_pass = 0; + sculpt_vertex_array_data_get(array, i, &array_index, &array_symm_pass); + + if (array_index == ARRAY_INSTANCE_ORIGINAL) { + continue; + } + SculptArrayCopy *copy = &array->copies[array_symm_pass][array_index]; + //sub_v3_v3v3(array->orco[i], SCULPT_vertex_co_get(ss, i), copy->mat[3]); + float co[3]; + float source_origin_symm[3]; + copy_v3_v3(co, SCULPT_vertex_co_get(ss, i)); + flip_v3_v3(source_origin_symm, array->source_origin, array_symm_pass); + mul_v3_m4v3(co, copy->imat, co); + mul_v3_m4v3(co, array->source_imat, co); + //sub_v3_v3v3(co, co, source_origin_symm); + + copy_v3_v3(array->orco[i], co); + } + } + + + SculptArray *array = ss->array; + if (array->mode == BRUSH_ARRAY_DEFORM_PATH) { + /* Deform path */ + for (int i = 0; i < array->path.tot_points; i++) { + ScultpArrayPathPoint *point = &array->path.points[i]; + float point_co[3]; + add_v3_v3v3(point_co, point->orco, array->source_origin); + const float len = len_v3v3(ss->cache->true_location, point_co); + const float fade = ss->cache->bstrength * BKE_brush_curve_strength(brush, len, ss->cache->radius); + if (fade <= 0.0f) { + continue; + } + madd_v3_v3v3fl(point->co, point->orco, ss->cache->grab_delta, fade); + } + for (int i = 0; i < array->path.tot_points; i++) { + sculpt_array_path_point_update(array, i); + } + + } + else { + /* Tweak radial angle. */ + /* + const float factor = 1.0f - ( len_v3(ss->cache->grab_delta) / ss->cache->initial_radius); + array->radial_angle = array->initial_radial_angle * clamp_f(factor, 0.0f, 1.0f); + */ + + float array_disp_co[3]; + float brush_co[3]; + add_v3_v3v3(brush_co, ss->cache->initial_location, ss->cache->grab_delta); + sub_v3_v3(brush_co, array->source_origin); + normalize_v3(brush_co); + normalize_v3_v3(array_disp_co, sculpt_array_delta_from_path(array)); + array->radial_angle = angle_signed_on_axis_v3v3_v3(brush_co, array_disp_co, array->normal); + } + + sculpt_array_update(ob, brush, ss->array); + sculpt_array_deform(sd, ob, nodes, totnode); + for (int i = 0; i < 5; i++) { + sculpt_array_smooth(sd, ob, nodes, totnode); + } + + return; + + } + + + if (brush->array_count == 0) { + return; + } + + if (!SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { + /* This brush manages its own symmetry. */ + return; + } + + if (SCULPT_stroke_is_first_brush_step(ss->cache)) { + if (ss->array) { + sculpt_array_cache_free(ss->array); + } + + ss->array = sculpt_array_cache_create(ob, brush->array_deform_type, brush->array_count); + sculpt_array_init(ob, brush, ss->array); + sculpt_array_stroke_sample_add(ob, ss->array); + sculpt_array_mesh_build(sd, ob, ss->array); + /* Original coordinates can't be stored yet as the SculptSession data needs to be updated after the mesh modifications performed when building the array geometry. */ + return; + } + + sculpt_array_ensure_base_transform(sd, ob, ss->array); + sculpt_array_ensure_original_coordinates(ob, ss->array); + sculpt_array_ensure_geometry_indices(ob, ss->array); + + + sculpt_array_stroke_sample_add(ob, ss->array); + + sculpt_array_update(ob, brush, ss->array); + + sculpt_array_deform(sd, ob, nodes, totnode); +} + +void SCULPT_array_path_draw(const uint gpuattr, + Brush *brush, + SculptSession *ss) { + + SculptArray *array = ss->array; + + + /* Disable debug drawing. */ + return; + + if (!array) { + return; + } + + if (!array->path.points) { + return; + } + + if (array->path.tot_points < 2) { + return; + } + + const int tot_points = array->path.tot_points; + immBegin(GPU_PRIM_LINE_STRIP, tot_points); + for (int i = 0; i < tot_points; i++) { + float co[3]; + copy_v3_v3(co, array->path.points[i].co); + add_v3_v3(co, array->source_origin); + immVertex3fv(gpuattr, co); + } + immEnd(); +}
\ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/sculpt_automasking.c b/source/blender/editors/sculpt_paint/sculpt_automasking.c index 35f48400fe2..1980aa615a7 100644 --- a/source/blender/editors/sculpt_paint/sculpt_automasking.c +++ b/source/blender/editors/sculpt_paint/sculpt_automasking.c @@ -223,13 +223,21 @@ static float *SCULPT_topology_automasking_init(Sculpt *sd, Object *ob, float *au SculptFloodFill flood; SCULPT_floodfill_init(ss, &flood); const float radius = ss->cache ? ss->cache->radius : FLT_MAX; - SCULPT_floodfill_add_active(sd, ob, ss, &flood, radius); + + char symm = SCULPT_mesh_symmetry_xyz_get(ob); + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + symm = 0; + SCULPT_floodfill_add_initial(&flood, SCULPT_active_vertex_get(ss)); + } + else { + SCULPT_floodfill_add_active(sd, ob, ss, &flood, radius); + } AutomaskFloodFillData fdata = { .automask_factor = automask_factor, .radius = radius, .use_radius = ss->cache && sculpt_automasking_is_constrained_by_radius(brush), - .symm = SCULPT_mesh_symmetry_xyz_get(ob), + .symm = symm, }; copy_v3_v3(fdata.location, SCULPT_active_vertex_co_get(ss)); SCULPT_floodfill_execute(ss, &flood, automask_floodfill_cb, &fdata); diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c index 37678ec276a..c2e8f2a8d90 100644 --- a/source/blender/editors/sculpt_paint/sculpt_boundary.c +++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c @@ -539,6 +539,8 @@ void SCULPT_boundary_data_free(SculptBoundary *boundary) MEM_SAFE_FREE(boundary->bend.pivot_positions); MEM_SAFE_FREE(boundary->bend.pivot_rotation_axis); MEM_SAFE_FREE(boundary->slide.directions); + MEM_SAFE_FREE(boundary->circle.origin); + MEM_SAFE_FREE(boundary->circle.radius); MEM_SAFE_FREE(boundary); } @@ -628,6 +630,45 @@ static void sculpt_boundary_twist_data_init(SculptSession *ss, SculptBoundary *b MEM_freeN(poly_verts); } +static void sculpt_boundary_circle_data_init(SculptSession *ss, SculptBoundary *boundary) +{ + + const int totvert = SCULPT_vertex_count_get(ss); + const int totcircles = boundary->max_propagation_steps + 1; + + boundary->circle.radius = MEM_calloc_arrayN(totcircles, sizeof(float), "radius"); + boundary->circle.origin = MEM_calloc_arrayN(totcircles, sizeof(float) * 3, "origin"); + + int *count = MEM_calloc_arrayN(totcircles, sizeof(int), "count"); + for (int i = 0; i < totvert; i++) { + const int propagation_step_index = boundary->edit_info[i].num_propagation_steps; + if (propagation_step_index == -1) { + continue; + } + add_v3_v3(boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, i)); + count[propagation_step_index]++; + } + + for (int i = 0; i < totcircles; i++) { + mul_v3_fl(boundary->circle.origin[i], 1.0f / count[i]); + } + + for (int i = 0; i < totvert; i++) { + const int propagation_step_index = boundary->edit_info[i].num_propagation_steps; + if (propagation_step_index == -1) { + continue; + } + boundary->circle.radius[propagation_step_index] += len_v3v3( + boundary->circle.origin[propagation_step_index], SCULPT_vertex_co_get(ss, i)); + } + + for (int i = 0; i < totcircles; i++) { + boundary->circle.radius[i] *= 1.0f / count[i]; + } + + MEM_freeN(count); +} + static float sculpt_boundary_displacement_from_grab_delta_get(SculptSession *ss, SculptBoundary *boundary) { @@ -946,6 +987,60 @@ static void do_boundary_brush_smooth_task_cb_ex(void *__restrict userdata, BKE_pbvh_vertex_iter_end; } +static void do_boundary_brush_circle_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const int symmetry_pass = ss->cache->mirror_symmetry_pass; + const SculptBoundary *boundary = ss->cache->boundaries[symmetry_pass]; + const ePaintSymmetryFlags symm = SCULPT_mesh_symmetry_xyz_get(data->ob); + const Brush *brush = data->brush; + + const float strength = ss->cache->bstrength; + + PBVHVertexIter vd; + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (boundary->edit_info[vd.index].num_propagation_steps == -1) { + continue; + } + + SCULPT_orig_vert_data_update(&orig_data, &vd); + if (!SCULPT_check_vertex_pivot_symmetry( + orig_data.co, boundary->initial_vertex_position, symm)) { + continue; + } + + const int propagation_steps = boundary->edit_info[vd.index].num_propagation_steps; + float *circle_origin = boundary->circle.origin[propagation_steps]; + float circle_disp[3]; + sub_v3_v3v3(circle_disp, circle_origin, orig_data.co); + normalize_v3(circle_disp); + mul_v3_fl(circle_disp, -boundary->circle.radius[propagation_steps]); + float target_circle_co[3]; + add_v3_v3v3(target_circle_co, circle_origin, circle_disp); + + float disp[3]; + sub_v3_v3v3(disp, target_circle_co, vd.co); + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f; + const float automask = SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index); + madd_v3_v3v3fl(target_co, + vd.co, + disp, + boundary->edit_info[vd.index].strength_factor * mask * automask * strength); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + /* Main Brush Function. */ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { @@ -981,6 +1076,9 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn case BRUSH_BOUNDARY_DEFORM_TWIST: sculpt_boundary_twist_data_init(ss, ss->cache->boundaries[symm_area]); break; + case BRUSH_BOUNDARY_DEFORM_CIRCLE: + sculpt_boundary_circle_data_init(ss, ss->cache->boundaries[symm_area]); + break; case BRUSH_BOUNDARY_DEFORM_INFLATE: case BRUSH_BOUNDARY_DEFORM_GRAB: /* Do nothing. These deform modes don't need any extra data to be precomputed. */ @@ -1026,6 +1124,9 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn case BRUSH_BOUNDARY_DEFORM_SMOOTH: BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_smooth_task_cb_ex, &settings); break; + case BRUSH_BOUNDARY_DEFORM_CIRCLE: + BLI_task_parallel_range(0, totnode, &data, do_boundary_brush_circle_task_cb_ex, &settings); + break; } } diff --git a/source/blender/editors/sculpt_paint/sculpt_cloth.c b/source/blender/editors/sculpt_paint/sculpt_cloth.c index a53a2126af4..856a2652e21 100644 --- a/source/blender/editors/sculpt_paint/sculpt_cloth.c +++ b/source/blender/editors/sculpt_paint/sculpt_cloth.c @@ -103,6 +103,10 @@ #include <stdlib.h> #include <string.h> +/* Experimental features. */ + +#define USE_SOLVER_RIPPLE_CONSTRAINT false + static void cloth_brush_simulation_location_get(SculptSession *ss, const Brush *brush, float r_location[3]) @@ -195,8 +199,8 @@ static float cloth_brush_simulation_falloff_get(const Brush *brush, #define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024 #define CLOTH_SIMULATION_TIME_STEP 0.01f #define CLOTH_DEFORMATION_SNAKEHOOK_STRENGTH 0.35f -#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.01f -#define CLOTH_DEFORMATION_GRAB_STRENGTH 0.1f +#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.5f +#define CLOTH_DEFORMATION_GRAB_STRENGTH 0.5f static bool cloth_brush_sim_has_length_constraint(SculptClothSimulation *cloth_sim, const int v1, @@ -464,6 +468,17 @@ static void do_cloth_brush_build_constraints_task_cb_ex( BKE_pbvh_vertex_iter_end; } +static void cloth_brush_constraint_pos_to_line(SculptClothSimulation *cloth_sim, const int v) +{ + if (!USE_SOLVER_RIPPLE_CONSTRAINT) { + return; + } + float line_points[2][3]; + copy_v3_v3(line_points[0], cloth_sim->init_pos[v]); + add_v3_v3v3(line_points[1], cloth_sim->init_pos[v], cloth_sim->init_normal[v]); + closest_to_line_v3(cloth_sim->pos[v], cloth_sim->pos[v], line_points[0], line_points[1]); +} + static void cloth_brush_apply_force_to_vertex(SculptSession *UNUSED(ss), SculptClothSimulation *cloth_sim, const float force[3], @@ -511,6 +526,17 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, plane_from_point_normal_v3(deform_plane, data->area_co, plane_normal); } + KelvinletParams params; + const float kv_force = 1.0f; + const float kv_shear_modulus = 1.0f; + const float kv_poisson_ratio = 0.4f; + bool use_elastic_drag = false; + if (brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_ELASTIC_DRAG) { + BKE_kelvinlet_init_params( + ¶ms, ss->cache->radius, kv_force, kv_shear_modulus, kv_poisson_ratio); + use_elastic_drag = true; + } + /* Gravity */ float gravity[3] = {0.0f}; if (ss->cache->supports_gravity) { @@ -538,7 +564,9 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, cloth_brush_apply_force_to_vertex(ss, ss->cache->cloth_sim, vertex_gravity, vd.index); /* When using the plane falloff mode the falloff is not constrained by the brush radius. */ - if (!sculpt_brush_test_sq_fn(&test, current_vertex_location) && !use_falloff_plane) { + /* Brushes that use elastic deformation are also not constrained by radius. */ + if (!sculpt_brush_test_sq_fn(&test, current_vertex_location) && !use_falloff_plane && + !use_elastic_drag) { continue; } @@ -627,6 +655,24 @@ static void do_cloth_brush_apply_forces_task_cb_ex(void *__restrict userdata, cloth_sim->length_constraint_tweak[vd.index] += fade * 0.1f; zero_v3(force); break; + case BRUSH_CLOTH_DEFORM_ELASTIC_DRAG: { + float final_disp[3]; + sub_v3_v3v3(brush_disp, ss->cache->location, ss->cache->last_location); + mul_v3_v3fl(final_disp, brush_disp, ss->cache->bstrength); + float location[3]; + if (use_falloff_plane) { + closest_to_plane_v3(location, deform_plane, vd.co); + } + else { + copy_v3_v3(location, ss->cache->location); + } + BKE_kelvinlet_grab_triscale(final_disp, ¶ms, vd.co, location, brush_disp); + mul_v3_fl(final_disp, 20.0f * (1.0f - fade)); + add_v3_v3(cloth_sim->pos[vd.index], final_disp); + zero_v3(force); + } + + break; } cloth_brush_apply_force_to_vertex(ss, ss->cache->cloth_sim, force, vd.index); @@ -761,6 +807,17 @@ static void cloth_brush_solve_collision(Object *object, mul_v3_m4v3(cloth_sim->pos[i], obmat_inv, cloth_sim->pos[i]); } } +static void cloth_simulation_noise_get(float *r_noise, + SculptSession *ss, + const int index, + const float strength) +{ + const uint *hash_co = (const uint *)SCULPT_vertex_co_get(ss, index); + for (int i = 0; i < 3; i++) { + const uint hash = BLI_hash_int_2d(hash_co[0], hash_co[1]) ^ BLI_hash_int_2d(hash_co[2], i); + r_noise[i] = (hash * (1.0f / 0xFFFFFFFF) - 0.5f) * strength; + } +} static void do_cloth_brush_solve_simulation_task_cb_ex( void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) @@ -808,6 +865,16 @@ static void do_cloth_brush_solve_simulation_task_cb_ex( madd_v3_v3fl(cloth_sim->pos[i], pos_diff, mask_v); madd_v3_v3fl(cloth_sim->pos[i], cloth_sim->acceleration[i], mask_v); + /* Prevents the vertices from sliding without creating folds when all vertices and forces are + * in the same plane. */ + float noise[3]; + cloth_simulation_noise_get(noise, ss, vd.index, 0.000001f); + add_v3_v3(cloth_sim->pos[i], noise); + + if (USE_SOLVER_RIPPLE_CONSTRAINT) { + cloth_brush_constraint_pos_to_line(cloth_sim, i); + } + if (cloth_sim->collider_list != NULL) { cloth_brush_solve_collision(data->ob, cloth_sim, i); } @@ -920,6 +987,10 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss, deformation_strength); } } + if (USE_SOLVER_RIPPLE_CONSTRAINT) { + cloth_brush_constraint_pos_to_line(cloth_sim, v1); + cloth_brush_constraint_pos_to_line(cloth_sim, v2); + } } } } @@ -1092,6 +1163,13 @@ SculptClothSimulation *SCULPT_cloth_brush_simulation_create(SculptSession *ss, totverts, sizeof(float[3]), "cloth sim softbody pos"); } + if (USE_SOLVER_RIPPLE_CONSTRAINT) { + cloth_sim->init_normal = MEM_calloc_arrayN(totverts, sizeof(float) * 3, "init noramls"); + for (int i = 0; i < totverts; i++) { + SCULPT_vertex_normal_get(ss, i, cloth_sim->init_normal[i]); + } + } + cloth_sim->mass = cloth_mass; cloth_sim->damping = cloth_damping; cloth_sim->softbody_strength = cloth_softbody_strength; @@ -1264,6 +1342,7 @@ void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim) MEM_SAFE_FREE(cloth_sim->init_pos); MEM_SAFE_FREE(cloth_sim->deformation_strength); MEM_SAFE_FREE(cloth_sim->node_state); + MEM_SAFE_FREE(cloth_sim->init_normal); BLI_ghash_free(cloth_sim->node_state_index, NULL, NULL); if (cloth_sim->collider_list) { BKE_collider_cache_free(&cloth_sim->collider_list); @@ -1361,6 +1440,25 @@ static EnumPropertyItem prop_cloth_filter_type[] = { {0, NULL, 0, NULL, NULL}, }; +typedef enum eSculpClothFilterPinchOriginType { + CLOTH_FILTER_PINCH_ORIGIN_CURSOR, + CLOTH_FILTER_PINCH_ORIGIN_FACE_SET, +} eSculptClothFilterPinchOriginType; + +static EnumPropertyItem prop_cloth_filter_pinch_origin_type[] = { + {CLOTH_FILTER_PINCH_ORIGIN_CURSOR, + "CURSOR", + 0, + "Cursor", + "Pinches to the location of the cursor"}, + {CLOTH_FILTER_PINCH_ORIGIN_FACE_SET, + "FACE_SET", + 0, + "Face Set", + "Pinches to the average location of the Face Set"}, + {0, NULL, 0, NULL, NULL}, +}; + static EnumPropertyItem prop_cloth_filter_orientation_items[] = { {SCULPT_FILTER_ORIENTATION_LOCAL, "LOCAL", @@ -1430,6 +1528,7 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, Sculpt *sd = data->sd; SculptSession *ss = data->ob->sculpt; PBVHNode *node = data->nodes[i]; + const ePaintSymmetryFlags symm = SCULPT_mesh_symmetry_xyz_get(data->ob); SculptClothSimulation *cloth_sim = ss->filter_cache->cloth_sim; @@ -1445,8 +1544,12 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, } mul_v3_fl(sculpt_gravity, sd->gravity_factor * data->filter_strength); + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]); + PBVHVertexIter vd; BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); float fade = vd.mask ? *vd.mask : 0.0f; fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); fade = 1.0f - fade; @@ -1480,11 +1583,17 @@ static void cloth_filter_apply_forces_task_cb(void *__restrict userdata, cloth_sim->length_constraint_tweak[vd.index] += fade * data->filter_strength * 0.01f; zero_v3(force); break; - case CLOTH_FILTER_PINCH: - sub_v3_v3v3(force, ss->filter_cache->cloth_sim_pinch_point, vd.co); + case CLOTH_FILTER_PINCH: { + char symm_area = SCULPT_get_vertex_symm_area(orig_data.co); + float pinch_point[3]; + copy_v3_v3(pinch_point, ss->filter_cache->cloth_sim_pinch_point); + SCULPT_flip_v3_by_symm_area( + pinch_point, symm, symm_area, ss->filter_cache->cloth_sim_pinch_point); + sub_v3_v3v3(force, pinch_point, vd.co); normalize_v3(force); mul_v3_fl(force, fade * data->filter_strength); break; + } case CLOTH_FILTER_SCALE: unit_m3(transform); scale_m3_fl(transform, 1.0f + (fade * data->filter_strength)); @@ -1569,6 +1678,28 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent return OPERATOR_RUNNING_MODAL; } +static void sculpt_cloth_filter_face_set_pinch_origin_calculate(float r_pinch_origin[3], + SculptSession *ss) +{ + const int totvert = SCULPT_vertex_count_get(ss); + const int active_face_set = SCULPT_active_face_set_get(ss); + float accum[3] = {0.0f}; + int tot = 0; + for (int i = 0; i < totvert; i++) { + if (!SCULPT_vertex_has_face_set(ss, i, active_face_set)) { + continue; + } + add_v3_v3(accum, SCULPT_vertex_co_get(ss, i)); + tot++; + } + if (tot > 0) { + mul_v3_v3fl(r_pinch_origin, accum, 1.0f / tot); + } + else { + copy_v3_v3(r_pinch_origin, SCULPT_active_vertex_co_get(ss)); + } +} + static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Object *ob = CTX_data_active_object(C); @@ -1583,7 +1714,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent SculptCursorGeometryInfo sgi; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); SCULPT_vertex_random_access_ensure(ss); @@ -1598,6 +1729,7 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent const float cloth_mass = RNA_float_get(op->ptr, "cloth_mass"); const float cloth_damping = RNA_float_get(op->ptr, "cloth_damping"); const bool use_collisions = RNA_boolean_get(op->ptr, "use_collisions"); + const int pinch_origin = RNA_enum_get(op->ptr, "pinch_origin"); ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create( ss, cloth_mass, @@ -1606,7 +1738,15 @@ static int sculpt_cloth_filter_invoke(bContext *C, wmOperator *op, const wmEvent use_collisions, cloth_filter_is_deformation_filter(filter_type)); - copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss)); + switch (pinch_origin) { + case CLOTH_FILTER_PINCH_ORIGIN_CURSOR: + copy_v3_v3(ss->filter_cache->cloth_sim_pinch_point, SCULPT_active_vertex_co_get(ss)); + break; + case CLOTH_FILTER_PINCH_ORIGIN_FACE_SET: + sculpt_cloth_filter_face_set_pinch_origin_calculate(ss->filter_cache->cloth_sim_pinch_point, + ss); + break; + } SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim); @@ -1662,6 +1802,12 @@ void SCULPT_OT_cloth_filter(struct wmOperatorType *ot) "Operation that is going to be applied to the mesh"); RNA_def_float( ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter strength", -10.0f, 10.0f); + RNA_def_enum(ot->srna, + "pinch_origin", + prop_cloth_filter_pinch_origin_type, + CLOTH_FILTER_PINCH_ORIGIN_CURSOR, + "Pinch Origin", + "Location that is used to direct the pinch force"); RNA_def_enum_flag(ot->srna, "force_axis", prop_cloth_filter_force_axis_items, diff --git a/source/blender/editors/sculpt_paint/sculpt_detail.c b/source/blender/editors/sculpt_paint/sculpt_detail.c index 188bb0a88eb..12114806594 100644 --- a/source/blender/editors/sculpt_paint/sculpt_detail.c +++ b/source/blender/editors/sculpt_paint/sculpt_detail.c @@ -170,7 +170,7 @@ static void sample_detail_voxel(bContext *C, ViewContext *vc, int mx, int my) /* Update the active vertex. */ const float mouse[2] = {mx, my}; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); /* Average the edge length of the connected edges to the active vertex. */ diff --git a/source/blender/editors/sculpt_paint/sculpt_expand.c b/source/blender/editors/sculpt_paint/sculpt_expand.c index 40874375772..08716fabe59 100644 --- a/source/blender/editors/sculpt_paint/sculpt_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_expand.c @@ -138,7 +138,8 @@ enum { SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY, SCULPT_EXPAND_MODAL_FALLOFF_TOPOLOGY_DIAGONALS, SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL, - SCULPT_EXPAND_MODAL_SNAP_TOGGLE, + SCULPT_EXPAND_MODAL_SNAP_ENABLE, + SCULPT_EXPAND_MODAL_SNAP_DISABLE, SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE, SCULPT_EXPAND_MODAL_LOOP_COUNT_DECREASE, SCULPT_EXPAND_MODAL_BRUSH_GRADIENT_TOGGLE, @@ -297,7 +298,7 @@ static bool sculpt_expand_face_state_get(SculptSession *ss, ExpandCache *expand_ const float active_factor = fmod(expand_cache->active_falloff, loop_len); const float falloff_factor = fmod(expand_cache->face_falloff[f], loop_len); - enabled = falloff_factor < active_factor; + enabled = falloff_factor <= active_factor; } if (expand_cache->falloff_type == SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET) { @@ -704,6 +705,71 @@ static float *sculpt_expand_diagonals_falloff_create(Object *ob, const int v) return dists; } +/** + * Poly Loop: + */ +static float *sculpt_expand_poly_loop_falloff_create(Object *ob, const int v) +{ + SculptSession *ss = ob->sculpt; + const int totvert = SCULPT_vertex_count_get(ss); + float *dists = MEM_calloc_arrayN(sizeof(float), totvert, "spherical dist"); + BLI_bitmap *visited_vertices = BLI_BITMAP_NEW(totvert, "visited vertices"); + GSQueue *queue = BLI_gsqueue_new(sizeof(int)); + + printf("POLY LOOP FALLOFF\n"); + + /* Search and initialize a boundary per symmetry pass, then mark those vertices as visited. */ + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char symm_it = 0; symm_it <= symm; symm_it++) { + if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { + continue; + } + + const int symm_vertex = sculpt_expand_get_vertex_index_for_symmetry_pass(ob, symm_it, v); + + BLI_bitmap *poly_loop = sculpt_poly_loop_from_cursor(ob); + + for (int i = 0; i < ss->totfaces; i++) { + if (!BLI_BITMAP_TEST(poly_loop, i)) { + continue; + } + MPoly *poly = &ss->mpoly[i]; + for (int l = 0; l < poly->totloop; l++) { + const int vertex = ss->mloop[poly->loopstart + l].v; + BLI_gsqueue_push(queue, &vertex); + BLI_BITMAP_ENABLE(visited_vertices, vertex); + } + } + MEM_freeN(poly_loop); + } + + /* If there are no boundaries, return a falloff with all values set to 0. */ + if (BLI_gsqueue_is_empty(queue)) { + return dists; + } + + /* Propagate the values from the boundaries to the rest of the mesh. */ + while (!BLI_gsqueue_is_empty(queue)) { + int v_next; + BLI_gsqueue_pop(queue, &v_next); + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, v_next, ni) { + if (BLI_BITMAP_TEST(visited_vertices, ni.index)) { + continue; + } + dists[ni.index] = dists[v_next] + 1.0f; + BLI_BITMAP_ENABLE(visited_vertices, ni.index); + BLI_gsqueue_push(queue, &ni.index); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + } + + BLI_gsqueue_free(queue); + MEM_freeN(visited_vertices); + return dists; +} + /* Functions to update the max_falloff value in the #ExpandCache. These functions are called after * initializing a new falloff to make sure that this value is always updated. */ @@ -946,7 +1012,8 @@ static void sculpt_expand_initialize_from_face_set_boundary(Object *ob, BLI_BITMAP_ENABLE(enabled_vertices, i); } - if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + /* TODO: Default to topology. */ + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && false) { sculpt_expand_geodesics_from_state_boundary(ob, expand_cache, enabled_vertices); } else { @@ -1033,6 +1100,11 @@ static void sculpt_expand_falloff_factors_from_vertex_and_symm_create( sculpt_expand_initialize_from_face_set_boundary( ob, expand_cache, expand_cache->initial_active_face_set, false); break; + case SCULPT_EXPAND_FALLOFF_POLY_LOOP: + expand_cache->vert_falloff = has_topology_info ? + sculpt_expand_poly_loop_falloff_create(ob, v) : + sculpt_expand_spherical_falloff_create(ob, v); + break; } /* Update max falloff values and propagate to base mesh faces if needed. */ @@ -1471,7 +1543,7 @@ static int sculpt_expand_target_vertex_update_and_get(bContext *C, { SculptSession *ss = ob->sculpt; SculptCursorGeometryInfo sgi; - if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) { + if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) { return SCULPT_active_vertex_get(ss); } return SCULPT_EXPAND_VERTEX_NONE; @@ -1742,20 +1814,18 @@ static int sculpt_expand_modal(bContext *C, wmOperator *op, const wmEvent *event } break; } - case SCULPT_EXPAND_MODAL_SNAP_TOGGLE: { - if (expand_cache->snap) { - expand_cache->snap = false; - if (expand_cache->snap_enabled_face_sets) { - BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL); - expand_cache->snap_enabled_face_sets = NULL; - } + case SCULPT_EXPAND_MODAL_SNAP_ENABLE: { + expand_cache->snap = true; + if (!expand_cache->snap_enabled_face_sets) { + expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets"); } - else { - expand_cache->snap = true; - if (!expand_cache->snap_enabled_face_sets) { - expand_cache->snap_enabled_face_sets = BLI_gset_int_new("snap face sets"); - } - sculpt_expand_snap_initialize_from_enabled(ss, expand_cache); + sculpt_expand_snap_initialize_from_enabled(ss, expand_cache); + } break; + case SCULPT_EXPAND_MODAL_SNAP_DISABLE: { + expand_cache->snap = false; + if (expand_cache->snap_enabled_face_sets) { + BLI_gset_free(expand_cache->snap_enabled_face_sets, NULL); + expand_cache->snap_enabled_face_sets = NULL; } } break; case SCULPT_EXPAND_MODAL_MOVE_TOGGLE: { @@ -2160,7 +2230,12 @@ void sculpt_expand_modal_keymap(wmKeyConfig *keyconf) "Diagonals Falloff", ""}, {SCULPT_EXPAND_MODAL_FALLOFF_SPHERICAL, "FALLOFF_SPHERICAL", 0, "Spherical Falloff", ""}, - {SCULPT_EXPAND_MODAL_SNAP_TOGGLE, "SNAP_TOGGLE", 0, "Snap expand to Face Sets", ""}, + {SCULPT_EXPAND_MODAL_SNAP_ENABLE, "SNAP_ENABLE", 0, "Snap expand to Face Sets", ""}, + {SCULPT_EXPAND_MODAL_SNAP_DISABLE, + "SNAP_DISABLE", + 0, + "Disable Snap expand to Face Sets", + ""}, {SCULPT_EXPAND_MODAL_LOOP_COUNT_INCREASE, "LOOP_COUNT_INCREASE", 0, @@ -2229,6 +2304,7 @@ void SCULPT_OT_expand(wmOperatorType *ot) {SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY, "BOUNDARY_TOPOLOGY", 0, "Boundary Topology", ""}, {SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET, "BOUNDARY_FACE_SET", 0, "Boundary Face Set", ""}, {SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET, "ACTIVE_FACE_SET", 0, "Active Face Set", ""}, + {SCULPT_EXPAND_FALLOFF_POLY_LOOP, "POLY_LOOP", 0, "POLY_LOOP", ""}, {0, NULL, 0, NULL, NULL}, }; diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set.c b/source/blender/editors/sculpt_paint/sculpt_face_set.c index bdbdb75732a..29c3c7f1184 100644 --- a/source/blender/editors/sculpt_paint/sculpt_face_set.c +++ b/source/blender/editors/sculpt_paint/sculpt_face_set.c @@ -111,7 +111,7 @@ int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const flo } SculptCursorGeometryInfo gi; - if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) { + if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false, false)) { return SCULPT_FACE_SET_NONE; } @@ -512,7 +512,6 @@ static EnumPropertyItem prop_sculpt_face_sets_init_types[] = { "Face Sets from Face Set Boundaries", "Create a Face Set per isolated Face Set", }, - {0, NULL, 0, NULL, NULL}, }; @@ -968,7 +967,7 @@ static int sculpt_face_sets_change_visibility_invoke(bContext *C, mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; SCULPT_vertex_random_access_ensure(ss); - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); return sculpt_face_sets_change_visibility_exec(C, op); } @@ -1052,6 +1051,10 @@ typedef enum eSculptFaceSetEditMode { SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2, SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3, SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4, + SCULPT_FACE_SET_EDIT_FAIR_CURVATURE = 5, + SCULPT_FACE_SET_EDIT_FILL_COMPONENT = 6, + SCULPT_FACE_SET_EDIT_EXTRUDE = 7, + SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY = 8, } eSculptFaceSetEditMode; static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = { @@ -1092,6 +1095,37 @@ static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = { "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " "vertex tangents", }, + /* + { + SCULPT_FACE_SET_EDIT_FAIR_CURVATURE, + "FAIR_CURVATURE", + 0, + "Fair Curvature", + "Creates a smooth as possible geometry patch from the Face Set minimizing changes in " + "surface curvature", + }, + */ + { + SCULPT_FACE_SET_EDIT_FILL_COMPONENT, + "FILL_COMPONENT", + 0, + "Fill Component", + "Expand a Face Set to fill all affected connected components", + }, + { + SCULPT_FACE_SET_EDIT_EXTRUDE, + "EXTRUDE", + 0, + "Extrude", + "Extrude a Face Set along the normals of the faces", + }, + { + SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY, + "ALL_TANGENCY", + 0, + "All tangency", + "Extrude a Face Set along the normals of the faces", + }, {0, NULL, 0, NULL, NULL}, }; @@ -1123,6 +1157,38 @@ static void sculpt_face_set_grow(Object *ob, } } +static void sculpt_face_set_fill_component(Object *ob, + SculptSession *ss, + const int active_face_set_id, + const bool UNUSED(modify_hidden)) +{ + SCULPT_connected_components_ensure(ob); + GSet *connected_components = BLI_gset_int_new("affected_components"); + + const int totvert = SCULPT_vertex_count_get(ss); + for (int i = 0; i < totvert; i++) { + if (!SCULPT_vertex_has_face_set(ss, i, active_face_set_id)) { + continue; + } + const int vertex_connected_component = ss->vertex_info.connected_component[i]; + if (BLI_gset_haskey(connected_components, POINTER_FROM_INT(vertex_connected_component))) { + continue; + } + BLI_gset_add(connected_components, POINTER_FROM_INT(vertex_connected_component)); + } + + for (int i = 0; i < totvert; i++) { + const int vertex_connected_component = ss->vertex_info.connected_component[i]; + if (!BLI_gset_haskey(connected_components, POINTER_FROM_INT(vertex_connected_component))) { + continue; + } + + SCULPT_vertex_face_set_set(ss, i, active_face_set_id); + } + + BLI_gset_free(connected_components, NULL); +} + static void sculpt_face_set_shrink(Object *ob, SculptSession *ss, const int *prev_face_sets, @@ -1268,6 +1334,10 @@ static void sculpt_face_set_apply_edit(Object *ob, MEM_SAFE_FREE(prev_face_sets); break; } + case SCULPT_FACE_SET_EDIT_FILL_COMPONENT: { + sculpt_face_set_fill_component(ob, ss, active_face_set_id, modify_hidden); + break; + } case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden); break; @@ -1277,6 +1347,23 @@ static void sculpt_face_set_apply_edit(Object *ob, case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY); break; + case SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY: { + GSet *face_sets_ids = BLI_gset_int_new("ids"); + for (int i = 0; i < ss->totfaces; i++) { + BLI_gset_add(face_sets_ids, POINTER_FROM_INT(ss->face_sets[i])); + } + + GSetIterator gs_iter; + GSET_ITER (gs_iter, face_sets_ids) { + const int face_set_id = POINTER_AS_INT(BLI_gsetIterator_getKey(&gs_iter)); + sculpt_face_set_edit_fair_face_set(ob, face_set_id, MESH_FAIRING_DEPTH_TANGENCY); + } + + BLI_gset_free(face_sets_ids, NULL); + } break; + case SCULPT_FACE_SET_EDIT_FAIR_CURVATURE: + sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_CURVATURE); + break; } } @@ -1289,7 +1376,7 @@ static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss, return false; } - if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) { + if (ELEM(mode, SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY, SCULPT_FACE_SET_EDIT_EXTRUDE)) { if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { /* Modification of base mesh geometry requires special remapping of multires displacement, * which does not happen here. @@ -1397,6 +1484,197 @@ static void sculpt_face_set_edit_modify_coordinates(bContext *C, MEM_freeN(nodes); } +typedef struct FaceSetExtrudeCD { + int active_face_set; + float cursor_location[3]; + float (*orig_co)[3]; + float init_mval[2]; + float (*orig_no)[3]; +} FaceSetExtrudeCD; + +static void sculpt_face_set_extrude_id(Object *ob, SculptSession *ss, const int active_face_set_id) +{ + + Mesh *mesh = ob->data; + const int next_face_set_id = ED_sculpt_face_sets_find_next_available_id(mesh); + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); + BMesh *bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + + BM_mesh_bm_from_me(bm, + mesh, + (&(struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + + BM_mesh_elem_table_init(bm, BM_FACE); + BM_mesh_elem_table_ensure(bm, BM_FACE); + BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_TAG, false); + BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_SELECT, false); + BMIter iter; + BMFace *f; + BM_mesh_select_mode_set(bm, SCE_SELECT_FACE); + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + const int face_index = BM_elem_index_get(f); + const int face_set_id = ss->face_sets[face_index]; + BM_elem_select_set(bm, (BMElem *)f, face_set_id == active_face_set_id); + BM_elem_flag_set(f, BM_ELEM_TAG, face_set_id == active_face_set_id); + } + BM_mesh_select_flush(bm); + BM_mesh_select_mode_flush(bm); + + BMOperator extop; + BMO_op_init(bm, &extop, BMO_FLAG_DEFAULTS, "extrude_face_region"); + BMO_slot_bool_set(extop.slots_in, "use_normal_flip", true); + BMO_slot_bool_set(extop.slots_in, "use_dissolve_ortho_edges", true); + BMO_slot_bool_set(extop.slots_in, "use_select_history", true); + char htype = BM_ALL_NOLOOP; + htype &= ~(BM_VERT | BM_EDGE); + if (htype & BM_FACE) { + htype |= BM_EDGE; + } + + BMO_slot_buffer_from_enabled_hflag(bm, &extop, extop.slots_in, "geom", htype, BM_ELEM_SELECT); + BMO_op_exec(bm, &extop); + BM_mesh_elem_hflag_disable_all(bm, BM_ALL_NOLOOP, BM_ELEM_SELECT, false); + + BMOIter siter; + BMElem *ele; + BMO_ITER (ele, &siter, extop.slots_out, "geom.out", BM_ALL_NOLOOP) { + BM_elem_flag_set(ele, BM_ELEM_TAG, true); + } + BMO_op_finish(bm, &extop); + + /* Set the new Face Set ID for the extrusion. */ + const int cd_face_sets_offset = CustomData_get_offset(&bm->pdata, CD_SCULPT_FACE_SETS); + BM_mesh_elem_table_ensure(bm, BM_FACE); + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + const int face_set_id = BM_ELEM_CD_GET_INT(f, cd_face_sets_offset); + if (face_set_id == active_face_set_id) { + continue; + } + BMVert *v; + BMIter face_iter; + BM_ITER_ELEM (v, &face_iter, f, BM_VERTS_OF_FACE) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + BM_ELEM_CD_SET_INT(f, cd_face_sets_offset, next_face_set_id); + break; + } + } + } + + BM_mesh_elem_hflag_enable_all(bm, BM_FACE, BM_ELEM_TAG, false); + BMO_op_callf(bm, + (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE), + "recalc_face_normals faces=%hf", + BM_ELEM_TAG); + + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false); + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false); + + BM_mesh_bm_to_me(NULL, + bm, + ob->data, + (&(struct BMeshToMeshParams){ + .calc_object_remap = false, + })); + + BM_mesh_free(bm); +} + +static int sculpt_face_set_edit_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + const int mode = RNA_enum_get(op->ptr, "mode"); + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + + const int totvert = SCULPT_vertex_count_get(ss); + + if (mode != SCULPT_FACE_SET_EDIT_EXTRUDE) { + return OPERATOR_FINISHED; + } + + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + FaceSetExtrudeCD *fsecd = op->customdata; + MEM_SAFE_FREE(fsecd->orig_co); + MEM_SAFE_FREE(fsecd->orig_no); + MEM_SAFE_FREE(op->customdata); + ED_sculpt_undo_geometry_end(ob); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS); + return OPERATOR_FINISHED; + } + + FaceSetExtrudeCD *fsecd = op->customdata; + float depth_world_space[3]; + float new_pos[3]; + mul_v3_m4v3(depth_world_space, ob->obmat, fsecd->cursor_location); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + float fmval[2] = {event->mval[0], fsecd->init_mval[1]}; + ED_view3d_win_to_3d(vc.v3d, vc.region, depth_world_space, fmval, new_pos); + float extrude_disp = len_v3v3(depth_world_space, new_pos); + + if (event->mval[0] <= fsecd->init_mval[0]) { + extrude_disp *= -1.0f; + } + + if (!fsecd->orig_co) { + fsecd->orig_co = MEM_calloc_arrayN(totvert, sizeof(float) * 3, "origco"); + fsecd->orig_no = MEM_calloc_arrayN(totvert, sizeof(float) * 3, "origno"); + for (int i = 0; i < totvert; i++) { + copy_v3_v3(fsecd->orig_co[i], SCULPT_vertex_co_get(ss, i)); + SCULPT_vertex_normal_get(ss, i, fsecd->orig_no[i]); + } + } + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + for (int i = 0; i < totvert; i++) { + if (!SCULPT_vertex_has_face_set(ss, i, fsecd->active_face_set)) { + continue; + } + madd_v3_v3v3fl(mvert[i].co, fsecd->orig_co[i], fsecd->orig_no[i], extrude_disp); + mvert[i].flag |= ME_VERT_PBVH_UPDATE; + } + + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update(nodes[i]); + } + MEM_SAFE_FREE(nodes); + + SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS); + return OPERATOR_RUNNING_MODAL; +} + +static void sculpt_face_set_extrude_begin(bContext *C, + wmOperator *op, + const wmEvent *event, + Object *ob, + const int active_face_set, + const float cursor_location[3]) +{ + FaceSetExtrudeCD *fsecd = MEM_callocN(sizeof(FaceSetExtrudeCD), "face set extrude cd"); + fsecd->active_face_set = active_face_set; + copy_v3_v3(fsecd->cursor_location, cursor_location); + float fmval[2] = {event->mval[0], event->mval[1]}; + copy_v2_v2(fsecd->init_mval, fmval); + op->customdata = fsecd; + + ED_sculpt_undo_geometry_begin(ob, "face set extrude"); + + sculpt_face_set_extrude_id(ob, ob->sculpt, active_face_set); + + BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data); +} + static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event) { Object *ob = CTX_data_active_object(C); @@ -1416,22 +1694,32 @@ static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEven * tool without brush cursor. */ SculptCursorGeometryInfo sgi; const float mouse[2] = {event->mval[0], event->mval[1]}; - if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) { + + if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) { /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ return OPERATOR_CANCELLED; } + const int active_face_set = SCULPT_active_face_set_get(ss); switch (mode) { + case SCULPT_FACE_SET_EDIT_EXTRUDE: + sculpt_face_set_extrude_begin(C, op, event, ob, active_face_set, sgi.location); + SCULPT_tag_update_overlays(C); + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY: sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden); break; case SCULPT_FACE_SET_EDIT_GROW: case SCULPT_FACE_SET_EDIT_SHRINK: + case SCULPT_FACE_SET_EDIT_FILL_COMPONENT: sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden); break; case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS: case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY: + case SCULPT_FACE_SET_EDIT_FAIR_CURVATURE: + case SCULPT_FACE_SET_EDIT_FAIR_ALL_TANGENCY: sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode); break; } @@ -1450,6 +1738,7 @@ void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot) /* Api callbacks. */ ot->invoke = sculpt_face_set_edit_invoke; + ot->modal = sculpt_face_set_edit_modal; ot->poll = SCULPT_mode_poll; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; diff --git a/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c new file mode 100644 index 00000000000..808515ab723 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_face_set_topology.c @@ -0,0 +1,208 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_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_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + +typedef enum eSculptFaceSetByTopologyMode { + SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART = 0, + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP = 1, +}; + +static EnumPropertyItem prop_sculpt_face_set_by_topology[] = { + { + SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART, + "LOOSE_PART", + 0, + "Loose Part", + "", + }, + { + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP, + "POLY_LOOP", + 0, + "Face Loop", + "", + }, + {0, NULL, 0, NULL, NULL}, +}; + +static void sculpt_face_set_by_topology_poly_loop(Object *ob, const int next_face_set_id) +{ + SculptSession *ss = ob->sculpt; + BLI_bitmap *poly_loop = sculpt_poly_loop_from_cursor(ob); + for (int i = 0; i < ss->totfaces; i++) { + if (BLI_BITMAP_TEST(poly_loop, i)) { + ss->face_sets[i] = next_face_set_id; + } + } + MEM_freeN(poly_loop); +} + +static int sculpt_face_set_by_topology_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + const int mode = RNA_enum_get(op->ptr, "mode"); + const bool repeat_previous = RNA_boolean_get(op->ptr, "repeat_previous"); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + printf("FACE SET TOPOLOGY\n"); + + /* Update the current active Face Set and Vertex as the operator can be used directly from the + * tool without brush cursor. */ + SculptCursorGeometryInfo sgi; + const float mouse[2] = {event->mval[0], event->mval[1]}; + if (!SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) { + /* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */ + return OPERATOR_CANCELLED; + } + + PBVHNode **nodes; + int totnode; + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode); + SCULPT_undo_push_begin(ob, "face set edit"); + SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_FACE_SETS); + + const int initial_poly = ss->active_face_index; + const int initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob); + + Mesh *mesh = BKE_object_get_original_mesh(ob); + int new_face_set = SCULPT_FACE_SET_NONE; + + if (repeat_previous && ss->face_set_last_created != SCULPT_FACE_SET_NONE && + initial_poly != ss->face_set_last_poly && initial_edge != ss->face_set_last_edge) { + new_face_set = ss->face_set_last_created; + } + else { + new_face_set = ED_sculpt_face_sets_find_next_available_id(mesh); + } + + switch (mode) { + case SCULPT_FACE_SET_TOPOLOGY_LOOSE_PART: + break; + case SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP: + sculpt_face_set_by_topology_poly_loop(ob, new_face_set); + break; + } + + ss->face_set_last_created = new_face_set; + ss->face_set_last_edge = initial_edge; + ss->face_set_last_poly = initial_poly; + + /* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */ + SCULPT_visibility_sync_all_face_sets_to_vertices(ob); + + for (int i = 0; i < totnode; i++) { + BKE_pbvh_node_mark_update_visibility(nodes[i]); + } + + BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility); + + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) { + BKE_mesh_flush_hidden_from_verts(ob->data); + } + + MEM_freeN(nodes); + + SCULPT_undo_push_end(); + SCULPT_tag_update_overlays(C); + + return OPERATOR_FINISHED; +} + +void SCULPT_OT_face_set_by_topology(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Face Set by Topology"; + ot->idname = "SCULPT_OT_face_set_by_topology"; + ot->description = "Create a new Face Set following the mesh topology"; + + /* Api callbacks. */ + ot->invoke = sculpt_face_set_by_topology_invoke; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_face_set_by_topology, + SCULPT_FACE_SET_TOPOLOGY_POLY_LOOP, + "Mode", + ""); + + RNA_def_boolean(ot->srna, + "repeat_previous", + true, + "Repeat previous Face Set", + "Repeat the latest created Face Set instead of a new one"); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_color.c b/source/blender/editors/sculpt_paint/sculpt_filter_color.c index 4b49bf2cefb..859ca9b2421 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_color.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_color.c @@ -274,7 +274,7 @@ static int sculpt_color_filter_invoke(bContext *C, wmOperator *op, const wmEvent SculptCursorGeometryInfo sgi; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); } /* Disable for multires and dyntopo for now */ diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c index 10f141e2311..97b960ff9e4 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mask.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mask.c @@ -316,6 +316,858 @@ void SCULPT_OT_mask_filter(struct wmOperatorType *ot) "Use a automatic number of iterations based on the number of vertices of the sculpt"); } +/******************************************************************************************/ +/* Interactive Preview Mask Filter */ + +#define SCULPT_IPMASK_FILTER_MIN_MULTITHREAD 1000 +#define SCULPT_IPMASK_FILTER_GRANULARITY 100 + +#define SCULPT_IPMASK_FILTER_QUANTIZE_STEP 0.1 + +typedef enum eSculptIPMaskFilterType { + IPMASK_FILTER_SMOOTH_SHARPEN, + IPMASK_FILTER_GROW_SHRINK, + IPMASK_FILTER_HARDER_SOFTER, + IPMASK_FILTER_CONTRAST, + IPMASK_FILTER_ADD_SUBSTRACT, + IPMASK_FILTER_INVERT, + IPMASK_FILTER_QUANTIZE, +} eSculptIPMaskFilterType; + +typedef enum MaskFilterStepDirectionType { + MASK_FILTER_STEP_DIRECTION_FORWARD, + MASK_FILTER_STEP_DIRECTION_BACKWARD, +} MaskFilterStepDirectionType; + +/* Grown/Shrink vertex callbacks. */ +static float sculpt_ipmask_vertex_grow_cb(SculptSession *ss, const int vertex, float *current_mask) +{ + float max = 0.0f; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + float vmask_f = current_mask[ni.index]; + if (vmask_f > max) { + max = vmask_f; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + return max; +} + +static float sculpt_ipmask_vertex_shrink_cb(SculptSession *ss, + const int vertex, + float *current_mask) +{ + float min = 1.0f; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + float vmask_f = current_mask[ni.index]; + if (vmask_f < min) { + min = vmask_f; + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + return min; +} + +/* Smooth/Sharpen vertex callbacks. */ +static float sculpt_ipmask_vertex_smooth_cb(SculptSession *ss, + const int vertex, + float *current_mask) +{ + float accum = current_mask[vertex]; + int total = 1; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + accum += current_mask[ni.index]; + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + return total > 0 ? accum / total : current_mask[vertex]; +} + +static float sculpt_ipmask_vertex_sharpen_cb(SculptSession *ss, + const int vertex, + float *current_mask) +{ + float accum = 0.0f; + int total = 0; + float vmask = current_mask[vertex]; + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + accum += current_mask[ni.index]; + total++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + const float avg = total > 0 ? accum / total : current_mask[vertex]; + const float val = avg - vmask; + + float new_mask; + if (vmask > 0.5f) { + new_mask = vmask + 0.03f; + } + else { + new_mask = vmask - 0.03f; + } + new_mask += val / 2.0f; + return clamp_f(new_mask, 0.0f, 1.0f); + +#ifdef SHARP_KERNEL + float accum = 0.0f; + float weight_accum = 0.0f; + const float neighbor_weight = -1.0f; + int neighbor_count = 0; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vertex, ni) { + accum += neighbor_weight * current_mask[ni.index]; + weight_accum += neighbor_weight; + neighbor_count++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + const float main_weight = (neighbor_count + 1) * 2.0f; + accum += main_weight * current_mask[vertex]; + weight_accum += main_weight; + + return clamp_f(accum / weight_accum, 0.0f, 1.0f); +#endif +} + +/* Harder/Softer callbacks. */ +#define SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP 0.01f +static float sculpt_ipmask_vertex_harder_cb(SculptSession *UNUSED(ss), + const int vertex, + float *current_mask) +{ + return clamp_f(current_mask[vertex] += current_mask[vertex] * + SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP, + 0.0f, + 1.0f); +} + +static float sculpt_ipmask_vertex_softer_cb(SculptSession *UNUSED(ss), + const int vertex, + float *current_mask) +{ + return clamp_f(current_mask[vertex] -= current_mask[vertex] * + SCULPT_IPMASK_FILTER_HARDER_SOFTER_STEP, + 0.0f, + 1.0f); +} + +/* Contrast Increase/Decrease callbacks. */ + +#define SCULPT_IPMASK_FILTER_CONTRAST_STEP 0.05f +static float sculpt_ipmask_filter_contrast(const float mask, const float contrast) +{ + float offset; + float delta = contrast / 2.0f; + float gain = 1.0f - delta * 2.0f; + if (contrast > 0.0f) { + gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON); + offset = gain * (-delta); + } + else { + delta *= -1.0f; + offset = gain * (delta); + } + return clamp_f(gain * mask + offset, 0.0f, 1.0f); +} + +static float sculpt_ipmask_vertex_contrast_increase_cb(SculptSession *UNUSED(ss), + const int vertex, + float *current_mask) +{ + return sculpt_ipmask_filter_contrast(current_mask[vertex], SCULPT_IPMASK_FILTER_CONTRAST_STEP); +} + +static float sculpt_ipmask_vertex_contrast_decrease_cb(SculptSession *UNUSED(ss), + const int vertex, + float *current_mask) +{ + return sculpt_ipmask_filter_contrast(current_mask[vertex], + -1.0f * SCULPT_IPMASK_FILTER_CONTRAST_STEP); +} + +static MaskFilterDeltaStep *sculpt_ipmask_filter_delta_create(const float *current_mask, + const float *next_mask, + const int totvert) +{ + int tot_modified_values = 0; + for (int i = 0; i < totvert; i++) { + if (current_mask[i] == next_mask[i]) { + continue; + } + tot_modified_values++; + } + + MaskFilterDeltaStep *delta_step = MEM_callocN(sizeof(MaskFilterDeltaStep), + "mask filter delta step"); + delta_step->totelem = tot_modified_values; + delta_step->index = MEM_malloc_arrayN(sizeof(int), tot_modified_values, "delta indices"); + delta_step->delta = MEM_malloc_arrayN(sizeof(float), tot_modified_values, "delta values"); + + int delta_step_index = 0; + for (int i = 0; i < totvert; i++) { + if (current_mask[i] == next_mask[i]) { + continue; + } + delta_step->index[delta_step_index] = i; + delta_step->delta[delta_step_index] = next_mask[i] - current_mask[i]; + delta_step_index++; + } + return delta_step; +} + +typedef struct SculptIPMaskFilterTaskData { + SculptSession *ss; + float *next_mask; + float *current_mask; + MaskFilterStepDirectionType direction; +} SculptIPMaskFilterTaskData; + +static void ipmask_filter_compute_step_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptIPMaskFilterTaskData *data = userdata; + if (data->direction == MASK_FILTER_STEP_DIRECTION_FORWARD) { + data->next_mask[i] = data->ss->filter_cache->mask_filter_step_forward( + data->ss, i, data->current_mask); + } + else { + data->next_mask[i] = data->ss->filter_cache->mask_filter_step_backward( + data->ss, i, data->current_mask); + } +} + +static float *sculpt_ipmask_step_compute(SculptSession *ss, + float *current_mask, + MaskFilterStepDirectionType direction) +{ + const int totvert = SCULPT_vertex_count_get(ss); + float *next_mask = MEM_malloc_arrayN(sizeof(float), totvert, "delta values"); + + SculptIPMaskFilterTaskData data = { + .ss = ss, + .next_mask = next_mask, + .current_mask = current_mask, + .direction = direction, + }; + TaskParallelSettings settings; + memset(&settings, 0, sizeof(TaskParallelSettings)); + settings.use_threading = totvert > SCULPT_IPMASK_FILTER_MIN_MULTITHREAD; + settings.min_iter_per_thread = SCULPT_IPMASK_FILTER_GRANULARITY; + BLI_task_parallel_range(0, totvert, &data, ipmask_filter_compute_step_task_cb, &settings); + + return next_mask; +} + +static float *sculpt_ipmask_current_state_get(SculptSession *ss) +{ + return MEM_dupallocN(ss->filter_cache->mask_filter_ref); +} + +static void sculpt_ipmask_reference_set(SculptSession *ss, float *new_mask) +{ + const int totvert = SCULPT_vertex_count_get(ss); + for (int i = 0; i < totvert; i++) { + ss->filter_cache->mask_filter_ref[i] = new_mask[i]; + } +} + +static void sculpt_ipmask_store_reference_step(SculptSession *ss) +{ + const int totvert = SCULPT_vertex_count_get(ss); + if (!ss->filter_cache->mask_filter_ref) { + ss->filter_cache->mask_filter_ref = MEM_malloc_arrayN(sizeof(float), totvert, "delta values"); + } + + for (int i = 0; i < totvert; i++) { + ss->filter_cache->mask_filter_ref[i] = SCULPT_vertex_mask_get(ss, i); + } +} + +static void ipmask_filter_apply_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ss; + FilterCache *filter_cache = ss->filter_cache; + PBVHNode *node = filter_cache->nodes[i]; + PBVHVertexIter vd; + bool update = false; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (SCULPT_automasking_factor_get(filter_cache->automasking, ss, vd.index) < 0.5f) { + continue; + } + + float new_mask; + if (data->next_mask) { + new_mask = interpf( + data->next_mask[vd.index], data->new_mask[vd.index], data->mask_interpolation); + } + else { + new_mask = data->new_mask[vd.index]; + } + + if (*vd.mask == new_mask) { + continue; + } + + *vd.mask = new_mask; + update = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (update) { + BKE_pbvh_node_mark_redraw(node); + } +} + +static void sculpt_ipmask_apply_mask_data(SculptSession *ss, + float *new_mask, + float *next_mask, + const float interpolation) +{ + FilterCache *filter_cache = ss->filter_cache; + SculptThreadedTaskData data = { + .ss = ss, + .nodes = filter_cache->nodes, + .new_mask = new_mask, + .next_mask = next_mask, + .mask_interpolation = interpolation, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode); + BLI_task_parallel_range(0, filter_cache->totnode, &data, ipmask_filter_apply_task_cb, &settings); +} + +static float *sculpt_ipmask_apply_delta_step(MaskFilterDeltaStep *delta_step, + const float *current_mask, + const MaskFilterStepDirectionType direction) +{ + float *next_mask = MEM_dupallocN(current_mask); + for (int i = 0; i < delta_step->totelem; i++) { + if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) { + next_mask[delta_step->index[i]] = current_mask[delta_step->index[i]] + delta_step->delta[i]; + } + else { + next_mask[delta_step->index[i]] = current_mask[delta_step->index[i]] - delta_step->delta[i]; + } + } + return next_mask; +} + +static float *sculpt_ipmask_restore_state_from_delta(SculptSession *ss, + MaskFilterDeltaStep *delta_step, + MaskFilterStepDirectionType direction) +{ + float *current_mask = sculpt_ipmask_current_state_get(ss); + float *next_mask = sculpt_ipmask_apply_delta_step(delta_step, current_mask, direction); + MEM_freeN(current_mask); + return next_mask; +} + +static float *sculpt_ipmask_compute_and_store_step(SculptSession *ss, + const int iterations, + const int delta_index, + MaskFilterStepDirectionType direction) +{ + BLI_assert(iterations > 0); + const int totvert = SCULPT_vertex_count_get(ss); + float *current_mask = sculpt_ipmask_current_state_get(ss); + float *original_mask = MEM_dupallocN(current_mask); + float *next_mask = NULL; + + /* Compute the filter. */ + for (int i = 0; i < iterations; i++) { + MEM_SAFE_FREE(next_mask); + next_mask = sculpt_ipmask_step_compute(ss, current_mask, direction); + MEM_freeN(current_mask); + current_mask = MEM_dupallocN(next_mask); + } + MEM_freeN(current_mask); + + /* Pack and store the delta step. */ + MaskFilterDeltaStep *delta_step; + if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) { + delta_step = sculpt_ipmask_filter_delta_create(original_mask, next_mask, totvert); + } + else { + delta_step = sculpt_ipmask_filter_delta_create(next_mask, original_mask, totvert); + } + BLI_ghash_insert(ss->filter_cache->mask_delta_step, POINTER_FROM_INT(delta_index), delta_step); + MEM_freeN(original_mask); + + return next_mask; +} + +static float *sculpt_ipmask_filter_mask_for_step_get(SculptSession *ss, + MaskFilterStepDirectionType direction, + const int iteration_count) +{ + FilterCache *filter_cache = ss->filter_cache; + int next_step = filter_cache->mask_filter_current_step; + int delta_index = next_step; + /* Get the next step and the delta step index associated with it. */ + if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) { + next_step = filter_cache->mask_filter_current_step + 1; + delta_index = filter_cache->mask_filter_current_step; + } + else { + next_step = filter_cache->mask_filter_current_step - 1; + delta_index = filter_cache->mask_filter_current_step - 1; + } + + /* Update the data one step forward/backward. */ + if (BLI_ghash_haskey(filter_cache->mask_delta_step, POINTER_FROM_INT(delta_index))) { + /* This step was already computed, restore it from the current step and a delta. */ + MaskFilterDeltaStep *delta_step = BLI_ghash_lookup(filter_cache->mask_delta_step, + POINTER_FROM_INT(delta_index)); + return sculpt_ipmask_restore_state_from_delta(ss, delta_step, direction); + } + + /* New step that was not yet computed. Compute and store the delta. */ + return sculpt_ipmask_compute_and_store_step(ss, iteration_count, delta_index, direction); +} + +static void sculpt_ipmask_filter_update_to_target_step(SculptSession *ss, + const int target_step, + const int iteration_count, + const float step_interpolation) +{ + FilterCache *filter_cache = ss->filter_cache; + + MaskFilterStepDirectionType direction; + /* Get the next step and the delta step index associated with it. */ + if (target_step > filter_cache->mask_filter_current_step) { + direction = MASK_FILTER_STEP_DIRECTION_FORWARD; + } + else { + direction = MASK_FILTER_STEP_DIRECTION_BACKWARD; + } + + while (filter_cache->mask_filter_current_step != target_step) { + /* Restore or compute a mask in the given direction. */ + float *new_mask = sculpt_ipmask_filter_mask_for_step_get(ss, direction, iteration_count); + + /* Store the full step. */ + sculpt_ipmask_reference_set(ss, new_mask); + MEM_freeN(new_mask); + + /* Update the current step count. */ + if (direction == MASK_FILTER_STEP_DIRECTION_FORWARD) { + filter_cache->mask_filter_current_step += 1; + } + else { + filter_cache->mask_filter_current_step -= 1; + } + } + + if (step_interpolation != 0.0f) { + float *next_mask = sculpt_ipmask_filter_mask_for_step_get( + ss, MASK_FILTER_STEP_DIRECTION_FORWARD, iteration_count); + sculpt_ipmask_apply_mask_data( + ss, filter_cache->mask_filter_ref, next_mask, step_interpolation); + MEM_freeN(next_mask); + } + else { + sculpt_ipmask_apply_mask_data(ss, filter_cache->mask_filter_ref, NULL, 0.0f); + } +} + +static void ipmask_filter_apply_from_original_task_cb( + void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ss; + FilterCache *filter_cache = ss->filter_cache; + PBVHNode *node = filter_cache->nodes[i]; + PBVHVertexIter vd; + SculptOrigVertData orig_data; + const eSculptIPMaskFilterType filter_type = data->filter_type; + bool update = false; + + /* Used for quantize filter. */ + const int steps = data->filter_strength / SCULPT_IPMASK_FILTER_QUANTIZE_STEP; + if (steps == 0) { + return; + } + const float step_size = 1.0f/steps; + + SCULPT_orig_vert_data_init(&orig_data, data->ob, node); + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + if (SCULPT_automasking_factor_get(filter_cache->automasking, ss, vd.index) < 0.5f) { + continue; + } + SCULPT_orig_vert_data_update(&orig_data, &vd); + float new_mask = orig_data.mask; + switch (filter_type) { + case IPMASK_FILTER_ADD_SUBSTRACT: + new_mask = orig_data.mask + data->filter_strength; + break; + case IPMASK_FILTER_INVERT: { + const float strength = clamp_f(data->filter_strength, 0.0f, 1.0f); + const float mask_invert = 1.0f - orig_data.mask; + new_mask = interpf(mask_invert, orig_data.mask, strength); + break; + } + case IPMASK_FILTER_QUANTIZE: { + const float remainder = fmod(orig_data.mask, step_size); + const float total_steps = (orig_data.mask - remainder) / step_size; + new_mask = total_steps * step_size; + break; + } + default: + BLI_assert(false); + break; + } + new_mask = clamp_f(new_mask, 0.0f, 1.0f); + if (*vd.mask == new_mask) { + continue; + } + + *vd.mask = new_mask; + update = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (update) { + BKE_pbvh_node_mark_redraw(node); + } +} + +static void sculpt_ipmask_apply_from_original_mask_data(Object *ob, + eSculptIPMaskFilterType filter_type, + const float strength) +{ + SculptSession *ss = ob->sculpt; + FilterCache *filter_cache = ss->filter_cache; + SculptThreadedTaskData data = { + .ob = ob, + .ss = ss, + .nodes = filter_cache->nodes, + .filter_strength = strength, + .filter_type = filter_type, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode); + BLI_task_parallel_range( + 0, filter_cache->totnode, &data, ipmask_filter_apply_from_original_task_cb, &settings); +} + +static bool sculpt_ipmask_filter_uses_apply_from_original( + const eSculptIPMaskFilterType filter_type) +{ + return ELEM(filter_type, IPMASK_FILTER_INVERT, IPMASK_FILTER_ADD_SUBSTRACT, IPMASK_FILTER_QUANTIZE); +} + +static void ipmask_filter_restore_original_mask_task_cb( + void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ss; + PBVHNode *node = data->nodes[i]; + SculptOrigVertData orig_data; + bool update = false; + SCULPT_orig_vert_data_init(&orig_data, data->ob, node); + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + *vd.mask = orig_data.mask; + update = true; + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + if (update) { + BKE_pbvh_node_mark_redraw(node); + } +} + +static void sculpt_ipmask_restore_original_mask(Object *ob) +{ + SculptSession *ss = ob->sculpt; + FilterCache *filter_cache = ss->filter_cache; + SculptThreadedTaskData data = { + .ob = ob, + .ss = ss, + .nodes = filter_cache->nodes, + }; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, filter_cache->totnode); + BLI_task_parallel_range( + 0, filter_cache->totnode, &data, ipmask_filter_restore_original_mask_task_cb, &settings); +} + +static void sculpt_ipmask_filter_cancel(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + sculpt_ipmask_restore_original_mask(ob); + SCULPT_undo_push_end(); + SCULPT_filter_cache_free(ss); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); +} + +#define IPMASK_FILTER_STEP_SENSITIVITY 0.05f +#define IPMASK_FILTER_STEPS_PER_FULL_STRENGTH 20 +static int sculpt_ipmask_filter_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + SculptSession *ss = ob->sculpt; + FilterCache *filter_cache = ss->filter_cache; + const int filter_type = RNA_enum_get(op->ptr, "filter_type"); + const bool use_step_interpolation = RNA_boolean_get(op->ptr, "use_step_interpolation"); + const int iteration_count = RNA_int_get(op->ptr, "iterations"); + + if ((event->type == EVT_ESCKEY && event->val == KM_PRESS) || + (event->type == RIGHTMOUSE && event->val == KM_PRESS)) { + sculpt_ipmask_filter_cancel(C, op); + return OPERATOR_FINISHED; + } + + if (ELEM(event->type, LEFTMOUSE, EVT_RETKEY, EVT_PADENTER)) { + for (int i = 0; i < filter_cache->totnode; i++) { + BKE_pbvh_node_mark_update_mask(filter_cache->nodes[i]); + } + SCULPT_filter_cache_free(ss); + SCULPT_undo_push_end(); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + return OPERATOR_FINISHED; + } + + if (event->type != MOUSEMOVE) { + return OPERATOR_RUNNING_MODAL; + } + + const float len = event->x - event->prevclickx; + const float target_step_fl = len * IPMASK_FILTER_STEP_SENSITIVITY * UI_DPI_FAC; + const int target_step = floorf(target_step_fl); + const float step_interpolation = use_step_interpolation ? target_step_fl - target_step : 0.0f; + const float full_step_strength = target_step_fl / IPMASK_FILTER_STEPS_PER_FULL_STRENGTH; + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + if (sculpt_ipmask_filter_uses_apply_from_original(filter_type)) { + sculpt_ipmask_apply_from_original_mask_data(ob, filter_type, full_step_strength); + } + else { + sculpt_ipmask_filter_update_to_target_step( + ss, target_step, iteration_count, step_interpolation); + } + + SCULPT_tag_update_overlays(C); + + return OPERATOR_RUNNING_MODAL; +} + +static void sculpt_ipmask_store_initial_undo_step(Object *ob) +{ + SculptSession *ss = ob->sculpt; + for (int i = 0; i < ss->filter_cache->totnode; i++) { + SCULPT_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK); + } +} + +static FilterCache *sculpt_ipmask_filter_cache_init(Object *ob, + Sculpt *sd, + const eSculptIPMaskFilterType filter_type, + const bool init_automasking) +{ + SculptSession *ss = ob->sculpt; + FilterCache *filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache"); + + filter_cache->active_face_set = SCULPT_FACE_SET_NONE; + if (init_automasking) { + filter_cache->automasking = SCULPT_automasking_cache_init(sd, NULL, ob); + } + filter_cache->mask_filter_current_step = 0; + + BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &filter_cache->nodes, &filter_cache->totnode); + + filter_cache->mask_delta_step = BLI_ghash_int_new("mask filter delta steps"); + switch (filter_type) { + case IPMASK_FILTER_SMOOTH_SHARPEN: + filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_smooth_cb; + filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_sharpen_cb; + break; + case IPMASK_FILTER_GROW_SHRINK: + filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_grow_cb; + filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_shrink_cb; + break; + case IPMASK_FILTER_HARDER_SOFTER: + filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_harder_cb; + filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_softer_cb; + break; + case IPMASK_FILTER_CONTRAST: + filter_cache->mask_filter_step_forward = sculpt_ipmask_vertex_contrast_increase_cb; + filter_cache->mask_filter_step_backward = sculpt_ipmask_vertex_contrast_decrease_cb; + break; + } + + return filter_cache; +} + +static int sculpt_ipmask_filter_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + SCULPT_undo_push_begin(ob, "mask filter"); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + + const int filter_type = RNA_enum_get(op->ptr, "filter_type"); + ss->filter_cache = sculpt_ipmask_filter_cache_init(ob, sd, filter_type, true); + sculpt_ipmask_store_initial_undo_step(ob); + sculpt_ipmask_store_reference_step(ss); + + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} +static int sculpt_ipmask_filter_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + const int iteration_count = RNA_int_get(op->ptr, "iterations"); + const float strength = RNA_float_get(op->ptr, "strength"); + const int filter_type = RNA_enum_get(op->ptr, "filter_type"); + const int direction = RNA_enum_get(op->ptr, "direction"); + + SCULPT_undo_push_begin(ob, "mask filter"); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + ss->filter_cache = sculpt_ipmask_filter_cache_init(ob, sd, filter_type, false); + sculpt_ipmask_store_initial_undo_step(ob); + sculpt_ipmask_store_reference_step(ss); + + const float target_step = direction == MASK_FILTER_STEP_DIRECTION_FORWARD ? 1 : -1; + if (sculpt_ipmask_filter_uses_apply_from_original(filter_type)) { + sculpt_ipmask_apply_from_original_mask_data(ob, filter_type, strength * target_step); + } + else { + sculpt_ipmask_filter_update_to_target_step(ss, target_step, iteration_count, 0.0f); + } + + SCULPT_tag_update_overlays(C); + SCULPT_filter_cache_free(ss); + SCULPT_undo_push_end(); + SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK); + return OPERATOR_FINISHED; +} + +void SCULPT_OT_ipmask_filter(struct wmOperatorType *ot) +{ + /* Identifiers. */ + ot->name = "Interactive Preview Mask Filter"; + ot->idname = "SCULPT_OT_ipmask_filter"; + ot->description = "Applies a filter to modify the current mask"; + + /* API callbacks. */ + ot->exec = sculpt_ipmask_filter_exec; + ot->invoke = sculpt_ipmask_filter_invoke; + ot->modal = sculpt_ipmask_filter_modal; + ot->cancel = sculpt_ipmask_filter_cancel; + ot->poll = SCULPT_mode_poll; + + ot->flag = OPTYPE_REGISTER; + + static EnumPropertyItem prop_ipmask_filter_types[] = { + {IPMASK_FILTER_SMOOTH_SHARPEN, + "SMOOTH_SHARPEN", + 0, + "Smooth/Sharpen", + "Smooth and sharpen the mask"}, + {IPMASK_FILTER_GROW_SHRINK, "GROW_SHRINK", 0, "Grow/Shrink", "Grow and shirnk the mask"}, + {IPMASK_FILTER_HARDER_SOFTER, + "HARDER_SOFTER", + 0, + "Harder/Softer", + "Makes the entire mask harder or softer"}, + {IPMASK_FILTER_ADD_SUBSTRACT, + "ADD_SUBSTRACT", + 0, + "Add/Substract", + "Adds or substract a value to the mask"}, + {IPMASK_FILTER_CONTRAST, + "CONTRAST", + 0, + "Contrast", + "Increases or decreases the contrast of the mask"}, + {IPMASK_FILTER_INVERT, "INVERT", 0, "Invert", "Inverts the mask"}, + {IPMASK_FILTER_QUANTIZE, "QUANTIZE", 0, "Quantize", "Quantizes the mask to intervals"}, + {0, NULL, 0, NULL, NULL}, + }; + + static EnumPropertyItem prop_ipmask_filter_direction_types[] = { + {MASK_FILTER_STEP_DIRECTION_FORWARD, + "FORWARD", + 0, + "Forward", + "Apply the filter in the forward direction"}, + {MASK_FILTER_STEP_DIRECTION_BACKWARD, + "BACKWARD", + 0, + "Backward", + "Apply the filter in the backward direction"}, + {0, NULL, 0, NULL, NULL}, + }; + + /* RNA. */ + RNA_def_enum(ot->srna, + "filter_type", + prop_ipmask_filter_types, + IPMASK_FILTER_GROW_SHRINK, + "Type", + "Filter that is going to be applied to the mask"); + RNA_def_enum(ot->srna, + "direction", + prop_ipmask_filter_direction_types, + MASK_FILTER_STEP_DIRECTION_FORWARD, + "Direction", + "Direction to apply the filter step"); + RNA_def_int(ot->srna, + "iterations", + 1, + 1, + 100, + "Iterations per Step", + "Number of times that the filter is going to be applied per step", + 1, + 100); + RNA_def_boolean( + ot->srna, + "use_step_interpolation", + true, + "Step Interpolation", + "Calculate and render intermediate values between multiple full steps of the filter"); + RNA_def_float( + ot->srna, "strength", 1.0f, -10.0f, 10.0f, "Strength", "Filter strength", -10.0f, 10.0f); +} + +/******************************************************************************************/ + static float neighbor_dirty_mask(SculptSession *ss, PBVHVertexIter *vd) { int total = 0; diff --git a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c index 3fc1a7674f7..455a1d3b033 100644 --- a/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c +++ b/source/blender/editors/sculpt_paint/sculpt_filter_mesh.c @@ -174,6 +174,13 @@ void SCULPT_filter_cache_init(bContext *C, Object *ob, Sculpt *sd, const int und copy_m4_m4(ss->filter_cache->viewmat_inv, vc.rv3d->viewinv); } +static void mask_filter_delta_step_free(void *delta_step_free) +{ + MaskFilterDeltaStep *delta_step = (MaskFilterDeltaStep *)delta_step_free; + MEM_SAFE_FREE(delta_step->delta); + MEM_SAFE_FREE(delta_step->index); + MEM_SAFE_FREE(delta_step); +} void SCULPT_filter_cache_free(SculptSession *ss) { if (ss->filter_cache->cloth_sim) { @@ -182,6 +189,10 @@ void SCULPT_filter_cache_free(SculptSession *ss) if (ss->filter_cache->automasking) { SCULPT_automasking_cache_free(ss->filter_cache->automasking); } + if (ss->filter_cache->mask_delta_step) { + BLI_ghash_free(ss->filter_cache->mask_delta_step, NULL, mask_filter_delta_step_free); + } + MEM_SAFE_FREE(ss->filter_cache->mask_filter_ref); MEM_SAFE_FREE(ss->filter_cache->nodes); MEM_SAFE_FREE(ss->filter_cache->mask_update_it); MEM_SAFE_FREE(ss->filter_cache->prev_mask); @@ -271,6 +282,25 @@ static EnumPropertyItem prop_mesh_filter_orientation_items[] = { {0, NULL, 0, NULL, NULL}, }; +typedef enum eMeshFilterSphereCenterType { + MESH_FILTER_SPHERE_CENTER_AVERAGE = 1 << 0, + MESH_FILTER_SPHERE_CENTER_OBJECT = 1 << 1, +} eMeshFilterSphereCenterType; + +static EnumPropertyItem prop_mesh_filter_sphere_center_items[] = { + {MESH_FILTER_SPHERE_CENTER_AVERAGE, + "AVERAGE", + 0, + "Center of Mass", + "Use the average position of all vertices as the sphere center"}, + {MESH_FILTER_SPHERE_CENTER_OBJECT, + "OBJECT", + 0, + "Object Origin", + "Use the object origin as the sphere center"}, + {0, NULL, 0, NULL, NULL}, +}; + static bool sculpt_mesh_filter_needs_pmap(eSculptMeshFilterType filter_type) { return ELEM(filter_type, @@ -289,6 +319,7 @@ static void mesh_filter_task_cb(void *__restrict userdata, SculptThreadedTaskData *data = userdata; SculptSession *ss = data->ob->sculpt; PBVHNode *node = data->nodes[i]; + FilterCache *filter_cache = ss->filter_cache; const eSculptMeshFilterType filter_type = data->filter_type; @@ -350,13 +381,15 @@ static void mesh_filter_task_cb(void *__restrict userdata, mul_m3_v3(transform, val); sub_v3_v3v3(disp, val, orig_co); break; - case MESH_FILTER_SPHERE: - normalize_v3_v3(disp, orig_co); + case MESH_FILTER_SPHERE: { + float sphere_space_co[3]; + sub_v3_v3v3(sphere_space_co, orig_co, filter_cache->sphere_center); + normalize_v3_v3(disp, sphere_space_co); if (fade > 0.0f) { - mul_v3_v3fl(disp, disp, fade); + mul_v3_v3fl(disp, disp, filter_cache->sphere_radius * fade); } else { - mul_v3_v3fl(disp, disp, -fade); + mul_v3_v3fl(disp, disp, filter_cache->sphere_radius * -fade); } unit_m3(transform); @@ -366,12 +399,11 @@ static void mesh_filter_task_cb(void *__restrict userdata, else { scale_m3_fl(transform, 1.0f + fade); } - copy_v3_v3(val, orig_co); + copy_v3_v3(val, sphere_space_co); mul_m3_v3(transform, val); - sub_v3_v3v3(disp2, val, orig_co); - + sub_v3_v3v3(disp2, val, sphere_space_co); mid_v3_v3v3(disp, disp, disp2); - break; + } break; case MESH_FILTER_RANDOM: { normal_short_to_float_v3(normal, orig_data.no); /* Index is not unique for multires, so hash by vertex coordinates. */ @@ -492,6 +524,36 @@ static void mesh_filter_enhance_details_init_directions(SculptSession *ss) } } +static void mesh_filter_sphere_center_calculate( + SculptSession *ss, const eMeshFilterSphereCenterType sphere_center_mode) +{ + FilterCache *filter_cache = ss->filter_cache; + switch (sphere_center_mode) { + case MESH_FILTER_SPHERE_CENTER_AVERAGE: { + const int totvert = SCULPT_vertex_count_get(ss); + float center_accum[3] = {0.0f}; + for (int i = 0; i < totvert; i++) { + add_v3_v3(center_accum, SCULPT_vertex_co_get(ss, i)); + } + mul_v3_v3fl(filter_cache->sphere_center, center_accum, 1.0f / totvert); + } break; + case MESH_FILTER_SPHERE_CENTER_OBJECT: + zero_v3(filter_cache->sphere_center); + break; + } +} + +static void mesh_filter_sphere_radius_calculate(SculptSession *ss) +{ + const int totvert = SCULPT_vertex_count_get(ss); + FilterCache *filter_cache = ss->filter_cache; + float accum = 0.0f; + for (int i = 0; i < totvert; i++) { + accum += len_v3v3(filter_cache->sphere_center, SCULPT_vertex_co_get(ss, i)); + } + filter_cache->sphere_radius = accum / totvert; +} + static void mesh_filter_surface_smooth_init(SculptSession *ss, const float shape_preservation, const float current_vertex_displacement) @@ -692,7 +754,7 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent SculptCursorGeometryInfo sgi; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); } SCULPT_vertex_random_access_ensure(ss); @@ -731,6 +793,13 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent mesh_filter_enhance_details_init_directions(ss); break; } + case MESH_FILTER_SPHERE: { + const eMeshFilterSphereCenterType sphere_center_mode = RNA_enum_get(op->ptr, + "sphere_center"); + mesh_filter_sphere_center_calculate(ss, sphere_center_mode); + mesh_filter_sphere_radius_calculate(ss); + break; + } case MESH_FILTER_ERASE_DISPLACEMENT: { mesh_filter_init_limit_surface_co(ss); break; @@ -834,4 +903,11 @@ void SCULPT_OT_mesh_filter(struct wmOperatorType *ot) "How much smooth the resulting shape is, ignoring high frequency details", 0, 10); + + RNA_def_enum(ot->srna, + "sphere_center", + prop_mesh_filter_sphere_center_items, + MESH_FILTER_SPHERE_CENTER_AVERAGE, + "Sphere Center", + "Position of the center of the sphere created by the filter"); } diff --git a/source/blender/editors/sculpt_paint/sculpt_gradient.c b/source/blender/editors/sculpt_paint/sculpt_gradient.c new file mode 100644 index 00000000000..3b8d839af35 --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_gradient.c @@ -0,0 +1,253 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_math_color_blend.h" +#include "BLI_task.h" + +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph.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 "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "UI_interface.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + +static EnumPropertyItem prop_sculpt_gradient_type[] = { + {SCULPT_GRADIENT_LINEAR, "LINEAR", 0, "Linear", ""}, + {SCULPT_GRADIENT_SPHERICAL, "SPHERICAL", 0, "Spherical", ""}, + {SCULPT_GRADIENT_RADIAL, "RADIAL", 0, "Radial", ""}, + {SCULPT_GRADIENT_ANGLE, "ANGLE", 0, "Angle", ""}, + {SCULPT_GRADIENT_REFLECTED, "REFLECTED", 0, "Reflected", ""}, + {0, NULL, 0, NULL, NULL}, +}; + +static void sculpt_gradient_apply_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; + SculptGradientContext *gcontext = ss->filter_cache->gradient_context; + + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + float fade = vd.mask ? *vd.mask : 0.0f; + fade *= SCULPT_automasking_factor_get(ss->filter_cache->automasking, ss, vd.index); + if (fade == 0.0f) { + continue; + } + + float world_co[3]; + float projected_co[3]; + + /* TODO: Implement symmetry by flipping this coordinate. */ + float symm_co[3]; + copy_v3_v3(symm_co, vd.co); + + mul_v3_m4v3(world_co, data->ob->obmat, symm_co); + /* TOOD: Implement this again. */ + /* ED_view3d_project(gcontext->vc.region, world_co, projected_co); */ + + float gradient_value = 0.0f; + switch (gcontext->gradient_type) { + case SCULPT_GRADIENT_LINEAR: + + break; + case SCULPT_GRADIENT_SPHERICAL: + + break; + case SCULPT_GRADIENT_RADIAL: { + const float dist = len_v2v2(projected_co, gcontext->line_points[0]); + gradient_value = dist / gcontext->line_length; + } break; + case SCULPT_GRADIENT_ANGLE: + break; + case SCULPT_GRADIENT_REFLECTED: + + break; + } + + gradient_value = clamp_f(gradient_value, 0.0f, 1.0f); + gcontext->sculpt_gradient_apply_for_element(sd, ss, &orig_data, &vd, gradient_value, fade); + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + gcontext->sculpt_gradient_node_update(data->nodes[n]); +} + +static int sculpt_gradient_update_exec(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + SculptGradientContext *gcontext = ss->filter_cache->gradient_context; + + if (event->type != MOUSEMOVE) { + return OPERATOR_RUNNING_MODAL; + } + + gcontext->line_points[0][0] = RNA_int_get(op->ptr, "xstart"); + gcontext->line_points[0][1] = RNA_int_get(op->ptr, "ystart"); + gcontext->line_points[1][0] = RNA_int_get(op->ptr, "xend"); + gcontext->line_points[1][1] = RNA_int_get(op->ptr, "yend"); + gcontext->line_length = len_v2v2(gcontext->line_points[0], gcontext->line_points[1]); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + }; + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + + BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode); + BLI_task_parallel_range( + 0, ss->filter_cache->totnode, &data, sculpt_gradient_apply_task_cb, &settings); + + SCULPT_flush_update_step(C, ss->filter_cache->gradient_context->update_type); + + return OPERATOR_RUNNING_MODAL; +} + +static void sculpt_gradient_properties(wmOperatorType *ot) +{ + RNA_def_enum( + ot->srna, "type", prop_sculpt_gradient_type, SCULPT_GRADIENT_LINEAR, "Gradient Type", ""); +} + +static void sculpt_gradient_context_init_common(bContext *C, + wmOperator *op, + const wmEvent *event, + SculptGradientContext *gcontext) +{ + /* View Context. */ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_viewcontext_init(C, &gcontext->vc, depsgraph); + + /* Properties */ + gcontext->gradient_type = RNA_enum_get(op->ptr, "type"); + gcontext->strength = RNA_float_get(op->ptr, "strength"); + + /* Symmetry. */ + Object *ob = gcontext->vc.obact; + gcontext->symm = SCULPT_mesh_symmetry_xyz_get(ob); + + /* Depth */ + SculptCursorGeometryInfo sgi; + float mouse[2] = {event->mval[0], event->mval[1]}; + const bool hit = SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); + if (hit) { + copy_v3_v3(gcontext->depth_point, sgi.location); + } + else { + zero_v3(gcontext->depth_point); + } +} + +static SculptGradientContext *sculpt_mask_gradient_context_create(Object *ob, wmOperator *op) +{ + SculptGradientContext *gradient_context = MEM_callocN(sizeof(SculptGradientContext), + "gradient context"); + gradient_context->update_type = SCULPT_UPDATE_MASK; + return gradient_context; +} + +static int sculpt_mask_gradient_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + + SCULPT_vertex_random_access_ensure(ss); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true, false); + SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_MASK); + ss->filter_cache->gradient_context = sculpt_mask_gradient_context_create(ob, op); + sculpt_gradient_context_init_common(C, op, event, ss->filter_cache->gradient_context); + + return WM_gesture_straightline_invoke(C, op, event); +} + +void SCULPT_OT_mask_gradient(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mask Gradient"; + ot->idname = "SCULPT_OT_mask_gradient"; + ot->description = "Creates or modifies the mask using a gradient"; + + /* api callbacks */ + /* + ot->invoke = WM_gesture_straightline_invoke; + ot->modal = WM_gesture_straightline_modal; + ot->exec = sculpt_gradient_update_exec; + + ot->poll = SCULPT_mode_poll; + */ + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* rna */ + sculpt_gradient_properties(ot); +} diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 696c3332a2b..e5aa3ffa7de 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -33,6 +33,8 @@ #include "BLI_gsqueue.h" #include "BLI_threads.h" +#include "ED_view3d.h" + #include "BKE_paint.h" #include "BKE_pbvh.h" @@ -72,6 +74,7 @@ void SCULPT_tag_update_overlays(bContext *C); typedef struct SculptCursorGeometryInfo { float location[3]; + float back_location[3]; float normal[3]; float active_vertex_co[3]; } SculptCursorGeometryInfo; @@ -80,7 +83,8 @@ bool SCULPT_stroke_get_location(struct bContext *C, float out[3], const float mo bool SCULPT_cursor_geometry_info_update(bContext *C, SculptCursorGeometryInfo *out, const float mouse[2], - bool use_sampled_normal); + bool use_sampled_normal, + bool use_back_depth); void SCULPT_geometry_preview_lines_update(bContext *C, struct SculptSession *ss, float radius); void SCULPT_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush); @@ -184,6 +188,7 @@ void SCULPT_fake_neighbors_free(struct Object *ob); /* Vertex Info. */ void SCULPT_boundary_info_ensure(Object *object); +void SCULPT_connected_components_ensure(Object *ob); /* Boundary Info needs to be initialized in order to use this function. */ bool SCULPT_vertex_is_boundary(const SculptSession *ss, const int index); @@ -202,6 +207,7 @@ void SCULPT_visibility_sync_all_vertex_to_face_sets(struct SculptSession *ss); int SCULPT_active_face_set_get(SculptSession *ss); int SCULPT_vertex_face_set_get(SculptSession *ss, int index); void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_set); +void SCULPT_vertex_face_set_increase(SculptSession *ss, int index, const int increase); bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set); bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index); @@ -470,6 +476,11 @@ BLI_INLINE bool SCULPT_tool_needs_all_pbvh_nodes(const Brush *brush) return true; } + if (brush->sculpt_tool == SCULPT_TOOL_ARRAY) { + /* Array Brush updates and modifies the entire mesh. */ + return true; + } + if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) { /* Boundary needs all nodes because it is not possible to know where the boundary * deformation is going to be propagated before calculating it. */ @@ -529,6 +540,13 @@ void SCULPT_boundary_edges_preview_draw(const uint gpuattr, const float outline_alpha); void SCULPT_boundary_pivot_line_preview_draw(const uint gpuattr, struct SculptSession *ss); +/* Array Brush. */ +void SCULPT_do_array_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); +void SCULPT_array_datalayers_free(Object *ob); +void SCULPT_array_path_draw(const uint gpuattr, + Brush *brush, + SculptSession *ss); + /* Multi-plane Scrape Brush. */ void SCULPT_do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); void SCULPT_multiplane_scrape_preview_draw(const uint gpuattr, @@ -580,6 +598,12 @@ void SCULPT_surface_smooth_displace_step(SculptSession *ss, const float fade); void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); +/* Directional Smooth Brush. */ +void SCULPT_do_directional_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); + +/* Uniform Weights Smooth Brush. */ +void SCULPT_do_uniform_weights_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); + /* Slide/Relax */ void SCULPT_relax_vertex(struct SculptSession *ss, struct PBVHVertexIter *vd, @@ -587,6 +611,9 @@ void SCULPT_relax_vertex(struct SculptSession *ss, bool filter_boundary_face_sets, float *r_final_pos); +/* Symmetrize Map. */ +void SCULPT_do_symmetrize_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode); + /* Undo */ typedef enum { @@ -689,6 +716,7 @@ typedef struct SculptThreadedTaskData { struct bContext *C; struct Sculpt *sd; struct Object *ob; + struct SculptSession *ss; const struct Brush *brush; struct PBVHNode **nodes; int totnode; @@ -723,6 +751,10 @@ typedef struct SculptThreadedTaskData { float (*mat)[4]; float (*vertCos)[3]; + /* When true, the displacement stored in the proxies will be aplied to the original coordinates + * instead of to the current coordinates. */ + bool use_proxies_orco; + /* X and Z vectors aligned to the stroke direction for operations where perpendicular vectors to * the stroke direction are needed. */ float (*stroke_xz)[3]; @@ -745,6 +777,9 @@ typedef struct SculptThreadedTaskData { float *wet_mix_sampled_color; float *prev_mask; + float *new_mask; + float *next_mask; + float mask_interpolation; float *pose_factor; float *pose_initial_co; @@ -766,6 +801,9 @@ typedef struct SculptThreadedTaskData { bool mask_expand_create_face_set; float transform_mats[8][4][4]; + float elastic_transform_mat[4][4]; + float elastic_transform_pivot[3]; + float elastic_transform_radius; /* Boundary brush */ float boundary_deform_strength; @@ -853,6 +891,8 @@ bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]); bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v); bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v); +void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob); + SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss, SculptBrushTest *test, char falloff_shape); @@ -950,7 +990,7 @@ typedef struct StrokeCache { /* Multires Displacement Smear. */ float (*prev_displacement)[3]; float (*limit_surface_co)[3]; - + /* The rest is temporary storage that isn't saved as a property */ bool first_time; /* Beginning of stroke may do some things special */ @@ -971,6 +1011,9 @@ typedef struct StrokeCache { bool is_rake_rotation_valid; struct SculptRakeData rake_data; + /* Geodesic distances. */ + float *geodesic_dists[PAINT_SYMM_AREAS]; + /* Face Sets */ int paint_face_set; @@ -981,6 +1024,9 @@ typedef struct StrokeCache { float true_view_normal[3]; float view_normal[3]; + float view_origin[3]; + float true_view_origin[3]; + /* sculpt_normal gets calculated by calc_sculpt_normal(), then the * sculpt_normal_symm gets updated quickly with the usual symmetry * transforms */ @@ -1001,6 +1047,11 @@ typedef struct StrokeCache { bool original; float anchored_location[3]; + /* Fairing. */ + bool *fairing_mask; + float *fairing_fade; + float (*prefairing_co)[3]; + /* Paint Brush. */ struct { float hardness; @@ -1048,6 +1099,10 @@ typedef struct StrokeCache { int saved_smooth_size; /* smooth tool copies the size of the current tool */ bool alt_smooth; + /* Scene Project Brush */ + struct SnapObjectContext *snap_context; + struct Depsgraph *depsgraph; + float plane_trim_squared; bool supports_gravity; @@ -1097,6 +1152,7 @@ typedef enum eSculptExpandFalloffType { SCULPT_EXPAND_FALLOFF_BOUNDARY_TOPOLOGY, SCULPT_EXPAND_FALLOFF_BOUNDARY_FACE_SET, SCULPT_EXPAND_FALLOFF_ACTIVE_FACE_SET, + SCULPT_EXPAND_FALLOFF_POLY_LOOP, } eSculptExpandFalloffType; typedef enum eSculptExpandTargetType { @@ -1236,6 +1292,55 @@ typedef struct ExpandCache { float (*original_colors)[4]; } ExpandCache; +typedef enum eSculptGradientType { + SCULPT_GRADIENT_LINEAR, + SCULPT_GRADIENT_SPHERICAL, + SCULPT_GRADIENT_RADIAL, + SCULPT_GRADIENT_ANGLE, + SCULPT_GRADIENT_REFLECTED, +} eSculptGradientType; +typedef struct SculptGradientContext { + + eSculptGradientType gradient_type; + ViewContext vc; + + int symm; + + int update_type; + float line_points[2][2]; + + float line_length; + + float depth_point[3]; + + float gradient_plane[4]; + float initial_location[3]; + + float gradient_line[3]; + float initial_projected_location[2]; + + float strength; + void (*sculpt_gradient_begin)(struct bContext *); + + void (*sculpt_gradient_apply_for_element)(struct Sculpt *, + struct SculptSession *, + SculptOrigVertData *orig_data, + PBVHVertexIter *vd, + float gradient_value, + float fade_value); + void (*sculpt_gradient_node_update)(struct PBVHNode *); + void (*sculpt_gradient_end)(struct bContext *); +} SculptGradientContext; + +/* IPMask filter vertex callback function. */ +typedef float(SculptIPMaskFilterStepVertexCB)(struct SculptSession *, int, float *); + +typedef struct MaskFilterDeltaStep { + int totelem; + int *index; + float *delta; +} MaskFilterDeltaStep; + typedef struct FilterCache { bool enabled_axis[3]; bool enabled_force_axis[3]; @@ -1257,6 +1362,10 @@ typedef struct FilterCache { float *sharpen_factor; float (*detail_directions)[3]; + /* Sphere mesh filter. */ + float sphere_center[3]; + float sphere_radius; + /* Filter orientation. */ SculptFilterOrientation orientation; float obmat[4][4]; @@ -1292,8 +1401,20 @@ typedef struct FilterCache { /* Transform. */ SculptTransformDisplacementMode transform_displacement_mode; + /* Gradient. */ + SculptGradientContext *gradient_context; + /* Auto-masking. */ AutomaskingCache *automasking; + + /* Mask Filter. */ + int mask_filter_current_step; + float *mask_filter_ref; + SculptIPMaskFilterStepVertexCB *mask_filter_step_forward; + SculptIPMaskFilterStepVertexCB *mask_filter_step_backward; + + GHash *mask_delta_step; + } FilterCache; void SCULPT_cache_calc_brushdata_symm(StrokeCache *cache, @@ -1318,8 +1439,15 @@ bool SCULPT_get_redraw_rect(struct ARegion *region, Object *ob, rcti *rect); +/* Poly Loops. */ +int sculpt_poly_loop_initial_edge_from_cursor(Object *ob); +BLI_bitmap *sculpt_poly_loop_from_cursor(struct Object *ob); + /* Operators. */ +/* Face Set by Topology. */ +void SCULPT_OT_face_set_by_topology(struct wmOperatorType *ot); + /* Expand. */ void SCULPT_OT_expand(struct wmOperatorType *ot); void sculpt_expand_modal_keymap(struct wmKeyConfig *keyconf); @@ -1332,6 +1460,8 @@ void SCULPT_OT_trim_lasso_gesture(struct wmOperatorType *ot); void SCULPT_OT_trim_box_gesture(struct wmOperatorType *ot); void SCULPT_OT_project_line_gesture(struct wmOperatorType *ot); +void SCULPT_OT_project_lasso_gesture(struct wmOperatorType *ot); +void SCULPT_OT_project_box_gesture(struct wmOperatorType *ot); /* Face Sets. */ void SCULPT_OT_face_sets_randomize_colors(struct wmOperatorType *ot); @@ -1356,6 +1486,8 @@ void SCULPT_OT_color_filter(struct wmOperatorType *ot); void SCULPT_OT_mask_filter(struct wmOperatorType *ot); void SCULPT_OT_dirty_mask(struct wmOperatorType *ot); +void SCULPT_OT_ipmask_filter(struct wmOperatorType *ot); + /* Mask and Face Sets Expand. */ void SCULPT_OT_mask_expand(struct wmOperatorType *ot); diff --git a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c index 9b06b2ee5d5..7f91c5dbc6e 100644 --- a/source/blender/editors/sculpt_paint/sculpt_mask_expand.c +++ b/source/blender/editors/sculpt_paint/sculpt_mask_expand.c @@ -186,7 +186,7 @@ static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent * float mouse[2]; mouse[0] = event->mval[0]; mouse[1] = event->mval[1]; - if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false)) { + if (SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false)) { /* The cursor is over the mesh, get the update iteration from the updated active vertex. */ mask_expand_update_it = ss->filter_cache->mask_update_it[(int)SCULPT_active_vertex_get(ss)]; } @@ -365,7 +365,7 @@ static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent op->customdata = MEM_mallocN(sizeof(float[2]), "initial mouse position"); copy_v2_v2(op->customdata, mouse); - SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false); + SCULPT_cursor_geometry_info_update(C, &sgi, mouse, false, false); BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); diff --git a/source/blender/editors/sculpt_paint/sculpt_poly_loop.c b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c new file mode 100644 index 00000000000..461834eff0e --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_poly_loop.c @@ -0,0 +1,309 @@ +/* + * 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) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_hash.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_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_customdata.h" +#include "BKE_mesh.h" +#include "BKE_mesh_fair.h" +#include "BKE_mesh_mapping.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_sculpt.h" +#include "ED_view3d.h" +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + +static void sculpt_poly_loop_topology_data_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + + if (!ss->epmap) { + BKE_mesh_edge_poly_map_create(&ss->epmap, + &ss->epmap_mem, + mesh->medge, + mesh->totedge, + mesh->mpoly, + mesh->totpoly, + mesh->mloop, + mesh->totloop); + } + if (!ss->vemap) { + BKE_mesh_vert_edge_map_create( + &ss->vemap, &ss->vemap_mem, mesh->medge, mesh->totvert, mesh->totedge); + } +} + +#define SCULPT_FACE_SET_LOOP_STEP_NONE -1 +static bool sculpt_poly_loop_step(SculptSession *ss, + const int from_poly, + const int edge, + int *r_next_poly) +{ + if (!ss->epmap) { + return false; + } + + int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE; + for (int i = 0; i < ss->epmap[edge].count; i++) { + if (ss->epmap[edge].indices[i] != from_poly) { + next_poly = ss->epmap[edge].indices[i]; + } + } + + if (next_poly == SCULPT_FACE_SET_LOOP_STEP_NONE) { + return false; + } + + *r_next_poly = next_poly; + return true; +} + +static int sculpt_poly_loop_opposite_edge_in_quad(SculptSession *ss, + const int poly, + const int edge) +{ + if (ss->mpoly[poly].totloop != 4) { + return edge; + } + + int edge_index_in_poly = 0; + for (int i = 0; i < ss->mpoly[poly].totloop; i++) { + if (edge == ss->mloop[ss->mpoly[poly].loopstart + i].e) { + edge_index_in_poly = i; + break; + } + } + + const int next_edge_index_in_poly = (edge_index_in_poly + 2) % 4; + return ss->mloop[ss->mpoly[poly].loopstart + next_edge_index_in_poly].e; +} + +int sculpt_poly_loop_initial_edge_from_cursor(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + + sculpt_poly_loop_topology_data_ensure(ob); + + float *location = ss->cursor_location; + + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + MPoly *initial_poly = &mesh->mpoly[ss->active_face_index]; + + if (initial_poly->totloop != 4) { + return 0; + } + + int closest_vert = mesh->mloop[initial_poly->loopstart].v; + for (int i = 0; i < initial_poly->totloop; i++) { + if (len_squared_v3v3(mvert[ss->mloop[initial_poly->loopstart + i].v].co, location) < + len_squared_v3v3(mvert[closest_vert].co, location)) { + closest_vert = ss->mloop[initial_poly->loopstart + i].v; + } + } + + int initial_edge = ss->vemap[closest_vert].indices[0]; + int closest_vert_on_initial_edge = mesh->medge[initial_edge].v1 == closest_vert ? + mesh->medge[initial_edge].v2 : + mesh->medge[initial_edge].v1; + for (int i = 0; i < ss->vemap[closest_vert].count; i++) { + const int edge_index = ss->vemap[closest_vert].indices[i]; + const int other_vert = mesh->medge[edge_index].v1 == closest_vert ? + mesh->medge[edge_index].v2 : + mesh->medge[edge_index].v1; + if (dist_to_line_segment_v3(location, mvert[closest_vert].co, mvert[other_vert].co) < + dist_to_line_segment_v3( + location, mvert[closest_vert].co, mvert[closest_vert_on_initial_edge].co)) { + initial_edge = edge_index; + closest_vert_on_initial_edge = other_vert; + } + } + return initial_edge; +} + +static void sculpt_poly_loop_iterate_and_fill(SculptSession *ss, + const int initial_poly, + const int initial_edge, + BLI_bitmap *poly_loop) +{ + int current_poly = initial_poly; + int current_edge = initial_edge; + int next_poly = SCULPT_FACE_SET_LOOP_STEP_NONE; + int max_steps = ss->totfaces; + + BLI_BITMAP_ENABLE(poly_loop, initial_poly); + + while (max_steps && sculpt_poly_loop_step(ss, current_poly, current_edge, &next_poly)) { + if (ss->face_sets[next_poly] == initial_poly) { + break; + } + if (ss->face_sets[next_poly] < 0) { + break; + } + if (ss->mpoly[next_poly].totloop != 4) { + break; + } + + BLI_BITMAP_ENABLE(poly_loop, next_poly); + current_edge = sculpt_poly_loop_opposite_edge_in_quad(ss, next_poly, current_edge); + current_poly = next_poly; + max_steps--; + } +} + + +static void sculpt_poly_loop_symm_poly_find(Object *ob, const int poly_index, const int edge_index, const char symm_it, int *r_poly_index, int *r_edge_index) { + if (symm_it == 0) { + *r_poly_index = poly_index; + *r_edge_index = edge_index; + return; + } + + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + MVert *mvert = SCULPT_mesh_deformed_mverts_get(ss); + + MPoly *original_poly = &mesh->mpoly[poly_index]; + float original_poly_center[3]; + BKE_mesh_calc_poly_center(original_poly, &mesh->mloop[original_poly->loopstart], mvert, original_poly_center); + + float symm_poly_center[3]; + flip_v3_v3(symm_poly_center, original_poly_center, symm_it); + + float min_poly_dist = FLT_MAX; + int search_poly_index = poly_index; + + + for (int i = 0; i < mesh->totpoly; i++) { + MPoly *poly = &mesh->mpoly[i]; + float poly_center[3]; + BKE_mesh_calc_poly_center(poly, &mesh->mloop[poly->loopstart], mvert, poly_center); + const float dist_to_poly_squared = len_squared_v3v3(symm_poly_center, poly_center); + if (dist_to_poly_squared < min_poly_dist) { + min_poly_dist = dist_to_poly_squared; + search_poly_index = i; + } + } + + *r_poly_index = search_poly_index; + MPoly *search_poly = &mesh->mpoly[search_poly_index]; + + + float original_edge_center[3]; + MEdge *original_edge = &mesh->medge[edge_index]; + mid_v3_v3v3(original_edge_center, mvert[original_edge->v1].co, mvert[original_edge->v2].co); + + float symm_edge_center[3]; + flip_v3_v3(symm_edge_center, original_edge_center, symm_it); + + float min_edge_dist = FLT_MAX; + int search_edge_index = edge_index; + + for (int i = 0; i < search_poly->totloop; i++) { + MLoop *loop = &mesh->mloop[search_poly->loopstart + i]; + MEdge *edge = &mesh->medge[loop->e]; + float edge_center[3]; + mid_v3_v3v3(edge_center, mvert[edge->v1].co, mvert[edge->v2].co); + const float dist_to_edge_squared = len_squared_v3v3(symm_edge_center, edge_center); + if (dist_to_edge_squared < min_edge_dist) { + min_edge_dist = dist_to_edge_squared; + search_edge_index = loop->e; + } + + *r_edge_index = search_edge_index; + } + + + +} + +BLI_bitmap *sculpt_poly_loop_from_cursor(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *mesh = BKE_object_get_original_mesh(ob); + BLI_bitmap *poly_loop = BLI_BITMAP_NEW(mesh->totpoly, "poly loop"); + + sculpt_poly_loop_topology_data_ensure(ob); + const int initial_edge = sculpt_poly_loop_initial_edge_from_cursor(ob); + const int initial_poly = ss->active_face_index; + + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + for (char symm_it = 0; symm_it <= symm; symm_it++) { + if (!SCULPT_is_symmetry_iteration_valid(symm_it, symm)) { + continue; + } + + int initial_poly_symm; + int initial_edge_symm; + sculpt_poly_loop_symm_poly_find(ob, initial_poly, initial_edge, symm_it, &initial_poly_symm, &initial_edge_symm); + + const int initial_edge_opposite = sculpt_poly_loop_opposite_edge_in_quad( + ss, initial_poly_symm, initial_edge_symm); + + sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_symm, poly_loop); + sculpt_poly_loop_iterate_and_fill(ss, initial_poly_symm, initial_edge_opposite, poly_loop); + + } + + return poly_loop; +} diff --git a/source/blender/editors/sculpt_paint/sculpt_pose.c b/source/blender/editors/sculpt_paint/sculpt_pose.c index 587ce346428..3b2193c5beb 100644 --- a/source/blender/editors/sculpt_paint/sculpt_pose.c +++ b/source/blender/editors/sculpt_paint/sculpt_pose.c @@ -157,6 +157,75 @@ static void pose_solve_scale_chain(SculptPoseIKChain *ik_chain, const float scal } } +static void do_pose_brush_bend_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain; + SculptPoseIKChainSegment *segments = ik_chain->segments; + const Brush *brush = data->brush; + + if (fabsf(ik_chain->bend_factor) <= 0.00001f) { + return; + } + + float final_pos[3]; + + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + + const float ik_chain_weight = segments[0].weights[vd.index] * + (1.0f - SCULPT_vertex_mask_get(ss, vd.index)); + if (ik_chain_weight == 0.0f) { + continue; + } + + float orig_co[3]; + mul_v3_m4v3(orig_co, ik_chain->bend_mat_inv, orig_data.co); + + const float bend_factor = ik_chain->bend_factor; + + if (fabsf(bend_factor) <= 0.0000001f) { + continue; + } + + if (orig_co[0] < 0.0f) { + continue; + } + + const float theta = orig_co[0] * bend_factor; + const float sint = sinf(theta); + const float cost = cosf(theta); + + float new_co[3]; + new_co[0] = -(orig_co[1] - 1.0f / bend_factor) * sint; + new_co[1] = (orig_co[1] - 1.0f / bend_factor) * cost + 1.0f / bend_factor; + new_co[2] = orig_co[2]; + + float final_co[3]; + float disp[3]; + mul_v3_m4v3(final_co, ik_chain->bend_mat, new_co); + + sub_v3_v3v3(disp, final_co, orig_data.co); + mul_v3_fl(disp, ik_chain_weight); + add_v3_v3v3(final_pos, orig_data.co, disp); + + float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd); + copy_v3_v3(target_co, final_pos); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + static void do_pose_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) @@ -665,6 +734,7 @@ static int pose_brush_num_effective_segments(const Brush *brush) * artifacts in the areas affected by multiple segments. */ if (ELEM(brush->pose_deform_type, BRUSH_POSE_DEFORM_SCALE_TRASLATE, + BRUSH_POSE_DEFORM_BEND, BRUSH_POSE_DEFORM_SQUASH_STRETCH)) { return 1; } @@ -1109,6 +1179,59 @@ static void sculpt_pose_do_squash_stretch_deform(SculptSession *ss, Brush *UNUSE pose_solve_scale_chain(ik_chain, scale); } +static void sculpt_pose_do_bend_deform(SculptSession *ss, Brush *UNUSED(brush)) +{ + const int totvert = SCULPT_vertex_count_get(ss); + SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain; + + if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(ss->cache)) { + sub_v3_v3v3(ik_chain->bend_mat[0], + ik_chain->segments[0].initial_head, + ik_chain->segments[0].initial_orig); + normalize_v3(ik_chain->bend_mat[0]); + copy_v3_v3(ik_chain->bend_mat[2], ss->cache->view_normal); + normalize_v3(ik_chain->bend_mat[2]); + cross_v3_v3v3(ik_chain->bend_mat[1], ik_chain->bend_mat[0], ik_chain->bend_mat[2]); + normalize_v3(ik_chain->bend_mat[1]); + copy_v3_v3(ik_chain->bend_mat[3], ik_chain->segments[0].initial_orig); + ik_chain->bend_mat[3][3] = 1.0f; + invert_m4_m4(ik_chain->bend_mat_inv, ik_chain->bend_mat); + + float lower = FLT_MAX; + float upper = -FLT_MAX; + + float smd_limit[2]; + + for (int i = 0; i < totvert; i++) { + if (ik_chain->segments[0].weights[i] == 0.0f) { + continue; + } + float bend_space_vert_co[3]; + mul_v3_m4v3(bend_space_vert_co, ik_chain->bend_mat_inv, SCULPT_vertex_co_get(ss, i)); + lower = min_ff(lower, bend_space_vert_co[0]); + upper = max_ff(upper, bend_space_vert_co[0]); + } + + ik_chain->bend_upper_limit = upper; + smd_limit[1] = lower + (upper - lower) * 1.0f; + smd_limit[0] = lower + (upper - lower) * 0.0f; + ik_chain->bend_limit = max_ff(FLT_EPSILON, smd_limit[1] - smd_limit[0]); + } + + float *original_dir = ik_chain->bend_mat[0]; + float current_dir[3]; + float brush_location[3]; + add_v3_v3v3(brush_location, ss->cache->initial_location, ss->cache->grab_delta); + sub_v3_v3v3(current_dir, brush_location, ik_chain->segments[0].initial_orig); + ik_chain->bend_factor = angle_signed_on_axis_v3v3_v3( + original_dir, current_dir, ss->cache->view_normal); + if (ik_chain->bend_factor > M_PI) { + ik_chain->bend_factor = ik_chain->bend_factor - (M_PI * 2.0f); + } + + ik_chain->bend_factor = 2.0f * (ik_chain->bend_factor / ik_chain->bend_limit); +} + static void sculpt_pose_align_pivot_local_space(float r_mat[4][4], ePaintSymmetryFlags symm, ePaintSymmetryAreas symm_area, @@ -1157,6 +1280,9 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) case BRUSH_POSE_DEFORM_SQUASH_STRETCH: sculpt_pose_do_squash_stretch_deform(ss, brush); break; + case BRUSH_POSE_DEFORM_BEND: + sculpt_pose_do_bend_deform(ss, brush); + break; } /* Flip the segment chain in all symmetry axis and calculate the transform matrices for each @@ -1227,7 +1353,13 @@ void SCULPT_do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) TaskParallelSettings settings; BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings); + + if (brush->pose_deform_type == BRUSH_POSE_DEFORM_BEND) { + BLI_task_parallel_range(0, totnode, &data, do_pose_brush_bend_task_cb_ex, &settings); + } + else { + BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings); + } } void SCULPT_pose_ik_chain_free(SculptPoseIKChain *ik_chain) diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c index 38165b7622f..4006359e0e4 100644 --- a/source/blender/editors/sculpt_paint/sculpt_smooth.c +++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c @@ -560,3 +560,192 @@ void SCULPT_do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in 0, totnode, &data, SCULPT_do_surface_smooth_brush_displace_task_cb_ex, &settings); } } + +static void SCULPT_do_directional_smooth_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = 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 stroke_disp[3]; + sub_v3_v3v3(stroke_disp, ss->cache->location, ss->cache->last_location); + normalize_v3(stroke_disp); + + float avg[3] = {0.0f, 0.0f, 0.0f}; + int neighbor_count = 0; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + float vertex_neighbor_disp[3]; + const float *neighbor_co = SCULPT_vertex_co_get(ss, ni.index); + sub_v3_v3v3(vertex_neighbor_disp, neighbor_co, vd.co); + normalize_v3(vertex_neighbor_disp); + if (fabsf(dot_v3v3(stroke_disp, vertex_neighbor_disp)) > 0.6f) { + neighbor_count++; + add_v3_v3(avg, neighbor_co); + } + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + /* Avoid division by 0 when there are no neighbors. */ + if (neighbor_count == 0) { + continue; + } + + float smooth_co[3]; + mul_v3_v3fl(smooth_co, avg, 1.0f / neighbor_count); + + float final_disp[3]; + sub_v3_v3v3(final_disp, smooth_co, vd.co); + madd_v3_v3v3fl(final_disp, vd.co, final_disp, fade); + SCULPT_clip(data->sd, ss, vd.co, final_disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + BKE_pbvh_vertex_iter_end; + } +} + +void SCULPT_do_directional_smooth_brush(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); + for (int i = 0; i < brush->surface_smooth_iterations; i++) { + BLI_task_parallel_range(0, totnode, &data, SCULPT_do_directional_smooth_task_cb_ex, &settings); + } +} + +static void SCULPT_do_uniform_weigths_smooth_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + const Brush *brush = data->brush; + const float bstrength = ss->cache->bstrength; + + PBVHVertexIter vd; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, data->brush->falloff_shape); + const int thread_id = BLI_task_parallel_thread_id(tls); + + BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { + if (!sculpt_brush_test_sq_fn(&test, vd.co)) { + continue; + } + const float fade = 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 len_accum = 0; + int tot_neighbors = 0; + + SculptVertexNeighborIter ni; + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + len_accum += len_v3v3(SCULPT_vertex_co_get(ss, vd.index), SCULPT_vertex_co_get(ss, ni.index)); + tot_neighbors++; + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + /* Avoid division by 0 when there are no neighbors. */ + if (tot_neighbors == 0) { + continue; + } + + const float len_avg = bstrength * len_accum / tot_neighbors; + + + float co_accum[3] = {0.0f}; + + SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd.index, ni) { + const float neighbor_co[3]; + const float neighbor_disp[3]; + sub_v3_v3v3(neighbor_disp, SCULPT_vertex_co_get(ss, ni.index), SCULPT_vertex_co_get(ss, vd.index)); + normalize_v3(neighbor_disp); + mul_v3_fl(neighbor_disp, len_avg); + add_v3_v3v3(neighbor_co, SCULPT_vertex_co_get(ss, vd.index), neighbor_disp); + add_v3_v3(co_accum, neighbor_co); + } + SCULPT_VERTEX_NEIGHBORS_ITER_END(ni); + + float smooth_co[3]; + mul_v3_v3fl(smooth_co, co_accum, 1.0f / tot_neighbors); + + float final_disp[3]; + sub_v3_v3v3(final_disp, smooth_co, vd.co); + madd_v3_v3v3fl(final_disp, vd.co, final_disp, fade); + SCULPT_clip(data->sd, ss, vd.co, final_disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + BKE_pbvh_vertex_iter_end; + } +} + + + +void SCULPT_do_uniform_weights_smooth_brush(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); + for (int i = 0; i < brush->surface_smooth_iterations; i++) { + BLI_task_parallel_range(0, totnode, &data, SCULPT_do_uniform_weigths_smooth_task_cb_ex, &settings); + } +}
\ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/sculpt_symmetrize.c b/source/blender/editors/sculpt_paint/sculpt_symmetrize.c new file mode 100644 index 00000000000..ea0178e0edb --- /dev/null +++ b/source/blender/editors/sculpt_paint/sculpt_symmetrize.c @@ -0,0 +1,302 @@ +/* + * 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) 2021 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edsculpt + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_task.h" + +#include "DNA_brush_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "BKE_brush.h" +#include "BKE_ccg.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_mesh.h" +#include "BKE_multires.h" +#include "BKE_node.h" +#include "BKE_object.h" +#include "BKE_paint.h" +#include "BKE_pbvh.h" +#include "BKE_scene.h" + +#include "paint_intern.h" +#include "sculpt_intern.h" + +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "bmesh.h" + +#include <math.h> +#include <stdlib.h> + +typedef uint MirrTopoHash_t; + +typedef struct MirrTopoVert_t { + MirrTopoHash_t hash; + int v_index; +} MirrTopoVert_t; + +static int mirrtopo_hash_sort(const void *l1, const void *l2) +{ + if ((MirrTopoHash_t)(intptr_t)l1 > (MirrTopoHash_t)(intptr_t)l2) { + return 1; + } + if ((MirrTopoHash_t)(intptr_t)l1 < (MirrTopoHash_t)(intptr_t)l2) { + return -1; + } + return 0; +} + +static int mirrtopo_vert_sort(const void *v1, const void *v2) +{ + if (((MirrTopoVert_t *)v1)->hash > ((MirrTopoVert_t *)v2)->hash) { + return 1; + } + if (((MirrTopoVert_t *)v1)->hash < ((MirrTopoVert_t *)v2)->hash) { + return -1; + } + return 0; +} + +void SCULPT_symmetrize_map_ensure(Object *ob) +{ + SculptSession *ss = ob->sculpt; + Mesh *me = BKE_object_get_original_mesh(ob); + + + if (ss->vertex_info.symmetrize_map) { + /* Nothing to do. */ + return; + } + + MEdge *medge = NULL, *med; + + int a, last; + int totvert, totedge; + int tot_unique = -1, tot_unique_prev = -1; + int tot_unique_edges = 0, tot_unique_edges_prev; + + MirrTopoHash_t *topo_hash = NULL; + MirrTopoHash_t *topo_hash_prev = NULL; + MirrTopoVert_t *topo_pairs; + MirrTopoHash_t topo_pass = 1; + + int *index_lookup; /* direct access to mesh_topo_store->index_lookup */ + + totvert = me->totvert; + topo_hash = MEM_callocN(totvert * sizeof(MirrTopoHash_t), "TopoMirr"); + + /* Initialize the vert-edge-user counts used to detect unique topology */ + totedge = me->totedge; + medge = me->medge; + + for (a = 0, med = medge; a < totedge; a++, med++) { + const uint i1 = med->v1, i2 = med->v2; + topo_hash[i1]++; + topo_hash[i2]++; + } + + topo_hash_prev = MEM_dupallocN(topo_hash); + + tot_unique_prev = -1; + tot_unique_edges_prev = -1; + while (true) { + /* use the number of edges per vert to give verts unique topology IDs */ + + tot_unique_edges = 0; + + /* This can make really big numbers, wrapping around here is fine */ + for (a = 0, med = medge; a < totedge; a++, med++) { + const uint i1 = med->v1, i2 = med->v2; + topo_hash[i1] += topo_hash_prev[i2] * topo_pass; + topo_hash[i2] += topo_hash_prev[i1] * topo_pass; + tot_unique_edges += (topo_hash[i1] != topo_hash[i2]); + } + memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert); + + /* sort so we can count unique values */ + qsort(topo_hash_prev, totvert, sizeof(MirrTopoHash_t), mirrtopo_hash_sort); + + tot_unique = 1; /* account for skipping the first value */ + for (a = 1; a < totvert; a++) { + if (topo_hash_prev[a - 1] != topo_hash_prev[a]) { + tot_unique++; + } + } + + if ((tot_unique <= tot_unique_prev) && (tot_unique_edges <= tot_unique_edges_prev)) { + /* Finish searching for unique values when 1 loop doesn't give a + * higher number of unique values compared to the previous loop. */ + break; + } + tot_unique_prev = tot_unique; + tot_unique_edges_prev = tot_unique_edges; + /* Copy the hash calculated this iteration, so we can use them next time */ + memcpy(topo_hash_prev, topo_hash, sizeof(MirrTopoHash_t) * totvert); + + topo_pass++; + } + + /* Hash/Index pairs are needed for sorting to find index pairs */ + topo_pairs = MEM_callocN(sizeof(MirrTopoVert_t) * totvert, "MirrTopoPairs"); + + /* since we are looping through verts, initialize these values here too */ + index_lookup = MEM_mallocN(totvert * sizeof(int), "mesh_topo_lookup"); + + for (a = 0; a < totvert; a++) { + topo_pairs[a].hash = topo_hash[a]; + topo_pairs[a].v_index = a; + + /* initialize lookup */ + index_lookup[a] = -1; + } + + qsort(topo_pairs, totvert, sizeof(MirrTopoVert_t), mirrtopo_vert_sort); + + last = 0; + + /* Get the pairs out of the sorted hashes, note, totvert+1 means we can use the previous 2, + * but you cant ever access the last 'a' index of MirrTopoPairs */ + for (a = 1; a <= totvert; a++) { + if ((a == totvert) || (topo_pairs[a - 1].hash != topo_pairs[a].hash)) { + const int match_count = a - last; + if (match_count == 2) { + const int j = topo_pairs[a - 1].v_index, k = topo_pairs[a - 2].v_index; + index_lookup[j] = k; + index_lookup[k] = j; + } + else if (match_count == 1) { + /* Center vertex. */ + const int j = topo_pairs[a - 1].v_index; + index_lookup[j] = j; + } + last = a; + } + } + + MEM_freeN(topo_pairs); + topo_pairs = NULL; + + MEM_freeN(topo_hash); + MEM_freeN(topo_hash_prev); + + ss->vertex_info.symmetrize_map = index_lookup; +} + + + +static void do_shape_symmetrize_brush_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; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, 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 int symmetrical_index = ss->vertex_info.symmetrize_map[vd.index]; + + if (symmetrical_index == -1) { + continue; + } + + float symm_co[3]; + copy_v3_v3(symm_co, SCULPT_vertex_co_get(ss, symmetrical_index)); + + symm_co[0] *= -1; + float new_co[3]; + copy_v3_v3(new_co, symm_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); + + + + float disp[3]; + sub_v3_v3v3(disp, new_co, vd.co); + madd_v3_v3v3fl(vd.co, vd.co, disp, fade); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + + BKE_pbvh_vertex_iter_end; + } +} + +/* Public functions. */ + +/* Main Brush Function. */ +void SCULPT_do_symmetrize_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + + + if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) { + return; + } + + if (!SCULPT_stroke_is_main_symmetry_pass(ss->cache)) { + return; + } + + SCULPT_symmetrize_map_ensure(ob); + + 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_shape_symmetrize_brush_task_cb, &settings); +}
\ No newline at end of file diff --git a/source/blender/editors/sculpt_paint/sculpt_transform.c b/source/blender/editors/sculpt_paint/sculpt_transform.c index 3c0a591e8a7..efe3b8acafe 100644 --- a/source/blender/editors/sculpt_paint/sculpt_transform.c +++ b/source/blender/editors/sculpt_paint/sculpt_transform.c @@ -32,6 +32,7 @@ #include "BKE_brush.h" #include "BKE_context.h" +#include "BKE_kelvinlet.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_object.h" @@ -73,16 +74,40 @@ void ED_sculpt_init_transform(struct bContext *C, Object *ob) copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos); copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot); copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale); - - SCULPT_undo_push_begin(ob, "Transform"); - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - ss->pivot_rot[3] = 1.0f; + SCULPT_undo_push_begin(ob, "Transform"); SCULPT_vertex_random_access_ensure(ss); SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS); - ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL; + switch (sd->transform_deform_target) { + case SCULPT_TRANSFORM_DEFORM_TARGET_GEOMETRY: + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + break; + case SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM: + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false); + ss->filter_cache->cloth_sim = SCULPT_cloth_brush_simulation_create( + ss, 1.0f, 1.0f, 0.0f, true, false); + SCULPT_cloth_brush_simulation_init(ss, ss->filter_cache->cloth_sim); + SCULPT_cloth_brush_store_simulation_state(ss, ss->filter_cache->cloth_sim); + SCULPT_cloth_brush_ensure_nodes_constraints(sd, + ob, + ss->filter_cache->nodes, + ss->filter_cache->totnode, + ss->filter_cache->cloth_sim, + ss->pivot_pos, + FLT_MAX); + + break; + } + + if (sd->transform_mode == SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC || + sd->transform_deform_target == SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM) { + ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL; + } + else { + ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL; + } } static void sculpt_transform_matrices_init(SculptSession *ss, @@ -122,13 +147,13 @@ static void sculpt_transform_matrices_init(SculptSession *ss, /* Translation matrix. */ sub_v3_v3v3(d_t, ss->pivot_pos, start_pivot_pos); - SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, ss->init_pivot_pos); + SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, start_pivot_pos); translate_m4(t_mat, d_t[0], d_t[1], d_t[2]); /* Rotation matrix. */ sub_qt_qtqt(d_r, ss->pivot_rot, start_pivot_rot); normalize_qt(d_r); - SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, ss->init_pivot_pos); + SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, start_pivot_pos); quat_to_mat4(r_mat, d_r); /* Scale matrix. */ @@ -185,7 +210,15 @@ static void sculpt_transform_task_cb(void *__restrict userdata, mul_m4_v3(data->transform_mats[(int)symm_area], transformed_co); sub_v3_v3v3(disp, transformed_co, start_co); mul_v3_fl(disp, 1.0f - fade); - add_v3_v3v3(vd.co, start_co, disp); + + switch (data->sd->transform_deform_target) { + case SCULPT_TRANSFORM_DEFORM_TARGET_GEOMETRY: + add_v3_v3v3(vd.co, start_co, disp); + break; + case SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM: + add_v3_v3v3(ss->filter_cache->cloth_sim->pos[vd.index], start_co, disp); + break; + } if (vd.mvert) { vd.mvert->flag |= ME_VERT_PBVH_UPDATE; @@ -218,6 +251,92 @@ static void sculpt_transform_all_vertices(Sculpt *sd, Object *ob) 0, ss->filter_cache->totnode, &data, sculpt_transform_task_cb, &settings); } +static void sculpt_elastic_transform_task_cb(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + PBVHNode *node = data->nodes[i]; + + float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[i])->co; + + SculptOrigVertData orig_data; + SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]); + + KelvinletParams params; + /* TODO(pablodp606): These parameters can be exposed if needed as transform strength and volume + * preservation like in the elastic deform brushes. Setting them to the same default as elastic + * deform triscale grab because they work well in most cases. */ + const float force = 1.0f; + const float shear_modulus = 1.0f; + const float poisson_ratio = 0.4f; + BKE_kelvinlet_init_params( + ¶ms, data->elastic_transform_radius, force, shear_modulus, poisson_ratio); + + SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) { + SCULPT_orig_vert_data_update(&orig_data, &vd); + float transformed_co[3], orig_co[3], disp[3]; + const float fade = vd.mask ? *vd.mask : 0.0f; + copy_v3_v3(orig_co, orig_data.co); + + copy_v3_v3(transformed_co, vd.co); + mul_m4_v3(data->elastic_transform_mat, transformed_co); + sub_v3_v3v3(disp, transformed_co, vd.co); + + float final_disp[3]; + BKE_kelvinlet_grab_triscale(final_disp, ¶ms, vd.co, data->elastic_transform_pivot, disp); + mul_v3_fl(final_disp, 20.0f * (1.0f - fade)); + + copy_v3_v3(proxy[vd.i], final_disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_mark_update(node); +} + +static void sculpt_transform_radius_elastic(Sculpt *sd, Object *ob, const float transform_radius) +{ + SculptSession *ss = ob->sculpt; + BLI_assert(ss->filter_cache->transform_displacement_mode == + SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL); + const char symm = SCULPT_mesh_symmetry_xyz_get(ob); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .elastic_transform_radius = transform_radius, + }; + + sculpt_transform_matrices_init( + ss, symm, ss->filter_cache->transform_displacement_mode, data.transform_mats); + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode); + + /* Elastic transform needs to apply all transform matrices to all vertices and then combine the + * displacement proxies as all vertices are modified by all symmetry passes. */ + for (ePaintSymmetryFlags symmpass = 0; symmpass <= symm; symmpass++) { + if (SCULPT_is_symmetry_iteration_valid(symmpass, symm)) { + flip_v3_v3(data.elastic_transform_pivot, ss->pivot_pos, symmpass); + const int symm_area = SCULPT_get_vertex_symm_area(data.elastic_transform_pivot); + copy_m4_m4(data.elastic_transform_mat, data.transform_mats[symm_area]); + BLI_task_parallel_range( + 0, ss->filter_cache->totnode, &data, sculpt_elastic_transform_task_cb, &settings); + } + } + SCULPT_combine_transform_proxies(sd, ob); +} + void ED_sculpt_update_modal_transform(struct bContext *C, Object *ob) { Sculpt *sd = CTX_data_tool_settings(C)->sculpt; @@ -227,12 +346,31 @@ void ED_sculpt_update_modal_transform(struct bContext *C, Object *ob) SCULPT_vertex_random_access_ensure(ss); BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); - sculpt_transform_all_vertices(sd, ob); + switch (sd->transform_mode) { + case SCULPT_TRANSFORM_MODE_ALL_VERTICES: { + sculpt_transform_all_vertices(sd, ob); + break; + } + case SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC: { + Brush *brush = BKE_paint_brush(&sd->paint); + Scene *scene = CTX_data_scene(C); + const float transform_radius = BKE_brush_unprojected_radius_get(scene, brush); + sculpt_transform_radius_elastic(sd, ob, transform_radius); + break; + } + } copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos); copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot); copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale); + if (sd->transform_deform_target == SCULPT_TRANSFORM_DEFORM_TARGET_CLOTH_SIM) { + SCULPT_cloth_sim_activate_nodes( + ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode); + SCULPT_cloth_brush_do_simulation_step( + sd, ob, ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode); + } + if (ss->deform_modifiers_active || ss->shapekey_active) { SCULPT_flush_stroke_deform(sd, ob, true); } |