diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py index 397ce5d0324..6a541863aef 100644 --- a/release/scripts/startup/bl_ui/properties_paint_common.py +++ b/release/scripts/startup/bl_ui/properties_paint_common.py @@ -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() diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 58687858a9e..7b63a4154fa 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -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; diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c index 88998d5063d..d4379262666 100644 --- a/source/blender/editors/sculpt_paint/paint_cursor.c +++ b/source/blender/editors/sculpt_paint/paint_cursor.c @@ -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) diff --git a/source/blender/editors/sculpt_paint/sculpt_boundary.c b/source/blender/editors/sculpt_paint/sculpt_boundary.c index c86e922a347..5bedee6f3e2 100644 --- a/source/blender/editors/sculpt_paint/sculpt_boundary.c +++ b/source/blender/editors/sculpt_paint/sculpt_boundary.c @@ -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); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index b11a7005fb5..22316eb4631 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -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); diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index 5d8eddaf5e1..6c4d2856526 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c index 38916e2a45a..a3fa4fed575 100644 --- a/source/blender/makesrna/intern/rna_brush.c +++ b/source/blender/makesrna/intern/rna_brush.c @@ -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);