diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint')
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_cursor.c | 46 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 897 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_intern.h | 26 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt_undo.c | 7 |
4 files changed, 967 insertions, 9 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index a000da57383..9464bdb901e 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -1180,6 +1180,20 @@ static void cursor_draw_point_with_symmetry(const uint gpuattr, } } +static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession *ss) +{ + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.6f); + GPU_depth_test(true); + GPU_line_width(1.0f); + if (ss->preview_vert_index_count > 0) { + immBegin(GPU_PRIM_LINES, ss->preview_vert_index_count); + for (int i = 0; i < ss->preview_vert_index_count; i++) { + immVertex3fv(gpuattr, sculpt_vertex_co_get(ss, ss->preview_vert_index_list[i])); + } + immEnd(); + } +} + static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) { Scene *scene = CTX_data_scene(C); @@ -1348,6 +1362,17 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) imm_draw_circle_wire_3d(pos, 0, 0, rds, 40); GPU_matrix_pop(); + /* Update and draw dynamic mesh preview lines */ + GPU_matrix_push(); + GPU_matrix_mul(vc.obact->obmat); + if (brush->sculpt_tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && ss->modifiers_active) { + sculpt_geometry_preview_lines_update(C, ss, rds); + sculpt_geometry_preview_lines_draw(pos, ss); + } + } + GPU_matrix_pop(); + GPU_matrix_pop_projection(); wmWindowViewport(win); @@ -1370,6 +1395,27 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused)) add_v3_v3(cursor_location, ss->cache->grab_delta); } cursor_draw_point_with_symmetry(pos, ar, cursor_location, sd, vc.obact, ss->cache->radius); + + /* Draw cached dynamic mesh preview lines */ + if (brush->sculpt_tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && ss->modifiers_active) { + GPU_matrix_push_projection(); + ED_view3d_draw_setup_view(CTX_wm_window(C), + CTX_data_depsgraph_pointer(C), + CTX_data_scene(C), + ar, + CTX_wm_view3d(C), + NULL, + NULL, + NULL); + GPU_matrix_push(); + GPU_matrix_mul(vc.obact->obmat); + sculpt_geometry_preview_lines_draw(pos, ss); + GPU_matrix_pop(); + GPU_matrix_pop_projection(); + } + } + wmWindowViewport(win); } } diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index e511a19b341..ab557dc59b5 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -30,8 +30,8 @@ #include "BLI_hash.h" #include "BLI_gsqueue.h" #include "BLI_stack.h" -#include "BLI_gsqueue.h" #include "BLI_task.h" +#include "BLI_stack.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -100,7 +100,7 @@ /* Do not use these functions while working with PBVH_GRIDS data in SculptSession */ -static float *sculpt_vertex_co_get(SculptSession *ss, int index) +float *sculpt_vertex_co_get(SculptSession *ss, int index) { switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: @@ -1142,6 +1142,7 @@ static bool sculpt_automasking_is_constrained_by_radius(Brush *br) typedef struct VertexTopologyIterator { int v; int it; + float edge_factor; } VertexTopologyIterator; static float *sculpt_topology_automasking_init(Sculpt *sd, Object *ob, float *automask_factor) @@ -3344,7 +3345,7 @@ static void do_elastic_deform_brush_task_cb_ex(void *__restrict userdata, radius_scaled[2] = radius_scaled[1] * radius_e[2]; float shear_modulus = 1.0f; - float poisson_ratio = brush->elastic_deform_compressibility; + float poisson_ratio = brush->elastic_deform_volume_preservation; float a = 1.0f / (4.0f * (float)M_PI * shear_modulus); float b = a / (4.0f * (1.0f - poisson_ratio)); @@ -3520,6 +3521,10 @@ static void do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) float pose_initial_co[3]; float transform_rot[4][4], transform_trans[4][4], transform_trans_inv[4][4]; + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return; + } + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); copy_v3_v3(pose_origin, ss->cache->pose_origin); @@ -5071,7 +5076,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe if (brush->sculpt_tool == SCULPT_TOOL_POSE && ss->cache->first_time && ss->cache->mirror_symmetry_pass == 0) { - sculpt_pose_brush_init(sd, ob, ss, brush); + if (BKE_pbvh_type(ss->pbvh) != PBVH_GRIDS) { + sculpt_pose_brush_init(sd, ob, ss, brush); + } } /* Apply one type of brush action */ @@ -5902,7 +5909,13 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru float grab_location[3], imat[4][4], delta[3], loc[3]; if (cache->first_time) { - copy_v3_v3(cache->orig_grab_location, cache->true_location); + if (tool == SCULPT_TOOL_GRAB && brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->orig_grab_location, + sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss))); + } + else { + copy_v3_v3(cache->orig_grab_location, cache->true_location); + } } else if (tool == SCULPT_TOOL_SNAKE_HOOK) { add_v3_v3(cache->true_location, cache->grab_delta); @@ -5955,6 +5968,14 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru copy_v3_v3(cache->old_grab_location, grab_location); if (tool == SCULPT_TOOL_GRAB) { + if (brush->flag & BRUSH_GRAB_ACTIVE_VERTEX) { + copy_v3_v3(cache->anchored_location, cache->orig_grab_location); + } + else { + copy_v3_v3(cache->anchored_location, cache->true_location); + } + } + else if (tool == SCULPT_TOOL_ELASTIC_DEFORM) { copy_v3_v3(cache->anchored_location, cache->true_location); } else if (tool == SCULPT_TOOL_ELASTIC_DEFORM) { @@ -7932,6 +7953,12 @@ static void sculpt_filter_cache_free(SculptSession *ss) if (ss->filter_cache->mask_update_it) { MEM_freeN(ss->filter_cache->mask_update_it); } + if (ss->filter_cache->prev_mask) { + MEM_freeN(ss->filter_cache->prev_mask); + } + if (ss->filter_cache->normal_factor) { + MEM_freeN(ss->filter_cache->normal_factor); + } MEM_freeN(ss->filter_cache); ss->filter_cache = NULL; } @@ -8118,6 +8145,10 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent SculptSession *ss = ob->sculpt; PBVH *pbvh = ob->sculpt->pbvh; + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return OPERATOR_CANCELLED; + } + int deform_axis = RNA_enum_get(op->ptr, "deform_axis"); if (deform_axis == 0) { return OPERATOR_CANCELLED; @@ -8321,6 +8352,10 @@ static int sculpt_mask_filter_exec(bContext *C, wmOperator *op) int totnode; int filter_type = RNA_enum_get(op->ptr, "filter_type"); + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return OPERATOR_CANCELLED; + } + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); sculpt_vertex_random_access_init(ss); @@ -8483,6 +8518,10 @@ static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op) Sculpt *sd = CTX_data_tool_settings(C)->sculpt; int totnode; + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return OPERATOR_CANCELLED; + } + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); sculpt_vertex_random_access_init(ss); @@ -8581,6 +8620,852 @@ static void SCULPT_OT_dirty_mask(struct wmOperatorType *ot) ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas"); } +static void sculpt_mask_expand_cancel(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + + MEM_freeN(op->customdata); + + int vert_count = sculpt_vertex_count_get(ss); + for (int i = 0; i < vert_count; i++) { + sculpt_vertex_mask_set(ss, i, ss->filter_cache->prev_mask[i]); + } + + for (int i = 0; i < ss->filter_cache->totnode; i++) { + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + + sculpt_flush_update_step(C); + sculpt_filter_cache_free(ss); + sculpt_undo_push_end(); + sculpt_flush_update_done(C, ob); + ED_workspace_status_text(C, NULL); +} + +static void sculpt_expand_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]; + PBVHVertexIter vd; + int update_it = data->mask_expand_update_it; + + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL) + { + int vi = vd.index; + float final_mask = *vd.mask; + if (data->mask_expand_use_normals) { + if (ss->filter_cache->normal_factor[sculpt_active_vertex_get(ss)] < + ss->filter_cache->normal_factor[vd.index]) { + final_mask = 1.0f; + } + else { + final_mask = 0.0f; + } + } + else { + if (ss->filter_cache->mask_update_it[vi] <= update_it && + ss->filter_cache->mask_update_it[vi] != 0) { + final_mask = 1.0f; + } + else { + final_mask = 0.0f; + } + } + + if (data->mask_expand_keep_prev_mask) { + final_mask = MAX2(ss->filter_cache->prev_mask[vd.index], final_mask); + } + + if (data->mask_expand_invert_mask) { + final_mask = 1.0f - final_mask; + } + + if (*vd.mask != final_mask) { + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + *vd.mask = final_mask; + BKE_pbvh_node_mark_redraw(node); + } + } + BKE_pbvh_vertex_iter_end; +} + +static int sculpt_mask_expand_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + float prevclick_f[2]; + copy_v2_v2(prevclick_f, op->customdata); + int prevclick[2] = {(int)prevclick_f[0], (int)prevclick_f[1]}; + int len = (int)len_v2v2_int(prevclick, event->mval); + len = ABS(len); + int mask_speed = RNA_int_get(op->ptr, "mask_speed"); + int mask_expand_update_it = len / mask_speed; + mask_expand_update_it = mask_expand_update_it + 1; + + if (RNA_boolean_get(op->ptr, "use_cursor")) { + SculptCursorGeometryInfo sgi; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + sculpt_cursor_geometry_info_update(C, &sgi, mouse, false); + mask_expand_update_it = ss->filter_cache->mask_update_it[(int)sculpt_active_vertex_get(ss)]; + } + + if ((event->type == ESCKEY && event->val == KM_PRESS) || + (event->type == RIGHTMOUSE && event->val == KM_PRESS)) { + sculpt_mask_expand_cancel(C, op); + return OPERATOR_CANCELLED; + } + + if ((event->type == LEFTMOUSE && event->val == KM_RELEASE) || + (event->type == RETKEY && event->val == KM_PRESS) || + (event->type == PADENTER && event->val == KM_PRESS)) { + + /* Smooth iterations */ + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .filter_type = MASK_FILTER_SMOOTH, + }; + + int smooth_iterations = RNA_int_get(op->ptr, "smooth_iterations"); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false); + for (int i = 0; i < smooth_iterations; i++) { + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && + ss->filter_cache->totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range(0, ss->filter_cache->totnode, &data, mask_filter_task_cb, &settings); + } + + /* Pivot position */ + if (RNA_boolean_get(op->ptr, "update_pivot")) { + const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + float avg[3]; + int total = 0; + float threshold = 0.2f; + zero_v3(avg); + int vertex_count = sculpt_vertex_count_get(ss); + for (int i = 0; i < vertex_count; i++) { + if (sculpt_vertex_mask_get(ss, i) < (0.5f + threshold) && + sculpt_vertex_mask_get(ss, i) > (0.5f - threshold) && + check_vertex_pivot_symmetry( + sculpt_vertex_co_get(ss, i), ss->filter_cache->mask_expand_initial_co, symm)) { + total++; + add_v3_v3(avg, sculpt_vertex_co_get(ss, i)); + } + } + if (total > 0) { + mul_v3_fl(avg, 1.0f / total); + copy_v3_v3(ss->pivot_pos, avg); + } + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); + } + + MEM_freeN(op->customdata); + + for (int i = 0; i < ss->filter_cache->totnode; i++) { + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + + sculpt_filter_cache_free(ss); + + sculpt_undo_push_end(); + sculpt_flush_update_done(C, ob); + ED_workspace_status_text(C, NULL); + return OPERATOR_FINISHED; + } + + if (event->type != MOUSEMOVE) { + return OPERATOR_RUNNING_MODAL; + } + + if (mask_expand_update_it == ss->filter_cache->mask_update_current_it) { + return OPERATOR_RUNNING_MODAL; + } + + if (mask_expand_update_it < ss->filter_cache->mask_update_last_it) { + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .mask_expand_update_it = mask_expand_update_it, + .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), + .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), + .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), + }; + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && + ss->filter_cache->totnode > SCULPT_THREADED_LIMIT); + + BLI_task_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); + ss->filter_cache->mask_update_current_it = mask_expand_update_it; + } + + sculpt_flush_update_step(C); + + return OPERATOR_RUNNING_MODAL; +} + +static int sculpt_mask_expand_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + PBVH *pbvh = ob->sculpt->pbvh; + float original_normal[3]; + + bool use_normals = RNA_boolean_get(op->ptr, "use_normals"); + int edge_sensitivity = RNA_int_get(op->ptr, "edge_sensitivity"); + + SculptCursorGeometryInfo sgi; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return OPERATOR_CANCELLED; + } + + sculpt_vertex_random_access_init(ss); + + op->customdata = MEM_mallocN(2 * sizeof(float), "initial mouse position"); + copy_v2_v2(op->customdata, mouse); + + sculpt_cursor_geometry_info_update(C, &sgi, mouse, false); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); + + int vertex_count = sculpt_vertex_count_get(ss); + + ss->filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache"); + + SculptSearchSphereData searchdata = { + .ss = ss, + .sd = sd, + .radius_squared = FLT_MAX, + }; + BKE_pbvh_search_gather(pbvh, + sculpt_search_sphere_cb, + &searchdata, + &ss->filter_cache->nodes, + &ss->filter_cache->totnode); + + sculpt_undo_push_begin("Mask Expand"); + + for (int i = 0; i < ss->filter_cache->totnode; i++) { + sculpt_undo_push_node(ob, ss->filter_cache->nodes[i], SCULPT_UNDO_MASK); + BKE_pbvh_node_mark_redraw(ss->filter_cache->nodes[i]); + } + + ss->filter_cache->mask_update_it = MEM_callocN(sizeof(int) * vertex_count, + "mask update iteration"); + if (use_normals) { + ss->filter_cache->normal_factor = MEM_callocN(sizeof(float) * vertex_count, + "mask update normal factor"); + } + + ss->filter_cache->prev_mask = MEM_callocN(sizeof(float) * vertex_count, "prev mask"); + for (int i = 0; i < vertex_count; i++) { + ss->filter_cache->prev_mask[i] = sculpt_vertex_mask_get(ss, i); + } + + ss->filter_cache->mask_update_last_it = 1; + ss->filter_cache->mask_update_current_it = 1; + ss->filter_cache->mask_update_it[(int)sculpt_active_vertex_get(ss)] = 1; + + copy_v3_v3(ss->filter_cache->mask_expand_initial_co, + sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss))); + + char *visited_vertices = MEM_callocN(vertex_count * sizeof(char), "visited vertices"); + + sculpt_vertex_normal_get(ss, sculpt_active_vertex_get(ss), original_normal); + + GSQueue *queue = BLI_gsqueue_new(sizeof(VertexTopologyIterator)); + VertexTopologyIterator mevit; + + const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + for (char i = 0; i <= symm; ++i) { + if (is_symmetry_iteration_valid(i, symm)) { + float location[3]; + flip_v3_v3(location, sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss)), i); + if (i == 0) { + mevit.v = sculpt_active_vertex_get(ss); + mevit.edge_factor = 1.0f; + } + else { + mevit.v = sculpt_nearest_vertex_get(sd, ob, location, FLT_MAX, false); + mevit.edge_factor = 1.0f; + } + if (mevit.v != -1) { + sculpt_vertex_mask_set(ss, mevit.v, 1.0f); + mevit.it = 0; + BLI_gsqueue_push(queue, &mevit); + } + } + } + + while (!BLI_gsqueue_is_empty(queue)) { + VertexTopologyIterator c_mevit; + BLI_gsqueue_pop(queue, &c_mevit); + SculptVertexNeighborIter ni; + sculpt_vertex_neighbors_iter_begin(ss, c_mevit.v, ni) + { + if (visited_vertices[(int)ni.index] == 0) { + VertexTopologyIterator new_entry; + new_entry.v = ni.index; + new_entry.it = c_mevit.it + 1; + ss->filter_cache->mask_update_it[(int)new_entry.v] = new_entry.it; + visited_vertices[(int)ni.index] = 1; + if (ss->filter_cache->mask_update_last_it < new_entry.it) { + ss->filter_cache->mask_update_last_it = new_entry.it; + } + if (use_normals) { + float current_normal[3], prev_normal[3]; + sculpt_vertex_normal_get(ss, ni.index, current_normal); + sculpt_vertex_normal_get(ss, c_mevit.v, prev_normal); + new_entry.edge_factor = dot_v3v3(current_normal, prev_normal) * c_mevit.edge_factor; + ss->filter_cache->normal_factor[ni.index] = dot_v3v3(original_normal, current_normal) * + powf(c_mevit.edge_factor, edge_sensitivity); + CLAMP(ss->filter_cache->normal_factor[ni.index], 0, 1); + } + BLI_gsqueue_push(queue, &new_entry); + } + } + sculpt_vertex_neighbors_iter_end(ni) + } + + if (use_normals) { + for (int repeat = 0; repeat < 2; repeat++) { + for (int i = 0; i < vertex_count; i++) { + float avg = 0; + SculptVertexNeighborIter ni; + sculpt_vertex_neighbors_iter_begin(ss, i, ni) + { + avg += ss->filter_cache->normal_factor[ni.index]; + } + sculpt_vertex_neighbors_iter_end(ni); + ss->filter_cache->normal_factor[i] = avg / ni.size; + } + } + } + + BLI_gsqueue_free(queue); + + MEM_freeN(visited_vertices); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + .mask_expand_update_it = 0, + .mask_expand_use_normals = RNA_boolean_get(op->ptr, "use_normals"), + .mask_expand_invert_mask = RNA_boolean_get(op->ptr, "invert"), + .mask_expand_keep_prev_mask = RNA_boolean_get(op->ptr, "keep_previous_mask"), + }; + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && + ss->filter_cache->totnode > SCULPT_THREADED_LIMIT); + + BLI_task_parallel_range(0, ss->filter_cache->totnode, &data, sculpt_expand_task_cb, &settings); + + const char *status_str = TIP_( + "Move the mouse to expand the mask from the active vertex. LBM: confirm mask, ESC/RMB: " + "cancel"); + ED_workspace_status_text(C, status_str); + + sculpt_flush_update_step(C); + WM_event_add_modal_handler(C, op); + return OPERATOR_RUNNING_MODAL; +} + +static void SCULPT_OT_mask_expand(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Mask Expand"; + ot->idname = "SCULPT_OT_mask_expand"; + ot->description = "Expands a mask from the initial active vertex under the cursor"; + + /* api callbacks */ + ot->invoke = sculpt_mask_expand_invoke; + ot->modal = sculpt_mask_expand_modal; + ot->cancel = sculpt_mask_expand_cancel; + ot->poll = sculpt_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->prop = RNA_def_boolean(ot->srna, "invert", true, "Invert", "Invert the new mask"); + ot->prop = RNA_def_boolean( + ot->srna, "use_cursor", true, "Use Cursor", "Expand the mask to the cursor position"); + ot->prop = RNA_def_boolean(ot->srna, + "update_pivot", + true, + "Update Pivot Position", + "Set the pivot position to the mask border after creating the mask"); + ot->prop = RNA_def_int(ot->srna, "smooth_iterations", 2, 0, 10, "Smooth iterations", "", 0, 10); + ot->prop = RNA_def_int(ot->srna, "mask_speed", 5, 1, 10, "Mask speed", "", 1, 10); + + ot->prop = RNA_def_boolean(ot->srna, + "use_normals", + true, + "Use Normals", + "Generate the mask using the normals and curvature of the model"); + ot->prop = RNA_def_boolean(ot->srna, + "keep_previous_mask", + false, + "Keep Previous Mask", + "Generate the new mask on top of the current one"); + ot->prop = RNA_def_int(ot->srna, + "edge_sensitivity", + 300, + 0, + 2000, + "Edge Detection Sensitivity", + "Sensitivity for expanding the mask across sculpted sharp edges when " + "using normals to generate the mask", + 0, + 2000); +} + +void sculpt_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius) +{ + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + Object *ob = CTX_data_active_object(C); + + ss->preview_vert_index_count = 0; + int totpoints = 0; + + /* This function is called from the cursor drawing code, so the PBVH may not be build yet */ + if (!ss->pbvh) { + return; + } + + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true); + + if (!ss->pmap) { + return; + } + + float brush_co[3]; + copy_v3_v3(brush_co, sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss))); + + char *visited_vertices = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(char), + "visited vertices"); + + if (ss->preview_vert_index_list == NULL) { + ss->preview_vert_index_list = MEM_callocN(4 * sizeof(int) * sculpt_vertex_count_get(ss), + "preview lines"); + } + + BLI_Stack *not_visited_vertices = BLI_stack_new(sizeof(VertexTopologyIterator), + "Not visited vertices stack"); + VertexTopologyIterator mevit; + mevit.v = sculpt_active_vertex_get(ss); + BLI_stack_push(not_visited_vertices, &mevit); + + while (!BLI_stack_is_empty(not_visited_vertices)) { + VertexTopologyIterator c_mevit; + BLI_stack_pop(not_visited_vertices, &c_mevit); + SculptVertexNeighborIter ni; + sculpt_vertex_neighbors_iter_begin(ss, c_mevit.v, ni) + { + VertexTopologyIterator new_entry; + new_entry.v = ni.index; + new_entry.it = c_mevit.it + 1; + ss->preview_vert_index_list[totpoints] = c_mevit.v; + totpoints++; + ss->preview_vert_index_list[totpoints] = new_entry.v; + totpoints++; + if (visited_vertices[(int)ni.index] == 0) { + visited_vertices[(int)ni.index] = 1; + if (len_squared_v3v3(brush_co, sculpt_vertex_co_get(ss, new_entry.v)) < radius * radius) { + BLI_stack_push(not_visited_vertices, &new_entry); + } + } + } + sculpt_vertex_neighbors_iter_end(ni) + } + + BLI_stack_free(not_visited_vertices); + + MEM_freeN(visited_vertices); + + ss->preview_vert_index_count = totpoints; +} +void ED_sculpt_init_transform(struct bContext *C) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + copy_v3_v3(ss->init_pivot_pos, ss->pivot_pos); + copy_v4_v4(ss->init_pivot_rot, ss->pivot_rot); + + ss->init_pivot_scale[0] = 1.0f; + ss->init_pivot_scale[1] = 1.0f; + ss->init_pivot_scale[2] = 1.0f; + + ss->pivot_scale[0] = 1.0f; + ss->pivot_scale[1] = 1.0f; + ss->pivot_scale[2] = 1.0f; + + sculpt_undo_push_begin("Transform"); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false); + + ss->pivot_rot[3] = 1.0f; + + sculpt_vertex_random_access_init(ss); + sculpt_filter_cache_init(ob, sd); +} + +typedef enum PaintSymmetryAreas { + AREA_SYMM_X = (1 << 0), + AREA_SYMM_Y = (1 << 1), + AREA_SYMM_Z = (1 << 2), +} PaintSymmetryAreas; + +static char sculpt_get_vertex_symm_area(float co[3]) +{ + float vco[3]; + char symm_area = 0; + copy_v3_v3(vco, co); + if (vco[0] < 0) { + symm_area |= AREA_SYMM_X; + } + if (vco[1] < 0) { + symm_area |= AREA_SYMM_Y; + } + if (vco[2] < 0) { + symm_area |= AREA_SYMM_Z; + } + return symm_area; +} + +static void flip_qt(float qt[4], char symm) +{ + float euler[3]; + if (symm & PAINT_SYMM_X) { + quat_to_eul(euler, qt); + euler[1] = -euler[1]; + euler[2] = -euler[2]; + eul_to_quat(qt, euler); + } + if (symm & PAINT_SYMM_Y) { + quat_to_eul(euler, qt); + euler[0] = -euler[0]; + euler[2] = -euler[2]; + eul_to_quat(qt, euler); + } + if (symm & PAINT_SYMM_Z) { + quat_to_eul(euler, qt); + euler[0] = -euler[0]; + euler[1] = -euler[1]; + eul_to_quat(qt, euler); + } +} + +static void sculpt_flip_transform_by_symm_area( + float disp[3], float rot[4], char symm, char symmarea, float pivot[3]) +{ + + for (char i = 0; i < 3; i++) { + char symm_it = 1 << i; + if (symm & symm_it) { + if (symmarea & symm_it) { + if (disp) { + flip_v3(disp, symm_it); + } + if (rot) { + flip_qt(rot, symm_it); + } + } + if (pivot[0] < 0) { + if (disp) { + flip_v3(disp, symm_it); + } + if (rot) { + flip_qt(rot, symm_it); + } + } + } + } +} + +static void sculpt_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]; + + SculptOrigVertData orig_data; + sculpt_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]); + + PBVHVertexIter vd; + + sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS); + BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_ALL) + { + sculpt_orig_vert_data_update(&orig_data, &vd); + float transformed_co[3], orig_co[3], disp[3]; + float fade = vd.mask ? *vd.mask : 0.0f; + copy_v3_v3(orig_co, orig_data.co); + char symm_area = sculpt_get_vertex_symm_area(orig_co); + + copy_v3_v3(transformed_co, orig_co); + mul_m4_v3(data->transform_mats[(int)symm_area], transformed_co); + sub_v3_v3v3(disp, transformed_co, orig_co); + mul_v3_fl(disp, 1.0f - fade); + + add_v3_v3v3(vd.co, orig_co, disp); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; + + BKE_pbvh_node_mark_redraw(node); + BKE_pbvh_node_mark_normals_update(node); +} + +void ED_sculpt_update_modal_transform(struct bContext *C) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + + sculpt_vertex_random_access_init(ss); + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .nodes = ss->filter_cache->nodes, + }; + + float final_pivot_pos[3], d_t[3], d_r[4]; + float t_mat[4][4], r_mat[4][4], s_mat[4][4], pivot_mat[4][4], pivot_imat[4][4], + transform_mat[4][4]; + + copy_v3_v3(final_pivot_pos, ss->pivot_pos); + for (int i = 0; i < 8; i++) { + copy_v3_v3(final_pivot_pos, ss->pivot_pos); + + unit_m4(pivot_mat); + + unit_m4(t_mat); + unit_m4(r_mat); + unit_m4(s_mat); + + /* Translation matrix */ + sub_v3_v3v3(d_t, ss->pivot_pos, ss->init_pivot_pos); + sculpt_flip_transform_by_symm_area(d_t, NULL, symm, (char)i, ss->init_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, ss->init_pivot_rot); + normalize_qt(d_r); + sculpt_flip_transform_by_symm_area(NULL, d_r, symm, (char)i, ss->init_pivot_pos); + quat_to_mat4(r_mat, d_r); + + /* Scale matrix */ + size_to_mat4(s_mat, ss->pivot_scale); + + /* Pivot matrix */ + sculpt_flip_transform_by_symm_area(final_pivot_pos, NULL, symm, (char)i, ss->init_pivot_pos); + translate_m4(pivot_mat, final_pivot_pos[0], final_pivot_pos[1], final_pivot_pos[2]); + invert_m4_m4(pivot_imat, pivot_mat); + + /* Final transform matrix */ + mul_m4_m4m4(transform_mat, r_mat, t_mat); + mul_m4_m4m4(transform_mat, transform_mat, s_mat); + mul_m4_m4m4(data.transform_mats[i], transform_mat, pivot_imat); + mul_m4_m4m4(data.transform_mats[i], pivot_mat, data.transform_mats[i]); + } + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && + ss->filter_cache->totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range( + 0, ss->filter_cache->totnode, &data, sculpt_transform_task_cb, &settings); + + if (ss->modifiers_active || ss->kb) { + sculpt_flush_stroke_deform(sd, ob, true); + } + + sculpt_flush_update_step(C); +} + +void ED_sculpt_end_transform(struct bContext *C) +{ + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + if (ss->filter_cache) { + sculpt_filter_cache_free(ss); + } + sculpt_undo_push_end(); + sculpt_flush_update_done(C, ob); +} + +typedef enum eSculptPivotPositionModes { + SCULPT_PIVOT_POSITION_ORIGIN = 0, + SCULPT_PIVOT_POSITION_UNMASKED = 1, + SCULPT_PIVOT_POSITION_MASK_BORDER = 2, + SCULPT_PIVOT_POSITION_ACTIVE_VERTEX = 3, + SCULPT_PIVOT_POSITION_CURSOR_SURFACE = 4, +} eSculptPivotPositionModes; + +EnumPropertyItem prop_sculpt_pivot_position_types[] = { + {SCULPT_PIVOT_POSITION_ORIGIN, + "ORIGIN", + 0, + "Origin", + "Sets the pivot to the origin of the sculpt"}, + {SCULPT_PIVOT_POSITION_UNMASKED, + "UNMASKED", + 0, + "Unmasked", + "Sets the pivot position to the average position of the unmasked vertices"}, + {SCULPT_PIVOT_POSITION_MASK_BORDER, + "BORDER", + 0, + "Mask border", + "Sets the pivot position to the center of the border of the mask"}, + {SCULPT_PIVOT_POSITION_ACTIVE_VERTEX, + "ACTIVE", + 0, + "Active vertex", + "Sets the pivot position to the active vertex position"}, + {SCULPT_PIVOT_POSITION_CURSOR_SURFACE, + "SURFACE", + 0, + "Surface", + "Sets the pivot position to the surface under the cursor"}, + {0, NULL, 0, NULL, NULL}, +}; + +static int sculpt_set_pivot_position_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Sculpt *sd = CTX_data_tool_settings(C)->sculpt; + Object *ob = CTX_data_active_object(C); + SculptSession *ss = ob->sculpt; + ARegion *ar = CTX_wm_region(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL; + + if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) { + return OPERATOR_CANCELLED; + } + + int mode = RNA_enum_get(op->ptr, "mode"); + + BKE_sculpt_update_object_for_edit(depsgraph, ob, false, true); + + int vert_count = sculpt_vertex_count_get(ss); + + /* Pivot to center */ + if (mode == SCULPT_PIVOT_POSITION_ORIGIN) { + zero_v3(ss->pivot_pos); + } + + /* Pivot to unmasked */ + if (mode == SCULPT_PIVOT_POSITION_UNMASKED) { + float avg[3]; + int total = 0; + zero_v3(avg); + for (int i = 0; i < vert_count; i++) { + if (sculpt_vertex_mask_get(ss, i) < 1.0f && + check_vertex_pivot_symmetry(sculpt_vertex_co_get(ss, i), ss->pivot_pos, symm)) { + total++; + add_v3_v3(avg, sculpt_vertex_co_get(ss, i)); + } + } + if (total > 0) { + mul_v3_fl(avg, 1.0f / total); + copy_v3_v3(ss->pivot_pos, avg); + } + } + + /* Pivot to mask border */ + if (mode == SCULPT_PIVOT_POSITION_MASK_BORDER) { + float avg[3]; + int total = 0; + float threshold = 0.2f; + zero_v3(avg); + for (int i = 0; i < vert_count; i++) { + if (sculpt_vertex_mask_get(ss, i) < (0.5f + threshold) && + sculpt_vertex_mask_get(ss, i) > (0.5f - threshold) && + check_vertex_pivot_symmetry(sculpt_vertex_co_get(ss, i), ss->pivot_pos, symm)) { + total++; + add_v3_v3(avg, sculpt_vertex_co_get(ss, i)); + } + } + if (total > 0) { + mul_v3_fl(avg, 1.0f / total); + copy_v3_v3(ss->pivot_pos, avg); + } + } + + /* Pivot to active vertex */ + if (mode == SCULPT_PIVOT_POSITION_ACTIVE_VERTEX) { + copy_v3_v3(ss->pivot_pos, sculpt_vertex_co_get(ss, sculpt_active_vertex_get(ss))); + } + + /* Pivot to raycast surface */ + if (mode == SCULPT_PIVOT_POSITION_CURSOR_SURFACE) { + float stroke_location[3]; + float mouse[2]; + mouse[0] = event->mval[0]; + mouse[1] = event->mval[1]; + if (sculpt_stroke_get_location(C, stroke_location, mouse)) { + copy_v3_v3(ss->pivot_pos, stroke_location); + } + } + + ED_region_tag_redraw(ar); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data); + + return OPERATOR_FINISHED; +} + +static void SCULPT_OT_set_pivot_position(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Pivot Position"; + ot->idname = "SCULPT_OT_set_pivot_position"; + ot->description = "Sets the sculpt transform pivot position"; + + /* api callbacks */ + ot->invoke = sculpt_set_pivot_position_invoke; + ot->poll = sculpt_mode_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + RNA_def_enum(ot->srna, + "mode", + prop_sculpt_pivot_position_types, + SCULPT_PIVOT_POSITION_UNMASKED, + "Mode", + ""); +} + void ED_operatortypes_sculpt(void) { WM_operatortype_append(SCULPT_OT_brush_stroke); @@ -8595,4 +9480,6 @@ void ED_operatortypes_sculpt(void) WM_operatortype_append(SCULPT_OT_mesh_filter); WM_operatortype_append(SCULPT_OT_mask_filter); WM_operatortype_append(SCULPT_OT_dirty_mask); + WM_operatortype_append(SCULPT_OT_mask_expand); + WM_operatortype_append(SCULPT_OT_set_pivot_position); } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 8d009b64b6b..b814e87fcb0 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -57,6 +57,10 @@ bool sculpt_cursor_geometry_info_update(bContext *C, SculptCursorGeometryInfo *out, const float mouse[2], bool use_sampled_normal); +void sculpt_geometry_preview_lines_update(bContext *C, struct SculptSession *ss, float radius); + +/* Sculpt PBVH abstraction API */ +float *sculpt_vertex_co_get(struct SculptSession *ss, int index); /* Dynamic topology */ void sculpt_pbvh_clear(Object *ob); @@ -118,6 +122,10 @@ typedef struct SculptUndoNode { int geom_totloop; int geom_totpoly; + /* pivot */ + float pivot_pos[3]; + float pivot_rot[4]; + size_t undo_size; } SculptUndoNode; @@ -169,16 +177,16 @@ typedef struct SculptThreadedTaskData { float (*mat)[4]; float (*vertCos)[3]; + int filter_type; + float filter_strength; + int *node_mask; + /* 0=towards view, 1=flipped */ float (*area_cos)[3]; float (*area_nos)[3]; int *count; bool any_vertex_sampled; - int filter_type; - float filter_strength; - int *node_mask; - float *prev_mask; float *pose_origin; @@ -189,6 +197,13 @@ typedef struct SculptThreadedTaskData { float nearest_vertex_search_co[3]; int nearest_vertex_index; + int mask_expand_update_it; + bool mask_expand_invert_mask; + bool mask_expand_use_normals; + bool mask_expand_keep_prev_mask; + + float transform_mats[8][4][4]; + ThreadMutex mutex; } SculptThreadedTaskData; @@ -376,6 +391,9 @@ typedef struct FilterCache { int mask_update_current_it; int mask_update_last_it; int *mask_update_it; + float *normal_factor; + float *prev_mask; + float mask_expand_initial_co[3]; } FilterCache; void sculpt_cache_calc_brushdata_symm(StrokeCache *cache, diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.c b/source/blender/editors/sculpt_paint/sculpt_undo.c index cb8afd5d7fa..3783eb17562 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.c +++ b/source/blender/editors/sculpt_paint/sculpt_undo.c @@ -494,6 +494,9 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase bool partial_update = true; for (unode = lb->first; unode; unode = unode->next) { + /* restore pivot */ + copy_v3_v3(ss->pivot_pos, unode->pivot_pos); + copy_v3_v3(ss->pivot_rot, unode->pivot_rot); if (STREQ(unode->idname, ob->id.name)) { if (unode->type == SCULPT_UNDO_MASK) { /* is possible that we can't do the mask undo (below) @@ -1055,6 +1058,10 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType break; } + /* store sculpt pivot */ + copy_v3_v3(unode->pivot_pos, ss->pivot_pos); + copy_v3_v3(unode->pivot_rot, ss->pivot_rot); + /* store active shape key */ if (ss->kb) { BLI_strncpy(unode->shapeName, ss->kb->name, sizeof(ss->kb->name)); |