Grease Pencil: Add Convert Curve Type operator
Adds a new operator in Grease Pencil edit mode to convert between curve
types. This acts as a replacment for the `Set Curve Type` operator as
the new operator better aligns with previous workflows and artist
expectations. Specifically using a threshold to adjust how well the
resulting curves fit to the original.
It can be found in the `Stroke` > `Convert Type` menu.
This operator aims at keeping visual fidelity between the curves. When
converting to a non-poly curve type, there's a `threshold` parameter
that dictates how closley the shapes will match (a value of zero meaning
an almost perfect match, and higher values will result in less accuracy
but lower control point count).
The conversion to `Catmull-Rom` does not do an actual curve fitting.
For now, this will resample the curves and then do an adaptive
simplification of the line (using the threshold parameter)
to simulate a curve fitting.
The `Set Curve Type` operator is no longer exposed in the
`Stroke` menu.
This also adds a new `geometry::fit_curves` function.
The function will fit a selection of curves to bézier curves. The
selected curves are treated as if they were poly curves.
The `thresholds` virtual array is the error threshold distance
for each curve that the fit should be within. The size of the virtual
array is assumed to have the same size as the total number of
input curves.
The `corners` virtual array allows specific input points to be treated
as sharp corners. The resulting bezier curve will have this point and
the handles will be set to "free".
There are two fitting methods:
* **Split**: Uses a least squares solver to find the control
points (faster, but less accurate).
* **Refit**: Iteratively removes knots with the least error starting
with a dense curve (slower, more accurate fit).
Co-authored-by: Casey Bianco-Davis <caseycasey739@gmail.com>
Co-authored-by: Hans Goudey <hans@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/137808
This commit is contained in:
@@ -5823,8 +5823,8 @@ class VIEW3D_MT_edit_greasepencil_stroke(Menu):
|
||||
|
||||
layout.separator()
|
||||
|
||||
layout.operator_menu_enum("grease_pencil.set_curve_type", property="type")
|
||||
layout.operator("grease_pencil.set_curve_resolution")
|
||||
layout.operator_menu_enum("grease_pencil.convert_curve_type", text="Convert Type", property="type")
|
||||
layout.operator("grease_pencil.set_curve_resolution", text="Set Resolution")
|
||||
|
||||
layout.separator()
|
||||
|
||||
@@ -8345,6 +8345,10 @@ class VIEW3D_MT_greasepencil_edit_context_menu(Menu):
|
||||
col.separator()
|
||||
|
||||
col.operator("grease_pencil.separate", text="Separate").mode = 'SELECTED'
|
||||
|
||||
col.separator()
|
||||
col.operator_menu_enum("grease_pencil.convert_curve_type", text="Convert Type", property="type")
|
||||
layout.operator("grease_pencil.set_curve_resolution", text="Convert Type")
|
||||
else:
|
||||
col = row.column(align=True)
|
||||
col.label(text="Point", icon='GP_SELECT_POINTS')
|
||||
@@ -8393,6 +8397,9 @@ class VIEW3D_MT_greasepencil_edit_context_menu(Menu):
|
||||
|
||||
col.operator_enum("grease_pencil.dissolve", "type")
|
||||
|
||||
col.separator()
|
||||
col.operator_menu_enum("grease_pencil.convert_curve_type", text="Convert Type", property="type")
|
||||
|
||||
|
||||
class GREASE_PENCIL_MT_Layers(Menu):
|
||||
bl_label = "Layers"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_utildefines.h"
|
||||
#include "BLI_vector.hh"
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
@@ -63,6 +64,7 @@
|
||||
#include "ED_view3d.hh"
|
||||
|
||||
#include "GEO_curves_remove_and_split.hh"
|
||||
#include "GEO_fit_curves.hh"
|
||||
#include "GEO_join_geometries.hh"
|
||||
#include "GEO_realize_instances.hh"
|
||||
#include "GEO_reorder.hh"
|
||||
@@ -4391,6 +4393,216 @@ static void GREASE_PENCIL_OT_outline(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Convert Curve Type Operator
|
||||
* \{ */
|
||||
|
||||
static const bke::CurvesGeometry fit_poly_curves(bke::CurvesGeometry &curves,
|
||||
const IndexMask &selection,
|
||||
const float threshold)
|
||||
{
|
||||
const VArray<float> thresholds = VArray<float>::ForSingle(threshold, curves.curves_num());
|
||||
/* TODO: Detect or manually provide corners. */
|
||||
const VArray<bool> corners = VArray<bool>::ForSingle(false, curves.points_num());
|
||||
return geometry::fit_poly_to_bezier_curves(
|
||||
curves, selection, thresholds, corners, geometry::FitMethod::Refit, {});
|
||||
}
|
||||
|
||||
static void convert_to_catmull_rom(bke::CurvesGeometry &curves,
|
||||
const IndexMask &selection,
|
||||
const float threshold)
|
||||
{
|
||||
if (curves.is_single_type(CURVE_TYPE_CATMULL_ROM)) {
|
||||
return;
|
||||
}
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask non_catmull_rom_curves_selection =
|
||||
curves.indices_for_curve_type(CURVE_TYPE_CATMULL_ROM, selection, memory)
|
||||
.complement(selection, memory);
|
||||
BLI_assert(!non_catmull_rom_curves_selection.is_empty());
|
||||
curves = geometry::resample_to_evaluated(curves, non_catmull_rom_curves_selection);
|
||||
|
||||
/* To avoid having too many control points, simplify the position attribute based on the
|
||||
* threshold. This doesn't replace an actual curve fitting (which would be better), but
|
||||
* is a decent approximation for the meantime. */
|
||||
const IndexMask points_to_remove = geometry::simplify_curve_attribute(
|
||||
curves.positions(),
|
||||
non_catmull_rom_curves_selection,
|
||||
curves.points_by_curve(),
|
||||
curves.cyclic(),
|
||||
threshold,
|
||||
curves.positions(),
|
||||
memory);
|
||||
curves.remove_points(points_to_remove, {});
|
||||
|
||||
geometry::ConvertCurvesOptions options;
|
||||
options.convert_bezier_handles_to_poly_points = false;
|
||||
options.convert_bezier_handles_to_catmull_rom_points = false;
|
||||
options.keep_bezier_shape_as_nurbs = true;
|
||||
options.keep_catmull_rom_shape_as_nurbs = true;
|
||||
curves = geometry::convert_curves(
|
||||
curves, non_catmull_rom_curves_selection, CURVE_TYPE_CATMULL_ROM, {}, options);
|
||||
}
|
||||
|
||||
static void convert_to_poly(bke::CurvesGeometry &curves, const IndexMask &selection)
|
||||
{
|
||||
if (curves.is_single_type(CURVE_TYPE_POLY)) {
|
||||
return;
|
||||
}
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask non_poly_curves_selection = curves
|
||||
.indices_for_curve_type(
|
||||
CURVE_TYPE_POLY, selection, memory)
|
||||
.complement(selection, memory);
|
||||
BLI_assert(!non_poly_curves_selection.is_empty());
|
||||
curves = geometry::resample_to_evaluated(curves, non_poly_curves_selection);
|
||||
}
|
||||
|
||||
static void convert_to_bezier(bke::CurvesGeometry &curves,
|
||||
const IndexMask &selection,
|
||||
const float threshold)
|
||||
{
|
||||
if (curves.is_single_type(CURVE_TYPE_BEZIER)) {
|
||||
return;
|
||||
}
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask poly_curves_selection = curves.indices_for_curve_type(
|
||||
CURVE_TYPE_POLY, selection, memory);
|
||||
if (!poly_curves_selection.is_empty()) {
|
||||
curves = fit_poly_curves(curves, poly_curves_selection, threshold);
|
||||
}
|
||||
|
||||
geometry::ConvertCurvesOptions options;
|
||||
options.convert_bezier_handles_to_poly_points = false;
|
||||
options.convert_bezier_handles_to_catmull_rom_points = false;
|
||||
options.keep_bezier_shape_as_nurbs = true;
|
||||
options.keep_catmull_rom_shape_as_nurbs = true;
|
||||
curves = geometry::convert_curves(curves, selection, CURVE_TYPE_BEZIER, {}, options);
|
||||
}
|
||||
|
||||
static void convert_to_nurbs(bke::CurvesGeometry &curves,
|
||||
const IndexMask &selection,
|
||||
const float threshold)
|
||||
{
|
||||
if (curves.is_single_type(CURVE_TYPE_NURBS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask poly_curves_selection = curves.indices_for_curve_type(
|
||||
CURVE_TYPE_POLY, selection, memory);
|
||||
if (!poly_curves_selection.is_empty()) {
|
||||
curves = fit_poly_curves(curves, poly_curves_selection, threshold);
|
||||
}
|
||||
|
||||
geometry::ConvertCurvesOptions options;
|
||||
options.convert_bezier_handles_to_poly_points = false;
|
||||
options.convert_bezier_handles_to_catmull_rom_points = false;
|
||||
options.keep_bezier_shape_as_nurbs = true;
|
||||
options.keep_catmull_rom_shape_as_nurbs = true;
|
||||
curves = geometry::convert_curves(curves, selection, CURVE_TYPE_NURBS, {}, options);
|
||||
}
|
||||
|
||||
static wmOperatorStatus grease_pencil_convert_curve_type_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
Object *object = CTX_data_active_object(C);
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
|
||||
|
||||
const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
|
||||
const float threshold = RNA_float_get(op->ptr, "threshold");
|
||||
|
||||
std::atomic<bool> changed = false;
|
||||
const Vector<MutableDrawingInfo> drawings = retrieve_editable_drawings(*scene, grease_pencil);
|
||||
threading::parallel_for_each(drawings, [&](const MutableDrawingInfo &info) {
|
||||
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask strokes = ed::greasepencil::retrieve_editable_and_selected_strokes(
|
||||
*object, info.drawing, info.layer_index, memory);
|
||||
if (strokes.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (dst_type) {
|
||||
case CURVE_TYPE_CATMULL_ROM:
|
||||
convert_to_catmull_rom(curves, strokes, threshold);
|
||||
break;
|
||||
case CURVE_TYPE_POLY:
|
||||
convert_to_poly(curves, strokes);
|
||||
break;
|
||||
case CURVE_TYPE_BEZIER:
|
||||
convert_to_bezier(curves, strokes, threshold);
|
||||
break;
|
||||
case CURVE_TYPE_NURBS:
|
||||
convert_to_nurbs(curves, strokes, threshold);
|
||||
break;
|
||||
}
|
||||
|
||||
info.drawing.tag_topology_changed();
|
||||
changed.store(true, std::memory_order_relaxed);
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
||||
WM_event_add_notifier(C, NC_GEOM | ND_DATA, &grease_pencil);
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static void grease_pencil_convert_curve_type_ui(bContext *C, wmOperator *op)
|
||||
{
|
||||
uiLayout *layout = op->layout;
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
|
||||
PointerRNA ptr = RNA_pointer_create_discrete(&wm->id, op->type->srna, op->properties);
|
||||
|
||||
uiLayoutSetPropSep(layout, true);
|
||||
uiLayoutSetPropDecorate(layout, false);
|
||||
|
||||
layout->prop(&ptr, "type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
|
||||
|
||||
if (dst_type == CURVE_TYPE_POLY) {
|
||||
return;
|
||||
}
|
||||
|
||||
layout->prop(&ptr, "threshold", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void GREASE_PENCIL_OT_convert_curve_type(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Convert Curve Type";
|
||||
ot->idname = "GREASE_PENCIL_OT_convert_curve_type";
|
||||
ot->description = "Convert type of selected curves";
|
||||
|
||||
ot->invoke = WM_menu_invoke;
|
||||
ot->exec = grease_pencil_convert_curve_type_exec;
|
||||
ot->poll = editable_grease_pencil_poll;
|
||||
ot->ui = grease_pencil_convert_curve_type_ui;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
ot->prop = RNA_def_enum(
|
||||
ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "");
|
||||
RNA_def_property_flag(ot->prop, PROP_SKIP_SAVE);
|
||||
|
||||
PropertyRNA *prop = RNA_def_float(
|
||||
ot->srna,
|
||||
"threshold",
|
||||
0.01f,
|
||||
0.0f,
|
||||
100.0f,
|
||||
"Threshold",
|
||||
"The distance that the resulting points are allowed to be within",
|
||||
0.0f,
|
||||
100.0f);
|
||||
RNA_def_property_subtype(prop, PROP_DISTANCE);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::ed::greasepencil
|
||||
|
||||
void ED_operatortypes_grease_pencil_edit()
|
||||
@@ -4433,6 +4645,7 @@ void ED_operatortypes_grease_pencil_edit()
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_stroke_split);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_remove_fill_guides);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_outline);
|
||||
WM_operatortype_append(GREASE_PENCIL_OT_convert_curve_type);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -18,6 +18,7 @@ set(SRC
|
||||
intern/extend_curves.cc
|
||||
intern/extract_elements.cc
|
||||
intern/fillet_curves.cc
|
||||
intern/fit_curves.cc
|
||||
intern/interpolate_curves.cc
|
||||
intern/join_geometries.cc
|
||||
intern/merge_curves.cc
|
||||
@@ -61,6 +62,7 @@ set(SRC
|
||||
GEO_extend_curves.hh
|
||||
GEO_extract_elements.hh
|
||||
GEO_fillet_curves.hh
|
||||
GEO_fit_curves.hh
|
||||
GEO_interpolate_curves.hh
|
||||
GEO_join_geometries.hh
|
||||
GEO_merge_curves.hh
|
||||
@@ -109,6 +111,7 @@ set(LIB
|
||||
PRIVATE bf::intern::atomic
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::extern::fmtlib
|
||||
PRIVATE bf::extern::curve_fit_nd
|
||||
PRIVATE bf::dependencies::optional::manifold
|
||||
)
|
||||
|
||||
|
||||
42
source/blender/geometry/GEO_fit_curves.hh
Normal file
42
source/blender/geometry/GEO_fit_curves.hh
Normal file
@@ -0,0 +1,42 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BKE_curves.hh"
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
enum class FitMethod {
|
||||
/**
|
||||
* Iteratively removes knots/control points with the least error starting with a dense curve.
|
||||
*/
|
||||
Refit,
|
||||
/**
|
||||
* Uses a least squares solver to recursively find the control points.
|
||||
*/
|
||||
Split
|
||||
};
|
||||
|
||||
/**
|
||||
* Fit the selected curves to Bézier curves.
|
||||
*
|
||||
* \param src_curves: The input curves.
|
||||
* \param curve_selection: A selection of curves to fit. The selected curves will be replaced by
|
||||
* the fitted bézier curves and the unselected curves are copied to the output geometry.
|
||||
* \param thresholds: A error threshold (fit distance) for each input curve. The fitted curve
|
||||
* should be within this distance.
|
||||
* \param corners: Boolean value for each input point. When this is true, the point is treated as a
|
||||
* corner in the curve fitting. The resulting bézier curve will include this point and the handles
|
||||
* will be "free", resulting in a sharp corner.
|
||||
* \param method: The fitting algorithm to use. See #FitMethod.
|
||||
*/
|
||||
bke::CurvesGeometry fit_poly_to_bezier_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask &curve_selection,
|
||||
const VArray<float> &thresholds,
|
||||
const VArray<bool> &corners,
|
||||
FitMethod method,
|
||||
const bke::AttributeFilter &attribute_filter);
|
||||
|
||||
} // namespace blender::geometry
|
||||
275
source/blender/geometry/intern/fit_curves.cc
Normal file
275
source/blender/geometry/intern/fit_curves.cc
Normal file
@@ -0,0 +1,275 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BKE_curves_utils.hh"
|
||||
#include "BKE_deform.hh"
|
||||
|
||||
#include "GEO_fit_curves.hh"
|
||||
|
||||
extern "C" {
|
||||
#include "curve_fit_nd.h"
|
||||
}
|
||||
|
||||
namespace blender::geometry {
|
||||
|
||||
bke::CurvesGeometry fit_poly_to_bezier_curves(const bke::CurvesGeometry &src_curves,
|
||||
const IndexMask &curve_selection,
|
||||
const VArray<float> &thresholds,
|
||||
const VArray<bool> &corners,
|
||||
const FitMethod method,
|
||||
const bke::AttributeFilter &attribute_filter)
|
||||
{
|
||||
if (curve_selection.is_empty()) {
|
||||
return src_curves;
|
||||
}
|
||||
|
||||
BLI_assert(thresholds.size() == src_curves.curves_num());
|
||||
BLI_assert(corners.size() == src_curves.points_num());
|
||||
|
||||
const OffsetIndices src_points_by_curve = src_curves.offsets();
|
||||
const Span<float3> src_positions = src_curves.positions();
|
||||
const VArray<bool> src_cyclic = src_curves.cyclic();
|
||||
|
||||
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
|
||||
BKE_defgroup_copy_list(&dst_curves.vertex_group_names, &src_curves.vertex_group_names);
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask unselected_curves = curve_selection.complement(src_curves.curves_range(),
|
||||
memory);
|
||||
|
||||
/* Write the new sizes to the dst_curve_sizes, they will be accumulated later to offsets. */
|
||||
MutableSpan<int> dst_curve_sizes = dst_curves.offsets_for_write();
|
||||
offset_indices::copy_group_sizes(src_points_by_curve, unselected_curves, dst_curve_sizes);
|
||||
MutableSpan<int8_t> dst_curve_types = dst_curves.curve_types_for_write();
|
||||
|
||||
/* NOTE: These spans own the data from the curve fit C-API. */
|
||||
Array<MutableSpan<float3>> cubic_array_per_curve(curve_selection.size());
|
||||
Array<MutableSpan<int>> corner_indices_per_curve(curve_selection.size());
|
||||
Array<MutableSpan<int>> original_indices_per_curve(curve_selection.size());
|
||||
|
||||
std::atomic<bool> success = false;
|
||||
curve_selection.foreach_index(GrainSize(32), [&](const int64_t curve_i, const int64_t pos) {
|
||||
const IndexRange points = src_points_by_curve[curve_i];
|
||||
const Span<float3> curve_positions = src_positions.slice(points);
|
||||
const bool is_cyclic = src_cyclic[curve_i];
|
||||
const float epsilon = thresholds[curve_i];
|
||||
|
||||
/* Both curve fitting algorithms expect the first and last points for non-cyclic curves to be
|
||||
* treated as if they were corners. */
|
||||
const bool use_first_as_corner = !is_cyclic && !corners[points.first()];
|
||||
const bool use_last_as_corner = !is_cyclic && !corners[points.last()];
|
||||
Vector<int, 32> src_corners;
|
||||
if (use_first_as_corner) {
|
||||
src_corners.append(0);
|
||||
}
|
||||
if (points.size() > 2) {
|
||||
for (const int i : IndexRange::from_begin_end(1, points.size() - 1)) {
|
||||
if (corners[points[i]]) {
|
||||
src_corners.append(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (use_last_as_corner) {
|
||||
src_corners.append(points.last());
|
||||
}
|
||||
const uint *src_corners_ptr = src_corners.is_empty() ?
|
||||
nullptr :
|
||||
reinterpret_cast<uint *>(src_corners.data());
|
||||
|
||||
const uint8_t flag = CURVE_FIT_CALC_HIGH_QUALIY | ((is_cyclic) ? CURVE_FIT_CALC_CYCLIC : 0);
|
||||
|
||||
float *cubic_array = nullptr;
|
||||
uint32_t *orig_index_map = nullptr;
|
||||
uint32_t cubic_array_size = 0;
|
||||
uint32_t *corner_index_array = nullptr;
|
||||
uint32_t corner_index_array_size = 0;
|
||||
int error = 1;
|
||||
if (method == FitMethod::Split) {
|
||||
error = curve_fit_cubic_to_points_fl(curve_positions.cast<float>().data(),
|
||||
curve_positions.size(),
|
||||
3,
|
||||
epsilon,
|
||||
flag,
|
||||
src_corners_ptr,
|
||||
src_corners.size(),
|
||||
&cubic_array,
|
||||
&cubic_array_size,
|
||||
&orig_index_map,
|
||||
&corner_index_array,
|
||||
&corner_index_array_size);
|
||||
}
|
||||
else if (method == FitMethod::Refit) {
|
||||
error = curve_fit_cubic_to_points_refit_fl(curve_positions.cast<float>().data(),
|
||||
curve_positions.size(),
|
||||
3,
|
||||
epsilon,
|
||||
flag,
|
||||
src_corners_ptr,
|
||||
src_corners.size(),
|
||||
/* Don't use automatic corner detection. */
|
||||
FLT_MAX,
|
||||
&cubic_array,
|
||||
&cubic_array_size,
|
||||
&orig_index_map,
|
||||
&corner_index_array,
|
||||
&corner_index_array_size);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
/* Some error occured. Fall back to using the input positions as the (poly) curve. */
|
||||
dst_curve_sizes[curve_i] = points.size();
|
||||
dst_curve_types[curve_i] = CURVE_TYPE_POLY;
|
||||
return;
|
||||
}
|
||||
|
||||
success.store(true, std::memory_order_relaxed);
|
||||
|
||||
const int dst_points_num = cubic_array_size;
|
||||
BLI_assert(dst_points_num > 0);
|
||||
|
||||
dst_curve_sizes[curve_i] = dst_points_num;
|
||||
dst_curve_types[curve_i] = CURVE_TYPE_BEZIER;
|
||||
|
||||
cubic_array_per_curve[pos] = MutableSpan<float3>(reinterpret_cast<float3 *>(cubic_array),
|
||||
dst_points_num * 3);
|
||||
corner_indices_per_curve[pos] = MutableSpan<int>(reinterpret_cast<int *>(corner_index_array),
|
||||
corner_index_array_size);
|
||||
original_indices_per_curve[pos] = MutableSpan<int>(reinterpret_cast<int *>(orig_index_map),
|
||||
dst_points_num);
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
/* None of the curve fittings succeeded. */
|
||||
return src_curves;
|
||||
}
|
||||
|
||||
const OffsetIndices dst_points_by_curve = offset_indices::accumulate_counts_to_offsets(
|
||||
dst_curve_sizes);
|
||||
dst_curves.resize(dst_curves.offsets().last(), dst_curves.curves_num());
|
||||
|
||||
const Span<float3> src_handles_left = src_curves.handle_positions_left();
|
||||
const Span<float3> src_handles_right = src_curves.handle_positions_right();
|
||||
const VArraySpan<int8_t> src_handle_types_left = src_curves.handle_types_left();
|
||||
const VArraySpan<int8_t> src_handle_types_right = src_curves.handle_types_right();
|
||||
|
||||
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
|
||||
MutableSpan<float3> dst_handles_left = dst_curves.handle_positions_left_for_write();
|
||||
MutableSpan<float3> dst_handles_right = dst_curves.handle_positions_right_for_write();
|
||||
MutableSpan<int8_t> dst_handle_types_left = dst_curves.handle_types_left_for_write();
|
||||
MutableSpan<int8_t> dst_handle_types_right = dst_curves.handle_types_right_for_write();
|
||||
|
||||
/* First handle the unselected curves. */
|
||||
if (!src_handles_left.is_empty()) {
|
||||
array_utils::copy_group_to_group(src_points_by_curve,
|
||||
dst_points_by_curve,
|
||||
unselected_curves,
|
||||
src_handles_left,
|
||||
dst_handles_left);
|
||||
}
|
||||
array_utils::copy_group_to_group(
|
||||
src_points_by_curve, dst_points_by_curve, unselected_curves, src_positions, dst_positions);
|
||||
if (!src_handles_right.is_empty()) {
|
||||
array_utils::copy_group_to_group(src_points_by_curve,
|
||||
dst_points_by_curve,
|
||||
unselected_curves,
|
||||
src_handles_right,
|
||||
dst_handles_right);
|
||||
}
|
||||
if (!src_handle_types_left.is_empty()) {
|
||||
array_utils::copy_group_to_group(src_points_by_curve,
|
||||
dst_points_by_curve,
|
||||
unselected_curves,
|
||||
src_handle_types_left,
|
||||
dst_handle_types_left);
|
||||
}
|
||||
if (!src_handle_types_right.is_empty()) {
|
||||
array_utils::copy_group_to_group(src_points_by_curve,
|
||||
dst_points_by_curve,
|
||||
unselected_curves,
|
||||
src_handle_types_right,
|
||||
dst_handle_types_right);
|
||||
}
|
||||
|
||||
Array<int> old_by_new_map(dst_curves.points_num());
|
||||
unselected_curves.foreach_index(GrainSize(1024), [&](const int64_t curve_i) {
|
||||
const IndexRange src_points = src_points_by_curve[curve_i];
|
||||
const IndexRange dst_points = dst_points_by_curve[curve_i];
|
||||
array_utils::fill_index_range<int>(old_by_new_map.as_mutable_span().slice(dst_points),
|
||||
src_points.start());
|
||||
});
|
||||
|
||||
/* Now copy the data of the newly fitted curves. */
|
||||
curve_selection.foreach_index(GrainSize(1024), [&](const int64_t curve_i, const int64_t pos) {
|
||||
const IndexRange src_points = src_points_by_curve[curve_i];
|
||||
const IndexRange dst_points = dst_points_by_curve[curve_i];
|
||||
MutableSpan<float3> positions = dst_positions.slice(dst_points);
|
||||
MutableSpan<int> old_by_new = old_by_new_map.as_mutable_span().slice(dst_points);
|
||||
|
||||
if (dst_curve_types[curve_i] == CURVE_TYPE_POLY) {
|
||||
/* Handle the curves for which the curve fitting has failed. */
|
||||
BLI_assert(src_points.size() == dst_points.size());
|
||||
positions.copy_from(src_positions.slice(src_points));
|
||||
dst_handles_left.slice(dst_points).copy_from(src_positions.slice(src_points));
|
||||
dst_handles_right.slice(dst_points).copy_from(src_positions.slice(src_points));
|
||||
dst_handle_types_left.slice(dst_points).fill(BEZIER_HANDLE_FREE);
|
||||
dst_handle_types_right.slice(dst_points).fill(BEZIER_HANDLE_FREE);
|
||||
array_utils::fill_index_range<int>(old_by_new, src_points.start());
|
||||
return;
|
||||
}
|
||||
|
||||
const Span<float3> cubic_array = cubic_array_per_curve[pos];
|
||||
BLI_assert(dst_points.size() * 3 == cubic_array.size());
|
||||
MutableSpan<float3> left_handles = dst_handles_left.slice(dst_points);
|
||||
MutableSpan<float3> right_handles = dst_handles_right.slice(dst_points);
|
||||
threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const int index = i * 3;
|
||||
positions[i] = cubic_array[index + 1];
|
||||
left_handles[i] = cubic_array[index];
|
||||
right_handles[i] = cubic_array[index + 2];
|
||||
}
|
||||
});
|
||||
|
||||
const Span<int> corner_indices = corner_indices_per_curve[pos];
|
||||
dst_handle_types_left.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
|
||||
dst_handle_types_right.slice(dst_points).fill_indices(corner_indices, BEZIER_HANDLE_FREE);
|
||||
|
||||
const Span<int> original_indices = original_indices_per_curve[pos];
|
||||
threading::parallel_for(dst_points.index_range(), 8192, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
old_by_new[i] = src_points[original_indices[i]];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dst_curves.update_curve_types();
|
||||
|
||||
bke::gather_attributes(
|
||||
src_curves.attributes(),
|
||||
bke::AttrDomain::Point,
|
||||
bke::AttrDomain::Point,
|
||||
bke::attribute_filter_with_skip_ref(
|
||||
attribute_filter,
|
||||
{"position", "handle_left", "handle_right", "handle_type_left", "handle_type_right"}),
|
||||
old_by_new_map,
|
||||
dst_curves.attributes_for_write());
|
||||
|
||||
/* Free all the data from the C-API. */
|
||||
for (MutableSpan<float3> cubic_array : cubic_array_per_curve) {
|
||||
free(cubic_array.data());
|
||||
}
|
||||
for (MutableSpan<int> corner_indices : corner_indices_per_curve) {
|
||||
free(corner_indices.data());
|
||||
}
|
||||
for (MutableSpan<int> original_indices : original_indices_per_curve) {
|
||||
free(original_indices.data());
|
||||
}
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
} // namespace blender::geometry
|
||||
Reference in New Issue
Block a user