From d918becba1aa8e9dade91167a070913678f5de05 Mon Sep 17 00:00:00 2001 From: Pablo Dobarro Date: Mon, 9 Sep 2019 17:44:08 +0200 Subject: Sculpt: Pose Brush This brush lets the user pose a model simulating an armature-like deformation. The pivot point for rotation is calculated automatically based on the radius of the brush and the topology of the model. Reviewed By: brecht Differential Revision: https://developer.blender.org/D5647 --- source/blender/blenkernel/intern/brush.c | 1 + source/blender/editors/sculpt_paint/paint_stroke.c | 4 +- source/blender/editors/sculpt_paint/sculpt.c | 284 ++++++++++++++++++++- .../blender/editors/sculpt_paint/sculpt_intern.h | 9 + source/blender/makesdna/DNA_brush_types.h | 2 + source/blender/makesrna/intern/rna_brush.c | 1 + 6 files changed, 295 insertions(+), 6 deletions(-) diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index f5f7fb71a64..bdc836225c9 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -902,6 +902,7 @@ void BKE_brush_sculpt_reset(Brush *br) break; case SCULPT_TOOL_GRAB: case SCULPT_TOOL_ELASTIC_DEFORM: + case SCULPT_TOOL_POSE: case SCULPT_TOOL_SNAKE_HOOK: case SCULPT_TOOL_THUMB: br->size = 75; diff --git a/source/blender/editors/sculpt_paint/paint_stroke.c b/source/blender/editors/sculpt_paint/paint_stroke.c index 0072888cb8d..d2173ed0d96 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.c +++ b/source/blender/editors/sculpt_paint/paint_stroke.c @@ -222,6 +222,7 @@ static bool paint_tool_require_location(Brush *brush, ePaintMode mode) if (ELEM(brush->sculpt_tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_POSE, SCULPT_TOOL_ROTATE, SCULPT_TOOL_SNAKE_HOOK, SCULPT_TOOL_THUMB)) { @@ -256,7 +257,8 @@ static bool paint_tool_require_inbetween_mouse_events(Brush *brush, ePaintMode m SCULPT_TOOL_GRAB, SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM)) { + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_POSE)) { return false; } else { diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index 0a4c77b5656..a5327ff571c 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -30,6 +30,7 @@ #include "BLI_hash.h" #include "BLI_gsqueue.h" #include "BLI_stack.h" +#include "BLI_gsqueue.h" #include "BLI_task.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" @@ -343,6 +344,25 @@ static void sculpt_vertex_mask_clamp(SculptSession *ss, int index, float min, fl } } +static bool check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm) +{ + bool is_in_symmetry_area = true; + for (int i = 0; i < 3; i++) { + char symm_it = 1 << i; + if (symm & symm_it) { + if (pco[i] == 0.0f) { + if (vco[i] > 0.0f) { + is_in_symmetry_area = false; + } + } + if (vco[i] * pco[i] < 0.0f) { + is_in_symmetry_area = false; + } + } + } + return is_in_symmetry_area; +} + static void do_nearest_vertex_get_task_cb(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) @@ -401,6 +421,7 @@ int sculpt_nearest_vertex_get( .max_distance_squared = max_distance * max_distance, .nearest_vertex_index = -1, }; + copy_v3_v3(task_data.nearest_vertex_search_co, co); BLI_mutex_init(&task_data.mutex); @@ -470,12 +491,13 @@ static bool sculpt_tool_needs_original(const char sculpt_tool) SCULPT_TOOL_THUMB, SCULPT_TOOL_LAYER, SCULPT_TOOL_DRAW_SHARP, - SCULPT_TOOL_ELASTIC_DEFORM); + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_POSE); } static bool sculpt_tool_is_proxy_used(const char sculpt_tool) { - return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER); + return ELEM(sculpt_tool, SCULPT_TOOL_SMOOTH, SCULPT_TOOL_LAYER, SCULPT_TOOL_POSE); } static bool sculpt_brush_use_topology_rake(const SculptSession *ss, const Brush *brush) @@ -1698,6 +1720,7 @@ static float brush_strength(const Sculpt *sd, return alpha * pressure * feather; case SCULPT_TOOL_ELASTIC_DEFORM: + case SCULPT_TOOL_POSE: return root_alpha * feather; default: @@ -3446,6 +3469,240 @@ static void do_elastic_deform_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, in BLI_task_parallel_range(0, totnode, &data, do_elastic_deform_brush_task_cb_ex, &settings); } +static void do_pose_brush_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + + PBVHVertexIter vd; + float disp[3], val[3]; + float final_pos[3]; + + 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 (check_vertex_pivot_symmetry( + orig_data.co, data->pose_initial_co, ss->cache->mirror_symmetry_pass)) { + copy_v3_v3(val, orig_data.co); + mul_m4_v3(data->transform_trans_inv, val); + mul_m4_v3(data->transform_rot, val); + mul_m4_v3(data->transform_trans, val); + sub_v3_v3v3(disp, val, orig_data.co); + + mul_v3_fl(disp, ss->cache->pose_factor[vd.index]); + float mask = vd.mask ? *vd.mask : 0.0f; + mul_v3_fl(disp, 1.0f - mask); + add_v3_v3v3(final_pos, orig_data.co, disp); + copy_v3_v3(vd.co, final_pos); + + if (vd.mvert) { + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + } + BKE_pbvh_vertex_iter_end; +} + +static void do_pose_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) +{ + SculptSession *ss = ob->sculpt; + Brush *brush = BKE_paint_brush(&sd->paint); + float grab_delta[3], rot_quat[4], initial_v[3], current_v[3], temp[3]; + float pose_origin[3]; + float pose_initial_co[3]; + float transform_rot[4][4], transform_trans[4][4], transform_trans_inv[4][4]; + + copy_v3_v3(grab_delta, ss->cache->grab_delta_symmetry); + + copy_v3_v3(pose_origin, ss->cache->pose_origin); + flip_v3(pose_origin, (char)ss->cache->mirror_symmetry_pass); + + copy_v3_v3(pose_initial_co, ss->cache->pose_initial_co); + flip_v3(pose_initial_co, (char)ss->cache->mirror_symmetry_pass); + + sub_v3_v3v3(initial_v, pose_initial_co, pose_origin); + normalize_v3(initial_v); + + add_v3_v3v3(temp, pose_initial_co, grab_delta); + sub_v3_v3v3(current_v, temp, pose_origin); + normalize_v3(current_v); + + rotation_between_vecs_to_quat(rot_quat, initial_v, current_v); + unit_m4(transform_rot); + unit_m4(transform_trans); + quat_to_mat4(transform_rot, rot_quat); + translate_m4(transform_trans, pose_origin[0], pose_origin[1], pose_origin[2]); + invert_m4_m4(transform_trans_inv, transform_trans); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = brush, + .nodes = nodes, + .grab_delta = grab_delta, + .pose_origin = pose_origin, + .pose_initial_co = pose_initial_co, + .transform_rot = transform_rot, + .transform_trans = transform_trans, + .transform_trans_inv = transform_trans_inv, + }; + + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range(0, totnode, &data, do_pose_brush_task_cb_ex, &settings); +} + +static void pose_brush_init_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) + { + SculptVertexNeighborIter ni; + float avg = 0; + int total = 0; + sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni) + { + avg += ss->cache->pose_factor[ni.index]; + total++; + } + sculpt_vertex_neighbors_iter_end(ni); + + if (total > 0) { + ss->cache->pose_factor[vd.index] = avg / (float)total; + } + } + BKE_pbvh_vertex_iter_end; +} + +static bool sculpt_pose_brush_is_vertex_inside_brush_radius(float vertex[3], + float br_co[3], + float radius, + char symm) +{ + for (char i = 0; i <= symm; ++i) { + if (is_symmetry_iteration_valid(i, symm)) { + float location[3]; + flip_v3_v3(location, br_co, (char)i); + if (len_v3v3(location, vertex) < radius) { + return true; + } + } + } + return false; +} + +static void sculpt_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br) +{ + sculpt_vertex_random_access_init(ss); + + ss->cache->pose_factor = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(float), "Pose factor"); + + copy_v3_v3(ss->cache->pose_initial_co, ss->cache->location); + + char *visited_vertices = MEM_callocN(sculpt_vertex_count_get(ss) * sizeof(char), + "Visited vertices"); + BLI_Stack *not_visited_vertices = BLI_stack_new(sizeof(VertexTopologyIterator), + "not visited vertices stack"); + + float tot_co = 0; + zero_v3(ss->cache->pose_origin); + + VertexTopologyIterator mevit; + + /* Add active vertex and symmetric vertices to the stack. */ + 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)), (char)i); + if (i == 0) { + mevit.v = sculpt_active_vertex_get(ss); + } + else { + mevit.v = sculpt_nearest_vertex_get( + sd, ob, location, ss->cache->radius * ss->cache->radius, false); + } + if (mevit.v != -1) { + mevit.it = 1; + BLI_stack_push(not_visited_vertices, &mevit); + } + } + } + + /* Flood fill the internal pose brush factor. Calculate the pose rotation point based on the + * boundaries of the brush factor*/ + 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) + { + if (visited_vertices[(int)ni.index] == 0) { + VertexTopologyIterator new_entry; + new_entry.v = ni.index; + new_entry.it = c_mevit.it + 1; + ss->cache->pose_factor[new_entry.v] = 1.0f; + visited_vertices[(int)ni.index] = 1; + if (sculpt_pose_brush_is_vertex_inside_brush_radius(sculpt_vertex_co_get(ss, new_entry.v), + ss->cache->pose_initial_co, + ss->cache->radius, + symm)) { + BLI_stack_push(not_visited_vertices, &new_entry); + } + else { + if (check_vertex_pivot_symmetry( + sculpt_vertex_co_get(ss, new_entry.v), ss->cache->pose_initial_co, symm)) { + tot_co++; + add_v3_v3(ss->cache->pose_origin, sculpt_vertex_co_get(ss, new_entry.v)); + } + } + } + } + sculpt_vertex_neighbors_iter_end(ni) + } + + BLI_stack_free(not_visited_vertices); + + MEM_freeN(visited_vertices); + + if (tot_co > 0) { + mul_v3_fl(ss->cache->pose_origin, 1.0f / (float)tot_co); + } + + /* Smooth the pose brush factor for cleaner deformation */ + + PBVHNode **nodes; + PBVH *pbvh = ob->sculpt->pbvh; + int totnode; + + BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode); + + SculptThreadedTaskData data = { + .sd = sd, + .ob = ob, + .brush = br, + .nodes = nodes, + }; + + for (int i = 0; i < 4; i++) { + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT); + BLI_task_parallel_range(0, totnode, &data, pose_brush_init_task_cb_ex, &settings); + } +} + static void do_nudge_brush_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict tls) @@ -4810,6 +5067,11 @@ 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); + } + /* Apply one type of brush action */ switch (brush->sculpt_tool) { case SCULPT_TOOL_DRAW: @@ -4865,6 +5127,8 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe break; case SCULPT_TOOL_MASK: do_mask_brush(sd, ob, nodes, totnode); + case SCULPT_TOOL_POSE: + do_pose_brush(sd, ob, nodes, totnode); break; case SCULPT_TOOL_DRAW_SHARP: do_draw_sharp_brush(sd, ob, nodes, totnode); @@ -4940,7 +5204,8 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata, SCULPT_TOOL_GRAB, SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB, - SCULPT_TOOL_ELASTIC_DEFORM); + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_POSE); PBVHVertexIter vd; PBVHProxyNode *proxies; @@ -5384,6 +5649,8 @@ static const char *sculpt_tool_name(Sculpt *sd) return "Draw Sharp Brush"; case SCULPT_TOOL_ELASTIC_DEFORM: return "Elastic Deform Brush"; + case SCULPT_TOOL_POSE: + return "Pose Brush"; } return "Sculpting"; @@ -5626,6 +5893,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru SCULPT_TOOL_NUDGE, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_SNAKE_HOOK, + SCULPT_TOOL_POSE, SCULPT_TOOL_THUMB) || sculpt_brush_use_topology_rake(ss, brush)) { float grab_location[3], imat[4][4], delta[3], loc[3]; @@ -5645,6 +5913,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru if (!cache->first_time) { switch (tool) { case SCULPT_TOOL_GRAB: + case SCULPT_TOOL_POSE: case SCULPT_TOOL_THUMB: case SCULPT_TOOL_ELASTIC_DEFORM: sub_v3_v3v3(delta, grab_location, cache->old_grab_location); @@ -5692,7 +5961,11 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru copy_v3_v3(cache->anchored_location, cache->orig_grab_location); } - if (ELEM(tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_THUMB, SCULPT_TOOL_ELASTIC_DEFORM)) { + if (ELEM(tool, + SCULPT_TOOL_GRAB, + SCULPT_TOOL_THUMB, + SCULPT_TOOL_ELASTIC_DEFORM, + SCULPT_TOOL_POSE)) { /* location stays the same for finding vertices in brush radius */ copy_v3_v3(cache->true_location, cache->orig_grab_location); @@ -5837,7 +6110,8 @@ static bool sculpt_needs_conectivity_info(const Brush *brush, SculptSession *ss, } return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss && ss->cache && ss->cache->alt_smooth) || (brush->sculpt_tool == SCULPT_TOOL_SMOOTH) || (brush->autosmooth_factor > 0) || - ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH))); + ((brush->sculpt_tool == SCULPT_TOOL_MASK) && (brush->mask_tool == BRUSH_MASK_SMOOTH)) || + (brush->sculpt_tool == SCULPT_TOOL_POSE)); } static void sculpt_stroke_modifiers_check(const bContext *C, Object *ob, const Brush *brush) diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index b02ccca3d4a..8d009b64b6b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -181,6 +181,10 @@ typedef struct SculptThreadedTaskData { float *prev_mask; + float *pose_origin; + float *pose_initial_co; + float (*transform_rot)[4], (*transform_trans)[4], (*transform_trans_inv)[4]; + float max_distance_squared; float nearest_vertex_search_co[3]; int nearest_vertex_index; @@ -334,6 +338,11 @@ typedef struct StrokeCache { bool original; float anchored_location[3]; + /* Pose brush */ + float *pose_factor; + float pose_initial_co[3]; + float pose_origin[3]; + float vertex_rotation; /* amount to rotate the vertices when using rotate brush */ struct Dial *dial; diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 1906259f3e9..56e255b132a 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -473,6 +473,7 @@ typedef enum eBrushSculptTool { SCULPT_TOOL_MASK = 19, SCULPT_TOOL_DRAW_SHARP = 20, SCULPT_TOOL_ELASTIC_DEFORM = 21, + SCULPT_TOOL_POSE = 22, } eBrushSculptTool; /* Brush.uv_sculpt_tool */ @@ -508,6 +509,7 @@ typedef enum eBrushUVSculptTool { SCULPT_TOOL_LAYER, \ SCULPT_TOOL_DRAW_SHARP, \ SCULPT_TOOL_ELASTIC_DEFORM, \ + SCULPT_TOOL_POSE, \ \ /* These brushes could handle dynamic topology, \ * but user feedback indicates it's better not to */ \ diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 5c972ed1a35..3aa96749512 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -93,6 +93,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = { {SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""}, {SCULPT_TOOL_DRAW_SHARP, "DRAW_SHARP", ICON_BRUSH_SCULPT_DRAW, "Draw Sharp", ""}, {SCULPT_TOOL_ELASTIC_DEFORM, "ELASTIC_DEFORM", ICON_BRUSH_GRAB, "Elastic Deform", ""}, + {SCULPT_TOOL_POSE, "POSE", ICON_BRUSH_GRAB, "Pose", ""}, {0, NULL, 0, NULL, NULL}, }; -- cgit v1.2.3