Sculpt: Elastic Transform

This commit is contained in:
Pablo Dobarro 2020-12-15 21:38:07 +01:00
parent cab8626cf2
commit 5775efba2f
6 changed files with 190 additions and 13 deletions

View File

@ -266,9 +266,15 @@ class _defs_annotate:
class _defs_transform:
def draw_transform_sculpt_tool_settings(context, layout):
if context.mode != 'SCULPT':
return
layout.prop(context.tool_settings.sculpt, "transform_mode")
@ToolDef.from_fn
def translate():
def draw_settings(context, layout, _tool):
_defs_transform.draw_transform_sculpt_tool_settings(context, layout)
_template_widget.VIEW3D_GGT_xform_gizmo.draw_settings_with_index(context, layout, 1)
return dict(
idname="builtin.move",
@ -284,6 +290,7 @@ class _defs_transform:
@ToolDef.from_fn
def rotate():
def draw_settings(context, layout, _tool):
_defs_transform.draw_transform_sculpt_tool_settings(context, layout)
_template_widget.VIEW3D_GGT_xform_gizmo.draw_settings_with_index(context, layout, 2)
return dict(
idname="builtin.rotate",
@ -299,6 +306,7 @@ class _defs_transform:
@ToolDef.from_fn
def scale():
def draw_settings(context, layout, _tool):
_defs_transform.draw_transform_sculpt_tool_settings(context, layout)
_template_widget.VIEW3D_GGT_xform_gizmo.draw_settings_with_index(context, layout, 3)
return dict(
idname="builtin.scale",
@ -354,6 +362,7 @@ class _defs_transform:
props = tool.gizmo_group_properties("VIEW3D_GGT_xform_gizmo")
layout.prop(props, "drag_action")
_defs_transform.draw_transform_sculpt_tool_settings(context, layout)
_template_widget.VIEW3D_GGT_xform_gizmo.draw_settings_with_index(context, layout, 1)
return dict(

View File

@ -6177,15 +6177,7 @@ static void sculpt_combine_proxies_task_cb(void *__restrict userdata,
SculptSession *ss = data->ob->sculpt;
Sculpt *sd = data->sd;
Object *ob = data->ob;
/* These brushes start from original coordinates. */
const bool use_orco = ELEM(data->brush->sculpt_tool,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE);
const bool use_orco = data->use_proxies_orco;
PBVHVertexIter vd;
PBVHProxyNode *proxies;
@ -6240,11 +6232,20 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
/* First line is tools that don't support proxies. */
if (ss->cache->supports_gravity || (sculpt_tool_is_proxy_used(brush->sculpt_tool) == false)) {
const bool use_orco = ELEM(brush->sculpt_tool,
SCULPT_TOOL_GRAB,
SCULPT_TOOL_ROTATE,
SCULPT_TOOL_THUMB,
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_BOUNDARY,
SCULPT_TOOL_POSE);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.use_proxies_orco = use_orco,
};
TaskParallelSettings settings;
@ -6255,6 +6256,27 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
MEM_SAFE_FREE(nodes);
}
void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob)
{
SculptSession *ss = ob->sculpt;
PBVHNode **nodes;
int totnode;
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = nodes,
.use_proxies_orco = false,
};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, &data, sculpt_combine_proxies_task_cb, &settings);
MEM_SAFE_FREE(nodes);
}
/**
* Copy the modified vertices from the #PBVH to the active key.
*/

View File

@ -706,6 +706,10 @@ typedef struct SculptThreadedTaskData {
float (*mat)[4];
float (*vertCos)[3];
/* When true, the displacement stored in the proxies will be aplied to the original coordinates
* instead of to the current coordinates. */
bool use_proxies_orco;
/* X and Z vectors aligned to the stroke direction for operations where perpendicular vectors to
* the stroke direction are needed. */
float (*stroke_xz)[3];
@ -749,6 +753,9 @@ typedef struct SculptThreadedTaskData {
bool mask_expand_create_face_set;
float transform_mats[8][4][4];
float elastic_transform_mat[4][4];
float elastic_transform_pivot[3];
float elastic_transform_radius;
/* Boundary brush */
float boundary_deform_strength;
@ -833,6 +840,8 @@ bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]);
bool SCULPT_search_sphere_cb(PBVHNode *node, void *data_v);
bool SCULPT_search_circle_cb(PBVHNode *node, void *data_v);
void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob);
SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss,
SculptBrushTest *test,
char falloff_shape);

View File

@ -32,6 +32,7 @@
#include "BKE_brush.h"
#include "BKE_context.h"
#include "BKE_kelvinlet.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_object.h"
@ -83,7 +84,12 @@ void ED_sculpt_init_transform(struct bContext *C)
SCULPT_vertex_random_access_ensure(ss);
SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL;
if (sd->transform_mode == SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC) {
ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL;
}
else {
ss->filter_cache->transform_displacement_mode = SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL;
}
}
static void sculpt_transform_matrices_init(SculptSession *ss,
@ -123,13 +129,13 @@ static void sculpt_transform_matrices_init(SculptSession *ss,
/* Translation matrix. */
sub_v3_v3v3(d_t, ss->pivot_pos, start_pivot_pos);
SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, ss->init_pivot_pos);
SCULPT_flip_v3_by_symm_area(d_t, symm, v_symm, start_pivot_pos);
translate_m4(t_mat, d_t[0], d_t[1], d_t[2]);
/* Rotation matrix. */
sub_qt_qtqt(d_r, ss->pivot_rot, start_pivot_rot);
normalize_qt(d_r);
SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, ss->init_pivot_pos);
SCULPT_flip_quat_by_symm_area(d_r, symm, v_symm, start_pivot_pos);
quat_to_mat4(r_mat, d_r);
/* Scale matrix. */
@ -220,6 +226,94 @@ static void sculpt_transform_all_vertices(Sculpt *sd, Object *ob)
0, ss->filter_cache->totnode, &data, sculpt_transform_task_cb, &settings);
}
static void sculpt_elastic_transform_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];
float(*proxy)[3] = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[i])->co;
SculptOrigVertData orig_data;
SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[i]);
KelvinletParams params;
/* TODO(pablodp606): These parameters can be exposed if needed as transform strength and volume
* preservation like in the elastic deform brushes. Setting them to the same default as elastic
* deform triscale grab because they work well in most cases. */
const float force = 1.0f;
const float shear_modulus = 1.0f;
const float poisson_ratio = 0.4f;
BKE_kelvinlet_init_params(
&params, data->elastic_transform_radius, force, shear_modulus, poisson_ratio);
SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS);
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
{
SCULPT_orig_vert_data_update(&orig_data, &vd);
float transformed_co[3], orig_co[3], disp[3];
const float fade = vd.mask ? *vd.mask : 0.0f;
copy_v3_v3(orig_co, orig_data.co);
copy_v3_v3(transformed_co, vd.co);
mul_m4_v3(data->elastic_transform_mat, transformed_co);
sub_v3_v3v3(disp, transformed_co, vd.co);
float final_disp[3];
BKE_kelvinlet_grab_triscale(final_disp, &params, vd.co, data->elastic_transform_pivot, disp);
mul_v3_fl(final_disp, 20.0f * (1.0f - fade));
copy_v3_v3(proxy[vd.i], final_disp);
if (vd.mvert) {
vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
}
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_update(node);
}
static void sculpt_transform_radius_elastic(Sculpt *sd, Object *ob, const float transform_radius)
{
BLI_assert(ss->filter_cache->transform_displacement_mode ==
SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL);
SculptSession *ss = ob->sculpt;
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.nodes = ss->filter_cache->nodes,
.elastic_transform_radius = transform_radius,
};
sculpt_transform_matrices_init(
ss, symm, ss->filter_cache->transform_displacement_mode, data.transform_mats);
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, ss->filter_cache->totnode);
/* Elastic transform needs to apply all transform matrices to all vertices and then combine the
* displacement proxies as all vertices are modified by all symmetry passes. */
for (ePaintSymmetryFlags symmpass = 0; symmpass <= symm; symmpass++) {
if (SCULPT_is_symmetry_iteration_valid(symmpass, symm)) {
flip_v3_v3(data.elastic_transform_pivot, ss->pivot_pos, symmpass);
const int symm_area = SCULPT_get_vertex_symm_area(data.elastic_transform_pivot);
copy_m4_m4(data.elastic_transform_mat, data.transform_mats[symm_area]);
BLI_task_parallel_range(
0, ss->filter_cache->totnode, &data, sculpt_elastic_transform_task_cb, &settings);
}
}
SCULPT_combine_transform_proxies(sd, ob);
}
void ED_sculpt_update_modal_transform(struct bContext *C)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
@ -230,7 +324,19 @@ void ED_sculpt_update_modal_transform(struct bContext *C)
SCULPT_vertex_random_access_ensure(ss);
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
sculpt_transform_all_vertices(sd, ob);
switch (sd->transform_mode) {
case SCULPT_TRANSFORM_MODE_ALL_VERTICES: {
sculpt_transform_all_vertices(sd, ob);
break;
}
case SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC: {
Brush *brush = BKE_paint_brush(&sd->paint);
Scene *scene = CTX_data_scene(C);
const float transform_radius = BKE_brush_unprojected_radius_get(scene, brush);
sculpt_transform_radius_elastic(sd, ob, transform_radius);
break;
}
}
copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos);
copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot);

View File

@ -968,6 +968,9 @@ typedef struct Sculpt {
// float pivot[3]; XXX not used?
int flags;
/* Transform tool. */
int transform_mode;
int automasking_flags;
/* Control tablet input */
@ -988,6 +991,8 @@ typedef struct Sculpt {
float constant_detail;
float detail_percent;
char _pad[4];
struct Object *gravity_object;
} Sculpt;
@ -2204,6 +2209,12 @@ typedef enum eSculptFlags {
SCULPT_HIDE_FACE_SETS = (1 << 17),
} eSculptFlags;
/* Sculpt.transform_mode */
typedef enum eSculptTransformMode {
SCULPT_TRANSFORM_MODE_ALL_VERTICES = 0,
SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC = 1,
} eSculptTrasnformMode;
/* ImagePaintSettings.mode */
typedef enum eImagePaintMode {
IMAGEPAINT_MODE_MATERIAL = 0, /* detect texture paint slots from the material */

View File

@ -734,6 +734,20 @@ static void rna_def_sculpt(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem sculpt_transform_mode_items[] = {
{SCULPT_TRANSFORM_MODE_ALL_VERTICES,
"ALL_VERTICES",
0,
"All Vertices",
"Applies the transformation to all vertices in the mesh"},
{SCULPT_TRANSFORM_MODE_RADIUS_ELASTIC,
"RADIUS_ELASTIC",
0,
"Elastic",
"Applies the transformation simulating elasticity using the radius of the cursor"},
{0, NULL, 0, NULL, NULL},
};
StructRNA *srna;
PropertyRNA *prop;
@ -871,6 +885,12 @@ static void rna_def_sculpt(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Gravity", "Amount of gravity after each dab");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "transform_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, sculpt_transform_mode_items);
RNA_def_property_ui_text(
prop, "Transform Mode", "How the transformation is going to be applied to the target");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
prop = RNA_def_property(srna, "gravity_object", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(