From 999dfce73609cdecccf5302099bcf0a410fa7ae6 Mon Sep 17 00:00:00 2001 From: XDzZyq Date: Tue, 27 Feb 2024 18:22:03 +0100 Subject: [PATCH] Sculpt: Add interpolate radius option for curve radius Add the option to interpolate the radius from nearby curves and add a fallback radius for when interpolation is turned off or when there are no neighbors. Resolves #117101 Pull Request: https://projects.blender.org/blender/blender/pulls/118339 --- .../startup/bl_ui/properties_paint_common.py | 10 +- scripts/startup/bl_ui/space_view3d.py | 5 + source/blender/blenkernel/intern/brush.cc | 2 + .../editors/sculpt_paint/curves_sculpt_add.cc | 8 +- .../sculpt_paint/curves_sculpt_density.cc | 3 + .../geometry/GEO_add_curves_on_mesh.hh | 2 + .../geometry/intern/add_curves_on_mesh.cc | 97 ++++++++++++++++++- source/blender/makesdna/DNA_brush_enums.h | 1 + source/blender/makesdna/DNA_brush_types.h | 4 +- source/blender/makesrna/intern/rna_brush.cc | 16 +++ 10 files changed, 139 insertions(+), 9 deletions(-) diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 7f3f0f70d35..3707557e256 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -801,16 +801,22 @@ def brush_settings(layout, context, brush, popover=False): layout.prop(brush.curves_sculpt_settings, "add_amount") col = layout.column(heading="Interpolate", align=True) col.prop(brush.curves_sculpt_settings, "interpolate_length", text="Length") + col.prop(brush.curves_sculpt_settings, "interpolate_radius", text="Radius") col.prop(brush.curves_sculpt_settings, "interpolate_shape", text="Shape") col.prop(brush.curves_sculpt_settings, "interpolate_point_count", text="Point Count") col = layout.column() col.active = not brush.curves_sculpt_settings.interpolate_length - col.prop(brush.curves_sculpt_settings, "curve_length") + col.prop(brush.curves_sculpt_settings, "curve_length", text="Length") + + col = layout.column() + col.active = not brush.curves_sculpt_settings.interpolate_radius + col.prop(brush.curves_sculpt_settings, "curve_radius", text="Radius") col = layout.column() col.active = not brush.curves_sculpt_settings.interpolate_point_count - col.prop(brush.curves_sculpt_settings, "points_per_curve") + col.prop(brush.curves_sculpt_settings, "points_per_curve", text="Points") + elif brush.curves_sculpt_tool == 'GROW_SHRINK': layout.prop(brush.curves_sculpt_settings, "scale_uniform") layout.prop(brush.curves_sculpt_settings, "minimum_length") diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index a2029153f92..4eee74c358d 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -8758,6 +8758,7 @@ class VIEW3D_PT_curves_sculpt_add_shape(Panel): col = layout.column(heading="Interpolate", align=True) col.prop(brush.curves_sculpt_settings, "interpolate_length", text="Length") + col.prop(brush.curves_sculpt_settings, "interpolate_radius", text="Radius") col.prop(brush.curves_sculpt_settings, "interpolate_shape", text="Shape") col.prop(brush.curves_sculpt_settings, "interpolate_point_count", text="Point Count") @@ -8765,6 +8766,10 @@ class VIEW3D_PT_curves_sculpt_add_shape(Panel): col.active = not brush.curves_sculpt_settings.interpolate_length col.prop(brush.curves_sculpt_settings, "curve_length", text="Length") + col = layout.column() + col.active = not brush.curves_sculpt_settings.interpolate_radius + col.prop(brush.curves_sculpt_settings, "curve_radius", text="Radius") + col = layout.column() col.active = not brush.curves_sculpt_settings.interpolate_point_count col.prop(brush.curves_sculpt_settings, "points_per_curve", text="Points") diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index 6abda3f884e..6806806d088 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -1644,10 +1644,12 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush) brush->curves_sculpt_settings = MEM_cnew(__func__); } BrushCurvesSculptSettings *settings = brush->curves_sculpt_settings; + settings->flag = BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_RADIUS; settings->add_amount = 1; settings->points_per_curve = 8; settings->minimum_length = 0.01f; settings->curve_length = 0.3f; + settings->curve_radius = 0.01f; settings->density_add_attempts = 100; settings->curve_parameter_falloff = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f); } diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc index 38af1f52802..6b231fcb33e 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_add.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_add.cc @@ -207,12 +207,15 @@ struct AddOperationExecutor { add_inputs.uvs = sampled_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; + add_inputs.interpolate_radius = brush_settings_->flag & + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_RADIUS; add_inputs.interpolate_shape = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE; add_inputs.interpolate_point_count = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.interpolate_resolution = curves_orig_->attributes().contains("resolution"); add_inputs.fallback_curve_length = brush_settings_->curve_length; + add_inputs.fallback_curve_radius = brush_settings_->curve_radius; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); add_inputs.transforms = &transforms_; add_inputs.surface_corner_tris = surface_corner_tris_orig; @@ -220,8 +223,9 @@ struct AddOperationExecutor { add_inputs.surface = &surface_orig; add_inputs.corner_normals_su = corner_normals_su; - if (add_inputs.interpolate_length || add_inputs.interpolate_shape || - add_inputs.interpolate_point_count || add_inputs.interpolate_resolution) + if (add_inputs.interpolate_length || add_inputs.interpolate_radius || + add_inputs.interpolate_shape || add_inputs.interpolate_point_count || + add_inputs.interpolate_resolution) { this->ensure_curve_roots_kdtree(); add_inputs.old_roots_kdtree = self_->curve_roots_kdtree_; diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc index 5e26beba3c9..a1e698a42dc 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_density.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_density.cc @@ -261,12 +261,15 @@ struct DensityAddOperationExecutor { add_inputs.uvs = new_uvs; add_inputs.interpolate_length = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH; + add_inputs.interpolate_radius = brush_settings_->flag & + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_RADIUS; add_inputs.interpolate_shape = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE; add_inputs.interpolate_point_count = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT; add_inputs.interpolate_resolution = curves_orig_->attributes().contains("resolution"); add_inputs.fallback_curve_length = brush_settings_->curve_length; + add_inputs.fallback_curve_radius = brush_settings_->curve_radius; add_inputs.fallback_point_count = std::max(2, brush_settings_->points_per_curve); add_inputs.transforms = &transforms_; add_inputs.surface = surface_orig_; diff --git a/source/blender/geometry/GEO_add_curves_on_mesh.hh b/source/blender/geometry/GEO_add_curves_on_mesh.hh index f5b0f188813..ef31028628b 100644 --- a/source/blender/geometry/GEO_add_curves_on_mesh.hh +++ b/source/blender/geometry/GEO_add_curves_on_mesh.hh @@ -22,10 +22,12 @@ struct AddCurvesOnMeshInputs { /** Determines shape of new curves. */ bool interpolate_length = false; + bool interpolate_radius = false; bool interpolate_shape = false; bool interpolate_point_count = false; bool interpolate_resolution = false; float fallback_curve_length = 0.0f; + float fallback_curve_radius = 0.0f; int fallback_point_count = 0; /** Information about the surface that the new curves are attached to. */ diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index 35c711a9c73..528b762fae9 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -236,13 +236,92 @@ static void interpolate_position_with_interpolation(CurvesGeometry &curves, }); } +static void calc_radius_without_interpolation(CurvesGeometry &curves, + const IndexRange new_points_range, + const float radius) +{ + bke::SpanAttributeWriter radius_attr = + curves.attributes_for_write().lookup_or_add_for_write_span("radius", + bke::AttrDomain::Point); + radius_attr.span.slice(new_points_range).fill(radius); + radius_attr.finish(); +} + +static void calc_radius_with_interpolation(CurvesGeometry &curves, + const int old_curves_num, + const float radius, + const Span new_lengths_cu, + const Span neighbors_per_curve) +{ + const int added_curves_num = new_lengths_cu.size(); + const OffsetIndices points_by_curve = curves.points_by_curve(); + bke::SpanAttributeWriter radius_attr = + curves.attributes_for_write().lookup_for_write_span("radius"); + + if (!radius_attr) { + return; + } + + MutableSpan positions_cu = curves.positions_for_write(); + MutableSpan radii_cu = radius_attr.span; + + threading::parallel_for(IndexRange(added_curves_num), 256, [&](const IndexRange range) { + for (const int i : range) { + const NeighborCurves &neighbors = neighbors_per_curve[i]; + const float length_cu = new_lengths_cu[i]; + const int curve_i = old_curves_num + i; + const IndexRange points = points_by_curve[curve_i]; + + if (neighbors.is_empty()) { + /* If there are no neighbors, just using uniform radius. */ + radii_cu.slice(points).fill(radius); + continue; + } + + radii_cu.slice(points).fill(0.0f); + + for (const NeighborCurve &neighbor : neighbors) { + const int neighbor_curve_i = neighbor.index; + const IndexRange neighbor_points = points_by_curve[neighbor_curve_i]; + const Span neighbor_positions_cu = positions_cu.slice(neighbor_points); + const Span neighbor_radii_cu = radius_attr.span.slice(neighbor_points); + + Array lengths(length_parameterize::segments_num(neighbor_points.size(), false)); + length_parameterize::accumulate_lengths(neighbor_positions_cu, false, lengths); + + const float neighbor_length_cu = lengths.last(); + + Array sample_lengths(points.size()); + const float length_factor = std::min(1.0f, length_cu / neighbor_length_cu); + const float resample_factor = (1.0f / (points.size() - 1.0f)) * length_factor; + for (const int i : sample_lengths.index_range()) { + sample_lengths[i] = i * resample_factor * neighbor_length_cu; + } + + Array indices(points.size()); + Array factors(points.size()); + length_parameterize::sample_at_lengths(lengths, sample_lengths, indices, factors); + + for (const int i : IndexRange(points.size())) { + const float sample_cu = math::interpolate( + neighbor_radii_cu[indices[i]], neighbor_radii_cu[indices[i] + 1], factors[i]); + + radii_cu[points[i]] += neighbor.weight * sample_cu; + } + } + } + }); + radius_attr.finish(); +} + AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, const AddCurvesOnMeshInputs &inputs) { AddCurvesOnMeshOutputs outputs; const bool use_interpolation = inputs.interpolate_length || inputs.interpolate_point_count || - inputs.interpolate_shape || inputs.interpolate_resolution; + inputs.interpolate_radius || inputs.interpolate_shape || + inputs.interpolate_resolution; Vector root_positions_cu; Vector bary_coords; @@ -314,7 +393,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, const int new_points_num = curves.offsets().last(); curves.resize(new_points_num, new_curves_num); - MutableSpan positions_cu = curves.positions_for_write(); + const OffsetIndices points_by_curve = curves.points_by_curve(); /* The new elements are added at the end of the arrays. */ outputs.new_points_range = curves.points_range().drop_front(old_points_num); @@ -325,9 +404,9 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, surface_uv_coords.take_back(added_curves_num).copy_from(used_uvs); /* Determine length of new curves. */ + Span positions_cu = curves.positions(); Array new_lengths_cu(added_curves_num); if (inputs.interpolate_length) { - const OffsetIndices points_by_curve = curves.points_by_curve(); interpolate_from_neighbors( neighbors_per_curve, inputs.fallback_curve_length, @@ -378,6 +457,16 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, inputs.transforms->surface_to_curves_normal); } + /* Initialize radius attribute */ + if (inputs.interpolate_radius) { + calc_radius_with_interpolation( + curves, old_curves_num, inputs.fallback_curve_radius, new_lengths_cu, neighbors_per_curve); + } + else { + calc_radius_without_interpolation( + curves, outputs.new_points_range, inputs.fallback_curve_radius); + } + curves.fill_curve_types(new_curves_range, CURVE_TYPE_CATMULL_ROM); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); @@ -400,7 +489,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, /* Explicitly set all other attributes besides those processed above to default values. */ bke::fill_attribute_range_default( - attributes, bke::AttrDomain::Point, {"position"}, outputs.new_points_range); + attributes, bke::AttrDomain::Point, {"position", "radius"}, outputs.new_points_range); bke::fill_attribute_range_default(attributes, bke::AttrDomain::Curve, {"curve_type", "surface_uv_coordinate", "resolution"}, diff --git a/source/blender/makesdna/DNA_brush_enums.h b/source/blender/makesdna/DNA_brush_enums.h index 8f5e93dfdab..034562c12dd 100644 --- a/source/blender/makesdna/DNA_brush_enums.h +++ b/source/blender/makesdna/DNA_brush_enums.h @@ -656,6 +656,7 @@ typedef enum eBrushCurvesSculptFlag { BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH = (1 << 2), BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE = (1 << 3), BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT = (1 << 4), + BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_RADIUS = (1 << 5), } eBrushCurvesSculptFlag; typedef enum eBrushCurvesSculptDensityMode { diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h index f4800083f53..069ba23103f 100644 --- a/source/blender/makesdna/DNA_brush_types.h +++ b/source/blender/makesdna/DNA_brush_types.h @@ -155,11 +155,13 @@ typedef struct BrushCurvesSculptSettings { float curve_length; /** Minimum distance between curve root points used by the Density brush. */ float minimum_distance; + /** The initial radius of curve. **/ + float curve_radius; /** How often the Density brush tries to add a new curve. */ int density_add_attempts; /** #eBrushCurvesSculptDensityMode. */ uint8_t density_mode; - char _pad[3]; + char _pad[7]; struct CurveMapping *curve_parameter_falloff; } BrushCurvesSculptSettings; diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index bf92734b77c..68842587189 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -2173,6 +2173,13 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Interpolate Length", "Use length of the curves in close proximity"); + prop = RNA_def_property(srna, "interpolate_radius", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna( + prop, nullptr, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_RADIUS); + RNA_def_property_boolean_default(prop, true); + RNA_def_property_ui_text( + prop, "Interpolate Radius", "Use radius of the curves in close proximity"); + prop = RNA_def_property(srna, "interpolate_point_count", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna( prop, nullptr, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT); @@ -2198,6 +2205,15 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Minimum Distance", "Goal distance between curve roots for the Density brush"); + prop = RNA_def_property(srna, "curve_radius", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_range(prop, 0.0, FLT_MAX); + RNA_def_property_float_default(prop, 0.01f); + RNA_def_property_ui_range(prop, 0.0, 1000.0f, 0.001, 2); + RNA_def_property_ui_text( + prop, + "Curve Radius", + "Radius of newly added curves when it is not interpolated from other curves"); + prop = RNA_def_property(srna, "density_add_attempts", PROP_INT, PROP_NONE); RNA_def_property_range(prop, 0, INT32_MAX); RNA_def_property_ui_text(