Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/gpencil_modifiers/CMakeLists.txt1
-rw-r--r--source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilenvelope.c629
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_defaults.h9
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_types.h41
-rw-r--r--source/blender/makesdna/intern/dna_defaults.c2
-rw-r--r--source/blender/makesrna/intern/rna_gpencil_modifier.c144
8 files changed, 828 insertions, 0 deletions
diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt
index 752d4aea61c..6108629183c 100644
--- a/source/blender/gpencil_modifiers/CMakeLists.txt
+++ b/source/blender/gpencil_modifiers/CMakeLists.txt
@@ -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
diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
index ff280b9ca0d..e88d864a86e 100644
--- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
+++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
@@ -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[]);
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
index e766615101a..6cf7f6f11e5 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
@@ -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
}
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilenvelope.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilenvelope.c
new file mode 100644
index 00000000000..0f736cac464
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilenvelope.c
@@ -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,
+};
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
index 3e06259dfd5..a87e7a7c397 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
@@ -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 */
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h
index 0539b84e093..1054c309ee5 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_types.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h
@@ -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
diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c
index b7f942e2502..197a863db72 100644
--- a/source/blender/makesdna/intern/dna_defaults.c
+++ b/source/blender/makesdna/intern/dna_defaults.c
@@ -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
diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c
index fbc622a73a8..edeb7443957 100644
--- a/source/blender/makesrna/intern/rna_gpencil_modifier.c
+++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c
@@ -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