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
This commit is contained in:
XDzZyq
2024-02-27 18:22:03 +01:00
committed by Hans Goudey
parent 1f9f7a07af
commit 999dfce736
10 changed files with 139 additions and 9 deletions

View File

@@ -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")

View File

@@ -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")

View File

@@ -1644,10 +1644,12 @@ void BKE_brush_init_curves_sculpt_settings(Brush *brush)
brush->curves_sculpt_settings = MEM_cnew<BrushCurvesSculptSettings>(__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);
}

View File

@@ -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_;

View File

@@ -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_;

View File

@@ -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. */

View File

@@ -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<float>("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<float> new_lengths_cu,
const Span<NeighborCurves> 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<float>("radius");
if (!radius_attr) {
return;
}
MutableSpan<float3> positions_cu = curves.positions_for_write();
MutableSpan<float> 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<float3> neighbor_positions_cu = positions_cu.slice(neighbor_points);
const Span<float> neighbor_radii_cu = radius_attr.span.slice(neighbor_points);
Array<float, 32> lengths(length_parameterize::segments_num(neighbor_points.size(), false));
length_parameterize::accumulate_lengths<float3>(neighbor_positions_cu, false, lengths);
const float neighbor_length_cu = lengths.last();
Array<float, 32> 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<int, 32> indices(points.size());
Array<float, 32> 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<float3> root_positions_cu;
Vector<float3> 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<float3> 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<float3> positions_cu = curves.positions();
Array<float> new_lengths_cu(added_curves_num);
if (inputs.interpolate_length) {
const OffsetIndices points_by_curve = curves.points_by_curve();
interpolate_from_neighbors<float>(
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"},

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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(