GPv3: Simplify Modifier #120018

Merged
Falk David merged 31 commits from filedescriptor/blender:gpv3-simplify-modifier into main 2024-03-28 18:16:24 +01:00
10 changed files with 605 additions and 0 deletions

View File

@ -162,6 +162,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu):
self.operator_modifier_add(layout, 'GREASE_PENCIL_MIRROR')
self.operator_modifier_add(layout, 'GREASE_PENCIL_MULTIPLY')
self.operator_modifier_add(layout, 'GREASE_PENCIL_OUTLINE')
self.operator_modifier_add(layout, 'GREASE_PENCIL_SIMPLIFY')
self.operator_modifier_add(layout, 'GREASE_PENCIL_SUBDIV')
self.operator_modifier_add(layout, 'LINEART')
layout.template_modifier_asset_menu_items(catalog_path=self.bl_label)

View File

@ -2181,6 +2181,49 @@ static void legacy_object_modifier_build(Object &object, GpencilModifierData &le
false);
}
static void legacy_object_modifier_simplify(Object &object, GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
object, eModifierType_GreasePencilSimplify, legacy_md);
auto &md_simplify = reinterpret_cast<GreasePencilSimplifyModifierData &>(md);
auto &legacy_md_simplify = reinterpret_cast<SimplifyGpencilModifierData &>(legacy_md);
switch (legacy_md_simplify.mode) {
case GP_SIMPLIFY_FIXED:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_FIXED;
break;
case GP_SIMPLIFY_ADAPTIVE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE;
break;
case GP_SIMPLIFY_SAMPLE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE;
break;
case GP_SIMPLIFY_MERGE:
md_simplify.mode = MOD_GREASE_PENCIL_SIMPLIFY_MERGE;
break;
}
md_simplify.step = legacy_md_simplify.step;
md_simplify.factor = legacy_md_simplify.factor;
md_simplify.length = legacy_md_simplify.length;
md_simplify.sharp_threshold = legacy_md_simplify.sharp_threshold;
md_simplify.distance = legacy_md_simplify.distance;
legacy_object_modifier_influence(md_simplify.influence,
legacy_md_simplify.layername,
legacy_md_simplify.layer_pass,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_LAYER,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_LAYERPASS,
&legacy_md_simplify.material,
legacy_md_simplify.pass_index,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_MATERIAL,
legacy_md_simplify.flag & GP_SIMPLIFY_INVERT_PASS,
"",
false,
nullptr,
false);
}
static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
{
BLI_assert(BLI_listbase_is_empty(&object.modifiers));
@ -2265,6 +2308,8 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
legacy_object_modifier_build(object, *gpd_md);
break;
case eGpencilModifierType_Simplify:
legacy_object_modifier_simplify(object, *gpd_md);
break;
case eGpencilModifierType_Texture:
break;
}

View File

@ -1062,4 +1062,14 @@
.percentage_fac = 0.0f, \
}
#define _DNA_DEFAULT_GreasePencilSimplifyModifierData \
{ \
.factor = 0.0f, \
.mode = MOD_GREASE_PENCIL_SIMPLIFY_FIXED, \
.step = 1, \
.length = 0.1f, \
.distance = 0.1f, \
}
/* clang-format off */

View File

@ -119,6 +119,7 @@ typedef enum ModifierType {
eModifierType_GreasePencilOutline = 82,
eModifierType_GreasePencilShrinkwrap = 83,
eModifierType_GreasePencilBuild = 84,
eModifierType_GreasePencilSimplify = 85,
NUM_MODIFIER_TYPES,
} ModifierType;
@ -3404,3 +3405,28 @@ typedef enum GreasePencilBuildFlag {
MOD_GREASE_PENCIL_BUILD_RESTRICT_TIME = (1 << 0),
MOD_GREASE_PENCIL_BUILD_USE_FADING = (1 << 14),
} GreasePencilBuildFlag;
typedef struct GreasePencilSimplifyModifierData {
ModifierData modifier;
GreasePencilModifierInfluenceData influence;
/** #GreasePencilSimplifyModifierMode. */
short mode;
char _pad[4];
/** Every n vertex to keep. */
short step;
float factor;
/** For sampling. */
float length;
float sharp_threshold;
/** Merge distance */
float distance;
} GreasePencilSimplifyModifierData;
typedef enum GreasePencilSimplifyModifierMode {
MOD_GREASE_PENCIL_SIMPLIFY_FIXED = 0,
MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE = 1,
MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE = 2,
MOD_GREASE_PENCIL_SIMPLIFY_MERGE = 3,
} GreasePencilSimplifyModifierMode;

View File

@ -356,6 +356,7 @@ SDNA_DEFAULT_DECL_STRUCT(GreasePencilHookModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilArmatureModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilTimeModifierSegment);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilTimeModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilSimplifyModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilEnvelopeModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilOutlineModifierData);
SDNA_DEFAULT_DECL_STRUCT(GreasePencilShrinkwrapModifierData);
@ -632,6 +633,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(GreasePencilArmatureModifierData),
SDNA_DEFAULT_DECL(GreasePencilTimeModifierSegment),
SDNA_DEFAULT_DECL(GreasePencilTimeModifierData),
SDNA_DEFAULT_DECL(GreasePencilSimplifyModifierData),
SDNA_DEFAULT_DECL(GreasePencilEnvelopeModifierData),
SDNA_DEFAULT_DECL(GreasePencilOutlineModifierData),
SDNA_DEFAULT_DECL(GreasePencilShrinkwrapModifierData),

View File

@ -259,6 +259,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
ICON_GP_MULTIFRAME_EDITING,
"Multiple Strokes",
"Generate multiple strokes around original strokes"},
{eModifierType_GreasePencilSimplify,
"GREASE_PENCIL_SIMPLIFY",
ICON_MOD_SIMPLIFY,
"Simplify",
"Simplify stroke reducing number of points"},
{eModifierType_GreasePencilSubdiv,
"GREASE_PENCIL_SUBDIV",
ICON_MOD_SUBSURF,
@ -1992,6 +1997,7 @@ RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilWeightAngle);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilArray);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilWeightProximity);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilHook);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilSimplify);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilEnvelope);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilOutline);
RNA_MOD_GREASE_PENCIL_MATERIAL_FILTER_SET(GreasePencilShrinkwrap);
@ -2008,6 +2014,7 @@ RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilWeightAngle);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilWeightProximity);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilHook);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilArmature);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilSimplify);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilEnvelope);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilShrinkwrap);
@ -9995,6 +10002,89 @@ static void rna_def_modifier_grease_pencil_weight_proximity(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_grease_pencil_simplify(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
static EnumPropertyItem prop_gpencil_simplify_mode_items[] = {
{MOD_GREASE_PENCIL_SIMPLIFY_FIXED,
"FIXED",
ICON_IPO_CONSTANT,
"Fixed",
"Delete alternating vertices in the stroke, except extremes"},
{MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE,
"ADAPTIVE",
ICON_IPO_EASE_IN_OUT,
"Adaptive",
"Use a Ramer-Douglas-Peucker algorithm to simplify the stroke preserving main shape"},
{MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE,
"SAMPLE",
ICON_IPO_EASE_IN_OUT,
"Sample",
"Re-sample the stroke with segments of the specified length"},
{MOD_GREASE_PENCIL_SIMPLIFY_MERGE,
"MERGE",
ICON_IPO_EASE_IN_OUT,
"Merge",
"Simplify the stroke by merging vertices closer than a given distance"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "GreasePencilSimplifyModifier", "Modifier");
RNA_def_struct_ui_text(srna, "Simplify Modifier", "Simplify Stroke modifier");
RNA_def_struct_sdna(srna, "GreasePencilSimplifyModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_SIMPLIFY);
rna_def_modifier_grease_pencil_layer_filter(srna);
rna_def_modifier_grease_pencil_material_filter(
srna, "rna_GreasePencilSimplifyModifier_material_filter_set");
rna_def_modifier_grease_pencil_vertex_group(
srna, "rna_GreasePencilSimplifyModifier_vertex_group_name_set");
prop = RNA_def_property(srna, "factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, nullptr, "factor");
RNA_def_property_range(prop, 0, 100.0);
RNA_def_property_ui_range(prop, 0, 5.0f, 1.0f, 3);
RNA_def_property_ui_text(prop, "Factor", "Factor of Simplify");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_gpencil_simplify_mode_items);
RNA_def_property_ui_text(prop, "Mode", "How to simplify the stroke");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "step", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "step");
RNA_def_property_range(prop, 1, 50);
RNA_def_property_ui_text(prop, "Iterations", "Number of times to apply simplify");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "length", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "length");
RNA_def_property_range(prop, 0, FLT_MAX);
RNA_def_property_ui_range(prop, 0.005, 1.0, 0.05, 3);
RNA_def_property_ui_text(prop, "Length", "Length of each segment");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "sharp_threshold", PROP_FLOAT, PROP_ANGLE);
RNA_def_property_float_sdna(prop, nullptr, "sharp_threshold");
RNA_def_property_range(prop, 0, M_PI);
RNA_def_property_ui_range(prop, 0, M_PI, 1.0, 1);
RNA_def_property_ui_text(
prop, "Sharp Threshold", "Preserve corners that have sharper angle than this threshold");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "distance", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "distance");
RNA_def_property_range(prop, 0, FLT_MAX);
RNA_def_property_ui_range(prop, 0, 1.0, 0.01, 3);
RNA_def_property_ui_text(prop, "Distance", "Distance between points");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_grease_pencil_armature(BlenderRNA *brna)
{
StructRNA *srna;
@ -10904,6 +10994,7 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_grease_pencil_armature(brna);
rna_def_modifier_grease_pencil_time_segment(brna);
rna_def_modifier_grease_pencil_time(brna);
rna_def_modifier_grease_pencil_simplify(brna);
rna_def_modifier_grease_pencil_envelope(brna);
rna_def_modifier_grease_pencil_outline(brna);
rna_def_modifier_grease_pencil_shrinkwrap(brna);

View File

@ -63,6 +63,7 @@ set(SRC
intern/MOD_grease_pencil_noise.cc
intern/MOD_grease_pencil_offset.cc
intern/MOD_grease_pencil_opacity.cc
intern/MOD_grease_pencil_simplify.cc
intern/MOD_grease_pencil_outline.cc
intern/MOD_grease_pencil_shrinkwrap.cc
intern/MOD_grease_pencil_smooth.cc

View File

@ -93,6 +93,7 @@ extern ModifierTypeInfo modifierType_GreasePencilHook;
extern ModifierTypeInfo modifierType_GreasePencilLineart;
extern ModifierTypeInfo modifierType_GreasePencilArmature;
extern ModifierTypeInfo modifierType_GreasePencilTime;
extern ModifierTypeInfo modifierType_GreasePencilSimplify;
extern ModifierTypeInfo modifierType_GreasePencilEnvelope;
extern ModifierTypeInfo modifierType_GreasePencilOutline;
extern ModifierTypeInfo modifierType_GreasePencilShrinkwrap;

View File

@ -0,0 +1,427 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
filedescriptor marked this conversation as resolved Outdated

Copyright year

Copyright year
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "BLI_index_mask.hh"
#include "BLI_kdtree.h"
#include "BLT_translation.hh"
#include "BLO_read_write.hh"
#include "DNA_defaults.h"
#include "DNA_modifier_types.h"
#include "DNA_screen_types.h"
#include "BKE_curves_utils.hh"
#include "BKE_geometry_fields.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_modifier.hh"
#include "GEO_resample_curves.hh"
#include "GEO_simplify_curves.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "MOD_grease_pencil_util.hh"
#include "MOD_ui_common.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
namespace blender {
static void init_data(ModifierData *md)
{
auto *gpmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(GreasePencilSimplifyModifierData), modifier);
modifier::greasepencil::init_influence_data(&gpmd->influence, true);
}
static void free_data(ModifierData *md)
{
auto *mmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
modifier::greasepencil::free_influence_data(&mmd->influence);
}
static void copy_data(const ModifierData *md, ModifierData *target, int flag)
{
const auto *gmd = reinterpret_cast<const GreasePencilSimplifyModifierData *>(md);
auto *tgmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(target);
BKE_modifier_copydata_generic(md, target, flag);
modifier::greasepencil::copy_influence_data(&gmd->influence, &tgmd->influence, flag);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const auto *mmd = reinterpret_cast<const GreasePencilSimplifyModifierData *>(md);
BLO_write_struct(writer, GreasePencilSimplifyModifierData, mmd);
modifier::greasepencil::write_influence_data(writer, &mmd->influence);
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
auto *mmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
modifier::greasepencil::read_influence_data(reader, &mmd->influence);
}
static IndexMask simplify_fixed(const bke::CurvesGeometry &curves,
const int step,
IndexMaskMemory &memory)
{
const OffsetIndices points_by_curve = curves.points_by_curve();
const Array<int> point_to_curve_map = curves.point_to_curve_map();
return IndexMask::from_predicate(
curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) {
const int curve_i = point_to_curve_map[i];
const IndexRange points = points_by_curve[curve_i];
if (points.drop_front(1).drop_back(1).contains(i)) {
const int local_i = i - points.start();
return local_i % int(math::pow(2.0f, float(step - 1))) == 0;
}
return false;
});
}
static int curve_merge_by_distance(const IndexRange points,
const Span<float> distances,
const IndexMask &selection,
const float merge_distance,
MutableSpan<int> r_merge_indices)
{
/* We use a KDTree_1d here, because we can only merge neighboring points in the curves. */
KDTree_1d *tree = BLI_kdtree_1d_new(selection.size());
/* The selection is an IndexMask of the points just in this curve. */
selection.foreach_index_optimized<int64_t>([&](const int64_t i, const int64_t pos) {
BLI_kdtree_1d_insert(tree, pos, &distances[i - points.first()]);
});
BLI_kdtree_1d_balance(tree);
Array<int> selection_merge_indices(selection.size(), -1);
const int duplicate_count = BLI_kdtree_1d_calc_duplicates_fast(
tree, merge_distance, false, selection_merge_indices.data());
BLI_kdtree_1d_free(tree);
array_utils::fill_index_range<int>(r_merge_indices);
selection.foreach_index([&](const int src_index, const int pos) {
const int merge_index = selection_merge_indices[pos];
if (merge_index != -1) {
const int src_merge_index = selection[merge_index] - points.first();
r_merge_indices[src_index - points.first()] = src_merge_index;
}
});
return duplicate_count;
}
/* NOTE: The code here is an adapted version of #blender::geometry::point_merge_by_distance. */
static bke::CurvesGeometry curves_merge_by_distance(
const bke::CurvesGeometry &src_curves,
const float merge_distance,
const IndexMask &selection,
const bke::AnonymousAttributePropagationInfo &propagation_info)
{
const int src_point_size = src_curves.points_num();
if (src_point_size == 0) {
return {};
}
filedescriptor marked this conversation as resolved Outdated

Declare these const

Declare these const
const OffsetIndices<int> points_by_curve = src_curves.points_by_curve();
const VArray<bool> cyclic = src_curves.cyclic();
src_curves.ensure_evaluated_lengths();
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
std::atomic<int> total_duplicate_count = 0;
Array<Array<int>> merge_indices_per_curve(src_curves.curves_num());
threading::parallel_for(src_curves.curves_range(), 512, [&](const IndexRange range) {
for (const int curve_i : range) {
const IndexRange points = points_by_curve[curve_i];
merge_indices_per_curve[curve_i].reinitialize(points.size());
Array<float> distances_along_curve(points.size());
distances_along_curve.first() = 0.0f;
const Span<float> lengths = src_curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]);
distances_along_curve.as_mutable_span().drop_front(1).copy_from(lengths);
MutableSpan<int> merge_indices = merge_indices_per_curve[curve_i].as_mutable_span();
array_utils::fill_index_range<int>(merge_indices);
const int duplicate_count = curve_merge_by_distance(points,
distances_along_curve,
selection.slice_content(points),
merge_distance,
merge_indices);
/* Write the curve size. The counts will be accumulated to offsets below. */
dst_offsets[curve_i] = points.size() - duplicate_count;
total_duplicate_count += duplicate_count;
}
filedescriptor marked this conversation as resolved
Review

Create the curves above the counting, using bke::curves::copy_only_curve_domain, then fill in the offsets directly.

Currently I think this is losing curve domain attributes

Create the curves above the counting, using `bke::curves::copy_only_curve_domain`, then fill in the offsets directly. Currently I think this is losing curve domain attributes
});
const int dst_point_size = src_point_size - total_duplicate_count;
dst_curves.resize(dst_point_size, src_curves.curves_num());
offset_indices::accumulate_counts_to_offsets(dst_offsets);
int merged_points = 0;
Array<int> src_to_dst_indices(src_point_size);
for (const int curve_i : src_curves.curves_range()) {
Review

Any chance you could share this index manipulation with the point cloud merge by distance index manipulation? I remember that being a huge pain to get right, and it might be nice to avoid having to refactor it multiple times.

OTOH there's always the argument that sharing code with the legacy modifiers makes it harder to change, but maybe that's not so relevant here.

Any chance you could share this index manipulation with the point cloud merge by distance index manipulation? I remember that being a huge pain to get right, and it might be nice to avoid having to refactor it multiple times. OTOH there's always the argument that sharing code with the legacy modifiers makes it harder to change, but maybe that's not so relevant here.
Review

I would prefer a curves_merge_by_distance implementation in the geometry namespace then TBH. And I can work on that soonish when e.g. porting the edit mode operator to merge by distance.

I would prefer a `curves_merge_by_distance` implementation in the `geometry` namespace then TBH. And I can work on that soonish when e.g. porting the edit mode operator to merge by distance.
Review

I would extract the index mapping code from the point cloud stuff and just use that here. It could be in a separate header, or in the same point cloud header. My main concern is just not duplicating all this tricky code, even if we hope it's only temporary.

I didn't look to see how much of a direct copy this was though, maybe you changed some parts of it?

I would extract the index mapping code from the point cloud stuff and just use that here. It could be in a separate header, or in the same point cloud header. My main concern is just not duplicating all this tricky code, even if we hope it's only temporary. I didn't look to see how much of a direct copy this was though, maybe you changed some parts of it?
Review

It's mostly the same as far as I remember. I'll have a look at what could be reused and report back here.

It's mostly the same as far as I remember. I'll have a look at what could be reused and report back here.
Review

Hm this will be tricky to share because of merge_indices_per_curve it looks like. The logic is sort of the same but has one more level of nesting.

Hm this will be tricky to share because of `merge_indices_per_curve` it looks like. The logic is sort of the same but has one more level of nesting.
Review

Okay, no need to force it, we can just leave it as is maybe. Thanks for checking.

Okay, no need to force it, we can just leave it as is maybe. Thanks for checking.
const IndexRange points = points_by_curve[curve_i];
const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
for (const int i : points.index_range()) {
const int point_i = points.start() + i;
src_to_dst_indices[point_i] = point_i - merged_points;
if (merge_indices[i] != i) {
merged_points++;
}
}
}
Array<int> point_merge_counts(dst_point_size, 0);
for (const int curve_i : src_curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
for (const int i : points.index_range()) {
const int merge_index = merge_indices[i];
const int point_src = points.start() + merge_index;
const int dst_index = src_to_dst_indices[point_src];
point_merge_counts[dst_index]++;
}
}
Array<int> map_offsets_data(dst_point_size + 1);
map_offsets_data.as_mutable_span().drop_back(1).copy_from(point_merge_counts);
OffsetIndices<int> map_offsets = offset_indices::accumulate_counts_to_offsets(map_offsets_data);
point_merge_counts.fill(0);
Array<int> merge_map_indices(src_point_size);
for (const int curve_i : src_curves.curves_range()) {
const IndexRange points = points_by_curve[curve_i];
const Span<int> merge_indices = merge_indices_per_curve[curve_i].as_span();
for (const int i : points.index_range()) {
const int point_i = points.start() + i;
const int merge_index = merge_indices[i];
const int dst_index = src_to_dst_indices[points.start() + merge_index];
merge_map_indices[map_offsets[dst_index].first() + point_merge_counts[dst_index]] = point_i;
point_merge_counts[dst_index]++;
}
}
bke::AttributeAccessor src_attributes = src_curves.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
src_attributes.for_all([&](const bke::AttributeIDRef &id,
const bke::AttributeMetaData &meta_data) {
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
return true;
}
if (meta_data.domain != bke::AttrDomain::Point) {
return true;
}
bke::GAttributeReader src_attribute = src_attributes.lookup(id);
bke::attribute_math::convert_to_static_type(src_attribute.varray.type(), [&](auto dummy) {
using T = decltype(dummy);
if constexpr (!std::is_void_v<bke::attribute_math::DefaultMixer<T>>) {
bke::SpanAttributeWriter<T> dst_attribute =
dst_attributes.lookup_or_add_for_write_only_span<T>(id, bke::AttrDomain::Point);
VArraySpan<T> src = src_attribute.varray.typed<T>();
threading::parallel_for(dst_curves.points_range(), 1024, [&](IndexRange range) {
for (const int dst_point_i : range) {
/* Create a separate mixer for every point to avoid allocating temporary buffers
* in the mixer the size of the result curves and to improve memory locality. */
Review

@HooglyBoogly @mod_moder had a comment here in the PR that was merged by mistake: #118546 (comment).
What do you think?

@HooglyBoogly @mod_moder had a comment here in the PR that was merged by mistake: https://projects.blender.org/blender/blender/pulls/118546#issuecomment-1156080. What do you think?
Review

I think this is fine. It's using a size of one just to avoid unnecessary temporary array allocations. Maybe use an array is better but I wouldn't worry about it here.

I think this is fine. It's using a size of one just to avoid unnecessary temporary array allocations. Maybe use an array is better but I wouldn't worry about it here.

Thanks for remaind, didn't understand the idea of merging pr and opening another one\

Thanks for remaind, didn't understand the idea of merging pr and opening another one\
bke::attribute_math::DefaultMixer<T> mixer{dst_attribute.span.slice(dst_point_i, 1)};
Span<int> src_merge_indices = merge_map_indices.as_span().slice(
map_offsets[dst_point_i]);
for (const int src_point_i : src_merge_indices) {
mixer.mix_in(0, src[src_point_i]);
}
mixer.finalize();
}
});
dst_attribute.finish();
}
});
return true;
});
return dst_curves;
}
static void simplify_drawing(const GreasePencilSimplifyModifierData &mmd,
const Object &ob,
bke::greasepencil::Drawing &drawing)
{
IndexMaskMemory memory;
const bke::CurvesGeometry &curves = drawing.strokes();
const IndexMask strokes = modifier::greasepencil::get_filtered_stroke_mask(
&ob, curves, mmd.influence, memory);
if (strokes.is_empty()) {
return;
filedescriptor marked this conversation as resolved Outdated

Removing this newline is nice aesthetically since it puts the empty check right after the variable it's checking :)

Removing this newline is nice aesthetically since it puts the empty check right after the variable it's checking :)
}
switch (mmd.mode) {
case MOD_GREASE_PENCIL_SIMPLIFY_FIXED: {
const IndexMask points_to_keep = simplify_fixed(curves, mmd.step, memory);
drawing.strokes_for_write() = bke::curves_copy_point_selection(curves, points_to_keep, {});
break;
}
filedescriptor marked this conversation as resolved Outdated

If you have the energy, better to implement this in terms of curves_copy_point_selection to avoid the IndexMask complement inside of remove_points.

If you have the energy, better to implement this in terms of `curves_copy_point_selection` to avoid the `IndexMask` complement inside of `remove_points`.
case MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE: {
const IndexMask points_to_delete = geometry::simplify_curve_attribute(
curves.positions(),
strokes,
curves.points_by_curve(),
curves.cyclic(),
mmd.factor,
curves.positions(),
memory);
drawing.strokes_for_write().remove_points(points_to_delete, {});
break;
}
case MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE: {
drawing.strokes_for_write() = geometry::resample_to_length(
curves, strokes, VArray<float>::ForSingle(mmd.length, curves.curves_num()), {});
break;
filedescriptor marked this conversation as resolved Outdated

No need to move an R-Value

No need to move an R-Value
}
case MOD_GREASE_PENCIL_SIMPLIFY_MERGE: {
const OffsetIndices points_by_curve = curves.points_by_curve();
const Array<int> point_to_curve_map = curves.point_to_curve_map();
const IndexMask points = IndexMask::from_predicate(
curves.points_range(), GrainSize(2048), memory, [&](const int64_t i) {
const int curve_i = point_to_curve_map[i];
const IndexRange points = points_by_curve[curve_i];
if (points.drop_front(1).drop_back(1).contains(i)) {
return true;
}
return false;
});
drawing.strokes_for_write() = curves_merge_by_distance(curves, mmd.distance, points, {});
break;
}
filedescriptor marked this conversation as resolved Outdated

No need to std::move an R-value

No need to `std::move` an R-value
default:
break;
}
filedescriptor marked this conversation as resolved
Review

No need for the default here

No need for the default here
drawing.tag_topology_changed();
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
const auto *mmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
if (!geometry_set->has_grease_pencil()) {
return;
filedescriptor marked this conversation as resolved
Review

const

`const`
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
const int current_frame = grease_pencil.runtime->eval_frame;
IndexMaskMemory mask_memory;
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
grease_pencil, mmd->influence, mask_memory);
const Vector<bke::greasepencil::Drawing *> drawings =
modifier::greasepencil::get_drawings_for_write(grease_pencil, layer_mask, current_frame);
threading::parallel_for_each(drawings, [&](bke::greasepencil::Drawing *drawing) {
simplify_drawing(*mmd, *ctx->object, *drawing);
});
}
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
{
auto *mmd = reinterpret_cast<GreasePencilSimplifyModifierData *>(md);
modifier::greasepencil::foreach_influence_ID_link(&mmd->influence, ob, walk, user_data);
}
static void panel_draw(const bContext * /*C*/, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr);
int mode = RNA_enum_get(ptr, "mode");
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE);
if (mode == MOD_GREASE_PENCIL_SIMPLIFY_FIXED) {
uiItemR(layout, ptr, "step", UI_ITEM_NONE, nullptr, ICON_NONE);
}
else if (mode == MOD_GREASE_PENCIL_SIMPLIFY_ADAPTIVE) {
uiItemR(layout, ptr, "factor", UI_ITEM_NONE, nullptr, ICON_NONE);
}
else if (mode == MOD_GREASE_PENCIL_SIMPLIFY_SAMPLE) {
uiItemR(layout, ptr, "length", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "sharp_threshold", UI_ITEM_NONE, nullptr, ICON_NONE);
}
else if (mode == MOD_GREASE_PENCIL_SIMPLIFY_MERGE) {
uiItemR(layout, ptr, "distance", UI_ITEM_NONE, nullptr, ICON_NONE);
}
modifier_panel_end(layout, ptr);
}
static void panel_register(ARegionType *region_type)
{
modifier_panel_register(region_type, eModifierType_GreasePencilSimplify, panel_draw);
}
} // namespace blender
ModifierTypeInfo modifierType_GreasePencilSimplify = {
/*idname*/ "GreasePencilSimplifyModifier",
/*name*/ N_("Simplify"),
/*struct_name*/ "GreasePencilSimplifyModifierData",
/*struct_size*/ sizeof(GreasePencilSimplifyModifierData),
/*srna*/ &RNA_GreasePencilSimplifyModifier,
/*type*/ ModifierTypeType::Nonconstructive,
/*flags*/
eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
eModifierTypeFlag_EnableInEditmode,
/*icon*/ ICON_MOD_SIMPLIFY,
/*copy_data*/ blender::copy_data,
/*deform_verts*/ nullptr,
/*deform_matrices*/ nullptr,
/*deform_verts_EM*/ nullptr,
/*deform_matrices_EM*/ nullptr,
/*modify_mesh*/ nullptr,
/*modify_geometry_set*/ blender::modify_geometry_set,
/*init_data*/ blender::init_data,
/*required_data_mask*/ nullptr,
/*free_data*/ blender::free_data,
/*is_disabled*/ nullptr,
/*update_depsgraph*/ nullptr,
/*depends_on_time*/ nullptr,
/*depends_on_normals*/ nullptr,
/*foreach_ID_link*/ blender::foreach_ID_link,
/*foreach_tex_link*/ nullptr,
/*free_runtime_data*/ nullptr,
/*panel_register*/ blender::panel_register,
/*blend_write*/ blender::blend_write,
/*blend_read*/ blender::blend_read,
/*foreach_cache*/ nullptr,
};

View File

@ -284,6 +284,7 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(GreasePencilLineart);
INIT_TYPE(GreasePencilArmature);
INIT_TYPE(GreasePencilTime);
INIT_TYPE(GreasePencilSimplify);
INIT_TYPE(GreasePencilEnvelope);
INIT_TYPE(GreasePencilOutline);
INIT_TYPE(GreasePencilShrinkwrap);