The old onion skinning used in 2.7x has been ported and converted to 2.8. Only basic features have been included. For more advanced onion skin features, use grease pencil objects. Onion Skin is supported in View 3D and Sequencer.
1563 lines
39 KiB
C
1563 lines
39 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* 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) 2008, Blender Foundation
|
|
* This is a new part of Blender
|
|
*
|
|
* Contributor(s): Joshua Leung, Antonio Vazquez
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/blenkernel/intern/gpencil.c
|
|
* \ingroup bke
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <math.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_string_utils.h"
|
|
#include "BLI_rand.h"
|
|
#include "BLI_ghash.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DNA_anim_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_material_types.h"
|
|
#include "DNA_gpencil_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_object_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_action.h"
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_deform.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_gpencil.h"
|
|
#include "BKE_colortools.h"
|
|
#include "BKE_icons.h"
|
|
#include "BKE_library.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_material.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
/* ************************************************** */
|
|
/* Draw Engine */
|
|
|
|
void(*BKE_gpencil_batch_cache_dirty_tag_cb)(bGPdata *gpd) = NULL;
|
|
void(*BKE_gpencil_batch_cache_free_cb)(bGPdata *gpd) = NULL;
|
|
|
|
void BKE_gpencil_batch_cache_dirty_tag(bGPdata *gpd)
|
|
{
|
|
if (gpd) {
|
|
DEG_id_tag_update(&gpd->id, OB_RECALC_DATA);
|
|
BKE_gpencil_batch_cache_dirty_tag_cb(gpd);
|
|
}
|
|
}
|
|
|
|
void BKE_gpencil_batch_cache_free(bGPdata *gpd)
|
|
{
|
|
if (gpd) {
|
|
BKE_gpencil_batch_cache_free_cb(gpd);
|
|
}
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* Memory Management */
|
|
|
|
/* clean vertex groups weights */
|
|
void BKE_gpencil_free_point_weights(MDeformVert *dvert)
|
|
{
|
|
if (dvert == NULL) {
|
|
return;
|
|
}
|
|
MEM_SAFE_FREE(dvert->dw);
|
|
}
|
|
|
|
void BKE_gpencil_free_stroke_weights(bGPDstroke *gps)
|
|
{
|
|
if (gps == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (gps->dvert == NULL) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < gps->totpoints; i++) {
|
|
MDeformVert *dvert = &gps->dvert[i];
|
|
BKE_gpencil_free_point_weights(dvert);
|
|
}
|
|
}
|
|
|
|
/* free stroke, doesn't unlink from any listbase */
|
|
void BKE_gpencil_free_stroke(bGPDstroke *gps)
|
|
{
|
|
if (gps == NULL) {
|
|
return;
|
|
}
|
|
/* free stroke memory arrays, then stroke itself */
|
|
if (gps->points) {
|
|
MEM_freeN(gps->points);
|
|
}
|
|
if (gps->dvert) {
|
|
BKE_gpencil_free_stroke_weights(gps);
|
|
MEM_freeN(gps->dvert);
|
|
}
|
|
if (gps->triangles)
|
|
MEM_freeN(gps->triangles);
|
|
|
|
MEM_freeN(gps);
|
|
}
|
|
|
|
/* Free strokes belonging to a gp-frame */
|
|
bool BKE_gpencil_free_strokes(bGPDframe *gpf)
|
|
{
|
|
bGPDstroke *gps_next;
|
|
bool changed = (BLI_listbase_is_empty(&gpf->strokes) == false);
|
|
|
|
/* free strokes */
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps_next) {
|
|
gps_next = gps->next;
|
|
BKE_gpencil_free_stroke(gps);
|
|
}
|
|
BLI_listbase_clear(&gpf->strokes);
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* Free strokes and colors belonging to a gp-frame */
|
|
bool BKE_gpencil_free_frame_runtime_data(bGPDframe *derived_gpf)
|
|
{
|
|
bGPDstroke *gps_next;
|
|
if (!derived_gpf) {
|
|
return false;
|
|
}
|
|
|
|
/* free strokes */
|
|
for (bGPDstroke *gps = derived_gpf->strokes.first; gps; gps = gps_next) {
|
|
gps_next = gps->next;
|
|
BKE_gpencil_free_stroke(gps);
|
|
}
|
|
BLI_listbase_clear(&derived_gpf->strokes);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Free all of a gp-layer's frames */
|
|
void BKE_gpencil_free_frames(bGPDlayer *gpl)
|
|
{
|
|
bGPDframe *gpf_next;
|
|
|
|
/* error checking */
|
|
if (gpl == NULL) return;
|
|
|
|
/* free frames */
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf_next) {
|
|
gpf_next = gpf->next;
|
|
|
|
/* free strokes and their associated memory */
|
|
BKE_gpencil_free_strokes(gpf);
|
|
BLI_freelinkN(&gpl->frames, gpf);
|
|
}
|
|
gpl->actframe = NULL;
|
|
}
|
|
|
|
|
|
|
|
/* Free all of the gp-layers for a viewport (list should be &gpd->layers or so) */
|
|
void BKE_gpencil_free_layers(ListBase *list)
|
|
{
|
|
bGPDlayer *gpl_next;
|
|
|
|
/* error checking */
|
|
if (list == NULL) return;
|
|
|
|
/* delete layers */
|
|
for (bGPDlayer *gpl = list->first; gpl; gpl = gpl_next) {
|
|
gpl_next = gpl->next;
|
|
|
|
/* free layers and their data */
|
|
BKE_gpencil_free_frames(gpl);
|
|
BLI_freelinkN(list, gpl);
|
|
}
|
|
}
|
|
|
|
/** Free (or release) any data used by this grease pencil (does not free the gpencil itself). */
|
|
void BKE_gpencil_free(bGPdata *gpd, bool free_all)
|
|
{
|
|
/* clear animation data */
|
|
BKE_animdata_free(&gpd->id, false);
|
|
|
|
/* free layers */
|
|
BKE_gpencil_free_layers(&gpd->layers);
|
|
|
|
/* materials */
|
|
MEM_SAFE_FREE(gpd->mat);
|
|
|
|
/* free all data */
|
|
if (free_all) {
|
|
/* clear cache */
|
|
BKE_gpencil_batch_cache_free(gpd);
|
|
}
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* Container Creation */
|
|
|
|
/* add a new gp-frame to the given layer */
|
|
bGPDframe *BKE_gpencil_frame_addnew(bGPDlayer *gpl, int cframe)
|
|
{
|
|
bGPDframe *gpf = NULL, *gf = NULL;
|
|
short state = 0;
|
|
|
|
/* error checking */
|
|
if (gpl == NULL)
|
|
return NULL;
|
|
|
|
/* allocate memory for this frame */
|
|
gpf = MEM_callocN(sizeof(bGPDframe), "bGPDframe");
|
|
gpf->framenum = cframe;
|
|
|
|
/* find appropriate place to add frame */
|
|
if (gpl->frames.first) {
|
|
for (gf = gpl->frames.first; gf; gf = gf->next) {
|
|
/* check if frame matches one that is supposed to be added */
|
|
if (gf->framenum == cframe) {
|
|
state = -1;
|
|
break;
|
|
}
|
|
|
|
/* if current frame has already exceeded the frame to add, add before */
|
|
if (gf->framenum > cframe) {
|
|
BLI_insertlinkbefore(&gpl->frames, gf, gpf);
|
|
state = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check whether frame was added successfully */
|
|
if (state == -1) {
|
|
printf("Error: Frame (%d) existed already for this layer. Using existing frame\n", cframe);
|
|
|
|
/* free the newly created one, and use the old one instead */
|
|
MEM_freeN(gpf);
|
|
|
|
/* return existing frame instead... */
|
|
BLI_assert(gf != NULL);
|
|
gpf = gf;
|
|
}
|
|
else if (state == 0) {
|
|
/* add to end then! */
|
|
BLI_addtail(&gpl->frames, gpf);
|
|
}
|
|
|
|
/* return frame */
|
|
return gpf;
|
|
}
|
|
|
|
/* add a copy of the active gp-frame to the given layer */
|
|
bGPDframe *BKE_gpencil_frame_addcopy(bGPDlayer *gpl, int cframe)
|
|
{
|
|
bGPDframe *new_frame;
|
|
bool found = false;
|
|
|
|
/* Error checking/handling */
|
|
if (gpl == NULL) {
|
|
/* no layer */
|
|
return NULL;
|
|
}
|
|
else if (gpl->actframe == NULL) {
|
|
/* no active frame, so just create a new one from scratch */
|
|
return BKE_gpencil_frame_addnew(gpl, cframe);
|
|
}
|
|
|
|
/* Create a copy of the frame */
|
|
new_frame = BKE_gpencil_frame_duplicate(gpl->actframe);
|
|
|
|
/* Find frame to insert it before */
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
if (gpf->framenum > cframe) {
|
|
/* Add it here */
|
|
BLI_insertlinkbefore(&gpl->frames, gpf, new_frame);
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
else if (gpf->framenum == cframe) {
|
|
/* This only happens when we're editing with framelock on...
|
|
* - Delete the new frame and don't do anything else here...
|
|
*/
|
|
BKE_gpencil_free_strokes(new_frame);
|
|
MEM_freeN(new_frame);
|
|
new_frame = NULL;
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == false) {
|
|
/* Add new frame to the end */
|
|
BLI_addtail(&gpl->frames, new_frame);
|
|
}
|
|
|
|
/* Ensure that frame is set up correctly, and return it */
|
|
if (new_frame) {
|
|
new_frame->framenum = cframe;
|
|
gpl->actframe = new_frame;
|
|
}
|
|
|
|
return new_frame;
|
|
}
|
|
|
|
/* add a new gp-layer and make it the active layer */
|
|
bGPDlayer *BKE_gpencil_layer_addnew(bGPdata *gpd, const char *name, bool setactive)
|
|
{
|
|
bGPDlayer *gpl = NULL;
|
|
bGPDlayer *gpl_active = NULL;
|
|
|
|
/* check that list is ok */
|
|
if (gpd == NULL)
|
|
return NULL;
|
|
|
|
/* allocate memory for frame and add to end of list */
|
|
gpl = MEM_callocN(sizeof(bGPDlayer), "bGPDlayer");
|
|
|
|
gpl_active = BKE_gpencil_layer_getactive(gpd);
|
|
|
|
/* add to datablock */
|
|
if (gpl_active == NULL) {
|
|
BLI_addtail(&gpd->layers, gpl);
|
|
}
|
|
else {
|
|
/* if active layer, add after that layer */
|
|
BLI_insertlinkafter(&gpd->layers, gpl_active, gpl);
|
|
}
|
|
|
|
/* annotation vs GP Object behaviour is slightly different */
|
|
if (gpd->flag & GP_DATA_ANNOTATIONS) {
|
|
/* set default color of new strokes for this layer */
|
|
copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
|
|
gpl->opacity = 1.0f;
|
|
|
|
/* set default thickness of new strokes for this layer */
|
|
gpl->thickness = 3;
|
|
|
|
/* Onion colors */
|
|
ARRAY_SET_ITEMS(gpl->gcolor_prev, 0.302f, 0.851f, 0.302f);
|
|
ARRAY_SET_ITEMS(gpl->gcolor_next, 0.250f, 0.1f, 1.0f);
|
|
}
|
|
else {
|
|
/* thickness parameter represents "thickness change", not absolute thickness */
|
|
gpl->thickness = 0;
|
|
gpl->opacity = 1.0f;
|
|
/* onion-skinning settings */
|
|
gpl->onion_flag |= GP_LAYER_ONIONSKIN;
|
|
}
|
|
|
|
/* auto-name */
|
|
BLI_strncpy(gpl->info, name, sizeof(gpl->info));
|
|
BLI_uniquename(&gpd->layers, gpl,
|
|
(gpd->flag & GP_DATA_ANNOTATIONS) ? DATA_("Note") : DATA_("GP_Layer"),
|
|
'.',
|
|
offsetof(bGPDlayer, info),
|
|
sizeof(gpl->info));
|
|
|
|
/* make this one the active one */
|
|
if (setactive)
|
|
BKE_gpencil_layer_setactive(gpd, gpl);
|
|
|
|
/* return layer */
|
|
return gpl;
|
|
}
|
|
|
|
/* add a new gp-datablock */
|
|
bGPdata *BKE_gpencil_data_addnew(Main *bmain, const char name[])
|
|
{
|
|
bGPdata *gpd;
|
|
|
|
/* allocate memory for a new block */
|
|
gpd = BKE_libblock_alloc(bmain, ID_GD, name, 0);
|
|
|
|
/* initial settings */
|
|
gpd->flag = (GP_DATA_DISPINFO | GP_DATA_EXPAND);
|
|
|
|
/* general flags */
|
|
gpd->flag |= GP_DATA_VIEWALIGN;
|
|
gpd->flag |= GP_DATA_STROKE_FORCE_RECALC;
|
|
|
|
/* GP object specific settings */
|
|
ARRAY_SET_ITEMS(gpd->line_color, 0.6f, 0.6f, 0.6f, 0.5f);
|
|
|
|
gpd->xray_mode = GP_XRAY_3DSPACE;
|
|
gpd->pixfactor = GP_DEFAULT_PIX_FACTOR;
|
|
|
|
/* grid settings */
|
|
ARRAY_SET_ITEMS(gpd->grid.color, 0.5f, 0.5f, 0.5f); // Color
|
|
ARRAY_SET_ITEMS(gpd->grid.scale, 1.0f, 1.0f); // Scale
|
|
gpd->grid.lines = GP_DEFAULT_GRID_LINES; // Number of lines
|
|
|
|
/* onion-skinning settings (datablock level) */
|
|
gpd->onion_flag |= (GP_ONION_GHOST_PREVCOL | GP_ONION_GHOST_NEXTCOL);
|
|
gpd->onion_flag |= GP_ONION_FADE;
|
|
gpd->onion_mode = GP_ONION_MODE_RELATIVE;
|
|
gpd->onion_factor = 0.5f;
|
|
ARRAY_SET_ITEMS(gpd->gcolor_prev, 0.145098f, 0.419608f, 0.137255f); /* green */
|
|
ARRAY_SET_ITEMS(gpd->gcolor_next, 0.125490f, 0.082353f, 0.529412f); /* blue */
|
|
gpd->gstep = 1;
|
|
gpd->gstep_next = 1;
|
|
|
|
return gpd;
|
|
}
|
|
|
|
|
|
/* ************************************************** */
|
|
/* Primitive Creation */
|
|
/* Utilities for easier bulk-creation of geometry */
|
|
|
|
/**
|
|
* Populate stroke with point data from data buffers
|
|
*
|
|
* \param array Flat array of point data values. Each entry has GP_PRIM_DATABUF_SIZE values
|
|
* \param mat 4x4 transform matrix to transform points into the right coordinate space
|
|
*/
|
|
void BKE_gpencil_stroke_add_points(bGPDstroke *gps, const float *array, const int totpoints, const float mat[4][4])
|
|
{
|
|
for (int i = 0; i < totpoints; i++) {
|
|
bGPDspoint *pt = &gps->points[i];
|
|
const int x = GP_PRIM_DATABUF_SIZE * i;
|
|
|
|
pt->x = array[x];
|
|
pt->y = array[x + 1];
|
|
pt->z = array[x + 2];
|
|
mul_m4_v3(mat, &pt->x);
|
|
|
|
pt->pressure = array[x + 3];
|
|
pt->strength = array[x + 4];
|
|
}
|
|
}
|
|
|
|
/* Create a new stroke, with pre-allocated data buffers */
|
|
bGPDstroke *BKE_gpencil_add_stroke(bGPDframe *gpf, int mat_idx, int totpoints, short thickness)
|
|
{
|
|
/* allocate memory for a new stroke */
|
|
bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
|
|
|
|
gps->thickness = thickness * 25;
|
|
gps->inittime = 0;
|
|
|
|
/* enable recalculation flag by default */
|
|
gps->flag = GP_STROKE_RECALC_CACHES | GP_STROKE_3DSPACE;
|
|
|
|
gps->totpoints = totpoints;
|
|
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
|
|
|
|
/* initialize triangle memory to dummy data */
|
|
gps->triangles = MEM_callocN(sizeof(bGPDtriangle), "GP Stroke triangulation");
|
|
gps->flag |= GP_STROKE_RECALC_CACHES;
|
|
gps->tot_triangles = 0;
|
|
|
|
gps->mat_nr = mat_idx;
|
|
|
|
/* add to frame */
|
|
BLI_addtail(&gpf->strokes, gps);
|
|
|
|
return gps;
|
|
}
|
|
|
|
|
|
/* ************************************************** */
|
|
/* Data Duplication */
|
|
|
|
/* make a copy of a given gpencil weights */
|
|
void BKE_gpencil_stroke_weights_duplicate(bGPDstroke *gps_src, bGPDstroke *gps_dst)
|
|
{
|
|
if (gps_src == NULL) {
|
|
return;
|
|
}
|
|
BLI_assert(gps_src->totpoints == gps_dst->totpoints);
|
|
|
|
BKE_defvert_array_copy(gps_dst->dvert, gps_src->dvert, gps_src->totpoints);
|
|
}
|
|
|
|
/* make a copy of a given gpencil stroke */
|
|
bGPDstroke *BKE_gpencil_stroke_duplicate(bGPDstroke *gps_src)
|
|
{
|
|
bGPDstroke *gps_dst = NULL;
|
|
|
|
gps_dst = MEM_dupallocN(gps_src);
|
|
gps_dst->prev = gps_dst->next = NULL;
|
|
|
|
gps_dst->points = MEM_dupallocN(gps_src->points);
|
|
|
|
if (gps_src->dvert != NULL) {
|
|
gps_dst->dvert = MEM_dupallocN(gps_src->dvert);
|
|
BKE_gpencil_stroke_weights_duplicate(gps_src, gps_dst);
|
|
}
|
|
else {
|
|
gps_dst->dvert = NULL;
|
|
}
|
|
|
|
/* Don't clear triangles, so that modifier evaluation can just use
|
|
* this without extra work first. Most places that need to force
|
|
* this data to get recalculated will destroy the data anyway though.
|
|
*/
|
|
gps_dst->triangles = MEM_dupallocN(gps_dst->triangles);
|
|
/* gps_dst->flag |= GP_STROKE_RECALC_CACHES; */
|
|
|
|
/* return new stroke */
|
|
return gps_dst;
|
|
}
|
|
|
|
/* make a copy of a given gpencil frame */
|
|
bGPDframe *BKE_gpencil_frame_duplicate(const bGPDframe *gpf_src)
|
|
{
|
|
bGPDstroke *gps_dst = NULL;
|
|
bGPDframe *gpf_dst;
|
|
|
|
/* error checking */
|
|
if (gpf_src == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* make a copy of the source frame */
|
|
gpf_dst = MEM_dupallocN(gpf_src);
|
|
gpf_dst->prev = gpf_dst->next = NULL;
|
|
|
|
/* copy strokes */
|
|
BLI_listbase_clear(&gpf_dst->strokes);
|
|
for (bGPDstroke *gps_src = gpf_src->strokes.first; gps_src; gps_src = gps_src->next) {
|
|
/* make copy of source stroke */
|
|
gps_dst = BKE_gpencil_stroke_duplicate(gps_src);
|
|
BLI_addtail(&gpf_dst->strokes, gps_dst);
|
|
}
|
|
|
|
/* return new frame */
|
|
return gpf_dst;
|
|
}
|
|
|
|
/* make a copy of strokes between gpencil frames */
|
|
void BKE_gpencil_frame_copy_strokes(bGPDframe *gpf_src, struct bGPDframe *gpf_dst)
|
|
{
|
|
bGPDstroke *gps_dst = NULL;
|
|
/* error checking */
|
|
if ((gpf_src == NULL) || (gpf_dst == NULL)) {
|
|
return;
|
|
}
|
|
|
|
/* copy strokes */
|
|
BLI_listbase_clear(&gpf_dst->strokes);
|
|
for (bGPDstroke *gps_src = gpf_src->strokes.first; gps_src; gps_src = gps_src->next) {
|
|
/* make copy of source stroke */
|
|
gps_dst = BKE_gpencil_stroke_duplicate(gps_src);
|
|
BLI_addtail(&gpf_dst->strokes, gps_dst);
|
|
}
|
|
}
|
|
|
|
/* make a copy of a given gpencil layer */
|
|
bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src)
|
|
{
|
|
const bGPDframe *gpf_src;
|
|
bGPDframe *gpf_dst;
|
|
bGPDlayer *gpl_dst;
|
|
|
|
/* error checking */
|
|
if (gpl_src == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* make a copy of source layer */
|
|
gpl_dst = MEM_dupallocN(gpl_src);
|
|
gpl_dst->prev = gpl_dst->next = NULL;
|
|
|
|
/* copy frames */
|
|
BLI_listbase_clear(&gpl_dst->frames);
|
|
for (gpf_src = gpl_src->frames.first; gpf_src; gpf_src = gpf_src->next) {
|
|
/* make a copy of source frame */
|
|
gpf_dst = BKE_gpencil_frame_duplicate(gpf_src);
|
|
BLI_addtail(&gpl_dst->frames, gpf_dst);
|
|
|
|
/* if source frame was the current layer's 'active' frame, reassign that too */
|
|
if (gpf_src == gpl_dst->actframe)
|
|
gpl_dst->actframe = gpf_dst;
|
|
}
|
|
|
|
/* return new layer */
|
|
return gpl_dst;
|
|
}
|
|
|
|
/**
|
|
* Only copy internal data of GreasePencil ID from source to already allocated/initialized destination.
|
|
* You probably never want to use that directly, use id_copy or BKE_id_copy_ex for typical needs.
|
|
*
|
|
* WARNING! This function will not handle ID user count!
|
|
*
|
|
* \param flag Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more).
|
|
*/
|
|
void BKE_gpencil_copy_data(bGPdata *gpd_dst, const bGPdata *gpd_src, const int UNUSED(flag))
|
|
{
|
|
/* duplicate material array */
|
|
if (gpd_src->mat) {
|
|
gpd_dst->mat = MEM_dupallocN(gpd_src->mat);
|
|
}
|
|
|
|
/* copy layers */
|
|
BLI_listbase_clear(&gpd_dst->layers);
|
|
for (const bGPDlayer *gpl_src = gpd_src->layers.first; gpl_src; gpl_src = gpl_src->next) {
|
|
/* make a copy of source layer and its data */
|
|
bGPDlayer *gpl_dst = BKE_gpencil_layer_duplicate(gpl_src); /* TODO here too could add unused flags... */
|
|
BLI_addtail(&gpd_dst->layers, gpl_dst);
|
|
}
|
|
|
|
}
|
|
|
|
/* Standard API to make a copy of GP datablock, separate from copying its data */
|
|
bGPdata *BKE_gpencil_copy(Main *bmain, const bGPdata *gpd)
|
|
{
|
|
bGPdata *gpd_copy;
|
|
BKE_id_copy_ex(bmain, &gpd->id, (ID **)&gpd_copy, 0, false);
|
|
return gpd_copy;
|
|
}
|
|
|
|
/* make a copy of a given gpencil datablock */
|
|
// XXX: Should this be deprecated?
|
|
bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool internal_copy)
|
|
{
|
|
bGPdata *gpd_dst;
|
|
|
|
/* Yuck and super-uber-hyper yuck!!!
|
|
* Should be replaceable with a no-main copy (LIB_ID_COPY_NO_MAIN etc.), but not sure about it,
|
|
* so for now keep old code for that one. */
|
|
|
|
/* error checking */
|
|
if (gpd_src == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (internal_copy) {
|
|
/* make a straight copy for undo buffers used during stroke drawing */
|
|
gpd_dst = MEM_dupallocN(gpd_src);
|
|
}
|
|
else {
|
|
BLI_assert(bmain != NULL);
|
|
BKE_id_copy_ex(bmain, &gpd_src->id, (ID **)&gpd_dst, 0, false);
|
|
}
|
|
|
|
/* Copy internal data (layers, etc.) */
|
|
BKE_gpencil_copy_data(gpd_dst, gpd_src, 0);
|
|
|
|
/* return new */
|
|
return gpd_dst;
|
|
}
|
|
|
|
void BKE_gpencil_make_local(Main *bmain, bGPdata *gpd, const bool lib_local)
|
|
{
|
|
BKE_id_make_local_generic(bmain, &gpd->id, true, lib_local);
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* GP Stroke API */
|
|
|
|
/* ensure selection status of stroke is in sync with its points */
|
|
void BKE_gpencil_stroke_sync_selection(bGPDstroke *gps)
|
|
{
|
|
bGPDspoint *pt;
|
|
int i;
|
|
|
|
/* error checking */
|
|
if (gps == NULL)
|
|
return;
|
|
|
|
/* we'll stop when we find the first selected point,
|
|
* so initially, we must deselect
|
|
*/
|
|
gps->flag &= ~GP_STROKE_SELECT;
|
|
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
|
if (pt->flag & GP_SPOINT_SELECT) {
|
|
gps->flag |= GP_STROKE_SELECT;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* GP Frame API */
|
|
|
|
/* delete the last stroke of the given frame */
|
|
void BKE_gpencil_frame_delete_laststroke(bGPDlayer *gpl, bGPDframe *gpf)
|
|
{
|
|
bGPDstroke *gps = (gpf) ? gpf->strokes.last : NULL;
|
|
int cfra = (gpf) ? gpf->framenum : 0; /* assume that the current frame was not locked */
|
|
|
|
/* error checking */
|
|
if (ELEM(NULL, gpf, gps))
|
|
return;
|
|
|
|
/* free the stroke and its data */
|
|
if (gps->points) {
|
|
MEM_freeN(gps->points);
|
|
}
|
|
if (gps->dvert) {
|
|
BKE_gpencil_free_stroke_weights(gps);
|
|
MEM_freeN(gps->dvert);
|
|
}
|
|
MEM_freeN(gps->triangles);
|
|
BLI_freelinkN(&gpf->strokes, gps);
|
|
|
|
/* if frame has no strokes after this, delete it */
|
|
if (BLI_listbase_is_empty(&gpf->strokes)) {
|
|
BKE_gpencil_layer_delframe(gpl, gpf);
|
|
BKE_gpencil_layer_getframe(gpl, cfra, GP_GETFRAME_USE_PREV);
|
|
}
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* GP Layer API */
|
|
|
|
/* Check if the given layer is able to be edited or not */
|
|
bool gpencil_layer_is_editable(const bGPDlayer *gpl)
|
|
{
|
|
/* Sanity check */
|
|
if (gpl == NULL)
|
|
return false;
|
|
|
|
/* Layer must be: Visible + Editable */
|
|
if ((gpl->flag & (GP_LAYER_HIDE | GP_LAYER_LOCKED)) == 0) {
|
|
/* Opacity must be sufficiently high that it is still "visible"
|
|
* Otherwise, it's not really "visible" to the user, so no point editing...
|
|
*/
|
|
if (gpl->opacity > GPENCIL_ALPHA_OPACITY_THRESH) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Something failed */
|
|
return false;
|
|
}
|
|
|
|
/* Look up the gp-frame on the requested frame number, but don't add a new one */
|
|
bGPDframe *BKE_gpencil_layer_find_frame(bGPDlayer *gpl, int cframe)
|
|
{
|
|
bGPDframe *gpf;
|
|
|
|
/* Search in reverse order, since this is often used for playback/adding,
|
|
* where it's less likely that we're interested in the earlier frames
|
|
*/
|
|
for (gpf = gpl->frames.last; gpf; gpf = gpf->prev) {
|
|
if (gpf->framenum == cframe) {
|
|
return gpf;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* get the appropriate gp-frame from a given layer
|
|
* - this sets the layer's actframe var (if allowed to)
|
|
* - extension beyond range (if first gp-frame is after all frame in interest and cannot add)
|
|
*/
|
|
bGPDframe *BKE_gpencil_layer_getframe(bGPDlayer *gpl, int cframe, eGP_GetFrame_Mode addnew)
|
|
{
|
|
bGPDframe *gpf = NULL;
|
|
short found = 0;
|
|
|
|
/* error checking */
|
|
if (gpl == NULL) return NULL;
|
|
|
|
/* check if there is already an active frame */
|
|
if (gpl->actframe) {
|
|
gpf = gpl->actframe;
|
|
|
|
/* do not allow any changes to layer's active frame if layer is locked from changes
|
|
* or if the layer has been set to stay on the current frame
|
|
*/
|
|
if (gpl->flag & GP_LAYER_FRAMELOCK)
|
|
return gpf;
|
|
/* do not allow any changes to actframe if frame has painting tag attached to it */
|
|
if (gpf->flag & GP_FRAME_PAINT)
|
|
return gpf;
|
|
|
|
/* try to find matching frame */
|
|
if (gpf->framenum < cframe) {
|
|
for (; gpf; gpf = gpf->next) {
|
|
if (gpf->framenum == cframe) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
else if ((gpf->next) && (gpf->next->framenum > cframe)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set the appropriate frame */
|
|
if (addnew) {
|
|
if ((found) && (gpf->framenum == cframe))
|
|
gpl->actframe = gpf;
|
|
else if (addnew == GP_GETFRAME_ADD_COPY)
|
|
gpl->actframe = BKE_gpencil_frame_addcopy(gpl, cframe);
|
|
else
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
|
}
|
|
else if (found)
|
|
gpl->actframe = gpf;
|
|
else
|
|
gpl->actframe = gpl->frames.last;
|
|
}
|
|
else {
|
|
for (; gpf; gpf = gpf->prev) {
|
|
if (gpf->framenum <= cframe) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set the appropriate frame */
|
|
if (addnew) {
|
|
if ((found) && (gpf->framenum == cframe))
|
|
gpl->actframe = gpf;
|
|
else if (addnew == GP_GETFRAME_ADD_COPY)
|
|
gpl->actframe = BKE_gpencil_frame_addcopy(gpl, cframe);
|
|
else
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
|
}
|
|
else if (found)
|
|
gpl->actframe = gpf;
|
|
else
|
|
gpl->actframe = gpl->frames.first;
|
|
}
|
|
}
|
|
else if (gpl->frames.first) {
|
|
/* check which of the ends to start checking from */
|
|
const int first = ((bGPDframe *)(gpl->frames.first))->framenum;
|
|
const int last = ((bGPDframe *)(gpl->frames.last))->framenum;
|
|
|
|
if (abs(cframe - first) > abs(cframe - last)) {
|
|
/* find gp-frame which is less than or equal to cframe */
|
|
for (gpf = gpl->frames.last; gpf; gpf = gpf->prev) {
|
|
if (gpf->framenum <= cframe) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* find gp-frame which is less than or equal to cframe */
|
|
for (gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
if (gpf->framenum <= cframe) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set the appropriate frame */
|
|
if (addnew) {
|
|
if ((found) && (gpf->framenum == cframe))
|
|
gpl->actframe = gpf;
|
|
else
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
|
}
|
|
else if (found)
|
|
gpl->actframe = gpf;
|
|
else {
|
|
/* unresolved errogenous situation! */
|
|
printf("Error: cannot find appropriate gp-frame\n");
|
|
/* gpl->actframe should still be NULL */
|
|
}
|
|
}
|
|
else {
|
|
/* currently no frames (add if allowed to) */
|
|
if (addnew)
|
|
gpl->actframe = BKE_gpencil_frame_addnew(gpl, cframe);
|
|
else {
|
|
/* don't do anything... this may be when no frames yet! */
|
|
/* gpl->actframe should still be NULL */
|
|
}
|
|
}
|
|
|
|
/* return */
|
|
return gpl->actframe;
|
|
}
|
|
|
|
/* delete the given frame from a layer */
|
|
bool BKE_gpencil_layer_delframe(bGPDlayer *gpl, bGPDframe *gpf)
|
|
{
|
|
bool changed = false;
|
|
|
|
/* error checking */
|
|
if (ELEM(NULL, gpl, gpf))
|
|
return false;
|
|
|
|
/* if this frame was active, make the previous frame active instead
|
|
* since it's tricky to set active frame otherwise
|
|
*/
|
|
if (gpl->actframe == gpf)
|
|
gpl->actframe = gpf->prev;
|
|
|
|
/* free the frame and its data */
|
|
changed = BKE_gpencil_free_strokes(gpf);
|
|
BLI_freelinkN(&gpl->frames, gpf);
|
|
|
|
return changed;
|
|
}
|
|
|
|
/* get the active gp-layer for editing */
|
|
bGPDlayer *BKE_gpencil_layer_getactive(bGPdata *gpd)
|
|
{
|
|
bGPDlayer *gpl;
|
|
|
|
/* error checking */
|
|
if (ELEM(NULL, gpd, gpd->layers.first))
|
|
return NULL;
|
|
|
|
/* loop over layers until found (assume only one active) */
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
if (gpl->flag & GP_LAYER_ACTIVE)
|
|
return gpl;
|
|
}
|
|
|
|
/* no active layer found */
|
|
return NULL;
|
|
}
|
|
|
|
/* set the active gp-layer */
|
|
void BKE_gpencil_layer_setactive(bGPdata *gpd, bGPDlayer *active)
|
|
{
|
|
bGPDlayer *gpl;
|
|
|
|
/* error checking */
|
|
if (ELEM(NULL, gpd, gpd->layers.first, active))
|
|
return;
|
|
|
|
/* loop over layers deactivating all */
|
|
for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
gpl->flag &= ~GP_LAYER_ACTIVE;
|
|
if (gpd->flag & GP_DATA_AUTOLOCK_LAYERS) {
|
|
gpl->flag |= GP_LAYER_LOCKED;
|
|
}
|
|
}
|
|
|
|
/* set as active one */
|
|
active->flag |= GP_LAYER_ACTIVE;
|
|
if (gpd->flag & GP_DATA_AUTOLOCK_LAYERS) {
|
|
active->flag &= ~GP_LAYER_LOCKED;
|
|
}
|
|
}
|
|
|
|
/* delete the active gp-layer */
|
|
void BKE_gpencil_layer_delete(bGPdata *gpd, bGPDlayer *gpl)
|
|
{
|
|
/* error checking */
|
|
if (ELEM(NULL, gpd, gpl))
|
|
return;
|
|
|
|
/* free layer */
|
|
BKE_gpencil_free_frames(gpl);
|
|
|
|
/* free icon providing preview of icon color */
|
|
BKE_icon_delete(gpl->runtime.icon_id);
|
|
|
|
BLI_freelinkN(&gpd->layers, gpl);
|
|
}
|
|
|
|
Material *BKE_gpencil_get_material_from_brush(Brush *brush)
|
|
{
|
|
Material *ma = NULL;
|
|
|
|
if ((brush != NULL) && (brush->gpencil_settings != NULL) &&
|
|
(brush->gpencil_settings->material != NULL))
|
|
{
|
|
ma = brush->gpencil_settings->material;
|
|
}
|
|
|
|
return ma;
|
|
}
|
|
|
|
/* Get active color, and add all default settings if we don't find anything */
|
|
Material *BKE_gpencil_material_ensure(Main *bmain, Object *ob)
|
|
{
|
|
Material *ma = NULL;
|
|
|
|
/* sanity checks */
|
|
if (ELEM(NULL, bmain, ob))
|
|
return NULL;
|
|
|
|
ma = give_current_material(ob, ob->actcol);
|
|
if (ma == NULL) {
|
|
if (ob->totcol == 0) {
|
|
BKE_object_material_slot_add(bmain, ob);
|
|
}
|
|
ma = BKE_material_add_gpencil(bmain, DATA_("Material"));
|
|
assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
}
|
|
else if (ma->gp_style == NULL) {
|
|
BKE_material_init_gpencil_settings(ma);
|
|
}
|
|
|
|
return ma;
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* GP Object - Boundbox Support */
|
|
|
|
/**
|
|
* Get min/max coordinate bounds for single stroke
|
|
* \return Returns whether we found any selected points
|
|
*/
|
|
bool BKE_gpencil_stroke_minmax(
|
|
const bGPDstroke *gps, const bool use_select,
|
|
float r_min[3], float r_max[3])
|
|
{
|
|
const bGPDspoint *pt;
|
|
int i;
|
|
bool changed = false;
|
|
|
|
if (ELEM(NULL, gps, r_min, r_max))
|
|
return false;
|
|
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
|
if ((use_select == false) || (pt->flag & GP_SPOINT_SELECT)) {
|
|
minmax_v3v3_v3(r_min, r_max, &pt->x);
|
|
changed = true;
|
|
}
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
/* get min/max bounds of all strokes in GP datablock */
|
|
bool BKE_gpencil_data_minmax(Object *ob, const bGPdata *gpd, float r_min[3], float r_max[3])
|
|
{
|
|
float bmat[3][3];
|
|
bool changed = false;
|
|
|
|
INIT_MINMAX(r_min, r_max);
|
|
|
|
if (gpd == NULL)
|
|
return changed;
|
|
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
bGPDframe *gpf = gpl->actframe;
|
|
|
|
if (gpf != NULL) {
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
changed = BKE_gpencil_stroke_minmax(gps, false, r_min, r_max);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((changed) && (ob)) {
|
|
copy_m3_m4(bmat, ob->obmat);
|
|
mul_m3_v3(bmat, r_min);
|
|
add_v3_v3(r_min, ob->obmat[3]);
|
|
mul_m3_v3(bmat, r_max);
|
|
add_v3_v3(r_max, ob->obmat[3]);
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
bool BKE_gpencil_stroke_select_check(
|
|
const bGPDstroke *gps)
|
|
{
|
|
const bGPDspoint *pt;
|
|
int i;
|
|
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
|
|
if (pt->flag & GP_SPOINT_SELECT) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* compute center of bounding box */
|
|
void BKE_gpencil_centroid_3d(bGPdata *gpd, float r_centroid[3])
|
|
{
|
|
float min[3], max[3], tot[3];
|
|
|
|
BKE_gpencil_data_minmax(NULL, gpd, min, max);
|
|
|
|
add_v3_v3v3(tot, min, max);
|
|
mul_v3_v3fl(r_centroid, tot, 0.5f);
|
|
}
|
|
|
|
|
|
/* create bounding box values */
|
|
static void boundbox_gpencil(Object *ob)
|
|
{
|
|
BoundBox *bb;
|
|
bGPdata *gpd;
|
|
float min[3], max[3];
|
|
|
|
if (ob->bb == NULL) {
|
|
ob->bb = MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
|
|
}
|
|
|
|
bb = ob->bb;
|
|
gpd = ob->data;
|
|
|
|
BKE_gpencil_data_minmax(NULL, gpd, min, max);
|
|
BKE_boundbox_init_from_minmax(bb, min, max);
|
|
|
|
bb->flag &= ~BOUNDBOX_DIRTY;
|
|
}
|
|
|
|
/* get bounding box */
|
|
BoundBox *BKE_gpencil_boundbox_get(Object *ob)
|
|
{
|
|
bGPdata *gpd;
|
|
|
|
if (ELEM(NULL, ob, ob->data))
|
|
return NULL;
|
|
|
|
gpd = ob->data;
|
|
if ((ob->bb) && ((ob->bb->flag & BOUNDBOX_DIRTY) == 0) &&
|
|
((gpd->flag & GP_DATA_CACHE_IS_DIRTY) == 0))
|
|
{
|
|
return ob->bb;
|
|
}
|
|
|
|
boundbox_gpencil(ob);
|
|
|
|
return ob->bb;
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* Apply Transforms */
|
|
|
|
void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4])
|
|
{
|
|
if (gpd == NULL)
|
|
return;
|
|
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
/* FIXME: For now, we just skip parented layers.
|
|
* Otherwise, we have to update each frame to find
|
|
* the current parent position/effects.
|
|
*/
|
|
if (gpl->parent) {
|
|
continue;
|
|
}
|
|
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
bGPDspoint *pt;
|
|
int i;
|
|
|
|
for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) {
|
|
mul_m4_v3(mat, &pt->x);
|
|
}
|
|
|
|
/* TODO: Do we need to do this? distortion may mean we need to re-triangulate */
|
|
gps->flag |= GP_STROKE_RECALC_CACHES;
|
|
gps->tot_triangles = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* ************************************************** */
|
|
/* GP Object - Vertex Groups */
|
|
|
|
/* remove a vertex group */
|
|
void BKE_gpencil_vgroup_remove(Object *ob, bDeformGroup *defgroup)
|
|
{
|
|
bGPdata *gpd = ob->data;
|
|
MDeformVert *dvert = NULL;
|
|
const int def_nr = BLI_findindex(&ob->defbase, defgroup);
|
|
|
|
/* Remove points data */
|
|
if (gpd) {
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
if (gps->dvert != NULL) {
|
|
for (int i = 0; i < gps->totpoints; i++) {
|
|
dvert = &gps->dvert[i];
|
|
MDeformWeight *dw = defvert_find_index(dvert, def_nr);
|
|
if (dw != NULL) {
|
|
defvert_remove_group(dvert, dw);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove the group */
|
|
BLI_freelinkN(&ob->defbase, defgroup);
|
|
DEG_id_tag_update(&gpd->id, OB_RECALC_OB | OB_RECALC_DATA);
|
|
}
|
|
|
|
|
|
void BKE_gpencil_dvert_ensure(bGPDstroke *gps)
|
|
{
|
|
if (gps->dvert == NULL) {
|
|
gps->dvert = MEM_callocN(sizeof(MDeformVert) * gps->totpoints, "gp_stroke_weights");
|
|
}
|
|
}
|
|
|
|
/* ************************************************** */
|
|
|
|
/**
|
|
* Apply smooth to stroke point
|
|
* \param gps Stroke to smooth
|
|
* \param i Point index
|
|
* \param inf Amount of smoothing to apply
|
|
*/
|
|
bool BKE_gpencil_smooth_stroke(bGPDstroke *gps, int i, float inf)
|
|
{
|
|
bGPDspoint *pt = &gps->points[i];
|
|
// float pressure = 0.0f;
|
|
float sco[3] = { 0.0f };
|
|
|
|
/* Do nothing if not enough points to smooth out */
|
|
if (gps->totpoints <= 2) {
|
|
return false;
|
|
}
|
|
|
|
/* Only affect endpoints by a fraction of the normal strength,
|
|
* to prevent the stroke from shrinking too much
|
|
*/
|
|
if ((i == 0) || (i == gps->totpoints - 1)) {
|
|
inf *= 0.1f;
|
|
}
|
|
|
|
/* Compute smoothed coordinate by taking the ones nearby */
|
|
/* XXX: This is potentially slow, and suffers from accumulation error as earlier points are handled before later ones */
|
|
{
|
|
// XXX: this is hardcoded to look at 2 points on either side of the current one (i.e. 5 items total)
|
|
const int steps = 2;
|
|
const float average_fac = 1.0f / (float)(steps * 2 + 1);
|
|
int step;
|
|
|
|
/* add the point itself */
|
|
madd_v3_v3fl(sco, &pt->x, average_fac);
|
|
|
|
/* n-steps before/after current point */
|
|
// XXX: review how the endpoints are treated by this algorithm
|
|
// XXX: falloff measures should also introduce some weighting variations, so that further-out points get less weight
|
|
for (step = 1; step <= steps; step++) {
|
|
bGPDspoint *pt1, *pt2;
|
|
int before = i - step;
|
|
int after = i + step;
|
|
|
|
CLAMP_MIN(before, 0);
|
|
CLAMP_MAX(after, gps->totpoints - 1);
|
|
|
|
pt1 = &gps->points[before];
|
|
pt2 = &gps->points[after];
|
|
|
|
/* add both these points to the average-sum (s += p[i]/n) */
|
|
madd_v3_v3fl(sco, &pt1->x, average_fac);
|
|
madd_v3_v3fl(sco, &pt2->x, average_fac);
|
|
|
|
}
|
|
}
|
|
|
|
/* Based on influence factor, blend between original and optimal smoothed coordinate */
|
|
interp_v3_v3v3(&pt->x, &pt->x, sco, inf);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Apply smooth for strength to stroke point */
|
|
bool BKE_gpencil_smooth_stroke_strength(bGPDstroke *gps, int point_index, float influence)
|
|
{
|
|
bGPDspoint *ptb = &gps->points[point_index];
|
|
|
|
/* Do nothing if not enough points */
|
|
if (gps->totpoints <= 2) {
|
|
return false;
|
|
}
|
|
|
|
/* Compute theoretical optimal value using distances */
|
|
bGPDspoint *pta, *ptc;
|
|
int before = point_index - 1;
|
|
int after = point_index + 1;
|
|
|
|
CLAMP_MIN(before, 0);
|
|
CLAMP_MAX(after, gps->totpoints - 1);
|
|
|
|
pta = &gps->points[before];
|
|
ptc = &gps->points[after];
|
|
|
|
/* the optimal value is the corresponding to the interpolation of the strength
|
|
* at the distance of point b
|
|
*/
|
|
float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x);
|
|
/* sometimes the factor can be wrong due stroke geometry, so use middle point */
|
|
if ((fac < 0.0f) || (fac > 1.0f)) {
|
|
fac = 0.5f;
|
|
}
|
|
const float optimal = (1.0f - fac) * pta->strength + fac * ptc->strength;
|
|
|
|
/* Based on influence factor, blend between original and optimal */
|
|
ptb->strength = (1.0f - influence) * ptb->strength + influence * optimal;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Apply smooth for thickness to stroke point (use pressure) */
|
|
bool BKE_gpencil_smooth_stroke_thickness(bGPDstroke *gps, int point_index, float influence)
|
|
{
|
|
bGPDspoint *ptb = &gps->points[point_index];
|
|
|
|
/* Do nothing if not enough points */
|
|
if ((gps->totpoints <= 2) || (point_index < 1)) {
|
|
return false;
|
|
}
|
|
|
|
/* Compute theoretical optimal value using distances */
|
|
bGPDspoint *pta, *ptc;
|
|
int before = point_index - 1;
|
|
int after = point_index + 1;
|
|
|
|
CLAMP_MIN(before, 0);
|
|
CLAMP_MAX(after, gps->totpoints - 1);
|
|
|
|
pta = &gps->points[before];
|
|
ptc = &gps->points[after];
|
|
|
|
/* the optimal value is the corresponding to the interpolation of the pressure
|
|
* at the distance of point b
|
|
*/
|
|
float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x);
|
|
/* sometimes the factor can be wrong due stroke geometry, so use middle point */
|
|
if ((fac < 0.0f) || (fac > 1.0f)) {
|
|
fac = 0.5f;
|
|
}
|
|
float optimal = interpf(ptc->pressure, pta->pressure, fac);
|
|
|
|
/* Based on influence factor, blend between original and optimal */
|
|
ptb->pressure = interpf(optimal, ptb->pressure, influence);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Apply smooth for UV rotation to stroke point (use pressure) */
|
|
bool BKE_gpencil_smooth_stroke_uv(bGPDstroke *gps, int point_index, float influence)
|
|
{
|
|
bGPDspoint *ptb = &gps->points[point_index];
|
|
|
|
/* Do nothing if not enough points */
|
|
if (gps->totpoints <= 2) {
|
|
return false;
|
|
}
|
|
|
|
/* Compute theoretical optimal value */
|
|
bGPDspoint *pta, *ptc;
|
|
int before = point_index - 1;
|
|
int after = point_index + 1;
|
|
|
|
CLAMP_MIN(before, 0);
|
|
CLAMP_MAX(after, gps->totpoints - 1);
|
|
|
|
pta = &gps->points[before];
|
|
ptc = &gps->points[after];
|
|
|
|
/* the optimal value is the corresponding to the interpolation of the pressure
|
|
* at the distance of point b
|
|
*/
|
|
float fac = line_point_factor_v3(&ptb->x, &pta->x, &ptc->x);
|
|
/* sometimes the factor can be wrong due stroke geometry, so use middle point */
|
|
if ((fac < 0.0f) || (fac > 1.0f)) {
|
|
fac = 0.5f;
|
|
}
|
|
float optimal = interpf(ptc->uv_rot, pta->uv_rot, fac);
|
|
|
|
/* Based on influence factor, blend between original and optimal */
|
|
ptb->uv_rot = interpf(optimal, ptb->uv_rot, influence);
|
|
CLAMP(ptb->uv_rot, -M_PI_2, M_PI_2);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get range of selected frames in layer.
|
|
* Always the active frame is considered as selected, so if no more selected the range
|
|
* will be equal to the current active frame.
|
|
* \param gpl Layer
|
|
* \param r_initframe Number of first selected frame
|
|
* \param r_endframe Number of last selected frame
|
|
*/
|
|
void BKE_gpencil_get_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_endframe)
|
|
{
|
|
*r_initframe = gpl->actframe->framenum;
|
|
*r_endframe = gpl->actframe->framenum;
|
|
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
if (gpf->flag & GP_FRAME_SELECT) {
|
|
if (gpf->framenum < *r_initframe) {
|
|
*r_initframe = gpf->framenum;
|
|
}
|
|
if (gpf->framenum > *r_endframe) {
|
|
*r_endframe = gpf->framenum;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Falloff factor base on frame range
|
|
* \param gpf Frame
|
|
* \param actnum Number of active frame in layer
|
|
* \param f_init Number of first selected frame
|
|
* \param f_end Number of last selected frame
|
|
* \param cur_falloff Curve with falloff factors
|
|
*/
|
|
float BKE_gpencil_multiframe_falloff_calc(bGPDframe *gpf, int actnum, int f_init, int f_end, CurveMapping *cur_falloff)
|
|
{
|
|
float fnum = 0.5f; /* default mid curve */
|
|
float value;
|
|
|
|
/* check curve is available */
|
|
if (cur_falloff == NULL) {
|
|
return 1.0f;
|
|
}
|
|
|
|
/* frames to the right of the active frame */
|
|
if (gpf->framenum < actnum) {
|
|
fnum = (float)(gpf->framenum - f_init) / (actnum - f_init);
|
|
fnum *= 0.5f;
|
|
value = curvemapping_evaluateF(cur_falloff, 0, fnum);
|
|
}
|
|
/* frames to the left of the active frame */
|
|
else if (gpf->framenum > actnum) {
|
|
fnum = (float)(gpf->framenum - actnum) / (f_end - actnum);
|
|
fnum *= 0.5f;
|
|
value = curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f);
|
|
}
|
|
else {
|
|
value = 1.0f;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/* remove strokes using a material */
|
|
void BKE_gpencil_material_index_remove(bGPdata *gpd, int index)
|
|
{
|
|
bGPDstroke *gps, *gpsn;
|
|
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
for (gps = gpf->strokes.first; gps; gps = gpsn) {
|
|
gpsn = gps->next;
|
|
if (gps->mat_nr == index) {
|
|
if (gps->points) {
|
|
MEM_freeN(gps->points);
|
|
}
|
|
if (gps->dvert) {
|
|
BKE_gpencil_free_stroke_weights(gps);
|
|
MEM_freeN(gps->dvert);
|
|
}
|
|
if (gps->triangles) MEM_freeN(gps->triangles);
|
|
BLI_freelinkN(&gpf->strokes, gps);
|
|
}
|
|
else {
|
|
/* reassign strokes */
|
|
if (gps->mat_nr > index) {
|
|
gps->mat_nr--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BKE_gpencil_material_remap(struct bGPdata *gpd, const unsigned int *remap, unsigned int remap_len)
|
|
{
|
|
const short remap_len_short = (short)remap_len;
|
|
|
|
#define MAT_NR_REMAP(n) \
|
|
if (n < remap_len_short) { \
|
|
BLI_assert(n >= 0 && remap[n] < remap_len_short); \
|
|
n = remap[n]; \
|
|
} ((void)0)
|
|
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
/* reassign strokes */
|
|
MAT_NR_REMAP(gps->mat_nr);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef MAT_NR_REMAP
|
|
|
|
}
|
|
|
|
/* statistics functions */
|
|
void BKE_gpencil_stats_update(bGPdata *gpd)
|
|
{
|
|
gpd->totlayer = 0;
|
|
gpd->totframe = 0;
|
|
gpd->totstroke = 0;
|
|
gpd->totpoint = 0;
|
|
|
|
for (bGPDlayer *gpl = gpd->layers.first; gpl; gpl = gpl->next) {
|
|
gpd->totlayer++;
|
|
for (bGPDframe *gpf = gpl->frames.first; gpf; gpf = gpf->next) {
|
|
gpd->totframe++;
|
|
for (bGPDstroke *gps = gpf->strokes.first; gps; gps = gps->next) {
|
|
gpd->totstroke++;
|
|
gpd->totpoint += gps->totpoints;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* get material index */
|
|
int BKE_gpencil_get_material_index(Object *ob, Material *ma)
|
|
{
|
|
short *totcol = give_totcolp(ob);
|
|
Material *read_ma = NULL;
|
|
for (short i = 0; i < *totcol; i++) {
|
|
read_ma = give_current_material(ob, i + 1);
|
|
if (ma == read_ma) {
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|