diff options
author | Jean Da Costa <jeacom256> | 2019-01-23 20:38:40 +0300 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@gmail.com> | 2019-01-23 22:25:14 +0300 |
commit | b592e34559da82f86339e97b25ceac9f844719ab (patch) | |
tree | 0f6f0d9a04ccd8eb0551a8c0cdc5ad4b80c9996c /source | |
parent | 4e9817a4fb2986b3d08da8380a6ce1d40c108970 (diff) |
Sculpt: add Topology Rake, to align edges along brush while painting.
This helps to generate cleaner topology and define sharp features for dynamic
topology. Best used on relatively low-poly meshes, it is not needed as much
for high detail areas and has a performance impact.
Differential Revision: https://developer.blender.org/D4189
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/blenkernel/intern/brush.c | 3 | ||||
-rw-r--r-- | source/blender/editors/sculpt_paint/sculpt.c | 204 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_brush_types.h | 12 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_brush.c | 18 |
4 files changed, 231 insertions, 6 deletions
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c index f9ba8765d04..bb42f7412e6 100644 --- a/source/blender/blenkernel/intern/brush.c +++ b/source/blender/blenkernel/intern/brush.c @@ -79,6 +79,7 @@ static void brush_defaults(Brush *brush) brush->size = 35; /* radius of the brush in pixels */ brush->alpha = 0.5f; /* brush strength/intensity probably variable should be renamed? */ brush->autosmooth_factor = 0.0f; + brush->topology_rake_factor = 0.0f; brush->crease_pinch_factor = 0.5f; brush->sculpt_plane = SCULPT_DISP_DIR_AREA; brush->plane_offset = 0.0f; /* how far above or below the plane that is found by averaging the faces */ @@ -739,6 +740,8 @@ void BKE_brush_debug_print_state(Brush *br) BR_TEST(autosmooth_factor, f); + BR_TEST(topology_rake_factor, f); + BR_TEST(crease_pinch_factor, f); BR_TEST(plane_trim, f); diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index b2df942b2fa..b25b9f4fff6 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -142,13 +142,22 @@ static bool sculpt_tool_is_proxy_used(const char sculpt_tool) SCULPT_TOOL_LAYER); } +static bool sculpt_brush_use_topology_rake( + const SculptSession *ss, const Brush *brush) +{ + return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(brush->sculpt_tool) && + (brush->topology_rake_factor > 0.0f) && + (ss->bm != NULL); +} + /** * Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action */ -static int sculpt_brush_needs_normal(const Brush *brush, float normal_weight) +static int sculpt_brush_needs_normal( + const SculptSession *ss, const Brush *brush) { return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(brush->sculpt_tool) && - (normal_weight > 0.0f)) || + (ss->cache->normal_weight > 0.0f)) || ELEM(brush->sculpt_tool, SCULPT_TOOL_BLOB, @@ -159,7 +168,8 @@ static int sculpt_brush_needs_normal(const Brush *brush, float normal_weight) SCULPT_TOOL_ROTATE, SCULPT_TOOL_THUMB) || - (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)); + (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA)) || + sculpt_brush_use_topology_rake(ss, brush); } /** \} */ @@ -1598,6 +1608,94 @@ static void bmesh_neighbor_average(float avg[3], BMVert *v) copy_v3_v3(avg, v->co); } +/* For bmesh: average only the four most aligned (parallel and perpendicular) edges + * relative to a direction. Naturally converges to a quad-like tesselation. */ +static void bmesh_four_neighbor_average(float avg[3], float direction[3], BMVert *v) +{ + /* Logic for 3 or more is identical. */ + const int vfcount = BM_vert_face_count_at_most(v, 3); + + /* Don't modify corner vertices. */ + if (vfcount < 2) { + copy_v3_v3(avg, v->co); + return; + } + + /* Project the direction to the vertex normal and create an aditional + * parallel vector. */ + float dir_a[3], dir_b[3]; + cross_v3_v3v3(dir_a, direction, v->no); + cross_v3_v3v3(dir_b, dir_a, v->no); + + /* The four vectors which will be used for smoothing. + * Ocasionally less than 4 verts match the requirements in that case + * use v as fallback. */ + BMVert *pos_a = v; + BMVert *neg_a = v; + BMVert *pos_b = v; + BMVert *neg_b = v; + + float pos_score_a = 0.0f; + float neg_score_a = 0.0f; + float pos_score_b = 0.0f; + float neg_score_b = 0.0f; + + BMIter liter; + BMLoop *l; + + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + BMVert *adj_v[2] = { l->prev->v, l->next->v }; + + for (int i = 0; i < ARRAY_SIZE(adj_v); i++) { + BMVert *v_other = adj_v[i]; + + if (vfcount != 2 || BM_vert_face_count_at_most(v_other, 2) <= 2) { + float vec[3]; + sub_v3_v3v3(vec, v_other->co, v->co); + normalize_v3(vec); + + /* The score is a measure of how orthogonal the edge is. */ + float score = dot_v3v3(vec, dir_a); + + if (score >= pos_score_a) { + pos_a = v_other; + pos_score_a = score; + } + else if (score < neg_score_a) { + neg_a = v_other; + neg_score_a = score; + } + /* The same scoring but for the perpendicular direction. */ + score = dot_v3v3(vec, dir_b); + + if (score >= pos_score_b) { + pos_b = v_other; + pos_score_b = score; + } + else if (score < neg_score_b) { + neg_b = v_other; + neg_score_b = score; + } + } + } + } + + /* Average everything together. */ + zero_v3(avg); + add_v3_v3(avg, pos_a->co); + add_v3_v3(avg, neg_a->co); + add_v3_v3(avg, pos_b->co); + add_v3_v3(avg, neg_b->co); + mul_v3_fl(avg, 0.25f); + + /* Preserve volume. */ + float vec[3]; + sub_v3_v3(avg, v->co); + mul_v3_v3fl(vec, v->no, dot_v3v3(avg, v->no)); + sub_v3_v3(avg, vec); + add_v3_v3(avg, v->co); +} + /* Same logic as neighbor_average_mask(), but for bmesh rather than mesh */ static float bmesh_neighbor_average_mask(BMVert *v, const int cd_vert_mask_offset) { @@ -1756,6 +1854,62 @@ static void do_smooth_brush_bmesh_task_cb_ex( BKE_pbvh_vertex_iter_end; } +static void do_topology_rake_bmesh_task_cb_ex( + void *__restrict userdata, + const int n, + const ParallelRangeTLS *__restrict tls) +{ + SculptThreadedTaskData *data = userdata; + SculptSession *ss = data->ob->sculpt; + Sculpt *sd = data->sd; + const Brush *brush = data->brush; + + float direction[3]; + copy_v3_v3(direction, ss->cache->grab_delta_symmetry); + + float tmp[3]; + mul_v3_v3fl( + tmp, ss->cache->sculpt_normal_symm, + dot_v3v3(ss->cache->sculpt_normal_symm, direction)); + sub_v3_v3(direction, tmp); + + /* Cancel if there's no grab data. */ + if (is_zero_v3(direction)) { + return; + } + + float bstrength = data->strength; + CLAMP(bstrength, 0.0f, 1.0f); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = + sculpt_brush_test_init_with_falloff_shape(ss, &test, data->brush->falloff_shape); + + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) + { + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + const float fade = bstrength * tex_strength( + ss, brush, vd.co, sqrtf(test.dist), + vd.no, vd.fno, *vd.mask, tls->thread_id) * ss->cache->pressure; + + float avg[3], val[3]; + + bmesh_four_neighbor_average(avg, direction, vd.bm_vert); + + sub_v3_v3v3(val, avg, vd.co); + + madd_v3_v3v3fl(val, vd.co, val, fade); + + sculpt_clip(sd, ss, vd.co, val); + + if (vd.mvert) + vd.mvert->flag |= ME_VERT_PBVH_UPDATE; + } + } + BKE_pbvh_vertex_iter_end; +} + static void do_smooth_brush_multires_task_cb_ex( void *__restrict userdata, const int n, @@ -1982,6 +2136,37 @@ static void smooth( } } +static void bmesh_topology_rake( + Sculpt *sd, Object *ob, PBVHNode **nodes, const int totnode, float bstrength) +{ + Brush *brush = BKE_paint_brush(&sd->paint); + CLAMP(bstrength, 0.0f, 1.0f); + + /* Interactions increase both strength and quality. */ + const int iterations = 3; + + int iteration; + const int count = iterations * bstrength + 1; + const float factor = iterations * bstrength / count; + + for (iteration = 0; iteration <= count; ++iteration) { + + SculptThreadedTaskData data = { + .sd = sd,.ob = ob,.brush = brush,.nodes = nodes, + .strength = factor, + }; + ParallelRangeSettings 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_topology_rake_bmesh_task_cb_ex, + &settings); + } +} + static void do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode) { SculptSession *ss = ob->sculpt; @@ -3607,7 +3792,7 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe do_brush_action_task_cb, &settings); - if (sculpt_brush_needs_normal(brush, ss->cache->normal_weight)) + if (sculpt_brush_needs_normal(ss, brush)) update_sculpt_normal(sd, ob, nodes, totnode); if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_AREA) @@ -3682,6 +3867,10 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe } } + if (sculpt_brush_use_topology_rake(ss, brush)) { + bmesh_topology_rake(sd, ob, nodes, totnode, brush->topology_rake_factor); + } + if (ss->cache->supports_gravity) do_gravity(sd, ob, nodes, totnode, sd->gravity_factor); @@ -4394,7 +4583,8 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru if (ELEM(tool, SCULPT_TOOL_GRAB, SCULPT_TOOL_NUDGE, SCULPT_TOOL_CLAY_STRIPS, SCULPT_TOOL_SNAKE_HOOK, - SCULPT_TOOL_THUMB)) + SCULPT_TOOL_THUMB) || + sculpt_brush_use_topology_rake(ss, brush)) { float grab_location[3], imat[4][4], delta[3], loc[3]; @@ -4434,6 +4624,10 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru invert_m4_m4(imat, ob->obmat); mul_mat3_m4_v3(imat, cache->grab_delta); break; + default: + sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location); + break; + } } else { diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index c980fa79d40..a2721526cf5 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -272,10 +272,12 @@ typedef struct Brush { char mask_tool; /** Active grease pencil tool. */ char gpencil_tool; - char _pad0[6]; + char _pad0[2]; float autosmooth_factor; + float topology_rake_factor; + float crease_pinch_factor; float plane_trim; @@ -469,6 +471,14 @@ typedef enum eBrushSculptTool { SCULPT_TOOL_MASK \ ) == 0) +#define SCULPT_TOOL_HAS_TOPOLOGY_RAKE(t) (ELEM(t, \ + /* These brushes, as currently coded, cannot support topology rake. */ \ + SCULPT_TOOL_GRAB, \ + SCULPT_TOOL_ROTATE, \ + SCULPT_TOOL_THUMB, \ + SCULPT_TOOL_MASK \ + ) == 0) + /* ImagePaintSettings.tool */ typedef enum eBrushImagePaintTool { PAINT_TOOL_DRAW = 0, diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 4aadcb04ad5..91da8c65b07 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -174,6 +174,12 @@ static bool rna_BrushCapabilitiesSculpt_has_accumulate_get(PointerRNA *ptr) return SCULPT_TOOL_HAS_ACCUMULATE(br->sculpt_tool); } +static bool rna_BrushCapabilitiesSculpt_has_topology_rake_get(PointerRNA *ptr) +{ + Brush *br = (Brush *)ptr->data; + return SCULPT_TOOL_HAS_TOPOLOGY_RAKE(br->sculpt_tool); +} + static bool rna_BrushCapabilitiesSculpt_has_auto_smooth_get(PointerRNA *ptr) { Brush *br = (Brush *)ptr->data; @@ -866,6 +872,7 @@ static void rna_def_sculpt_capabilities(BlenderRNA *brna) SCULPT_TOOL_CAPABILITY(has_accumulate, "Has Accumulate"); SCULPT_TOOL_CAPABILITY(has_auto_smooth, "Has Auto Smooth"); + SCULPT_TOOL_CAPABILITY(has_topology_rake, "Has Topology Rake"); SCULPT_TOOL_CAPABILITY(has_height, "Has Height"); SCULPT_TOOL_CAPABILITY(has_jitter, "Has Jitter"); SCULPT_TOOL_CAPABILITY(has_normal_weight, "Has Crease/Pinch Factor"); @@ -1610,6 +1617,17 @@ static void rna_def_brush(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Autosmooth", "Amount of smoothing to automatically apply to each stroke"); RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "topology_rake_factor", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_float_sdna(prop, NULL, "topology_rake_factor"); + RNA_def_property_float_default(prop, 0); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Topology Rake", "Automatically align edges to the brush direction to " + "to generate cleaner topology and define sharp features " + "dynamic topology. Best used on low-poly meshes as it has " + "a performance impact"); + RNA_def_property_update(prop, 0, "rna_Brush_update"); + prop = RNA_def_property(srna, "stencil_pos", PROP_FLOAT, PROP_XYZ); RNA_def_property_float_sdna(prop, NULL, "stencil_pos"); RNA_def_property_array(prop, 2); |