This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/gpencil/gpencil_interpolate.c
Antonio Vazquez 459af75d1e GPencil: New Shrinkwrap modifier
his new modifier is equals to the existing mesh modifier but adapted to grease pencil.

The underlying functions used to calculate the shrink are the same used in meshes.

{F11794101}

Reviewed By: pepeland, HooglyBoogly

Differential Revision: https://developer.blender.org/D13192
2021-12-13 17:09:22 +01:00

1828 lines
58 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2016, Blender Foundation
* This is a new part of Blender
* Operators for interpolating new Grease Pencil frames from existing strokes
*/
/** \file
* \ingroup edgpencil
*/
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_easing.h"
#include "BLI_ghash.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_color_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 "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_report.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "WM_api.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "ED_gpencil.h"
#include "ED_screen.h"
#include "DEG_depsgraph.h"
#include "gpencil_intern.h"
/* Temporary interpolate operation data */
typedef struct tGPDinterpolate_layer {
struct tGPDinterpolate_layer *next, *prev;
/** layer */
struct bGPDlayer *gpl;
/** frame before current frame (interpolate-from) */
struct bGPDframe *prevFrame;
/** frame after current frame (interpolate-to) */
struct bGPDframe *nextFrame;
/** interpolated frame */
struct bGPDframe *interFrame;
/** interpolate factor */
float factor;
/* List of strokes and Hash tablets to create temp relationship between strokes. */
struct ListBase selected_strokes;
struct GHash *used_strokes;
struct GHash *pair_strokes;
} tGPDinterpolate_layer;
typedef struct tGPDinterpolate {
/** Current depsgraph from context */
struct Depsgraph *depsgraph;
/** current scene from context */
struct Scene *scene;
/** area where painting originated */
struct ScrArea *area;
/** region where painting originated */
struct ARegion *region;
/** current object */
struct Object *ob;
/** current GP datablock */
struct bGPdata *gpd;
/** current material */
struct Material *mat;
/* Space Conversion Data */
struct GP_SpaceConversion gsc;
/** current frame number */
int cframe;
/** (tGPDinterpolate_layer) layers to be interpolated */
ListBase ilayers;
/** value for determining the displacement influence */
float shift;
/** initial interpolation factor for active layer */
float init_factor;
/** shift low limit (-100%) */
float low_limit;
/** shift upper limit (200%) */
float high_limit;
/** flag from toolsettings */
int flag;
/** Flip mode. */
int flipmode;
/** smooth factor */
float smooth_factor;
/** smooth iterations */
int smooth_steps;
NumInput num; /* numeric input */
} tGPDinterpolate;
typedef enum eGP_InterpolateFlipMode {
/* No flip. */
GP_INTERPOLATE_NOFLIP = 0,
/* Flip always. */
GP_INTERPOLATE_FLIP = 1,
/* Flip if needed. */
GP_INTERPOLATE_FLIPAUTO = 2,
} eGP_InterpolateFlipMode;
/* ************************************************ */
/* Core/Shared Utilities */
/* Poll callback for interpolation operators */
static bool gpencil_view3d_poll(bContext *C)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
/* only 3D view */
ScrArea *area = CTX_wm_area(C);
if (area && area->spacetype != SPACE_VIEW3D) {
return false;
}
/* need data to interpolate */
if (ELEM(NULL, gpd, gpl)) {
return false;
}
return true;
}
/* Return if the stroke must be flipped or not. The logic of the calculation
* is to check if the lines from extremes crossed. All is done in 2D. */
static bool gpencil_stroke_need_flip(Depsgraph *depsgraph,
Object *ob,
bGPDlayer *gpl,
GP_SpaceConversion *gsc,
bGPDstroke *gps_from,
bGPDstroke *gps_to)
{
float diff_mat[4][4];
/* calculate parent matrix */
BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
bGPDspoint *pt, pt_dummy_ps;
float v_from_start[2], v_to_start[2], v_from_end[2], v_to_end[2];
/* Line from start of strokes. */
pt = &gps_from->points[0];
gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v_from_start[0], &v_from_start[1]);
pt = &gps_to->points[0];
gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v_to_start[0], &v_to_start[1]);
/* Line from end of strokes. */
pt = &gps_from->points[gps_from->totpoints - 1];
gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v_from_end[0], &v_from_end[1]);
pt = &gps_to->points[gps_to->totpoints - 1];
gpencil_point_to_parent_space(pt, diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps_from, &pt_dummy_ps, &v_to_end[0], &v_to_end[1]);
const bool isect_lines = (isect_seg_seg_v2(v_from_start, v_to_start, v_from_end, v_to_end) ==
ISECT_LINE_LINE_CROSS);
/* If the vectors intersect. */
if (isect_lines) {
/* For sharp angles, check distance between extremes. */
float v1[2], v2[2];
sub_v2_v2v2(v1, v_to_start, v_from_start);
sub_v2_v2v2(v2, v_to_end, v_from_end);
float angle = angle_v2v2(v1, v2);
if (angle < DEG2RADF(15.0f)) {
/* Check the original stroke orientation using a point of destination stroke
* `(S)<--??-->(E) <--->`. */
float dist_start = len_squared_v2v2(v_from_start, v_to_start);
float dist_end = len_squared_v2v2(v_from_end, v_to_start);
/* Oriented with end nearer of destination stroke.
* `(S)--->(E) <--->` */
if (dist_start >= dist_end) {
dist_start = len_squared_v2v2(v_from_end, v_to_start);
dist_end = len_squared_v2v2(v_from_end, v_to_end);
/* `(S)--->(E) (E)<---(S)` */
return (dist_start >= dist_end);
}
/* Oriented inversed with original stroke start near of destination stroke.
* `(E)<----(S) <--->` */
dist_start = len_squared_v2v2(v_from_start, v_to_start);
dist_end = len_squared_v2v2(v_from_start, v_to_end);
/* `(E)<---(S) (S)--->(E)` */
return (dist_start < dist_end);
}
return true;
}
/* Check that both vectors have the same direction. */
float v1[2], v2[2];
sub_v2_v2v2(v1, v_from_end, v_from_start);
sub_v2_v2v2(v2, v_to_end, v_to_start);
mul_v2_v2v2(v1, v1, v2);
if ((v1[0] < 0.0f) && (v1[1] < 0.0f)) {
return true;
}
return false;
}
/* Return the stroke related to the selection index, returning the stroke with
* the smallest selection index greater than reference index. */
static bGPDstroke *gpencil_stroke_get_related(GHash *used_strokes,
bGPDframe *gpf,
const int reference_index)
{
bGPDstroke *gps_found = NULL;
int lower_index = INT_MAX;
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->select_index > reference_index) {
if (!BLI_ghash_haskey(used_strokes, gps)) {
if (gps->select_index < lower_index) {
lower_index = gps->select_index;
gps_found = gps;
}
}
}
}
/* Set as used. */
if (gps_found) {
BLI_ghash_insert(used_strokes, gps_found, gps_found);
}
return gps_found;
}
/* Load a Hash with the relationship between strokes. */
static void gpencil_stroke_pair_table(bContext *C,
tGPDinterpolate *tgpi,
tGPDinterpolate_layer *tgpil)
{
bGPdata *gpd = tgpi->gpd;
const bool only_selected = (GPENCIL_EDIT_MODE(gpd) &&
((tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED) != 0));
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
/* Create hash tablets with relationship between strokes. */
BLI_listbase_clear(&tgpil->selected_strokes);
tgpil->used_strokes = BLI_ghash_ptr_new(__func__);
tgpil->pair_strokes = BLI_ghash_ptr_new(__func__);
/* Create a table with source and target pair of strokes. */
LISTBASE_FOREACH (bGPDstroke *, gps_from, &tgpil->prevFrame->strokes) {
bGPDstroke *gps_to = NULL;
/* only selected */
if (GPENCIL_EDIT_MODE(gpd) && (only_selected) && ((gps_from->flag & GP_STROKE_SELECT) == 0)) {
continue;
}
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
continue;
}
/* Check if the material is editable. */
if (ED_gpencil_stroke_material_editable(tgpi->ob, tgpil->gpl, gps_from) == false) {
continue;
}
/* Try to get the related stroke. */
if ((is_multiedit) && (gps_from->select_index > 0)) {
gps_to = gpencil_stroke_get_related(
tgpil->used_strokes, tgpil->nextFrame, gps_from->select_index);
}
/* If not found, get final stroke to interpolate using position in the array. */
if (gps_to == NULL) {
int fFrame = BLI_findindex(&tgpil->prevFrame->strokes, gps_from);
gps_to = BLI_findlink(&tgpil->nextFrame->strokes, fFrame);
}
if (ELEM(NULL, gps_from, gps_to)) {
continue;
}
if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) {
continue;
}
/* Insert the pair entry in the hash table and the list of strokes to keep order. */
BLI_addtail(&tgpil->selected_strokes, BLI_genericNodeN(gps_from));
BLI_ghash_insert(tgpil->pair_strokes, gps_from, gps_to);
}
}
static void gpencil_interpolate_smooth_stroke(bGPDstroke *gps,
float smooth_factor,
int smooth_steps)
{
if (smooth_factor == 0.0f) {
return;
}
float reduce = 0.0f;
for (int r = 0; r < smooth_steps; r++) {
for (int i = 0; i < gps->totpoints - 1; i++) {
BKE_gpencil_stroke_smooth_point(gps, i, smooth_factor - reduce, false);
BKE_gpencil_stroke_smooth_strength(gps, i, smooth_factor);
}
reduce += 0.25f; /* reduce the factor */
}
}
/* Perform interpolation */
static void gpencil_interpolate_update_points(const bGPDstroke *gps_from,
const bGPDstroke *gps_to,
bGPDstroke *new_stroke,
float factor)
{
/* update points */
for (int i = 0; i < new_stroke->totpoints; i++) {
const bGPDspoint *prev = &gps_from->points[i];
const bGPDspoint *next = &gps_to->points[i];
bGPDspoint *pt = &new_stroke->points[i];
/* Interpolate all values */
interp_v3_v3v3(&pt->x, &prev->x, &next->x, factor);
pt->pressure = interpf(prev->pressure, next->pressure, 1.0f - factor);
pt->strength = interpf(prev->strength, next->strength, 1.0f - factor);
CLAMP(pt->strength, GPENCIL_STRENGTH_MIN, 1.0f);
}
}
/* ****************** Interpolate Interactive *********************** */
/* Helper: free all temp strokes for display. */
static void gpencil_interpolate_free_tagged_strokes(bGPDframe *gpf)
{
if (gpf == NULL) {
return;
}
LISTBASE_FOREACH_MUTABLE (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_TAG) {
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
}
}
}
/* Helper: Untag all strokes. */
static void gpencil_interpolate_untag_strokes(bGPDlayer *gpl)
{
if (gpl == NULL) {
return;
}
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_TAG) {
gps->flag &= ~GP_STROKE_TAG;
}
}
}
}
/* Helper: Update all strokes interpolated */
static void gpencil_interpolate_update_strokes(bContext *C, tGPDinterpolate *tgpi)
{
bGPdata *gpd = tgpi->gpd;
const float shift = tgpi->shift;
LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) {
const float factor = tgpil->factor + shift;
bGPDframe *gpf = tgpil->gpl->actframe;
/* Free temp strokes used for display. */
gpencil_interpolate_free_tagged_strokes(gpf);
/* Clear previous interpolations. */
gpencil_interpolate_free_tagged_strokes(tgpil->interFrame);
LISTBASE_FOREACH (LinkData *, link, &tgpil->selected_strokes) {
bGPDstroke *gps_from = link->data;
if (!BLI_ghash_haskey(tgpil->pair_strokes, gps_from)) {
continue;
}
bGPDstroke *gps_to = (bGPDstroke *)BLI_ghash_lookup(tgpil->pair_strokes, gps_from);
/* Create new stroke. */
bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true);
new_stroke->flag |= GP_STROKE_TAG;
new_stroke->select_index = 0;
/* Update points position. */
gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
/* Add to strokes. */
BLI_addtail(&tgpil->interFrame->strokes, new_stroke);
/* Add temp strokes to display. */
if (gpf) {
bGPDstroke *gps_eval = BKE_gpencil_stroke_duplicate(new_stroke, true, true);
gps_eval->flag |= GP_STROKE_TAG;
BLI_addtail(&gpf->strokes, gps_eval);
}
}
}
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
}
/* Helper: Get previous keyframe (exclude breakdown type). */
static bGPDframe *gpencil_get_previous_keyframe(bGPDlayer *gpl, int cfra)
{
if (gpl->actframe != NULL && gpl->actframe->framenum < cfra &&
gpl->actframe->key_type != BEZT_KEYTYPE_BREAKDOWN) {
return gpl->actframe;
}
LISTBASE_FOREACH_BACKWARD (bGPDframe *, gpf, &gpl->frames) {
if (gpf->key_type == BEZT_KEYTYPE_BREAKDOWN) {
continue;
}
if (gpf->framenum >= cfra) {
continue;
}
return gpf;
}
return NULL;
}
/* Helper: Get next keyframe (exclude breakdown type). */
static bGPDframe *gpencil_get_next_keyframe(bGPDlayer *gpl, int cfra)
{
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
if (gpf->key_type == BEZT_KEYTYPE_BREAKDOWN) {
continue;
}
if (gpf->framenum <= cfra) {
continue;
}
return gpf;
}
return NULL;
}
/* Helper: Create internal strokes interpolated */
static void gpencil_interpolate_set_points(bContext *C, tGPDinterpolate *tgpi)
{
Scene *scene = tgpi->scene;
bGPdata *gpd = tgpi->gpd;
bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
bGPDframe *actframe = active_gpl->actframe;
/* save initial factor for active layer to define shift limits */
tgpi->init_factor = (float)(tgpi->cframe - actframe->framenum) /
(actframe->next->framenum - actframe->framenum + 1);
/* limits are 100% below 0 and 100% over the 100% */
tgpi->low_limit = -1.0f - tgpi->init_factor;
tgpi->high_limit = 2.0f - tgpi->init_factor;
/* set layers */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
tGPDinterpolate_layer *tgpil;
/* all layers or only active */
if (!(tgpi->flag & GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS) && (gpl != active_gpl)) {
continue;
}
/* only editable and visible layers are considered */
if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
continue;
}
if ((gpl->actframe == NULL) || (gpl->actframe->next == NULL)) {
continue;
}
/* create temp data for each layer */
tgpil = MEM_callocN(sizeof(tGPDinterpolate_layer), "GPencil Interpolate Layer");
tgpil->gpl = gpl;
bGPDframe *gpf = gpencil_get_previous_keyframe(gpl, CFRA);
tgpil->prevFrame = BKE_gpencil_frame_duplicate(gpf, true);
gpf = gpencil_get_next_keyframe(gpl, CFRA);
tgpil->nextFrame = BKE_gpencil_frame_duplicate(gpf, true);
BLI_addtail(&tgpi->ilayers, tgpil);
/* Create a new temporary frame. */
tgpil->interFrame = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
tgpil->interFrame->framenum = tgpi->cframe;
/* get interpolation factor by layer (usually must be equal for all layers, but not sure) */
tgpil->factor = (float)(tgpi->cframe - tgpil->prevFrame->framenum) /
(tgpil->nextFrame->framenum - tgpil->prevFrame->framenum + 1);
/* Load the relationship between frames. */
gpencil_stroke_pair_table(C, tgpi, tgpil);
/* Create new strokes data with interpolated points reading original stroke. */
LISTBASE_FOREACH (LinkData *, link, &tgpil->selected_strokes) {
bGPDstroke *gps_from = link->data;
if (!BLI_ghash_haskey(tgpil->pair_strokes, gps_from)) {
continue;
}
bGPDstroke *gps_to = (bGPDstroke *)BLI_ghash_lookup(tgpil->pair_strokes, gps_from);
/* If destination stroke is smaller, resize new_stroke to size of gps_to stroke. */
if (gps_from->totpoints > gps_to->totpoints) {
BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true);
}
if (gps_to->totpoints > gps_from->totpoints) {
BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true);
}
/* Flip stroke. */
if (tgpi->flipmode == GP_INTERPOLATE_FLIP) {
BKE_gpencil_stroke_flip(gps_to);
}
else if (tgpi->flipmode == GP_INTERPOLATE_FLIPAUTO) {
if (gpencil_stroke_need_flip(
tgpi->depsgraph, tgpi->ob, gpl, &tgpi->gsc, gps_from, gps_to)) {
BKE_gpencil_stroke_flip(gps_to);
}
}
/* Create new stroke. */
bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true);
new_stroke->flag |= GP_STROKE_TAG;
new_stroke->select_index = 0;
/* Update points position. */
gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, tgpil->factor);
gpencil_interpolate_smooth_stroke(new_stroke, tgpi->smooth_factor, tgpi->smooth_steps);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
/* add to strokes */
BLI_addtail(&tgpil->interFrame->strokes, new_stroke);
}
}
}
/* ----------------------- */
/* Helper: calculate shift based on position of mouse (we only use x-axis for now.
* since this is more convenient for users to do), and store new shift value
*/
static void gpencil_mouse_update_shift(tGPDinterpolate *tgpi, wmOperator *op, const wmEvent *event)
{
float mid = (float)(tgpi->region->winx - tgpi->region->winrct.xmin) / 2.0f;
float mpos = event->xy[0] - tgpi->region->winrct.xmin;
if (mpos >= mid) {
tgpi->shift = ((mpos - mid) * tgpi->high_limit) / mid;
}
else {
tgpi->shift = tgpi->low_limit - ((mpos * tgpi->low_limit) / mid);
}
CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
RNA_float_set(op->ptr, "shift", tgpi->shift);
}
/* Helper: Draw status message while the user is running the operator */
static void gpencil_interpolate_status_indicators(bContext *C, tGPDinterpolate *p)
{
Scene *scene = p->scene;
char status_str[UI_MAX_DRAW_STR];
char msg_str[UI_MAX_DRAW_STR];
BLI_strncpy(msg_str, TIP_("GPencil Interpolation: "), UI_MAX_DRAW_STR);
if (hasNumInput(&p->num)) {
char str_ofs[NUM_STR_REP_LEN];
outputNumInput(&p->num, str_ofs, &scene->unit);
BLI_snprintf(status_str, sizeof(status_str), "%s%s", msg_str, str_ofs);
}
else {
BLI_snprintf(status_str,
sizeof(status_str),
"%s%d %%",
msg_str,
(int)((p->init_factor + p->shift) * 100.0f));
}
ED_area_status_text(p->area, status_str);
ED_workspace_status_text(
C, TIP_("ESC/RMB to cancel, Enter/LMB to confirm, WHEEL/MOVE to adjust factor"));
}
/* Update screen and stroke */
static void gpencil_interpolate_update(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
{
/* update shift indicator in header */
gpencil_interpolate_status_indicators(C, tgpi);
/* apply... */
tgpi->shift = RNA_float_get(op->ptr, "shift");
/* update points position */
gpencil_interpolate_update_strokes(C, tgpi);
}
/* ----------------------- */
/* Exit and free memory */
static void gpencil_interpolate_exit(bContext *C, wmOperator *op)
{
tGPDinterpolate *tgpi = op->customdata;
bGPdata *gpd = tgpi->gpd;
/* don't assume that operator data exists at all */
if (tgpi) {
/* clear status message area */
ED_area_status_text(tgpi->area, NULL);
ED_workspace_status_text(C, NULL);
/* Clear any temp stroke. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
gpencil_interpolate_free_tagged_strokes(gpf);
}
}
/* finally, free memory used by temp data */
LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) {
BKE_gpencil_free_strokes(tgpil->prevFrame);
BKE_gpencil_free_strokes(tgpil->nextFrame);
BKE_gpencil_free_strokes(tgpil->interFrame);
MEM_SAFE_FREE(tgpil->prevFrame);
MEM_SAFE_FREE(tgpil->nextFrame);
MEM_SAFE_FREE(tgpil->interFrame);
/* Free list of strokes. */
BLI_freelistN(&tgpil->selected_strokes);
/* Free Hash tablets. */
if (tgpil->used_strokes != NULL) {
BLI_ghash_free(tgpil->used_strokes, NULL, NULL);
}
if (tgpil->pair_strokes != NULL) {
BLI_ghash_free(tgpil->pair_strokes, NULL, NULL);
}
}
BLI_freelistN(&tgpi->ilayers);
MEM_SAFE_FREE(tgpi);
}
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
/* clear pointer */
op->customdata = NULL;
}
/* Init new temporary interpolation data */
static bool gpencil_interpolate_set_init_values(bContext *C, wmOperator *op, tGPDinterpolate *tgpi)
{
/* set current scene and window */
tgpi->depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
tgpi->scene = CTX_data_scene(C);
tgpi->area = CTX_wm_area(C);
tgpi->region = CTX_wm_region(C);
tgpi->ob = CTX_data_active_object(C);
/* Setup space conversions. */
gpencil_point_conversion_init(C, &tgpi->gsc);
/* set current frame number */
tgpi->cframe = tgpi->scene->r.cfra;
/* set GP datablock */
tgpi->gpd = tgpi->ob->data;
/* set interpolation weight */
tgpi->shift = RNA_float_get(op->ptr, "shift");
SET_FLAG_FROM_TEST(
tgpi->flag, (RNA_enum_get(op->ptr, "layers") == 1), GP_TOOLFLAG_INTERPOLATE_ALL_LAYERS);
SET_FLAG_FROM_TEST(
tgpi->flag,
(GPENCIL_EDIT_MODE(tgpi->gpd) && (RNA_boolean_get(op->ptr, "interpolate_selected_only"))),
GP_TOOLFLAG_INTERPOLATE_ONLY_SELECTED);
tgpi->flipmode = RNA_enum_get(op->ptr, "flip");
tgpi->smooth_factor = RNA_float_get(op->ptr, "smooth_factor");
tgpi->smooth_steps = RNA_int_get(op->ptr, "smooth_steps");
/* Untag strokes to be sure nothing is pending due any canceled process. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &tgpi->gpd->layers) {
gpencil_interpolate_untag_strokes(gpl);
}
/* Set layers */
gpencil_interpolate_set_points(C, tgpi);
return 1;
}
/* Allocate memory and initialize values */
static tGPDinterpolate *gpencil_session_init_interpolation(bContext *C, wmOperator *op)
{
tGPDinterpolate *tgpi = MEM_callocN(sizeof(tGPDinterpolate), "GPencil Interpolate Data");
/* define initial values */
gpencil_interpolate_set_init_values(C, op, tgpi);
/* return context data for running operator */
return tgpi;
}
/* Init interpolation: Allocate memory and set init values */
static int gpencil_interpolate_init(bContext *C, wmOperator *op)
{
tGPDinterpolate *tgpi;
/* check context */
tgpi = op->customdata = gpencil_session_init_interpolation(C, op);
if (tgpi == NULL) {
/* something wasn't set correctly in context */
gpencil_interpolate_exit(C, op);
return 0;
}
/* everything is now setup ok */
return 1;
}
/* ----------------------- */
/* Invoke handler: Initialize the operator */
static int gpencil_interpolate_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
wmWindow *win = CTX_wm_window(C);
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl = CTX_data_active_gpencil_layer(C);
Scene *scene = CTX_data_scene(C);
tGPDinterpolate *tgpi = NULL;
/* Cannot interpolate if not between 2 frames. */
int cfra = CFRA;
bGPDframe *gpf_prv = gpencil_get_previous_keyframe(gpl, cfra);
bGPDframe *gpf_next = gpencil_get_next_keyframe(gpl, cfra);
if (ELEM(NULL, gpf_prv, gpf_next)) {
BKE_report(
op->reports,
RPT_ERROR,
"Cannot find valid keyframes to interpolate (Breakdowns keyframes are not allowed)");
return OPERATOR_CANCELLED;
}
if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) {
BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode");
return OPERATOR_CANCELLED;
}
/* try to initialize context data needed */
if (!gpencil_interpolate_init(C, op)) {
if (op->customdata) {
MEM_freeN(op->customdata);
}
return OPERATOR_CANCELLED;
}
tgpi = op->customdata;
/* set cursor to indicate modal */
WM_cursor_modal_set(win, WM_CURSOR_EW_SCROLL);
/* update shift indicator in header */
gpencil_interpolate_status_indicators(C, tgpi);
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | NA_EDITED, NULL);
/* add a modal handler for this operator */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
/* Modal handler: Events handling during interactive part */
static int gpencil_interpolate_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
tGPDinterpolate *tgpi = op->customdata;
wmWindow *win = CTX_wm_window(C);
bGPDframe *gpf_dst;
bGPDstroke *gps_dst;
const bool has_numinput = hasNumInput(&tgpi->num);
switch (event->type) {
case LEFTMOUSE: /* confirm */
case EVT_PADENTER:
case EVT_RETKEY: {
/* return to normal cursor and header status */
ED_area_status_text(tgpi->area, NULL);
ED_workspace_status_text(C, NULL);
WM_cursor_modal_restore(win);
/* insert keyframes as required... */
LISTBASE_FOREACH (tGPDinterpolate_layer *, tgpil, &tgpi->ilayers) {
gpf_dst = BKE_gpencil_layer_frame_get(tgpil->gpl, tgpi->cframe, GP_GETFRAME_ADD_NEW);
gpf_dst->key_type = BEZT_KEYTYPE_BREAKDOWN;
/* Copy strokes. */
LISTBASE_FOREACH (bGPDstroke *, gps_src, &tgpil->interFrame->strokes) {
if (gps_src->totpoints == 0) {
continue;
}
/* make copy of source stroke, then adjust pointer to points too */
gps_dst = BKE_gpencil_stroke_duplicate(gps_src, true, true);
gps_dst->flag &= ~GP_STROKE_TAG;
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(tgpi->gpd, gps_dst);
BLI_addtail(&gpf_dst->strokes, gps_dst);
}
}
/* clean up temp data */
gpencil_interpolate_exit(C, op);
/* done! */
return OPERATOR_FINISHED;
}
case EVT_ESCKEY: /* cancel */
case RIGHTMOUSE: {
/* return to normal cursor and header status */
ED_area_status_text(tgpi->area, NULL);
ED_workspace_status_text(C, NULL);
WM_cursor_modal_restore(win);
/* clean up temp data */
gpencil_interpolate_exit(C, op);
/* canceled! */
return OPERATOR_CANCELLED;
}
case WHEELUPMOUSE: {
tgpi->shift = tgpi->shift + 0.01f;
CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
RNA_float_set(op->ptr, "shift", tgpi->shift);
/* update screen */
gpencil_interpolate_update(C, op, tgpi);
break;
}
case WHEELDOWNMOUSE: {
tgpi->shift = tgpi->shift - 0.01f;
CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
RNA_float_set(op->ptr, "shift", tgpi->shift);
/* update screen */
gpencil_interpolate_update(C, op, tgpi);
break;
}
case MOUSEMOVE: /* calculate new position */
{
/* Only handle mouse-move if not doing numeric-input. */
if (has_numinput == false) {
/* Update shift based on position of mouse. */
gpencil_mouse_update_shift(tgpi, op, event);
/* update screen */
gpencil_interpolate_update(C, op, tgpi);
}
break;
}
default: {
if ((event->val == KM_PRESS) && handleNumInput(C, &tgpi->num, event)) {
const float factor = tgpi->init_factor;
float value;
/* Grab shift from numeric input, and store this new value (the user see an int) */
value = (factor + tgpi->shift) * 100.0f;
applyNumInput(&tgpi->num, &value);
tgpi->shift = value / 100.0f;
/* recalculate the shift to get the right value in the frame scale */
tgpi->shift = tgpi->shift - factor;
CLAMP(tgpi->shift, tgpi->low_limit, tgpi->high_limit);
RNA_float_set(op->ptr, "shift", tgpi->shift);
/* update screen */
gpencil_interpolate_update(C, op, tgpi);
break;
}
/* unhandled event - allow to pass through */
return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
}
}
/* still running... */
return OPERATOR_RUNNING_MODAL;
}
/* Cancel handler */
static void gpencil_interpolate_cancel(bContext *C, wmOperator *op)
{
/* this is just a wrapper around exit() */
gpencil_interpolate_exit(C, op);
}
void GPENCIL_OT_interpolate(wmOperatorType *ot)
{
static const EnumPropertyItem flip_modes[] = {
{GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""},
{GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""},
{GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""},
{0, NULL, 0, NULL, NULL},
};
PropertyRNA *prop;
/* identifiers */
ot->name = "Grease Pencil Interpolation";
ot->idname = "GPENCIL_OT_interpolate";
ot->description = "Interpolate grease pencil strokes between frames";
/* callbacks */
ot->invoke = gpencil_interpolate_invoke;
ot->modal = gpencil_interpolate_modal;
ot->cancel = gpencil_interpolate_cancel;
ot->poll = gpencil_view3d_poll;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
static const EnumPropertyItem gpencil_interpolation_layer_items[] = {
{0, "ACTIVE", 0, "Active", ""},
{1, "ALL", 0, "All Layers", ""},
{0, NULL, 0, NULL, NULL},
};
/* properties */
RNA_def_float_factor(
ot->srna,
"shift",
0.0f,
-1.0f,
1.0f,
"Shift",
"Bias factor for which frame has more influence on the interpolated strokes",
-0.9f,
0.9f);
RNA_def_enum(ot->srna,
"layers",
gpencil_interpolation_layer_items,
0,
"Layer",
"Layers included in the interpolation");
RNA_def_boolean(ot->srna,
"interpolate_selected_only",
0,
"Only Selected",
"Interpolate only selected strokes");
RNA_def_enum(ot->srna,
"flip",
flip_modes,
GP_INTERPOLATE_FLIPAUTO,
"Flip Mode",
"Invert destination stroke to match start and end with source stroke");
RNA_def_int(ot->srna,
"smooth_steps",
1,
1,
3,
"Iterations",
"Number of times to smooth newly created strokes",
1,
3);
RNA_def_float(ot->srna,
"smooth_factor",
0.0f,
0.0f,
2.0f,
"Smooth",
"Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise",
0.0f,
2.0f);
prop = RNA_def_boolean(ot->srna, "release_confirm", 0, "Confirm on Release", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/* ****************** Interpolate Sequence *********************** */
/* Helper: Perform easing equation calculations for GP interpolation operator */
static float gpencil_interpolate_seq_easing_calc(wmOperator *op, float time)
{
const float begin = 0.0f;
const float change = 1.0f;
const float duration = 1.0f;
const float back = RNA_float_get(op->ptr, "back");
const float amplitude = RNA_float_get(op->ptr, "amplitude");
const float period = RNA_float_get(op->ptr, "period");
const eBezTriple_Easing easing = RNA_enum_get(op->ptr, "easing");
const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type");
float result = time;
switch (type) {
case GP_IPO_BACK:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_back_ease_in(time, begin, change, duration, back);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_back_ease_out(time, begin, change, duration, back);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_back_ease_in_out(time, begin, change, duration, back);
break;
default: /* default/auto: same as ease out */
result = BLI_easing_back_ease_out(time, begin, change, duration, back);
break;
}
break;
case GP_IPO_BOUNCE:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_bounce_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_bounce_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_bounce_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease out */
result = BLI_easing_bounce_ease_out(time, begin, change, duration);
break;
}
break;
case GP_IPO_CIRC:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_circ_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_circ_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_circ_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_circ_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_CUBIC:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_cubic_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_cubic_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_cubic_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_cubic_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_ELASTIC:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_elastic_ease_in(time, begin, change, duration, amplitude, period);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_elastic_ease_out(time, begin, change, duration, amplitude, period);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_elastic_ease_in_out(
time, begin, change, duration, amplitude, period);
break;
default: /* default/auto: same as ease out */
result = BLI_easing_elastic_ease_out(time, begin, change, duration, amplitude, period);
break;
}
break;
case GP_IPO_EXPO:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_expo_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_expo_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_expo_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_expo_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_QUAD:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_quad_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_quad_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_quad_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_quad_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_QUART:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_quart_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_quart_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_quart_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_quart_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_QUINT:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_quint_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_quint_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_quint_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_quint_ease_in(time, begin, change, duration);
break;
}
break;
case GP_IPO_SINE:
switch (easing) {
case BEZT_IPO_EASE_IN:
result = BLI_easing_sine_ease_in(time, begin, change, duration);
break;
case BEZT_IPO_EASE_OUT:
result = BLI_easing_sine_ease_out(time, begin, change, duration);
break;
case BEZT_IPO_EASE_IN_OUT:
result = BLI_easing_sine_ease_in_out(time, begin, change, duration);
break;
default: /* default/auto: same as ease in */
result = BLI_easing_sine_ease_in(time, begin, change, duration);
break;
}
break;
default:
printf("%s: Unknown interpolation type\n", __func__);
break;
}
return result;
}
static int gpencil_interpolate_seq_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = ob->data;
bGPDlayer *active_gpl = CTX_data_active_gpencil_layer(C);
/* Setup space conversions. */
GP_SpaceConversion gsc;
gpencil_point_conversion_init(C, &gsc);
int cfra = CFRA;
GP_Interpolate_Settings *ipo_settings = &ts->gp_interpolate;
const int step = RNA_int_get(op->ptr, "step");
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const bool all_layers = (bool)(RNA_enum_get(op->ptr, "layers") == 1);
const bool only_selected = (GPENCIL_EDIT_MODE(gpd) &&
(RNA_boolean_get(op->ptr, "interpolate_selected_only") != 0));
eGP_InterpolateFlipMode flipmode = RNA_enum_get(op->ptr, "flip");
const float smooth_factor = RNA_float_get(op->ptr, "smooth_factor");
const int smooth_steps = RNA_int_get(op->ptr, "smooth_steps");
const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type");
if (ipo_settings->custom_ipo == NULL) {
ipo_settings->custom_ipo = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
}
BKE_curvemapping_init(ipo_settings->custom_ipo);
/* Cannot interpolate if not between 2 frames. */
bGPDframe *gpf_prv = gpencil_get_previous_keyframe(active_gpl, cfra);
bGPDframe *gpf_next = gpencil_get_next_keyframe(active_gpl, cfra);
if (ELEM(NULL, gpf_prv, gpf_next)) {
BKE_report(
op->reports,
RPT_ERROR,
"Cannot find valid keyframes to interpolate (Breakdowns keyframes are not allowed)");
return OPERATOR_CANCELLED;
}
if (GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd)) {
BKE_report(op->reports, RPT_ERROR, "Cannot interpolate in curve edit mode");
return OPERATOR_CANCELLED;
}
/* loop all layer to check if need interpolation */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
/* all layers or only active */
if ((!all_layers) && (gpl != active_gpl)) {
continue;
}
/* only editable and visible layers are considered */
if (!BKE_gpencil_layer_is_editable(gpl)) {
continue;
}
gpf_prv = gpencil_get_previous_keyframe(gpl, cfra);
gpf_next = gpencil_get_next_keyframe(gpl, cfra);
/* Need a set of frames to interpolate. */
if ((gpf_prv == NULL) || (gpf_next == NULL)) {
continue;
}
/* Store extremes. */
bGPDframe *prevFrame = BKE_gpencil_frame_duplicate(gpf_prv, true);
bGPDframe *nextFrame = BKE_gpencil_frame_duplicate(gpf_next, true);
/* Create a table with source and target pair of strokes. */
ListBase selected_strokes = {NULL};
GHash *used_strokes = BLI_ghash_ptr_new(__func__);
GHash *pair_strokes = BLI_ghash_ptr_new(__func__);
LISTBASE_FOREACH (bGPDstroke *, gps_from, &prevFrame->strokes) {
bGPDstroke *gps_to = NULL;
/* Only selected. */
if (GPENCIL_EDIT_MODE(gpd) && (only_selected) &&
((gps_from->flag & GP_STROKE_SELECT) == 0)) {
continue;
}
/* Skip strokes that are invalid for current view. */
if (ED_gpencil_stroke_can_use(C, gps_from) == false) {
continue;
}
/* Check if the material is editable. */
if (ED_gpencil_stroke_material_editable(ob, gpl, gps_from) == false) {
continue;
}
/* Try to get the related stroke. */
if ((is_multiedit) && (gps_from->select_index > 0)) {
gps_to = gpencil_stroke_get_related(used_strokes, nextFrame, gps_from->select_index);
}
/* If not found, get final stroke to interpolate using position in the array. */
if (gps_to == NULL) {
int fFrame = BLI_findindex(&prevFrame->strokes, gps_from);
gps_to = BLI_findlink(&nextFrame->strokes, fFrame);
}
if (ELEM(NULL, gps_from, gps_to)) {
continue;
}
if ((gps_from->totpoints == 0) || (gps_to->totpoints == 0)) {
continue;
}
/* if destination stroke is smaller, resize new_stroke to size of gps_to stroke */
if (gps_from->totpoints > gps_to->totpoints) {
BKE_gpencil_stroke_uniform_subdivide(gpd, gps_to, gps_from->totpoints, true);
}
if (gps_to->totpoints > gps_from->totpoints) {
BKE_gpencil_stroke_uniform_subdivide(gpd, gps_from, gps_to->totpoints, true);
}
/* Flip stroke. */
if (flipmode == GP_INTERPOLATE_FLIP) {
BKE_gpencil_stroke_flip(gps_to);
}
else if (flipmode == GP_INTERPOLATE_FLIPAUTO) {
if (gpencil_stroke_need_flip(depsgraph, ob, gpl, &gsc, gps_from, gps_to)) {
BKE_gpencil_stroke_flip(gps_to);
}
}
/* Insert the pair entry in the hash table and in the list of strokes to keep same order.
*/
BLI_addtail(&selected_strokes, BLI_genericNodeN(gps_from));
BLI_ghash_insert(pair_strokes, gps_from, gps_to);
}
/* Loop over intermediary frames and create the interpolation. */
for (int cframe = prevFrame->framenum + step; cframe < nextFrame->framenum; cframe += step) {
/* Get interpolation factor. */
float framerange = nextFrame->framenum - prevFrame->framenum;
CLAMP_MIN(framerange, 1.0f);
float factor = (float)(cframe - prevFrame->framenum) / framerange;
if (type == GP_IPO_CURVEMAP) {
/* custom curvemap */
if (ipo_settings->custom_ipo) {
factor = BKE_curvemapping_evaluateF(ipo_settings->custom_ipo, 0, factor);
}
else {
BKE_report(op->reports, RPT_ERROR, "Custom interpolation curve does not exist");
continue;
}
}
else if (type >= GP_IPO_BACK) {
/* easing equation... */
factor = gpencil_interpolate_seq_easing_calc(op, factor);
}
/* Apply the factor to all pair of strokes. */
LISTBASE_FOREACH (LinkData *, link, &selected_strokes) {
bGPDstroke *gps_from = link->data;
if (!BLI_ghash_haskey(pair_strokes, gps_from)) {
continue;
}
bGPDstroke *gps_to = (bGPDstroke *)BLI_ghash_lookup(pair_strokes, gps_from);
/* Create new stroke. */
bGPDstroke *new_stroke = BKE_gpencil_stroke_duplicate(gps_from, true, true);
new_stroke->flag |= GP_STROKE_TAG;
new_stroke->select_index = 0;
/* Update points position. */
gpencil_interpolate_update_points(gps_from, gps_to, new_stroke, factor);
gpencil_interpolate_smooth_stroke(new_stroke, smooth_factor, smooth_steps);
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, new_stroke);
/* Add strokes to frame. */
bGPDframe *interFrame = BKE_gpencil_layer_frame_get(gpl, cframe, GP_GETFRAME_ADD_NEW);
interFrame->key_type = BEZT_KEYTYPE_BREAKDOWN;
BLI_addtail(&interFrame->strokes, new_stroke);
}
}
BLI_freelistN(&selected_strokes);
/* Free Hash tablets. */
if (used_strokes != NULL) {
BLI_ghash_free(used_strokes, NULL, NULL);
}
if (pair_strokes != NULL) {
BLI_ghash_free(pair_strokes, NULL, NULL);
}
BKE_gpencil_free_strokes(prevFrame);
BKE_gpencil_free_strokes(nextFrame);
MEM_SAFE_FREE(prevFrame);
MEM_SAFE_FREE(nextFrame);
}
/* notifiers */
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
static void gpencil_interpolate_seq_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
uiLayout *col, *row;
const eGP_Interpolate_Type type = RNA_enum_get(op->ptr, "type");
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
row = uiLayoutRow(layout, true);
uiItemR(row, op->ptr, "step", 0, NULL, ICON_NONE);
row = uiLayoutRow(layout, true);
uiItemR(row, op->ptr, "layers", 0, NULL, ICON_NONE);
if (CTX_data_mode_enum(C) == CTX_MODE_EDIT_GPENCIL) {
row = uiLayoutRow(layout, true);
uiItemR(row, op->ptr, "interpolate_selected_only", 0, NULL, ICON_NONE);
}
row = uiLayoutRow(layout, true);
uiItemR(row, op->ptr, "flip", 0, NULL, ICON_NONE);
col = uiLayoutColumn(layout, true);
uiItemR(col, op->ptr, "smooth_factor", 0, NULL, ICON_NONE);
uiItemR(col, op->ptr, "smooth_steps", 0, NULL, ICON_NONE);
row = uiLayoutRow(layout, true);
uiItemR(row, op->ptr, "type", 0, NULL, ICON_NONE);
if (type == GP_IPO_CURVEMAP) {
/* Get an RNA pointer to ToolSettings to give to the custom curve. */
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
PointerRNA gpsettings_ptr;
RNA_pointer_create(
&scene->id, &RNA_GPencilInterpolateSettings, &ts->gp_interpolate, &gpsettings_ptr);
uiTemplateCurveMapping(
layout, &gpsettings_ptr, "interpolation_curve", 0, false, true, true, false);
}
else if (type != GP_IPO_LINEAR) {
row = uiLayoutRow(layout, false);
uiItemR(row, op->ptr, "easing", 0, NULL, ICON_NONE);
if (type == GP_IPO_BACK) {
row = uiLayoutRow(layout, false);
uiItemR(row, op->ptr, "back", 0, NULL, ICON_NONE);
}
else if (type == GP_IPO_ELASTIC) {
row = uiLayoutRow(layout, false);
uiItemR(row, op->ptr, "amplitude", 0, NULL, ICON_NONE);
row = uiLayoutRow(layout, false);
uiItemR(row, op->ptr, "period", 0, NULL, ICON_NONE);
}
}
}
void GPENCIL_OT_interpolate_sequence(wmOperatorType *ot)
{
static const EnumPropertyItem gpencil_interpolation_layer_items[] = {
{0, "ACTIVE", 0, "Active", ""},
{1, "ALL", 0, "All Layers", ""},
{0, NULL, 0, NULL, NULL},
};
/**
* \note this is a near exact duplicate of #rna_enum_beztriple_interpolation_mode_items,
* Changes here will likely apply there too.
*/
static const EnumPropertyItem gpencil_interpolation_type_items[] = {
/* interpolation */
{0, "", 0, N_("Interpolation"), "Standard transitions between keyframes"},
{GP_IPO_LINEAR,
"LINEAR",
ICON_IPO_LINEAR,
"Linear",
"Straight-line interpolation between A and B (i.e. no ease in/out)"},
{GP_IPO_CURVEMAP,
"CUSTOM",
ICON_IPO_BEZIER,
"Custom",
"Custom interpolation defined using a curve map"},
/* easing */
{0,
"",
0,
N_("Easing (by strength)"),
"Predefined inertial transitions, useful for motion graphics (from least to most "
"''dramatic'')"},
{GP_IPO_SINE,
"SINE",
ICON_IPO_SINE,
"Sinusoidal",
"Sinusoidal easing (weakest, almost linear but with a slight curvature)"},
{GP_IPO_QUAD, "QUAD", ICON_IPO_QUAD, "Quadratic", "Quadratic easing"},
{GP_IPO_CUBIC, "CUBIC", ICON_IPO_CUBIC, "Cubic", "Cubic easing"},
{GP_IPO_QUART, "QUART", ICON_IPO_QUART, "Quartic", "Quartic easing"},
{GP_IPO_QUINT, "QUINT", ICON_IPO_QUINT, "Quintic", "Quintic easing"},
{GP_IPO_EXPO, "EXPO", ICON_IPO_EXPO, "Exponential", "Exponential easing (dramatic)"},
{GP_IPO_CIRC,
"CIRC",
ICON_IPO_CIRC,
"Circular",
"Circular easing (strongest and most dynamic)"},
{0, "", 0, N_("Dynamic Effects"), "Simple physics-inspired easing effects"},
{GP_IPO_BACK, "BACK", ICON_IPO_BACK, "Back", "Cubic easing with overshoot and settle"},
{GP_IPO_BOUNCE,
"BOUNCE",
ICON_IPO_BOUNCE,
"Bounce",
"Exponentially decaying parabolic bounce, like when objects collide"},
{GP_IPO_ELASTIC,
"ELASTIC",
ICON_IPO_ELASTIC,
"Elastic",
"Exponentially decaying sine wave, like an elastic band"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem gpencil_interpolation_easing_items[] = {
{BEZT_IPO_EASE_AUTO,
"AUTO",
ICON_IPO_EASE_IN_OUT,
"Automatic Easing",
"Easing type is chosen automatically based on what the type of interpolation used "
"(e.g. 'Ease In' for transitional types, and 'Ease Out' for dynamic effects)"},
{BEZT_IPO_EASE_IN,
"EASE_IN",
ICON_IPO_EASE_IN,
"Ease In",
"Only on the end closest to the next keyframe"},
{BEZT_IPO_EASE_OUT,
"EASE_OUT",
ICON_IPO_EASE_OUT,
"Ease Out",
"Only on the end closest to the first keyframe"},
{BEZT_IPO_EASE_IN_OUT,
"EASE_IN_OUT",
ICON_IPO_EASE_IN_OUT,
"Ease In and Out",
"Segment between both keyframes"},
{0, NULL, 0, NULL, NULL},
};
static const EnumPropertyItem flip_modes[] = {
{GP_INTERPOLATE_NOFLIP, "NOFLIP", 0, "No Flip", ""},
{GP_INTERPOLATE_FLIP, "FLIP", 0, "Flip", ""},
{GP_INTERPOLATE_FLIPAUTO, "AUTO", 0, "Automatic", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Interpolate Sequence";
ot->idname = "GPENCIL_OT_interpolate_sequence";
ot->description = "Generate 'in-betweens' to smoothly interpolate between Grease Pencil frames";
/* api callbacks */
ot->exec = gpencil_interpolate_seq_exec;
ot->poll = gpencil_view3d_poll;
ot->ui = gpencil_interpolate_seq_ui;
RNA_def_int(ot->srna,
"step",
1,
1,
MAXFRAME,
"Step",
"Number of frames between generated interpolated frames",
1,
MAXFRAME);
RNA_def_enum(ot->srna,
"layers",
gpencil_interpolation_layer_items,
0,
"Layer",
"Layers included in the interpolation");
RNA_def_boolean(ot->srna,
"interpolate_selected_only",
0,
"Only Selected",
"Interpolate only selected strokes");
RNA_def_enum(ot->srna,
"flip",
flip_modes,
GP_INTERPOLATE_FLIPAUTO,
"Flip Mode",
"Invert destination stroke to match start and end with source stroke");
RNA_def_int(ot->srna,
"smooth_steps",
1,
1,
3,
"Iterations",
"Number of times to smooth newly created strokes",
1,
3);
RNA_def_float(ot->srna,
"smooth_factor",
0.0f,
0.0f,
2.0f,
"Smooth",
"Amount of smoothing to apply to interpolated strokes, to reduce jitter/noise",
0.0f,
2.0f);
RNA_def_enum(ot->srna,
"type",
gpencil_interpolation_type_items,
0,
"Type",
"Interpolation method to use the next time 'Interpolate Sequence' is run");
RNA_def_enum(
ot->srna,
"easing",
gpencil_interpolation_easing_items,
0,
"Easing",
"Which ends of the segment between the preceding and following grease pencil frames "
"easing interpolation is applied to");
RNA_def_float(ot->srna,
"back",
1.702f,
0.0f,
FLT_MAX,
"Back",
"Amount of overshoot for 'back' easing",
0.0f,
FLT_MAX);
RNA_def_float(ot->srna,
"amplitude",
0.15f,
0.0f,
FLT_MAX,
"Amplitude",
"Amount to boost elastic bounces for 'elastic' easing",
0.0f,
FLT_MAX);
RNA_def_float(ot->srna,
"period",
0.15f,
-FLT_MAX,
FLT_MAX,
"Period",
"Time between bounces for elastic easing",
-FLT_MAX,
FLT_MAX);
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* ******************** Remove Breakdowns ************************ */
static bool gpencil_interpolate_reverse_poll(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
if (area == NULL) {
return false;
}
if (!ELEM(area->spacetype, SPACE_VIEW3D, SPACE_ACTION)) {
return false;
}
bGPdata *gpd = ED_gpencil_data_get_active(C);
if (gpd == NULL) {
return false;
}
bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd);
if (gpl == NULL) {
return false;
}
/* need to be on a breakdown frame */
if ((gpl->actframe == NULL) || (gpl->actframe->key_type != BEZT_KEYTYPE_BREAKDOWN)) {
CTX_wm_operator_poll_msg_set(C, "Expected current frame to be a breakdown");
return false;
}
return true;
}
static int gpencil_interpolate_reverse_exec(bContext *C, wmOperator *UNUSED(op))
{
bGPdata *gpd = ED_gpencil_data_get_active(C);
/* Go through each layer, deleting the breakdowns around the current frame,
* but only if there is a keyframe nearby to stop at
*/
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
/* only editable and visible layers are considered */
if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == NULL)) {
continue;
}
bGPDframe *start_key = NULL;
bGPDframe *end_key = NULL;
bGPDframe *gpf, *gpfn;
/* Only continue if we're currently on a breakdown keyframe */
if ((gpl->actframe == NULL) || (gpl->actframe->key_type != BEZT_KEYTYPE_BREAKDOWN)) {
continue;
}
/* Search left for "start_key" (i.e. the first breakdown to remove) */
gpf = gpl->actframe;
while (gpf) {
if (gpf->key_type == BEZT_KEYTYPE_BREAKDOWN) {
/* A breakdown... keep going left */
start_key = gpf;
gpf = gpf->prev;
}
else {
/* Not a breakdown (may be a key, or an extreme,
* or something else that wasn't generated)... stop */
break;
}
}
/* Search right for "end_key" (i.e. the last breakdown to remove) */
gpf = gpl->actframe;
while (gpf) {
if (gpf->key_type == BEZT_KEYTYPE_BREAKDOWN) {
/* A breakdown... keep going right */
end_key = gpf;
gpf = gpf->next;
}
else {
/* Not a breakdown... stop */
break;
}
}
/* Did we find anything? */
/* NOTE: We should only proceed if there's something before/after these extents...
* Otherwise, there's just an extent of breakdowns with no keys to interpolate between
*/
if ((start_key && end_key) && ELEM(NULL, start_key->prev, end_key->next) == false) {
/* Set actframe to the key before start_key, since the keys have been removed now */
gpl->actframe = start_key->prev;
/* Free each frame we're removing (except the last one) */
for (gpf = start_key; gpf && gpf != end_key; gpf = gpfn) {
gpfn = gpf->next;
/* free strokes and their associated memory */
BKE_gpencil_free_strokes(gpf);
BLI_freelinkN(&gpl->frames, gpf);
}
/* Now free the last one... */
BKE_gpencil_free_strokes(end_key);
BLI_freelinkN(&gpl->frames, end_key);
}
}
/* notifiers */
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_interpolate_reverse(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Breakdowns";
ot->idname = "GPENCIL_OT_interpolate_reverse";
ot->description =
"Remove breakdown frames generated by interpolating between two Grease Pencil frames";
/* callbacks */
ot->exec = gpencil_interpolate_reverse_exec;
ot->poll = gpencil_interpolate_reverse_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}