1909 lines
48 KiB
C
1909 lines
48 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) 2008, Blender Foundation
|
|
* This is a new part of Blender
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <math.h>
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_string_utils.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_action.h"
|
|
#include "BKE_animsys.h"
|
|
#include "BKE_deform.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"
|
|
|
|
static CLG_LogRef LOG = {"bke.gpencil"};
|
|
|
|
/* ************************************************** */
|
|
/* 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, ID_RECALC_GEOMETRY);
|
|
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) {
|
|
CLOG_ERROR(&LOG, "Frame (%d) existed already for this layer. Using existing frame", 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 behavior 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;
|
|
}
|
|
|
|
/* 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;
|
|
/* always enable object onion skin swith */
|
|
gpd->flag |= GP_DATA_SHOW_ONIONSKINS;
|
|
/* GP object specific settings */
|
|
ARRAY_SET_ITEMS(gpd->line_color, 0.6f, 0.6f, 0.6f, 0.5f);
|
|
|
|
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;
|
|
gps->inittime = 0;
|
|
|
|
/* enable recalculation flag by default */
|
|
gps->flag = GP_STROKE_RECALC_GEOMETRY | 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_GEOMETRY;
|
|
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_GEOMETRY; */
|
|
|
|
/* 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 BKE_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(bmain, &gpd->id, (ID **)&gpd_copy);
|
|
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(bmain, &gpd_src->id, (ID **)&gpd_dst);
|
|
}
|
|
|
|
/* 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! */
|
|
CLOG_STR_ERROR(&LOG, "cannot find appropriate gp-frame");
|
|
/* 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;
|
|
}
|
|
|
|
void BKE_gpencil_brush_set_material(Brush *brush, Material *ma)
|
|
{
|
|
BLI_assert(brush);
|
|
BLI_assert(brush->gpencil_settings);
|
|
if (brush->gpencil_settings->material != ma) {
|
|
if (brush->gpencil_settings->material) {
|
|
id_us_min(&brush->gpencil_settings->material->id);
|
|
}
|
|
if (ma) {
|
|
id_us_plus(&ma->id);
|
|
}
|
|
brush->gpencil_settings->material = ma;
|
|
}
|
|
}
|
|
|
|
/* Adds the pinned material to the object if necessary. */
|
|
Material *BKE_gpencil_handle_brush_material(Main *bmain, Object *ob, Brush *brush)
|
|
{
|
|
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
Material *ma = BKE_gpencil_get_material_from_brush(brush);
|
|
|
|
/* check if the material is already on object material slots and add it if missing */
|
|
if (ma && BKE_gpencil_get_material_index(ob, ma) < 0) {
|
|
BKE_object_material_slot_add(bmain, ob);
|
|
assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
}
|
|
|
|
return ma;
|
|
}
|
|
else {
|
|
/* using active material instead */
|
|
return give_current_material(ob, ob->actcol);
|
|
}
|
|
}
|
|
|
|
/* Assigns the material to object (if not already present) and returns its index (mat_nr). */
|
|
int BKE_gpencil_handle_material(Main *bmain, Object *ob, Material *material)
|
|
{
|
|
if (!material) {
|
|
return -1;
|
|
}
|
|
int index = BKE_gpencil_get_material_index(ob, material);
|
|
if (index < 0) {
|
|
BKE_object_material_slot_add(bmain, ob);
|
|
assign_material(bmain, ob, material, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
return ob->totcol - 1;
|
|
}
|
|
return index;
|
|
}
|
|
|
|
/** Creates a new gpencil material and assigns it to object.
|
|
*
|
|
* \param *r_index: value is set to zero based index of the new material if r_index is not NULL
|
|
*/
|
|
Material *BKE_gpencil_handle_new_material(Main *bmain, Object *ob, const char *name, int *r_index)
|
|
{
|
|
Material *ma = BKE_material_add_gpencil(bmain, name);
|
|
id_us_min(&ma->id); /* no users yet */
|
|
|
|
BKE_object_material_slot_add(bmain, ob);
|
|
assign_material(bmain, ob, ma, ob->totcol, BKE_MAT_ASSIGN_USERPREF);
|
|
|
|
if (r_index) {
|
|
*r_index = ob->actcol - 1;
|
|
}
|
|
return ma;
|
|
}
|
|
|
|
/* Returns the material for a brush with respect to its pinned state. */
|
|
Material *BKE_gpencil_get_material_for_brush(Object *ob, Brush *brush)
|
|
{
|
|
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
Material *ma = BKE_gpencil_get_material_from_brush(brush);
|
|
return ma;
|
|
}
|
|
else {
|
|
return give_current_material(ob, ob->actcol);
|
|
}
|
|
}
|
|
|
|
/* Returns the material index for a brush with respect to its pinned state. */
|
|
int BKE_gpencil_get_material_index_for_brush(Object *ob, Brush *brush)
|
|
{
|
|
if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
return BKE_gpencil_get_material_index(ob, brush->gpencil_settings->material);
|
|
}
|
|
else {
|
|
return ob->actcol - 1;
|
|
}
|
|
}
|
|
|
|
/* Guaranteed to return a material assigned to object. Returns never NULL. */
|
|
Material *BKE_gpencil_current_input_toolsettings_material(Main *bmain, Object *ob, ToolSettings *ts)
|
|
{
|
|
if (ts && ts->gp_paint && ts->gp_paint->paint.brush) {
|
|
return BKE_gpencil_current_input_brush_material(bmain, ob, ts->gp_paint->paint.brush);
|
|
}
|
|
else {
|
|
return BKE_gpencil_current_input_brush_material(bmain, ob, NULL);
|
|
}
|
|
}
|
|
|
|
/* Guaranteed to return a material assigned to object. Returns never NULL. */
|
|
Material *BKE_gpencil_current_input_brush_material(Main *bmain, Object *ob, Brush *brush)
|
|
{
|
|
if (brush) {
|
|
Material *ma = BKE_gpencil_handle_brush_material(bmain, ob, brush);
|
|
if (ma) {
|
|
return ma;
|
|
}
|
|
else if (brush->gpencil_settings->flag & GP_BRUSH_MATERIAL_PINNED) {
|
|
/* it is easier to just unpin a NULL material, instead of setting a new one */
|
|
brush->gpencil_settings->flag &= ~GP_BRUSH_MATERIAL_PINNED;
|
|
}
|
|
}
|
|
return BKE_gpencil_current_input_material(bmain, ob);
|
|
}
|
|
|
|
/* Guaranteed to return a material assigned to object. Returns never NULL. Only use this for materials unrelated to user input */
|
|
Material *BKE_gpencil_current_input_material(Main *bmain, Object *ob)
|
|
{
|
|
Material *ma = give_current_material(ob, ob->actcol);
|
|
if (ma) {
|
|
return ma;
|
|
}
|
|
return BKE_gpencil_handle_new_material(bmain, ob, "Material", NULL);
|
|
}
|
|
|
|
/* 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 = BKE_gpencil_current_input_material(bmain, ob);
|
|
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(const bGPdata *gpd, float r_min[3], float r_max[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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(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->runtime.bb == NULL) {
|
|
ob->runtime.bb = MEM_callocN(sizeof(BoundBox), "GPencil boundbox");
|
|
}
|
|
|
|
bb = ob->runtime.bb;
|
|
gpd = ob->data;
|
|
|
|
if (!BKE_gpencil_data_minmax(gpd, min, max)) {
|
|
min[0] = min[1] = min[2] = -1.0f;
|
|
max[0] = max[1] = max[2] = 1.0f;
|
|
}
|
|
|
|
BKE_boundbox_init_from_minmax(bb, min, max);
|
|
|
|
bb->flag &= ~BOUNDBOX_DIRTY;
|
|
}
|
|
|
|
/* get bounding box */
|
|
BoundBox *BKE_gpencil_boundbox_get(Object *ob)
|
|
{
|
|
if (ELEM(NULL, ob, ob->data))
|
|
return NULL;
|
|
|
|
bGPdata *gpd = (bGPdata *)ob->data;
|
|
if ((ob->runtime.bb) && ((gpd->flag & GP_DATA_CACHE_IS_DIRTY) == 0)) {
|
|
return ob->runtime.bb;
|
|
}
|
|
|
|
boundbox_gpencil(ob);
|
|
|
|
return ob->runtime.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_GEOMETRY;
|
|
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);
|
|
}
|
|
else {
|
|
/* reorganize weights in other strokes */
|
|
for (int g = 0; g < gps->dvert->totweight; g++) {
|
|
dw = &dvert->dw[g];
|
|
if ((dw != NULL) && (dw->def_nr > def_nr)) {
|
|
dw->def_nr--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Remove the group */
|
|
BLI_freelinkN(&ob->defbase, defgroup);
|
|
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
|
|
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 (0-based like mat_nr not actcol) */
|
|
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;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Get points of stroke always flat to view not affected by camera view or view position */
|
|
void BKE_gpencil_stroke_2d_flat(const bGPDspoint *points, int totpoints, float(*points2d)[2], int *r_direction)
|
|
{
|
|
BLI_assert(totpoints >= 2);
|
|
|
|
const bGPDspoint *pt0 = &points[0];
|
|
const bGPDspoint *pt1 = &points[1];
|
|
const bGPDspoint *pt3 = &points[(int)(totpoints * 0.75)];
|
|
|
|
float locx[3];
|
|
float locy[3];
|
|
float loc3[3];
|
|
float normal[3];
|
|
|
|
/* local X axis (p0 -> p1) */
|
|
sub_v3_v3v3(locx, &pt1->x, &pt0->x);
|
|
|
|
/* point vector at 3/4 */
|
|
float v3[3];
|
|
if (totpoints == 2) {
|
|
mul_v3_v3fl(v3, &pt3->x, 0.001f);
|
|
}
|
|
else {
|
|
copy_v3_v3(v3, &pt3->x);
|
|
}
|
|
|
|
sub_v3_v3v3(loc3, v3, &pt0->x);
|
|
|
|
/* vector orthogonal to polygon plane */
|
|
cross_v3_v3v3(normal, locx, loc3);
|
|
|
|
/* local Y axis (cross to normal/x axis) */
|
|
cross_v3_v3v3(locy, normal, locx);
|
|
|
|
/* Normalize vectors */
|
|
normalize_v3(locx);
|
|
normalize_v3(locy);
|
|
|
|
/* Get all points in local space */
|
|
for (int i = 0; i < totpoints; i++) {
|
|
const bGPDspoint *pt = &points[i];
|
|
float loc[3];
|
|
|
|
/* Get local space using first point as origin */
|
|
sub_v3_v3v3(loc, &pt->x, &pt0->x);
|
|
|
|
points2d[i][0] = dot_v3v3(loc, locx);
|
|
points2d[i][1] = dot_v3v3(loc, locy);
|
|
}
|
|
|
|
/* Concave (-1), Convex (1), or Autodetect (0)? */
|
|
*r_direction = (int)locy[2];
|
|
}
|
|
|
|
/* Get points of stroke always flat to view not affected by camera view or view position
|
|
* using another stroke as reference
|
|
*/
|
|
void BKE_gpencil_stroke_2d_flat_ref(
|
|
const bGPDspoint *ref_points, int ref_totpoints,
|
|
const bGPDspoint *points, int totpoints,
|
|
float(*points2d)[2], const float scale, int *r_direction)
|
|
{
|
|
BLI_assert(totpoints >= 2);
|
|
|
|
const bGPDspoint *pt0 = &ref_points[0];
|
|
const bGPDspoint *pt1 = &ref_points[1];
|
|
const bGPDspoint *pt3 = &ref_points[(int)(ref_totpoints * 0.75)];
|
|
|
|
float locx[3];
|
|
float locy[3];
|
|
float loc3[3];
|
|
float normal[3];
|
|
|
|
/* local X axis (p0 -> p1) */
|
|
sub_v3_v3v3(locx, &pt1->x, &pt0->x);
|
|
|
|
/* point vector at 3/4 */
|
|
float v3[3];
|
|
if (totpoints == 2) {
|
|
mul_v3_v3fl(v3, &pt3->x, 0.001f);
|
|
}
|
|
else {
|
|
copy_v3_v3(v3, &pt3->x);
|
|
}
|
|
|
|
sub_v3_v3v3(loc3, v3, &pt0->x);
|
|
|
|
/* vector orthogonal to polygon plane */
|
|
cross_v3_v3v3(normal, locx, loc3);
|
|
|
|
/* local Y axis (cross to normal/x axis) */
|
|
cross_v3_v3v3(locy, normal, locx);
|
|
|
|
/* Normalize vectors */
|
|
normalize_v3(locx);
|
|
normalize_v3(locy);
|
|
|
|
/* Get all points in local space */
|
|
for (int i = 0; i < totpoints; i++) {
|
|
const bGPDspoint *pt = &points[i];
|
|
float loc[3];
|
|
float v1[3];
|
|
float vn[3] = { 0.0f, 0.0f, 0.0f };
|
|
|
|
/* apply scale to extremes of the stroke to get better collision detection
|
|
* the scale is divided to get more control in the UI parameter
|
|
*/
|
|
/* first point */
|
|
if (i == 0) {
|
|
const bGPDspoint *pt_next = &points[i + 1];
|
|
sub_v3_v3v3(vn, &pt->x, &pt_next->x);
|
|
normalize_v3(vn);
|
|
mul_v3_fl(vn, scale / 10.0f);
|
|
add_v3_v3v3(v1, &pt->x, vn);
|
|
}
|
|
/* last point */
|
|
else if (i == totpoints - 1) {
|
|
const bGPDspoint *pt_prev = &points[i - 1];
|
|
sub_v3_v3v3(vn, &pt->x, &pt_prev->x);
|
|
normalize_v3(vn);
|
|
mul_v3_fl(vn, scale / 10.0f);
|
|
add_v3_v3v3(v1, &pt->x, vn);
|
|
}
|
|
else {
|
|
copy_v3_v3(v1, &pt->x);
|
|
}
|
|
|
|
/* Get local space using first point as origin (ref stroke) */
|
|
sub_v3_v3v3(loc, v1, &pt0->x);
|
|
|
|
points2d[i][0] = dot_v3v3(loc, locx);
|
|
points2d[i][1] = dot_v3v3(loc, locy);
|
|
}
|
|
|
|
/* Concave (-1), Convex (1), or Autodetect (0)? */
|
|
*r_direction = (int)locy[2];
|
|
}
|
|
|
|
/**
|
|
* Trim stroke to the first intersection or loop
|
|
* \param gps: Stroke data
|
|
*/
|
|
bool BKE_gpencil_trim_stroke(bGPDstroke *gps)
|
|
{
|
|
if (gps->totpoints < 4) {
|
|
return false;
|
|
}
|
|
bool intersect = false;
|
|
int start, end;
|
|
float point[3];
|
|
/* loop segments from start until we have an intersection */
|
|
for (int i = 0; i < gps->totpoints - 2; i++) {
|
|
start = i;
|
|
bGPDspoint *a = &gps->points[start];
|
|
bGPDspoint *b = &gps->points[start + 1];
|
|
for (int j = start + 2; j < gps->totpoints - 1; j++) {
|
|
end = j + 1;
|
|
bGPDspoint *c = &gps->points[j];
|
|
bGPDspoint *d = &gps->points[end];
|
|
float pointb[3];
|
|
/* get intersection */
|
|
if (isect_line_line_v3(&a->x, &b->x, &c->x, &d->x, point, pointb)) {
|
|
if (len_v3(point) > 0.0f) {
|
|
float closest[3];
|
|
/* check intersection is on both lines */
|
|
float lambda = closest_to_line_v3(closest, point, &a->x, &b->x);
|
|
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
|
|
continue;
|
|
}
|
|
lambda = closest_to_line_v3(closest, point, &c->x, &d->x);
|
|
if ((lambda <= 0.0f) || (lambda >= 1.0f)) {
|
|
continue;
|
|
}
|
|
else {
|
|
intersect = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (intersect) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* trim unwanted points */
|
|
if (intersect) {
|
|
|
|
/* save points */
|
|
bGPDspoint *old_points = MEM_dupallocN(gps->points);
|
|
MDeformVert *old_dvert = NULL;
|
|
MDeformVert *dvert_src = NULL;
|
|
|
|
if (gps->dvert != NULL) {
|
|
old_dvert = MEM_dupallocN(gps->dvert);
|
|
}
|
|
|
|
/* resize gps */
|
|
int newtot = end - start + 1;
|
|
|
|
gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * newtot);
|
|
if (gps->dvert != NULL) {
|
|
gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * newtot);
|
|
}
|
|
|
|
for (int i = 0; i < newtot; i++) {
|
|
int idx = start + i;
|
|
bGPDspoint *pt_src = &old_points[idx];
|
|
bGPDspoint *pt_new = &gps->points[i];
|
|
memcpy(pt_new, pt_src, sizeof(bGPDspoint));
|
|
if (gps->dvert != NULL) {
|
|
dvert_src = &old_dvert[idx];
|
|
MDeformVert *dvert = &gps->dvert[i];
|
|
memcpy(dvert, dvert_src, sizeof(MDeformVert));
|
|
if (dvert_src->dw) {
|
|
memcpy(dvert->dw, dvert_src->dw, sizeof(MDeformWeight));
|
|
}
|
|
}
|
|
if (idx == start || idx == end) {
|
|
copy_v3_v3(&pt_new->x, point);
|
|
}
|
|
}
|
|
|
|
gps->flag |= GP_STROKE_RECALC_GEOMETRY;
|
|
gps->tot_triangles = 0;
|
|
gps->totpoints = newtot;
|
|
|
|
MEM_SAFE_FREE(old_points);
|
|
MEM_SAFE_FREE(old_dvert);
|
|
}
|
|
return intersect;
|
|
}
|