Changeset View
Changeset View
Standalone View
Standalone View
source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c
| Show First 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
| #include "RNA_access.h" | #include "RNA_access.h" | ||||
| #include "DEG_depsgraph.h" | #include "DEG_depsgraph.h" | ||||
| #include "DEG_depsgraph_query.h" | #include "DEG_depsgraph_query.h" | ||||
| #include "MOD_gpencil_modifiertypes.h" | #include "MOD_gpencil_modifiertypes.h" | ||||
| #include "MOD_gpencil_ui_common.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) | static void initData(GpencilModifierData *md) | ||||
| { | { | ||||
| BuildGpencilModifierData *gpmd = (BuildGpencilModifierData *)md; | BuildGpencilModifierData *gpmd = (BuildGpencilModifierData *)md; | ||||
| BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); | BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); | ||||
| MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(BuildGpencilModifierData), modifier); | MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(BuildGpencilModifierData), modifier); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 183 Lines • ▼ Show 20 Lines | typedef struct tStrokeBuildDetails { | ||||
| /* Number of points - Cache for more convenient access */ | /* Number of points - Cache for more convenient access */ | ||||
| int totpoints; | int totpoints; | ||||
| /* Distance to control object, used to sort the strokes if set. */ | /* Distance to control object, used to sort the strokes if set. */ | ||||
| float distance; | float distance; | ||||
| } tStrokeBuildDetails; | } tStrokeBuildDetails; | ||||
| static int cmp_stroke_build_details(const void *ps1, const void *ps2) | static int cmp_stroke_build_details(const void *ps1, const void *ps2) | ||||
| { | { | ||||
antoniov: In the final patch you shouldn't include `ToDos` | |||||
| tStrokeBuildDetails *p1 = (tStrokeBuildDetails *)ps1; | tStrokeBuildDetails *p1 = (tStrokeBuildDetails *)ps1; | ||||
| tStrokeBuildDetails *p2 = (tStrokeBuildDetails *)ps2; | tStrokeBuildDetails *p2 = (tStrokeBuildDetails *)ps2; | ||||
| return p1->distance > p2->distance ? 1 : (p1->distance == p2->distance ? 0 : -1); | 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, | static void build_sequential(Object *ob, | ||||
| BuildGpencilModifierData *mmd, | BuildGpencilModifierData *mmd, | ||||
| Depsgraph *depsgraph, | |||||
| bGPdata *gpd, | bGPdata *gpd, | ||||
| bGPDframe *gpf, | bGPDframe *gpf, | ||||
| const int target_def_nr, | int target_def_nr, | ||||
Done Inline ActionsDon't use C++ comments // ... use /* */ above the line. antoniov: Don't use C++ comments // ... use /* */ above the line. | |||||
| float fac, | 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 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; | bGPDstroke *gps; | ||||
| /* Recycled counter. */ | |||||
| size_t i; | 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. */ | /* 1) Determine which strokes to start with (& adapt total number of strokes to build). */ | ||||
| if (mmd->mode == GP_BUILD_MODE_ADDITIVE) { | |||||
| if (additive) { | |||||
| if (gpf->prev) { | if (gpf->prev) { | ||||
| start_stroke = BLI_listbase_count(&gpf->prev->strokes); | start_stroke = BLI_listbase_count(&gpf->runtime.gpf_orig->prev->strokes); | ||||
| } | |||||
| else { | |||||
| start_stroke = 0; | |||||
| } | } | ||||
| if (start_stroke <= tot_strokes) { | if (start_stroke <= tot_strokes) { | ||||
| tot_strokes = tot_strokes - start_stroke; | 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! */ | /* NOTE: This assumes that the total number of points won't overflow! */ | ||||
| tStrokeBuildDetails *table = MEM_callocN(sizeof(tStrokeBuildDetails) * tot_strokes, __func__); | 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++) { | for (gps = BLI_findlink(&gpf->strokes, start_stroke), i = 0; gps; gps = gps->next, i++) { | ||||
| tStrokeBuildDetails *cell = &table[i]; | tStrokeBuildDetails *cell = &table[i]; | ||||
| cell->gps = gps; | cell->gps = gps; | ||||
| cell->totpoints = gps->totpoints; | cell->totpoints = gps->totpoints; | ||||
| sumpoints += cell->totpoints; | |||||
| totpoints += cell->totpoints; | |||||
| /* Compute distance to control object if set, and build according to that order. */ | /* Compute distance to control object if set, and build according to that order. */ | ||||
| if (mmd->object) { | if (mmd->object) { | ||||
| float sv1[3], sv2[3]; | float sv1[3], sv2[3]; | ||||
| mul_v3_m4v3(sv1, ob->object_to_world, &gps->points[0].x); | mul_v3_m4v3(sv1, ob->object_to_world, &gps->points[0].x); | ||||
| mul_v3_m4v3(sv2, ob->object_to_world, &gps->points[gps->totpoints - 1].x); | mul_v3_m4v3(sv2, ob->object_to_world, &gps->points[gps->totpoints - 1].x); | ||||
| float dist_l = len_v3v3(sv1, mmd->object->loc); | float dist_l = len_v3v3(sv1, mmd->object->loc); | ||||
| float dist_r = len_v3v3(sv2, mmd->object->loc); | float dist_r = len_v3v3(sv2, mmd->object->loc); | ||||
| if (dist_r < dist_l) { | if (dist_r < dist_l) { | ||||
| BKE_gpencil_stroke_flip(gps); | BKE_gpencil_stroke_flip(gps); | ||||
| cell->distance = dist_r; | cell->distance = dist_r; | ||||
| } | } | ||||
| else { | else { | ||||
| cell->distance = dist_l; | cell->distance = dist_l; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| if (mmd->object) { | if (mmd->object) { | ||||
| qsort(table, tot_strokes, sizeof(tStrokeBuildDetails), cmp_stroke_build_details); | 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++) { | for (i = 0; i < tot_strokes; i++) { | ||||
| tStrokeBuildDetails *cell = &table[i]; | tStrokeBuildDetails *cell = &table[i]; | ||||
| if (i == 0) { | if (i == 0) { | ||||
| cell->start_idx = 0; | cell->start_idx = 0; | ||||
| } | } | ||||
| else { | 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; | 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 first_visible = 0; | ||||
| size_t last_visible = 0; | size_t last_visible = 0; | ||||
| /* Need signed numbers because the representation of fading offset would exceed the beginning and | /* Need signed numbers because the representation of fading offset would exceed the beginning and | ||||
| * the end of offsets. */ | * the end of offsets. */ | ||||
| int fade_start = 0; | int fade_start = 0; | ||||
| int fade_end = 0; | int fade_end = 0; | ||||
| bool fading_enabled = (mmd->flag & GP_BUILD_USE_FADING); | bool fading_enabled = (mmd->flag & GP_BUILD_USE_FADING); | ||||
| float set_fade_fac = fading_enabled ? mmd->fade_fac : 0.0f; | 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; | float use_fade_fac = use_fac - set_fade_fac; | ||||
| CLAMP(use_fade_fac, 0.0f, 1.0f); | CLAMP(use_fade_fac, 0.0f, 1.0f); | ||||
| switch (mmd->transition) { | switch (mmd->transition) { | ||||
| /* Show in forward order | /* Show in forward order | ||||
| * - As fac increases, the number of visible points increases | * - As fac increases, the number of visible points increases | ||||
| */ | */ | ||||
| case GP_BUILD_TRANSITION_GROW: | case GP_BUILD_TRANSITION_GROW: | ||||
| first_visible = 0; /* always visible */ | first_visible = 0; /* always visible */ | ||||
| last_visible = (size_t)roundf(totpoints * use_fac); | last_visible = (size_t)roundf(sumpoints * use_fac); | ||||
| fade_start = (int)roundf(totpoints * use_fade_fac); | fade_start = (int)roundf(sumpoints * use_fade_fac); | ||||
| fade_end = last_visible; | fade_end = last_visible; | ||||
| break; | break; | ||||
| /* Hide in reverse order | /* Hide in reverse order | ||||
| * - As fac increases, the number of points visible at the end decreases | * - As fac increases, the number of points visible at the end decreases | ||||
| */ | */ | ||||
| case GP_BUILD_TRANSITION_SHRINK: | case GP_BUILD_TRANSITION_SHRINK: | ||||
| first_visible = 0; /* always visible (until last point removed) */ | first_visible = 0; /* always visible (until last point removed) */ | ||||
| last_visible = (size_t)(totpoints * (1.0f + set_fade_fac - use_fac)); | last_visible = (size_t)(sumpoints * (1.0f + set_fade_fac - use_fac)); | ||||
| fade_start = (int)roundf(totpoints * (1.0f - use_fade_fac - set_fade_fac)); | fade_start = (int)roundf(sumpoints * (1.0f - use_fade_fac - set_fade_fac)); | ||||
| fade_end = last_visible; | fade_end = last_visible; | ||||
| break; | break; | ||||
| /* Hide in forward order | /* Hide in forward order | ||||
| * - As fac increases, the early points start getting hidden | * - As fac increases, the early points start getting hidden | ||||
| */ | */ | ||||
| case GP_BUILD_TRANSITION_VANISH: | case GP_BUILD_TRANSITION_VANISH: | ||||
| first_visible = (size_t)(totpoints * use_fade_fac); | first_visible = (size_t)(sumpoints * use_fade_fac); | ||||
| last_visible = totpoints; /* i.e. visible until the end, unless first overlaps this */ | last_visible = sumpoints; /* i.e. visible until the end, unless first overlaps this */ | ||||
| fade_start = first_visible; | fade_start = first_visible; | ||||
| fade_end = (int)roundf(totpoints * use_fac); | fade_end = (int)roundf(sumpoints * use_fac); | ||||
| break; | break; | ||||
| } | } | ||||
| /* 4) Go through all strokes, deciding which to keep, and/or how much of each to keep */ | /* 4) Go through all strokes, deciding which to keep, and/or how much of each to keep */ | ||||
| for (i = 0; i < tot_strokes; i++) { | for (i = 0; i < tot_strokes; i++) { | ||||
| tStrokeBuildDetails *cell = &table[i]; | tStrokeBuildDetails *cell = &table[i]; | ||||
| /* Determine what portion of the stroke is visible */ | /* Determine what portion of the stroke is visible */ | ||||
| Show All 40 Lines | else { | ||||
| int points_num = last_visible - cell->start_idx; | int points_num = last_visible - cell->start_idx; | ||||
| reduce_stroke_points(gpd, gpf, cell->gps, points_num, mmd->transition); | reduce_stroke_points(gpd, gpf, cell->gps, points_num, mmd->transition); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* Free table */ | /* Free table */ | ||||
| MEM_freeN(table); | MEM_freeN(table); | ||||
| if (mmd->time_mode == GP_BUILD_TIMEMODE_DRAWSPEED) { | |||||
| MEM_freeN(idx_times); | |||||
| } | |||||
| } | } | ||||
| /* --------------------------------------------- */ | /* --------------------------------------------- */ | ||||
| /* Concurrent - Show multiple strokes at once */ | /* Concurrent - Show multiple strokes at once */ | ||||
| static void build_concurrent(BuildGpencilModifierData *mmd, | static void build_concurrent(BuildGpencilModifierData *mmd, | ||||
| bGPdata *gpd, | bGPdata *gpd, | ||||
| bGPDframe *gpf, | bGPDframe *gpf, | ||||
| const int target_def_nr, | const int target_def_nr, | ||||
| float fac) | float fac) | ||||
| { | { | ||||
| bGPDstroke *gps, *gps_next; | bGPDstroke *gps, *gps_next; | ||||
| int max_points = 0; | int max_points = 0; | ||||
| const bool reverse = (mmd->transition != GP_BUILD_TRANSITION_GROW); | const bool reverse = (mmd->transition != GP_BUILD_TRANSITION_GROW); | ||||
| /* 1) Determine the longest stroke, to figure out when short strokes should start */ | /* 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) { | for (gps = gpf->strokes.first; gps; gps = gps->next) { | ||||
| if (gps->totpoints > max_points) { | if (gps->totpoints > max_points) { | ||||
| max_points = gps->totpoints; | max_points = gps->totpoints; | ||||
| } | } | ||||
| } | } | ||||
| if (max_points == 0) { | if (max_points == 0) { | ||||
| printf("ERROR: Strokes are all empty (GP Build Modifier: %s)\n", __func__); | printf("ERROR: Strokes are all empty (GP Build Modifier: %s)\n", __func__); | ||||
| return; | return; | ||||
| ▲ Show 20 Lines • Show All 97 Lines • ▼ Show 20 Lines | |||||
| static void generate_geometry(GpencilModifierData *md, | static void generate_geometry(GpencilModifierData *md, | ||||
| Depsgraph *depsgraph, | Depsgraph *depsgraph, | ||||
| Object *ob, | Object *ob, | ||||
| bGPdata *gpd, | bGPdata *gpd, | ||||
| bGPDlayer *gpl, | bGPDlayer *gpl, | ||||
| bGPDframe *gpf) | bGPDframe *gpf) | ||||
| { | { | ||||
| BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md; | BuildGpencilModifierData *mmd = (BuildGpencilModifierData *)md; | ||||
| /* Prevent incompatible options at runtime. */ | |||||
| if (mmd->mode == GP_BUILD_MODE_ADDITIVE) { | if (mmd->mode == GP_BUILD_MODE_ADDITIVE) { | ||||
| mmd->transition = GP_BUILD_TRANSITION_GROW; | 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 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); | const float ctime = DEG_get_ctime(depsgraph); | ||||
| /* Early exit if it's an empty frame */ | /* Early exit if it's an empty frame */ | ||||
| if (gpf->strokes.first == NULL) { | if (gpf->strokes.first == NULL) { | ||||
| return; | return; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | static void generate_geometry(GpencilModifierData *md, | ||||
| * (e.g. to have one forward, and one backwards modifier) | * (e.g. to have one forward, and one backwards modifier) | ||||
| */ | */ | ||||
| if (mmd->flag & GP_BUILD_RESTRICT_TIME) { | if (mmd->flag & GP_BUILD_RESTRICT_TIME) { | ||||
| if ((ctime < mmd->start_frame) || (ctime > mmd->end_frame)) { | if ((ctime < mmd->start_frame) || (ctime > mmd->end_frame)) { | ||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| /* Default "fac" value to call build_sequential even with | |||||
| * GP_BUILD_TIMEMODE_DRAWSPEED, which uses separate logic | |||||
| * in function build_sequential() | |||||
| */ | |||||
| float fac = 1; | |||||
| if (mmd->time_mode != GP_BUILD_TIMEMODE_DRAWSPEED) { | |||||
| /* Compute start and end frames for the animation effect | /* Compute start and end frames for the animation effect | ||||
| * By default, the upper bound is given by the "maximum length" setting | * By default, the upper bound is given by the "length" setting. | ||||
| */ | */ | ||||
| float start_frame = is_percentage ? gpf->framenum : gpf->framenum + mmd->start_delay; | 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 | /* When use percentage don't need a limit in the upper bound, so use a maximum value for the | ||||
| * frame. */ | * last frame. */ | ||||
| float end_frame = is_percentage ? start_frame + 9999 : start_frame + mmd->length; | float end_frame = is_percentage ? start_frame + 9999 : start_frame + mmd->length; | ||||
| if (gpf->next) { | if (gpf->next) { | ||||
| /* Use the next frame or upper bound as end frame, whichever is lower/closer */ | /* Use the next frame or upper bound as end frame, whichever is lower/closer */ | ||||
| end_frame = MIN2(end_frame, gpf->next->framenum); | end_frame = MIN2(end_frame, gpf->next->framenum); | ||||
| } | } | ||||
| /* Early exit if current frame is outside start/end bounds */ | /* Early exit if current frame is outside start/end bounds */ | ||||
| /* NOTE: If we're beyond the next/previous frames (if existent), | /* NOTE: If we're beyond the next/previous frames (if existent), | ||||
| * then we wouldn't have this problem anyway... */ | * then we wouldn't have this problem anyway... */ | ||||
| if (ctime < start_frame) { | if (ctime < start_frame) { | ||||
| /* Before Start - Animation hasn't started. Display initial state. */ | /* Before Start - Animation hasn't started. Display initial state. */ | ||||
| if (reverse) { | if (reverse) { | ||||
| /* 1) Reverse = Start with all, end with nothing. | /* 1) Reverse = Start with all, end with nothing. | ||||
| * ==> Do nothing (everything already present) | * ==> Do nothing (everything already present) | ||||
| */ | */ | ||||
| } | } | ||||
| else { | else { | ||||
| /* 2) Forward Order = Start with nothing, end with the full frame. | /* 2) Forward Order = Start with nothing, end with the full frame. | ||||
| * ==> Free all strokes, and return an empty frame | * ==> Free all strokes, and return an empty frame | ||||
| */ | */ | ||||
| gpf_clear_all_strokes(gpf); | gpf_clear_all_strokes(gpf); | ||||
| } | } | ||||
| /* Early exit */ | /* Early exit */ | ||||
| return; | return; | ||||
| } | } | ||||
| if (ctime >= end_frame) { | if (ctime >= end_frame) { | ||||
| /* Past End - Animation finished. Display final result. */ | /* Past End - Animation finished. Display final result. */ | ||||
| if (reverse) { | if (reverse) { | ||||
| /* 1) Reverse = Start with all, end with nothing. | /* 1) Reverse = Start with all, end with nothing. | ||||
| * ==> Free all strokes, and return an empty frame | * ==> Free all strokes, and return an empty frame | ||||
| */ | */ | ||||
| gpf_clear_all_strokes(gpf); | gpf_clear_all_strokes(gpf); | ||||
| } | } | ||||
| else { | else { | ||||
| /* 2) Forward Order = Start with nothing, end with the full frame. | /* 2) Forward Order = Start with nothing, end with the full frame. | ||||
| * ==> Do Nothing (everything already present) | * ==> Do Nothing (everything already present) | ||||
| */ | */ | ||||
| } | } | ||||
| /* Early exit */ | /* Early exit */ | ||||
| return; | 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); | |||||
| } | |||||
| /* Determine how far along we are between the keyframes */ | /* Calling the correct build mode */ | ||||
| float fac = is_percentage ? mmd->percentage_fac : | |||||
| (ctime - start_frame) / (end_frame - start_frame); | |||||
| /* Time management mode */ | |||||
| switch (mmd->mode) { | switch (mmd->mode) { | ||||
| case GP_BUILD_MODE_SEQUENTIAL: | 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; | break; | ||||
| case GP_BUILD_MODE_CONCURRENT: | case GP_BUILD_MODE_CONCURRENT: | ||||
| build_concurrent(mmd, gpd, gpf, target_def_nr, fac); | build_concurrent(mmd, gpd, gpf, target_def_nr, fac); | ||||
| break; | break; | ||||
| case GP_BUILD_MODE_ADDITIVE: | |||||
| build_sequential(ob, mmd, gpd, gpf, target_def_nr, fac, true); | |||||
| break; | |||||
| default: | default: | ||||
| printf("Unsupported build mode (%d) for GP Build Modifier: '%s'\n", | printf("Unsupported build mode (%d) for GP Build Modifier: '%s'\n", | ||||
| mmd->mode, | mmd->mode, | ||||
| mmd->modifier.name); | mmd->modifier.name); | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| Show All 9 Lines | if (gpf == NULL) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| generate_geometry(md, depsgraph, ob, gpd, gpl, gpf); | generate_geometry(md, depsgraph, ob, gpd, gpl, gpf); | ||||
| } | } | ||||
| } | } | ||||
| static void panel_draw(const bContext *UNUSED(C), Panel *panel) | static void panel_draw(const bContext *UNUSED(C), Panel *panel) | ||||
| { | { | ||||
| uiLayout *row, *sub; | |||||
| uiLayout *layout = panel->layout; | uiLayout *layout = panel->layout; | ||||
| PointerRNA ob_ptr; | PointerRNA ob_ptr; | ||||
| PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); | PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); | ||||
| int mode = RNA_enum_get(ptr, "mode"); | const int mode = RNA_enum_get(ptr, "mode"); | ||||
| const bool use_percentage = RNA_boolean_get(ptr, "use_percentage"); | int time_mode = RNA_enum_get(ptr, "time_mode"); | ||||
| uiLayoutSetPropSep(layout, true); | uiLayoutSetPropSep(layout, true); | ||||
| /* First: Build mode and build settings. */ | |||||
| uiItemR(layout, ptr, "mode", 0, NULL, ICON_NONE); | 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) { | if (mode == GP_BUILD_MODE_CONCURRENT) { | ||||
| uiItemR(layout, ptr, "concurrent_time_alignment", 0, NULL, ICON_NONE); | /* 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; | |||||
| } | } | ||||
| uiItemS(layout); | |||||
| if (ELEM(mode, GP_BUILD_MODE_SEQUENTIAL, GP_BUILD_MODE_CONCURRENT)) { | |||||
| uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE); | uiItemR(layout, ptr, "transition", 0, NULL, ICON_NONE); | ||||
| } | } | ||||
| 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); | uiItemS(layout); | ||||
| row = uiLayoutRowWithHeading(layout, true, IFACE_("Factor")); | /* Second: Time mode and time settings. */ | ||||
| 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); | |||||
| 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); | |||||
| } | |||||
| 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; | |||||
| } | |||||
| uiItemS(layout); | uiItemS(layout); | ||||
| if (ELEM(mode, GP_BUILD_MODE_SEQUENTIAL, GP_BUILD_MODE_ADDITIVE)) { | |||||
| uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); | uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); | ||||
| } | |||||
| /* Some housekeeping to prevent clashes between incompatible | |||||
| * options */ | |||||
| /* Check for incompatible time modifier. */ | /* Check for incompatible time modifier. */ | ||||
| Object *ob = ob_ptr.data; | Object *ob = ob_ptr.data; | ||||
| GpencilModifierData *md = ptr->data; | GpencilModifierData *md = ptr->data; | ||||
| if (BKE_gpencil_modifiers_findby_type(ob, eGpencilModifierType_Time) != NULL) { | if (BKE_gpencil_modifiers_findby_type(ob, eGpencilModifierType_Time) != NULL) { | ||||
| BKE_gpencil_modifier_set_error(md, "Build and Time Offset modifiers are incompatible"); | BKE_gpencil_modifier_set_error(md, "Build and Time Offset modifiers are incompatible"); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 121 Lines • Show Last 20 Lines | |||||
In the final patch you shouldn't include ToDos