Sculpt: New Layer Brush

The Layer brush was in Blender before 2.81, when the sculpt API was
introduced. It had a huge amount of bugs and glitches which made it
almost unusable for anything but the most trivial cases. Also, it needed
some hacks in the code just to support the persistent base.

The brush was completely rewritten using the Sculpt API. It fulfills the
same use case as the old one, but it has:
- All previous artifacts fixed
- Simpler code
- Persistent base now works with multires thanks to the sculpt API
- Small cursor widget to preview the layer height
- More controllable and smoother strength and deformation
- More correct masking support
- More predictable invert support. When using persistent base, the brush invert mode resets to layer height 0, instead of jumping from +1 to -1. The brush can still be inverted in the brush direction property.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D7147
This commit is contained in:
Pablo Dobarro 2020-04-14 21:06:49 +02:00
parent 7dd8c889f1
commit 47f46637be
Notes: blender-bot 2023-12-16 18:59:41 +01:00
Referenced by issue #75797, Persistent Base Layer Brush doesnt work first time and after Undo
Referenced by issue #75743, Crash: Sculpt Layer brush + Stroke Method "Anchored" makes Blender crash
Referenced by commit 3ddba82716, Cleanup: Remove unused PBVH node pointer
12 changed files with 163 additions and 174 deletions

View File

@ -600,19 +600,10 @@ def brush_settings(layout, context, brush, popover=False):
# use_persistent, set_persistent_base
if capabilities.has_persistence:
ob = context.sculpt_object
do_persistent = True
# not supported yet for this case
for md in ob.modifiers:
if md.type == 'MULTIRES':
do_persistent = False
break
if do_persistent:
layout.separator()
layout.prop(brush, "use_persistent")
layout.operator("sculpt.set_persistent_base")
layout.separator()
layout.separator()
layout.prop(brush, "use_persistent")
layout.operator("sculpt.set_persistent_base")
layout.separator()
if brush.sculpt_tool == 'CLAY_STRIPS':
row = layout.row()

View File

@ -279,6 +279,12 @@ typedef struct SculptClothSimulation {
} SculptClothSimulation;
typedef struct SculptLayerPersistentBase {
float co[3];
float no[3];
float disp;
} SculptLayerPersistentBase;
/* Session data (mode-specific) */
typedef struct SculptSession {
@ -329,9 +335,6 @@ typedef struct SculptSession {
unsigned int texcache_side, *texcache, texcache_actual;
struct ImagePool *tex_pool;
/* Layer brush persistence between strokes */
float (*layer_co)[3]; /* Copy of the mesh vertices' locations */
struct StrokeCache *cache;
struct FilterCache *filter_cache;
@ -359,6 +362,10 @@ typedef struct SculptSession {
float pose_origin[3];
SculptPoseIKChain *pose_ik_chain_preview;
/* Layer brush persistence between strokes */
/* This is freed with the PBVH, so it is always in sync with the mesh. */
SculptLayerPersistentBase *layer_base;
/* Transform operator */
float pivot_pos[3];
float pivot_rot[4];

View File

@ -122,7 +122,6 @@ void BKE_pbvh_build_bmesh(PBVH *bvh,
const int cd_vert_node_offset,
const int cd_face_node_offset);
void BKE_pbvh_free(PBVH *bvh);
void BKE_pbvh_free_layer_disp(PBVH *bvh);
/* Hierarchical Search in the BVH, two methods:
* - for each hit calling a callback
@ -310,14 +309,6 @@ void BKE_pbvh_face_sets_set(PBVH *bvh, int *face_sets);
void BKE_pbvh_face_sets_color_set(PBVH *bvh, int seed, int color_default);
/* Layer displacement */
/* Get the node's displacement layer, creating it if necessary */
float *BKE_pbvh_node_layer_disp_get(PBVH *pbvh, PBVHNode *node);
/* If the node has a displacement layer, free it and set to null */
void BKE_pbvh_node_layer_disp_free(PBVHNode *node);
/* vertex deformer */
float (*BKE_pbvh_vert_coords_alloc(struct PBVH *pbvh))[3];
void BKE_pbvh_vert_coords_apply(struct PBVH *pbvh, const float (*vertCos)[3], const int totvert);

View File

@ -1422,6 +1422,12 @@ void BKE_brush_sculpt_reset(Brush *br)
br->cloth_deform_type = BRUSH_CLOTH_DEFORM_DRAG;
br->flag &= ~(BRUSH_ALPHA_PRESSURE | BRUSH_SIZE_PRESSURE);
break;
case SCULPT_TOOL_LAYER:
br->flag &= ~BRUSH_SPACE_ATTEN;
br->hardness = 0.35f;
br->alpha = 1.0f;
br->height = 0.05f;
break;
default:
break;
}

View File

@ -1306,9 +1306,10 @@ static void sculptsession_free_pbvh(Object *object)
}
MEM_SAFE_FREE(ss->pmap);
MEM_SAFE_FREE(ss->pmap_mem);
MEM_SAFE_FREE(ss->layer_base);
MEM_SAFE_FREE(ss->preview_vert_index_list);
ss->preview_vert_index_count = 0;
}
@ -1353,31 +1354,17 @@ void BKE_sculptsession_free(Object *ob)
BM_log_free(ss->bm_log);
}
if (ss->texcache) {
MEM_freeN(ss->texcache);
}
MEM_SAFE_FREE(ss->texcache);
if (ss->tex_pool) {
BKE_image_pool_free(ss->tex_pool);
}
if (ss->layer_co) {
MEM_freeN(ss->layer_co);
}
MEM_SAFE_FREE(ss->orig_cos);
MEM_SAFE_FREE(ss->deform_cos);
MEM_SAFE_FREE(ss->deform_imats);
if (ss->orig_cos) {
MEM_freeN(ss->orig_cos);
}
if (ss->deform_cos) {
MEM_freeN(ss->deform_cos);
}
if (ss->deform_imats) {
MEM_freeN(ss->deform_imats);
}
if (ss->preview_vert_index_list) {
MEM_freeN(ss->preview_vert_index_list);
}
MEM_SAFE_FREE(ss->preview_vert_index_list);
if (ss->pose_ik_chain_preview) {
for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) {

View File

@ -685,8 +685,6 @@ void BKE_pbvh_free(PBVH *bvh)
if (node->face_vert_indices) {
MEM_freeN((void *)node->face_vert_indices);
}
BKE_pbvh_node_layer_disp_free(node);
if (node->bm_faces) {
BLI_gset_free(node->bm_faces, NULL);
}
@ -722,13 +720,6 @@ void BKE_pbvh_free(PBVH *bvh)
MEM_freeN(bvh);
}
void BKE_pbvh_free_layer_disp(PBVH *bvh)
{
for (int i = 0; i < bvh->totnode; i++) {
BKE_pbvh_node_layer_disp_free(&bvh->nodes[i]);
}
}
static void pbvh_iter_begin(PBVHIter *iter,
PBVH *bvh,
BKE_pbvh_SearchCallback scb,
@ -2768,26 +2759,6 @@ void BKE_pbvh_grids_update(
}
}
/* Get the node's displacement layer, creating it if necessary */
float *BKE_pbvh_node_layer_disp_get(PBVH *bvh, PBVHNode *node)
{
if (!node->layer_disp) {
int totvert = 0;
BKE_pbvh_node_num_verts(bvh, node, &totvert, NULL);
node->layer_disp = MEM_callocN(sizeof(float) * totvert, "layer disp");
}
return node->layer_disp;
}
/* If the node has a displacement layer, free it and set to null */
void BKE_pbvh_node_layer_disp_free(PBVHNode *node)
{
if (node->layer_disp) {
MEM_freeN(node->layer_disp);
node->layer_disp = NULL;
}
}
float (*BKE_pbvh_vert_coords_alloc(PBVH *pbvh))[3]
{
float(*vertCos)[3] = NULL;

View File

@ -1216,6 +1216,34 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession
}
}
void SCULPT_layer_brush_height_preview_draw(const uint gpuattr,
const Brush *brush,
const float obmat[4][4],
const float location[3],
const float normal[3],
const float rds,
const float line_width,
const float outline_col[3],
const float alpha)
{
float cursor_trans[4][4], cursor_rot[4][4];
float z_axis[4] = {0.0f, 0.0f, 1.0f, 0.0f};
float quat[4];
float height_preview_trans[3];
copy_m4_m4(cursor_trans, obmat);
madd_v3_v3v3fl(height_preview_trans, location, normal, brush->height);
translate_m4(
cursor_trans, height_preview_trans[0], height_preview_trans[1], height_preview_trans[2]);
rotation_between_vecs_to_quat(quat, z_axis, normal);
quat_to_mat4(cursor_rot, quat);
GPU_matrix_mul(cursor_trans);
GPU_matrix_mul(cursor_rot);
GPU_line_width(line_width);
immUniformColor3fvAlpha(outline_col, alpha * 0.5f);
imm_draw_circle_wire_3d(gpuattr, 0, 0, rds, 80);
}
static bool paint_use_2d_cursor(ePaintMode mode)
{
if (mode >= PAINT_MODE_TEXTURE_3D) {
@ -1476,6 +1504,21 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
GPU_matrix_pop();
}
/* Layer brush height. */
if (brush->sculpt_tool == SCULPT_TOOL_LAYER) {
GPU_matrix_push();
SCULPT_layer_brush_height_preview_draw(pos,
brush,
vc.obact->obmat,
gi.location,
gi.normal,
rds,
1.0f,
outline_col,
outline_alpha);
GPU_matrix_pop();
}
/* Update and draw dynamic mesh preview lines. */
GPU_matrix_push();
GPU_matrix_mul(vc.obact->obmat);

View File

@ -1709,10 +1709,7 @@ static void calc_area_normal_and_center_task_cb(void *__restrict userdata,
/* Update the test radius to sample the normal using the normal radius of the brush. */
if (data->brush->ob_mode == OB_MODE_SCULPT) {
float test_radius = sqrtf(normal_test.radius_squared);
/* Layer brush produces artifacts with normal and area radius. */
if (!(ss->cache && data->brush->sculpt_tool == SCULPT_TOOL_LAYER)) {
test_radius *= data->brush->normal_radius_factor;
}
test_radius *= data->brush->normal_radius_factor;
normal_test.radius = test_radius;
normal_test.radius_squared = test_radius * test_radius;
}
@ -1724,15 +1721,13 @@ static void calc_area_normal_and_center_task_cb(void *__restrict userdata,
if (data->brush->ob_mode == OB_MODE_SCULPT) {
float test_radius = sqrtf(area_test.radius_squared);
/* Layer brush produces artifacts with normal and area radius */
if (!(ss->cache && data->brush->sculpt_tool == SCULPT_TOOL_LAYER)) {
/* Enable area radius control only on Scrape for now */
if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) &&
data->brush->area_radius_factor > 0.0f) {
test_radius *= data->brush->area_radius_factor;
}
else {
test_radius *= data->brush->normal_radius_factor;
}
/* Enable area radius control only on Scrape for now */
if (ELEM(data->brush->sculpt_tool, SCULPT_TOOL_SCRAPE, SCULPT_TOOL_FILL) &&
data->brush->area_radius_factor > 0.0f) {
test_radius *= data->brush->area_radius_factor;
}
else {
test_radius *= data->brush->normal_radius_factor;
}
area_test.radius = test_radius;
area_test.radius_squared = test_radius * test_radius;
@ -4072,23 +4067,14 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
const Brush *brush = data->brush;
const float *offset = data->offset;
const bool use_persistent_base = ss->layer_base && brush->flag & BRUSH_PERSISTENT;
PBVHVertexIter vd;
SculptOrigVertData orig_data;
float *layer_disp;
const float bstrength = ss->cache->bstrength;
const float lim = (bstrength < 0.0f) ? -data->brush->height : data->brush->height;
/* XXX: layer brush needs conversion to proxy but its more complicated */
/* proxy = BKE_pbvh_node_add_proxy(ss->pbvh, nodes[n])->co; */
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
/* Why does this have to be thread-protected? */
BLI_mutex_lock(&data->mutex);
layer_disp = BKE_pbvh_node_layer_disp_get(ss->pbvh, data->nodes[n]);
BLI_mutex_unlock(&data->mutex);
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
@ -4098,38 +4084,65 @@ static void do_layer_brush_task_cb_ex(void *__restrict userdata,
SCULPT_orig_vert_data_update(&orig_data, &vd);
if (sculpt_brush_test_sq_fn(&test, orig_data.co)) {
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
sqrtf(test.dist),
vd.no,
vd.fno,
vd.mask ? *vd.mask : 0.0f,
vd.index,
tls->thread_id);
float *disp = &layer_disp[vd.i];
float val[3];
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,
tls->thread_id);
*disp += fade;
/* Don't let the displacement go past the limit. */
if ((lim < 0.0f && *disp < lim) || (lim >= 0.0f && *disp > lim)) {
*disp = lim;
}
mul_v3_v3fl(val, offset, *disp);
if (!ss->multires.active && !ss->bm && ss->layer_co && (brush->flag & BRUSH_PERSISTENT)) {
int index = vd.vert_indices[vd.i];
/* Persistent base. */
add_v3_v3(val, ss->layer_co[index]);
const int vi = vd.index;
float *disp_factor;
if (use_persistent_base) {
disp_factor = &ss->layer_base[vi].disp;
}
else {
add_v3_v3(val, orig_data.co);
disp_factor = &ss->cache->layer_displacement_factor[vi];
}
SCULPT_clip(sd, ss, vd.co, val);
/* When using persistent base, the layer brush Ctrl invert mode resets the height of the
* layer to 0. This makes possible to clean edges of previously added layers on top of the
* base. */
/* The main direction of the layers is inverted using the regular brush strength with the
* brush direction property. */
if (use_persistent_base && ss->cache->invert) {
(*disp_factor) += fabsf(fade * bstrength * (*disp_factor)) *
((*disp_factor) > 0.0f ? -1.0f : 1.0f);
}
else {
(*disp_factor) += fade * bstrength * (1.05f - fabsf(*disp_factor));
}
if (vd.mask) {
const float clamp_mask = 1.0f - *vd.mask;
CLAMP(*disp_factor, -clamp_mask, clamp_mask);
}
else {
CLAMP(*disp_factor, -1.0f, 1.0f);
}
float final_co[3];
float normal[3];
if (use_persistent_base) {
copy_v3_v3(normal, ss->layer_base[vi].no);
mul_v3_fl(normal, brush->height);
madd_v3_v3v3fl(final_co, ss->layer_base[vi].co, normal, *disp_factor);
}
else {
normal_short_to_float_v3(normal, orig_data.no);
mul_v3_fl(normal, brush->height);
madd_v3_v3v3fl(final_co, orig_data.co, normal, *disp_factor);
}
float vdisp[3];
sub_v3_v3v3(vdisp, final_co, vd.co);
mul_v3_fl(vdisp, fabsf(fade));
add_v3_v3v3(final_co, vd.co, vdisp);
SCULPT_clip(sd, ss, vd.co, final_co);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
@ -4143,24 +4156,23 @@ static void do_layer_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
float offset[3];
mul_v3_v3v3(offset, ss->cache->scale, ss->cache->sculpt_normal_symm);
if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_symmetry_pass == 0 &&
ss->cache->first_time) {
ss->cache->layer_displacement_factor = MEM_callocN(sizeof(float) * SCULPT_vertex_count_get(ss),
"layer displacement factor");
}
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.offset = offset,
};
BLI_mutex_init(&data.mutex);
PBVHParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
BKE_pbvh_parallel_range(0, totnode, &data, do_layer_brush_task_cb_ex, &settings);
BLI_mutex_end(&data.mutex);
}
static void do_inflate_brush_task_cb_ex(void *__restrict userdata,
@ -5948,6 +5960,7 @@ void SCULPT_cache_free(StrokeCache *cache)
{
MEM_SAFE_FREE(cache->dial);
MEM_SAFE_FREE(cache->surface_smooth_laplacian_disp);
MEM_SAFE_FREE(cache->layer_displacement_factor);
if (cache->pose_ik_chain) {
SCULPT_pose_ik_chain_free(cache->pose_ik_chain);
@ -6006,14 +6019,9 @@ static void sculpt_update_cache_invariants(
ss->cache = cache;
/* Set scaling adjustment. */
if (brush->sculpt_tool == SCULPT_TOOL_LAYER) {
max_scale = 1.0f;
}
else {
max_scale = 0.0f;
for (int i = 0; i < 3; i++) {
max_scale = max_ff(max_scale, fabsf(ob->scale[i]));
}
max_scale = 0.0f;
for (int i = 0; i < 3; i++) {
max_scale = max_ff(max_scale, fabsf(ob->scale[i]));
}
cache->scale[0] = max_scale / ob->scale[0];
cache->scale[1] = max_scale / ob->scale[1];
@ -6129,32 +6137,6 @@ static void sculpt_update_cache_invariants(
normalize_v3(cache->true_gravity_direction);
}
/* Initialize layer brush displacements and persistent coords. */
if (brush->sculpt_tool == SCULPT_TOOL_LAYER) {
/* Not supported yet for multires or dynamic topology. */
if (!ss->multires.active && !ss->bm && !ss->layer_co && (brush->flag & BRUSH_PERSISTENT)) {
if (!ss->layer_co) {
ss->layer_co = MEM_mallocN(sizeof(float) * 3 * ss->totvert, "sculpt mesh vertices copy");
}
if (ss->deform_cos) {
memcpy(ss->layer_co, ss->deform_cos, ss->totvert);
}
else {
for (int i = 0; i < ss->totvert; i++) {
copy_v3_v3(ss->layer_co[i], ss->mvert[i].co);
}
}
}
if (ss->bm) {
/* Free any remaining layer displacements from nodes. If not and topology changes
* from using another tool, then next layer toolstroke
* can access past disp array bounds. */
BKE_pbvh_free_layer_disp(ss->pbvh);
}
}
/* Make copies of the mesh vertex locations and normals for some tools. */
if (brush->flag & BRUSH_ANCHORED) {
cache->original = true;
@ -7290,13 +7272,25 @@ static void SCULPT_OT_brush_stroke(wmOperatorType *ot)
static int sculpt_set_persistent_base_exec(bContext *C, wmOperator *UNUSED(op))
{
SculptSession *ss = CTX_data_active_object(C)->sculpt;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
if (ss) {
if (ss->layer_co) {
MEM_freeN(ss->layer_co);
SCULPT_vertex_random_access_init(ss);
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false);
MEM_SAFE_FREE(ss->layer_base);
const int totvert = SCULPT_vertex_count_get(ss);
ss->layer_base = MEM_mallocN(sizeof(SculptLayerPersistentBase) * totvert,
"layer persistent base");
for (int i = 0; i < totvert; i++) {
copy_v3_v3(ss->layer_base[i].co, SCULPT_vertex_co_get(ss, i));
SCULPT_vertex_normal_get(ss, i, ss->layer_base[i].no);
ss->layer_base[i].disp = 0.0f;
}
ss->layer_co = NULL;
}
return OPERATOR_FINISHED;

View File

@ -781,6 +781,9 @@ typedef struct StrokeCache {
/* Stores the displacement produced by the laplacian step of HC smooth. */
float (*surface_smooth_laplacian_disp)[3];
/* Layer brush */
float *layer_displacement_factor;
float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
struct Dial *dial;

View File

@ -1314,10 +1314,6 @@ void SCULPT_undo_push_end_ex(const bool use_nested_undo)
MEM_freeN(unode->no);
unode->no = NULL;
}
if (unode->node) {
BKE_pbvh_node_layer_disp_free(unode->node);
}
}
/* We could remove this and enforce all callers run in an operator using 'OPTYPE_UNDO'. */

View File

@ -689,7 +689,6 @@ typedef enum eBrushUVSculptTool {
SCULPT_TOOL_SLIDE_RELAX, \
SCULPT_TOOL_CREASE, \
SCULPT_TOOL_BLOB, \
SCULPT_TOOL_LAYER, \
SCULPT_TOOL_INFLATE, \
SCULPT_TOOL_CLAY, \
SCULPT_TOOL_CLAY_STRIPS, \

View File

@ -2094,6 +2094,7 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_float_sdna(prop, NULL, "height");
RNA_def_property_float_default(prop, 0.5f);
RNA_def_property_range(prop, 0, 1.0f);
RNA_def_property_ui_range(prop, 0, 0.2f, 1, 3);
RNA_def_property_ui_text(
prop, "Brush Height", "Affectable height of brush (layer height for layer tool, i.e.)");
RNA_def_property_update(prop, 0, "rna_Brush_update");