Curves: Interpolate point count in add brush

This commit adds an option to interpolate the number of control points
in new curves based on the count in neighboring existing curves. The
idea is to provide a more automatic default than manually controlling
the number of points in a curve, so users don't have to think about
the resolution quite as much.

Internally, some utilities for creating new curves are extracted to a
new header file. These can be used for the various nodes and operators
that create new curves.

The top-bar UI will be adjusted in a separate patch, probably moving
all of the settings that affect the size and shape of the new curves
into a popover.

Differential Revision: https://developer.blender.org/D14877
This commit is contained in:
Hans Goudey 2022-05-10 18:27:24 +02:00
parent 6f7959f55f
commit 8852191b77
Notes: blender-bot 2024-01-16 18:05:25 +01:00
Referenced by commit 195986a719, Fix: Curves interpolate point count option missing from panels
8 changed files with 140 additions and 72 deletions

View File

@ -523,6 +523,7 @@ class _draw_tool_settings_context_mode:
layout.prop(brush.curves_sculpt_settings, "curve_length")
layout.prop(brush.curves_sculpt_settings, "interpolate_length")
layout.prop(brush.curves_sculpt_settings, "interpolate_shape")
layout.prop(brush.curves_sculpt_settings, "interpolate_point_count")
if brush.curves_sculpt_tool == 'GROW_SHRINK':
layout.prop(brush, "direction", expand=True, text="")

View File

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_curves.hh"
/** \file
* \ingroup bke
* \brief Low-level operations for curves.
*/
namespace blender::bke::curves {
/**
* Copy the size of every curve in #curve_ranges to the corresponding index in #counts.
*/
void fill_curve_counts(const bke::CurvesGeometry &curves,
Span<IndexRange> curve_ranges,
MutableSpan<int> counts);
/**
* Turn an array of sizes into the offset at each index including all previous sizes.
*/
void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets, int start_offset = 0);
} // namespace blender::bke::curves

View File

@ -117,6 +117,7 @@ set(SRC
intern/curveprofile.cc
intern/curves.cc
intern/curves_geometry.cc
intern/curves_utils.cc
intern/customdata.cc
intern/customdata_file.c
intern/data_transfer.c
@ -356,6 +357,7 @@ set(SRC
BKE_curveprofile.h
BKE_curves.h
BKE_curves.hh
BKE_curves_utils.hh
BKE_customdata.h
BKE_customdata_file.h
BKE_data_transfer.h

View File

@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BKE_curves_utils.hh"
namespace blender::bke::curves {
void fill_curve_counts(const bke::CurvesGeometry &curves,
const Span<IndexRange> curve_ranges,
MutableSpan<int> counts)
{
threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange ranges_range) {
for (const IndexRange curves_range : curve_ranges.slice(ranges_range)) {
threading::parallel_for(curves_range, 4096, [&](IndexRange range) {
for (const int i : range) {
counts[i] = curves.points_for_curve(i).size();
}
});
}
});
}
void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets, const int start_offset)
{
int offset = start_offset;
for (const int i : counts_to_offsets.index_range().drop_back(1)) {
const int count = counts_to_offsets[i];
BLI_assert(count > 0);
counts_to_offsets[i] = offset;
offset += count;
}
counts_to_offsets.last() = offset;
}
} // namespace blender::bke::curves

View File

@ -18,6 +18,7 @@
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
@ -105,10 +106,11 @@ struct AddOperationExecutor {
bool use_front_face_;
bool interpolate_length_;
bool interpolate_shape_;
bool interpolate_point_count_;
bool use_interpolation_;
float new_curve_length_;
int add_amount_;
int points_per_curve_;
int constant_points_per_curve_;
/** Various matrices to convert between coordinate spaces. */
float4x4 curves_to_world_mat_;
@ -129,6 +131,15 @@ struct AddOperationExecutor {
Vector<int> looptri_indices;
};
struct NeighborInfo {
/* Curve index of the neighbor. */
int index;
/* The weights of all neighbors of a new curve add up to 1. */
float weight;
};
static constexpr int max_neighbors = 5;
using NeighborsVector = Vector<NeighborInfo, max_neighbors>;
void execute(AddOperation &self, bContext *C, const StrokeExtension &stroke_extension)
{
self_ = &self;
@ -172,10 +183,12 @@ struct AddOperationExecutor {
const eBrushFalloffShape falloff_shape = static_cast<eBrushFalloffShape>(
brush_->falloff_shape);
add_amount_ = std::max(0, brush_settings_->add_amount);
points_per_curve_ = std::max(2, brush_settings_->points_per_curve);
constant_points_per_curve_ = std::max(2, brush_settings_->points_per_curve);
interpolate_length_ = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH;
interpolate_shape_ = brush_settings_->flag & BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE;
use_interpolation_ = interpolate_length_ || interpolate_shape_;
interpolate_point_count_ = brush_settings_->flag &
BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT;
use_interpolation_ = interpolate_length_ || interpolate_shape_ || interpolate_point_count_;
new_curve_length_ = brush_settings_->curve_length;
tot_old_curves_ = curves_->curves_num();
@ -215,18 +228,26 @@ struct AddOperationExecutor {
return;
}
Array<NeighborsVector> neighbors_per_curve;
if (use_interpolation_) {
this->ensure_curve_roots_kdtree();
neighbors_per_curve = this->find_curve_neighbors(added_points);
}
/* Resize to add the new curves, building the offests in the array owned by thge curves. */
const int tot_added_curves = added_points.bary_coords.size();
const int tot_added_points = tot_added_curves * points_per_curve_;
curves_->resize(curves_->points_num(), curves_->curves_num() + tot_added_curves);
if (interpolate_point_count_) {
this->initialize_curve_offsets_with_interpolation(neighbors_per_curve);
}
else {
this->initialize_curve_offsets_without_interpolation(constant_points_per_curve_);
}
curves_->resize(curves_->points_num() + tot_added_points,
curves_->curves_num() + tot_added_curves);
/* Resize to add the correct point count calculated as part of building the offsets. */
curves_->resize(curves_->offsets().last(), curves_->curves_num());
threading::parallel_invoke([&]() { this->initialize_curve_offsets(tot_added_curves); },
[&]() { this->initialize_attributes(added_points); });
this->initialize_attributes(added_points, neighbors_per_curve);
curves_->update_curve_types();
@ -580,33 +601,37 @@ struct AddOperationExecutor {
}
}
void initialize_curve_offsets(const int tot_added_curves)
void initialize_curve_offsets_with_interpolation(const Span<NeighborsVector> neighbors_per_curve)
{
MutableSpan<int> offsets = curves_->offsets_for_write();
threading::parallel_for(IndexRange(tot_added_curves), 1024, [&](const IndexRange range) {
for (const int i : range) {
const int curve_i = tot_old_curves_ + i;
offsets[curve_i + 1] = tot_old_points_ + (i + 1) * points_per_curve_;
MutableSpan<int> new_offsets = curves_->offsets_for_write().drop_front(tot_old_curves_);
attribute_math::DefaultMixer<int> mixer{new_offsets};
threading::parallel_for(neighbors_per_curve.index_range(), 1024, [&](IndexRange curves_range) {
for (const int i : curves_range) {
for (const NeighborInfo &neighbor : neighbors_per_curve[i]) {
const int neighbor_points_num = curves_->points_for_curve(neighbor.index).size();
mixer.mix_in(i, neighbor_points_num, neighbor.weight);
}
}
});
mixer.finalize();
bke::curves::accumulate_counts_to_offsets(new_offsets, tot_old_points_);
}
struct NeighborInfo {
/* Curve index of the neighbor. */
int index;
/* The weights of all neighbors of a new curve add up to 1. */
float weight;
};
static constexpr int max_neighbors = 5;
using NeighborsVector = Vector<NeighborInfo, max_neighbors>;
void initialize_attributes(const AddedPoints &added_points)
void initialize_curve_offsets_without_interpolation(const int points_per_curve)
{
Array<NeighborsVector> neighbors_per_curve;
if (use_interpolation_) {
neighbors_per_curve = this->find_curve_neighbors(added_points);
MutableSpan<int> new_offsets = curves_->offsets_for_write().drop_front(tot_old_curves_);
int offset = tot_old_points_;
for (const int i : new_offsets.index_range()) {
new_offsets[i] = offset;
offset += points_per_curve;
}
}
void initialize_attributes(const AddedPoints &added_points,
const Span<NeighborsVector> neighbors_per_curve)
{
Array<float> new_lengths_cu(added_points.bary_coords.size());
if (interpolate_length_) {
this->interpolate_lengths(neighbors_per_curve, new_lengths_cu);
@ -735,15 +760,14 @@ struct AddOperationExecutor {
threading::parallel_for(
added_points.bary_coords.index_range(), 256, [&](const IndexRange range) {
for (const int i : range) {
const int first_point_i = tot_old_points_ + i * points_per_curve_;
const IndexRange points = curves_->points_for_curve(tot_old_curves_ + i);
const float3 &root_cu = added_points.positions_cu[i];
const float length = lengths_cu[i];
const float3 &normal_su = normals_su[i];
const float3 normal_cu = math::normalize(surface_to_curves_normal_mat_ * normal_su);
const float3 tip_cu = root_cu + length * normal_cu;
initialize_straight_curve_positions(
root_cu, tip_cu, positions_cu.slice(first_point_i, points_per_curve_));
initialize_straight_curve_positions(root_cu, tip_cu, positions_cu.slice(points));
}
});
}
@ -764,23 +788,22 @@ struct AddOperationExecutor {
added_points.bary_coords.index_range(), 256, [&](const IndexRange range) {
for (const int i : range) {
const Span<NeighborInfo> neighbors = neighbors_per_curve[i];
const IndexRange points = curves_->points_for_curve(tot_old_curves_ + i);
const float length_cu = new_lengths_cu[i];
const float3 &normal_su = new_normals_su[i];
const float3 normal_cu = math::normalize(surface_to_curves_normal_mat_ * normal_su);
const float3 &root_cu = added_points.positions_cu[i];
const int first_point_i = tot_old_points_ + i * points_per_curve_;
if (neighbors.is_empty()) {
/* If there are no neighbors, just make a straight line. */
const float3 tip_cu = root_cu + length_cu * normal_cu;
initialize_straight_curve_positions(
root_cu, tip_cu, positions_cu.slice(first_point_i, points_per_curve_));
initialize_straight_curve_positions(root_cu, tip_cu, positions_cu.slice(points));
continue;
}
positions_cu.slice(first_point_i, points_per_curve_).fill(root_cu);
positions_cu.slice(points).fill(root_cu);
for (const NeighborInfo &neighbor : neighbors) {
const int neighbor_curve_i = neighbor.index;
@ -814,8 +837,8 @@ struct AddOperationExecutor {
const float neighbor_length_cu = neighbor_spline.length();
const float length_factor = std::min(1.0f, length_cu / neighbor_length_cu);
const float resample_factor = (1.0f / (points_per_curve_ - 1.0f)) * length_factor;
for (const int j : IndexRange(points_per_curve_)) {
const float resample_factor = (1.0f / (points.size() - 1.0f)) * length_factor;
for (const int j : IndexRange(points.size())) {
const Spline::LookupResult lookup = neighbor_spline.lookup_evaluated_factor(
j * resample_factor);
const float index_factor = lookup.evaluated_index + lookup.factor;
@ -825,7 +848,7 @@ struct AddOperationExecutor {
const float3 relative_coord = p - neighbor_root_cu;
float3 rotated_relative_coord = relative_coord;
mul_m3_v3(normal_rotation_cu, rotated_relative_coord);
positions_cu[first_point_i + j] += neighbor.weight * rotated_relative_coord;
positions_cu[points[j]] += neighbor.weight * rotated_relative_coord;
}
}
}

View File

@ -8,6 +8,7 @@
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_curves_utils.hh"
#include "BKE_geometry_fields.hh"
#include "GEO_resample_curves.hh"
@ -196,37 +197,6 @@ static void copy_between_curves(const bke::CurvesGeometry &src_curves,
});
}
/**
* Copy the size of every curve in #curve_ranges to the corresponding index in #counts.
*/
static void fill_curve_counts(const bke::CurvesGeometry &src_curves,
const Span<IndexRange> curve_ranges,
MutableSpan<int> counts)
{
threading::parallel_for(curve_ranges.index_range(), 512, [&](IndexRange ranges_range) {
for (const IndexRange curves_range : curve_ranges.slice(ranges_range)) {
for (const int i : curves_range) {
counts[i] = src_curves.points_for_curve(i).size();
}
}
});
}
/**
* Turn an array of sizes into the offset at each index including all previous sizes.
*/
static void accumulate_counts_to_offsets(MutableSpan<int> counts_to_offsets)
{
int total = 0;
for (const int i : counts_to_offsets.index_range().drop_back(1)) {
const int count = counts_to_offsets[i];
BLI_assert(count > 0);
counts_to_offsets[i] = total;
total += count;
}
counts_to_offsets.last() = total;
}
static Curves *resample_to_uniform(const CurveComponent &src_component,
const fn::Field<bool> &selection_field,
const fn::Field<int> &count_field)
@ -257,8 +227,8 @@ static Curves *resample_to_uniform(const CurveComponent &src_component,
src_curves.curves_range(), nullptr);
/* Fill the counts for the curves that aren't selected and accumulate the counts into offsets. */
fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
accumulate_counts_to_offsets(dst_offsets);
bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
bke::curves::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
/* All resampled curves are poly curves. */
@ -428,8 +398,8 @@ Curves *resample_to_evaluated(const CurveComponent &src_component,
dst_offsets[i] = src_curves.evaluated_points_for_curve(i).size();
}
});
fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
accumulate_counts_to_offsets(dst_offsets);
bke::curves::fill_curve_counts(src_curves, unselected_ranges, dst_offsets);
bke::curves::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());

View File

@ -618,6 +618,7 @@ typedef enum eBrushCurvesSculptFlag {
BRUSH_CURVES_SCULPT_FLAG_GROW_SHRINK_INVERT = (1 << 1),
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),
} eBrushCurvesSculptFlag;
#define MAX_BRUSH_PIXEL_RADIUS 500

View File

@ -1975,6 +1975,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_point_count", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, NULL, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_POINT_COUNT);
RNA_def_property_ui_text(prop,
"Interpolate Point Count",
"Use the number of points from the curves in close proximity");
prop = RNA_def_property(srna, "interpolate_shape", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE);
RNA_def_property_ui_text(