GPv3: Interpolate Tool
Interpolation tool for strokes ported from GPv2. Adds a new operator that inserts a new frame with interpolated curves. The source curves are taken from the previous/next keyframe. Co-authored-by: Hans Goudey <hans@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/122155
This commit is contained in:
@@ -4578,7 +4578,7 @@ def km_gpencil_legacy_stroke_vertex_replace(_params):
|
||||
|
||||
|
||||
# Grease Pencil v3
|
||||
def km_grease_pencil_paint_mode(_params):
|
||||
def km_grease_pencil_paint_mode(params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Grease Pencil Paint Mode",
|
||||
@@ -4600,6 +4600,10 @@ def km_grease_pencil_paint_mode(_params):
|
||||
# Isolate Layer
|
||||
("grease_pencil.layer_isolate", {"type": 'NUMPAD_ASTERIX', "value": 'PRESS'}, None),
|
||||
|
||||
op_tool_optional(
|
||||
("grease_pencil.interpolate", {"type": 'E', "value": 'PRESS', "ctrl": True}, None),
|
||||
(op_tool_cycle, "builtin.interpolate"), params),
|
||||
|
||||
op_asset_shelf_popup(
|
||||
"VIEW3D_AST_brush_gpencil_paint",
|
||||
{"type": 'SPACE', "value": 'PRESS', "shift": True}
|
||||
@@ -4730,6 +4734,9 @@ def km_grease_pencil_edit_mode(params):
|
||||
# Set Handle Type
|
||||
("grease_pencil.set_handle_type", {"type": 'V', "value": 'PRESS'}, None),
|
||||
|
||||
op_tool_optional(
|
||||
("grease_pencil.interpolate", {"type": 'E', "value": 'PRESS', "ctrl": True}, None),
|
||||
(op_tool_cycle, "builtin.interpolate"), params),
|
||||
])
|
||||
|
||||
return keymap
|
||||
@@ -8626,6 +8633,27 @@ def km_3d_view_tool_paint_gpencil_interpolate(params):
|
||||
)
|
||||
|
||||
|
||||
def km_grease_pencil_interpolate_tool_modal_map(params):
|
||||
items = []
|
||||
keymap = (
|
||||
"Interpolate Tool Modal Map",
|
||||
{"space_type": 'EMPTY', "region_type": 'WINDOW', "modal": True},
|
||||
{"items": items},
|
||||
)
|
||||
|
||||
items.extend([
|
||||
("CANCEL", {"type": 'ESC', "value": 'PRESS', "any": True}, None),
|
||||
("CANCEL", {"type": 'RIGHTMOUSE', "value": 'PRESS', "any": True}, None),
|
||||
("CONFIRM", {"type": 'RET', "value": 'PRESS', "any": True}, None),
|
||||
("CONFIRM", {"type": 'NUMPAD_ENTER', "value": 'PRESS', "any": True}, None),
|
||||
("CONFIRM", {"type": 'LEFTMOUSE', "value": 'RELEASE', "any": True}, None),
|
||||
("INCREASE", {"type": 'WHEELUPMOUSE', "value": 'PRESS'}, None),
|
||||
("DECREASE", {"type": 'WHEELDOWNMOUSE', "value": 'PRESS'}, None),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Tool System (3D View, Grease Pencil, Edit)
|
||||
|
||||
@@ -8967,6 +8995,28 @@ def km_sequencer_editor_tool_scale(params):
|
||||
)
|
||||
|
||||
|
||||
def km_3d_view_tool_edit_grease_pencil_interpolate(params):
|
||||
return (
|
||||
"3D View Tool: Edit Grease Pencil, Interpolate",
|
||||
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
|
||||
{"items": [
|
||||
("grease_pencil.interpolate", params.tool_maybe_tweak_event,
|
||||
None),
|
||||
]},
|
||||
)
|
||||
|
||||
|
||||
def km_3d_view_tool_paint_grease_pencil_interpolate(params):
|
||||
return (
|
||||
"3D View Tool: Paint Grease Pencil, Interpolate",
|
||||
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
|
||||
{"items": [
|
||||
("grease_pencil.interpolate", params.tool_maybe_tweak_event,
|
||||
None),
|
||||
]},
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Full Configuration
|
||||
|
||||
@@ -9116,6 +9166,7 @@ def generate_keymaps(params=None):
|
||||
km_node_link_modal_map(params),
|
||||
km_grease_pencil_primitive_tool_modal_map(params),
|
||||
km_grease_pencil_fill_tool_modal_map(params),
|
||||
km_grease_pencil_interpolate_tool_modal_map(params),
|
||||
|
||||
# Gizmos.
|
||||
km_generic_gizmo(params),
|
||||
@@ -9265,6 +9316,8 @@ def generate_keymaps(params=None):
|
||||
km_sequencer_editor_tool_move(params),
|
||||
km_sequencer_editor_tool_rotate(params),
|
||||
km_sequencer_editor_tool_scale(params),
|
||||
km_3d_view_tool_edit_grease_pencil_interpolate(params),
|
||||
km_3d_view_tool_paint_grease_pencil_interpolate(params),
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@@ -2174,6 +2174,48 @@ class _defs_grease_pencil_paint:
|
||||
draw_settings=draw_settings,
|
||||
)
|
||||
|
||||
@ToolDef.from_fn
|
||||
def interpolate():
|
||||
def draw_settings(_context, layout, tool):
|
||||
props = tool.operator_properties("grease_pencil.interpolate")
|
||||
layout.prop(props, "layers")
|
||||
layout.prop(props, "exclude_breakdowns")
|
||||
layout.prop(props, "flip")
|
||||
layout.prop(props, "smooth_factor")
|
||||
layout.prop(props, "smooth_steps")
|
||||
|
||||
return dict(
|
||||
idname="builtin.interpolate",
|
||||
label="Interpolate",
|
||||
icon="ops.pose.breakdowner",
|
||||
cursor='DEFAULT',
|
||||
widget=None,
|
||||
keymap=(),
|
||||
draw_settings=draw_settings,
|
||||
)
|
||||
|
||||
|
||||
class _defs_grease_pencil_edit:
|
||||
@ToolDef.from_fn
|
||||
def interpolate():
|
||||
def draw_settings(_context, layout, tool):
|
||||
props = tool.operator_properties("grease_pencil.interpolate")
|
||||
layout.prop(props, "layers")
|
||||
layout.prop(props, "exclude_breakdowns")
|
||||
layout.prop(props, "flip")
|
||||
layout.prop(props, "smooth_factor")
|
||||
layout.prop(props, "smooth_steps")
|
||||
|
||||
return dict(
|
||||
idname="builtin.interpolate",
|
||||
label="Interpolate",
|
||||
icon="ops.pose.breakdowner",
|
||||
cursor='DEFAULT',
|
||||
widget=None,
|
||||
keymap=(),
|
||||
draw_settings=draw_settings,
|
||||
)
|
||||
|
||||
|
||||
class _defs_image_generic:
|
||||
|
||||
@@ -3610,6 +3652,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
_defs_edit_mesh.tosphere,
|
||||
),
|
||||
None,
|
||||
_defs_grease_pencil_edit.interpolate,
|
||||
None,
|
||||
*_tools_annotate,
|
||||
],
|
||||
'PARTICLE': [
|
||||
@@ -3725,6 +3769,8 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
|
||||
_defs_grease_pencil_paint.curve,
|
||||
_defs_grease_pencil_paint.box,
|
||||
_defs_grease_pencil_paint.circle,
|
||||
None,
|
||||
_defs_grease_pencil_paint.interpolate,
|
||||
],
|
||||
'PAINT_GPENCIL': [
|
||||
_defs_view3d_generic.cursor,
|
||||
|
||||
@@ -403,12 +403,13 @@ class LayerRuntime {
|
||||
*/
|
||||
class Layer : public ::GreasePencilLayer {
|
||||
public:
|
||||
using SortedKeysIterator = const int *;
|
||||
|
||||
Layer();
|
||||
explicit Layer(StringRefNull name);
|
||||
Layer(const Layer &other);
|
||||
~Layer();
|
||||
|
||||
public:
|
||||
/* Define the common functions for #TreeNode. */
|
||||
TREENODE_COMMON_METHODS;
|
||||
/**
|
||||
@@ -486,6 +487,11 @@ class Layer : public ::GreasePencilLayer {
|
||||
* exists.
|
||||
*/
|
||||
int sorted_keys_index_at(int frame_number) const;
|
||||
/**
|
||||
* \returns an iterator into the `sorted_keys` span to the frame at \a frame_number or nullptr if
|
||||
* no such frame exists.
|
||||
*/
|
||||
SortedKeysIterator sorted_keys_iterator_at(int frame_number) const;
|
||||
|
||||
/**
|
||||
* \returns a pointer to the active frame at \a frame_number or nullptr if there is no frame.
|
||||
@@ -557,14 +563,6 @@ class Layer : public ::GreasePencilLayer {
|
||||
void set_view_layer_name(const char *new_name);
|
||||
|
||||
private:
|
||||
using SortedKeysIterator = const int *;
|
||||
|
||||
private:
|
||||
/**
|
||||
* \returns an iterator into the `sorted_keys` span to the frame at \a frame_number or nullptr if
|
||||
* no such frame exists.
|
||||
*/
|
||||
SortedKeysIterator sorted_keys_iterator_at(int frame_number) const;
|
||||
/**
|
||||
* \returns the key of the active frame at \a frame_number or #std::nullopt if no such frame
|
||||
* exists.
|
||||
|
||||
@@ -157,6 +157,20 @@ void sample_uniform(Span<float> accumulated_segment_lengths,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
MutableSpan<float> r_factors);
|
||||
|
||||
/**
|
||||
* Find evenly spaced samples along the lengths, starting at the end.
|
||||
*
|
||||
* \param accumulated_segment_lengths: The accumulated lengths of the original elements being
|
||||
* sampled. Could be calculated by #accumulate_lengths.
|
||||
* \param include_first_point: Generally false for cyclic sequences and true otherwise.
|
||||
* \param r_segment_indices: The index of the previous point at each sample.
|
||||
* \param r_factors: The portion of the length in each segment at each sample.
|
||||
*/
|
||||
void sample_uniform_reverse(Span<float> accumulated_segment_lengths,
|
||||
bool include_first_point,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
MutableSpan<float> r_factors);
|
||||
|
||||
/**
|
||||
* For each provided sample length, find the segment index and interpolation factor.
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "BLI_length_parameterize.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include <algorithm>
|
||||
|
||||
namespace blender::length_parameterize {
|
||||
|
||||
@@ -36,6 +37,35 @@ void sample_uniform(const Span<float> accumulated_segment_lengths,
|
||||
});
|
||||
}
|
||||
|
||||
void sample_uniform_reverse(const Span<float> accumulated_segment_lengths,
|
||||
const bool include_first_point,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
MutableSpan<float> r_factors)
|
||||
{
|
||||
const int count = r_segment_indices.size();
|
||||
BLI_assert(count > 0);
|
||||
BLI_assert(accumulated_segment_lengths.size() >= 1);
|
||||
BLI_assert(
|
||||
std::is_sorted(accumulated_segment_lengths.begin(), accumulated_segment_lengths.end()));
|
||||
|
||||
if (count == 1) {
|
||||
r_segment_indices[0] = accumulated_segment_lengths.size() - 1;
|
||||
r_factors[0] = 1.0f;
|
||||
return;
|
||||
}
|
||||
const float total_length = accumulated_segment_lengths.last();
|
||||
const float step_length = total_length / (count - include_first_point);
|
||||
threading::parallel_for(IndexRange(count), 512, [&](const IndexRange range) {
|
||||
SampleSegmentHint hint;
|
||||
for (const int i : range) {
|
||||
/* Use maximum to avoid issues with floating point accuracy. */
|
||||
const float sample_length = std::max(0.0f, total_length - i * step_length);
|
||||
sample_at_length(
|
||||
accumulated_segment_lengths, sample_length, r_segment_indices[i], r_factors[i], &hint);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void sample_at_lengths(const Span<float> accumulated_segment_lengths,
|
||||
const Span<float> sample_lengths,
|
||||
MutableSpan<int> r_segment_indices,
|
||||
|
||||
@@ -199,6 +199,7 @@ void ED_operatortypes_grease_pencil()
|
||||
ED_operatortypes_grease_pencil_material();
|
||||
ED_operatortypes_grease_pencil_primitives();
|
||||
ED_operatortypes_grease_pencil_weight_paint();
|
||||
ED_operatortypes_grease_pencil_interpolate();
|
||||
}
|
||||
|
||||
void ED_operatormacros_grease_pencil()
|
||||
@@ -247,4 +248,5 @@ void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
|
||||
keymap_grease_pencil_fill_tool(keyconf);
|
||||
ED_primitivetool_modal_keymap(keyconf);
|
||||
ED_filltool_modal_keymap(keyconf);
|
||||
ED_interpolatetool_modal_keymap(keyconf);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
#include "ED_grease_pencil.hh"
|
||||
#include "ED_undo.hh"
|
||||
|
||||
|
||||
@@ -58,10 +58,12 @@ void ED_operatortypes_grease_pencil_edit();
|
||||
void ED_operatortypes_grease_pencil_material();
|
||||
void ED_operatortypes_grease_pencil_primitives();
|
||||
void ED_operatortypes_grease_pencil_weight_paint();
|
||||
void ED_operatortypes_grease_pencil_interpolate();
|
||||
void ED_operatormacros_grease_pencil();
|
||||
void ED_keymap_grease_pencil(wmKeyConfig *keyconf);
|
||||
void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf);
|
||||
void ED_filltool_modal_keymap(wmKeyConfig *keyconf);
|
||||
void ED_interpolatetool_modal_keymap(wmKeyConfig *keyconf);
|
||||
|
||||
void GREASE_PENCIL_OT_stroke_cutter(wmOperatorType *ot);
|
||||
|
||||
@@ -606,6 +608,22 @@ void draw_grease_pencil_strokes(const RegionView3D &rv3d,
|
||||
|
||||
} // namespace image_render
|
||||
|
||||
enum class InterpolateFlipMode : int8_t {
|
||||
/* No flip. */
|
||||
None = 0,
|
||||
/* Flip always. */
|
||||
Flip,
|
||||
/* Flip if needed. */
|
||||
FlipAuto,
|
||||
};
|
||||
|
||||
enum class InterpolateLayerMode : int8_t {
|
||||
/* Only interpolate on the active layer. */
|
||||
Active = 0,
|
||||
/* Interpolate strokes on every layer. */
|
||||
All,
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new strokes tracing the rendered outline of existing strokes.
|
||||
* \param drawing: Drawing with input strokes.
|
||||
|
||||
@@ -50,6 +50,7 @@ set(SRC
|
||||
grease_pencil_draw_ops.cc
|
||||
grease_pencil_erase.cc
|
||||
grease_pencil_fill.cc
|
||||
grease_pencil_interpolate.cc
|
||||
grease_pencil_paint.cc
|
||||
grease_pencil_sculpt_clone.cc
|
||||
grease_pencil_sculpt_common.cc
|
||||
|
||||
828
source/blender/editors/sculpt_paint/grease_pencil_interpolate.cc
Normal file
828
source/blender/editors/sculpt_paint/grease_pencil_interpolate.cc
Normal file
@@ -0,0 +1,828 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_grease_pencil.hh"
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_math_angle_types.hh"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_rotation.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "DNA_grease_pencil_types.h"
|
||||
|
||||
#include "ED_grease_pencil.hh"
|
||||
#include "ED_numinput.hh"
|
||||
#include "ED_screen.hh"
|
||||
|
||||
#include "GEO_interpolate_curves.hh"
|
||||
#include "GEO_smooth_curves.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace blender::ed::sculpt_paint::greasepencil {
|
||||
|
||||
using ed::greasepencil::InterpolateFlipMode;
|
||||
using ed::greasepencil::InterpolateLayerMode;
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Interpolate Operator
|
||||
* \{ */
|
||||
|
||||
constexpr float interpolate_factor_min = -1.0f;
|
||||
constexpr float interpolate_factor_max = 2.0f;
|
||||
|
||||
/* Pair of curves in a layer that get interpolated. */
|
||||
struct InterpolationPairs {
|
||||
Vector<int> from_frames;
|
||||
Vector<int> to_frames;
|
||||
Vector<int> from_curves;
|
||||
Vector<int> to_curves;
|
||||
};
|
||||
|
||||
struct InterpolateOpData {
|
||||
struct LayerData {
|
||||
/* Curve pairs to interpolate from this layer. */
|
||||
InterpolationPairs curve_pairs;
|
||||
|
||||
/* Geometry of the target frame before interpolation for restoring on cancel. */
|
||||
std::optional<bke::CurvesGeometry> orig_curves;
|
||||
};
|
||||
|
||||
/* Layers to include. */
|
||||
IndexMaskMemory layer_mask_memory;
|
||||
IndexMask layer_mask;
|
||||
/* Exclude breakdown keyframes when finding intervals. */
|
||||
bool exclude_breakdowns;
|
||||
|
||||
/* Interpolation factor bias controlled by the user. */
|
||||
float shift;
|
||||
/* Interpolation base factor for the active layer. */
|
||||
float init_factor;
|
||||
InterpolateFlipMode flipmode;
|
||||
float smooth_factor;
|
||||
int smooth_steps;
|
||||
|
||||
NumInput numeric_input;
|
||||
Array<LayerData> layer_data;
|
||||
int active_layer_index;
|
||||
};
|
||||
|
||||
using FramesMapKeyIntervalT = std::pair<int, int>;
|
||||
|
||||
static std::optional<FramesMapKeyIntervalT> find_frames_interval(
|
||||
const bke::greasepencil::Layer &layer, const int frame_number, const bool exclude_breakdowns)
|
||||
{
|
||||
using Layer = bke::greasepencil::Layer;
|
||||
using bke::greasepencil::FramesMapKeyT;
|
||||
using SortedKeysIterator = Layer::SortedKeysIterator;
|
||||
|
||||
const Span<FramesMapKeyT> sorted_keys = layer.sorted_keys();
|
||||
SortedKeysIterator prev_key_it = layer.sorted_keys_iterator_at(frame_number);
|
||||
if (!prev_key_it) {
|
||||
return std::nullopt;
|
||||
}
|
||||
SortedKeysIterator next_key_it = std::next(prev_key_it);
|
||||
|
||||
/* Skip over invalid keyframes on either side. */
|
||||
auto is_valid_keyframe = [&](const FramesMapKeyT key) {
|
||||
const GreasePencilFrame *frame = layer.frame_at(key);
|
||||
if (!frame || frame->is_end()) {
|
||||
return false;
|
||||
}
|
||||
if (exclude_breakdowns && frame->type == BEZT_KEYTYPE_BREAKDOWN) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (; next_key_it != sorted_keys.end(); ++next_key_it) {
|
||||
if (is_valid_keyframe(*next_key_it)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; prev_key_it != sorted_keys.begin(); --prev_key_it) {
|
||||
if (is_valid_keyframe(*prev_key_it)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (next_key_it == sorted_keys.end() || !is_valid_keyframe(*prev_key_it)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::make_pair(*prev_key_it, *next_key_it);
|
||||
}
|
||||
|
||||
/* Build index lists for curve interpolation using index. */
|
||||
static void find_curve_mapping_from_index(const GreasePencil &grease_pencil,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const int current_frame,
|
||||
const bool exclude_breakdowns,
|
||||
InterpolationPairs &pairs)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const std::optional<FramesMapKeyIntervalT> interval = find_frames_interval(
|
||||
layer, current_frame, exclude_breakdowns);
|
||||
if (!interval) {
|
||||
return;
|
||||
}
|
||||
|
||||
BLI_assert(layer.has_drawing_at(interval->first));
|
||||
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);
|
||||
|
||||
const int pairs_num = std::min(from_drawing.strokes().curves_num(),
|
||||
to_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);
|
||||
array_utils::fill_index_range(
|
||||
pairs.from_curves.as_mutable_span().slice(old_pairs_num, pairs_num));
|
||||
array_utils::fill_index_range(pairs.to_curves.as_mutable_span().slice(old_pairs_num, pairs_num));
|
||||
}
|
||||
|
||||
static bool compute_auto_flip(const Span<float3> from_positions, const Span<float3> to_positions)
|
||||
{
|
||||
if (from_positions.size() < 2 || to_positions.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr float min_angle = DEG2RADF(15);
|
||||
|
||||
const float3 &from_first = from_positions.first();
|
||||
const float3 &from_last = from_positions.last();
|
||||
const float3 &to_first = to_positions.first();
|
||||
const float3 &to_last = to_positions.last();
|
||||
|
||||
/* If lines intersect at a sharp angle check distances. */
|
||||
if (isect_seg_seg_v2(from_first, to_first, from_last, to_last) == ISECT_LINE_LINE_CROSS) {
|
||||
if (math::angle_between(math::normalize(to_first - from_first),
|
||||
math::normalize(to_last - from_last))
|
||||
.radian() < min_angle)
|
||||
{
|
||||
if (math::distance_squared(from_first, to_first) >=
|
||||
math::distance_squared(from_last, to_first))
|
||||
{
|
||||
return math::distance_squared(from_last, to_first) >=
|
||||
math::distance_squared(from_last, to_last);
|
||||
}
|
||||
|
||||
return math::distance_squared(from_first, to_first) <
|
||||
math::distance_squared(from_first, to_last);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return math::dot(from_last - from_first, to_last - to_first) < 0.0f;
|
||||
}
|
||||
|
||||
static bke::CurvesGeometry interpolate_between_curves(const GreasePencil &grease_pencil,
|
||||
const bke::greasepencil::Layer &layer,
|
||||
const InterpolationPairs &curve_pairs,
|
||||
const float mix_factor,
|
||||
const InterpolateFlipMode flip_mode)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const int dst_curve_num = curve_pairs.from_curves.size();
|
||||
BLI_assert(curve_pairs.to_curves.size() == dst_curve_num);
|
||||
BLI_assert(curve_pairs.from_frames.size() == dst_curve_num);
|
||||
BLI_assert(curve_pairs.to_frames.size() == dst_curve_num);
|
||||
|
||||
/* Sort pairs by unique to/from frame combinations.
|
||||
* Curves for each frame pair are then interpolated together.
|
||||
* Map entries are indices into the original curve_pairs array,
|
||||
* so the order of strokes can be maintained. */
|
||||
Array<int> sorted_pairs(dst_curve_num);
|
||||
array_utils::fill_index_range(sorted_pairs.as_mutable_span());
|
||||
std::sort(sorted_pairs.begin(), sorted_pairs.end(), [&](const int a, const int b) {
|
||||
const int from_frame_a = curve_pairs.from_frames[a];
|
||||
const int to_frame_a = curve_pairs.to_frames[a];
|
||||
const int from_frame_b = curve_pairs.from_frames[b];
|
||||
const int to_frame_b = curve_pairs.to_frames[b];
|
||||
return from_frame_a < from_frame_b ||
|
||||
(from_frame_a == from_frame_b && to_frame_a < to_frame_b);
|
||||
});
|
||||
|
||||
/* 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);
|
||||
}();
|
||||
|
||||
/* Compute curve length and flip mode for each pair. */
|
||||
Vector<int> dst_curve_offsets;
|
||||
Vector<bool> dst_curve_flip;
|
||||
const OffsetIndices dst_points_by_curve = [&]() {
|
||||
for (const int pair_range_i : curves_by_pair.index_range()) {
|
||||
const IndexRange pair_range = curves_by_pair[pair_range_i];
|
||||
BLI_assert(!pair_range.is_empty());
|
||||
|
||||
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];
|
||||
const Drawing *from_drawing = grease_pencil.get_drawing_at(layer, from_frame);
|
||||
const Drawing *to_drawing = grease_pencil.get_drawing_at(layer, to_frame);
|
||||
if (!from_drawing || !to_drawing) {
|
||||
continue;
|
||||
}
|
||||
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 Span<float3> from_positions = from_drawing->strokes().positions();
|
||||
const Span<float3> to_positions = to_drawing->strokes().positions();
|
||||
|
||||
for (const int sorted_index : pair_range) {
|
||||
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.append(std::max(from_points.size(), to_points.size()));
|
||||
switch (flip_mode) {
|
||||
case InterpolateFlipMode::None:
|
||||
dst_curve_flip.append(false);
|
||||
break;
|
||||
case InterpolateFlipMode::Flip:
|
||||
dst_curve_flip.append(true);
|
||||
break;
|
||||
case InterpolateFlipMode::FlipAuto: {
|
||||
dst_curve_flip.append(compute_auto_flip(from_positions.slice(from_points),
|
||||
to_positions.slice(to_points)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Last entry for overall size. */
|
||||
if (dst_curve_offsets.is_empty()) {
|
||||
return OffsetIndices<int>{};
|
||||
}
|
||||
|
||||
dst_curve_offsets.append(0);
|
||||
return offset_indices::accumulate_counts_to_offsets(dst_curve_offsets);
|
||||
}();
|
||||
const int dst_point_num = dst_points_by_curve.total_size();
|
||||
|
||||
bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
|
||||
/* Offsets are empty when there are no curves. */
|
||||
if (dst_curve_num > 0) {
|
||||
dst_curves.offsets_for_write().copy_from(dst_curve_offsets);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
for (const int pair_range_i : curves_by_pair.index_range()) {
|
||||
const IndexRange pair_range = curves_by_pair[pair_range_i];
|
||||
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];
|
||||
const Drawing *from_drawing = grease_pencil.get_drawing_at(layer, from_frame);
|
||||
const Drawing *to_drawing = grease_pencil.get_drawing_at(layer, to_frame);
|
||||
if (!from_drawing || !to_drawing) {
|
||||
continue;
|
||||
}
|
||||
const IndexRange from_curves = from_drawing->strokes().curves_range();
|
||||
const IndexRange to_curves = to_drawing->strokes().curves_range();
|
||||
|
||||
/* Subset of target curves that are filled by this frame pair. */
|
||||
IndexMaskMemory selection_memory;
|
||||
const IndexMask selection = 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()));
|
||||
}
|
||||
geometry::interpolate_curves(from_drawing->strokes(),
|
||||
to_drawing->strokes(),
|
||||
pair_from_indices,
|
||||
pair_to_indices,
|
||||
selection,
|
||||
dst_curve_flip,
|
||||
mix_factor,
|
||||
dst_curves);
|
||||
}
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
static void grease_pencil_interpolate_status_indicators(bContext &C,
|
||||
const InterpolateOpData &opdata)
|
||||
{
|
||||
Scene &scene = *CTX_data_scene(&C);
|
||||
ScrArea &area = *CTX_wm_area(&C);
|
||||
|
||||
const StringRef msg = IFACE_("GPencil Interpolation: ");
|
||||
|
||||
std::string status;
|
||||
if (hasNumInput(&opdata.numeric_input)) {
|
||||
char str_ofs[NUM_STR_REP_LEN];
|
||||
outputNumInput(&const_cast<NumInput &>(opdata.numeric_input), str_ofs, &scene.unit);
|
||||
status = msg + std::string(str_ofs);
|
||||
}
|
||||
else {
|
||||
status = msg + std::to_string(int((opdata.init_factor + opdata.shift) * 100.0f)) + " %";
|
||||
}
|
||||
|
||||
ED_area_status_text(&area, status.c_str());
|
||||
ED_workspace_status_text(
|
||||
&C, IFACE_("ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/MOVE to adjust factor"));
|
||||
}
|
||||
|
||||
/* Utility function to get a drawing at the exact frame number. */
|
||||
static bke::greasepencil::Drawing *get_drawing_at_exact_frame(GreasePencil &grease_pencil,
|
||||
bke::greasepencil::Layer &layer,
|
||||
const int frame_number)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
const std::optional<int> start_frame = layer.start_frame_at(frame_number);
|
||||
if (start_frame && *start_frame == frame_number) {
|
||||
return grease_pencil.get_editable_drawing_at(layer, frame_number);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bke::greasepencil::Drawing *ensure_drawing_at_exact_frame(
|
||||
GreasePencil &grease_pencil,
|
||||
bke::greasepencil::Layer &layer,
|
||||
InterpolateOpData::LayerData &layer_data,
|
||||
const int frame_number)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
|
||||
if (Drawing *drawing = get_drawing_at_exact_frame(grease_pencil, layer, frame_number)) {
|
||||
layer_data.orig_curves = drawing->strokes();
|
||||
return drawing;
|
||||
}
|
||||
return grease_pencil.insert_frame(layer, frame_number);
|
||||
}
|
||||
|
||||
static void grease_pencil_interpolate_update(bContext &C, const wmOperator &op)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
const auto &opdata = *static_cast<InterpolateOpData *>(op.customdata);
|
||||
const Scene &scene = *CTX_data_scene(&C);
|
||||
const int current_frame = scene.r.cfra;
|
||||
Object &object = *CTX_data_active_object(&C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
const auto flip_mode = InterpolateFlipMode(RNA_enum_get(op.ptr, "flip"));
|
||||
|
||||
opdata.layer_mask.foreach_index([&](const int layer_index) {
|
||||
Layer &layer = *grease_pencil.layer(layer_index);
|
||||
const InterpolateOpData::LayerData &layer_data = opdata.layer_data[layer_index];
|
||||
|
||||
/* Drawings must be created on operator invoke. */
|
||||
Drawing *dst_drawing = get_drawing_at_exact_frame(grease_pencil, layer, current_frame);
|
||||
if (dst_drawing == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float mix_factor = opdata.init_factor + opdata.shift;
|
||||
bke::CurvesGeometry interpolated_curves = interpolate_between_curves(
|
||||
grease_pencil, layer, layer_data.curve_pairs, mix_factor, flip_mode);
|
||||
|
||||
if (opdata.smooth_factor > 0.0f && opdata.smooth_steps > 0) {
|
||||
MutableSpan<float3> positions = interpolated_curves.positions_for_write();
|
||||
geometry::smooth_curve_attribute(
|
||||
interpolated_curves.curves_range(),
|
||||
interpolated_curves.points_by_curve(),
|
||||
VArray<bool>::ForSingle(true, interpolated_curves.points_num()),
|
||||
interpolated_curves.cyclic(),
|
||||
opdata.smooth_steps,
|
||||
opdata.smooth_factor,
|
||||
false,
|
||||
false,
|
||||
positions);
|
||||
interpolated_curves.tag_positions_changed();
|
||||
}
|
||||
|
||||
dst_drawing->strokes_for_write() = std::move(interpolated_curves);
|
||||
dst_drawing->tag_topology_changed();
|
||||
});
|
||||
|
||||
grease_pencil_interpolate_status_indicators(C, opdata);
|
||||
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(&C, NC_GPENCIL | NA_EDITED, nullptr);
|
||||
}
|
||||
|
||||
/* Restore timeline changes when cancelled. */
|
||||
static void grease_pencil_interpolate_restore(bContext &C, wmOperator &op)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
if (op.customdata == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &opdata = *static_cast<InterpolateOpData *>(op.customdata);
|
||||
const Scene &scene = *CTX_data_scene(&C);
|
||||
const int current_frame = scene.r.cfra;
|
||||
Object &object = *CTX_data_active_object(&C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
|
||||
opdata.layer_mask.foreach_index([&](const int layer_index) {
|
||||
Layer &layer = *grease_pencil.layer(layer_index);
|
||||
const InterpolateOpData::LayerData &layer_data = opdata.layer_data[layer_index];
|
||||
|
||||
if (layer_data.orig_curves) {
|
||||
/* Keyframe existed before the operator, restore geometry. */
|
||||
Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, current_frame);
|
||||
if (drawing) {
|
||||
drawing->strokes_for_write() = *layer_data.orig_curves;
|
||||
drawing->tag_topology_changed();
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(&C, NC_GPENCIL | NA_EDITED, nullptr);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Frame was empty, remove the added drawing. */
|
||||
grease_pencil.remove_frames(layer, {current_frame});
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(&C, NC_GPENCIL | NA_EDITED, nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static bool grease_pencil_interpolate_init(const bContext &C, wmOperator &op)
|
||||
{
|
||||
using bke::greasepencil::Drawing;
|
||||
using bke::greasepencil::Layer;
|
||||
|
||||
const Scene &scene = *CTX_data_scene(&C);
|
||||
const int current_frame = scene.r.cfra;
|
||||
Object &object = *CTX_data_active_object(&C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object.data);
|
||||
|
||||
BLI_assert(grease_pencil.has_active_layer());
|
||||
const Layer &active_layer = *grease_pencil.get_active_layer();
|
||||
|
||||
op.customdata = MEM_new<InterpolateOpData>(__func__);
|
||||
InterpolateOpData &data = *static_cast<InterpolateOpData *>(op.customdata);
|
||||
|
||||
data.shift = RNA_float_get(op.ptr, "shift");
|
||||
data.exclude_breakdowns = RNA_boolean_get(op.ptr, "exclude_breakdowns");
|
||||
data.flipmode = InterpolateFlipMode(RNA_enum_get(op.ptr, "flip"));
|
||||
data.smooth_factor = RNA_float_get(op.ptr, "smooth_factor");
|
||||
data.smooth_steps = RNA_int_get(op.ptr, "smooth_steps");
|
||||
data.active_layer_index = *grease_pencil.get_layer_index(active_layer);
|
||||
|
||||
const auto layer_mode = InterpolateLayerMode(RNA_enum_get(op.ptr, "layers"));
|
||||
switch (layer_mode) {
|
||||
case InterpolateLayerMode::Active:
|
||||
data.layer_mask = IndexRange::from_single(data.active_layer_index);
|
||||
break;
|
||||
case InterpolateLayerMode::All:
|
||||
data.layer_mask = IndexMask::from_predicate(
|
||||
grease_pencil.layers().index_range(),
|
||||
GrainSize(1024),
|
||||
data.layer_mask_memory,
|
||||
[&](const int layer_index) { return grease_pencil.layer(layer_index)->is_editable(); });
|
||||
break;
|
||||
}
|
||||
|
||||
data.layer_data.reinitialize(grease_pencil.layers().size());
|
||||
data.layer_mask.foreach_index([&](const int layer_index) {
|
||||
Layer &layer = *grease_pencil.layer(layer_index);
|
||||
InterpolateOpData::LayerData &layer_data = data.layer_data[layer_index];
|
||||
|
||||
/* Pair from/to curves by index. */
|
||||
find_curve_mapping_from_index(
|
||||
grease_pencil, layer, current_frame, data.exclude_breakdowns, layer_data.curve_pairs);
|
||||
|
||||
ensure_drawing_at_exact_frame(grease_pencil, layer, layer_data, current_frame);
|
||||
});
|
||||
|
||||
const std::optional<FramesMapKeyIntervalT> active_layer_interval = find_frames_interval(
|
||||
active_layer, current_frame, data.exclude_breakdowns);
|
||||
data.init_factor = active_layer_interval ?
|
||||
float(current_frame - active_layer_interval->first) /
|
||||
(active_layer_interval->second - active_layer_interval->first + 1) :
|
||||
0.5f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Exit and free memory. */
|
||||
static void grease_pencil_interpolate_exit(bContext &C, wmOperator &op)
|
||||
{
|
||||
ScrArea &area = *CTX_wm_area(&C);
|
||||
|
||||
if (op.customdata == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ED_area_status_text(&area, nullptr);
|
||||
ED_workspace_status_text(&C, nullptr);
|
||||
|
||||
MEM_delete(static_cast<InterpolateOpData *>(op.customdata));
|
||||
op.customdata = nullptr;
|
||||
}
|
||||
|
||||
static bool grease_pencil_interpolate_poll(bContext *C)
|
||||
{
|
||||
if (!ed::greasepencil::active_grease_pencil_poll(C)) {
|
||||
return false;
|
||||
}
|
||||
ToolSettings *ts = CTX_data_tool_settings(C);
|
||||
if (!ts || !ts->gp_paint) {
|
||||
return false;
|
||||
}
|
||||
/* Only 3D view */
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
if (area && area->spacetype != SPACE_VIEW3D) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Invoke handler: Initialize the operator */
|
||||
static int grease_pencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
|
||||
{
|
||||
wmWindow &win = *CTX_wm_window(C);
|
||||
|
||||
if (!grease_pencil_interpolate_init(*C, *op)) {
|
||||
grease_pencil_interpolate_exit(*C, *op);
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
InterpolateOpData &opdata = *static_cast<InterpolateOpData *>(op->customdata);
|
||||
|
||||
/* Set cursor to indicate modal operator. */
|
||||
WM_cursor_modal_set(&win, WM_CURSOR_EW_SCROLL);
|
||||
|
||||
grease_pencil_interpolate_status_indicators(*C, opdata);
|
||||
|
||||
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, nullptr);
|
||||
|
||||
WM_event_add_modal_handler(C, op);
|
||||
|
||||
return OPERATOR_RUNNING_MODAL;
|
||||
}
|
||||
|
||||
enum class InterpolateToolModalEvent : int8_t {
|
||||
Cancel = 1,
|
||||
Confirm,
|
||||
Increase,
|
||||
Decrease,
|
||||
};
|
||||
|
||||
/* Modal handler: Events handling during interactive part */
|
||||
static int grease_pencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
wmWindow &win = *CTX_wm_window(C);
|
||||
const ARegion ®ion = *CTX_wm_region(C);
|
||||
ScrArea &area = *CTX_wm_area(C);
|
||||
InterpolateOpData &opdata = *static_cast<InterpolateOpData *>(op->customdata);
|
||||
const bool has_numinput = hasNumInput(&opdata.numeric_input);
|
||||
|
||||
switch (event->type) {
|
||||
case EVT_MODAL_MAP: {
|
||||
switch (InterpolateToolModalEvent(event->val)) {
|
||||
case InterpolateToolModalEvent::Cancel:
|
||||
ED_area_status_text(&area, nullptr);
|
||||
ED_workspace_status_text(C, nullptr);
|
||||
WM_cursor_modal_restore(&win);
|
||||
|
||||
grease_pencil_interpolate_restore(*C, *op);
|
||||
grease_pencil_interpolate_exit(*C, *op);
|
||||
return OPERATOR_CANCELLED;
|
||||
case InterpolateToolModalEvent::Confirm:
|
||||
ED_area_status_text(&area, nullptr);
|
||||
ED_workspace_status_text(C, nullptr);
|
||||
WM_cursor_modal_restore(&win);
|
||||
|
||||
/* Write current factor to properties for the next execution. */
|
||||
RNA_float_set(op->ptr, "shift", opdata.shift);
|
||||
|
||||
grease_pencil_interpolate_exit(*C, *op);
|
||||
return OPERATOR_FINISHED;
|
||||
case InterpolateToolModalEvent::Increase:
|
||||
opdata.shift = std::clamp(opdata.init_factor + opdata.shift + 0.01f,
|
||||
interpolate_factor_min,
|
||||
interpolate_factor_max) -
|
||||
opdata.init_factor;
|
||||
grease_pencil_interpolate_update(*C, *op);
|
||||
break;
|
||||
case InterpolateToolModalEvent::Decrease:
|
||||
opdata.shift = std::clamp(opdata.init_factor + opdata.shift - 0.01f,
|
||||
interpolate_factor_min,
|
||||
interpolate_factor_max) -
|
||||
opdata.init_factor;
|
||||
grease_pencil_interpolate_update(*C, *op);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MOUSEMOVE:
|
||||
/* Only handle mouse-move if not doing numeric-input. */
|
||||
if (!has_numinput) {
|
||||
const float mouse_pos = event->mval[0];
|
||||
const float factor = std::clamp(
|
||||
mouse_pos / region.winx, interpolate_factor_min, interpolate_factor_max);
|
||||
opdata.shift = factor - opdata.init_factor;
|
||||
|
||||
grease_pencil_interpolate_update(*C, *op);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
if ((event->val == KM_PRESS) && handleNumInput(C, &opdata.numeric_input, event)) {
|
||||
float value = (opdata.init_factor + opdata.shift) * 100.0f;
|
||||
applyNumInput(&opdata.numeric_input, &value);
|
||||
opdata.shift = std::clamp(value * 0.01f, interpolate_factor_min, interpolate_factor_max) -
|
||||
opdata.init_factor;
|
||||
|
||||
grease_pencil_interpolate_update(*C, *op);
|
||||
break;
|
||||
}
|
||||
/* Unhandled event, allow to pass through. */
|
||||
return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
|
||||
}
|
||||
}
|
||||
|
||||
return OPERATOR_RUNNING_MODAL;
|
||||
}
|
||||
|
||||
static void grease_pencil_interpolate_cancel(bContext *C, wmOperator *op)
|
||||
{
|
||||
grease_pencil_interpolate_restore(*C, *op);
|
||||
grease_pencil_interpolate_exit(*C, *op);
|
||||
}
|
||||
|
||||
static void GREASE_PENCIL_OT_interpolate(wmOperatorType *ot)
|
||||
{
|
||||
static const EnumPropertyItem flip_modes[] = {
|
||||
{int(InterpolateFlipMode::None), "NONE", 0, "No Flip", ""},
|
||||
{int(InterpolateFlipMode::Flip), "FLIP", 0, "Flip", ""},
|
||||
{int(InterpolateFlipMode::FlipAuto), "AUTO", 0, "Automatic", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem gpencil_interpolation_layer_items[] = {
|
||||
{int(InterpolateLayerMode::Active), "ACTIVE", 0, "Active", ""},
|
||||
{int(InterpolateLayerMode::All), "ALL", 0, "All Layers", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
ot->name = "Grease Pencil Interpolation";
|
||||
ot->idname = "GREASE_PENCIL_OT_interpolate";
|
||||
ot->description = "Interpolate grease pencil strokes between frames";
|
||||
|
||||
ot->invoke = grease_pencil_interpolate_invoke;
|
||||
ot->modal = grease_pencil_interpolate_modal;
|
||||
ot->cancel = grease_pencil_interpolate_cancel;
|
||||
ot->poll = grease_pencil_interpolate_poll;
|
||||
|
||||
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
|
||||
|
||||
RNA_def_float_factor(
|
||||
ot->srna,
|
||||
"shift",
|
||||
0.0f,
|
||||
-1.0f,
|
||||
1.0f,
|
||||
"Shift",
|
||||
"Bias factor for which frame has more influence on the interpolated strokes",
|
||||
-0.9f,
|
||||
0.9f);
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"layers",
|
||||
gpencil_interpolation_layer_items,
|
||||
0,
|
||||
"Layer",
|
||||
"Layers included in the interpolation");
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"exclude_breakdowns",
|
||||
false,
|
||||
"Exclude Breakdowns",
|
||||
"Exclude existing Breakdowns keyframes as interpolation extremes");
|
||||
|
||||
RNA_def_enum(ot->srna,
|
||||
"flip",
|
||||
flip_modes,
|
||||
int(InterpolateFlipMode::FlipAuto),
|
||||
"Flip Mode",
|
||||
"Invert destination stroke to match start and end with source stroke");
|
||||
|
||||
RNA_def_int(ot->srna,
|
||||
"smooth_steps",
|
||||
1,
|
||||
1,
|
||||
3,
|
||||
"Iterations",
|
||||
"Number of times to smooth newly created strokes",
|
||||
1,
|
||||
3);
|
||||
|
||||
RNA_def_float(ot->srna,
|
||||
"smooth_factor",
|
||||
0.0f,
|
||||
0.0f,
|
||||
2.0f,
|
||||
"Smooth",
|
||||
"Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise",
|
||||
0.0f,
|
||||
2.0f);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::sculpt_paint::greasepencil
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Registration
|
||||
* \{ */
|
||||
|
||||
void ED_operatortypes_grease_pencil_interpolate()
|
||||
{
|
||||
using namespace blender::ed::sculpt_paint::greasepencil;
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_interpolate);
|
||||
}
|
||||
|
||||
void ED_interpolatetool_modal_keymap(wmKeyConfig *keyconf)
|
||||
{
|
||||
using namespace blender::ed::sculpt_paint::greasepencil;
|
||||
static const EnumPropertyItem modal_items[] = {
|
||||
{int(InterpolateToolModalEvent::Cancel), "CANCEL", 0, "Cancel", ""},
|
||||
{int(InterpolateToolModalEvent::Confirm), "CONFIRM", 0, "Confirm", ""},
|
||||
{int(InterpolateToolModalEvent::Increase), "INCREASE", 0, "Increase", ""},
|
||||
{int(InterpolateToolModalEvent::Decrease), "DECREASE", 0, "Decrease", ""},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
wmKeyMap *keymap = WM_modalkeymap_find(keyconf, "Interpolate Tool Modal Map");
|
||||
|
||||
/* This function is called for each space-type, only needs to add map once. */
|
||||
if (keymap && keymap->modal_items) {
|
||||
return;
|
||||
}
|
||||
|
||||
keymap = WM_modalkeymap_ensure(keyconf, "Interpolate Tool Modal Map", modal_items);
|
||||
|
||||
WM_modalkeymap_assign(keymap, "GREASE_PENCIL_OT_interpolate");
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -20,6 +20,7 @@ set(SRC
|
||||
intern/curve_constraints.cc
|
||||
intern/extend_curves.cc
|
||||
intern/fillet_curves.cc
|
||||
intern/interpolate_curves.cc
|
||||
intern/join_geometries.cc
|
||||
intern/merge_curves.cc
|
||||
intern/mesh_boolean.cc
|
||||
@@ -57,6 +58,7 @@ set(SRC
|
||||
GEO_curve_constraints.hh
|
||||
GEO_extend_curves.hh
|
||||
GEO_fillet_curves.hh
|
||||
GEO_interpolate_curves.hh
|
||||
GEO_join_geometries.hh
|
||||
GEO_merge_curves.hh
|
||||
GEO_mesh_boolean.hh
|
||||
|
||||
27
source/blender/geometry/GEO_interpolate_curves.hh
Normal file
27
source/blender/geometry/GEO_interpolate_curves.hh
Normal file
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FN_field.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
/**
|
||||
* Create new curves that are interpolated between "from" and "to" curves.
|
||||
* \param selection: Selection of curves in \a dst_curves that are being filled.
|
||||
*/
|
||||
void interpolate_curves(const bke::CurvesGeometry &from_curves,
|
||||
const bke::CurvesGeometry &to_curves,
|
||||
Span<int> from_curve_indices,
|
||||
Span<int> to_curve_indices,
|
||||
const IndexMask &selection,
|
||||
Span<bool> curve_flip_direction,
|
||||
const float mix_factor,
|
||||
bke::CurvesGeometry &dst_curves);
|
||||
|
||||
} // namespace blender::geometry
|
||||
397
source/blender/geometry/intern/interpolate_curves.cc
Normal file
397
source/blender/geometry/intern/interpolate_curves.cc
Normal file
@@ -0,0 +1,397 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BKE_attribute_math.hh"
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_length_parameterize.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
|
||||
#include "GEO_interpolate_curves.hh"
|
||||
#include "GEO_resample_curves.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
using bke::CurvesGeometry;
|
||||
|
||||
/**
|
||||
* Return true if the attribute should be copied/interpolated to the result curves.
|
||||
* Don't output attributes that correspond to curve types that have no curves in the result.
|
||||
*/
|
||||
static bool interpolate_attribute_to_curves(const bke::AttributeIDRef &attribute_id,
|
||||
const std::array<int, CURVE_TYPES_NUM> &type_counts)
|
||||
{
|
||||
if (attribute_id.is_anonymous()) {
|
||||
return true;
|
||||
}
|
||||
if (ELEM(attribute_id.name(),
|
||||
"handle_type_left",
|
||||
"handle_type_right",
|
||||
"handle_left",
|
||||
"handle_right"))
|
||||
{
|
||||
return type_counts[CURVE_TYPE_BEZIER] != 0;
|
||||
}
|
||||
if (ELEM(attribute_id.name(), "nurbs_weight")) {
|
||||
return type_counts[CURVE_TYPE_NURBS] != 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the attribute should be copied to poly curves.
|
||||
*/
|
||||
static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attribute_id)
|
||||
{
|
||||
static const Set<StringRef> no_interpolation{{
|
||||
"handle_type_left",
|
||||
"handle_type_right",
|
||||
"handle_right",
|
||||
"handle_left",
|
||||
"nurbs_weight",
|
||||
}};
|
||||
return !no_interpolation.contains(attribute_id.name());
|
||||
}
|
||||
|
||||
struct AttributesForInterpolation {
|
||||
Vector<GVArraySpan> src_from;
|
||||
Vector<GVArraySpan> src_to;
|
||||
|
||||
Vector<bke::GSpanAttributeWriter> dst;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve spans from source and result attributes.
|
||||
*/
|
||||
static AttributesForInterpolation retrieve_attribute_spans(const Span<bke::AttributeIDRef> ids,
|
||||
const CurvesGeometry &src_from_curves,
|
||||
const CurvesGeometry &src_to_curves,
|
||||
CurvesGeometry &dst_curves)
|
||||
{
|
||||
AttributesForInterpolation result;
|
||||
|
||||
const bke::AttributeAccessor src_from_attributes = src_from_curves.attributes();
|
||||
const bke::AttributeAccessor src_to_attributes = src_to_curves.attributes();
|
||||
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
for (const int i : ids.index_range()) {
|
||||
eCustomDataType data_type;
|
||||
|
||||
const GVArray src_from_attribute = *src_from_attributes.lookup(ids[i], bke::AttrDomain::Point);
|
||||
if (src_from_attribute) {
|
||||
data_type = bke::cpp_type_to_custom_data_type(src_from_attribute.type());
|
||||
|
||||
const GVArray src_to_attribute = *src_to_attributes.lookup(
|
||||
ids[i], bke::AttrDomain::Point, data_type);
|
||||
|
||||
result.src_from.append(src_from_attribute);
|
||||
result.src_to.append(src_to_attribute ? src_to_attribute : GVArraySpan{});
|
||||
}
|
||||
else {
|
||||
const GVArray src_to_attribute = *src_to_attributes.lookup(ids[i], bke::AttrDomain::Point);
|
||||
/* Attribute should exist on at least one of the geometries. */
|
||||
BLI_assert(src_to_attribute);
|
||||
|
||||
data_type = bke::cpp_type_to_custom_data_type(src_to_attribute.type());
|
||||
|
||||
result.src_from.append(GVArraySpan{});
|
||||
result.src_to.append(src_to_attribute);
|
||||
}
|
||||
|
||||
bke::GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
ids[i], bke::AttrDomain::Point, data_type);
|
||||
result.dst.append(std::move(dst_attribute));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather a set of all generic attribute IDs to copy to the result curves.
|
||||
*/
|
||||
static AttributesForInterpolation gather_point_attributes_to_interpolate(
|
||||
const CurvesGeometry &from_curves, const CurvesGeometry &to_curves, CurvesGeometry &dst_curves)
|
||||
{
|
||||
VectorSet<bke::AttributeIDRef> ids;
|
||||
auto add_attribute = [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) {
|
||||
if (meta_data.domain != bke::AttrDomain::Point) {
|
||||
return true;
|
||||
}
|
||||
if (meta_data.data_type == CD_PROP_STRING) {
|
||||
return true;
|
||||
}
|
||||
if (!interpolate_attribute_to_curves(id, dst_curves.curve_type_counts())) {
|
||||
return true;
|
||||
}
|
||||
if (interpolate_attribute_to_poly_curve(id)) {
|
||||
ids.add(id);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
from_curves.attributes().for_all(add_attribute);
|
||||
to_curves.attributes().for_all(add_attribute);
|
||||
|
||||
/* Position is handled differently since it has non-generic interpolation for Bezier
|
||||
* curves and because the evaluated positions are cached for each evaluated point. */
|
||||
ids.remove_contained("position");
|
||||
|
||||
return retrieve_attribute_spans(ids, from_curves, to_curves, dst_curves);
|
||||
}
|
||||
|
||||
/* Resample a span of attribute values from source curves to a destination buffer. */
|
||||
static void sample_curve_attribute(const bke::CurvesGeometry &src_curves,
|
||||
const OffsetIndices<int> dst_points_by_curve,
|
||||
const GSpan src_data,
|
||||
const IndexMask &curve_selection,
|
||||
const Span<int> sample_indices,
|
||||
const Span<float> sample_factors,
|
||||
GMutableSpan dst_data)
|
||||
{
|
||||
const CPPType &type = src_data.type();
|
||||
BLI_assert(dst_data.type() == type);
|
||||
|
||||
const OffsetIndices<int> src_points_by_curve = src_curves.points_by_curve();
|
||||
const OffsetIndices<int> src_evaluated_points_by_curve = src_curves.evaluated_points_by_curve();
|
||||
const VArray<int8_t> curve_types = src_curves.curve_types();
|
||||
|
||||
#ifndef NDEBUG
|
||||
const int dst_points_num = dst_data.size();
|
||||
BLI_assert(sample_indices.size() == dst_points_num);
|
||||
BLI_assert(sample_factors.size() == dst_points_num);
|
||||
#endif
|
||||
|
||||
bke::attribute_math::convert_to_static_type(type, [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
Span<T> src = src_data.typed<T>();
|
||||
MutableSpan<T> dst = dst_data.typed<T>();
|
||||
|
||||
Vector<T> evaluated_data;
|
||||
curve_selection.foreach_index([&](const int i_curve) {
|
||||
const IndexRange src_points = src_points_by_curve[i_curve];
|
||||
const IndexRange dst_points = dst_points_by_curve[i_curve];
|
||||
|
||||
if (curve_types[i_curve] == CURVE_TYPE_POLY) {
|
||||
length_parameterize::interpolate(src.slice(src_points),
|
||||
sample_indices.slice(dst_points),
|
||||
sample_factors.slice(dst_points),
|
||||
dst.slice(dst_points));
|
||||
}
|
||||
else {
|
||||
const IndexRange src_evaluated_points = src_evaluated_points_by_curve[i_curve];
|
||||
evaluated_data.reinitialize(src_evaluated_points.size());
|
||||
src_curves.interpolate_to_evaluated(
|
||||
i_curve, src.slice(src_points), evaluated_data.as_mutable_span());
|
||||
length_parameterize::interpolate(evaluated_data.as_span(),
|
||||
sample_indices.slice(dst_points),
|
||||
sample_factors.slice(dst_points),
|
||||
dst.slice(dst_points));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void mix_arrays(const Span<T> from,
|
||||
const Span<T> to,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static void mix_arrays(const GSpan src_from,
|
||||
const GSpan src_to,
|
||||
const float mix_factor,
|
||||
const IndexMask &group_selection,
|
||||
const OffsetIndices<int> groups,
|
||||
const GMutableSpan dst)
|
||||
{
|
||||
group_selection.foreach_index(GrainSize(32), [&](const int curve) {
|
||||
const IndexRange range = groups[curve];
|
||||
bke::attribute_math::convert_to_static_type(dst.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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 &selection,
|
||||
const Span<bool> curve_flip_direction,
|
||||
const float mix_factor,
|
||||
CurvesGeometry &dst_curves)
|
||||
{
|
||||
BLI_assert(from_curve_indices.size() == selection.size());
|
||||
BLI_assert(to_curve_indices.size() == selection.size());
|
||||
|
||||
if (from_curves.curves_num() == 0 || to_curves.curves_num() == 0) {
|
||||
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();
|
||||
|
||||
/* All resampled curves are poly curves. */
|
||||
dst_curves.fill_curve_types(selection, CURVE_TYPE_POLY);
|
||||
|
||||
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
|
||||
|
||||
AttributesForInterpolation attributes = gather_point_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. */
|
||||
selection.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];
|
||||
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);
|
||||
}
|
||||
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 (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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* For every attribute, evaluate attributes from every curve in the range in the original
|
||||
* curve's "evaluated points", then use linear interpolation to sample to the result. */
|
||||
for (const int i_attribute : attributes.dst.index_range()) {
|
||||
const GSpan src_from = attributes.src_from[i_attribute];
|
||||
const GSpan src_to = attributes.src_to[i_attribute];
|
||||
GMutableSpan dst = attributes.dst[i_attribute].span;
|
||||
|
||||
/* Mix factors depend on which of the from/to curves geometries has attribute data. If
|
||||
* only one geometry has attribute data it gets the full mix weight. */
|
||||
if (!src_from.is_empty() && !src_to.is_empty()) {
|
||||
GArray<> from_samples(dst.type(), dst.size());
|
||||
GArray<> to_samples(dst.type(), dst.size());
|
||||
sample_curve_attribute(from_curves,
|
||||
dst_points_by_curve,
|
||||
src_from,
|
||||
selection,
|
||||
from_sample_indices,
|
||||
from_sample_factors,
|
||||
from_samples);
|
||||
sample_curve_attribute(to_curves,
|
||||
dst_points_by_curve,
|
||||
src_to,
|
||||
selection,
|
||||
to_sample_indices,
|
||||
to_sample_factors,
|
||||
to_samples);
|
||||
mix_arrays(from_samples, to_samples, mix_factor, selection, dst_points_by_curve, dst);
|
||||
}
|
||||
else if (!src_from.is_empty()) {
|
||||
sample_curve_attribute(from_curves,
|
||||
dst_points_by_curve,
|
||||
src_from,
|
||||
selection,
|
||||
from_sample_indices,
|
||||
from_sample_factors,
|
||||
dst);
|
||||
}
|
||||
else if (!src_to.is_empty()) {
|
||||
sample_curve_attribute(to_curves,
|
||||
dst_points_by_curve,
|
||||
src_to,
|
||||
selection,
|
||||
to_sample_indices,
|
||||
to_sample_factors,
|
||||
dst);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Array<float3> from_samples(dst_positions.size());
|
||||
Array<float3> to_samples(dst_positions.size());
|
||||
|
||||
/* Interpolate the evaluated positions to the resampled curves. */
|
||||
sample_curve_attribute(from_curves,
|
||||
dst_points_by_curve,
|
||||
from_evaluated_positions,
|
||||
selection,
|
||||
from_sample_indices,
|
||||
from_sample_factors,
|
||||
from_samples.as_mutable_span());
|
||||
sample_curve_attribute(to_curves,
|
||||
dst_points_by_curve,
|
||||
to_evaluated_positions,
|
||||
selection,
|
||||
to_sample_indices,
|
||||
to_sample_factors,
|
||||
to_samples.as_mutable_span());
|
||||
|
||||
mix_arrays(from_samples.as_span(),
|
||||
to_samples.as_span(),
|
||||
mix_factor,
|
||||
selection,
|
||||
dst_points_by_curve,
|
||||
dst_positions);
|
||||
}
|
||||
|
||||
for (bke::GSpanAttributeWriter &attribute : attributes.dst) {
|
||||
attribute.finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
Reference in New Issue
Block a user