diff options
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 |