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:
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user