D15041: Sculpt: Elastic Transform

This implements transform modes for the transform tool and Elastic
Transform. This mode uses the Kelvinlets from elastic deform to apply
the transformation to the mesh, using the cursor radius to control the
elasticity falloff.

{F9269771}

In order for this to work, the transform tool uses incremental mode when
elastic transform is enabled. This allows to integrate the displacement of
the Kelvinet in multiple steps.

Review By: Sergey Sharbin & Daniel Bystedt & Julian Kaspar & Campbell
Barton

Differential Revision: https://developer.blender.org/D9653

Ref D15041
This commit is contained in:
Pablo Dobarro 2022-06-03 19:21:04 -07:00 committed by Joseph Eagar
parent e230ccaf8c
commit e90ba74d3e
7 changed files with 263 additions and 19 deletions

View File

@ -261,9 +261,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",
@ -279,6 +285,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",
@ -294,6 +301,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",
@ -349,6 +357,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

@ -613,6 +613,10 @@ typedef struct SculptSession {
float init_pivot_rot[4];
float init_pivot_scale[3];
float prev_pivot_pos[3];
float prev_pivot_rot[4];
float prev_pivot_scale[3];
union {
struct {
struct SculptVertexPaintGeomMap gmap;

View File

@ -3535,15 +3535,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;
@ -3598,12 +3590,23 @@ static void sculpt_combine_proxies(Sculpt *sd, Object *ob)
return;
}
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
SculptThreadedTaskData data = {
/* First line is tools that don't support proxies. */
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);
BKE_pbvh_gather_proxies(ss->pbvh, &nodes, &totnode);
SculptThreadedTaskData data = {
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.use_proxies_orco = use_orco,
};
TaskParallelSettings settings;
@ -3612,6 +3615,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

@ -247,6 +247,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];
@ -290,6 +294,10 @@ 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_pivot_init[3];
float elastic_transform_radius;
/* Boundary brush */
float boundary_deform_strength;
@ -372,6 +380,14 @@ typedef enum SculptFilterOrientation {
SCULPT_FILTER_ORIENTATION_VIEW = 2,
} SculptFilterOrientation;
/* Defines how transform tools are going to apply its displacement. */
typedef enum SculptTransformDisplacementMode {
/* Displaces the elements from their original coordinates. */
SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL = 0,
/* Displaces the elements incrementally from their previous position. */
SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL = 1,
} SculptTransformDisplacementMode;
#define SCULPT_CLAY_STABILIZER_LEN 10
typedef struct AutomaskingSettings {
@ -440,6 +456,8 @@ typedef struct FilterCache {
int active_face_set;
SculptTransformDisplacementMode transform_displacement_mode;
/* Auto-masking. */
AutomaskingCache *automasking;
@ -1137,12 +1155,15 @@ 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);
/**
* Initialize a point-in-brush test with a given falloff shape.
*
* \param falloff_shape: #PAINT_FALLOFF_SHAPE_SPHERE or #PAINT_FALLOFF_SHAPE_TUBE.
* \return The brush falloff function.
*/
SculptBrushTestFn SCULPT_brush_test_init_with_falloff_shape(SculptSession *ss,
SculptBrushTest *test,
char falloff_shape);

View File

@ -16,6 +16,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"
@ -33,6 +34,7 @@
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_view3d.h"
#include "paint_intern.h"
#include "sculpt_intern.h"
@ -54,6 +56,10 @@ void ED_sculpt_init_transform(struct bContext *C, Object *ob)
copy_v4_v4(ss->init_pivot_rot, ss->pivot_rot);
copy_v3_v3(ss->init_pivot_scale, ss->pivot_scale);
copy_v3_v3(ss->prev_pivot_pos, ss->pivot_pos);
copy_v4_v4(ss->prev_pivot_rot, ss->pivot_rot);
copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale);
SCULPT_undo_push_begin(ob, "Transform");
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
@ -61,10 +67,18 @@ void ED_sculpt_init_transform(struct bContext *C, Object *ob)
SCULPT_vertex_random_access_ensure(ss);
SCULPT_filter_cache_init(C, ob, sd, SCULPT_UNDO_COORDS);
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,
const char symm,
const SculptTransformDisplacementMode t_mode,
float r_transform_mats[8][4][4])
{
@ -73,9 +87,18 @@ static void sculpt_transform_matrices_init(SculptSession *ss,
transform_mat[4][4];
float start_pivot_pos[3], start_pivot_rot[4], start_pivot_scale[3];
copy_v3_v3(start_pivot_pos, ss->init_pivot_pos);
copy_v4_v4(start_pivot_rot, ss->init_pivot_rot);
copy_v3_v3(start_pivot_scale, ss->init_pivot_scale);
switch (t_mode) {
case SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL:
copy_v3_v3(start_pivot_pos, ss->init_pivot_pos);
copy_v4_v4(start_pivot_rot, ss->init_pivot_rot);
copy_v3_v3(start_pivot_scale, ss->init_pivot_scale);
break;
case SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL:
copy_v3_v3(start_pivot_pos, ss->prev_pivot_pos);
copy_v4_v4(start_pivot_rot, ss->prev_pivot_rot);
copy_v3_v3(start_pivot_scale, ss->prev_pivot_scale);
break;
}
for (int i = 0; i < PAINT_SYMM_AREAS; i++) {
ePaintSymmetryAreas v_symm = i;
@ -134,16 +157,26 @@ static void sculpt_transform_task_cb(void *__restrict userdata,
SCULPT_undo_push_node(data->ob, node, SCULPT_UNDO_COORDS);
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
SCULPT_orig_vert_data_update(&orig_data, &vd);
float *start_co;
float transformed_co[3], orig_co[3], disp[3];
float fade = vd.mask ? *vd.mask : 0.0f;
copy_v3_v3(orig_co, orig_data.co);
char symm_area = SCULPT_get_vertex_symm_area(orig_co);
copy_v3_v3(transformed_co, orig_co);
switch (ss->filter_cache->transform_displacement_mode) {
case SCULPT_TRANSFORM_DISPLACEMENT_ORIGINAL:
start_co = orig_co;
break;
case SCULPT_TRANSFORM_DISPLACEMENT_INCREMENTAL:
start_co = vd.co;
break;
}
copy_v3_v3(transformed_co, start_co);
mul_m4_v3(data->transform_mats[(int)symm_area], transformed_co);
sub_v3_v3v3(disp, transformed_co, orig_co);
sub_v3_v3v3(disp, transformed_co, start_co);
mul_v3_fl(disp, 1.0f - fade);
add_v3_v3v3(vd.co, orig_co, disp);
add_v3_v3v3(vd.co, start_co, disp);
if (vd.mvert) {
BKE_pbvh_vert_mark_update(ss->pbvh, vd.index);
@ -165,7 +198,8 @@ static void sculpt_transform_all_vertices(Sculpt *sd, Object *ob)
.nodes = ss->filter_cache->nodes,
};
sculpt_transform_matrices_init(ss, symm, data.transform_mats);
sculpt_transform_matrices_init(
ss, symm, ss->filter_cache->transform_displacement_mode, data.transform_mats);
/* Regular transform applies all symmetry passes at once as it is split by symmetry areas
* (each vertex can only be transformed once by the transform matrix of its area). */
@ -175,6 +209,98 @@ 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) {
BKE_pbvh_vert_mark_update(ss->pbvh, vd.index);
}
}
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);
flip_v3_v3(data.elastic_transform_pivot_init, ss->init_pivot_pos, symmpass);
printf(
"%.2f %.2f %.2f\n", ss->init_pivot_pos[0], ss->init_pivot_pos[1], ss->init_pivot_pos[2]);
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, Object *ob)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
@ -184,7 +310,36 @@ void ED_sculpt_update_modal_transform(struct bContext *C, Object *ob)
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);
float transform_radius;
if (BKE_brush_use_locked_size(scene, brush)) {
transform_radius = BKE_brush_unprojected_radius_get(scene, brush);
}
else {
ViewContext vc;
ED_view3d_viewcontext_init(C, &vc, depsgraph);
transform_radius = paint_calc_object_space_radius(
&vc, ss->init_pivot_pos, BKE_brush_size_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);
copy_v3_v3(ss->prev_pivot_scale, ss->pivot_scale);
if (ss->deform_modifiers_active || ss->shapekey_active) {
SCULPT_flush_stroke_deform(sd, ob, true);

View File

@ -991,6 +991,9 @@ typedef struct Sculpt {
// float pivot[3]; XXX not used?
int flags;
/* Transform tool. */
int transform_mode;
int automasking_flags;
/* Control tablet input */
@ -1011,6 +1014,8 @@ typedef struct Sculpt {
float constant_detail;
float detail_percent;
char _pad[4];
struct Object *gravity_object;
} Sculpt;
@ -2279,6 +2284,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;
/** PaintModeSettings.mode */
typedef enum ePaintCanvasSource {
/** Paint on the active node of the active material slot. */

View File

@ -774,6 +774,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;
@ -912,6 +926,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(