Sculpt: Boundary Brush Falloff Types and Offset

This adds the boundary_falloff_type and boundary_offset to control how the
falloff of the Boundary Brush is applied.

Boundary Origin Offset is the same concept as the Pose Origin offset in
the Pose Brush. It is a multiplier that adds extra length to the brush
radius to locate the deformation pivot further from the boundary without
affecting the falloff.

The Falloff type includes Constant (previous default), brush radius, loop
and loop and invert. Loop and Loop and Invert can be used to create
deformation patterns in a mesh.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D8526
This commit is contained in:
Pablo Dobarro 2020-08-11 16:33:35 +02:00
parent db7cfd03b0
commit c77bf95221
7 changed files with 127 additions and 8 deletions

View File

@ -722,6 +722,8 @@ def brush_settings(layout, context, brush, popover=False):
elif sculpt_tool == 'BOUNDARY':
col = layout.column()
col.prop(brush, "boundary_deform_type")
col.prop(brush, "boundary_falloff_type")
col.prop(brush, "boundary_offset")
elif sculpt_tool == 'TOPOLOGY':
col = layout.column()

View File

@ -337,6 +337,11 @@ typedef struct SculptBoundary {
int vertices_capacity;
int num_vertices;
/* Distance from a vertex in the boundary to initial vertex indexed by vertex index, taking into
* account the lengh of all edges between them. Any vertex that is not in the boundary will have
* a distance of 0. */
float *distance;
/* Data for drawing the preview. */
SculptBoundaryPreviewEdge *edges;
int edges_capacity;

View File

@ -1542,7 +1542,7 @@ static void paint_cursor_preview_boundary_data_update(PaintCursorContext *pconte
}
ss->boundary_preview = SCULPT_boundary_data_init(
pcontext->vc.obact, ss->active_vertex_index, pcontext->radius);
pcontext->vc.obact, pcontext->brush, ss->active_vertex_index, pcontext->radius);
}
static void paint_cursor_draw_3d_view_brush_cursor_inactive(PaintCursorContext *pcontext)

View File

@ -132,10 +132,14 @@ static int BOUNDARY_INDICES_BLOCK_SIZE = 300;
static void sculpt_boundary_index_add(SculptBoundary *bdata,
const int new_index,
const float distance,
GSet *included_vertices)
{
bdata->vertices[bdata->num_vertices] = new_index;
if (bdata->distance) {
bdata->distance[new_index] = distance;
}
if (included_vertices) {
BLI_gset_add(included_vertices, POINTER_FROM_INT(new_index));
}
@ -213,7 +217,11 @@ static bool boundary_floodfill_cb(
BoundaryFloodFillData *data = userdata;
SculptBoundary *bdata = data->bdata;
if (SCULPT_vertex_is_boundary(ss, to_v)) {
sculpt_boundary_index_add(bdata, to_v, data->included_vertices);
const float edge_len = len_v3v3(SCULPT_vertex_co_get(ss, from_v),
SCULPT_vertex_co_get(ss, to_v));
const float distance_boundary_to_dst = bdata->distance ? bdata->distance[from_v] + edge_len :
0.0f;
sculpt_boundary_index_add(bdata, to_v, distance_boundary_to_dst, data->included_vertices);
if (!is_duplicate) {
sculpt_boundary_preview_edge_add(bdata, from_v, to_v);
}
@ -224,11 +232,16 @@ static bool boundary_floodfill_cb(
static void sculpt_boundary_indices_init(SculptSession *ss,
SculptBoundary *bdata,
const bool init_boundary_distances,
const int initial_boundary_index)
{
const int totvert = SCULPT_vertex_count_get(ss);
bdata->vertices = MEM_malloc_arrayN(
BOUNDARY_INDICES_BLOCK_SIZE, sizeof(int), "boundary indices");
if (init_boundary_distances) {
bdata->distance = MEM_calloc_arrayN(totvert, sizeof(float), "boundary distances");
}
bdata->edges = MEM_malloc_arrayN(
BOUNDARY_INDICES_BLOCK_SIZE, sizeof(SculptBoundaryPreviewEdge), "boundary edges");
@ -238,7 +251,7 @@ static void sculpt_boundary_indices_init(SculptSession *ss,
bdata->initial_vertex = initial_boundary_index;
copy_v3_v3(bdata->initial_vertex_position, SCULPT_vertex_co_get(ss, bdata->initial_vertex));
sculpt_boundary_index_add(bdata, initial_boundary_index, included_vertices);
sculpt_boundary_index_add(bdata, initial_boundary_index, 0.0f, included_vertices);
SCULPT_floodfill_add_initial(&flood, initial_boundary_index);
BoundaryFloodFillData fdata = {
@ -407,7 +420,8 @@ static void sculpt_boundary_edit_data_init(SculptSession *ss,
*/
static void sculpt_boundary_falloff_factor_init(SculptSession *ss,
SculptBoundary *bdata,
Brush *brush)
Brush *brush,
const float radius)
{
const int totvert = SCULPT_vertex_count_get(ss);
BKE_curvemapping_init(brush->curve);
@ -417,12 +431,55 @@ static void sculpt_boundary_falloff_factor_init(SculptSession *ss,
bdata->edit_info[i].strength_factor = BKE_brush_curve_strength(
brush, bdata->edit_info[i].num_propagation_steps, bdata->max_propagation_steps);
}
if (bdata->edit_info[i].original_vertex == bdata->initial_vertex) {
/* All vertices that are propagated from the original vertex won't be affected by the
* boundary falloff, so there is no need to calculate anything else. */
continue;
}
if (!bdata->distance) {
/* There are falloff modes that do not require to modify the previously calculated falloff
* based on boundary distances. */
continue;
}
const float boundary_distance = bdata->distance[bdata->edit_info[i].original_vertex];
float falloff_distance = 0.0f;
float direction = 1.0f;
switch (brush->boundary_falloff_type) {
case BRUSH_BOUNDARY_FALLOFF_RADIUS:
falloff_distance = boundary_distance;
break;
case BRUSH_BOUNDARY_FALLOFF_LOOP: {
const int div = boundary_distance / radius;
const float mod = fmodf(boundary_distance, radius);
falloff_distance = div % 2 == 0 ? mod : radius - mod;
} break;
case BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT: {
const int div = boundary_distance / radius;
const float mod = fmodf(boundary_distance, radius);
falloff_distance = div % 2 == 0 ? mod : radius - mod;
/* Inverts the faloff in the intervals 1 2 5 6 9 10 ... */
if (((div - 1) & 2) == 0) {
direction = -1.0f;
}
} break;
case BRUSH_BOUNDARY_FALLOFF_CONSTANT:
/* For constant falloff distances are not allocated, so this should never happen. */
BLI_assert(false);
}
bdata->edit_info[i].strength_factor *= direction * BKE_brush_curve_strength(
brush, falloff_distance, radius);
}
}
/* Main function to get SculptBoundary data both for brush deformation and viewport preview. Can
* return NULL if there is no boundary from the given vertex using the given radius. */
SculptBoundary *SCULPT_boundary_data_init(Object *object,
Brush *brush,
const int initial_vertex,
const float radius)
{
@ -446,8 +503,12 @@ SculptBoundary *SCULPT_boundary_data_init(Object *object,
SculptBoundary *bdata = MEM_callocN(sizeof(SculptBoundary), "Boundary edit data");
sculpt_boundary_indices_init(ss, bdata, boundary_initial_vertex);
sculpt_boundary_edit_data_init(ss, bdata, boundary_initial_vertex, radius);
const bool init_boundary_distances = brush->boundary_falloff_type !=
BRUSH_BOUNDARY_FALLOFF_CONSTANT;
sculpt_boundary_indices_init(ss, bdata, init_boundary_distances, boundary_initial_vertex);
const float boundary_radius = radius * (1.0f + brush->boundary_offset);
sculpt_boundary_edit_data_init(ss, bdata, boundary_initial_vertex, boundary_radius);
return bdata;
}
@ -455,6 +516,7 @@ SculptBoundary *SCULPT_boundary_data_init(Object *object,
void SCULPT_boundary_data_free(SculptBoundary *bdata)
{
MEM_SAFE_FREE(bdata->vertices);
MEM_SAFE_FREE(bdata->distance);
MEM_SAFE_FREE(bdata->edit_info);
MEM_SAFE_FREE(bdata->bend.pivot_positions);
MEM_SAFE_FREE(bdata->bend.pivot_rotation_axis);
@ -775,7 +837,7 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
}
ss->cache->bdata[symm_area] = SCULPT_boundary_data_init(
ob, initial_vertex, ss->cache->initial_radius);
ob, brush, initial_vertex, ss->cache->initial_radius);
if (ss->cache->bdata[symm_area]) {
@ -795,7 +857,8 @@ void SCULPT_do_boundary_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totn
break;
}
sculpt_boundary_falloff_factor_init(ss, ss->cache->bdata[symm_area], brush);
sculpt_boundary_falloff_factor_init(
ss, ss->cache->bdata[symm_area], brush, ss->cache->initial_radius);
}
}

View File

@ -398,6 +398,7 @@ void SCULPT_pose_ik_chain_free(struct SculptPoseIKChain *ik_chain);
/* Boundary Brush. */
struct SculptBoundary *SCULPT_boundary_data_init(Object *object,
Brush *brush,
const int initial_vertex,
const float radius);
void SCULPT_boundary_data_free(struct SculptBoundary *bdata);

View File

@ -387,6 +387,13 @@ typedef enum eBrushBoundaryDeformType {
BRUSH_BOUNDARY_DEFORM_TWIST = 4,
} eBrushBushBoundaryDeformType;
typedef enum eBrushBoundaryFalloffType {
BRUSH_BOUNDARY_FALLOFF_CONSTANT = 0,
BRUSH_BOUNDARY_FALLOFF_RADIUS = 1,
BRUSH_BOUNDARY_FALLOFF_LOOP = 2,
BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT = 3,
} eBrushBoundaryFalloffType;
/* Gpencilsettings.Vertex_mode */
typedef enum eGp_Vertex_Mode {
/* Affect to Stroke only. */
@ -596,6 +603,8 @@ typedef struct Brush {
/* boundary */
int boundary_deform_type;
int boundary_falloff_type;
float boundary_offset;
/* cloth */
int cloth_deform_type;

View File

@ -1997,6 +1997,31 @@ static void rna_def_brush(BlenderRNA *brna)
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_boundary_falloff_type_items[] = {
{BRUSH_BOUNDARY_FALLOFF_CONSTANT,
"CONSTANT",
0,
"Constant",
"Applies the same deformation in the entire boundary"},
{BRUSH_BOUNDARY_FALLOFF_RADIUS,
"RADIUS",
0,
"Brush Radius",
"Applies the deformation in a localiced area limited by the brush radius"},
{BRUSH_BOUNDARY_FALLOFF_LOOP,
"LOOP",
0,
"Loop",
"Applies the brush falloff in a loop pattern"},
{BRUSH_BOUNDARY_FALLOFF_LOOP_INVERT,
"LOOP_INVERT",
0,
"Loop and Invert",
"Applies the fallof radius in a loop pattern, inverting the displacement direction in each "
"pattern repetition"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem brush_cloth_simulation_area_type_items[] = {
{BRUSH_CLOTH_SIMULATION_AREA_LOCAL,
"LOCAL",
@ -2194,6 +2219,12 @@ static void rna_def_brush(BlenderRNA *brna)
"Part of the mesh that is going to be simulated when the stroke is active");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "boundary_falloff_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_boundary_falloff_type_items);
RNA_def_property_ui_text(
prop, "Boundary Falloff", "How the brush falloff is applied across the boundary");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "smooth_deform_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, brush_smooth_deform_type_items);
RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
@ -2545,6 +2576,14 @@ static void rna_def_brush(BlenderRNA *brna)
"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, "boundary_offset", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "boundary_offset");
RNA_def_property_range(prop, 0.0f, 30.0f);
RNA_def_property_ui_text(prop,
"Boundary Origin Offset",
"Offset of the boundary origin in relation to the brush radius");
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);