diff --git a/source/blender/blenkernel/BKE_brush.hh b/source/blender/blenkernel/BKE_brush.hh index 9263dc8ffeb..7e24d938d63 100644 --- a/source/blender/blenkernel/BKE_brush.hh +++ b/source/blender/blenkernel/BKE_brush.hh @@ -10,6 +10,8 @@ * General operations for brushes. */ +#include "BLI_span.hh" + #include "DNA_brush_enums.h" #include "DNA_color_types.h" #include "DNA_object_enums.h" @@ -87,6 +89,15 @@ void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask); * Library Operations */ void BKE_brush_curve_preset(Brush *b, enum eCurveMappingPreset preset); + +/** + * Combine the brush strength based on the distances and brush settings with the existing factors. + */ +void BKE_brush_calc_curve_factors(eBrushCurvePreset preset, + const CurveMapping *cumap, + blender::Span distances, + float brush_radius, + blender::MutableSpan factors); /** * Uses the brush curve control to find a strength value between 0 and 1. */ diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index ae75a9d2878..544311c025d 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -15,6 +15,7 @@ #include "DNA_scene_types.h" #include "BLI_listbase.h" +#include "BLI_math_base.hh" #include "BLI_math_rotation.h" #include "BLI_rand.h" @@ -2618,6 +2619,129 @@ void BKE_brush_randomize_texture_coords(UnifiedPaintSettings *ups, bool mask) } } +void BKE_brush_calc_curve_factors(const eBrushCurvePreset preset, + const CurveMapping *cumap, + const blender::Span distances, + const float brush_radius, + const blender::MutableSpan factors) +{ + BLI_assert(factors.size() == distances.size()); + + const float radius_rcp = blender::math::rcp(brush_radius); + switch (preset) { + case BRUSH_CURVE_CUSTOM: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + factors[i] *= BKE_curvemapping_evaluateF(cumap, 0, distance * radius_rcp); + } + break; + } + case BRUSH_CURVE_SHARP: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= factor * factor; + } + break; + } + case BRUSH_CURVE_SMOOTH: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= 3.0f * factor * factor - 2.0f * factor * factor * factor; + } + break; + } + case BRUSH_CURVE_SMOOTHER: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= pow3f(factor) * (factor * (factor * 6.0f - 15.0f) + 10.0f); + } + break; + } + case BRUSH_CURVE_ROOT: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= sqrtf(factor); + } + break; + } + case BRUSH_CURVE_LIN: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= factor; + } + break; + } + case BRUSH_CURVE_CONSTANT: { + break; + } + case BRUSH_CURVE_SPHERE: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= sqrtf(2 * factor - factor * factor); + } + break; + } + case BRUSH_CURVE_POW4: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= factor * factor * factor * factor; + } + break; + } + case BRUSH_CURVE_INVSQUARE: { + for (const int i : distances.index_range()) { + const float distance = distances[i]; + if (distance >= brush_radius) { + factors[i] = 0.0f; + continue; + } + const float factor = 1.0f - distance * radius_rcp; + factors[i] *= factor * (2.0f - factor); + } + break; + } + } +} + float BKE_brush_curve_strength(const eBrushCurvePreset preset, const CurveMapping *cumap, const float distance, diff --git a/source/blender/editors/sculpt_paint/brushes/clay_strips.cc b/source/blender/editors/sculpt_paint/brushes/clay_strips.cc index 3642144920f..7a65ce6186c 100644 --- a/source/blender/editors/sculpt_paint/brushes/clay_strips.cc +++ b/source/blender/editors/sculpt_paint/brushes/clay_strips.cc @@ -59,7 +59,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -68,6 +68,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_cube_distance_falloff(ss, brush, mat, positions_eval, verts, distances, factors); scale_factors(distances, cache.radius); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/crease.cc b/source/blender/editors/sculpt_paint/brushes/crease.cc index 0cc2888dddc..2d0a8707efc 100644 --- a/source/blender/editors/sculpt_paint/brushes/crease.cc +++ b/source/blender/editors/sculpt_paint/brushes/crease.cc @@ -90,7 +90,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -99,6 +99,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/draw.cc b/source/blender/editors/sculpt_paint/brushes/draw.cc index 101fb4eeae7..3c1366ca376 100644 --- a/source/blender/editors/sculpt_paint/brushes/draw.cc +++ b/source/blender/editors/sculpt_paint/brushes/draw.cc @@ -54,7 +54,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -63,6 +63,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/draw_vector_displacement.cc b/source/blender/editors/sculpt_paint/brushes/draw_vector_displacement.cc index 5edd8fedd5a..5d496412a4f 100644 --- a/source/blender/editors/sculpt_paint/brushes/draw_vector_displacement.cc +++ b/source/blender/editors/sculpt_paint/brushes/draw_vector_displacement.cc @@ -75,7 +75,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -84,6 +84,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/fill.cc b/source/blender/editors/sculpt_paint/brushes/fill.cc index 9abc9810f03..42d47c3aba8 100644 --- a/source/blender/editors/sculpt_paint/brushes/fill.cc +++ b/source/blender/editors/sculpt_paint/brushes/fill.cc @@ -56,7 +56,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -65,6 +65,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/flatten.cc b/source/blender/editors/sculpt_paint/brushes/flatten.cc index 0c063c4b440..abc0ff97268 100644 --- a/source/blender/editors/sculpt_paint/brushes/flatten.cc +++ b/source/blender/editors/sculpt_paint/brushes/flatten.cc @@ -55,7 +55,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -64,6 +64,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/inflate.cc b/source/blender/editors/sculpt_paint/brushes/inflate.cc index dff30dcafac..80af4810ef7 100644 --- a/source/blender/editors/sculpt_paint/brushes/inflate.cc +++ b/source/blender/editors/sculpt_paint/brushes/inflate.cc @@ -62,7 +62,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -71,6 +71,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/mask.cc b/source/blender/editors/sculpt_paint/brushes/mask.cc index 784b1e9ade6..c2f5fc33888 100644 --- a/source/blender/editors/sculpt_paint/brushes/mask.cc +++ b/source/blender/editors/sculpt_paint/brushes/mask.cc @@ -76,7 +76,7 @@ static void calc_faces(const Brush &brush, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide(mesh, verts, factors); - + filter_region_clip_factors(ss, positions, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -85,6 +85,7 @@ static void calc_faces(const Brush &brush, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/scrape.cc b/source/blender/editors/sculpt_paint/brushes/scrape.cc index 5beab46fef7..da9b02981c6 100644 --- a/source/blender/editors/sculpt_paint/brushes/scrape.cc +++ b/source/blender/editors/sculpt_paint/brushes/scrape.cc @@ -56,7 +56,7 @@ static void calc_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -65,6 +65,7 @@ static void calc_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/smooth.cc b/source/blender/editors/sculpt_paint/brushes/smooth.cc index fc592ee8fc0..2abb2fa659d 100644 --- a/source/blender/editors/sculpt_paint/brushes/smooth.cc +++ b/source/blender/editors/sculpt_paint/brushes/smooth.cc @@ -121,7 +121,7 @@ BLI_NOINLINE static void apply_positions_faces(const Sculpt &sd, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide_and_mask(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -130,6 +130,7 @@ BLI_NOINLINE static void apply_positions_faces(const Sculpt &sd, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (cache.automasking) { diff --git a/source/blender/editors/sculpt_paint/brushes/smooth_mask.cc b/source/blender/editors/sculpt_paint/brushes/smooth_mask.cc index 27f6f7c4d93..6c710e8d2a3 100644 --- a/source/blender/editors/sculpt_paint/brushes/smooth_mask.cc +++ b/source/blender/editors/sculpt_paint/brushes/smooth_mask.cc @@ -119,7 +119,7 @@ static void apply_masks_faces(const Brush &brush, tls.factors.reinitialize(verts.size()); const MutableSpan factors = tls.factors; fill_factor_from_hide(mesh, verts, factors); - + filter_region_clip_factors(ss, positions_eval, verts, factors); if (brush.flag & BRUSH_FRONTFACE) { calc_front_face(cache.view_normal, vert_normals, verts, factors); } @@ -128,6 +128,7 @@ static void apply_masks_faces(const Brush &brush, const MutableSpan distances = tls.distances; calc_distance_falloff( ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors); + apply_hardness_to_distances(cache, distances); calc_brush_strength_factors(cache, brush, distances, factors); if (ss.cache->automasking) { diff --git a/source/blender/editors/sculpt_paint/mesh_brush_common.hh b/source/blender/editors/sculpt_paint/mesh_brush_common.hh index 99fc1661a7b..f7c1b089c31 100644 --- a/source/blender/editors/sculpt_paint/mesh_brush_common.hh +++ b/source/blender/editors/sculpt_paint/mesh_brush_common.hh @@ -76,11 +76,21 @@ void calc_front_face(const float3 &view_normal, Span vert_indices, MutableSpan factors); +/** + * When the 3D view's clipping planes are enabled, brushes shouldn't have any effect on vertices + * outside of the planes, because they're not visible. This function disables the factors for those + * vertices. + */ +void filter_region_clip_factors(const SculptSession &ss, + Span vert_positions, + Span verts, + MutableSpan factors); + /** * Calculate distances based on the distance from the brush cursor and various other settings. * Also ignore vertices that are too far from the cursor. */ -void calc_distance_falloff(SculptSession &ss, +void calc_distance_falloff(const SculptSession &ss, Span vert_positions, Span vert_indices, eBrushFalloffShape falloff_shape, @@ -99,6 +109,12 @@ void calc_cube_distance_falloff(SculptSession &ss, MutableSpan r_distances, MutableSpan factors); +/** + * Scale the distances based on the brush radius and the cached "hardness" setting, which increases + * the strength of the effect for vertices torwards the outside of the radius. + */ +void apply_hardness_to_distances(const StrokeCache &cache, MutableSpan distances); + /** * Modify the factors based on distances to the brush cursor, using various brush settings. */ diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 6e685865d9d..1ea6400f75e 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -6615,7 +6615,34 @@ void calc_front_face(const float3 &view_normal, } } -void calc_distance_falloff(SculptSession &ss, +void filter_region_clip_factors(const SculptSession &ss, + const Span positions, + const Span verts, + const MutableSpan factors) +{ + const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d; + const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d; + if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) { + return; + } + + const ePaintSymmetryFlags mirror_symmetry_pass = ss.cache ? ss.cache->mirror_symmetry_pass : + ePaintSymmetryFlags(0); + const int radial_symmetry_pass = ss.cache ? ss.cache->radial_symmetry_pass : 0; + const float4x4 symm_rot_mat_inv = ss.cache ? ss.cache->symm_rot_mat_inv : float4x4::identity(); + for (const int i : verts.index_range()) { + float3 symm_co; + flip_v3_v3(symm_co, positions[verts[i]], mirror_symmetry_pass); + if (radial_symmetry_pass) { + symm_co = math::transform_point(symm_rot_mat_inv, symm_co); + } + if (ED_view3d_clipping_test(rv3d, symm_co, true)) { + factors[i] = 0.0f; + } + } +} + +void calc_distance_falloff(const SculptSession &ss, const Span positions, const Span verts, const eBrushFalloffShape falloff_shape, @@ -6625,21 +6652,33 @@ void calc_distance_falloff(SculptSession &ss, BLI_assert(verts.size() == factors.size()); BLI_assert(verts.size() == r_distances.size()); - SculptBrushTest test; - const SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, test, falloff_shape); + const float3 &test_location = ss.cache ? ss.cache->location : ss.cursor_location; + if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE && (ss.cache || ss.filter_cache)) { + /* The tube falloff shape requires the cached view normal. */ + const float3 &view_normal = ss.cache ? ss.cache->view_normal : ss.filter_cache->view_normal; + float4 test_plane; + plane_from_point_normal_v3(test_plane, test_location, view_normal); + for (const int i : verts.index_range()) { + float3 projected; + closest_to_plane_normalized_v3(projected, test_plane, positions[verts[i]]); + r_distances[i] = math::distance_squared(projected, test_location); + } + } + else { + for (const int i : verts.index_range()) { + r_distances[i] = math::distance_squared(test_location, positions[verts[i]]); + } + } - for (const int i : verts.index_range()) { - if (factors[i] == 0.0f) { - r_distances[i] = FLT_MAX; - continue; + const float radius_sq = ss.cache ? ss.cache->radius_squared : + ss.cursor_radius * ss.cursor_radius; + for (const int i : r_distances.index_range()) { + if (r_distances[i] < radius_sq) { + r_distances[i] = std::sqrt(r_distances[i]); } - if (!sculpt_brush_test_sq_fn(test, positions[verts[i]])) { + else { factors[i] = 0.0f; - r_distances[i] = FLT_MAX; - continue; } - r_distances[i] = math::sqrt(test.dist); } } @@ -6673,26 +6712,38 @@ void calc_cube_distance_falloff(SculptSession &ss, } } +void apply_hardness_to_distances(const StrokeCache &cache, const MutableSpan distances) +{ + const float hardness = cache.paint_brush.hardness; + if (hardness == 0.0f) { + return; + } + if (hardness == 1.0f) { + distances.fill(0.0f); + return; + } + const float radius = cache.radius; + const float threshold = hardness * radius; + const float radius_inv = math::rcp(radius); + const float hardness_inv_rcp = math::rcp(1.0f - hardness); + for (const int i : distances.index_range()) { + if (distances[i] < threshold) { + distances[i] = 0.0f; + } + else { + const float radius_factor = (distances[i] * radius_inv - hardness) * hardness_inv_rcp; + distances[i] = radius_factor * radius; + } + } +} + void calc_brush_strength_factors(const StrokeCache &cache, const Brush &brush, const Span distances, const MutableSpan factors) { - BLI_assert(factors.size() == distances.size()); - - for (const int i : factors.index_range()) { - if (factors[i] == 0.0f) { - /* Skip already masked-out points, as they might be outside of the brush radius and be - * unaffected anyway. Having such large values in the calculations below might lead to - * non-finite values, leading to undesired results. */ - continue; - } - - const float hardness = sculpt_apply_hardness(cache, distances[i]); - const float strength = BKE_brush_curve_strength(&brush, hardness, cache.radius); - - factors[i] *= strength; - } + BKE_brush_calc_curve_factors( + eBrushCurvePreset(brush.curve_preset), brush.curve, distances, cache.radius, factors); } void calc_brush_texture_factors(SculptSession &ss,