Fix #130323: Make Grease Pencil interpolate tool retain point distribution
The interpolate tool was using a linear re-sampling of the curves, which changes non-uniform point distributions can causes unexpected shifts in points along the curve, even very close to an input stroke (mix factor ~0 or ~1). This patch adds an alternative interpolation function with explicit segment indices and fractions, which can then be computed by the interpolation tool. The point segment factors are chosen such that each point of the input strokes has an exact matching point in the interpolation. When the factor is close to 0 or 1 the shape of the curve thus matches the respective input exactly. This approach is more similar to what GPv2 did, except instead of sub-dividing each segment it simply generates a _local_ uniform sample to fit more points into the same segment. These extra points are colinear at the extremes of the mix factor range, so the curve matches the input curves visually. Pull Request: https://projects.blender.org/blender/blender/pulls/130594
This commit is contained in:
@@ -3845,7 +3845,7 @@ def km_grease_pencil_paint_mode(params):
|
||||
"ctrl": True}, {"properties": [("use_selection", False)]}),
|
||||
(op_tool_cycle, "builtin.interpolate"), params),
|
||||
("grease_pencil.interpolate_sequence", {"type": 'E', "value": 'PRESS',
|
||||
"shift": True, "ctrl": True}, None),
|
||||
"shift": True, "ctrl": True}, {"properties": [("use_selection", False)]}),
|
||||
|
||||
# Lasso/Box erase
|
||||
("grease_pencil.erase_lasso", {"type": 'RIGHTMOUSE', "value": 'PRESS', "ctrl": True, "alt": True}, None),
|
||||
@@ -4009,7 +4009,7 @@ def km_grease_pencil_edit_mode(params):
|
||||
"ctrl": True}, {"properties": [("use_selection", True)]}),
|
||||
(op_tool_cycle, "builtin.interpolate"), params),
|
||||
("grease_pencil.interpolate_sequence", {"type": 'E', "value": 'PRESS',
|
||||
"shift": True, "ctrl": True}, None),
|
||||
"shift": True, "ctrl": True}, {"properties": [("use_selection", True)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
@@ -5713,7 +5713,7 @@ class VIEW3D_MT_edit_greasepencil(Menu):
|
||||
layout.separator()
|
||||
|
||||
layout.menu("VIEW3D_MT_edit_greasepencil_animation", text="Animation")
|
||||
layout.operator("grease_pencil.interpolate_sequence", text="Interpolate Sequence")
|
||||
layout.operator("grease_pencil.interpolate_sequence", text="Interpolate Sequence").use_selection = True
|
||||
|
||||
layout.separator()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_easing.h"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_length_parameterize.hh"
|
||||
#include "BLI_math_angle_types.hh"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
@@ -257,6 +258,21 @@ static bool find_curve_mapping_from_index(const GreasePencil &grease_pencil,
|
||||
BLI_assert(layer.has_drawing_at(interval->second));
|
||||
const Drawing &from_drawing = *grease_pencil.get_drawing_at(layer, interval->first);
|
||||
const Drawing &to_drawing = *grease_pencil.get_drawing_at(layer, interval->second);
|
||||
/* In addition to interpolated pairs, the unselected original strokes are also included, making
|
||||
* the total pair count the same as the "from" curve count. */
|
||||
const int pairs_num = from_drawing.strokes().curves_num();
|
||||
|
||||
const int old_pairs_num = pairs.from_frames.size();
|
||||
pairs.from_frames.append_n_times(interval->first, pairs_num);
|
||||
pairs.to_frames.append_n_times(interval->second, pairs_num);
|
||||
pairs.from_curves.resize(old_pairs_num + pairs_num);
|
||||
pairs.to_curves.resize(old_pairs_num + pairs_num);
|
||||
MutableSpan<int> from_curves = pairs.from_curves.as_mutable_span().slice(old_pairs_num,
|
||||
pairs_num);
|
||||
MutableSpan<int> to_curves = pairs.to_curves.as_mutable_span().slice(old_pairs_num, pairs_num);
|
||||
|
||||
/* Write source indices into the pair data. If one drawing has more selected curves than the
|
||||
* other the remainder is ignored. */
|
||||
|
||||
IndexMaskMemory memory;
|
||||
IndexMask from_selection, to_selection;
|
||||
@@ -270,21 +286,28 @@ static bool find_curve_mapping_from_index(const GreasePencil &grease_pencil,
|
||||
from_selection = from_drawing.strokes().curves_range();
|
||||
to_selection = to_drawing.strokes().curves_range();
|
||||
}
|
||||
/* Discard additional elements of the larger selection. */
|
||||
if (from_selection.size() > to_selection.size()) {
|
||||
from_selection.slice(0, to_selection.size());
|
||||
}
|
||||
else if (to_selection.size() > from_selection.size()) {
|
||||
to_selection.slice(0, from_selection.size());
|
||||
}
|
||||
|
||||
const int pairs_num = std::min(from_selection.size(), to_selection.size());
|
||||
|
||||
const int old_pairs_num = pairs.from_frames.size();
|
||||
pairs.from_frames.append_n_times(interval->first, pairs_num);
|
||||
pairs.to_frames.append_n_times(interval->second, pairs_num);
|
||||
pairs.from_curves.resize(old_pairs_num + pairs_num);
|
||||
pairs.to_curves.resize(old_pairs_num + pairs_num);
|
||||
|
||||
/* Write source indices into the pair data. The drawing with fewer curves will discard some based
|
||||
* on index. */
|
||||
from_selection.slice(0, pairs_num)
|
||||
.to_indices(pairs.from_curves.as_mutable_span().slice(old_pairs_num, pairs_num));
|
||||
to_selection.slice(0, pairs_num)
|
||||
.to_indices(pairs.to_curves.as_mutable_span().slice(old_pairs_num, pairs_num));
|
||||
/* By default: copy the "from" curve and ignore the "to" curve. */
|
||||
array_utils::fill_index_range(from_curves);
|
||||
to_curves.fill(-1);
|
||||
/* Selected curves are interpolated. */
|
||||
IndexMask::foreach_segment_zipped({from_selection, to_selection},
|
||||
[&](Span<IndexMaskSegment> segments) {
|
||||
const IndexMaskSegment &from_segment = segments[0];
|
||||
const IndexMaskSegment &to_segment = segments[1];
|
||||
BLI_assert(from_segment.size() == to_segment.size());
|
||||
for (const int i : from_segment.index_range()) {
|
||||
to_curves[from_segment[i]] = to_segment[i];
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -360,6 +383,43 @@ InterpolateOpData *InterpolateOpData::from_operator(const bContext &C, const wmO
|
||||
return data;
|
||||
}
|
||||
|
||||
/* Find ranges of sorted pairs with the same from/to frame intervals. */
|
||||
static Vector<int> find_curve_pair_offsets(const InterpolationPairs &curve_pairs,
|
||||
const Span<int> order)
|
||||
{
|
||||
Vector<int> pair_offsets;
|
||||
|
||||
int prev_from_frame = INT_MIN;
|
||||
int prev_to_frame = INT_MIN;
|
||||
int current_count = 0;
|
||||
for (const int pair_index : order) {
|
||||
const int from_frame = curve_pairs.from_frames[pair_index];
|
||||
const int to_frame = curve_pairs.to_frames[pair_index];
|
||||
if (from_frame != prev_from_frame || to_frame != prev_to_frame) {
|
||||
/* New pair. */
|
||||
if (current_count > 0) {
|
||||
pair_offsets.append(current_count);
|
||||
}
|
||||
current_count = 0;
|
||||
}
|
||||
++current_count;
|
||||
}
|
||||
if (current_count > 0) {
|
||||
pair_offsets.append(current_count);
|
||||
}
|
||||
|
||||
/* Last entry for overall size. */
|
||||
if (pair_offsets.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/* Extra element for the total size needed for OffsetIndices. */
|
||||
pair_offsets.append(0);
|
||||
offset_indices::accumulate_counts_to_offsets(pair_offsets);
|
||||
|
||||
return pair_offsets;
|
||||
}
|
||||
|
||||
static bool compute_auto_flip(const Span<float3> from_positions, const Span<float3> to_positions)
|
||||
{
|
||||
if (from_positions.size() < 2 || to_positions.size() < 2) {
|
||||
@@ -396,6 +456,72 @@ static bool compute_auto_flip(const Span<float3> from_positions, const Span<floa
|
||||
return math::dot(from_last - from_first, to_last - to_first) < 0.0f;
|
||||
}
|
||||
|
||||
/* Copy existing sample positions and insert new samples inbetween to reach the final count. */
|
||||
static void sample_curve_padded(const bke::CurvesGeometry &curves,
|
||||
const int curve_index,
|
||||
const bool cyclic,
|
||||
const bool reverse,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
MutableSpan<float> r_factors)
|
||||
{
|
||||
const int num_dst_points = r_segment_indices.size();
|
||||
const IndexRange src_points = curves.points_by_curve()[curve_index];
|
||||
/* Extra segment at the end for cyclic curves. */
|
||||
const int num_src_segments = src_points.size() - 1 + cyclic;
|
||||
/* There should be at least one source point for every output sample. */
|
||||
BLI_assert(num_dst_points >= num_src_segments);
|
||||
const Span<float3> src_positions = curves.positions();
|
||||
if (src_points.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Array<float> segment_lengths(num_src_segments + 1);
|
||||
segment_lengths[0] = 0.0f;
|
||||
for (const int i : src_points.index_range().drop_front(1)) {
|
||||
segment_lengths[i] = segment_lengths[i - 1] + math::distance(src_positions[src_points[i - 1]],
|
||||
src_positions[src_points[i]]);
|
||||
}
|
||||
if (cyclic) {
|
||||
const int i = src_points.size();
|
||||
segment_lengths[i] = segment_lengths[i - 1] + math::distance(src_positions[src_points[i - 1]],
|
||||
src_positions[src_points[0]]);
|
||||
}
|
||||
const float total_length = segment_lengths.last();
|
||||
const int num_free_samples = num_dst_points - num_src_segments;
|
||||
/* Factor for computing the fraction of remaining samples in a segment. */
|
||||
const float length_to_free_sample_count = math::safe_divide(float(num_free_samples),
|
||||
total_length);
|
||||
|
||||
int samples_start = 0;
|
||||
for (const int segment : IndexRange(num_src_segments)) {
|
||||
const int free_samples_start = math::round(segment_lengths[segment] *
|
||||
length_to_free_sample_count);
|
||||
const int free_samples_end = math::round(segment_lengths[segment + 1] *
|
||||
length_to_free_sample_count);
|
||||
const IndexRange samples = IndexRange::from_begin_size(
|
||||
samples_start, 1 + free_samples_end - free_samples_start);
|
||||
BLI_assert(samples.size() >= 1);
|
||||
samples_start = samples.one_after_last();
|
||||
|
||||
const int point_index = segment < src_points.size() ? segment : 0;
|
||||
r_segment_indices.slice(samples).fill(point_index);
|
||||
for (const int sample_i : samples.index_range()) {
|
||||
const int sample = reverse ? samples[samples.size() - 1 - sample_i] : samples[sample_i];
|
||||
const float factor = float(sample_i) / samples.size();
|
||||
r_factors[sample] = reverse ? 1.0f - factor : factor;
|
||||
}
|
||||
}
|
||||
if (cyclic) {
|
||||
r_segment_indices.last() = 0;
|
||||
r_factors.last() = 0.0f;
|
||||
}
|
||||
else {
|
||||
r_segment_indices.last() = src_points.size() - 1;
|
||||
r_factors.last() = 0.0f;
|
||||
}
|
||||
BLI_assert(samples_start == num_dst_points);
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease_pencil,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const InterpolationPairs &curve_pairs,
|
||||
@@ -425,36 +551,8 @@ static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease
|
||||
});
|
||||
|
||||
/* Find ranges of sorted pairs with the same from/to frame intervals. */
|
||||
Vector<int> pair_offsets;
|
||||
const OffsetIndices curves_by_pair = [&]() {
|
||||
int prev_from_frame = INT_MIN;
|
||||
int prev_to_frame = INT_MIN;
|
||||
int current_count = 0;
|
||||
for (const int sorted_index : IndexRange(dst_curve_num)) {
|
||||
const int pair_index = sorted_pairs[sorted_index];
|
||||
const int from_frame = curve_pairs.from_frames[pair_index];
|
||||
const int to_frame = curve_pairs.to_frames[pair_index];
|
||||
if (from_frame != prev_from_frame || to_frame != prev_to_frame) {
|
||||
/* New pair. */
|
||||
if (current_count > 0) {
|
||||
pair_offsets.append(current_count);
|
||||
}
|
||||
current_count = 0;
|
||||
}
|
||||
++current_count;
|
||||
}
|
||||
if (current_count > 0) {
|
||||
pair_offsets.append(current_count);
|
||||
}
|
||||
|
||||
/* Last entry for overall size. */
|
||||
if (pair_offsets.is_empty()) {
|
||||
return OffsetIndices<int>{};
|
||||
}
|
||||
|
||||
pair_offsets.append(0);
|
||||
return offset_indices::accumulate_counts_to_offsets(pair_offsets);
|
||||
}();
|
||||
Vector<int> pair_offsets = find_curve_pair_offsets(curve_pairs, sorted_pairs);
|
||||
const OffsetIndices<int> curves_by_pair(pair_offsets);
|
||||
|
||||
/* Compute curve length and flip mode for each pair. */
|
||||
Array<int> dst_curve_offsets(curves_by_pair.size() + 1, 0);
|
||||
@@ -486,23 +584,44 @@ static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease
|
||||
const int pair_index = sorted_pairs[sorted_index];
|
||||
const int from_curve = curve_pairs.from_curves[pair_index];
|
||||
const int to_curve = curve_pairs.to_curves[pair_index];
|
||||
const IndexRange from_points = from_points_by_curve[from_curve];
|
||||
const IndexRange to_points = to_points_by_curve[to_curve];
|
||||
|
||||
dst_curve_offsets[pair_index] = std::max(from_points.size(), to_points.size());
|
||||
switch (flip_mode) {
|
||||
case InterpolateFlipMode::None:
|
||||
dst_curve_flip[pair_index] = false;
|
||||
break;
|
||||
case InterpolateFlipMode::Flip:
|
||||
dst_curve_flip[pair_index] = true;
|
||||
break;
|
||||
case InterpolateFlipMode::FlipAuto: {
|
||||
dst_curve_flip[pair_index] = compute_auto_flip(from_positions.slice(from_points),
|
||||
to_positions.slice(to_points));
|
||||
break;
|
||||
int curve_size = 0;
|
||||
bool curve_flip = false;
|
||||
if (from_curve < 0 && to_curve < 0) {
|
||||
/* No output curve. */
|
||||
}
|
||||
else if (from_curve < 0) {
|
||||
const IndexRange to_points = to_points_by_curve[to_curve];
|
||||
curve_size = to_points.size();
|
||||
curve_flip = false;
|
||||
}
|
||||
else if (to_curve < 0) {
|
||||
const IndexRange from_points = from_points_by_curve[from_curve];
|
||||
curve_size = from_points.size();
|
||||
curve_flip = false;
|
||||
}
|
||||
else {
|
||||
const IndexRange from_points = from_points_by_curve[from_curve];
|
||||
const IndexRange to_points = to_points_by_curve[to_curve];
|
||||
|
||||
curve_size = std::max(from_points.size(), to_points.size());
|
||||
switch (flip_mode) {
|
||||
case InterpolateFlipMode::None:
|
||||
curve_flip = false;
|
||||
break;
|
||||
case InterpolateFlipMode::Flip:
|
||||
curve_flip = true;
|
||||
break;
|
||||
case InterpolateFlipMode::FlipAuto: {
|
||||
curve_flip = compute_auto_flip(from_positions.slice(from_points),
|
||||
to_positions.slice(to_points));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dst_curve_offsets[pair_index] = curve_size;
|
||||
dst_curve_flip[pair_index] = curve_flip;
|
||||
}
|
||||
}
|
||||
return offset_indices::accumulate_counts_to_offsets(dst_curve_offsets);
|
||||
@@ -522,11 +641,23 @@ static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease
|
||||
/* Sorted map arrays that can be passed to the interpolation function directly.
|
||||
* These index maps have the same order as the sorted indices, so slices of indices can be used
|
||||
* for interpolating all curves of a frame pair at once. */
|
||||
Array<int> sorted_from_curve_indices(dst_curve_num);
|
||||
Array<int> sorted_to_curve_indices(dst_curve_num);
|
||||
Array<int> from_curve_buffer(dst_curve_num);
|
||||
Array<int> to_curve_buffer(dst_curve_num);
|
||||
Array<int> from_sample_indices(dst_point_num);
|
||||
Array<int> to_sample_indices(dst_point_num);
|
||||
Array<float> from_sample_factors(dst_point_num);
|
||||
Array<float> to_sample_factors(dst_point_num);
|
||||
IndexMaskMemory memory;
|
||||
|
||||
for (const int pair_range_i : curves_by_pair.index_range()) {
|
||||
const IndexRange pair_range = curves_by_pair[pair_range_i];
|
||||
/* Subset of target curves that are filled by this frame pair. Selection is built from pair
|
||||
* indices, which correspond to dst curve indices. */
|
||||
const IndexMask dst_curve_mask = IndexMask::from_indices(
|
||||
sorted_pairs.as_span().slice(pair_range), memory);
|
||||
MutableSpan<int> from_indices = from_curve_buffer.as_mutable_span().slice(pair_range);
|
||||
MutableSpan<int> to_indices = to_curve_buffer.as_mutable_span().slice(pair_range);
|
||||
|
||||
const int first_pair_index = sorted_pairs[pair_range.first()];
|
||||
const int from_frame = curve_pairs.from_frames[first_pair_index];
|
||||
const int to_frame = curve_pairs.to_frames[first_pair_index];
|
||||
@@ -535,32 +666,73 @@ static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease
|
||||
if (!from_drawing || !to_drawing) {
|
||||
continue;
|
||||
}
|
||||
const IndexRange from_curves = from_drawing->strokes().curves_range();
|
||||
const IndexRange to_curves = to_drawing->strokes().curves_range();
|
||||
const OffsetIndices from_points_by_curve = from_drawing->strokes().points_by_curve();
|
||||
const OffsetIndices to_points_by_curve = to_drawing->strokes().points_by_curve();
|
||||
const VArray<bool> from_curves_cyclic = from_drawing->strokes().cyclic();
|
||||
const VArray<bool> to_curves_cyclic = to_drawing->strokes().cyclic();
|
||||
|
||||
IndexMaskMemory selection_memory;
|
||||
/* Subset of target curves that are filled by this frame pair. Selection is built from pair
|
||||
* indices, which correspond to dst curve indices. */
|
||||
const IndexMask dst_curve_mask = IndexMask::from_indices(
|
||||
sorted_pairs.as_span().slice(pair_range), selection_memory);
|
||||
MutableSpan<int> pair_from_indices = sorted_from_curve_indices.as_mutable_span().slice(
|
||||
pair_range);
|
||||
MutableSpan<int> pair_to_indices = sorted_to_curve_indices.as_mutable_span().slice(pair_range);
|
||||
for (const int i : pair_range) {
|
||||
const int pair_index = sorted_pairs[i];
|
||||
sorted_from_curve_indices[i] = std::clamp(
|
||||
curve_pairs.from_curves[pair_index], 0, int(from_curves.last()));
|
||||
sorted_to_curve_indices[i] = std::clamp(
|
||||
curve_pairs.to_curves[pair_index], 0, int(to_curves.last()));
|
||||
for (const int i : pair_range.index_range()) {
|
||||
const int pair_index = sorted_pairs[pair_range[i]];
|
||||
const IndexRange dst_points = dst_points_by_curve[pair_index];
|
||||
from_indices[i] = curve_pairs.from_curves[pair_index];
|
||||
to_indices[i] = curve_pairs.to_curves[pair_index];
|
||||
|
||||
const int from_curve = curve_pairs.from_curves[pair_index];
|
||||
const int to_curve = curve_pairs.to_curves[pair_index];
|
||||
|
||||
BLI_assert(from_curve >= 0 || to_curve >= 0);
|
||||
if (to_curve < 0) {
|
||||
/* Copy "from" curve. */
|
||||
array_utils::fill_index_range(from_sample_indices.as_mutable_span().slice(dst_points));
|
||||
from_sample_factors.fill(0.0f);
|
||||
continue;
|
||||
}
|
||||
if (from_curve < 0) {
|
||||
/* Copy "to" curve. */
|
||||
array_utils::fill_index_range(to_sample_indices.as_mutable_span().slice(dst_points));
|
||||
to_sample_factors.fill(0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
const IndexRange from_points = from_points_by_curve[from_curve];
|
||||
const IndexRange to_points = to_points_by_curve[to_curve];
|
||||
if (from_points.size() >= to_points.size()) {
|
||||
/* Target curve samples match 'from' points. */
|
||||
BLI_assert(from_points.size() == dst_points.size());
|
||||
array_utils::fill_index_range(from_sample_indices.as_mutable_span().slice(dst_points));
|
||||
from_sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
|
||||
sample_curve_padded(to_drawing->strokes(),
|
||||
to_curve,
|
||||
to_curves_cyclic[to_curve],
|
||||
dst_curve_flip[pair_index],
|
||||
to_sample_indices.as_mutable_span().slice(dst_points),
|
||||
to_sample_factors.as_mutable_span().slice(dst_points));
|
||||
}
|
||||
else {
|
||||
/* Target curve samples match 'to' points. */
|
||||
BLI_assert(to_points.size() == dst_points.size());
|
||||
sample_curve_padded(from_drawing->strokes(),
|
||||
from_curve,
|
||||
from_curves_cyclic[from_curve],
|
||||
dst_curve_flip[pair_index],
|
||||
from_sample_indices.as_mutable_span().slice(dst_points),
|
||||
from_sample_factors.as_mutable_span().slice(dst_points));
|
||||
array_utils::fill_index_range(to_sample_indices.as_mutable_span().slice(dst_points));
|
||||
to_sample_factors.fill(0.0f);
|
||||
}
|
||||
}
|
||||
geometry::interpolate_curves(from_drawing->strokes(),
|
||||
to_drawing->strokes(),
|
||||
pair_from_indices,
|
||||
pair_to_indices,
|
||||
dst_curve_mask,
|
||||
dst_curve_flip,
|
||||
mix_factor,
|
||||
dst_curves);
|
||||
|
||||
geometry::interpolate_curves_with_samples(from_drawing->strokes(),
|
||||
to_drawing->strokes(),
|
||||
from_indices,
|
||||
to_indices,
|
||||
from_sample_indices,
|
||||
to_sample_indices,
|
||||
from_sample_factors,
|
||||
to_sample_factors,
|
||||
dst_curve_mask,
|
||||
mix_factor,
|
||||
dst_curves);
|
||||
}
|
||||
|
||||
return dst_curves;
|
||||
@@ -1242,6 +1414,7 @@ static void grease_pencil_interpolate_sequence_ui(bContext *C, wmOperator *op)
|
||||
|
||||
row = uiLayoutRow(layout, true);
|
||||
uiItemR(row, op->ptr, "exclude_breakdowns", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
uiItemR(row, op->ptr, "use_selection", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
row = uiLayoutRow(layout, true);
|
||||
uiItemR(row, op->ptr, "flip", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
@@ -1314,6 +1487,12 @@ static void GREASE_PENCIL_OT_interpolate_sequence(wmOperatorType *ot)
|
||||
"Exclude Breakdowns",
|
||||
"Exclude existing Breakdowns keyframes as interpolation extremes");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"use_selection",
|
||||
false,
|
||||
"Use Selection",
|
||||
"Use only selected strokes for interpolating");
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"flip",
|
||||
grease_pencil_interpolate_flip_mode_items,
|
||||
|
||||
@@ -21,7 +21,19 @@ void interpolate_curves(const bke::CurvesGeometry &from_curves,
|
||||
Span<int> to_curve_indices,
|
||||
const IndexMask &dst_curve_mask,
|
||||
Span<bool> dst_curve_flip_direction,
|
||||
const float mix_factor,
|
||||
float mix_factor,
|
||||
bke::CurvesGeometry &dst_curves);
|
||||
|
||||
void interpolate_curves_with_samples(const bke::CurvesGeometry &from_curves,
|
||||
const bke::CurvesGeometry &to_curves,
|
||||
Span<int> from_curve_indices,
|
||||
Span<int> to_curve_indices,
|
||||
Span<int> from_sample_indices,
|
||||
Span<int> to_sample_indices,
|
||||
Span<float> from_sample_factors,
|
||||
Span<float> to_sample_factors,
|
||||
const IndexMask &dst_curve_mask,
|
||||
float mix_factor,
|
||||
bke::CurvesGeometry &dst_curves);
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
@@ -206,6 +206,10 @@ static void sample_curve_attribute(const bke::CurvesGeometry &src_curves,
|
||||
Vector<T> evaluated_data;
|
||||
dst_curve_mask.foreach_index([&](const int i_dst_curve, const int pos) {
|
||||
const int i_src_curve = src_curve_indices[pos];
|
||||
if (i_src_curve < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const IndexRange src_points = src_points_by_curve[i_src_curve];
|
||||
const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
|
||||
|
||||
@@ -235,14 +239,22 @@ static void mix_arrays(const Span<T> from,
|
||||
const float mix_factor,
|
||||
const MutableSpan<T> dst)
|
||||
{
|
||||
for (const int i : dst.index_range()) {
|
||||
dst[i] = math::interpolate(from[i], to[i], mix_factor);
|
||||
if (mix_factor == 0.0f) {
|
||||
dst.copy_from(from);
|
||||
}
|
||||
else if (mix_factor == 1.0f) {
|
||||
dst.copy_from(to);
|
||||
}
|
||||
else {
|
||||
for (const int i : dst.index_range()) {
|
||||
dst[i] = math::interpolate(from[i], to[i], mix_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mix_arrays(const GSpan src_from,
|
||||
const GSpan src_to,
|
||||
const float mix_factor,
|
||||
const Span<float> mix_factors,
|
||||
const IndexMask &selection,
|
||||
const GMutableSpan dst)
|
||||
{
|
||||
@@ -252,14 +264,23 @@ static void mix_arrays(const GSpan src_from,
|
||||
const Span<T> to = src_to.typed<T>();
|
||||
const MutableSpan<T> dst_typed = dst.typed<T>();
|
||||
selection.foreach_index(GrainSize(512), [&](const int curve) {
|
||||
dst_typed[curve] = math::interpolate(from[curve], to[curve], mix_factor);
|
||||
const float mix_factor = mix_factors[curve];
|
||||
if (mix_factor == 0.0f) {
|
||||
dst_typed[curve] = from[curve];
|
||||
}
|
||||
else if (mix_factor == 1.0f) {
|
||||
dst_typed[curve] = to[curve];
|
||||
}
|
||||
else {
|
||||
dst_typed[curve] = math::interpolate(from[curve], to[curve], mix_factor);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static void mix_arrays(const GSpan src_from,
|
||||
const GSpan src_to,
|
||||
const float mix_factor,
|
||||
const Span<float> mix_factors,
|
||||
const IndexMask &group_selection,
|
||||
const OffsetIndices<int> groups,
|
||||
const GMutableSpan dst)
|
||||
@@ -271,29 +292,34 @@ static void mix_arrays(const GSpan src_from,
|
||||
const Span<T> from = src_from.typed<T>();
|
||||
const Span<T> to = src_to.typed<T>();
|
||||
const MutableSpan<T> dst_typed = dst.typed<T>();
|
||||
mix_arrays(from.slice(range), to.slice(range), mix_factor, dst_typed.slice(range));
|
||||
mix_arrays(from.slice(range), to.slice(range), mix_factors[curve], dst_typed.slice(range));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
const CurvesGeometry &to_curves,
|
||||
const Span<int> from_curve_indices,
|
||||
const Span<int> to_curve_indices,
|
||||
const IndexMask &dst_curve_mask,
|
||||
const Span<bool> dst_curve_flip_direction,
|
||||
const float mix_factor,
|
||||
CurvesGeometry &dst_curves)
|
||||
void interpolate_curves_with_samples(const CurvesGeometry &from_curves,
|
||||
const CurvesGeometry &to_curves,
|
||||
const Span<int> from_curve_indices,
|
||||
const Span<int> to_curve_indices,
|
||||
const Span<int> from_sample_indices,
|
||||
const Span<int> to_sample_indices,
|
||||
const Span<float> from_sample_factors,
|
||||
const Span<float> to_sample_factors,
|
||||
const IndexMask &dst_curve_mask,
|
||||
const float mix_factor,
|
||||
CurvesGeometry &dst_curves)
|
||||
{
|
||||
BLI_assert(from_curve_indices.size() == dst_curve_mask.size());
|
||||
BLI_assert(to_curve_indices.size() == dst_curve_mask.size());
|
||||
BLI_assert(from_sample_indices.size() == dst_curves.points_num());
|
||||
BLI_assert(to_sample_indices.size() == dst_curves.points_num());
|
||||
BLI_assert(from_sample_factors.size() == dst_curves.points_num());
|
||||
BLI_assert(to_sample_factors.size() == dst_curves.points_num());
|
||||
|
||||
if (from_curves.is_empty() || to_curves.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const VArray<bool> from_curves_cyclic = from_curves.cyclic();
|
||||
const VArray<bool> to_curves_cyclic = to_curves.cyclic();
|
||||
const Span<float3> from_evaluated_positions = from_curves.evaluated_positions();
|
||||
const Span<float3> to_evaluated_positions = to_curves.evaluated_positions();
|
||||
|
||||
@@ -307,61 +333,20 @@ void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
AttributesForInterpolation curve_attributes = gather_curve_attributes_to_interpolate(
|
||||
from_curves, to_curves, dst_curves);
|
||||
|
||||
from_curves.ensure_evaluated_lengths();
|
||||
to_curves.ensure_evaluated_lengths();
|
||||
|
||||
/* Sampling arbitrary attributes works by first interpolating them to the curve's standard
|
||||
* "evaluated points" and then interpolating that result with the uniform samples. This is
|
||||
* potentially wasteful when down-sampling a curve to many fewer points. There are two possible
|
||||
* solutions: only sample the necessary points for interpolation, or first sample curve
|
||||
* parameter/segment indices and evaluate the curve directly. */
|
||||
Array<int> from_sample_indices(dst_curves.points_num());
|
||||
Array<int> to_sample_indices(dst_curves.points_num());
|
||||
Array<float> from_sample_factors(dst_curves.points_num());
|
||||
Array<float> to_sample_factors(dst_curves.points_num());
|
||||
|
||||
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
|
||||
|
||||
/* Gather uniform samples based on the accumulated lengths of the original curve. */
|
||||
dst_curve_mask.foreach_index(GrainSize(32), [&](const int i_dst_curve, const int pos) {
|
||||
Array<float> mix_factors(dst_curves.curves_num());
|
||||
dst_curve_mask.foreach_index(GrainSize(512), [&](const int i_dst_curve, const int pos) {
|
||||
const int i_from_curve = from_curve_indices[pos];
|
||||
const int i_to_curve = to_curve_indices[pos];
|
||||
const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
|
||||
const Span<float> from_lengths = from_curves.evaluated_lengths_for_curve(
|
||||
i_from_curve, from_curves_cyclic[i_from_curve]);
|
||||
const Span<float> to_lengths = to_curves.evaluated_lengths_for_curve(
|
||||
i_to_curve, to_curves_cyclic[i_to_curve]);
|
||||
|
||||
if (from_lengths.is_empty()) {
|
||||
/* Handle curves with only one evaluated point. */
|
||||
from_sample_indices.as_mutable_span().slice(dst_points).fill(0);
|
||||
from_sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
|
||||
if (i_from_curve >= 0 && i_to_curve >= 0) {
|
||||
mix_factors[i_dst_curve] = mix_factor;
|
||||
}
|
||||
else if (i_to_curve >= 0) {
|
||||
mix_factors[i_dst_curve] = 1.0f;
|
||||
}
|
||||
else {
|
||||
length_parameterize::sample_uniform(from_lengths,
|
||||
!from_curves_cyclic[i_from_curve],
|
||||
from_sample_indices.as_mutable_span().slice(dst_points),
|
||||
from_sample_factors.as_mutable_span().slice(dst_points));
|
||||
}
|
||||
if (to_lengths.is_empty()) {
|
||||
/* Handle curves with only one evaluated point. */
|
||||
to_sample_indices.as_mutable_span().slice(dst_points).fill(0);
|
||||
to_sample_factors.as_mutable_span().slice(dst_points).fill(0.0f);
|
||||
}
|
||||
else {
|
||||
if (dst_curve_flip_direction[i_dst_curve]) {
|
||||
length_parameterize::sample_uniform_reverse(
|
||||
to_lengths,
|
||||
!to_curves_cyclic[i_to_curve],
|
||||
to_sample_indices.as_mutable_span().slice(dst_points),
|
||||
to_sample_factors.as_mutable_span().slice(dst_points));
|
||||
}
|
||||
else {
|
||||
length_parameterize::sample_uniform(to_lengths,
|
||||
!to_curves_cyclic[i_to_curve],
|
||||
to_sample_indices.as_mutable_span().slice(dst_points),
|
||||
to_sample_factors.as_mutable_span().slice(dst_points));
|
||||
}
|
||||
mix_factors[i_dst_curve] = 0.0f;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -398,7 +383,7 @@ void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
to_sample_indices,
|
||||
to_sample_factors,
|
||||
to_samples);
|
||||
mix_arrays(from_samples, to_samples, mix_factor, dst_curve_mask, dst_points_by_curve, dst);
|
||||
mix_arrays(from_samples, to_samples, mix_factors, dst_curve_mask, dst_points_by_curve, dst);
|
||||
}
|
||||
else if (!src_from.is_empty()) {
|
||||
sample_curve_attribute(from_curves,
|
||||
@@ -446,7 +431,7 @@ void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
|
||||
mix_arrays(from_samples.as_span(),
|
||||
to_samples.as_span(),
|
||||
mix_factor,
|
||||
mix_factors,
|
||||
dst_curve_mask,
|
||||
dst_points_by_curve,
|
||||
dst_positions);
|
||||
@@ -473,7 +458,7 @@ void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
GArray<> to_samples(dst.type(), dst.size());
|
||||
array_utils::copy(GVArray::ForSpan(src_from), dst_curve_mask, from_samples);
|
||||
array_utils::copy(GVArray::ForSpan(src_to), dst_curve_mask, to_samples);
|
||||
mix_arrays(from_samples, to_samples, mix_factor, dst_curve_mask, dst);
|
||||
mix_arrays(from_samples, to_samples, mix_factors, dst_curve_mask, dst);
|
||||
}
|
||||
else if (!src_from.is_empty()) {
|
||||
array_utils::copy(GVArray::ForSpan(src_from), dst_curve_mask, dst);
|
||||
@@ -491,4 +476,88 @@ void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
}
|
||||
}
|
||||
|
||||
static void sample_curve_uniform(const bke::CurvesGeometry &curves,
|
||||
const int curve_index,
|
||||
const bool cyclic,
|
||||
const bool reverse,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
MutableSpan<float> r_factors)
|
||||
{
|
||||
const Span<float> segment_lengths = curves.evaluated_lengths_for_curve(curve_index, cyclic);
|
||||
if (segment_lengths.is_empty()) {
|
||||
/* Handle curves with only one evaluated point. */
|
||||
r_segment_indices.fill(0);
|
||||
r_factors.fill(0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
length_parameterize::sample_uniform_reverse(
|
||||
segment_lengths, !cyclic, r_segment_indices, r_factors);
|
||||
}
|
||||
else {
|
||||
length_parameterize::sample_uniform(segment_lengths, !cyclic, r_segment_indices, r_factors);
|
||||
}
|
||||
};
|
||||
|
||||
void interpolate_curves(const CurvesGeometry &from_curves,
|
||||
const CurvesGeometry &to_curves,
|
||||
const Span<int> from_curve_indices,
|
||||
const Span<int> to_curve_indices,
|
||||
const IndexMask &dst_curve_mask,
|
||||
const Span<bool> dst_curve_flip_direction,
|
||||
const float mix_factor,
|
||||
CurvesGeometry &dst_curves)
|
||||
{
|
||||
const VArray<bool> from_curves_cyclic = from_curves.cyclic();
|
||||
const VArray<bool> to_curves_cyclic = to_curves.cyclic();
|
||||
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
|
||||
|
||||
/* Sampling arbitrary attributes works by first interpolating them to the curve's standard
|
||||
* "evaluated points" and then interpolating that result with the uniform samples. This is
|
||||
* potentially wasteful when down-sampling a curve to many fewer points. There are two possible
|
||||
* solutions: only sample the necessary points for interpolation, or first sample curve
|
||||
* parameter/segment indices and evaluate the curve directly. */
|
||||
Array<int> from_sample_indices(dst_curves.points_num());
|
||||
Array<int> to_sample_indices(dst_curves.points_num());
|
||||
Array<float> from_sample_factors(dst_curves.points_num());
|
||||
Array<float> to_sample_factors(dst_curves.points_num());
|
||||
|
||||
from_curves.ensure_evaluated_lengths();
|
||||
to_curves.ensure_evaluated_lengths();
|
||||
|
||||
/* Gather uniform samples based on the accumulated lengths of the original curve. */
|
||||
dst_curve_mask.foreach_index(GrainSize(32), [&](const int i_dst_curve, const int pos) {
|
||||
const int i_from_curve = from_curve_indices[pos];
|
||||
const int i_to_curve = to_curve_indices[pos];
|
||||
|
||||
const IndexRange dst_points = dst_points_by_curve[i_dst_curve];
|
||||
/* First curve is sampled in forward direction, second curve may be reversed. */
|
||||
sample_curve_uniform(from_curves,
|
||||
i_from_curve,
|
||||
from_curves_cyclic[i_from_curve],
|
||||
false,
|
||||
from_sample_indices.as_mutable_span().slice(dst_points),
|
||||
from_sample_factors.as_mutable_span().slice(dst_points));
|
||||
sample_curve_uniform(to_curves,
|
||||
i_to_curve,
|
||||
to_curves_cyclic[i_to_curve],
|
||||
dst_curve_flip_direction[i_dst_curve],
|
||||
to_sample_indices.as_mutable_span().slice(dst_points),
|
||||
to_sample_factors.as_mutable_span().slice(dst_points));
|
||||
});
|
||||
|
||||
interpolate_curves_with_samples(from_curves,
|
||||
to_curves,
|
||||
from_curve_indices,
|
||||
to_curve_indices,
|
||||
from_sample_indices,
|
||||
to_sample_indices,
|
||||
from_sample_factors,
|
||||
to_sample_factors,
|
||||
dst_curve_mask,
|
||||
mix_factor,
|
||||
dst_curves);
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
|
||||
Reference in New Issue
Block a user