Sculpt: Enable Cloth Simulation Target for Pose and Boundary

This adds a new brush property called "Deformation Target" which
controls how the brush deformations is going to affect the mesh data. By
default is set to Geometry, which makes the brushes displace the
vertices. When set to Cloth Simulation, the deformation of the brush is
applied to the cloth solver constraints, so the simulation is
responsible to apply the final deformation. This allows to add cloth
simulation effects to other sculpt tools with minor modifications to their
code.

This patch enables Cloth Simulation deformation target for Pose and
Boundary brushes, which are tools that are already designed to work in
low poly counts and produce large deformations. This allows creating the
most common cloth effects, like bending and compressing folds, without
relying on collisions.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D8578
This commit is contained in:
Pablo Dobarro 2020-08-18 15:11:51 +02:00
parent bedd6f90ca
commit c2f0522760
8 changed files with 118 additions and 22 deletions

View File

@ -640,6 +640,8 @@ def brush_settings(layout, context, brush, popover=False):
layout.separator()
elif sculpt_tool == 'POSE':
layout.separator()
layout.prop(brush, "deform_target")
layout.separator()
layout.prop(brush, "pose_deform_type")
layout.prop(brush, "pose_origin_type")
@ -721,6 +723,8 @@ def brush_settings(layout, context, brush, popover=False):
col.prop(brush, "smear_deform_type")
elif sculpt_tool == 'BOUNDARY':
layout.prop(brush, "deform_target")
layout.separator()
col = layout.column()
col.prop(brush, "boundary_deform_type")
col.prop(brush, "boundary_falloff_type")

View File

@ -275,6 +275,19 @@ void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3])
SCULPT_vertex_normal_get(ss, SCULPT_active_vertex_get(ss), normal);
}
float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss,
const int deform_target,
PBVHVertexIter *iter)
{
switch (deform_target) {
case BRUSH_DEFORM_TARGET_GEOMETRY:
return iter->co;
case BRUSH_DEFORM_TARGET_CLOTH_SIM:
return ss->cache->cloth_sim->deformation_pos[iter->index];
}
return iter->co;
}
/* Sculpt Face Sets and Visibility. */
int SCULPT_active_face_set_get(SculptSession *ss)
@ -5690,6 +5703,16 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
SCULPT_pose_brush_init(sd, ob, ss, brush);
}
if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
if (!ss->cache->cloth_sim) {
ss->cache->cloth_sim = SCULPT_cloth_brush_simulation_create(ss, brush, 1.0f, 0.0f, false);
SCULPT_cloth_brush_simulation_init(ss, ss->cache->cloth_sim);
SCULPT_cloth_brush_build_nodes_constraints(
sd, ob, nodes, totnode, ss->cache->cloth_sim, ss->cache->location, FLT_MAX);
}
SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
}
bool invert = ss->cache->pen_flip || ss->cache->invert || brush->flag & BRUSH_DIR_IN;
/* Apply one type of brush action. */
@ -5828,6 +5851,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
do_gravity(sd, ob, nodes, totnode, sd->gravity_factor);
}
if (brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
if (SCULPT_stroke_is_main_symmetry_pass(ss->cache)) {
SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
}
}
MEM_SAFE_FREE(nodes);
/* Update average stroke position. */
@ -7308,7 +7337,8 @@ static void sculpt_brush_stroke_init(bContext *C, wmOperator *op)
need_mask = true;
}
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
if (brush->sculpt_tool == SCULPT_TOOL_CLOTH ||
brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
need_mask = true;
}

View File

@ -633,6 +633,7 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
const int symm_area = ss->cache->mirror_symmetry_pass;
SculptBoundary *boundary = ss->cache->boundaries[symm_area];
const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@ -657,12 +658,13 @@ static void do_boundary_brush_bend_task_cb_ex(void *__restrict userdata,
orig_data.co, boundary->initial_vertex_position, symm)) {
const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
float t_orig_co[3];
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
sub_v3_v3v3(t_orig_co, orig_data.co, boundary->bend.pivot_positions[vd.index]);
rotate_v3_v3v3fl(vd.co,
rotate_v3_v3v3fl(target_co,
t_orig_co,
boundary->bend.pivot_rotation_axis[vd.index],
angle * boundary->edit_info[vd.index].strength_factor * mask);
add_v3_v3(vd.co, boundary->bend.pivot_positions[vd.index]);
add_v3_v3(target_co, boundary->bend.pivot_positions[vd.index]);
}
}
@ -682,6 +684,7 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata,
const int symm_area = ss->cache->mirror_symmetry_pass;
SculptBoundary *boundary = ss->cache->boundaries[symm_area];
const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@ -699,7 +702,8 @@ static void do_boundary_brush_slide_task_cb_ex(void *__restrict userdata,
if (SCULPT_check_vertex_pivot_symmetry(
orig_data.co, boundary->initial_vertex_position, symm)) {
const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
madd_v3_v3v3fl(vd.co,
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
madd_v3_v3v3fl(target_co,
orig_data.co,
boundary->slide.directions[vd.index],
boundary->edit_info[vd.index].strength_factor * disp * mask * strength);
@ -722,6 +726,7 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata,
const int symm_area = ss->cache->mirror_symmetry_pass;
SculptBoundary *boundary = ss->cache->boundaries[symm_area];
const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@ -741,7 +746,8 @@ static void do_boundary_brush_inflate_task_cb_ex(void *__restrict userdata,
const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
float normal[3];
normal_short_to_float_v3(normal, orig_data.no);
madd_v3_v3v3fl(vd.co,
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
madd_v3_v3v3fl(target_co,
orig_data.co,
normal,
boundary->edit_info[vd.index].strength_factor * disp * mask * strength);
@ -764,6 +770,7 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata,
const int symm_area = ss->cache->mirror_symmetry_pass;
SculptBoundary *boundary = ss->cache->boundaries[symm_area];
const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@ -779,7 +786,8 @@ static void do_boundary_brush_grab_task_cb_ex(void *__restrict userdata,
if (SCULPT_check_vertex_pivot_symmetry(
orig_data.co, boundary->initial_vertex_position, symm)) {
const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
madd_v3_v3v3fl(vd.co,
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
madd_v3_v3v3fl(target_co,
orig_data.co,
ss->cache->grab_delta_symmetry,
boundary->edit_info[vd.index].strength_factor * mask * strength);
@ -802,6 +810,7 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
const int symm_area = ss->cache->mirror_symmetry_pass;
SculptBoundary *boundary = ss->cache->boundaries[symm_area];
const ePaintSymmetryFlags symm = data->sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
const Brush *brush = data->brush;
const float strength = ss->cache->bstrength;
@ -826,12 +835,13 @@ static void do_boundary_brush_twist_task_cb_ex(void *__restrict userdata,
orig_data.co, boundary->initial_vertex_position, symm)) {
const float mask = vd.mask ? 1.0f - *vd.mask : 1.0f;
float t_orig_co[3];
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
sub_v3_v3v3(t_orig_co, orig_data.co, boundary->twist.pivot_position);
rotate_v3_v3v3fl(vd.co,
rotate_v3_v3v3fl(target_co,
t_orig_co,
boundary->twist.rotation_axis,
angle * mask * boundary->edit_info[vd.index].strength_factor);
add_v3_v3(vd.co, boundary->twist.pivot_position);
add_v3_v3(target_co, boundary->twist.pivot_position);
}
}

View File

@ -134,6 +134,7 @@ static float cloth_brush_simulation_falloff_get(const Brush *brush,
#define CLOTH_SIMULATION_ITERATIONS 5
#define CLOTH_MAX_CONSTRAINTS_PER_VERTEX 1024
#define CLOTH_SIMULATION_TIME_STEP 0.01f
#define CLOTH_DEFORMATION_TARGET_STRENGTH 0.35f
static bool cloth_brush_sim_has_length_constraint(SculptClothSimulation *cloth_sim,
const int v1,
@ -297,9 +298,22 @@ static void do_cloth_brush_build_constraints_task_cb_ex(
}
}
if (cloth_is_deform_brush && len_squared < radius_squared) {
const float fade = BKE_brush_curve_strength(brush, sqrtf(len_squared), ss->cache->radius);
cloth_brush_add_deformation_constraint(data->cloth_sim, vd.index, fade);
if (brush && brush->sculpt_tool == SCULPT_TOOL_CLOTH) {
/* The cloth brush works by applying forces in most of its modes, but some of them require
* deformation coordinates to make the simulation stable. */
if (cloth_is_deform_brush && len_squared < radius_squared) {
/* When a deform brush is used as part of the cloth brush, deformation constraints are
* created with different strengths and only inside the radius of the brush. */
const float fade = BKE_brush_curve_strength(brush, sqrtf(len_squared), ss->cache->radius);
cloth_brush_add_deformation_constraint(data->cloth_sim, vd.index, fade);
}
}
else if (data->cloth_sim->deformation_pos) {
/* Any other tool that target the cloth simulation handle the falloff in
* their own code when modifying the deformation coordinates of the simulation, so
* deformation constraints are created with a fixed strength for all vercies. */
cloth_brush_add_deformation_constraint(
data->cloth_sim, vd.index, CLOTH_DEFORMATION_TARGET_STRENGTH);
}
if (pin_simulation_boundary) {
@ -716,7 +730,7 @@ static void cloth_brush_satisfy_constraints(SculptSession *ss,
}
}
static void cloth_brush_do_simulation_step(
void SCULPT_cloth_brush_do_simulation_step(
Sculpt *sd, Object *ob, SculptClothSimulation *cloth_sim, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
@ -924,7 +938,6 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
{
SculptSession *ss = ob->sculpt;
Brush *brush = BKE_paint_brush(&sd->paint);
const int totverts = SCULPT_vertex_count_get(ss);
/* In the first brush step of each symmetry pass, build the constraints for the vertices in all
* nodes inside the simulation's limits. */
@ -954,15 +967,13 @@ void SCULPT_do_cloth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode
}
/* Store the initial state in the simulation. */
for (int i = 0; i < totverts; i++) {
copy_v3_v3(ss->cache->cloth_sim->pos[i], SCULPT_vertex_co_get(ss, i));
}
SCULPT_cloth_brush_store_simulation_state(ss, ss->cache->cloth_sim);
/* Apply forces to the vertices. */
cloth_brush_apply_brush_foces(sd, ob, nodes, totnode);
/* Update and write the simulation to the nodes. */
cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
SCULPT_cloth_brush_do_simulation_step(sd, ob, ss->cache->cloth_sim, nodes, totnode);
}
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim)
@ -1189,7 +1200,7 @@ static int sculpt_cloth_filter_modal(bContext *C, wmOperator *op, const wmEvent
0, ss->filter_cache->totnode, &data, cloth_filter_apply_forces_task_cb, &settings);
/* Update and write the simulation to the nodes. */
cloth_brush_do_simulation_step(
SCULPT_cloth_brush_do_simulation_step(
sd, ob, ss->filter_cache->cloth_sim, ss->filter_cache->nodes, ss->filter_cache->totnode);
if (ss->deform_modifiers_active || ss->shapekey_active) {

View File

@ -104,6 +104,12 @@ void SCULPT_vertex_persistent_normal_get(SculptSession *ss, int index, float no[
* current coordinate of the vertex. */
void SCULPT_vertex_limit_surface_get(SculptSession *ss, int index, float r_co[3]);
/* Returns the pointer to the coordinates that should be edited from a brush tool iterator
* depending on the given deformation target. */
float *SCULPT_brush_deform_target_vertex_co_get(SculptSession *ss,
const int deform_target,
PBVHVertexIter *iter);
#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
typedef struct SculptVertexNeighborIter {
/* Storage */
@ -350,6 +356,7 @@ void SCULPT_do_cloth_brush(struct Sculpt *sd,
struct Object *ob,
struct PBVHNode **nodes,
int totnode);
void SCULPT_cloth_simulation_free(struct SculptClothSimulation *cloth_sim);
struct SculptClothSimulation *SCULPT_cloth_brush_simulation_create(struct SculptSession *ss,
@ -391,8 +398,12 @@ void SCULPT_cloth_plane_falloff_preview_draw(const uint gpuattr,
BLI_INLINE bool SCULPT_is_cloth_deform_brush(const Brush *brush)
{
return brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB;
return (brush->sculpt_tool == SCULPT_TOOL_CLOTH &&
brush->cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) ||
/* All brushes that are not the cloth brush deform the simulation using softbody
constriants instead of applying forces. */
(brush->sculpt_tool != SCULPT_TOOL_CLOTH &&
brush->deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM);
}
/* Pose Brush. */

View File

@ -165,6 +165,7 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
SculptPoseIKChain *ik_chain = ss->cache->pose_ik_chain;
SculptPoseIKChainSegment *segments = ik_chain->segments;
const Brush *brush = data->brush;
PBVHVertexIter vd;
float disp[3], new_co[3];
@ -206,7 +207,9 @@ static void do_pose_brush_task_cb_ex(void *__restrict userdata,
/* Apply the accumulated displacement to the vertex. */
add_v3_v3v3(final_pos, orig_data.co, total_disp);
copy_v3_v3(vd.co, final_pos);
float *target_co = SCULPT_brush_deform_target_vertex_co_get(ss, brush->deform_target, &vd);
copy_v3_v3(target_co, final_pos);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;

View File

@ -322,6 +322,11 @@ typedef enum eBrushCurvePreset {
BRUSH_CURVE_SMOOTHER = 9,
} eBrushCurvePreset;
typedef enum eBrushDeformTarget {
BRUSH_DEFORM_TARGET_GEOMETRY = 0,
BRUSH_DEFORM_TARGET_CLOTH_SIM = 1,
} eBrushDeformTarget;
typedef enum eBrushElasticDeformType {
BRUSH_ELASTIC_DEFORM_GRAB = 0,
BRUSH_ELASTIC_DEFORM_GRAB_BISCALE = 1,
@ -539,7 +544,7 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
char _pad0[1];
char _pad0[5];
/** Projection shape (sphere, circle). */
char falloff_shape;
@ -587,6 +592,8 @@ typedef struct Brush {
/* Maximun distance to search fake neighbors from a vertex. */
float disconnected_distance_max;
int deform_target;
/* automasking */
int automasking_flags;
int automasking_boundary_edges_propagation_steps;

View File

@ -1979,6 +1979,20 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_deformation_target_items[] = {
{BRUSH_DEFORM_TARGET_GEOMETRY,
"GEOMETRY",
0,
"Geometry",
"Brush deformation displaces the vertices of the mesh"},
{BRUSH_DEFORM_TARGET_CLOTH_SIM,
"CLOTH_SIM",
0,
"Cloth Simulation",
"Brush deforms the mesh by deforming the constraints of a cloth simulation"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_elastic_deform_type_items[] = {
{BRUSH_ELASTIC_DEFORM_GRAB, "GRAB", 0, "Grab", ""},
{BRUSH_ELASTIC_DEFORM_GRAB_BISCALE, "GRAB_BISCALE", 0, "Bi-scale Grab", ""},
@ -2207,6 +2221,12 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Curve Preset", "");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "deform_target", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_deformation_target_items);
RNA_def_property_ui_text(
prop, "Deformation Target", "How the deformation of the brush will affect the object");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "elastic_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_elastic_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");