GPencil: New Envelope Modifier

This new modifier creates a shape known as envelope. It connects all
points that are n points apart. There is also a mode which fits a
single stroke to the envelope shape that is determined by that rule.

For more details, refer to the patch.

Reviewed By: NicksBest, antoniov, frogstomp, mendio

Differential Revision: http://developer.blender.org/D14341
This commit is contained in:
Henrik Dick
2022-03-24 13:01:46 +01:00
parent dd161ff7a4
commit cee6af0056
8 changed files with 828 additions and 0 deletions

View File

@@ -37,6 +37,7 @@ set(SRC
intern/MOD_gpencilbuild.c
intern/MOD_gpencilcolor.c
intern/MOD_gpencildash.c
intern/MOD_gpencilenvelope.c
intern/MOD_gpencilhook.c
intern/MOD_gpencillattice.c
intern/MOD_gpencillength.c

View File

@@ -35,6 +35,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_WeightAngle;
extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart;
extern GpencilModifierTypeInfo modifierType_Gpencil_Dash;
extern GpencilModifierTypeInfo modifierType_Gpencil_Shrinkwrap;
extern GpencilModifierTypeInfo modifierType_Gpencil_Envelope;
/* MOD_gpencil_util.c */
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);

View File

@@ -56,6 +56,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Lineart);
INIT_GP_TYPE(Dash);
INIT_GP_TYPE(Shrinkwrap);
INIT_GP_TYPE(Envelope);
#undef INIT_GP_TYPE
}

View File

@@ -0,0 +1,629 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2017 Blender Foundation. */
/** \file
* \ingroup modifiers
*/
#include <stdio.h>
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_math_geom.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_defaults.h"
#include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_gpencil_modifier.h"
#include "BKE_lib_query.h"
#include "BKE_modifier.h"
#include "BKE_screen.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "RNA_access.h"
#include "MOD_gpencil_modifiertypes.h"
#include "MOD_gpencil_ui_common.h"
#include "MOD_gpencil_util.h"
#include "MEM_guardedalloc.h"
static void initData(GpencilModifierData *md)
{
EnvelopeGpencilModifierData *gpmd = (EnvelopeGpencilModifierData *)md;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(EnvelopeGpencilModifierData), modifier);
}
static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
{
BKE_gpencil_modifier_copydata_generic(md, target);
}
static float calc_min_radius_v3v3(float p1[3], float p2[3], float dir[3])
{
/* Use plane-conic-intersections to choose the maximal radius.
* The conic is deifned 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)));
*/
float diff[3];
float p1_dir = dot_v3v3(p1, dir);
float p2_dir = dot_v3v3(p2, dir);
float p2_sqr = len_squared_v3(p2);
float diff_dir = p1_dir - p2_dir;
float u = 0.5f;
if (diff_dir != 0.0f) {
float p = p2_dir / diff_dir;
sub_v3_v3v3(diff, p1, p2);
float diff_sqr = len_squared_v3(diff);
float diff_p2 = dot_v3v3(diff, p2);
float q = (2 * diff_p2 * p2_dir - p2_sqr * diff_dir) / (diff_dir * diff_sqr);
if (p * p - q >= 0) {
u = -p - sqrtf(p * p - q) * copysign(1.0f, p);
CLAMP(u, 0.0f, 1.0f);
}
else {
u = 0.5f - copysign(0.5f, p);
}
}
else {
float p1_sqr = len_squared_v3(p1);
u = p1_sqr < p2_sqr ? 1.0f : 0.0f;
}
float p[3];
interp_v3_v3v3(p, p2, p1, u);
/* 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. */
float v = len_squared_v3(p) / (2 * interpf(p1_dir, p2_dir, u));
if (v < 0 || !isfinite(v)) {
/* No limit to the radius from this segment. */
return 1e16f;
}
return v;
}
static float calc_radius_limit(
bGPDstroke *gps, bGPDspoint *points, float dir[3], int spread, const int i)
{
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
bGPDspoint *pt = &points[i];
/* NOTE this part is the second performance critical part. Improvements are welcome. */
float radius_limit = 1e16f;
float p1[3], p2[3];
if (is_cyclic) {
if (gps->totpoints / 2 < spread) {
spread = gps->totpoints / 2;
}
const int start = i + gps->totpoints;
for (int j = -spread; j <= spread; j++) {
j += (j == 0);
const int i1 = (start + j) % gps->totpoints;
const int i2 = (start + j + (j > 0) - (j < 0)) % gps->totpoints;
sub_v3_v3v3(p1, &points[i1].x, &pt->x);
sub_v3_v3v3(p2, &points[i2].x, &pt->x);
float r = calc_min_radius_v3v3(p1, p2, dir);
radius_limit = min_ff(radius_limit, r);
}
}
else {
const int start = max_ii(-spread, 1 - i);
const int end = min_ii(spread, gps->totpoints - 2 - i);
for (int j = start; j <= end; j++) {
if (j == 0) {
continue;
}
const int i1 = i + j;
const int i2 = i + j + (j > 0) - (j < 0);
sub_v3_v3v3(p1, &points[i1].x, &pt->x);
sub_v3_v3v3(p2, &points[i2].x, &pt->x);
float r = calc_min_radius_v3v3(p1, p2, dir);
radius_limit = min_ff(radius_limit, r);
}
}
return radius_limit;
}
static void apply_stroke_envelope(
bGPDstroke *gps, int spread, const int def_nr, const bool invert_vg, const float thickness)
{
const bool is_cyclic = (gps->flag & GP_STROKE_CYCLIC) != 0;
if (is_cyclic) {
const int half = gps->totpoints / 2;
spread = abs(((spread + half) % gps->totpoints) - half);
}
else {
spread = min_ii(spread, gps->totpoints - 1);
}
const int spread_left = (spread + 2) / 2;
const int spread_right = (spread + 1) / 2;
/* Copy the point data. Only need positions, but extracting them
* is probably just as expensive as a full copy. */
bGPDspoint *old_points = (bGPDspoint *)MEM_dupallocN(gps->points);
/* Deform the stroke to match the envelope shape. */
for (int i = 0; i < gps->totpoints; i++) {
MDeformVert *dvert = gps->dvert != NULL ? &gps->dvert[i] : NULL;
/* Verify in vertex group. */
float weight = get_modifier_point_weight(dvert, invert_vg, def_nr);
if (weight < 0.0f) {
continue;
}
int index1 = i - spread_left;
int index2 = i + spread_right;
CLAMP(index1, 0, gps->totpoints - 1);
CLAMP(index2, 0, gps->totpoints - 1);
bGPDspoint *point = &gps->points[i];
point->pressure *= interpf(thickness, 1.0f, weight);
float closest[3];
float closest2[3];
copy_v3_v3(closest2, &point->x);
float dist = 0.0f;
float dist2 = 0.0f;
/* Create plane from point and neighbors and intersect that with the line. */
float v1[3], v2[3], plane_no[3];
sub_v3_v3v3(
v1,
&old_points[is_cyclic ? (i - 1 + gps->totpoints) % gps->totpoints : max_ii(0, i - 1)].x,
&old_points[i].x);
sub_v3_v3v3(
v2,
&old_points[is_cyclic ? (i + 1) % gps->totpoints : min_ii(gps->totpoints - 1, i + 1)].x,
&old_points[i].x);
normalize_v3(v1);
normalize_v3(v2);
sub_v3_v3v3(plane_no, v1, v2);
if (normalize_v3(plane_no) == 0.0f) {
continue;
}
/* Now find the intersections with the plane. */
/* NOTE this part is the first performance critical part. Improvements are welcome. */
float tmp_closest[3];
for (int j = -spread_right; j <= spread_left; j++) {
const int i1 = is_cyclic ? (i + j - spread_left + gps->totpoints) % gps->totpoints :
max_ii(0, i + j - spread_left);
const int i2 = is_cyclic ? (i + j + spread_right) % gps->totpoints :
min_ii(gps->totpoints - 1, i + j + spread_right);
/*bool side = dot_v3v3(&old_points[i1].x, plane_no) < dot_v3v3(plane_no, &old_points[i2].x);
if (side) {
continue;
}*/
float lambda = line_plane_factor_v3(
&point->x, plane_no, &old_points[i1].x, &old_points[i2].x);
if (lambda <= 0.0f || lambda >= 1.0f) {
continue;
}
interp_v3_v3v3(tmp_closest, &old_points[i1].x, &old_points[i2].x, lambda);
float dir[3];
sub_v3_v3v3(dir, tmp_closest, &point->x);
float d = len_v3(dir);
/* Use a formula to find the diameter of the circle that would touch the line. */
float cos_angle = fabsf(dot_v3v3(plane_no, &old_points[i1].x) -
dot_v3v3(plane_no, &old_points[i2].x)) /
len_v3v3(&old_points[i1].x, &old_points[i2].x);
d *= 2 * cos_angle / (1 + cos_angle);
float to_closest[3];
sub_v3_v3v3(to_closest, closest, &point->x);
if (dist == 0.0f) {
dist = d;
copy_v3_v3(closest, tmp_closest);
}
else if (dot_v3v3(to_closest, dir) >= 0) {
if (d > dist) {
dist = d;
copy_v3_v3(closest, tmp_closest);
}
}
else {
if (d > dist2) {
dist2 = d;
copy_v3_v3(closest2, tmp_closest);
}
}
}
if (dist == 0.0f) {
copy_v3_v3(closest, &point->x);
}
if (dist2 == 0.0f) {
copy_v3_v3(closest2, &point->x);
}
dist = dist + dist2;
if (dist < FLT_EPSILON) {
continue;
}
float use_dist = dist;
/* Apply radius limiting to not cross existing lines. */
float dir[3], new_center[3];
interp_v3_v3v3(new_center, closest2, closest, 0.5f);
sub_v3_v3v3(dir, new_center, &point->x);
if (normalize_v3(dir) != 0.0f && (is_cyclic || (i > 0 && i < gps->totpoints - 1))) {
const float max_radius = calc_radius_limit(gps, old_points, dir, spread, i);
use_dist = min_ff(use_dist, 2 * max_radius);
}
float fac = use_dist * weight;
/* The 50 is an internal constant for the default pixel size. The result can be messed up if
* bGPdata.pixfactor is not default, but I think modifiers shouldn't access that. */
point->pressure += fac * 50.0f * GP_DEFAULT_PIX_FACTOR;
interp_v3_v3v3(&point->x, &point->x, new_center, fac / len_v3v3(closest, closest2));
}
MEM_freeN(old_points);
}
/**
* Apply envelope effect to the stroke.
*/
static void deformStroke(GpencilModifierData *md,
Depsgraph *UNUSED(depsgraph),
Object *ob,
bGPDlayer *gpl,
bGPDframe *UNUSED(gpf),
bGPDstroke *gps)
{
EnvelopeGpencilModifierData *mmd = (EnvelopeGpencilModifierData *)md;
if (mmd->mode != GP_ENVELOPE_DEFORM) {
return;
}
const int def_nr = BKE_object_defgroup_name_index(ob, mmd->vgname);
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
mmd->material,
mmd->pass_index,
mmd->layer_pass,
3,
gpl,
gps,
mmd->flag & GP_ENVELOPE_INVERT_LAYER,
mmd->flag & GP_ENVELOPE_INVERT_PASS,
mmd->flag & GP_ENVELOPE_INVERT_LAYERPASS,
mmd->flag & GP_ENVELOPE_INVERT_MATERIAL)) {
return;
}
if (mmd->spread <= 0) {
return;
}
apply_stroke_envelope(
gps, mmd->spread, def_nr, (mmd->flag & GP_ENVELOPE_INVERT_VGROUP) != 0, mmd->thickness);
}
static void add_stroke(Object *ob,
bGPDstroke *gps,
const int point_index,
const int connection_index,
const int size,
const int mat_nr,
const float thickness,
const float strength,
ListBase *results)
{
bGPdata *gpd = ob->data;
bGPDstroke *gps_dst = BKE_gpencil_stroke_new(mat_nr, size, gps->thickness);
const int size1 = size == 4 ? 2 : 1;
const int size2 = size - size1;
memcpy(&gps_dst->points[0], &gps->points[connection_index], size1 * sizeof(bGPDspoint));
memcpy(&gps_dst->points[size1], &gps->points[point_index], size2 * sizeof(bGPDspoint));
for (int i = 0; i < size; i++) {
gps_dst->points[i].pressure *= thickness;
gps_dst->points[i].strength *= strength;
memset(&gps_dst->points[i].runtime, 0, sizeof(bGPDspoint_Runtime));
}
if (gps->dvert != NULL) {
gps_dst->dvert = MEM_malloc_arrayN(size, sizeof(MDeformVert), __func__);
BKE_defvert_array_copy(&gps_dst->dvert[0], &gps->dvert[connection_index], size1);
BKE_defvert_array_copy(&gps_dst->dvert[size1], &gps->dvert[point_index], size2);
}
BLI_addtail(results, gps_dst);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps_dst);
}
static void add_stroke_cyclic(Object *ob,
bGPDstroke *gps,
const int point_index,
const int connection_index,
const int mat_nr,
const float thickness,
const float strength,
ListBase *results)
{
bGPdata *gpd = ob->data;
bGPDstroke *gps_dst = BKE_gpencil_stroke_new(mat_nr, 4, gps->thickness);
int connection_index2 = (connection_index + 1) % gps->totpoints;
int point_index2 = (point_index + 1) % gps->totpoints;
gps_dst->points[0] = gps->points[connection_index];
gps_dst->points[1] = gps->points[connection_index2];
gps_dst->points[2] = gps->points[point_index];
gps_dst->points[3] = gps->points[point_index2];
for (int i = 0; i < 4; i++) {
gps_dst->points[i].pressure *= thickness;
gps_dst->points[i].strength *= strength;
memset(&gps_dst->points[i].runtime, 0, sizeof(bGPDspoint_Runtime));
}
if (gps->dvert != NULL) {
gps_dst->dvert = MEM_malloc_arrayN(4, sizeof(MDeformVert), __func__);
BKE_defvert_array_copy(&gps_dst->dvert[0], &gps->dvert[connection_index], 1);
BKE_defvert_array_copy(&gps_dst->dvert[1], &gps->dvert[connection_index2], 1);
BKE_defvert_array_copy(&gps_dst->dvert[2], &gps->dvert[point_index], 1);
BKE_defvert_array_copy(&gps_dst->dvert[3], &gps->dvert[point_index2], 1);
}
BLI_addtail(results, gps_dst);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps_dst);
}
static void add_stroke_simple(Object *ob,
bGPDstroke *gps,
const int point_index,
const int connection_index,
const int mat_nr,
const float thickness,
const float strength,
ListBase *results)
{
bGPdata *gpd = ob->data;
bGPDstroke *gps_dst = BKE_gpencil_stroke_new(mat_nr, 2, gps->thickness);
gps_dst->points[0] = gps->points[connection_index];
gps_dst->points[0].pressure *= thickness;
gps_dst->points[0].strength *= strength;
memset(&gps_dst->points[0].runtime, 0, sizeof(bGPDspoint_Runtime));
gps_dst->points[1] = gps->points[point_index];
gps_dst->points[1].pressure *= thickness;
gps_dst->points[1].strength *= strength;
memset(&gps_dst->points[1].runtime, 0, sizeof(bGPDspoint_Runtime));
if (gps->dvert != NULL) {
gps_dst->dvert = MEM_malloc_arrayN(2, sizeof(MDeformVert), __func__);
BKE_defvert_array_copy(&gps_dst->dvert[0], &gps->dvert[connection_index], 1);
BKE_defvert_array_copy(&gps_dst->dvert[1], &gps->dvert[point_index], 1);
}
BLI_addtail(results, gps_dst);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps_dst);
}
static void generate_geometry(GpencilModifierData *md, Object *ob, bGPDlayer *gpl, bGPDframe *gpf)
{
EnvelopeGpencilModifierData *mmd = (EnvelopeGpencilModifierData *)md;
ListBase duplicates = {0};
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (!is_stroke_affected_by_modifier(ob,
mmd->layername,
mmd->material,
mmd->pass_index,
mmd->layer_pass,
3,
gpl,
gps,
mmd->flag & GP_ENVELOPE_INVERT_LAYER,
mmd->flag & GP_ENVELOPE_INVERT_PASS,
mmd->flag & GP_ENVELOPE_INVERT_LAYERPASS,
mmd->flag & GP_ENVELOPE_INVERT_MATERIAL)) {
continue;
}
const int mat_nr = mmd->mat_nr < 0 ? gps->mat_nr : min_ii(mmd->mat_nr, ob->totcol - 1);
if (mmd->mode == GP_ENVELOPE_FILLS) {
if (gps->flag & GP_STROKE_CYCLIC) {
for (int i = 0; i < gps->totpoints; i++) {
const int connection_index = (i + mmd->spread) % gps->totpoints;
add_stroke_cyclic(
ob, gps, i, connection_index, mat_nr, mmd->thickness, mmd->strength, &duplicates);
}
}
else {
for (int i = 1; i < gps->totpoints - 1 && i < mmd->spread + 1; i++) {
add_stroke(ob, gps, i, 0, 3, mat_nr, mmd->thickness, mmd->strength, &duplicates);
}
for (int i = 0; i < gps->totpoints - 1; i++) {
const int connection_index = min_ii(i + mmd->spread, gps->totpoints - 1);
const int size = i == gps->totpoints - 2 ? 2 :
connection_index < gps->totpoints - 1 ? 4 :
3;
add_stroke(ob,
gps,
i,
connection_index,
size,
mat_nr,
mmd->thickness,
mmd->strength,
&duplicates);
}
}
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
}
else {
BLI_assert(mmd->mode == GP_ENVELOPE_SEGMENTS);
if (gps->flag & GP_STROKE_CYCLIC) {
for (int i = 0; i < gps->totpoints; i++) {
const int connection_index = (i + 1 + mmd->spread) % gps->totpoints;
add_stroke_simple(
ob, gps, i, connection_index, mat_nr, mmd->thickness, mmd->strength, &duplicates);
}
}
else {
for (int i = -mmd->spread; i < gps->totpoints - 1; i++) {
const int connection_index = min_ii(i + 1 + mmd->spread, gps->totpoints - 1);
add_stroke_simple(ob,
gps,
max_ii(0, i),
connection_index,
mat_nr,
mmd->thickness,
mmd->strength,
&duplicates);
}
}
}
}
if (!BLI_listbase_is_empty(&duplicates)) {
/* Add strokes to the start of the stroke list to ensure the new lines are drawn underneath the
* original line. */
BLI_movelisttolist_reverse(&gpf->strokes, &duplicates);
}
}
/**
* Apply envelope effect to the strokes.
*/
static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob)
{
EnvelopeGpencilModifierData *mmd = (EnvelopeGpencilModifierData *)md;
if (mmd->mode == GP_ENVELOPE_DEFORM || mmd->spread <= 0) {
return;
}
Scene *scene = DEG_get_evaluated_scene(depsgraph);
bGPdata *gpd = (bGPdata *)ob->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
bGPDframe *gpf = BKE_gpencil_frame_retime_get(depsgraph, scene, ob, gpl);
if (gpf == NULL) {
continue;
}
generate_geometry(md, ob, gpl, gpf);
}
}
static void bakeModifier(struct Main *UNUSED(bmain),
Depsgraph *depsgraph,
GpencilModifierData *md,
Object *ob)
{
EnvelopeGpencilModifierData *mmd = (EnvelopeGpencilModifierData *)md;
if (mmd->mode == GP_ENVELOPE_DEFORM) {
generic_bake_deform_stroke(depsgraph, md, ob, false, deformStroke);
}
else {
bGPdata *gpd = ob->data;
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
generate_geometry(md, ob, gpl, gpf);
}
}
}
}
static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
{
EnvelopeGpencilModifierData *mmd = (EnvelopeGpencilModifierData *)md;
walk(userData, ob, (ID **)&mmd->material, IDWALK_CB_USER);
}
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
uiLayout *row;
uiLayout *layout = panel->layout;
PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
uiLayoutSetPropSep(layout, true);
uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "spread", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "thickness", 0, NULL, ICON_NONE);
const int mode = RNA_enum_get(ptr, "mode");
if (mode != GP_ENVELOPE_DEFORM) {
uiItemR(layout, ptr, "strength", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "mat_nr", 0, NULL, ICON_NONE);
}
gpencil_modifier_panel_end(layout, ptr);
}
static void mask_panel_draw(const bContext *UNUSED(C), Panel *panel)
{
gpencil_modifier_masking_panel_draw(panel, true, true);
}
static void panelRegister(ARegionType *region_type)
{
PanelType *panel_type = gpencil_modifier_panel_register(
region_type, eGpencilModifierType_Envelope, panel_draw);
PanelType *mask_panel_type = gpencil_modifier_subpanel_register(
region_type, "mask", "Influence", NULL, mask_panel_draw, panel_type);
}
GpencilModifierTypeInfo modifierType_Gpencil_Envelope = {
/* name */ "Envelope",
/* structName */ "EnvelopeGpencilModifierData",
/* structSize */ sizeof(EnvelopeGpencilModifierData),
/* type */ eGpencilModifierTypeType_Gpencil,
/* flags */ eGpencilModifierTypeFlag_SupportsEditmode,
/* copyData */ copyData,
/* deformStroke */ deformStroke,
/* generateStrokes */ generateStrokes,
/* bakeModifier */ bakeModifier,
/* remapTime */ NULL,
/* initData */ initData,
/* freeData */ NULL,
/* isDisabled */ NULL,
/* updateDepsgraph */ NULL,
/* dependsOnTime */ NULL,
/* foreachIDLink */ foreachIDLink,
/* foreachTexLink */ NULL,
/* panelRegister */ panelRegister,
};

View File

@@ -366,5 +366,14 @@
.smooth_step = 1, \
}
#define _DNA_DEFAULT_EnvelopeGpencilModifierData \
{ \
.spread = 10, \
.mode = GP_ENVELOPE_SEGMENTS, \
.mat_nr = -1, \
.thickness = 1.0f, \
.strength = 1.0f, \
}
/* clang-format off */

View File

@@ -46,6 +46,7 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Dash = 22,
eGpencilModifierType_WeightAngle = 23,
eGpencilModifierType_Shrinkwrap = 24,
eGpencilModifierType_Envelope = 25,
/* Keep last. */
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@@ -1127,6 +1128,46 @@ typedef enum eShrinkwrapGpencil_Flag {
GP_SHRINKWRAP_INVERT_VGROUP = (1 << 6),
} eShrinkwrapGpencil_Flag;
typedef struct EnvelopeGpencilModifierData {
GpencilModifierData modifier;
/** Material for filtering. */
struct Material *material;
/** Layer name. */
char layername[64];
/** Optional vertexgroup name, MAX_VGROUP_NAME. */
char vgname[64];
/** Custom index for passes. */
int pass_index;
/** Several flags. */
int flag;
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;
/** Custom index for passes. */
int layer_pass;
/* Length of the envelope effect. */
int spread;
} EnvelopeGpencilModifierData;
typedef enum eEnvelopeGpencil_Flag {
GP_ENVELOPE_INVERT_LAYER = (1 << 0),
GP_ENVELOPE_INVERT_PASS = (1 << 1),
GP_ENVELOPE_INVERT_VGROUP = (1 << 2),
GP_ENVELOPE_INVERT_LAYERPASS = (1 << 3),
GP_ENVELOPE_INVERT_MATERIAL = (1 << 4),
} eEnvelopeGpencil_Flag;
/* Texture->mode */
typedef enum eEnvelopeGpencil_Mode {
GP_ENVELOPE_DEFORM = 0,
GP_ENVELOPE_SEGMENTS = 1,
GP_ENVELOPE_FILLS = 2,
} eEnvelopeGpencil_Mode;
#ifdef __cplusplus
}
#endif

View File

@@ -316,6 +316,7 @@ SDNA_DEFAULT_DECL_STRUCT(LengthGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(DashGpencilModifierSegment);
SDNA_DEFAULT_DECL_STRUCT(ShrinkwrapGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(EnvelopeGpencilModifierData);
#undef SDNA_DEFAULT_DECL_STRUCT
@@ -555,6 +556,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(DashGpencilModifierData),
SDNA_DEFAULT_DECL(DashGpencilModifierSegment),
SDNA_DEFAULT_DECL(ShrinkwrapGpencilModifierData),
SDNA_DEFAULT_DECL(EnvelopeGpencilModifierData),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX

View File

@@ -79,6 +79,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_DASH,
"Dot Dash",
"Generate dot-dash styled strokes"},
{eGpencilModifierType_Envelope,
"GP_ENVELOPE",
ICON_MOD_SKIN,
"Envelope",
"Create an envelope shape"},
{eGpencilModifierType_Length,
"GP_LENGTH",
ICON_MOD_LENGTH,
@@ -208,6 +213,25 @@ static const EnumPropertyItem gpencil_length_mode_items[] = {
{GP_LENGTH_ABSOLUTE, "ABSOLUTE", 0, "Absolute", "Length in geometry space"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem gpencil_envelope_mode_items[] = {
{GP_ENVELOPE_DEFORM,
"DEFORM",
0,
"Deform",
"Deform the stroke to best match the envelope shape"},
{GP_ENVELOPE_SEGMENTS,
"SEGMENTS",
0,
"Segments",
"Add segments to create the envelope. Keep the original stroke"},
{GP_ENVELOPE_FILLS,
"FILLS",
0,
"Fills",
"Add fill segments to create the envelope. Don't keep the original stroke"},
{0, NULL, 0, NULL, NULL},
};
#endif
#ifdef RNA_RUNTIME
@@ -279,6 +303,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_LineartGpencilModifier;
case eGpencilModifierType_Dash:
return &RNA_DashGpencilModifierData;
case eGpencilModifierType_Envelope:
return &RNA_EnvelopeGpencilModifier;
/* Default */
case eGpencilModifierType_None:
case NUM_GREASEPENCIL_MODIFIER_TYPES:
@@ -355,6 +381,7 @@ RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, target_vgname);
RNA_GP_MOD_VGROUP_NAME_SET(WeightAngle, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Lineart, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Shrinkwrap, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Envelope, vgname);
# undef RNA_GP_MOD_VGROUP_NAME_SET
@@ -775,6 +802,16 @@ static void rna_ShrinkwrapGpencilModifier_face_cull_set(struct PointerRNA *ptr,
swm->shrink_opts = (swm->shrink_opts & ~MOD_SHRINKWRAP_CULL_TARGET_MASK) | value;
}
static void rna_EnvelopeGpencilModifier_material_set(PointerRNA *ptr,
PointerRNA value,
struct ReportList *reports)
{
EnvelopeGpencilModifierData *emd = (EnvelopeGpencilModifierData *)ptr->data;
Material **ma_target = &emd->material;
rna_GpencilModifier_material_set(ptr, value, ma_target, reports);
}
#else
static void rna_def_modifier_gpencilnoise(BlenderRNA *brna)
@@ -3968,6 +4005,112 @@ static void rna_def_modifier_gpencilshrinkwrap(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
static void rna_def_modifier_gpencilenvelope(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "EnvelopeGpencilModifier", "GpencilModifier");
RNA_def_struct_ui_text(srna, "Envelope Modifier", "Envelope stroke effect modifier");
RNA_def_struct_sdna(srna, "EnvelopeGpencilModifierData");
RNA_def_struct_ui_icon(srna, ICON_MOD_SKIN);
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "layer", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "layername");
RNA_def_property_ui_text(prop, "Layer", "Layer name");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "material", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop,
NULL,
"rna_EnvelopeGpencilModifier_material_set",
NULL,
"rna_GpencilModifier_material_poll");
RNA_def_property_ui_text(prop, "Material", "Material used for filtering effect");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "vgname");
RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modulating the deform");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_EnvelopeGpencilModifier_vgname_set");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "pass_index", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "pass_index");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Pass", "Pass index");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "spread", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "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_GpencilModifier_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "mode");
RNA_def_property_enum_items(prop, gpencil_envelope_mode_items);
RNA_def_property_ui_text(prop, "Mode", "Algorithm to use for generating the envelope");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "mat_nr", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "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_GpencilModifier_update");
prop = RNA_def_property(srna, "thickness", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "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_GpencilModifier_update");
prop = RNA_def_property(srna, "strength", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "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_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_layers", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_ENVELOPE_INVERT_LAYER);
RNA_def_property_ui_text(prop, "Inverse Layers", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_materials", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_ENVELOPE_INVERT_MATERIAL);
RNA_def_property_ui_text(prop, "Inverse Materials", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_material_pass", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_ENVELOPE_INVERT_PASS);
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_vertex", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_ENVELOPE_INVERT_VGROUP);
RNA_def_property_ui_text(prop, "Inverse VertexGroup", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "layer_pass", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "layer_pass");
RNA_def_property_range(prop, 0, 100);
RNA_def_property_ui_text(prop, "Pass", "Layer pass index");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
prop = RNA_def_property(srna, "invert_layer_pass", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_ENVELOPE_INVERT_LAYERPASS);
RNA_def_property_ui_text(prop, "Inverse Pass", "Inverse filter");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
RNA_define_lib_overridable(false);
}
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -4058,6 +4201,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencillength(brna);
rna_def_modifier_gpencildash(brna);
rna_def_modifier_gpencilshrinkwrap(brna);
rna_def_modifier_gpencilenvelope(brna);
}
#endif