GPencil: Build modifier add "natural drawing" time

This patch uses the recorded drawing speed to rebuild the strokes. This results in a way more
natural feel of the animation.

Here's a short summary of existing data used:

- gps->points->time: This is a timestamp in seconds of when the point was created
  since the creation of the stroke. It's quite often 0 (I added a sanitization routine).
- gpf->inittime: This is a timestamp in seconds when a stroke was drawn measured
  since some unknown point in time. I only ever use the difference between two strokes,
  so the absolute value is not relevant.

Reviewed By: frogstomp, antoniov, mendio

Differential Revision: https://developer.blender.org/D16759
This commit is contained in:
Marc Chéhab 2023-01-10 15:37:02 +01:00 committed by Antonio Vazquez
parent fddb76e9af
commit 250eda36b8
Notes: blender-bot 2023-02-14 04:20:36 +01:00
Referenced by commit 50d6af1e0e, Fix invalid string comparison
6 changed files with 400 additions and 140 deletions

View File

@ -3842,6 +3842,20 @@ void blo_do_versions_300(FileData *fd, Library * /*lib*/, Main *bmain)
LISTBASE_FOREACH (Light *, light, &bmain->lights) {
light->radius = light->area_size;
}
/* Grease Pencil Build modifier: Set default value for new natural drawspeed factor and maximum
* gap. */
if (!DNA_struct_elem_find(fd->filesdna, "BuildGpencilModifierData", "float", "speed_fac") ||
!DNA_struct_elem_find(fd->filesdna, "BuildGpencilModifierData", "float", "speed_maxgap")) {
LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
if (md->type == eGpencilModifierType_Build) {
BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md;
mmd->speed_fac = 1.2f;
mmd->speed_maxgap = 0.5f;
}
}
}
}
}
/**

View File

@ -83,7 +83,7 @@ typedef struct tGPspoint {
float pressure;
/** Pressure of tablet at this point for alpha factor. */
float strength;
/** Time relative to stroke start (used when converting to path). */
/** Time relative to stroke start (used when converting to path & in build modifier). */
float time;
/** Factor of uv along the stroke. */
float uv_fac;

View File

@ -47,6 +47,13 @@
#include "MOD_gpencil_modifiertypes.h"
#include "MOD_gpencil_ui_common.h"
/* Two hard-coded values for GP_BUILD_MODE_ADDITIVE with GP_BUILD_TIMEMODE_DRAWSPEED. */
/* The minimum time gap we should worry about points with no time. */
#define GP_BUILD_CORRECTGAP 0.001
/* The time for geometric strokes */
#define GP_BUILD_TIME_GEOSTROKES 1.0
static void initData(GpencilModifierData *md)
{
BuildGpencilModifierData *gpmd = (BuildGpencilModifierData *)md;
@ -252,53 +259,55 @@ static int cmp_stroke_build_details(const void *ps1, const void *ps2)
return p1->distance > p2->distance ? 1 : (p1->distance == p2->distance ? 0 : -1);
}
/* Sequential and additive - Show strokes one after the other. */
/* Sequential - Show strokes one after the other (includes additive mode). */
static void build_sequential(Object *ob,
BuildGpencilModifierData *mmd,
Depsgraph *depsgraph,
bGPdata *gpd,
bGPDframe *gpf,
const int target_def_nr,
int target_def_nr,
float fac,
bool additive)
const float *ctime)
{
/* Total number of strokes in this run. */
size_t tot_strokes = BLI_listbase_count(&gpf->strokes);
size_t start_stroke;
/* First stroke to build. */
size_t start_stroke = 0;
/* Pointer to current stroke. */
bGPDstroke *gps;
/* Recycled counter. */
size_t i;
Scene *scene = DEG_get_evaluated_scene(depsgraph);
/* Framerate of scene. */
const float fps = (((float)scene->r.frs_sec) / scene->r.frs_sec_base);
/* 1) Determine which strokes to start with & total strokes to build. */
if (additive) {
/* 1) Determine which strokes to start with (& adapt total number of strokes to build). */
if (mmd->mode == GP_BUILD_MODE_ADDITIVE) {
if (gpf->prev) {
start_stroke = BLI_listbase_count(&gpf->prev->strokes);
}
else {
start_stroke = 0;
start_stroke = BLI_listbase_count(&gpf->runtime.gpf_orig->prev->strokes);
}
if (start_stroke <= tot_strokes) {
tot_strokes = tot_strokes - start_stroke;
}
else {
start_stroke = 0;
}
}
else {
start_stroke = 0;
}
/* 2) Compute proportion of time each stroke should occupy */
/* 2) Compute proportion of time each stroke should occupy. */
/* NOTE: This assumes that the total number of points won't overflow! */
tStrokeBuildDetails *table = MEM_callocN(sizeof(tStrokeBuildDetails) * tot_strokes, __func__);
size_t totpoints = 0;
/* Pointer to cache table of times for each point. */
float *idx_times;
/* Running overall time sum incrementing per point. */
float sumtime = 0;
/* Running overall point sum. */
size_t sumpoints = 0;
/* 2.1) First pass - Tally up points */
/* 2.1) Pass to initially tally up points. */
for (gps = BLI_findlink(&gpf->strokes, start_stroke), i = 0; gps; gps = gps->next, i++) {
tStrokeBuildDetails *cell = &table[i];
cell->gps = gps;
cell->totpoints = gps->totpoints;
totpoints += cell->totpoints;
sumpoints += cell->totpoints;
/* Compute distance to control object if set, and build according to that order. */
if (mmd->object) {
@ -321,7 +330,104 @@ static void build_sequential(Object *ob,
qsort(table, tot_strokes, sizeof(tStrokeBuildDetails), cmp_stroke_build_details);
}
/* 2.2) Second pass - Compute the overall indices for points */
/* 2.2) If GP_BUILD_TIMEMODE_DRAWSPEED: Tally up point timestamps & delays to idx_times. */
if (mmd->time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) {
idx_times = MEM_callocN(sizeof(float) * sumpoints, __func__);
/* Maximum time gap between strokes in seconds. */
const float GP_BUILD_MAXGAP = mmd->speed_maxgap;
/* Running reference to overall current point. */
size_t curpoint = 0;
/* Running timestamp of last point that had data. */
float last_pointtime = 0;
for (i = 0; i < tot_strokes; i++) {
tStrokeBuildDetails *cell = &table[i];
/* Adding delay between strokes to sumtime. */
if (mmd->object == NULL) {
/* Normal case: Delay to last stroke. */
if (i != 0 && 0 < cell->gps->inittime && 0 < (cell - 1)->gps->inittime) {
float curgps_delay = fabs(cell->gps->inittime - (cell - 1)->gps->inittime) -
last_pointtime;
if (0 < curgps_delay) {
sumtime += MIN2(curgps_delay, GP_BUILD_MAXGAP);
}
}
}
/* Going through the points of the current stroke
* and filling in "zeropoints" where "time" = 0. */
/* Count of consecutive points where "time" is 0. */
int zeropoints = 0;
for (int j = 0; j < cell->totpoints; j++) {
/* Defining time for first point in stroke. */
if (j == 0) {
idx_times[curpoint] = sumtime;
last_pointtime = cell->gps->points[0].time;
}
/* Entering subsequent points */
else {
if (cell->gps->points[j].time == 0) {
idx_times[curpoint] = sumtime;
zeropoints++;
}
/* From here current point has time data */
else {
float deltatime = fabs(cell->gps->points[j].time - last_pointtime);
/* Do we need to sanitize previous points? */
if (0 < zeropoints) {
/* Only correct if timegap bigger than MIN_CORRECTGAP. */
if (GP_BUILD_CORRECTGAP < deltatime) {
/* Cycling backwards through zeropoints to fix them. */
for (int k = 0; k < zeropoints; k++) {
float linear_fill = interpf(
deltatime, 0, ((float)k + 1) / (zeropoints + 1)); /* Factor = Proportion. */
idx_times[curpoint - k - 1] = sumtime + linear_fill;
}
}
else {
zeropoints = 0;
}
}
/* Normal behaviour with time data */
idx_times[curpoint] = sumtime + deltatime;
sumtime = idx_times[curpoint];
last_pointtime = cell->gps->points[j].time;
zeropoints = 0;
}
}
curpoint += 1;
}
/* If stroke had no time data at all, use mmd->time_geostrokes. */
if (zeropoints + 1 == cell->totpoints) {
for (int j = 0; j < cell->totpoints; j++) {
idx_times[(int)curpoint - j - 1] = (float)(cell->totpoints - j) *
GP_BUILD_TIME_GEOSTROKES /
(float)cell->totpoints +
sumtime;
}
last_pointtime = GP_BUILD_TIME_GEOSTROKES;
sumtime += GP_BUILD_TIME_GEOSTROKES;
}
}
float gp_build_speedfactor = mmd->speed_fac;
/* If current frame can't be built before next frame, adjust gp_build_speedfactor. */
if (gpf->next &&
(gpf->framenum + sumtime * fps / gp_build_speedfactor) > gpf->next->framenum) {
gp_build_speedfactor = sumtime * fps / (gpf->next->framenum - gpf->framenum);
}
/* Apply gp_build_speedfactor to all points & to sumtime. */
for (i = 0; i < sumpoints; i++) {
float *idx_time = &idx_times[i];
*idx_time /= gp_build_speedfactor;
}
sumtime /= gp_build_speedfactor;
}
/* 2.3) Pass to compute overall indices for points (per stroke). */
for (i = 0; i < tot_strokes; i++) {
tStrokeBuildDetails *cell = &table[i];
@ -329,12 +435,12 @@ static void build_sequential(Object *ob,
cell->start_idx = 0;
}
else {
cell->start_idx = (cell - 1)->end_idx;
cell->start_idx = (cell - 1)->end_idx + 1;
}
cell->end_idx = cell->start_idx + cell->totpoints - 1;
}
/* 3) Determine the global indices for points that should be visible */
/* 3) Determine the global indices for points that should be visible. */
size_t first_visible = 0;
size_t last_visible = 0;
/* Need signed numbers because the representation of fading offset would exceed the beginning and
@ -343,30 +449,60 @@ static void build_sequential(Object *ob,
int fade_end = 0;
bool fading_enabled = (mmd->flag & GP_BUILD_USE_FADING);
float set_fade_fac = fading_enabled ? mmd->fade_fac : 0.0f;
float use_fac = interpf(1 + set_fade_fac, 0, fac);
float use_fac;
if (mmd->time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) {
/* Recalculate equivalent of "fac" using timestamps. */
float targettime = (*ctime - (float)gpf->framenum) / fps;
fac = 0;
/* If ctime is in current frame, find last point. */
if (0 < targettime && targettime < sumtime) {
/* All except GP_BUILD_TRANSITION_SHRINK count forwards. */
if (mmd->transition != GP_BUILD_TRANSITION_SHRINK) {
for (i = 0; i < sumpoints; i++) {
if (targettime < idx_times[i]) {
fac = (float)i / sumpoints;
break;
}
}
}
else {
for (i = 0; i < sumpoints; i++) {
if (targettime < sumtime - idx_times[sumpoints - i - 1]) {
fac = (float)i / sumpoints;
break;
}
}
}
}
/* Don't check if ctime is beyond time of current frame. */
else if (targettime >= sumtime) {
fac = 1;
}
}
use_fac = interpf(1 + set_fade_fac, 0, fac);
float use_fade_fac = use_fac - set_fade_fac;
CLAMP(use_fade_fac, 0.0f, 1.0f);
switch (mmd->transition) {
/* Show in forward order
* - As fac increases, the number of visible points increases
*/
/* Show in forward order
* - As fac increases, the number of visible points increases
*/
case GP_BUILD_TRANSITION_GROW:
first_visible = 0; /* always visible */
last_visible = (size_t)roundf(totpoints * use_fac);
fade_start = (int)roundf(totpoints * use_fade_fac);
last_visible = (size_t)roundf(sumpoints * use_fac);
fade_start = (int)roundf(sumpoints * use_fade_fac);
fade_end = last_visible;
break;
/* Hide in reverse order
* - As fac increases, the number of points visible at the end decreases
*/
/* Hide in reverse order
* - As fac increases, the number of points visible at the end decreases
*/
case GP_BUILD_TRANSITION_SHRINK:
first_visible = 0; /* always visible (until last point removed) */
last_visible = (size_t)(totpoints * (1.0f + set_fade_fac - use_fac));
fade_start = (int)roundf(totpoints * (1.0f - use_fade_fac - set_fade_fac));
last_visible = (size_t)(sumpoints * (1.0f + set_fade_fac - use_fac));
fade_start = (int)roundf(sumpoints * (1.0f - use_fade_fac - set_fade_fac));
fade_end = last_visible;
break;
@ -374,10 +510,10 @@ static void build_sequential(Object *ob,
* - As fac increases, the early points start getting hidden
*/
case GP_BUILD_TRANSITION_VANISH:
first_visible = (size_t)(totpoints * use_fade_fac);
last_visible = totpoints; /* i.e. visible until the end, unless first overlaps this */
first_visible = (size_t)(sumpoints * use_fade_fac);
last_visible = sumpoints; /* i.e. visible until the end, unless first overlaps this */
fade_start = first_visible;
fade_end = (int)roundf(totpoints * use_fac);
fade_end = (int)roundf(sumpoints * use_fac);
break;
}
@ -434,6 +570,9 @@ static void build_sequential(Object *ob,
/* Free table */
MEM_freeN(table);
if (mmd->time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) {
MEM_freeN(idx_times);
}
}
/* --------------------------------------------- */
@ -451,7 +590,7 @@ static void build_concurrent(BuildGpencilModifierData *mmd,
const bool reverse = (mmd->transition != GP_BUILD_TRANSITION_GROW);
/* 1) Determine the longest stroke, to figure out when short strokes should start */
/* FIXME: A *really* long stroke here could dwarf everything else, causing bad timings */
/* Todo: A *really* long stroke here could dwarf everything else, causing bad timings */
for (gps = gpf->strokes.first; gps; gps = gps->next) {
if (gps->totpoints > max_points) {
max_points = gps->totpoints;
@ -565,11 +704,17 @@ static void generate_geometry(GpencilModifierData *md,
bGPDframe *gpf)
{
BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md;
/* Prevent incompatible options at runtime. */
if (mmd->mode == GP_BUILD_MODE_ADDITIVE) {
mmd->transition = GP_BUILD_TRANSITION_GROW;
mmd->start_delay = 0;
}
if (mmd->mode == GP_BUILD_MODE_CONCURRENT && mmd->time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) {
mmd->time_mode = GP_BUILD_TIMEMODE_FRAMES;
}
const bool reverse = (mmd->transition != GP_BUILD_TRANSITION_GROW);
const bool is_percentage = (mmd->flag & GP_BUILD_PERCENTAGE);
const bool is_percentage = (mmd->time_mode == GP_BUILD_TIMEMODE_PERCENTAGE);
const float ctime = DEG_get_ctime(depsgraph);
@ -633,75 +778,78 @@ static void generate_geometry(GpencilModifierData *md,
}
}
/* Compute start and end frames for the animation effect
* By default, the upper bound is given by the "maximum length" setting
/* Default "fac" value to call build_sequential even with
* GP_BUILD_TIMEMODE_DRAWSPEED, which uses separate logic
* in function build_sequential()
*/
float start_frame = is_percentage ? gpf->framenum : gpf->framenum + mmd->start_delay;
/* When use percentage don't need a limit in the upper bound, so use a maximum value for the last
* frame. */
float end_frame = is_percentage ? start_frame + 9999 : start_frame + mmd->length;
float fac = 1;
if (gpf->next) {
/* Use the next frame or upper bound as end frame, whichever is lower/closer */
end_frame = MIN2(end_frame, gpf->next->framenum);
if (mmd->time_mode != GP_BUILD_TIMEMODE_DRAWSPEED) {
/* Compute start and end frames for the animation effect
* By default, the upper bound is given by the "length" setting.
*/
float start_frame = is_percentage ? gpf->framenum : gpf->framenum + mmd->start_delay;
/* When use percentage don't need a limit in the upper bound, so use a maximum value for the
* last frame. */
float end_frame = is_percentage ? start_frame + 9999 : start_frame + mmd->length;
if (gpf->next) {
/* Use the next frame or upper bound as end frame, whichever is lower/closer */
end_frame = MIN2(end_frame, gpf->next->framenum);
}
/* Early exit if current frame is outside start/end bounds */
/* NOTE: If we're beyond the next/previous frames (if existent),
* then we wouldn't have this problem anyway... */
if (ctime < start_frame) {
/* Before Start - Animation hasn't started. Display initial state. */
if (reverse) {
/* 1) Reverse = Start with all, end with nothing.
* ==> Do nothing (everything already present)
*/
}
else {
/* 2) Forward Order = Start with nothing, end with the full frame.
* ==> Free all strokes, and return an empty frame
*/
gpf_clear_all_strokes(gpf);
}
/* Early exit */
return;
}
if (ctime >= end_frame) {
/* Past End - Animation finished. Display final result. */
if (reverse) {
/* 1) Reverse = Start with all, end with nothing.
* ==> Free all strokes, and return an empty frame
*/
gpf_clear_all_strokes(gpf);
}
else {
/* 2) Forward Order = Start with nothing, end with the full frame.
* ==> Do Nothing (everything already present)
*/
}
/* Early exit */
return;
}
/* Determine how far along we are given current time, start_frame and end_frame */
fac = is_percentage ? mmd->percentage_fac : (ctime - start_frame) / (end_frame - start_frame);
}
/* Early exit if current frame is outside start/end bounds */
/* NOTE: If we're beyond the next/previous frames (if existent),
* then we wouldn't have this problem anyway... */
if (ctime < start_frame) {
/* Before Start - Animation hasn't started. Display initial state. */
if (reverse) {
/* 1) Reverse = Start with all, end with nothing.
* ==> Do nothing (everything already present)
*/
}
else {
/* 2) Forward Order = Start with nothing, end with the full frame.
* ==> Free all strokes, and return an empty frame
*/
gpf_clear_all_strokes(gpf);
}
/* Early exit */
return;
}
if (ctime >= end_frame) {
/* Past End - Animation finished. Display final result. */
if (reverse) {
/* 1) Reverse = Start with all, end with nothing.
* ==> Free all strokes, and return an empty frame
*/
gpf_clear_all_strokes(gpf);
}
else {
/* 2) Forward Order = Start with nothing, end with the full frame.
* ==> Do Nothing (everything already present)
*/
}
/* Early exit */
return;
}
/* Determine how far along we are between the keyframes */
float fac = is_percentage ? mmd->percentage_fac :
(ctime - start_frame) / (end_frame - start_frame);
/* Time management mode */
/* Calling the correct build mode */
switch (mmd->mode) {
case GP_BUILD_MODE_SEQUENTIAL:
build_sequential(ob, mmd, gpd, gpf, target_def_nr, fac, false);
case GP_BUILD_MODE_ADDITIVE:
build_sequential(ob, mmd, depsgraph, gpd, gpf, target_def_nr, fac, &ctime);
break;
case GP_BUILD_MODE_CONCURRENT:
build_concurrent(mmd, gpd, gpf, target_def_nr, fac);
break;
case GP_BUILD_MODE_ADDITIVE:
build_sequential(ob, mmd, gpd, gpf, target_def_nr, fac, true);
break;
default:
printf("Unsupported build mode (%d) for GP Build Modifier: '%s'\n",
mmd->mode,
@ -727,49 +875,59 @@ static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Objec
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
{
uiLayout *row, *sub;
uiLayout *layout = panel->layout;
PointerRNA ob_ptr;
PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
int mode = RNA_enum_get(ptr, "mode");
const bool use_percentage = RNA_boolean_get(ptr, "use_percentage");
const int mode = RNA_enum_get(ptr, "mode");
int time_mode = RNA_enum_get(ptr, "time_mode");
uiLayoutSetPropSep(layout, true);
/* First: Build mode and build settings. */
uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE);
if (mode == GP_BUILD_MODE_SEQUENTIAL) {
uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE);
}
if (mode == GP_BUILD_MODE_CONCURRENT) {
/* Concurrent mode doesn't support GP_BUILD_TIMEMODE_DRAWSPEED, so unset it. */
if (time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) {
RNA_enum_set(ptr, "time_mode", GP_BUILD_TIMEMODE_FRAMES);
time_mode = GP_BUILD_TIMEMODE_FRAMES;
}
uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE);
}
uiItemS(layout);
/* Second: Time mode and time settings. */
uiItemR(layout, ptr, "time_mode", 0, NULL, ICON_NONE);
if (mode == GP_BUILD_MODE_CONCURRENT) {
uiItemR(layout, ptr, "concurrent_time_alignment", 0, NULL, ICON_NONE);
}
uiItemS(layout);
if (ELEM(mode, GP_BUILD_MODE_SEQUENTIAL, GP_BUILD_MODE_CONCURRENT)) {
uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE);
switch (time_mode) {
case GP_BUILD_TIMEMODE_DRAWSPEED:
uiItemR(layout, ptr, "speed_factor", 0, NULL, ICON_NONE);
uiItemR(layout, ptr, "speed_maxgap", 0, NULL, ICON_NONE);
break;
case GP_BUILD_TIMEMODE_FRAMES:
uiItemR(layout, ptr, "length", 0, IFACE_("Frames"), ICON_NONE);
if (mode != GP_BUILD_MODE_ADDITIVE) {
uiItemR(layout, ptr, "start_delay", 0, NULL, ICON_NONE);
}
break;
case GP_BUILD_TIMEMODE_PERCENTAGE:
uiItemR(layout, ptr, "percentage_factor", 0, NULL, ICON_NONE);
break;
default:
break;
}
row = uiLayoutRow(layout, true);
uiLayoutSetActive(row, !use_percentage);
uiItemR(row, ptr, "start_delay", 0, NULL, ICON_NONE);
row = uiLayoutRow(layout, true);
uiLayoutSetActive(row, !use_percentage);
uiItemR(row, ptr, "length", 0, IFACE_("Frames"), ICON_NONE);
uiItemS(layout);
uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE);
row = uiLayoutRowWithHeading(layout, true, IFACE_("Factor"));
uiLayoutSetPropDecorate(row, false);
uiItemR(row, ptr, "use_percentage", 0, "", ICON_NONE);
sub = uiLayoutRow(row, true);
uiLayoutSetActive(sub, use_percentage);
uiItemR(sub, ptr, "percentage_factor", 0, "", ICON_NONE);
uiItemDecoratorR(row, ptr, "percentage_factor", 0);
uiItemS(layout);
if (ELEM(mode, GP_BUILD_MODE_SEQUENTIAL, GP_BUILD_MODE_ADDITIVE)) {
uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE);
}
/* Some housekeeping to prevent clashes between incompatible
* options */
/* Check for incompatible time modifier. */
Object *ob = ob_ptr.data;

View File

@ -54,6 +54,9 @@
.mode = 0, \
.transition = 0, \
.time_alignment = 0, \
.time_mode = 0, \
.speed_fac = 1.2f, \
.speed_maxgap = 0.5f, \
.percentage_fac = 0.0f, \
}

View File

@ -452,11 +452,19 @@ typedef struct BuildGpencilModifierData {
short transition;
/**
* (eGpencilBuild_TimeAlignment)
* (eBuildGpencil_TimeAlignment)
* For the "Concurrent" mode, when should "shorter" strips start/end.
*/
short time_alignment;
/* Speed factor for GP_BUILD_TIMEMODE_DRAWSPEED. */
float speed_fac;
/* Maxmium time gap between strokes for GP_BUILD_TIMEMODE_DRAWSPEED. */
float speed_maxgap;
/* Which time mode should be used. */
short time_mode;
char _pad[6];
/** Build origin control object. */
struct Object *object;
@ -499,6 +507,15 @@ typedef enum eBuildGpencil_TimeAlignment {
/* TODO: Random Offsets, Stretch-to-Fill */
} eBuildGpencil_TimeAlignment;
typedef enum eBuildGpencil_TimeMode {
/* Use a number of frames build. */
GP_BUILD_TIMEMODE_FRAMES = 0,
/* Use manual percentage to build. */
GP_BUILD_TIMEMODE_PERCENTAGE = 1,
/* Use factor of recorded speed to build. */
GP_BUILD_TIMEMODE_DRAWSPEED = 2,
} eBuildGpencil_TimeMode;
typedef enum eBuildGpencil_Flag {
/* Restrict modifier to particular layer/passes? */
GP_BUILD_INVERT_LAYER = (1 << 0),
@ -507,10 +524,7 @@ typedef enum eBuildGpencil_Flag {
/* Restrict modifier to only operating between the nominated frames */
GP_BUILD_RESTRICT_TIME = (1 << 2),
GP_BUILD_INVERT_LAYERPASS = (1 << 3),
/* Use a percentage instead of frame number to evaluate strokes. */
GP_BUILD_PERCENTAGE = (1 << 4),
GP_BUILD_USE_FADING = (1 << 5),
GP_BUILD_USE_FADING = (1 << 4),
} eBuildGpencil_Flag;
typedef struct LatticeGpencilModifierData {

View File

@ -158,6 +158,25 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem gpencil_build_time_mode_items[] = {
{GP_BUILD_TIMEMODE_DRAWSPEED,
"DRAWSPEED",
0,
"Natural Drawing Speed",
"Use recorded speed multiplied by a factor"},
{GP_BUILD_TIMEMODE_FRAMES,
"FRAMES",
0,
"Number of Frames",
"Set a fixed number of frames for all build animations"},
{GP_BUILD_TIMEMODE_PERCENTAGE,
"PERCENTAGE",
0,
"Percentage Factor",
"Set a manual percentage to build"},
{0, NULL, 0, NULL, NULL},
};
#ifndef RNA_RUNTIME
static const EnumPropertyItem modifier_modify_color_items[] = {
{GP_MODIFY_COLOR_BOTH, "BOTH", 0, "Stroke & Fill", "Modify fill and stroke colors"},
@ -925,6 +944,33 @@ static void rna_EnvelopeGpencilModifier_material_set(PointerRNA *ptr,
rna_GpencilModifier_material_set(ptr, value, ma_target, reports);
}
const EnumPropertyItem *gpencil_build_time_mode_filter(bContext *UNUSED(C),
PointerRNA *ptr,
PropertyRNA *prop,
bool *r_free)
{
GpencilModifierData *md = ptr->data;
BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md;
const bool is_concurrent = (mmd->mode == GP_BUILD_MODE_CONCURRENT);
EnumPropertyItem *item_list = NULL;
int totitem = 0;
for (const EnumPropertyItem *item = gpencil_build_time_mode_items; item->identifier != NULL;
item++) {
if (is_concurrent && item->identifier == "DRAWSPEED") {
continue;
}
RNA_enum_item_add(&item_list, &totitem, item);
}
RNA_enum_item_end(&item_list, &totitem);
*r_free = true;
return item_list;
}
#else
static void rna_def_modifier_gpencilnoise(BlenderRNA *brna)
@ -2466,7 +2512,7 @@ static void rna_def_modifier_gpencilbuild(BlenderRNA *brna)
/* Mode */
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_gpencil_build_mode_items);
RNA_def_property_ui_text(prop, "Mode", "How many strokes are being animated at a time");
RNA_def_property_ui_text(prop, "Mode", "How strokes are being built");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Direction */
@ -2480,9 +2526,7 @@ static void rna_def_modifier_gpencilbuild(BlenderRNA *brna)
prop = RNA_def_property(srna, "start_delay", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "start_delay");
RNA_def_property_ui_text(
prop,
"Start Delay",
"Number of frames after each GP keyframe before the modifier has any effect");
prop, "Delay", "Number of frames after each GP keyframe before the modifier has any effect");
RNA_def_property_range(prop, 0, MAXFRAMEF);
RNA_def_property_ui_range(prop, 0, 200, 1, -1);
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
@ -2501,8 +2545,35 @@ static void rna_def_modifier_gpencilbuild(BlenderRNA *brna)
prop = RNA_def_property(srna, "concurrent_time_alignment", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "time_alignment");
RNA_def_property_enum_items(prop, prop_gpencil_build_time_align_items);
RNA_def_property_ui_text(prop, "Time Alignment", "How should strokes start to appear/disappear");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Which time mode to use: Current frames, manual percentage, or drawspeed. */
prop = RNA_def_property(srna, "time_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "time_mode");
RNA_def_property_enum_items(prop, gpencil_build_time_mode_items);
RNA_def_property_enum_funcs(prop, NULL, NULL, "gpencil_build_time_mode_filter");
RNA_def_property_ui_text(
prop, "Time Alignment", "When should strokes start to appear/disappear");
prop,
"Timing",
"Use drawing speed, a number of frames, or a manual factor to build strokes");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Speed factor for GP_BUILD_TIMEMODE_DRAWSPEED. */
/* Todo: Does it work? */
prop = RNA_def_property(srna, "speed_factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "speed_fac");
RNA_def_property_ui_text(prop, "Speed Factor", "Multiply recorded drawing speed by a factor");
RNA_def_property_range(prop, 0.0f, 100.0f);
RNA_def_property_ui_range(prop, 0, 5, 0.001, -1);
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Max gap in seconds between strokes for GP_BUILD_TIMEMODE_DRAWSPEED. */
prop = RNA_def_property(srna, "speed_maxgap", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, NULL, "speed_maxgap");
RNA_def_property_ui_text(prop, "Maximum Gap", "The maximum gap between strokes in seconds");
RNA_def_property_range(prop, 0.0f, 100.0f);
RNA_def_property_ui_range(prop, 0, 4, 0.01, -1);
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Time Limits */
@ -2512,9 +2583,9 @@ static void rna_def_modifier_gpencilbuild(BlenderRNA *brna)
prop, "Restrict Frame Range", "Only modify strokes during the specified frame range");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
/* Use percentage */
/* Use percentage bool (used by sequential & concurrent modes) */
prop = RNA_def_property(srna, "use_percentage", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", GP_BUILD_PERCENTAGE);
RNA_def_property_boolean_sdna(prop, NULL, "time_mode", GP_BUILD_TIMEMODE_PERCENTAGE);
RNA_def_property_ui_text(
prop, "Restrict Visible Points", "Use a percentage factor to determine the visible points");
RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");