Sculpt: Elastic deform type for Snake Hook

This adds deformation types to snake hook and the elastic deformation
type. This mode deforms the mesh using a kelvinlet instead of applying
the displacement directly inside the brush radius, which is great for
stylized shapes sketching.

Changes in rake rotation when using elastic are too strong when set
to 1, so I'll add a nicer way to support rake rotations with smoother
transitions in the future.

Reviewed By: sergey, JulienKaspar

Differential Revision: https://developer.blender.org/D9560
This commit is contained in:
Pablo Dobarro 2020-12-09 22:19:34 +01:00
parent 19560eef1a
commit d870a60dd9
5 changed files with 100 additions and 21 deletions

View File

@ -643,6 +643,11 @@ def brush_settings(layout, context, brush, popover=False):
layout.prop(brush, "elastic_deform_volume_preservation", slider=True)
layout.separator()
elif sculpt_tool == 'SNAKE_HOOK':
layout.separator()
layout.prop(brush, "snake_hook_deform_type")
layout.separator()
elif sculpt_tool == 'POSE':
layout.separator()
layout.prop(brush, "deform_target")

View File

@ -4232,6 +4232,8 @@ static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata,
(len_v3(grab_delta) / ss->cache->radius)) :
0.0f;
const bool do_elastic = brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC;
proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
SculptBrushTest test;
@ -4239,18 +4241,28 @@ static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata,
ss, &test, data->brush->falloff_shape);
const int thread_id = BLI_task_parallel_thread_id(tls);
KelvinletParams params;
BKE_kelvinlet_init_params(&params, ss->cache->radius, bstrength, 1.0f, 0.4f);
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 * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
sqrtf(test.dist),
vd.no,
vd.fno,
vd.mask ? *vd.mask : 0.0f,
vd.index,
thread_id);
if (do_elastic || sculpt_brush_test_sq_fn(&test, vd.co)) {
float fade;
if (do_elastic) {
fade = 1.0f;
}
else {
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,
thread_id);
}
mul_v3_v3fl(proxy[vd.i], grab_delta, fade);
@ -4289,6 +4301,17 @@ static void do_snake_hook_brush_task_cb_ex(void *__restrict userdata,
add_v3_v3(proxy[vd.i], delta_rotate);
}
if (do_elastic) {
float disp[3];
BKE_kelvinlet_grab_triscale(disp, &params, vd.co, ss->cache->location, proxy[vd.i]);
mul_v3_fl(disp, bstrength * 20.0f);
if (vd.mask) {
mul_v3_fl(disp, 1.0f - *vd.mask);
}
mul_v3_fl(disp, SCULPT_automasking_factor_get(ss->cache->automasking, ss, vd.index));
copy_v3_v3(proxy[vd.i], disp);
}
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
@ -5714,16 +5737,8 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
/* Build a list of all nodes that are potentially within the brush's area of influence */
/* These brushes need to update all nodes as they are not constrained by the brush radius */
/* Elastic deform needs all nodes to avoid artifacts as the effect of the brush is not
* constrained by the radius. */
/* Pose needs all nodes because it applies all symmetry iterations at the same time and the IK
* chain can grow to any area of the model. */
/* This can be optimized by filtering the nodes after calculating the chain. */
if (ELEM(brush->sculpt_tool,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_POSE,
SCULPT_TOOL_BOUNDARY)) {
if (SCULPT_tool_needs_all_pbvh_nodes(brush)) {
/* These brushes need to update all nodes as they are not constrained by the brush radius */
BKE_pbvh_search_gather(ss->pbvh, NULL, NULL, &nodes, &totnode);
}
else if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {

View File

@ -434,6 +434,38 @@ BLI_INLINE bool SCULPT_is_cloth_deform_brush(const Brush *brush)
brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM);
}
BLI_INLINE bool SCULPT_tool_needs_all_pbvh_nodes(const Brush *brush)
{
if (brush->sculpt_tool == SCULPT_TOOL_ELASTIC_DEFORM) {
/* Elastic deformations in any brush need all nodes to avoid artifacts as the effect
* of the Kelvinlet is not constrained by the radius. */
return true;
}
if (brush->sculpt_tool == SCULPT_TOOL_POSE) {
/* Pose needs all nodes because it applies all symmetry iterations at the same time
* and the IK chain can grow to any area of the model. */
/* TODO: This can be optimized by filtering the nodes after calculating the chain. */
return true;
}
if (brush->sculpt_tool == SCULPT_TOOL_BOUNDARY) {
/* Boundary needs all nodes because it is not possible to know where the boundary
* deformation is going to be propagated before calculating it. */
/* TODO: after calculating the boudnary info in the first iteration, it should be
* possible to get the nodes that have vertices included in any boundary deformation
* and cache them. */
return true;
}
if (brush->sculpt_tool == SCULPT_TOOL_SNAKE_HOOK &&
brush->snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC) {
/* Snake hook in elastic deform type has same requirements as the elastic deform tool. */
return true;
}
return false;
}
/* Pose Brush. */
void SCULPT_do_pose_brush(struct Sculpt *sd,
struct Object *ob,

View File

@ -402,6 +402,11 @@ typedef enum eBrushBoundaryFalloffType {
BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT = 3,
} eBrushBoundaryFalloffType;
typedef enum eBrushSnakeHookDeformType {
BRUSH_SNAKE_HOOK_DEFORM_FALLOFF = 0,
BRUSH_SNAKE_HOOK_DEFORM_ELASTIC = 1,
} eBrushSnakeHookDeformType;
/* Gpencilsettings.Vertex_mode */
typedef enum eGp_Vertex_Mode {
/* Affect to Stroke only. */
@ -573,7 +578,7 @@ typedef struct Brush {
char gpencil_sculpt_tool;
/** Active grease pencil weight tool. */
char gpencil_weight_tool;
char _pad1[2];
char _pad1[6];
float autosmooth_factor;
@ -607,6 +612,9 @@ typedef struct Brush {
int elastic_deform_type;
float elastic_deform_volume_preservation;
/* snake hook */
int snake_hook_deform_type;
/* pose */
int pose_deform_type;
float pose_offset;

View File

@ -2072,6 +2072,20 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_snake_hook_deform_type_items[] = {
{BRUSH_SNAKE_HOOK_DEFORM_FALLOFF,
"FALLOFF",
0,
"Radius Falloff",
"Applies the brush falloff in the tip of the brush"},
{BRUSH_SNAKE_HOOK_DEFORM_ELASTIC,
"ELASTIC",
0,
"Elastic",
"Modifies the entire mesh using elastic deform"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_cloth_deform_type_items[] = {
{BRUSH_CLOTH_DEFORM_DRAG, "DRAG", 0, "Drag", ""},
{BRUSH_CLOTH_DEFORM_PUSH, "PUSH", 0, "Push", ""},
@ -2309,6 +2323,11 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "snake_hook_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_snake_hook_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "cloth_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_cloth_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");