GPv3: Envelope Modifier

Ports the Envelope modifier from GPv2.

Pull Request: https://projects.blender.org/blender/blender/pulls/118272
This commit is contained in:
Lukas Tönne
2024-03-01 10:42:41 +01:00
committed by Falk David
parent bcb0d6ef9f
commit 68c12fe0ec
10 changed files with 933 additions and 1 deletions
@@ -156,6 +156,7 @@ class OBJECT_MT_modifier_add_generate(ModifierAddMenu, Menu):
if ob_type == 'GREASEPENCIL':
self.operator_modifier_add(layout, 'GREASE_PENCIL_ARRAY')
self.operator_modifier_add(layout, 'GREASE_PENCIL_DASH')
self.operator_modifier_add(layout, 'GREASE_PENCIL_ENVELOPE')
self.operator_modifier_add(layout, 'GREASE_PENCIL_LENGTH')
self.operator_modifier_add(layout, 'GREASE_PENCIL_MIRROR')
self.operator_modifier_add(layout, 'GREASE_PENCIL_MULTIPLY')
@@ -961,6 +961,45 @@ static void legacy_object_modifier_dash(Object &object, GpencilModifierData &leg
false);
}
static void legacy_object_modifier_envelope(Object &object, GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
object, eModifierType_GreasePencilEnvelope, legacy_md);
auto &md_envelope = reinterpret_cast<GreasePencilEnvelopeModifierData &>(md);
auto &legacy_md_envelope = reinterpret_cast<EnvelopeGpencilModifierData &>(legacy_md);
switch (eEnvelopeGpencil_Mode(legacy_md_envelope.mode)) {
case GP_ENVELOPE_DEFORM:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_DEFORM;
break;
case GP_ENVELOPE_SEGMENTS:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS;
break;
case GP_ENVELOPE_FILLS:
md_envelope.mode = MOD_GREASE_PENCIL_ENVELOPE_FILLS;
break;
}
md_envelope.mat_nr = legacy_md_envelope.mat_nr;
md_envelope.thickness = legacy_md_envelope.thickness;
md_envelope.strength = legacy_md_envelope.strength;
md_envelope.skip = legacy_md_envelope.skip;
md_envelope.spread = legacy_md_envelope.spread;
legacy_object_modifier_influence(md_envelope.influence,
legacy_md_envelope.layername,
legacy_md_envelope.layer_pass,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_LAYER,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_LAYERPASS,
&legacy_md_envelope.material,
legacy_md_envelope.pass_index,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_MATERIAL,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_PASS,
legacy_md_envelope.vgname,
legacy_md_envelope.flag & GP_ENVELOPE_INVERT_VGROUP,
nullptr,
false);
}
static void legacy_object_modifier_hook(Object &object, GpencilModifierData &legacy_md)
{
ModifierData &md = legacy_object_modifier_common(
@@ -1631,6 +1670,9 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
case eGpencilModifierType_Dash:
legacy_object_modifier_dash(object, *gpd_md);
break;
case eGpencilModifierType_Envelope:
legacy_object_modifier_envelope(object, *gpd_md);
break;
case eGpencilModifierType_Hook:
legacy_object_modifier_hook(object, *gpd_md);
break;
@@ -1684,7 +1726,6 @@ static void legacy_object_modifiers(Main & /*bmain*/, Object &object)
legacy_object_modifier_weight_lineart(object, *gpd_md);
break;
case eGpencilModifierType_Shrinkwrap:
case eGpencilModifierType_Envelope:
case eGpencilModifierType_Outline:
break;
}
@@ -1011,4 +1011,14 @@
.segment_repeat = 1, \
}
#define _DNA_DEFAULT_GreasePencilEnvelopeModifierData \
{ \
.spread = 10, \
.mode = MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS, \
.mat_nr = -1, \
.thickness = 1.0f, \
.strength = 1.0f, \
.skip = 0, \
}
/* clang-format off */
@@ -115,6 +115,7 @@ typedef enum ModifierType {
eModifierType_GreasePencilLineart = 78,
eModifierType_GreasePencilArmature = 79,
eModifierType_GreasePencilTime = 80,
eModifierType_GreasePencilEnvelope = 81,
NUM_MODIFIER_TYPES,
} ModifierType;
@@ -3225,3 +3226,27 @@ typedef enum GreasePencilTimeModifierSegmentMode {
MOD_GREASE_PENCIL_TIME_SEG_MODE_REVERSE = 1,
MOD_GREASE_PENCIL_TIME_SEG_MODE_PINGPONG = 2,
} GreasePencilTimeModifierSegmentMode;
typedef struct GreasePencilEnvelopeModifierData {
ModifierData modifier;
GreasePencilModifierInfluenceData influence;
/* #GreasePencilEnvelopeModifierMode. */
int mode;
/** Material for the new strokes. */
int mat_nr;
/** Thickness multiplier for the new strokes. */
float thickness;
/** Strength multiplier for the new strokes. */
float strength;
/** Number of points to skip over. */
int skip;
/* Length of the envelope effect. */
int spread;
} GreasePencilEnvelopeModifierData;
/* Texture->mode */
typedef enum GreasePencilEnvelopeModifierMode {
MOD_GREASE_PENCIL_ENVELOPE_DEFORM = 0,
MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS = 1,
MOD_GREASE_PENCIL_ENVELOPE_FILLS = 2,
} GreasePencilEnvelopeModifierMode;
@@ -349,6 +349,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(GreasePencilEnvelopeModifierData);
#undef SDNA_DEFAULT_DECL_STRUCT
@@ -617,6 +618,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(GreasePencilArmatureModifierData),
SDNA_DEFAULT_DECL(GreasePencilTimeModifierSegment),
SDNA_DEFAULT_DECL(GreasePencilTimeModifierData),
SDNA_DEFAULT_DECL(GreasePencilEnvelopeModifierData),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX
@@ -259,6 +259,11 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
ICON_MOD_SUBSURF,
"Subdivide strokes",
"Grease Pencil subdivide modifier"},
{eModifierType_GreasePencilEnvelope,
"GREASE_PENCIL_ENVELOPE",
ICON_MOD_ENVELOPE,
"Envelope",
"Create an envelope shape"},
RNA_ENUM_ITEM_HEADING(N_("Deform"), nullptr),
{eModifierType_Armature,
@@ -1948,6 +1953,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(GreasePencilEnvelope);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOffset);
RNA_MOD_GREASE_PENCIL_VERTEX_GROUP_SET(GreasePencilOpacity);
@@ -1960,6 +1966,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(GreasePencilEnvelope);
static void rna_GreasePencilLineartModifier_material_set(PointerRNA *ptr,
PointerRNA value,
@@ -10105,6 +10112,89 @@ static void rna_def_modifier_grease_pencil_time(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_grease_pencil_envelope(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
static const EnumPropertyItem envelope_mode_items[] = {
{MOD_GREASE_PENCIL_ENVELOPE_DEFORM,
"DEFORM",
0,
"Deform",
"Deform the stroke to best match the envelope shape"},
{MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS,
"SEGMENTS",
0,
"Segments",
"Add segments to create the envelope. Keep the original stroke"},
{MOD_GREASE_PENCIL_ENVELOPE_FILLS,
"FILLS",
0,
"Fills",
"Add fill segments to create the envelope. Don't keep the original stroke"},
{0, nullptr, 0, nullptr, nullptr},
};
srna = RNA_def_struct(brna, "GreasePencilEnvelopeModifier", "Modifier");
RNA_def_struct_ui_text(
srna, "Grease Pencil Envelope Modifier", "AEnvelope stroke effect modifier");
RNA_def_struct_sdna(srna, "GreasePencilEnvelopeModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_ENVELOPE);
rna_def_modifier_grease_pencil_layer_filter(srna);
rna_def_modifier_grease_pencil_material_filter(
srna, "rna_GreasePencilEnvelopeModifier_material_filter_set");
rna_def_modifier_grease_pencil_vertex_group(
srna, "rna_GreasePencilEnvelopeModifier_vertex_group_name_set");
rna_def_modifier_panel_open_prop(srna, "open_influence_panel", 0);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "mode");
RNA_def_property_enum_items(prop, envelope_mode_items);
RNA_def_property_ui_text(prop, "Mode", "Algorithm to use for generating the envelope");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "spread", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "spread");
RNA_def_property_range(prop, 1, INT_MAX);
RNA_def_property_ui_text(
prop, "Spread Length", "The number of points to skip to create straight segments");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "mat_nr", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "mat_nr");
RNA_def_property_range(prop, -1, INT16_MAX);
RNA_def_property_ui_text(prop, "Material Index", "The material to use for the new strokes");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "thickness", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, nullptr, "thickness");
RNA_def_property_range(prop, 0, FLT_MAX);
RNA_def_property_ui_range(prop, 0, 1, 10, 3);
RNA_def_property_ui_text(prop, "Thickness", "Multiplier for the thickness of the new strokes");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, nullptr, "strength");
RNA_def_property_range(prop, 0, FLT_MAX);
RNA_def_property_ui_range(prop, 0, 1, 10, 3);
RNA_def_property_ui_text(prop, "Strength", "Multiplier for the strength of the new strokes");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "skip", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "skip");
RNA_def_property_range(prop, 0, INT_MAX);
RNA_def_property_ui_text(
prop, "Skip Segments", "The number of generated segments to skip to reduce complexity");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
RNA_define_lib_overridable(false);
}
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -10294,6 +10384,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_envelope(brna);
}
#endif
+1
View File
@@ -53,6 +53,7 @@ set(SRC
intern/MOD_grease_pencil_array.cc
intern/MOD_grease_pencil_color.cc
intern/MOD_grease_pencil_dash.cc
intern/MOD_grease_pencil_envelope.cc
intern/MOD_grease_pencil_hook.cc
intern/MOD_grease_pencil_lattice.cc
intern/MOD_grease_pencil_length.cc
@@ -93,6 +93,7 @@ extern ModifierTypeInfo modifierType_GreasePencilHook;
extern ModifierTypeInfo modifierType_GreasePencilLineart;
extern ModifierTypeInfo modifierType_GreasePencilArmature;
extern ModifierTypeInfo modifierType_GreasePencilTime;
extern ModifierTypeInfo modifierType_GreasePencilEnvelope;
/* MOD_util.cc */
@@ -0,0 +1,759 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup modifiers
*/
#include "DNA_defaults.h"
#include "DNA_modifier_types.h"
#include "BLI_math_geom.h"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_lib_query.hh"
#include "BKE_material.h"
#include "BKE_modifier.hh"
#include "BKE_screen.hh"
#include "BLO_read_write.hh"
#include "GEO_realize_instances.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "BLT_translation.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "MOD_grease_pencil_util.hh"
#include "MOD_modifiertypes.hh"
#include "MOD_ui_common.hh"
namespace blender {
static void init_data(ModifierData *md)
{
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(emd, modifier));
MEMCPY_STRUCT_AFTER(emd, DNA_struct_default_get(GreasePencilEnvelopeModifierData), modifier);
modifier::greasepencil::init_influence_data(&emd->influence, false);
}
static void copy_data(const ModifierData *md, ModifierData *target, const int flag)
{
const auto *emd = reinterpret_cast<const GreasePencilEnvelopeModifierData *>(md);
auto *temd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(target);
modifier::greasepencil::free_influence_data(&temd->influence);
BKE_modifier_copydata_generic(md, target, flag);
modifier::greasepencil::copy_influence_data(&emd->influence, &temd->influence, flag);
}
static void free_data(ModifierData *md)
{
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
modifier::greasepencil::free_influence_data(&emd->influence);
}
static void foreach_ID_link(ModifierData *md, Object *ob, IDWalkFunc walk, void *user_data)
{
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
modifier::greasepencil::foreach_influence_ID_link(&emd->influence, ob, walk, user_data);
}
static inline float3 calculate_plane(const float3 &center, const float3 &prev, const float3 &next)
{
const float3 v1 = math::normalize(prev - center);
const float3 v2 = math::normalize(next - center);
return math::normalize(v1 - v2);
}
static inline std::optional<float3> find_plane_intersection(const float3 &plane_point,
const float3 &plane_normal,
const float3 &from,
const float3 &to)
{
const float lambda = line_plane_factor_v3(plane_point, plane_normal, from, to);
if (lambda <= 0.0f || lambda >= 1.0f) {
return std::nullopt;
}
return math::interpolate(from, to, lambda);
}
/* "Infinite" radius in case no limit is applied. */
static const float unlimited_radius = FLT_MAX;
/**
* Compute the minimal radius of a circle centered on the direction vector,
* going through the origin and touching the line (p1, p2).
*
* Use plane-conic-intersections to choose the minimal radius.
* The conic is defined in 4D as f({x,y,z,t}) = x*x + y*y + z*z - t*t = 0
* Then a plane is defined parametrically as
* {p}(u, v) = {p1,0}*u + {p2,0}*(1-u) + {dir,1}*v with 0 <= u <= 1 and v >= 0
* Now compute the intersection point with the smallest t.
* To do so, compute the parameters u, v such that f(p(u, v)) = 0 and v is minimal.
* This can be done analytically and the solution is:
* u = -dot(p2,dir) / dot(p1-p2, dir) +/- sqrt((dot(p2,dir) / dot(p1-p2, dir))^2 -
* (2*dot(p1-p2,p2)*dot(p2,dir)-dot(p2,p2)*dot(p1-p2,dir))/(dot(p1-p2,dir)*dot(p1-p2,p1-p2)));
* v = ({p1}u + {p2}*(1-u))^2 / (2*(dot(p1,dir)*u + dot(p2,dir)*(1-u)));
*/
static float calc_min_radius_v3v3(const float3 &p1, const float3 &p2, const float3 &dir)
{
const float p1_dir = math::dot(p1, dir);
const float p2_dir = math::dot(p2, dir);
const float p2_sqr = math::length_squared(p2);
const float diff_dir = p1_dir - p2_dir;
const float u = [=]() {
if (diff_dir == 0.0f) {
const float p1_sqr = math::length_squared(p1);
return p1_sqr < p2_sqr ? 1.0f : 0.0f;
}
const float p = p2_dir / diff_dir;
const float3 diff = p1 - p2;
const float diff_sqr = math::length_squared(diff);
const float diff_p2 = math::dot(diff, p2);
const float q = (2 * diff_p2 * p2_dir - p2_sqr * diff_dir) / (diff_dir * diff_sqr);
if (p * p - q < 0) {
return 0.5f - std::copysign(0.5f, p);
}
return math::clamp(-p - math::sqrt(p * p - q) * std::copysign(1.0f, p), 0.0f, 1.0f);
}();
/* v is the determined minimal radius. In case p1 and p2 are the same, there is a
* simple proof for the following formula using the geometric mean theorem and Thales theorem. */
const float v = math::length_squared(math::interpolate(p2, p1, u)) /
(2 * math::interpolate(p2_dir, p1_dir, u));
if (v < 0 || !isfinite(v)) {
/* No limit to the radius from this segment. */
return unlimited_radius;
}
return v;
}
static float calc_radius_limit(const Span<float3> positions,
const bool is_cyclic,
const int spread,
const int point,
const float3 &direction)
{
if (math::is_zero(direction)) {
return unlimited_radius;
}
const int point_num = positions.size();
const float3 &center = positions[point];
float result = unlimited_radius;
if (is_cyclic) {
/* Spread should be limited to half the points in the cyclic case. */
BLI_assert(spread <= point_num / 2);
/* Left side. */
for (const int line_i : IndexRange(spread)) {
const int from_i = (point - line_i - 2 + point_num) % point_num;
const int to_i = (point - line_i - 1 + point_num) % point_num;
const float limit = calc_min_radius_v3v3(
positions[from_i] - center, positions[to_i] - center, direction);
result = std::min(result, limit);
}
/* Right side. */
for (const int line_i : IndexRange(spread)) {
const int from_i = (point + line_i + 1 + point_num) % point_num;
const int to_i = (point + line_i + 2 + point_num) % point_num;
const float limit = calc_min_radius_v3v3(
positions[from_i] - center, positions[to_i] - center, direction);
result = std::min(result, limit);
}
}
else {
if (point == 0 || point >= point_num - 1) {
return unlimited_radius;
}
/* Left side. */
const int spread_left = std::min(spread, std::max(point - 2, 0));
for (const int line_i : IndexRange(spread_left)) {
const int from_i = std::max(point - line_i - 2, 0);
const int to_i = std::max(point - line_i - 1, 0);
const float limit = calc_min_radius_v3v3(
positions[from_i] - center, positions[to_i] - center, direction);
result = std::min(result, limit);
}
/* Right side. */
const int spread_right = std::min(spread, std::max(point_num - point - 2, 0));
for (const int line_i : IndexRange(spread_right)) {
const int from_i = std::min(point + line_i + 1, point_num - 1);
const int to_i = std::min(point + line_i + 2, point_num - 1);
const float limit = calc_min_radius_v3v3(
positions[from_i] - center, positions[to_i] - center, direction);
result = std::min(result, limit);
}
}
return result;
}
/**
* Find a suitable center and radius to enclose the envelope around a point.
*/
static bool find_envelope(const Span<float3> positions,
const bool is_cyclic,
const int spread,
const int point,
float3 &r_center,
float &r_radius)
{
/* Compute a plane normal for intersections. */
const IndexRange points = positions.index_range();
const float3 &pos = positions[point];
const float3 &prev_pos =
positions[points.contains(point - 1) ? point - 1 : (is_cyclic ? points.last() : point)];
const float3 &next_pos =
positions[points.contains(point + 1) ? point + 1 : (is_cyclic ? points.first() : point)];
const float3 plane_normal = calculate_plane(pos, prev_pos, next_pos);
if (math::is_zero(plane_normal)) {
return false;
}
/* Find two intersections with maximal radii. */
float max_distance1 = 0.0f;
float max_distance2 = 0.0f;
float3 intersect1 = pos;
float3 intersect2 = pos;
for (const int line_i : IndexRange(spread + 2)) {
/* Raw indices, can be out of range. */
const int from_spread_i = point - spread - 1 + line_i;
const int to_spread_i = point + line_i;
/* Clamp or wrap to valid indices. */
const int from_i = is_cyclic ? (from_spread_i + points.size()) % points.size() :
std::max(from_spread_i, int(points.first()));
const int to_i = is_cyclic ? (to_spread_i + points.size()) % points.size() :
std::min(to_spread_i, int(points.last()));
const float3 &from_pos = positions[from_i];
const float3 &to_pos = positions[to_i];
const float3 line_delta = to_pos - from_pos;
const std::optional<float3> line_intersect = find_plane_intersection(
pos, plane_normal, from_pos, to_pos);
if (!line_intersect) {
continue;
}
const float3 line_direction = line_intersect.value() - pos;
const float line_distance = math::length(line_direction);
/* Diameter of a sphere centered in the plane, touching both #pos and the intersection line. */
const float cos_angle = math::abs(math::dot(plane_normal, line_delta)) /
math::length(line_delta);
const float diameter = line_distance * 2.0f * cos_angle / (1 + cos_angle);
if (line_i == 0) {
max_distance1 = diameter;
intersect1 = line_intersect.value();
continue;
}
/* Use as vector 1 or 2 based on primary direction. */
if (math::dot(intersect1 - pos, line_direction) >= 0.0f) {
if (diameter > max_distance1) {
intersect1 = line_intersect.value();
max_distance1 = diameter;
}
}
else {
if (diameter > max_distance2) {
intersect2 = line_intersect.value();
max_distance2 = diameter;
}
}
}
r_radius = 0.5f * (max_distance1 + max_distance2);
if (r_radius < FLT_EPSILON) {
return false;
}
const float3 new_center = 0.5f * (intersect1 + intersect2);
/* Apply radius limiting to not cross existing lines. */
const float3 dir = math::normalize(new_center - pos);
r_radius = std::min(r_radius, calc_radius_limit(positions, is_cyclic, spread, point, dir));
r_center = math::interpolate(
pos, new_center, 2.0f * r_radius / math::distance(intersect1, intersect2));
return true;
}
static void deform_drawing_as_envelope(const GreasePencilEnvelopeModifierData &emd,
bke::greasepencil::Drawing &drawing,
const IndexMask &curves_mask)
{
bke::CurvesGeometry &curves = drawing.strokes_for_write();
const bke::AttributeAccessor attributes = curves.attributes();
const MutableSpan<float3> positions = curves.positions_for_write();
const MutableSpan<float> radii = drawing.radii_for_write();
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
const VArray<float> vgroup_weights = modifier::greasepencil::get_influence_vertex_weights(
curves, emd.influence);
const VArray<bool> cyclic_flags = *attributes.lookup_or_default(
"cyclic", bke::AttrDomain::Curve, false);
/* Cache to avoid affecting neighboring point results when updating positions. */
const Array<float3> old_positions(positions.as_span());
curves_mask.foreach_index(GrainSize(512), [&](const int64_t curve_i) {
const IndexRange points = points_by_curve[curve_i];
const bool cyclic = cyclic_flags[curve_i];
const int point_num = points.size();
const int spread = cyclic ? (math::abs(((emd.spread + point_num / 2) % point_num) -
point_num / 2)) :
std::min(emd.spread, point_num - 1);
for (const int64_t i : points.index_range()) {
const int64_t point_i = points[i];
const float weight = vgroup_weights[point_i];
float3 envelope_center;
float envelope_radius;
if (!find_envelope(old_positions.as_span().slice(points),
cyclic,
spread,
i,
envelope_center,
envelope_radius))
{
continue;
}
const float target_radius = radii[point_i] * emd.thickness + envelope_radius;
radii[point_i] = math::interpolate(radii[point_i], target_radius, weight);
positions[point_i] = math::interpolate(old_positions[point_i], envelope_center, weight);
}
});
drawing.tag_positions_changed();
curves.tag_radii_changed();
}
struct EnvelopeInfo {
/* Offset left and right from the source point. */
int spread;
/* Number of points to skip. */
int skip;
/* Number of points in each envelope stroke. */
int points_per_curve;
/* Material index assigned to new strokes. */
int material_index;
float thickness;
float strength;
};
static EnvelopeInfo get_envelope_info(const GreasePencilEnvelopeModifierData &emd,
const ModifierEvalContext &ctx)
{
EnvelopeInfo info;
info.spread = emd.spread;
info.skip = emd.skip;
switch (GreasePencilEnvelopeModifierMode(emd.mode)) {
case MOD_GREASE_PENCIL_ENVELOPE_DEFORM:
info.points_per_curve = 0;
break;
case MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS:
info.points_per_curve = 2;
break;
case MOD_GREASE_PENCIL_ENVELOPE_FILLS:
info.points_per_curve = 2 * (2 + emd.skip);
break;
}
info.material_index = std::min(emd.mat_nr, ctx.object->totcol - 1);
info.thickness = emd.thickness;
info.strength = emd.strength;
return info;
}
static int curve_spread(const EnvelopeInfo &info, const int point_num, const bool is_cyclic_curve)
{
/* Clamp spread in the cyclic case to half the curve size. */
return is_cyclic_curve ? std::min(info.spread, point_num / 2) : info.spread;
}
static int curve_envelope_strokes_num(const EnvelopeInfo &info,
const int point_num,
const bool is_cyclic_curve)
{
const int spread = curve_spread(info, point_num, is_cyclic_curve);
/* Number of envelope strokes making up the envelope. */
const int num_strokes = point_num + spread - 1;
/* Skip strokes (only every n-th point generates strokes). */
const int num_strokes_simplified = (num_strokes + info.skip) / (1 + info.skip);
return num_strokes_simplified;
}
/**
* Create a single stroke as part of the envelope.
*
* In the simplest cast creates a single edge.
* Example for spread 4:
*
* (p-5) (p-4) (p-3) (p-2) (p-1) ( p ) (p+1) (p+2) (p+3) (p+4) (p+5)
* ---------------------------
*
* If fills is true a closed curve is created that connects contiguous point ranges.
* Example (skip=0):
*
* (p-5) (p-4) (p-3) (p-2) (p-1) ( p ) (p+1) (p+2) (p+3) (p+4) (p+5)
* | --- --------------------- --- |
* | |
* -------------------------------------
*
* If skip > 0 more points are included in the range.
* Example (skip=2):
*
* (p-5) (p-4) (p-3) (p-2) (p-1) ( p ) (p+1) (p+2) (p+3) (p+4) (p+5)
* | --- --- --- --------- --- --- --- |
* | |
* -------------------------------------------------
*/
static void create_envelope_stroke_for_point(const IndexRange src_curve_points,
const bool src_curve_cyclic,
const int point,
const int spread,
const int base_length,
const MutableSpan<int> point_src_indices)
{
const int point_num = src_curve_points.size();
BLI_assert(point_src_indices.size() == base_length * 2);
/* Clamp or wrap to ensure a valid index. */
auto get_index = [=](const int index) -> int {
return src_curve_cyclic ? (index + point_num) % point_num :
math::clamp(index, 0, point_num - 1);
};
for (const int i : IndexRange(base_length)) {
const int reverse_i = base_length - 1 - i;
const int point_left = get_index(point - spread + reverse_i);
const int point_right = get_index(point + reverse_i);
point_src_indices[i] = src_curve_points[point_left];
point_src_indices[base_length + i] = src_curve_points[point_right];
}
}
static void create_envelope_strokes_for_curve(const EnvelopeInfo &info,
const int src_curve_index,
const IndexRange src_curve_points,
const bool src_curve_cyclic,
const VArray<int> &src_material_indices,
const IndexRange dst_points,
const MutableSpan<int> curve_offsets,
const MutableSpan<int> material_indices,
const MutableSpan<int> curve_src_indices,
const MutableSpan<int> point_src_indices)
{
const int src_point_num = src_curve_points.size();
const int spread = curve_spread(info, src_point_num, src_curve_cyclic);
const int num_strokes = curve_envelope_strokes_num(info, src_point_num, src_curve_cyclic);
const bool use_fills = info.points_per_curve > 2;
/* Length of continuous point ranges that get connected. */
const int base_length = use_fills ? 2 + info.skip : 1;
BLI_assert(curve_offsets.size() == num_strokes);
BLI_assert(material_indices.size() == num_strokes);
BLI_assert(curve_src_indices.size() == num_strokes);
BLI_assert(point_src_indices.size() == num_strokes * info.points_per_curve);
curve_src_indices.fill(src_curve_index);
/*
* Index range here goes beyond the point range:
* This adds points [i - spread, i + 1] as a curve.
* The total range covers [-spread - 1, spread + 1].
* Each span only gets added once since it repeats for neighboring points.
*/
for (const int i : IndexRange(num_strokes)) {
const IndexRange dst_envelope_points = {i * info.points_per_curve, info.points_per_curve};
curve_offsets[i] = dst_points[dst_envelope_points.start()];
material_indices[i] = info.material_index >= 0 ? info.material_index :
src_material_indices[src_curve_index];
create_envelope_stroke_for_point(src_curve_points,
src_curve_cyclic,
i,
spread,
base_length,
point_src_indices.slice(dst_envelope_points));
}
}
static void create_envelope_strokes(const EnvelopeInfo &info,
bke::greasepencil::Drawing &drawing,
const IndexMask &curves_mask,
const bool keep_original)
{
const bke::CurvesGeometry &src_curves = drawing.strokes();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
const VArray<bool> src_cyclic = *src_attributes.lookup_or_default(
"cyclic", bke::AttrDomain::Curve, false);
const VArray<int> src_material_indices = *src_attributes.lookup_or_default(
"material_index", bke::AttrDomain::Curve, 0);
/* Count envelopes. */
Array<int> envelope_curves_by_curve(src_curves.curve_num + 1);
Array<int> envelope_points_by_curve(src_curves.curve_num + 1);
curves_mask.foreach_index([&](const int64_t src_curve_i) {
const IndexRange points = src_curves.points_by_curve()[src_curve_i];
const int curve_num = curve_envelope_strokes_num(info, points.size(), src_cyclic[src_curve_i]);
envelope_curves_by_curve[src_curve_i] = curve_num;
envelope_points_by_curve[src_curve_i] = info.points_per_curve * curve_num;
});
/* Ranges by source curve for envelope curves and points. */
const OffsetIndices envelope_curve_offsets = offset_indices::accumulate_counts_to_offsets(
envelope_curves_by_curve, keep_original ? src_curves.curve_num : 0);
const OffsetIndices envelope_point_offsets = offset_indices::accumulate_counts_to_offsets(
envelope_points_by_curve, keep_original ? src_curves.point_num : 0);
const int dst_curve_num = envelope_curve_offsets.total_size();
const int dst_point_num = envelope_point_offsets.total_size();
if (dst_curve_num == 0 || dst_point_num == 0) {
return;
}
bke::CurvesGeometry dst_curves(dst_point_num, dst_curve_num);
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
bke::SpanAttributeWriter<int> dst_material_indices =
dst_attributes.lookup_or_add_for_write_span<int>("material_index", bke::AttrDomain::Curve);
bke::SpanAttributeWriter<bool> dst_cyclic = dst_attributes.lookup_or_add_for_write_span<bool>(
"cyclic", bke::AttrDomain::Curve);
/* Map each destination curve and point to its source. */
Array<int> src_curve_indices(dst_curve_num);
Array<int> src_point_indices(dst_point_num);
if (keep_original) {
/* Add indices to original data. */
dst_curves.offsets_for_write()
.slice(src_curves.curves_range())
.copy_from(src_curves.offsets().drop_back(1));
array_utils::fill_index_range(
src_curve_indices.as_mutable_span().slice(src_curves.curves_range()));
array_utils::fill_index_range(
src_point_indices.as_mutable_span().slice(src_curves.points_range()));
array_utils::copy(src_material_indices,
dst_material_indices.span.slice(src_curves.curves_range()));
}
curves_mask.foreach_index([&](const int64_t i) {
const bool src_curve_cyclic = src_cyclic[i];
const IndexRange src_curve_points = src_curves.points_by_curve()[i];
const IndexRange envelope_curves = envelope_curve_offsets[i];
const IndexRange envelope_points = envelope_point_offsets[i];
create_envelope_strokes_for_curve(info,
i,
src_curve_points,
src_curve_cyclic,
src_material_indices,
envelope_points,
dst_curves.offsets_for_write().slice(envelope_curves),
dst_material_indices.span.slice(envelope_curves),
src_curve_indices.as_mutable_span().slice(envelope_curves),
src_point_indices.as_mutable_span().slice(envelope_points));
});
dst_curves.offsets_for_write().last() = dst_point_num;
bke::gather_attributes(
src_attributes, bke::AttrDomain::Point, {}, {}, src_point_indices, dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Curve,
{},
{"cyclic", "material_index"},
src_curve_indices,
dst_attributes);
/* Apply thickness and strength factors. */
{
bke::SpanAttributeWriter<float> radius_writer =
dst_attributes.lookup_or_add_for_write_span<float>(
"radius",
bke::AttrDomain::Point,
bke::AttributeInitVArray(VArray<float>::ForSingle(0.01f, dst_point_num)));
bke::SpanAttributeWriter<float> opacity_writer =
dst_attributes.lookup_or_add_for_write_span<float>(
"opacity",
bke::AttrDomain::Point,
bke::AttributeInitVArray(VArray<float>::ForSingle(1.0f, dst_point_num)));
const IndexRange all_new_points = keep_original ?
IndexRange(src_curves.point_num,
dst_point_num - src_curves.point_num) :
IndexRange(dst_point_num);
for (const int point_i : all_new_points) {
radius_writer.span[point_i] *= info.thickness;
opacity_writer.span[point_i] *= info.strength;
}
radius_writer.finish();
opacity_writer.finish();
}
dst_cyclic.finish();
dst_material_indices.finish();
dst_curves.update_curve_types();
drawing.strokes_for_write() = std::move(dst_curves);
drawing.tag_topology_changed();
}
static void modify_drawing(const GreasePencilEnvelopeModifierData &emd,
const ModifierEvalContext &ctx,
bke::greasepencil::Drawing &drawing)
{
const EnvelopeInfo info = get_envelope_info(emd, ctx);
IndexMaskMemory mask_memory;
const IndexMask curves_mask = modifier::greasepencil::get_filtered_stroke_mask(
ctx.object, drawing.strokes(), emd.influence, mask_memory);
const auto mode = GreasePencilEnvelopeModifierMode(emd.mode);
switch (mode) {
case MOD_GREASE_PENCIL_ENVELOPE_DEFORM:
deform_drawing_as_envelope(emd, drawing, curves_mask);
break;
case MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS:
create_envelope_strokes(info, drawing, curves_mask, true);
break;
case MOD_GREASE_PENCIL_ENVELOPE_FILLS:
create_envelope_strokes(info, drawing, curves_mask, false);
break;
}
}
static void modify_geometry_set(ModifierData *md,
const ModifierEvalContext *ctx,
bke::GeometrySet *geometry_set)
{
using bke::greasepencil::Drawing;
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
if (!geometry_set->has_grease_pencil()) {
return;
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
const int frame = grease_pencil.runtime->eval_frame;
IndexMaskMemory mask_memory;
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
grease_pencil, emd->influence, mask_memory);
const Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
grease_pencil, layer_mask, frame);
threading::parallel_for_each(drawings,
[&](Drawing *drawing) { modify_drawing(*emd, *ctx, *drawing); });
}
static void panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA ob_ptr;
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
const GreasePencilEnvelopeModifierMode mode = GreasePencilEnvelopeModifierMode(
RNA_enum_get(ptr, "mode"));
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "mode", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "spread", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "thickness", UI_ITEM_NONE, nullptr, ICON_NONE);
switch (mode) {
case MOD_GREASE_PENCIL_ENVELOPE_DEFORM:
break;
case MOD_GREASE_PENCIL_ENVELOPE_FILLS:
case MOD_GREASE_PENCIL_ENVELOPE_SEGMENTS:
uiItemR(layout, ptr, "strength", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "mat_nr", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, ptr, "skip", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
}
if (uiLayout *influence_panel = uiLayoutPanelProp(
C, layout, ptr, "open_influence_panel", "Influence"))
{
modifier::greasepencil::draw_layer_filter_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_material_filter_settings(C, influence_panel, ptr);
modifier::greasepencil::draw_vertex_group_settings(C, influence_panel, ptr);
}
modifier_panel_end(layout, ptr);
}
static void panel_register(ARegionType *region_type)
{
modifier_panel_register(region_type, eModifierType_GreasePencilEnvelope, panel_draw);
}
static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const ModifierData *md)
{
const auto *emd = reinterpret_cast<const GreasePencilEnvelopeModifierData *>(md);
BLO_write_struct(writer, GreasePencilEnvelopeModifierData, emd);
modifier::greasepencil::write_influence_data(writer, &emd->influence);
}
static void blend_read(BlendDataReader *reader, ModifierData *md)
{
auto *emd = reinterpret_cast<GreasePencilEnvelopeModifierData *>(md);
modifier::greasepencil::read_influence_data(reader, &emd->influence);
}
} // namespace blender
ModifierTypeInfo modifierType_GreasePencilEnvelope = {
/*idname*/ "GreasePencilEnvelope",
/*name*/ N_("Envelope"),
/*struct_name*/ "GreasePencilEnvelopeModifierData",
/*struct_size*/ sizeof(GreasePencilEnvelopeModifierData),
/*srna*/ &RNA_GreasePencilEnvelopeModifier,
/*type*/ ModifierTypeType::Nonconstructive,
/*flags*/ eModifierTypeFlag_AcceptsGreasePencil | eModifierTypeFlag_SupportsEditmode |
eModifierTypeFlag_EnableInEditmode | eModifierTypeFlag_SupportsMapping,
/*icon*/ ICON_MOD_ENVELOPE,
/*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,
};
@@ -284,5 +284,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(GreasePencilLineart);
INIT_TYPE(GreasePencilArmature);
INIT_TYPE(GreasePencilTime);
INIT_TYPE(GreasePencilEnvelope);
#undef INIT_TYPE
}