Sculpt: Pose Brush Face Sets origin mode

This commit introduces a new mode for calculating the positions and
weights of the IK segments in the Pose Brush based on the Face Sets.

The first segment of the chain will always include all face sets inside
the brush radius and it will propagate until the boundary of the last
face sets added in the flood fill. Then consecutive connected face sets
are added to the chain until the chain length limit is reached or all
face sets of the mesh are already part of the chain.

This feature enables complete control over the pose brush origins in
case that is needed. Also, with this mode, the user can have a library
of base meshes with face sets already configured to get to the initial
pose as fast as possible.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D7235
This commit is contained in:
Pablo Dobarro 2020-03-27 18:14:14 +01:00
parent 4c0cca78eb
commit 9120191fe2
6 changed files with 327 additions and 52 deletions

View File

@ -626,6 +626,7 @@ def brush_settings(layout, context, brush, popover=False):
if brush.sculpt_tool == 'POSE':
layout.separator()
layout.prop(brush, "pose_origin_type")
layout.prop(brush, "pose_offset")
layout.prop(brush, "pose_smooth_iterations")
layout.prop(brush, "pose_ik_segments")

View File

@ -196,21 +196,15 @@ float SCULPT_vertex_mask_get(SculptSession *ss, int index)
return 0.0f;
}
static int SCULPT_active_vertex_get(SculptSession *ss)
int SCULPT_active_vertex_get(SculptSession *ss)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES:
return ss->active_vertex_index;
case PBVH_BMESH:
return ss->active_vertex_index;
case PBVH_GRIDS:
return ss->active_vertex_index;
if (ELEM(BKE_pbvh_type(ss->pbvh), PBVH_FACES, PBVH_BMESH, PBVH_GRIDS)) {
return ss->active_vertex_index;
}
return 0;
}
static const float *SCULPT_active_vertex_co_get(SculptSession *ss)
const float *SCULPT_active_vertex_co_get(SculptSession *ss)
{
return SCULPT_vertex_co_get(ss, SCULPT_active_vertex_get(ss));
}
@ -373,7 +367,7 @@ static void SCULPT_vertex_face_set_set(SculptSession *ss, int index, int face_se
}
}
static int SCULPT_vertex_face_set_get(SculptSession *ss, int index)
int SCULPT_vertex_face_set_get(SculptSession *ss, int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES: {
@ -394,7 +388,7 @@ static int SCULPT_vertex_face_set_get(SculptSession *ss, int index)
return 0;
}
static bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set)
bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES: {
@ -464,7 +458,7 @@ void SCULPT_visibility_sync_all_vertex_to_face_sets(SculptSession *ss)
}
}
static bool sculpt_vertex_has_unique_face_set(SculptSession *ss, int index)
bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
case PBVH_FACES: {
@ -819,11 +813,35 @@ void SCULPT_floodfill_init(SculptSession *ss, SculptFloodFill *flood)
flood->visited_vertices = MEM_callocN(vertex_count * sizeof(char), "visited vertices");
}
static void sculpt_floodfill_add_initial(SculptFloodFill *flood, int index)
void sculpt_floodfill_add_initial(SculptFloodFill *flood, int index)
{
BLI_gsqueue_push(flood->queue, &index);
}
void SCULPT_floodfill_add_initial_with_symmetry(
Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, int index, float radius)
{
/* Add active vertex and symmetric vertices to the queue. */
const char symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL;
for (char i = 0; i <= symm; ++i) {
if (SCULPT_is_symmetry_iteration_valid(i, symm)) {
int v = -1;
if (i == 0) {
v = index;
}
else if (radius > 0.0f) {
float radius_squared = (radius == FLT_MAX) ? FLT_MAX : radius * radius;
float location[3];
flip_v3_v3(location, SCULPT_vertex_co_get(ss, index), i);
v = SCULPT_nearest_vertex_get(sd, ob, location, radius_squared, false);
}
if (v != -1) {
sculpt_floodfill_add_initial(flood, v);
}
}
}
}
void SCULPT_floodfill_add_active(
Sculpt *sd, Object *ob, SculptSession *ss, SculptFloodFill *flood, float radius)
{
@ -3639,7 +3657,7 @@ static void do_relax_face_sets_brush_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
{
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
if (relax_face_sets != sculpt_vertex_has_unique_face_set(ss, vd.index)) {
if (relax_face_sets != SCULPT_vertex_has_unique_face_set(ss, vd.index)) {
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
brush,
vd.co,
@ -3856,7 +3874,7 @@ void SCULPT_relax_vertex(SculptSession *ss,
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, vd->index, ni) {
if (!filter_boundary_face_sets ||
(filter_boundary_face_sets && !sculpt_vertex_has_unique_face_set(ss, ni.index))) {
(filter_boundary_face_sets && !SCULPT_vertex_has_unique_face_set(ss, ni.index))) {
add_v3_v3(smooth_pos, SCULPT_vertex_co_get(ss, ni.index));
count++;
}
@ -9478,7 +9496,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
/* Skip the edges of the face set when relaxing or smoothing. There is a relax face set
* option to relax the boindaries independently. */
if (filter_type == MESH_FILTER_RELAX) {
if (!sculpt_vertex_has_unique_face_set(ss, vd.index)) {
if (!SCULPT_vertex_has_unique_face_set(ss, vd.index)) {
continue;
}
}
@ -9492,7 +9510,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
}
if (filter_type == MESH_FILTER_RELAX_FACE_SETS) {
if (relax_face_sets == sculpt_vertex_has_unique_face_set(ss, vd.index)) {
if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.index)) {
continue;
}
}

View File

@ -125,6 +125,9 @@ void SCULPT_vertex_neighbors_get(struct SculptSession *ss,
} \
((void)0)
int SCULPT_active_vertex_get(SculptSession *ss);
const float *SCULPT_active_vertex_co_get(SculptSession *ss);
/* Sculpt Original Data */
typedef struct {
struct BMLog *bm_log;
@ -143,6 +146,11 @@ typedef struct {
void SCULPT_orig_vert_data_init(SculptOrigVertData *data, Object *ob, PBVHNode *node);
void SCULPT_orig_vert_data_update(SculptOrigVertData *orig_data, PBVHVertexIter *iter);
/* Face Sets */
int SCULPT_vertex_face_set_get(SculptSession *ss, int index);
bool SCULPT_vertex_has_face_set(SculptSession *ss, int index, int face_set);
bool SCULPT_vertex_has_unique_face_set(SculptSession *ss, int index);
/* Dynamic topology */
void sculpt_pbvh_clear(Object *ob);
void sculpt_dyntopo_node_layers_add(struct SculptSession *ss);
@ -196,6 +204,13 @@ void SCULPT_floodfill_add_active(struct Sculpt *sd,
struct SculptSession *ss,
SculptFloodFill *flood,
float radius);
void SCULPT_floodfill_add_initial_with_symmetry(struct Sculpt *sd,
struct Object *ob,
struct SculptSession *ss,
SculptFloodFill *flood,
int index,
float radius);
void sculpt_floodfill_add_initial(SculptFloodFill *flood, int index);
void SCULPT_floodfill_execute(
struct SculptSession *ss,
SculptFloodFill *flood,

View File

@ -351,9 +351,29 @@ typedef struct PoseFloodFillData {
float *pose_factor;
float pose_origin[3];
int tot_co;
int current_face_set;
int next_face_set;
int prev_face_set;
int next_vertex;
bool next_face_set_found;
/* Store the visited face sets to avoid going back when calculating the chain. */
GSet *visited_face_sets;
/* In face sets origin mode, each vertex can only be assigned to one face set. */
bool *is_weighted;
bool is_first_iteration;
/* Fallback origin. If we can't find any face set to continue, use the position of all vertices
* that have the current face set. */
float fallback_origin[3];
int fallback_count;
} PoseFloodFillData;
static bool pose_floodfill_cb(
static bool pose_topology_floodfill_cb(
SculptSession *ss, int UNUSED(from_v), int to_v, bool is_duplicate, void *userdata)
{
PoseFloodFillData *data = userdata;
@ -377,6 +397,100 @@ static bool pose_floodfill_cb(
return false;
}
static bool pose_face_sets_floodfill_cb(
SculptSession *ss, int UNUSED(from_v), int to_v, bool is_duplicate, void *userdata)
{
PoseFloodFillData *data = userdata;
const int index = to_v;
bool visit_next = false;
const float *co = SCULPT_vertex_co_get(ss, index);
const bool symmetry_check = SCULPT_check_vertex_pivot_symmetry(
co, data->pose_initial_co, data->symm) &&
!is_duplicate;
/* First iteration. Continue expanding using topology until a vertex is outside the brush radius
* to determine the first face set. */
if (data->current_face_set == SCULPT_FACE_SET_NONE) {
data->pose_factor[index] = 1.0f;
data->is_weighted[index] = true;
if (sculpt_pose_brush_is_vertex_inside_brush_radius(
co, data->pose_initial_co, data->radius, data->symm)) {
const int visited_face_set = SCULPT_vertex_face_set_get(ss, index);
BLI_gset_add(data->visited_face_sets, visited_face_set);
}
else if (symmetry_check) {
data->current_face_set = SCULPT_vertex_face_set_get(ss, index);
BLI_gset_add(data->visited_face_sets, data->current_face_set);
}
return true;
}
/* We already have a current face set, so we can start checking the face sets of the vertices. */
/* In the first iteration we need to check all face sets we already visited as the flood fill may
* still not be finished in some of them. */
bool is_vertex_valid = false;
if (data->is_first_iteration) {
GSetIterator gs_iter;
GSET_ITER (gs_iter, data->visited_face_sets) {
const int visited_face_set = BLI_gsetIterator_getKey(&gs_iter);
is_vertex_valid |= SCULPT_vertex_has_face_set(ss, index, visited_face_set);
}
}
else {
is_vertex_valid = SCULPT_vertex_has_face_set(ss, index, data->current_face_set);
}
if (is_vertex_valid) {
if (!data->is_weighted[index]) {
data->pose_factor[index] = 1.0f;
data->is_weighted[index] = true;
visit_next = true;
}
/* Fallback origin accumulation. */
if (symmetry_check) {
add_v3_v3(data->fallback_origin, SCULPT_vertex_co_get(ss, index));
data->fallback_count++;
}
if (symmetry_check && !SCULPT_vertex_has_unique_face_set(ss, index)) {
/* We only add coordiates for calculating the origin when it is possible to go from this
* vertex to another vertex in a valid face set for the next iteration. */
bool count_as_boundary = false;
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, index, ni) {
int next_face_set_candidate = SCULPT_vertex_face_set_get(ss, ni.index);
/* Check if we can get a valid face set for the next iteration from this neighbor. */
if (SCULPT_vertex_has_unique_face_set(ss, ni.index) &&
!BLI_gset_haskey(data->visited_face_sets, next_face_set_candidate)) {
if (!data->next_face_set_found) {
data->next_face_set = next_face_set_candidate;
data->next_vertex = ni.index;
data->next_face_set_found = true;
}
count_as_boundary = true;
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
/* Origin accumulation. */
if (count_as_boundary) {
add_v3_v3(data->pose_origin, SCULPT_vertex_co_get(ss, index));
data->tot_co++;
}
}
}
return visit_next;
}
/* Public functions. */
/* Calculate the pose origin and (Optionaly the pose factor) that is used when using the pose brush
@ -407,7 +521,7 @@ void SCULPT_pose_calc_pose_data(Sculpt *sd,
};
zero_v3(fdata.pose_origin);
copy_v3_v3(fdata.pose_initial_co, initial_location);
SCULPT_floodfill_execute(ss, &flood, pose_floodfill_cb, &fdata);
SCULPT_floodfill_execute(ss, &flood, pose_topology_floodfill_cb, &fdata);
SCULPT_floodfill_free(&flood);
if (fdata.tot_co > 0) {
@ -454,12 +568,47 @@ static void pose_brush_init_task_cb_ex(void *__restrict userdata,
BKE_pbvh_vertex_iter_end;
}
SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
Object *ob,
SculptSession *ss,
Brush *br,
const float initial_location[3],
const float radius)
/* Init the IK chain with empty weights. */
static SculptPoseIKChain *pose_ik_chain_new(const int totsegments, const int totverts)
{
SculptPoseIKChain *ik_chain = MEM_callocN(sizeof(SculptPoseIKChain), "Pose IK Chain");
ik_chain->tot_segments = totsegments;
ik_chain->segments = MEM_callocN(totsegments * sizeof(SculptPoseIKChainSegment),
"Pose IK Chain Segments");
for (int i = 0; i < totsegments; i++) {
ik_chain->segments[i].weights = MEM_callocN(totverts * sizeof(float), "Pose IK weights");
}
return ik_chain;
}
/* Init the origin/head pairs of all the segments from the calculated origins. */
static void pose_ik_chain_origin_heads_init(SculptPoseIKChain *ik_chain,
const float initial_location[3])
{
float origin[3];
float head[3];
for (int i = 0; i < ik_chain->tot_segments; i++) {
if (i == 0) {
copy_v3_v3(head, initial_location);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
else {
copy_v3_v3(head, ik_chain->segments[i - 1].orig);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
copy_v3_v3(ik_chain->segments[i].orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_head, head);
ik_chain->segments[i].len = len_v3v3(head, origin);
}
}
SculptPoseIKChain *SCULPT_pose_ik_chain_init_topology(Sculpt *sd,
Object *ob,
SculptSession *ss,
Brush *br,
const float initial_location[3],
const float radius)
{
const float chain_segment_len = radius * (1.0f + br->pose_offset);
@ -480,14 +629,7 @@ SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
pose_factor_grow[nearest_vertex_index] = 1.0f;
/* Init the IK chain with empty weights. */
SculptPoseIKChain *ik_chain = MEM_callocN(sizeof(SculptPoseIKChain), "Pose IK Chain");
ik_chain->tot_segments = br->pose_ik_segments;
ik_chain->segments = MEM_callocN(ik_chain->tot_segments * sizeof(SculptPoseIKChainSegment),
"Pose IK Chain Segments");
for (int i = 0; i < br->pose_ik_segments; i++) {
ik_chain->segments[i].weights = MEM_callocN(totvert * sizeof(float), "Pose IK weights");
}
SculptPoseIKChain *ik_chain = pose_ik_chain_new(br->pose_ik_segments, totvert);
/* Calculate the first segment in the chain using the brush radius and the pose origin offset. */
copy_v3_v3(next_chain_segment_target, initial_location);
@ -532,23 +674,7 @@ SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
}
}
/* Init the origin/head pairs of all the segments from the calculated origins. */
float origin[3];
float head[3];
for (int i = 0; i < ik_chain->tot_segments; i++) {
if (i == 0) {
copy_v3_v3(head, initial_location);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
else {
copy_v3_v3(head, ik_chain->segments[i - 1].orig);
copy_v3_v3(origin, ik_chain->segments[i].orig);
}
copy_v3_v3(ik_chain->segments[i].orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_orig, origin);
copy_v3_v3(ik_chain->segments[i].initial_head, head);
ik_chain->segments[i].len = len_v3v3(head, origin);
}
pose_ik_chain_origin_heads_init(ik_chain, initial_location);
MEM_freeN(pose_factor_grow);
MEM_freeN(pose_factor_grow_prev);
@ -556,6 +682,94 @@ SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
return ik_chain;
}
SculptPoseIKChain *SCULPT_pose_ik_chain_init_face_sets(
Sculpt *sd, Object *ob, SculptSession *ss, Brush *br, const float radius)
{
int totvert = SCULPT_vertex_count_get(ss);
SculptPoseIKChain *ik_chain = pose_ik_chain_new(br->pose_ik_segments, totvert);
GSet *visited_face_sets = BLI_gset_int_new_ex("visited_face_sets", ik_chain->tot_segments);
bool *is_weighted = MEM_callocN(sizeof(bool) * totvert, "weighted");
int current_face_set = SCULPT_FACE_SET_NONE;
int prev_face_set = SCULPT_FACE_SET_NONE;
int current_vertex = SCULPT_active_vertex_get(ss);
for (int s = 0; s < ik_chain->tot_segments; s++) {
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
SCULPT_floodfill_add_initial_with_symmetry(sd, ob, ss, &flood, current_vertex, FLT_MAX);
BLI_gset_add(visited_face_sets, current_face_set);
PoseFloodFillData fdata = {
.radius = radius,
.symm = sd->paint.symmetry_flags & PAINT_SYMM_AXIS_ALL,
.pose_factor = ik_chain->segments[s].weights,
.tot_co = 0,
.fallback_count = 0,
.current_face_set = current_face_set,
.prev_face_set = prev_face_set,
.visited_face_sets = visited_face_sets,
.is_weighted = is_weighted,
.next_face_set_found = false,
.is_first_iteration = s == 0,
};
zero_v3(fdata.pose_origin);
zero_v3(fdata.fallback_origin);
copy_v3_v3(fdata.pose_initial_co, SCULPT_vertex_co_get(ss, current_vertex));
SCULPT_floodfill_execute(ss, &flood, pose_face_sets_floodfill_cb, &fdata);
SCULPT_floodfill_free(&flood);
if (fdata.tot_co > 0) {
mul_v3_fl(fdata.pose_origin, 1.0f / (float)fdata.tot_co);
copy_v3_v3(ik_chain->segments[s].orig, fdata.pose_origin);
}
else if (fdata.fallback_count > 0) {
mul_v3_fl(fdata.fallback_origin, 1.0f / (float)fdata.fallback_count);
copy_v3_v3(ik_chain->segments[s].orig, fdata.fallback_origin);
}
else {
zero_v3(ik_chain->segments[s].orig);
}
prev_face_set = fdata.current_face_set;
current_face_set = fdata.next_face_set;
current_vertex = fdata.next_vertex;
}
BLI_gset_free(visited_face_sets, NULL);
pose_ik_chain_origin_heads_init(ik_chain, SCULPT_active_vertex_co_get(ss));
MEM_SAFE_FREE(is_weighted);
return ik_chain;
}
SculptPoseIKChain *SCULPT_pose_ik_chain_init(Sculpt *sd,
Object *ob,
SculptSession *ss,
Brush *br,
const float initial_location[3],
const float radius)
{
switch (br->pose_origin_type) {
case BRUSH_POSE_ORIGIN_TOPOLOGY:
return SCULPT_pose_ik_chain_init_topology(sd, ob, ss, br, initial_location, radius);
break;
case BRUSH_POSE_ORIGIN_FACE_SETS:
return SCULPT_pose_ik_chain_init_face_sets(sd, ob, ss, br, radius);
break;
}
return NULL;
}
void SCULPT_pose_brush_init(Sculpt *sd, Object *ob, SculptSession *ss, Brush *br)
{
PBVHNode **nodes;

View File

@ -291,6 +291,11 @@ typedef enum eBrushClothForceFalloffType {
BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
} eBrushClothForceFalloffType;
typedef enum eBrushPoseOriginType {
BRUSH_POSE_ORIGIN_TOPOLOGY = 0,
BRUSH_POSE_ORIGIN_FACE_SETS = 1,
} eBrushPoseOriginType;
/* Gpencilsettings.Vertex_mode */
typedef enum eGp_Vertex_Mode {
/* Affect to Stroke only. */
@ -406,7 +411,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;
@ -467,6 +472,7 @@ typedef struct Brush {
float pose_offset;
int pose_smooth_iterations;
int pose_ik_segments;
int pose_origin_type;
/* cloth */
int cloth_deform_type;

View File

@ -1805,6 +1805,20 @@ static void rna_def_brush(BlenderRNA *brna)
0,
"Surface",
"Smooths the surface of the mesh, preserving the volue"},
};
static const EnumPropertyItem brush_pose_origin_type_items[] = {
{BRUSH_POSE_ORIGIN_TOPOLOGY,
"TOPOLOGY",
0,
"Topology",
"Sets the rotation origin automatically using the topology and shape of the mesh as a "
"guide"},
{BRUSH_POSE_ORIGIN_FACE_SETS,
"FACE_SETS",
0,
"Face Sets",
"Creates a pose segment per face sets, starting from the active face set"},
{0, NULL, 0, NULL, NULL},
};
@ -1928,6 +1942,13 @@ 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, "pose_origin_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_pose_origin_type_items);
RNA_def_property_ui_text(prop,
"Rotation Origins",
"Method to set the rotation origins for the segments of the brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "jitter_unit", PROP_ENUM, PROP_NONE); /* as an enum */
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, brush_jitter_unit_items);