Sculpt: Pose Brush option to affect loose parts

This option allows posing meshes with different disconnected elements
using the Pose Brush.

This is achieved by doing the following:
- Creating an ID per vertex that stores the connected component of that vertex.
- By using those IDs, one fake topology connection is created per vertex to the nearest vertex in a different ID. The maximum distance to create that connection is determined by the "Max Element Distance" property. These fake connectivity neighbors are used in the Sculpt API functions iterators, so all the algorithms of the Pose Brush can run without modifications as if everything was part of the same mesh.

In order to make this work, the "Connected only" property of the Pose Brush needs to be disabled. This will add an extra performance cost to the Pose Brush and its preview. To achieve optimal results, max element distance should be as low as possible.

Reviewed By: sergey, campbellbarton

Differential Revision: https://developer.blender.org/D7282
This commit is contained in:
Pablo Dobarro 2020-06-23 16:10:47 +02:00
parent 6ac235a6f7
commit 438bd82371
11 changed files with 343 additions and 5 deletions

View File

@ -636,6 +636,10 @@ def brush_settings(layout, context, brush, popover=False):
if brush.pose_deform_type == 'ROTATE_TWIST' and brush.pose_origin_type in {'TOPOLOGY', 'FACE_SETS'}:
layout.prop(brush, "pose_ik_segments")
layout.prop(brush, "use_pose_ik_anchored")
layout.prop(brush, "use_connected_only")
layout.prop(brush, "disconnected_distance_max")
layout.separator()
if brush.sculpt_tool == 'CLOTH':

View File

@ -289,6 +289,22 @@ typedef struct SculptLayerPersistentBase {
float disp;
} SculptLayerPersistentBase;
typedef struct SculptVertexInfo {
/* Idexed by vertex, stores and ID of its topologycally connected component. */
int *connected_component;
} SculptVertexInfo;
typedef struct SculptFakeNeighbors {
bool use_fake_neighbors;
/* Max distance used to calculate neighborhood information. */
float current_max_distance;
/* Idexed by vertex, stores the vertex index of its fake neighbor if available. */
int *fake_neighbor_index;
} SculptFakeNeighbors;
/* Session data (mode-specific) */
typedef struct SculptSession {
@ -380,6 +396,9 @@ typedef struct SculptSession {
/* This is freed with the PBVH, so it is always in sync with the mesh. */
SculptLayerPersistentBase *layer_base;
SculptVertexInfo vertex_info;
SculptFakeNeighbors fake_neighbors;
/* Transform operator */
float pivot_pos[3];
float pivot_rot[4];

View File

@ -248,6 +248,7 @@ static void brush_defaults(Brush *brush)
FROM_DEFAULT(crease_pinch_factor);
FROM_DEFAULT(normal_radius_factor);
FROM_DEFAULT(area_radius_factor);
FROM_DEFAULT(disconnected_distance_max);
FROM_DEFAULT(sculpt_plane);
FROM_DEFAULT(plane_offset);
FROM_DEFAULT(clone.alpha);
@ -1558,7 +1559,7 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_POSE:
br->pose_smooth_iterations = 4;
br->pose_ik_segments = 1;
br->flag2 |= BRUSH_POSE_IK_ANCHORED;
br->flag2 |= BRUSH_POSE_IK_ANCHORED | BRUSH_USE_CONNECTED_ONLY;
br->flag &= ~BRUSH_ALPHA_PRESSURE;
br->flag &= ~BRUSH_SPACE;
br->flag &= ~BRUSH_SPACE_ATTEN;

View File

@ -1368,6 +1368,9 @@ void BKE_sculptsession_free(Object *ob)
MEM_SAFE_FREE(ss->preview_vert_index_list);
MEM_SAFE_FREE(ss->vertex_info.connected_component);
MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index);
if (ss->pose_ik_chain_preview) {
for (int i = 0; i < ss->pose_ik_chain_preview->tot_segments; i++) {
MEM_SAFE_FREE(ss->pose_ik_chain_preview->segments[i].weights);

View File

@ -1759,6 +1759,14 @@ void do_versions_after_linking_280(Main *bmain, ReportList *UNUSED(reports))
brush->tip_scale_x = 1.0f;
}
}
/* Pose Brush with support for loose parts. */
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
if (brush->sculpt_tool == SCULPT_TOOL_POSE && brush->disconnected_distance_max == 0.0f) {
brush->flag2 |= BRUSH_USE_CONNECTED_ONLY;
brush->disconnected_distance_max = 0.1f;
}
}
}
}

View File

@ -649,6 +649,13 @@ static void sculpt_vertex_neighbors_get_faces(SculptSession *ss,
}
}
}
if (ss->fake_neighbors.use_fake_neighbors) {
BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL);
if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) {
sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]);
}
}
}
static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
@ -681,6 +688,13 @@ static void sculpt_vertex_neighbors_get_grids(SculptSession *ss,
neighbors.coords[i].y * key->grid_size + neighbors.coords[i].x);
}
if (ss->fake_neighbors.use_fake_neighbors) {
BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL);
if (ss->fake_neighbors.fake_neighbor_index[index] != FAKE_NEIGHBOR_NONE) {
sculpt_vertex_neighbor_add(iter, ss->fake_neighbors.fake_neighbor_index[index]);
}
}
if (neighbors.coords != neighbors.coords_fixed) {
MEM_freeN(neighbors.coords);
}
@ -7113,6 +7127,9 @@ void SCULPT_flush_update_done(const bContext *C, Object *ob, SculptUpdateType up
if (update_flags & SCULPT_UPDATE_COORDS) {
BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateOriginalBB);
/* Coordinates were modified, so fake neighbors are not longer valid. */
SCULPT_fake_neighbors_free(ob);
}
if (update_flags & SCULPT_UPDATE_MASK) {
@ -8139,6 +8156,251 @@ static void SCULPT_OT_sample_color(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER;
}
/* Fake Neighbors. */
/* This allows the sculpt tools to work on meshes with multiple connected components as they had
* only one connected component. When initialized and enabled, the sculpt API will return extra
* connectivity neighbors that are not in the real mesh. These neighbors are calculated for each
* vertex using the minimun distance to a vertex that is in a different connected component. */
/* The fake neighbors first need to be ensured to be initialized.
* After that tools which needs fake neighbors functionality need to
* temporarily enable it:
*
* void my_awesome_sculpt_tool() {
* SCULPT_fake_neighbors_ensure(sd, object, brush->disconnected_distance_max);
* SCULPT_fake_neighbors_enable(ob);
*
* ... Logic of the tool ...
* SCULPT_fake_neighbors_disable(ob);
* }
*
* Such approach allows to keep all the connectivity information ready for reuse
* (withouy having lag prior to every stroke), but also makes it so the affect
* is localized to a specific brushes and tools only. */
enum {
SCULPT_TOPOLOGY_ID_NONE,
SCULPT_TOPOLOGY_ID_DEFAULT,
};
static int SCULPT_vertex_get_connected_component(SculptSession *ss, int index)
{
if (ss->vertex_info.connected_component) {
return ss->vertex_info.connected_component[index];
}
return SCULPT_TOPOLOGY_ID_DEFAULT;
}
static void SCULPT_fake_neighbor_init(SculptSession *ss, const float max_dist)
{
const int totvert = SCULPT_vertex_count_get(ss);
ss->fake_neighbors.fake_neighbor_index = MEM_malloc_arrayN(
totvert, sizeof(int), "fake neighbor");
for (int i = 0; i < totvert; i++) {
ss->fake_neighbors.fake_neighbor_index[i] = FAKE_NEIGHBOR_NONE;
}
ss->fake_neighbors.current_max_distance = max_dist;
}
static void SCULPT_fake_neighbor_add(SculptSession *ss, int v_index_a, int v_index_b)
{
if (ss->fake_neighbors.fake_neighbor_index[v_index_a] == FAKE_NEIGHBOR_NONE) {
ss->fake_neighbors.fake_neighbor_index[v_index_a] = v_index_b;
ss->fake_neighbors.fake_neighbor_index[v_index_b] = v_index_a;
}
}
static void sculpt_pose_fake_neighbors_free(SculptSession *ss)
{
MEM_SAFE_FREE(ss->fake_neighbors.fake_neighbor_index);
}
typedef struct NearestVertexFakeNeighborTLSData {
int nearest_vertex_index;
float nearest_vertex_distance_squared;
int current_topology_id;
} NearestVertexFakeNeighborTLSData;
static void do_fake_neighbor_search_task_cb(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict tls)
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
NearestVertexFakeNeighborTLSData *nvtd = tls->userdata_chunk;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
int vd_topology_id = SCULPT_vertex_get_connected_component(ss, vd.index);
if (vd_topology_id != nvtd->current_topology_id &&
ss->fake_neighbors.fake_neighbor_index[vd.index] == FAKE_NEIGHBOR_NONE) {
float distance_squared = len_squared_v3v3(vd.co, data->nearest_vertex_search_co);
if (distance_squared < nvtd->nearest_vertex_distance_squared &&
distance_squared < data->max_distance_squared) {
nvtd->nearest_vertex_index = vd.index;
nvtd->nearest_vertex_distance_squared = distance_squared;
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void fake_neighbor_search_reduce(const void *__restrict UNUSED(userdata),
void *__restrict chunk_join,
void *__restrict chunk)
{
NearestVertexFakeNeighborTLSData *join = chunk_join;
NearestVertexFakeNeighborTLSData *nvtd = chunk;
if (join->nearest_vertex_index == -1) {
join->nearest_vertex_index = nvtd->nearest_vertex_index;
join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared;
}
else if (nvtd->nearest_vertex_distance_squared < join->nearest_vertex_distance_squared) {
join->nearest_vertex_index = nvtd->nearest_vertex_index;
join->nearest_vertex_distance_squared = nvtd->nearest_vertex_distance_squared;
}
}
static int SCULPT_fake_neighbor_search(Sculpt *sd, Object *ob, const int index, float max_distance)
{
SculptSession *ss = ob->sculpt;
PBVHNode **nodes = NULL;
int totnode;
SculptSearchSphereData data = {
.ss = ss,
.sd = sd,
.radius_squared = max_distance * max_distance,
.original = false,
.center = SCULPT_vertex_co_get(ss, index),
};
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, &totnode);
if (totnode == 0) {
return -1;
}
SculptThreadedTaskData task_data = {
.sd = sd,
.ob = ob,
.nodes = nodes,
.max_distance_squared = max_distance * max_distance,
};
copy_v3_v3(task_data.nearest_vertex_search_co, SCULPT_vertex_co_get(ss, index));
NearestVertexFakeNeighborTLSData nvtd;
nvtd.nearest_vertex_index = -1;
nvtd.nearest_vertex_distance_squared = FLT_MAX;
nvtd.current_topology_id = SCULPT_vertex_get_connected_component(ss, index);
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
settings.func_reduce = fake_neighbor_search_reduce;
settings.userdata_chunk = &nvtd;
settings.userdata_chunk_size = sizeof(NearestVertexFakeNeighborTLSData);
BLI_task_parallel_range(0, totnode, &task_data, do_fake_neighbor_search_task_cb, &settings);
MEM_SAFE_FREE(nodes);
return nvtd.nearest_vertex_index;
}
typedef struct SculptTopologyIDFloodFillData {
int next_id;
} SculptTopologyIDFloodFillData;
static bool SCULPT_connected_components_floodfill_cb(
SculptSession *ss, int from_v, int to_v, bool UNUSED(is_duplicate), void *userdata)
{
SculptTopologyIDFloodFillData *data = userdata;
ss->vertex_info.connected_component[from_v] = data->next_id;
ss->vertex_info.connected_component[to_v] = data->next_id;
return true;
}
void SCULPT_connected_components_ensure(Object *ob)
{
SculptSession *ss = ob->sculpt;
/* Topology IDs already initialized. They only need to be recalculated when the PBVH is rebuild.
*/
if (ss->vertex_info.connected_component) {
return;
}
const int totvert = SCULPT_vertex_count_get(ss);
ss->vertex_info.connected_component = MEM_malloc_arrayN(totvert, sizeof(int), "topology ID");
for (int i = 0; i < totvert; i++) {
ss->vertex_info.connected_component[i] = SCULPT_TOPOLOGY_ID_NONE;
}
int next_id = 0;
for (int i = 0; i < totvert; i++) {
if (ss->vertex_info.connected_component[i] == SCULPT_TOPOLOGY_ID_NONE) {
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
SCULPT_floodfill_add_initial(&flood, i);
SculptTopologyIDFloodFillData data;
data.next_id = next_id;
SCULPT_floodfill_execute(ss, &flood, SCULPT_connected_components_floodfill_cb, &data);
SCULPT_floodfill_free(&flood);
next_id++;
}
}
}
void SCULPT_fake_neighbors_ensure(Sculpt *sd, Object *ob, const float max_dist)
{
SculptSession *ss = ob->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
/* Fake neighbors were already initialized with the same distance, so no need to be recalculated.
*/
if (ss->fake_neighbors.fake_neighbor_index &&
ss->fake_neighbors.current_max_distance == max_dist) {
return;
}
SCULPT_connected_components_ensure(ob);
SCULPT_fake_neighbor_init(ss, max_dist);
for (int i = 0; i < totvert; i++) {
const int from_v = i;
/* This vertex does not have a fake neighbor yet, seach one for it. */
if (ss->fake_neighbors.fake_neighbor_index[from_v] == FAKE_NEIGHBOR_NONE) {
const int to_v = SCULPT_fake_neighbor_search(sd, ob, from_v, max_dist);
if (to_v != -1) {
/* Add the fake neighbor if available. */
SCULPT_fake_neighbor_add(ss, from_v, to_v);
}
}
}
}
void SCULPT_fake_neighbors_enable(Object *ob)
{
SculptSession *ss = ob->sculpt;
BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL);
ss->fake_neighbors.use_fake_neighbors = true;
}
void SCULPT_fake_neighbors_disable(Object *ob)
{
SculptSession *ss = ob->sculpt;
BLI_assert(ss->fake_neighbors.fake_neighbor_index != NULL);
ss->fake_neighbors.use_fake_neighbors = false;
}
void SCULPT_fake_neighbors_free(Object *ob)
{
SculptSession *ss = ob->sculpt;
sculpt_pose_fake_neighbors_free(ss);
}
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);

View File

@ -148,6 +148,15 @@ void SCULPT_active_vertex_normal_get(SculptSession *ss, float normal[3]);
bool SCULPT_vertex_is_boundary(SculptSession *ss, const int index);
/* Fake Neighbors */
#define FAKE_NEIGHBOR_NONE -1
void SCULPT_fake_neighbors_ensure(struct Sculpt *sd, Object *ob, const float max_dist);
void SCULPT_fake_neighbors_enable(Object *ob);
void SCULPT_fake_neighbors_disable(Object *ob);
void SCULPT_fake_neighbors_free(struct Object *ob);
/* Sculpt Visibility API */
void SCULPT_vertex_visible_set(SculptSession *ss, int index, bool visible);

View File

@ -937,18 +937,32 @@ SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
const float initial_location[3],
const float radius)
{
SculptPoseIKChain *ik_chain = NULL;
const bool use_fake_neighbors = !(br->flag2 & BRUSH_USE_CONNECTED_ONLY);
if (use_fake_neighbors) {
SCULPT_fake_neighbors_ensure(sd, ob, br->disconnected_distance_max);
SCULPT_fake_neighbors_enable(ob);
}
switch (br->pose_origin_type) {
case BRUSH_POSE_ORIGIN_TOPOLOGY:
return pose_ik_chain_init_topology(sd, ob, ss, br, initial_location, radius);
ik_chain = pose_ik_chain_init_topology(sd, ob, ss, br, initial_location, radius);
break;
case BRUSH_POSE_ORIGIN_FACE_SETS:
return pose_ik_chain_init_face_sets(sd, ob, ss, br, radius);
ik_chain = pose_ik_chain_init_face_sets(sd, ob, ss, br, radius);
break;
case BRUSH_POSE_ORIGIN_FACE_SETS_FK:
return pose_ik_chain_init_face_sets_fk(sd, ob, ss, radius, initial_location);
break;
}
return NULL;
if (use_fake_neighbors) {
SCULPT_fake_neighbors_disable(ob);
}
return ik_chain;
}
void SCULPT_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br)

View File

@ -47,6 +47,7 @@
.crease_pinch_factor = 0.5f, \
.normal_radius_factor = 0.5f, \
.area_radius_factor = 0.5f, \
.disconnected_distance_max = 0.1f, \
.sculpt_plane = SCULPT_DISP_DIR_AREA, \
.cloth_damping = 0.01, \
.cloth_mass = 1, \

View File

@ -474,7 +474,7 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
char _pad0[5];
char _pad0[1];
/** Projection shape (sphere, circle). */
char falloff_shape;
@ -519,6 +519,9 @@ typedef struct Brush {
int curve_preset;
/* Maximun distance to search fake neighbors from a vertex. */
float disconnected_distance_max;
/* automasking */
int automasking_flags;
int automasking_boundary_edges_propagation_steps;
@ -680,6 +683,7 @@ typedef enum eBrushFlags2 {
BRUSH_MULTIPLANE_SCRAPE_DYNAMIC = (1 << 0),
BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW = (1 << 1),
BRUSH_POSE_IK_ANCHORED = (1 << 2),
BRUSH_USE_CONNECTED_ONLY = (1 << 3),
} eBrushFlags2;
typedef enum {

View File

@ -2362,6 +2362,14 @@ static void rna_def_brush(BlenderRNA *brna)
prop, "Pose Origin Offset", "Offset of the pose origin in relation to the brush radius");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "disconnected_distance_max", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "disconnected_distance_max");
RNA_def_property_range(prop, 0.0f, 10.0f);
RNA_def_property_ui_text(prop,
"Max Element Distance",
"Maximum distance to search for disconnected loose parts in the mesh");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "surface_smooth_shape_preservation", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "surface_smooth_shape_preservation");
RNA_def_property_range(prop, 0.0f, 1.0f);
@ -2657,6 +2665,11 @@ static void rna_def_brush(BlenderRNA *brna)
prop, "Keep Anchor Point", "Keep the position of the last segment in the IK chain fixed");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_connected_only", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_USE_CONNECTED_ONLY);
RNA_def_property_ui_text(prop, "Connected Only", "Affect only topologically connected elements");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "invert_to_scrape_fill", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_INVERT_TO_SCRAPE_FILL);
RNA_def_property_ui_text(prop,