Sculpt: Initialize hairs with custom radius #118339

Merged
Hans Goudey merged 10 commits from Zyq-XDz/blender:custom-hair-radius into main 2024-02-27 18:22:16 +01:00
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

@ -8723,6 +8723,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")
@ -8730,6 +8731,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

@ -1645,10 +1645,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,
Zyq-XDz marked this conversation as resolved Outdated

"do thing without doing thing" is a confusing name! How about "calc_radius_without_interpolation"?

"do thing without doing thing" is a confusing name! How about "calc_radius_without_interpolation"?
const IndexRange new_points_range,
const float radius)
{
bke::SpanAttributeWriter radius_attr =
Zyq-XDz marked this conversation as resolved
Review

points_by_curve is unused

`points_by_curve` is unused
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();
}
Zyq-XDz marked this conversation as resolved
Review

This can be replaced with radius_attr.span.slice(new_points_range).fill(radius);

This can be replaced with `radius_attr.span.slice(new_points_range).fill(radius);`
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;
}
Zyq-XDz marked this conversation as resolved
Review

No need to call finish() in this case :)

No need to call `finish()` in this case :)
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];
Zyq-XDz marked this conversation as resolved Outdated

Spelling: radiuses -> radii

Spelling: `radiuses` -> `radii`
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);
Zyq-XDz marked this conversation as resolved
Review

If there are no neighbors, where is root_radiuses_cu coming from?

If there are no neighbors, where is `root_radiuses_cu` coming from?
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);
Zyq-XDz marked this conversation as resolved Outdated

0 -> 0.0f

`0` -> `0.0f`
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);
Zyq-XDz marked this conversation as resolved
Review

neighbor_root_cu is unused

`neighbor_root_cu` is unused
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),
Zyq-XDz marked this conversation as resolved Outdated

Best not to change these values, since they're stored in files. You can just add the new one at the end

Best not to change these values, since they're stored in files. You can just add the new one at the end
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(