Sculpt: Mesh Filter Tool

The mesh filter tool applies a deformation to all vertices in the mesh at the same time. It includes multiple deformation modes and the option to lock the deformation axis.
This commit also includes the FilterCache, which is needed in some new operators and tools.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D5513
This commit is contained in:
Pablo Dobarro 2019-09-09 15:42:51 +02:00
parent 97a5f961c4
commit 6a4df70d41
6 changed files with 401 additions and 4 deletions

View File

@ -5718,6 +5718,15 @@ def km_3d_view_tool_sculpt_lasso_mask(params):
]},
)
def km_3d_view_tool_sculpt_mesh_filter(params):
return (
"3D View Tool: Sculpt, Mesh Filter",
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("sculpt.mesh_filter", {"type": params.tool_tweak, "value": 'ANY'},
None)
]},
)
def km_3d_view_tool_paint_weight_sample_weight(params):
return (
@ -6159,6 +6168,7 @@ def generate_keymaps(params=None):
km_3d_view_tool_sculpt_box_hide(params),
km_3d_view_tool_sculpt_box_mask(params),
km_3d_view_tool_sculpt_lasso_mask(params),
km_3d_view_tool_sculpt_mesh_filter(params),
km_3d_view_tool_paint_weight_sample_weight(params),
km_3d_view_tool_paint_weight_sample_vertex_group(params),
km_3d_view_tool_paint_weight_gradient(params),

View File

@ -985,6 +985,22 @@ class _defs_sculpt:
keymap=(),
)
@ToolDef.from_fn
def mesh_filter():
def draw_settings(context, layout, tool):
props = tool.operator_properties("sculpt.mesh_filter")
layout.prop(props, "type", expand=False)
layout.prop(props, "strength")
layout.prop(props, "deform_axis")
return dict(
idname="builtin.mesh_filter",
label="Mesh Filter",
icon="ops.sculpt.mesh_filter",
widget=None,
keymap= (),
draw_settings=draw_settings,
)
class _defs_vertex_paint:
@ -1967,6 +1983,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
),
_defs_sculpt.hide_border,
None,
_defs_sculpt.mesh_filter,
None,
*_tools_annotate,
],
'PAINT_TEXTURE': [

View File

@ -256,6 +256,7 @@ typedef struct SculptSession {
float (*layer_co)[3]; /* Copy of the mesh vertices' locations */
struct StrokeCache *cache;
struct FilterCache *filter_cache;
/* Cursor data and active vertex for tools */
int active_vertex_index;

View File

@ -1268,7 +1268,7 @@ void BKE_sculpt_update_object_before_eval(Object *ob)
SculptSession *ss = ob->sculpt;
if (ss && ss->building_vp_handle == false) {
if (!ss->cache) {
if (!ss->cache && !ss->filter_cache) {
/* We free pbvh on changes, except in the middle of drawing a stroke
* since it can't deal with changing PVBH node organization, we hope
* topology does not change in the meantime .. weak. */

View File

@ -27,6 +27,7 @@
#include "BLI_math.h"
#include "BLI_blenlib.h"
#include "BLI_dial_2d.h"
#include "BLI_hash.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLI_ghash.h"
@ -108,6 +109,13 @@ static float *sculpt_vertex_co_get(SculptSession *ss, int index)
}
}
static void sculpt_vertex_random_access_init(SculptSession *ss)
{
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
BM_mesh_elem_index_ensure(ss->bm, BM_VERT);
}
}
#if 0 /* UNUSED */
static int sculpt_active_vertex_get(SculptSession *ss)
@ -134,6 +142,7 @@ static int sculpt_vertex_count_get(SculptSession *ss)
}
}
static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3])
{
switch (BKE_pbvh_type(ss->pbvh)) {
@ -4766,12 +4775,12 @@ static void sculpt_flush_stroke_deform_task_cb(void *__restrict userdata,
}
/* flush displacement from deformed PBVH to original layer */
static void sculpt_flush_stroke_deform(Sculpt *sd, Object *ob)
static void sculpt_flush_stroke_deform(Sculpt *sd, Object *ob, bool is_proxy_used)
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
if (sculpt_tool_is_proxy_used(brush->sculpt_tool)) {
if (is_proxy_used) {
/* this brushes aren't using proxies, so sculpt_combine_proxies() wouldn't
* propagate needed deformation to original base */
@ -6109,7 +6118,7 @@ static void sculpt_stroke_update_step(bContext *C,
* sculpt_flush_update_step().
*/
if (ss->modifiers_active) {
sculpt_flush_stroke_deform(sd, ob);
sculpt_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush->sculpt_tool));
}
else if (ss->kb) {
sculpt_update_keyblock(ob);
@ -7256,6 +7265,346 @@ static void SCULPT_OT_set_detail_size(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static void filter_cache_init_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;
BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
{
if (!vd.mask || (vd.mask && *vd.mask < 1.0f)) {
data->node_mask[i] = 1;
}
}
BKE_pbvh_vertex_iter_end;
if (data->node_mask[i] == 1) {
sculpt_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS);
}
}
static void sculpt_filter_cache_init(Object *ob, Sculpt *sd)
{
SculptSession *ss = ob->sculpt;
PBVH *pbvh = ob->sculpt->pbvh;
PBVHNode **nodes;
int totnode;
ss->filter_cache = MEM_callocN(sizeof(FilterCache), "filter cache");
ss->filter_cache->random_seed = rand();
SculptSearchSphereData search_data = {
.original = true,
};
BKE_pbvh_search_gather(pbvh, NULL, &search_data, &nodes, &totnode);
int *node_mask = MEM_callocN((unsigned int)totnode * sizeof(int), "node mask");
for (int i = 0; i < totnode; i++) {
BKE_pbvh_node_mark_normals_update(nodes[i]);
}
BKE_pbvh_update_normals(ss->pbvh, NULL);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = nodes,
.node_mask = node_mask,
};
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, filter_cache_init_task_cb, &settings);
int tot_active_nodes = 0;
int active_node_index = 0;
PBVHNode **active_nodes;
/* Count number of PBVH nodes that are not fully masked */
for (int i = 0; i < totnode; i++) {
if (node_mask[i] == 1) {
tot_active_nodes++;
}
}
/* Create the final list of nodes that is going to be processed in the filter */
active_nodes = MEM_callocN(tot_active_nodes * sizeof(PBVHNode *), "active nodes");
for (int i = 0; i < totnode; i++) {
if (node_mask[i] == 1) {
active_nodes[active_node_index] = nodes[i];
active_node_index++;
}
}
ss->filter_cache->nodes = active_nodes;
ss->filter_cache->totnode = tot_active_nodes;
if (nodes) {
MEM_freeN(nodes);
}
if (node_mask) {
MEM_freeN(node_mask);
}
}
static void sculpt_filter_cache_free(SculptSession *ss)
{
if (ss->filter_cache->nodes) {
MEM_freeN(ss->filter_cache->nodes);
}
if (ss->filter_cache->mask_update_it) {
MEM_freeN(ss->filter_cache->mask_update_it);
}
MEM_freeN(ss->filter_cache);
ss->filter_cache = NULL;
}
typedef enum eSculptMeshFilterTypes {
MESH_FILTER_SMOOTH = 0,
MESH_FILTER_SCALE = 1,
MESH_FILTER_INFLATE = 2,
MESH_FILTER_SPHERE = 3,
MESH_FILTER_RANDOM = 4,
} eSculptMeshFilterTypes;
static EnumPropertyItem prop_mesh_filter_types[] = {
{MESH_FILTER_SMOOTH, "SMOOTH", 0, "Smooth", "Smooth mesh"},
{MESH_FILTER_SCALE, "SCALE", 0, "Scale", "Scale mesh"},
{MESH_FILTER_INFLATE, "INFLATE", 0, "Inflate", "Inflate mesh"},
{MESH_FILTER_SPHERE, "SPHERE", 0, "Sphere", "Morph into sphere"},
{MESH_FILTER_RANDOM, "RANDOM", 0, "Random", "Randomize vertex positions"},
{0, NULL, 0, NULL, NULL},
};
typedef enum eMeshFilterDeformAxis {
MESH_FILTER_DEFORM_X = 1 << 0,
MESH_FILTER_DEFORM_Y = 1 << 1,
MESH_FILTER_DEFORM_Z = 1 << 2,
} eMeshFilterDeformAxis;
static EnumPropertyItem prop_mesh_filter_deform_axis_items[] = {
{MESH_FILTER_DEFORM_X, "X", 0, "X", "Deform in the X axis"},
{MESH_FILTER_DEFORM_Y, "Y", 0, "Y", "Deform in the Y axis"},
{MESH_FILTER_DEFORM_Z, "Z", 0, "Z", "Deform in the Z axis"},
{0, NULL, 0, NULL, NULL},
};
static void mesh_filter_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];
const int filter_type = data->filter_type;
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 orig_co[3], val[3], avg[3], normal[3], disp[3], disp2[3], transform[3][3], final_pos[3];
float fade = vd.mask ? *vd.mask : 0.0f;
fade = 1 - fade;
fade *= data->filter_strength;
copy_v3_v3(orig_co, orig_data.co);
switch (filter_type) {
case MESH_FILTER_SMOOTH:
CLAMP(fade, -1.0f, 1.0f);
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
neighbor_average(ss, avg, vd.index);
}
else if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
bmesh_neighbor_average(avg, vd.bm_vert);
}
sub_v3_v3v3(val, avg, orig_co);
madd_v3_v3v3fl(val, orig_co, val, fade);
sub_v3_v3v3(disp, val, orig_co);
break;
case MESH_FILTER_INFLATE:
normal_short_to_float_v3(normal, orig_data.no);
mul_v3_v3fl(disp, normal, fade);
break;
case MESH_FILTER_SCALE:
unit_m3(transform);
scale_m3_fl(transform, 1 + fade);
copy_v3_v3(val, orig_co);
mul_m3_v3(transform, val);
sub_v3_v3v3(disp, val, orig_co);
break;
case MESH_FILTER_SPHERE:
normalize_v3_v3(disp, orig_co);
if (fade > 0) {
mul_v3_v3fl(disp, disp, fade);
}
else {
mul_v3_v3fl(disp, disp, -fade);
}
unit_m3(transform);
if (fade > 0) {
scale_m3_fl(transform, 1 - fade);
}
else {
scale_m3_fl(transform, 1 + fade);
}
copy_v3_v3(val, orig_co);
mul_m3_v3(transform, val);
sub_v3_v3v3(disp2, val, orig_co);
mid_v3_v3v3(disp, disp, disp2);
break;
case MESH_FILTER_RANDOM:
normal_short_to_float_v3(normal, orig_data.no);
mul_v3_fl(normal, BLI_hash_int_01(vd.index ^ ss->filter_cache->random_seed) - 0.5f);
mul_v3_v3fl(disp, normal, fade);
break;
}
for (int it = 0; it < 3; it++) {
if (!ss->filter_cache->enabled_axis[it]) {
disp[it] = 0.0f;
}
}
add_v3_v3v3(final_pos, orig_co, disp);
copy_v3_v3(vd.co, final_pos);
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);
}
static int sculpt_mesh_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;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
int filter_type = RNA_enum_get(op->ptr, "type");
float filter_strength = RNA_float_get(op->ptr, "strength");
if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
sculpt_filter_cache_free(ss);
sculpt_undo_push_end();
sculpt_flush_update_done(C, ob);
return OPERATOR_FINISHED;
}
if (event->type != MOUSEMOVE) {
return OPERATOR_RUNNING_MODAL;
}
float len = event->prevclickx - event->mval[0];
filter_strength = filter_strength * -len * 0.001f * UI_DPI_FAC;
sculpt_vertex_random_access_init(ss);
bool needs_pmap = (filter_type == MESH_FILTER_SMOOTH);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = ss->filter_cache->nodes,
.filter_type = filter_type,
.filter_strength = filter_strength,
};
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, mesh_filter_task_cb, &settings);
if (ss->modifiers_active || ss->kb) {
sculpt_flush_stroke_deform(sd, ob, true);
}
sculpt_flush_update_step(C);
return OPERATOR_RUNNING_MODAL;
}
static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
Object *ob = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
int filter_type = RNA_enum_get(op->ptr, "type");
SculptSession *ss = ob->sculpt;
PBVH *pbvh = ob->sculpt->pbvh;
int deform_axis = RNA_enum_get(op->ptr, "deform_axis");
if (deform_axis == 0) {
return OPERATOR_CANCELLED;
}
sculpt_vertex_random_access_init(ss);
bool needs_pmap = (filter_type == MESH_FILTER_SMOOTH);
BKE_sculpt_update_object_for_edit(depsgraph, ob, needs_pmap, false);
if (BKE_pbvh_type(pbvh) == PBVH_FACES && needs_pmap && !ob->sculpt->pmap) {
return OPERATOR_CANCELLED;
}
sculpt_undo_push_begin("Mesh filter");
sculpt_filter_cache_init(ob, sd);
ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Filter mesh";
ot->idname = "SCULPT_OT_mesh_filter";
ot->description = "Applies a filter to modify the current mesh";
/* api callbacks */
ot->invoke = sculpt_mesh_filter_invoke;
ot->modal = sculpt_mesh_filter_modal;
ot->poll = sculpt_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* rna */
RNA_def_enum(ot->srna,
"type",
prop_mesh_filter_types,
MESH_FILTER_INFLATE,
"Filter type",
"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_flag(ot->srna,
"deform_axis",
prop_mesh_filter_deform_axis_items,
MESH_FILTER_DEFORM_X | MESH_FILTER_DEFORM_Y | MESH_FILTER_DEFORM_Z,
"Deform axis",
"Apply the deformation in the selected axis");
}
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@ -7267,4 +7616,5 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_detail_flood_fill);
WM_operatortype_append(SCULPT_OT_sample_detail_size);
WM_operatortype_append(SCULPT_OT_set_detail_size);
WM_operatortype_append(SCULPT_OT_mesh_filter);
}

View File

@ -175,6 +175,10 @@ typedef struct SculptThreadedTaskData {
int *count;
bool any_vertex_sampled;
int filter_type;
float filter_strength;
int *node_mask;
ThreadMutex mutex;
} SculptThreadedTaskData;
@ -341,6 +345,20 @@ typedef struct StrokeCache {
} StrokeCache;
typedef struct FilterCache {
bool enabled_axis[3];
int random_seed;
/* unmasked nodes */
PBVHNode **nodes;
int totnode;
/* mask expand iteration caches */
int mask_update_current_it;
int mask_update_last_it;
int *mask_update_it;
} FilterCache;
void sculpt_cache_calc_brushdata_symm(StrokeCache *cache,
const char symm,
const char axis,