From 6e39445f80bea7abf19c9a82d0dcc3cc3dddc7d2 Mon Sep 17 00:00:00 2001 From: Antonio Vazquez Date: Thu, 19 Mar 2020 11:35:17 +0100 Subject: GPencil: Cleanup - Split BKE_gpencil.h geometry functions into BKE_gpencil_geom.h This split prepare the code for future geometry functions. --- source/blender/blenkernel/BKE_gpencil.h | 67 - source/blender/blenkernel/BKE_gpencil_geom.h | 113 + source/blender/blenkernel/CMakeLists.txt | 2 + source/blender/blenkernel/intern/gpencil.c | 2178 +------------------- source/blender/blenkernel/intern/gpencil_geom.c | 2022 ++++++++++++++++++ .../blender/blenkernel/intern/gpencil_modifier.c | 1 + source/blender/blenkernel/intern/object.c | 1 + source/blender/blenloader/intern/versioning_280.c | 1 + .../blender/draw/intern/draw_cache_impl_gpencil.c | 1 + .../blender/editors/gpencil/gpencil_add_monkey.c | 1 + .../blender/editors/gpencil/gpencil_add_stroke.c | 1 + source/blender/editors/gpencil/gpencil_edit.c | 1 + source/blender/editors/gpencil/gpencil_fill.c | 1 + .../blender/editors/gpencil/gpencil_interpolate.c | 1 + source/blender/editors/gpencil/gpencil_merge.c | 1 + source/blender/editors/gpencil/gpencil_paint.c | 1 + source/blender/editors/gpencil/gpencil_primitive.c | 1 + .../blender/editors/gpencil/gpencil_sculpt_paint.c | 1 + source/blender/editors/gpencil/gpencil_utils.c | 1 + source/blender/editors/gpencil/gpencil_uv.c | 1 + source/blender/editors/object/object_add.c | 2 +- source/blender/editors/object/object_transform.c | 1 + source/blender/editors/space_view3d/view3d_edit.c | 2 +- .../blender/editors/transform/transform_generics.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilarmature.c | 1 + .../gpencil_modifiers/intern/MOD_gpencilarray.c | 1 + .../gpencil_modifiers/intern/MOD_gpencilbuild.c | 1 + .../gpencil_modifiers/intern/MOD_gpencilhook.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencillattice.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilmultiply.c | 1 + .../gpencil_modifiers/intern/MOD_gpencilnoise.c | 1 + .../gpencil_modifiers/intern/MOD_gpenciloffset.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilsimplify.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilsmooth.c | 2 +- .../gpencil_modifiers/intern/MOD_gpencilsubdiv.c | 2 +- source/blender/makesrna/intern/rna_gpencil.c | 1 + source/blender/makesrna/intern/rna_object_api.c | 2 +- 37 files changed, 2269 insertions(+), 2155 deletions(-) create mode 100644 source/blender/blenkernel/BKE_gpencil_geom.h create mode 100644 source/blender/blenkernel/intern/gpencil_geom.c diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 5d23029df9e..8cd3081389e 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -28,7 +28,6 @@ extern "C" { #endif -struct BoundBox; struct Brush; struct CurveMapping; struct Depsgraph; @@ -226,18 +225,8 @@ struct Material *BKE_gpencil_object_material_ensure_from_active_input_brush(stru struct Brush *brush); struct Material *BKE_gpencil_object_material_ensure_from_active_input_material(struct Object *ob); -/* object boundbox */ -bool BKE_gpencil_data_minmax(const struct bGPdata *gpd, float r_min[3], float r_max[3]); -bool BKE_gpencil_stroke_minmax(const struct bGPDstroke *gps, - const bool use_select, - float r_min[3], - float r_max[3]); bool BKE_gpencil_stroke_select_check(const struct bGPDstroke *gps); -struct BoundBox *BKE_gpencil_boundbox_get(struct Object *ob); -void BKE_gpencil_centroid_3d(struct bGPdata *gpd, float r_centroid[3]); -void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps); - /* vertex groups */ void BKE_gpencil_dvert_ensure(struct bGPDstroke *gps); void BKE_gpencil_vgroup_remove(struct Object *ob, struct bDeformGroup *defgroup); @@ -246,66 +235,10 @@ void BKE_gpencil_stroke_weights_duplicate(struct bGPDstroke *gps_src, struct bGP /* Set active frame by layer. */ void BKE_gpencil_frame_active_set(struct Depsgraph *depsgraph, struct bGPdata *gpd); -/* stroke geometry utilities */ -void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]); -void BKE_gpencil_stroke_simplify_adaptive(struct bGPDstroke *gps, float factor); -void BKE_gpencil_stroke_simplify_fixed(struct bGPDstroke *gps); -void BKE_gpencil_stroke_subdivide(struct bGPDstroke *gps, int level, int type); -bool BKE_gpencil_stroke_trim(struct bGPDstroke *gps); -void BKE_gpencil_stroke_merge_distance(struct bGPDframe *gpf, - struct bGPDstroke *gps, - const float threshold, - const bool use_unselected); - -void BKE_gpencil_stroke_2d_flat(const struct bGPDspoint *points, - int totpoints, - float (*points2d)[2], - int *r_direction); -void BKE_gpencil_stroke_2d_flat_ref(const struct bGPDspoint *ref_points, - int ref_totpoints, - const struct bGPDspoint *points, - int totpoints, - float (*points2d)[2], - const float scale, - int *r_direction); -void BKE_gpencil_stroke_fill_triangulate(struct bGPDstroke *gps); -void BKE_gpencil_stroke_geometry_update(struct bGPDstroke *gps); -void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); - -void BKE_gpencil_transform(struct bGPdata *gpd, float mat[4][4]); - -bool BKE_gpencil_stroke_sample(struct bGPDstroke *gps, const float dist, const bool select); -bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf); -bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); -bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); -bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); -bool BKE_gpencil_stroke_close(struct bGPDstroke *gps); -void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag); - -bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float tip_length); -bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, - const int index_from, - const int index_to); -bool BKE_gpencil_stroke_split(struct bGPDframe *gpf, - struct bGPDstroke *gps, - const int before_index, - struct bGPDstroke **remaining_gps); -bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist); - -float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d); - void BKE_gpencil_frame_range_selected(struct bGPDlayer *gpl, int *r_initframe, int *r_endframe); float BKE_gpencil_multiframe_falloff_calc( struct bGPDframe *gpf, int actnum, int f_init, int f_end, struct CurveMapping *cur_falloff); -void BKE_gpencil_convert_curve(struct Main *bmain, - struct Scene *scene, - struct Object *ob_gp, - struct Object *ob_cu, - const bool gpencil_lines, - const bool use_collections, - const bool only_stroke); - void BKE_gpencil_palette_ensure(struct Main *bmain, struct Scene *scene); bool BKE_gpencil_from_image(struct SpaceImage *sima, diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h new file mode 100644 index 00000000000..8c52e6d458b --- /dev/null +++ b/source/blender/blenkernel/BKE_gpencil_geom.h @@ -0,0 +1,113 @@ +/* + * 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 + */ + +#ifndef __BKE_GPENCIL_GEOM_H__ +#define __BKE_GPENCIL_GEOM_H__ + +/** \file + * \ingroup bke + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct BoundBox; +struct Depsgraph; +struct Main; +struct Object; +struct Scene; +struct bGPDframe; +struct bGPDlayer; +struct bGPDspoint; +struct bGPDstroke; +struct bGPdata; + +/* Object boundbox. */ +bool BKE_gpencil_data_minmax(const struct bGPdata *gpd, float r_min[3], float r_max[3]); +bool BKE_gpencil_stroke_minmax(const struct bGPDstroke *gps, + const bool use_select, + float r_min[3], + float r_max[3]); + +struct BoundBox *BKE_gpencil_boundbox_get(struct Object *ob); +void BKE_gpencil_centroid_3d(struct bGPdata *gpd, float r_centroid[3]); +void BKE_gpencil_stroke_boundingbox_calc(struct bGPDstroke *gps); + +/* stroke geometry utilities */ +void BKE_gpencil_stroke_normal(const struct bGPDstroke *gps, float r_normal[3]); +void BKE_gpencil_stroke_simplify_adaptive(struct bGPDstroke *gps, float factor); +void BKE_gpencil_stroke_simplify_fixed(struct bGPDstroke *gps); +void BKE_gpencil_stroke_subdivide(struct bGPDstroke *gps, int level, int type); +bool BKE_gpencil_stroke_trim(struct bGPDstroke *gps); +void BKE_gpencil_stroke_merge_distance(struct bGPDframe *gpf, + struct bGPDstroke *gps, + const float threshold, + const bool use_unselected); + +void BKE_gpencil_stroke_2d_flat(const struct bGPDspoint *points, + int totpoints, + float (*points2d)[2], + int *r_direction); +void BKE_gpencil_stroke_2d_flat_ref(const struct bGPDspoint *ref_points, + int ref_totpoints, + const struct bGPDspoint *points, + int totpoints, + float (*points2d)[2], + const float scale, + int *r_direction); +void BKE_gpencil_stroke_fill_triangulate(struct bGPDstroke *gps); +void BKE_gpencil_stroke_geometry_update(struct bGPDstroke *gps); +void BKE_gpencil_stroke_uv_update(struct bGPDstroke *gps); + +void BKE_gpencil_transform(struct bGPdata *gpd, float mat[4][4]); + +bool BKE_gpencil_stroke_sample(struct bGPDstroke *gps, const float dist, const bool select); +bool BKE_gpencil_stroke_smooth(struct bGPDstroke *gps, int i, float inf); +bool BKE_gpencil_stroke_smooth_strength(struct bGPDstroke *gps, int point_index, float influence); +bool BKE_gpencil_stroke_smooth_thickness(struct bGPDstroke *gps, int point_index, float influence); +bool BKE_gpencil_stroke_smooth_uv(struct bGPDstroke *gps, int point_index, float influence); +bool BKE_gpencil_stroke_close(struct bGPDstroke *gps); +void BKE_gpencil_dissolve_points(struct bGPDframe *gpf, struct bGPDstroke *gps, const short tag); + +bool BKE_gpencil_stroke_stretch(struct bGPDstroke *gps, const float dist, const float tip_length); +bool BKE_gpencil_stroke_trim_points(struct bGPDstroke *gps, + const int index_from, + const int index_to); +bool BKE_gpencil_stroke_split(struct bGPDframe *gpf, + struct bGPDstroke *gps, + const int before_index, + struct bGPDstroke **remaining_gps); +bool BKE_gpencil_stroke_shrink(struct bGPDstroke *gps, const float dist); + +float BKE_gpencil_stroke_length(const struct bGPDstroke *gps, bool use_3d); + +void BKE_gpencil_convert_curve(struct Main *bmain, + struct Scene *scene, + struct Object *ob_gp, + struct Object *ob_cu, + const bool gpencil_lines, + const bool use_collections, + const bool only_stroke); + +#ifdef __cplusplus +} +#endif + +#endif /* __BKE_GPENCIL_GEOM_H__ */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 7fd5470cecc..37e00b56fd9 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -119,6 +119,7 @@ set(SRC intern/font.c intern/freestyle.c intern/gpencil.c + intern/gpencil_geom.c intern/gpencil_modifier.c intern/hair.c intern/icons.c @@ -299,6 +300,7 @@ set(SRC BKE_freestyle.h BKE_global.h BKE_gpencil.h + BKE_gpencil_geom.h BKE_gpencil_modifier.h BKE_hair.h BKE_icons.h diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 7036da1f870..555b0af5a63 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -33,43 +33,34 @@ #include "BLI_blenlib.h" #include "BLI_math_vector.h" -#include "BLI_polyfill_2d.h" #include "BLI_string_utils.h" -#include "BLI_utildefines.h" #include "BLT_translation.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" -#include "DNA_anim_types.h" #include "DNA_gpencil_types.h" #include "DNA_material_types.h" #include "DNA_meshdata_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" #include "DNA_space_types.h" -#include "DNA_userdef_types.h" #include "BKE_action.h" -#include "BKE_animsys.h" #include "BKE_collection.h" #include "BKE_colortools.h" -#include "BKE_curve.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_icons.h" #include "BKE_idtype.h" #include "BKE_image.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" -#include "BKE_object.h" #include "BKE_paint.h" #include "BLI_math_color.h" -#include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" static CLG_LogRef LOG = {"bke.gpencil"}; @@ -1412,58 +1403,6 @@ Material *BKE_gpencil_object_material_ensure_active(Object *ob) } /* ************************************************** */ -/* 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; - } - - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - bGPDframe *gpf = gpl->actframe; - - if (gpf != NULL) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - 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; @@ -1476,101 +1415,6 @@ bool BKE_gpencil_stroke_select_check(const bGPDstroke *gps) 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); -} - -/* Compute stroke bounding box. */ -void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps) -{ - INIT_MINMAX(gps->boundbox_min, gps->boundbox_max); - BKE_gpencil_stroke_minmax(gps, false, gps->boundbox_min, gps->boundbox_max); -} - -/* 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; - } - - const float scalef = mat4_to_scale(mat); - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - /* 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; - } - - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - bGPDspoint *pt; - int i; - - for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) { - mul_m4_v3(mat, &pt->x); - pt->pressure *= scalef; - } - - /* Distortion may mean we need to re-triangulate. */ - BKE_gpencil_stroke_geometry_update(gps); - } - } - } -} - /* ************************************************** */ /* GP Object - Vertex Groups */ @@ -1624,918 +1468,143 @@ void BKE_gpencil_dvert_ensure(bGPDstroke *gps) /* ************************************************** */ -static void stroke_defvert_create_nr_list(MDeformVert *dv_list, - int count, - ListBase *result, - int *totweight) -{ - LinkData *ld; - MDeformVert *dv; - MDeformWeight *dw; - int i, j; - int tw = 0; - for (i = 0; i < count; i++) { - dv = &dv_list[i]; - - /* find def_nr in list, if not exist, then create one */ - for (j = 0; j < dv->totweight; j++) { - bool found = false; - dw = &dv->dw[j]; - for (ld = result->first; ld; ld = ld->next) { - if (ld->data == POINTER_FROM_INT(dw->def_nr)) { - found = true; - break; - } - } - if (!found) { - ld = MEM_callocN(sizeof(LinkData), "def_nr_item"); - ld->data = POINTER_FROM_INT(dw->def_nr); - BLI_addtail(result, ld); - tw++; - } - } - } - - *totweight = tw; -} - -static MDeformVert *stroke_defvert_new_count(int count, int totweight, ListBase *def_nr_list) +/** + * 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_frame_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_endframe) { - int i, j; - LinkData *ld; - MDeformVert *dst = MEM_mallocN(count * sizeof(MDeformVert), "new_deformVert"); + *r_initframe = gpl->actframe->framenum; + *r_endframe = gpl->actframe->framenum; - for (i = 0; i < count; i++) { - dst[i].dw = MEM_mallocN(sizeof(MDeformWeight) * totweight, "new_deformWeight"); - dst[i].totweight = totweight; - j = 0; - /* re-assign deform groups */ - for (ld = def_nr_list->first; ld; ld = ld->next) { - dst[i].dw[j].def_nr = POINTER_AS_INT(ld->data); - j++; + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + if (gpf->flag & GP_FRAME_SELECT) { + if (gpf->framenum < *r_initframe) { + *r_initframe = gpf->framenum; + } + if (gpf->framenum > *r_endframe) { + *r_endframe = gpf->framenum; + } } } - - return dst; } -static void stroke_interpolate_deform_weights( - bGPDstroke *gps, int index_from, int index_to, float ratio, MDeformVert *vert) +/** + * 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) { - const MDeformVert *vl = &gps->dvert[index_from]; - const MDeformVert *vr = &gps->dvert[index_to]; - int i; + float fnum = 0.5f; /* default mid curve */ + float value; - for (i = 0; i < vert->totweight; i++) { - float wl = BKE_defvert_find_weight(vl, vert->dw[i].def_nr); - float wr = BKE_defvert_find_weight(vr, vert->dw[i].def_nr); - vert->dw[i].weight = interpf(wr, wl, ratio); - } -} - -static int stroke_march_next_point(const bGPDstroke *gps, - const int index_next_pt, - const float *current, - const float dist, - float *result, - float *pressure, - float *strength, - float *vert_color, - float *ratio_result, - int *index_from, - int *index_to) -{ - float remaining_till_next = 0.0f; - float remaining_march = dist; - float step_start[3]; - float point[3]; - int next_point_index = index_next_pt; - bGPDspoint *pt = NULL; - - if (!(next_point_index < gps->totpoints)) { - return -1; + /* check curve is available */ + if (cur_falloff == NULL) { + return 1.0f; } - copy_v3_v3(step_start, current); - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - remaining_till_next = len_v3v3(point, step_start); - - while (remaining_till_next < remaining_march) { - remaining_march -= remaining_till_next; - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - copy_v3_v3(step_start, point); - next_point_index++; - if (!(next_point_index < gps->totpoints)) { - next_point_index = gps->totpoints - 1; - break; - } - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - remaining_till_next = len_v3v3(point, step_start); + /* 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 = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum); } - if (remaining_till_next < remaining_march) { - pt = &gps->points[next_point_index]; - copy_v3_v3(result, &pt->x); - *pressure = gps->points[next_point_index].pressure; - *strength = gps->points[next_point_index].strength; - memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float) * 4); - - *index_from = next_point_index - 1; - *index_to = next_point_index; - *ratio_result = 1.0f; - - return 0; + /* 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 = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f); } else { - float ratio = remaining_march / remaining_till_next; - interp_v3_v3v3(result, step_start, point, ratio); - *pressure = interpf( - gps->points[next_point_index].pressure, gps->points[next_point_index - 1].pressure, ratio); - *strength = interpf( - gps->points[next_point_index].strength, gps->points[next_point_index - 1].strength, ratio); - interp_v4_v4v4(vert_color, - gps->points[next_point_index - 1].vert_color, - gps->points[next_point_index].vert_color, - ratio); - - *index_from = next_point_index - 1; - *index_to = next_point_index; - *ratio_result = ratio; - - return next_point_index; - } -} - -static int stroke_march_next_point_no_interp(const bGPDstroke *gps, - const int index_next_pt, - const float *current, - const float dist, - float *result) -{ - float remaining_till_next = 0.0f; - float remaining_march = dist; - float step_start[3]; - float point[3]; - int next_point_index = index_next_pt; - bGPDspoint *pt = NULL; - - if (!(next_point_index < gps->totpoints)) { - return -1; + value = 1.0f; } - copy_v3_v3(step_start, current); - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - remaining_till_next = len_v3v3(point, step_start); - - while (remaining_till_next < remaining_march) { - remaining_march -= remaining_till_next; - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - copy_v3_v3(step_start, point); - next_point_index++; - if (!(next_point_index < gps->totpoints)) { - next_point_index = gps->totpoints - 1; - break; - } - pt = &gps->points[next_point_index]; - copy_v3_v3(point, &pt->x); - remaining_till_next = len_v3v3(point, step_start); - } - if (remaining_till_next < remaining_march) { - pt = &gps->points[next_point_index]; - copy_v3_v3(result, &pt->x); - return 0; - } - else { - float ratio = remaining_march / remaining_till_next; - interp_v3_v3v3(result, step_start, point, ratio); - return next_point_index; - } + return value; } -static int stroke_march_count(const bGPDstroke *gps, const float dist) +/* reassign strokes using a material */ +void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index) { - int point_count = 0; - float point[3]; - int next_point_index = 1; - bGPDspoint *pt = NULL; - - pt = &gps->points[0]; - copy_v3_v3(point, &pt->x); - point_count++; - - while ((next_point_index = stroke_march_next_point_no_interp( - gps, next_point_index, point, dist, point)) > -1) { - point_count++; - if (next_point_index == 0) { - break; /* last point finished */ + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* reassign strokes */ + if ((gps->mat_nr > index) || (gps->mat_nr > totcol - 1)) { + gps->mat_nr--; + CLAMP_MIN(gps->mat_nr, 0); + } + } } } - return point_count; } -/** - * Resample a stroke - * \param gps: Stroke to sample - * \param dist: Distance of one segment - */ -bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool select) +/* remove strokes using a material */ +bool BKE_gpencil_material_index_used(bGPdata *gpd, int index) { - bGPDspoint *pt = gps->points; - bGPDspoint *pt1 = NULL; - bGPDspoint *pt2 = NULL; - int i; - LinkData *ld; - ListBase def_nr_list = {0}; - - if (gps->totpoints < 2 || dist < FLT_EPSILON) { - return false; - } - /* TODO: Implement feature point preservation. */ - int count = stroke_march_count(gps, dist); - - bGPDspoint *new_pt = MEM_callocN(sizeof(bGPDspoint) * count, "gp_stroke_points_sampled"); - MDeformVert *new_dv = NULL; - - int result_totweight; - - if (gps->dvert != NULL) { - stroke_defvert_create_nr_list(gps->dvert, gps->totpoints, &def_nr_list, &result_totweight); - new_dv = stroke_defvert_new_count(count, result_totweight, &def_nr_list); - } - - int next_point_index = 1; - i = 0; - float pressure, strength, ratio_result; - float vert_color[4]; - int index_from, index_to; - float last_coord[3]; - - /* 1st point is always at the start */ - pt1 = &gps->points[0]; - copy_v3_v3(last_coord, &pt1->x); - pt2 = &new_pt[i]; - copy_v3_v3(&pt2->x, last_coord); - new_pt[i].pressure = pt[0].pressure; - new_pt[i].strength = pt[0].strength; - memcpy(new_pt[i].vert_color, pt[0].vert_color, sizeof(float) * 4); - if (select) { - new_pt[i].flag |= GP_SPOINT_SELECT; - } - i++; - - if (new_dv) { - stroke_interpolate_deform_weights(gps, 0, 0, 0, &new_dv[0]); - } - - /* the rest */ - while ((next_point_index = stroke_march_next_point(gps, - next_point_index, - last_coord, - dist, - last_coord, - &pressure, - &strength, - vert_color, - &ratio_result, - &index_from, - &index_to)) > -1) { - pt2 = &new_pt[i]; - copy_v3_v3(&pt2->x, last_coord); - new_pt[i].pressure = pressure; - new_pt[i].strength = strength; - memcpy(new_pt[i].vert_color, vert_color, sizeof(float) * 4); - if (select) { - new_pt[i].flag |= GP_SPOINT_SELECT; - } - - if (new_dv) { - stroke_interpolate_deform_weights(gps, index_from, index_to, ratio_result, &new_dv[i]); - } - - i++; - if (next_point_index == 0) { - break; /* last point finished */ - } - } - - gps->points = new_pt; - /* Free original vertex list. */ - MEM_freeN(pt); - - if (new_dv) { - /* Free original weight data. */ - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - while ((ld = BLI_pophead(&def_nr_list))) { - MEM_freeN(ld); + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + if (gps->mat_nr == index) { + return true; + } + } } - - gps->dvert = new_dv; } - gps->totpoints = i; - - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); - - return true; + return false; } -/** - * Backbone stretch similar to Freestyle. - * \param gps: Stroke to sample - * \param dist: Distance of one segment - * \param tip_length: Ignore tip jittering, set zero to use default value. - */ -bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float tip_length) +void BKE_gpencil_material_remap(struct bGPdata *gpd, + const unsigned int *remap, + unsigned int remap_len) { - bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt; - int i; - float threshold = (tip_length == 0 ? 0.001f : tip_length); - - if (gps->totpoints < 2 || dist < FLT_EPSILON) { - return false; - } - - last_pt = &pt[gps->totpoints - 1]; - second_last = &pt[gps->totpoints - 2]; - next_pt = &pt[1]; - - float len1 = 0.0f; - float len2 = 0.0f; + const short remap_len_short = (short)remap_len; - i = 1; - while (len1 < threshold && gps->totpoints > i) { - next_pt = &pt[i]; - len1 = len_v3v3(&next_pt->x, &pt->x); - i++; - } +#define MAT_NR_REMAP(n) \ + if (n < remap_len_short) { \ + BLI_assert(n >= 0 && remap[n] < remap_len_short); \ + n = remap[n]; \ + } \ + ((void)0) - i = 2; - while (len2 < threshold && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - len2 = len_v3v3(&last_pt->x, &second_last->x); - i++; + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + /* reassign strokes */ + MAT_NR_REMAP(gps->mat_nr); + } + } } - float extend1 = (len1 + dist) / len1; - float extend2 = (len2 + dist) / len2; - - float result1[3], result2[3]; - - interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); - interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); - - copy_v3_v3(&pt->x, result1); - copy_v3_v3(&last_pt->x, result2); - - return true; +#undef MAT_NR_REMAP } -/** - * Trim stroke to needed segments - * \param gps: Target stroke - * \param index_from: the index of the first point to be used in the trimmed result - * \param index_to: the index of the last point to be used in the trimmed result - */ -bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const int index_to) +/* Load a table with material conversion index for merged materials. */ +bool BKE_gpencil_merge_materials_table_get(Object *ob, + const float hue_threshold, + const float sat_threshold, + const float val_threshold, + GHash *r_mat_table) { - bGPDspoint *pt = gps->points, *new_pt; - MDeformVert *dv, *new_dv; - - const int new_count = index_to - index_from + 1; + bool changed = false; - if (new_count >= gps->totpoints) { - return false; - } + Material *ma_primary = NULL; + Material *ma_secondary = NULL; + MaterialGPencilStyle *gp_style_primary = NULL; + MaterialGPencilStyle *gp_style_secondary = NULL; - if (new_count == 1) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->points); - gps->points = NULL; - gps->dvert = NULL; - gps->totpoints = 0; - return false; - } - - new_pt = MEM_callocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed"); - - for (int i = 0; i < new_count; i++) { - memcpy(&new_pt[i], &pt[i + index_from], sizeof(bGPDspoint)); - } - - if (gps->dvert) { - new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_trimmed"); - for (int i = 0; i < new_count; i++) { - dv = &gps->dvert[i + index_from]; - new_dv[i].flag = dv->flag; - new_dv[i].totweight = dv->totweight; - new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight, - "gp_stroke_dverts_dw_trimmed"); - for (int j = 0; j < dv->totweight; j++) { - new_dv[i].dw[j].weight = dv->dw[j].weight; - new_dv[i].dw[j].def_nr = dv->dw[j].def_nr; - } - } - MEM_freeN(gps->dvert); - gps->dvert = new_dv; - } - - MEM_freeN(gps->points); - gps->points = new_pt; - gps->totpoints = new_count; - - return true; -} - -bool BKE_gpencil_stroke_split(bGPDframe *gpf, - bGPDstroke *gps, - const int before_index, - bGPDstroke **remaining_gps) -{ - bGPDstroke *new_gps; - bGPDspoint *pt = gps->points, *new_pt; - MDeformVert *dv, *new_dv; - - if (before_index >= gps->totpoints || before_index == 0) { - return false; - } - - const int new_count = gps->totpoints - before_index; - const int old_count = before_index; - - /* Handle remaining segments first. */ - - new_gps = BKE_gpencil_stroke_add_existing_style( - gpf, gps, gps->mat_nr, new_count, gps->thickness); - - new_pt = new_gps->points; /* Allocated from above. */ - - for (int i = 0; i < new_count; i++) { - memcpy(&new_pt[i], &pt[i + before_index], sizeof(bGPDspoint)); - } - - if (gps->dvert) { - new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, - "gp_stroke_dverts_remaining(MDeformVert)"); - for (int i = 0; i < new_count; i++) { - dv = &gps->dvert[i + before_index]; - new_dv[i].flag = dv->flag; - new_dv[i].totweight = dv->totweight; - new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight, - "gp_stroke_dverts_dw_remaining(MDeformWeight)"); - for (int j = 0; j < dv->totweight; j++) { - new_dv[i].dw[j].weight = dv->dw[j].weight; - new_dv[i].dw[j].def_nr = dv->dw[j].def_nr; - } - } - new_gps->dvert = new_dv; - } - - (*remaining_gps) = new_gps; - - /* Trim the original stroke into a shorter one. - * Keep the end point. */ - - BKE_gpencil_stroke_trim_points(gps, 0, old_count); - BKE_gpencil_stroke_geometry_update(gps); - return true; -} - -/** - * Shrink the stroke by length. - * \param gps: Stroke to shrink - * \param dist: delta length - */ -bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) -{ - bGPDspoint *pt = gps->points, *second_last; - int i; - - if (gps->totpoints < 2 || dist < FLT_EPSILON) { - return false; - } - - second_last = &pt[gps->totpoints - 2]; - - float len1, this_len1, cut_len1; - float len2, this_len2, cut_len2; - int index_start, index_end; - - len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f; - - i = 1; - while (len1 < dist && gps->totpoints > i - 1) { - this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x); - len1 += this_len1; - cut_len1 = len1 - dist; - i++; - } - index_start = i - 2; - - i = 2; - while (len2 < dist && gps->totpoints >= i) { - second_last = &pt[gps->totpoints - i]; - this_len2 = len_v3v3(&second_last[1].x, &second_last->x); - len2 += this_len2; - cut_len2 = len2 - dist; - i++; - } - index_end = gps->totpoints - i + 2; - - if (len1 < dist || len2 < dist || index_end <= index_start) { - index_start = index_end = 0; /* empty stroke */ - } - - if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) { - index_start = index_end = 0; /* no length left to cut */ - } - - BKE_gpencil_stroke_trim_points(gps, index_start, index_end); - - if (gps->totpoints == 0) { - return false; - } - - pt = gps->points; - - float cut1 = cut_len1 / this_len1; - float cut2 = cut_len2 / this_len2; - - float result1[3], result2[3]; - - interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1); - interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2); - - copy_v3_v3(&pt[0].x, result1); - copy_v3_v3(&pt[gps->totpoints - 1].x, result2); - - return true; -} - -/** - * Apply smooth to stroke point - * \param gps: Stroke to smooth - * \param i: Point index - * \param inf: Amount of smoothing to apply - */ -bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf) -{ - bGPDspoint *pt = &gps->points[i]; - 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_stroke_smooth_strength(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; - } - /* Only affect endpoints by a fraction of the normal influence */ - float inf = influence; - if ((point_index == 0) || (point_index == gps->totpoints - 1)) { - inf *= 0.01f; - } - /* Limit max influence to reduce pop effect. */ - CLAMP_MAX(inf, 0.98f); - - float total = 0.0f; - float max_strength = 0.0f; - const int steps = 4; - const float average_fac = 1.0f / (float)(steps * 2 + 1); - int step; - - /* add the point itself */ - total += ptb->strength * average_fac; - max_strength = ptb->strength; - - /* n-steps before/after current point */ - for (step = 1; step <= steps; step++) { - bGPDspoint *pt1, *pt2; - int before = point_index - step; - int after = point_index + 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) */ - total += pt1->strength * average_fac; - total += pt2->strength * average_fac; - /* Save max value. */ - if (max_strength < pt1->strength) { - max_strength = pt1->strength; - } - if (max_strength < pt2->strength) { - max_strength = pt2->strength; - } - } - - /* Based on influence factor, blend between original and optimal smoothed value. */ - ptb->strength = interpf(ptb->strength, total, inf); - /* Clamp to maximum stroke strength to avoid weird results. */ - CLAMP_MAX(ptb->strength, max_strength); - - return true; -} - -/** - * Apply smooth for thickness to stroke point (use pressure) */ -bool BKE_gpencil_stroke_smooth_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; - } - /* Only affect endpoints by a fraction of the normal influence */ - float inf = influence; - if ((point_index == 0) || (point_index == gps->totpoints - 1)) { - inf *= 0.01f; - } - /* Limit max influence to reduce pop effect. */ - CLAMP_MAX(inf, 0.98f); - - float total = 0.0f; - float max_pressure = 0.0f; - const int steps = 4; - const float average_fac = 1.0f / (float)(steps * 2 + 1); - int step; - - /* add the point itself */ - total += ptb->pressure * average_fac; - max_pressure = ptb->pressure; - - /* n-steps before/after current point */ - for (step = 1; step <= steps; step++) { - bGPDspoint *pt1, *pt2; - int before = point_index - step; - int after = point_index + 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) */ - total += pt1->pressure * average_fac; - total += pt2->pressure * average_fac; - /* Save max value. */ - if (max_pressure < pt1->pressure) { - max_pressure = pt1->pressure; - } - if (max_pressure < pt2->pressure) { - max_pressure = pt2->pressure; - } - } - - /* Based on influence factor, blend between original and optimal smoothed value. */ - ptb->pressure = interpf(ptb->pressure, total, inf); - /* Clamp to maximum stroke thickness to avoid weird results. */ - CLAMP_MAX(ptb->pressure, max_pressure); - return true; -} - -/** - * Apply smooth for UV rotation to stroke point (use pressure). - */ -bool BKE_gpencil_stroke_smooth_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_frame_range_selected(bGPDlayer *gpl, int *r_initframe, int *r_endframe) -{ - *r_initframe = gpl->actframe->framenum; - *r_endframe = gpl->actframe->framenum; - - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - 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 = BKE_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 = BKE_curvemapping_evaluateF(cur_falloff, 0, fnum + 0.5f); - } - else { - value = 1.0f; - } - - return value; -} - -/* reassign strokes using a material */ -void BKE_gpencil_material_index_reassign(bGPdata *gpd, int totcol, int index) -{ - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - /* reassign strokes */ - if ((gps->mat_nr > index) || (gps->mat_nr > totcol - 1)) { - gps->mat_nr--; - CLAMP_MIN(gps->mat_nr, 0); - } - } - } - } -} - -/* remove strokes using a material */ -bool BKE_gpencil_material_index_used(bGPdata *gpd, int index) -{ - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - if (gps->mat_nr == index) { - return true; - } - } - } - } - - return false; -} - -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) - - LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { - LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { - LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { - /* reassign strokes */ - MAT_NR_REMAP(gps->mat_nr); - } - } - } - -#undef MAT_NR_REMAP -} - -/* Load a table with material conversion index for merged materials. */ -bool BKE_gpencil_merge_materials_table_get(Object *ob, - const float hue_threshold, - const float sat_threshold, - const float val_threshold, - GHash *r_mat_table) -{ - bool changed = false; - - Material *ma_primary = NULL; - Material *ma_secondary = NULL; - MaterialGPencilStyle *gp_style_primary = NULL; - MaterialGPencilStyle *gp_style_secondary = NULL; - - short *totcol = BKE_object_material_len_p(ob); - if (totcol == 0) { - return changed; + short *totcol = BKE_object_material_len_p(ob); + if (totcol == 0) { + return changed; } for (int idx_primary = 0; idx_primary < *totcol; idx_primary++) { @@ -2650,1053 +1719,6 @@ int BKE_gpencil_object_material_index_get(Object *ob, Material *ma) 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]; -} - -/* calc texture coordinates using flat projected points */ -static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2], - bGPDstroke *gps, - const float minv[2], - float maxv[2], - float (*r_uv)[2]) -{ - const float s = sin(gps->uv_rotation); - const float c = cos(gps->uv_rotation); - - /* Calc center for rotation. */ - float center[2] = {0.5f, 0.5f}; - float d[2]; - d[0] = maxv[0] - minv[0]; - d[1] = maxv[1] - minv[1]; - for (int i = 0; i < gps->totpoints; i++) { - r_uv[i][0] = (points2d[i][0] - minv[0]) / d[0]; - r_uv[i][1] = (points2d[i][1] - minv[1]) / d[1]; - - /* Apply translation. */ - add_v2_v2(r_uv[i], gps->uv_translation); - - /* Apply Rotation. */ - r_uv[i][0] -= center[0]; - r_uv[i][1] -= center[1]; - - float x = r_uv[i][0] * c - r_uv[i][1] * s; - float y = r_uv[i][0] * s + r_uv[i][1] * c; - - r_uv[i][0] = x + center[0]; - r_uv[i][1] = y + center[1]; - - /* Apply scale. */ - if (gps->uv_scale != 0.0f) { - mul_v2_fl(r_uv[i], 1.0f / gps->uv_scale); - } - } -} - -/* Triangulate stroke for high quality fill (this is done only if cache is null or stroke was - * modified) */ -void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps) -{ - BLI_assert(gps->totpoints >= 3); - - /* allocate memory for temporary areas */ - gps->tot_triangles = gps->totpoints - 2; - uint(*tmp_triangles)[3] = MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles, - "GP Stroke temp triangulation"); - float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints, - "GP Stroke temp 2d points"); - float(*uv)[2] = MEM_mallocN(sizeof(*uv) * gps->totpoints, "GP Stroke temp 2d uv data"); - - int direction = 0; - - /* convert to 2d and triangulate */ - BKE_gpencil_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction); - BLI_polyfill_calc(points2d, (uint)gps->totpoints, direction, tmp_triangles); - - /* calc texture coordinates automatically */ - float minv[2]; - float maxv[2]; - /* first needs bounding box data */ - ARRAY_SET_ITEMS(minv, -1.0f, -1.0f); - ARRAY_SET_ITEMS(maxv, 1.0f, 1.0f); - - /* calc uv data */ - gpencil_calc_stroke_fill_uv(points2d, gps, minv, maxv, uv); - - /* Save triangulation data. */ - if (gps->tot_triangles > 0) { - MEM_SAFE_FREE(gps->triangles); - gps->triangles = MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles, - "GP Stroke triangulation"); - - for (int i = 0; i < gps->tot_triangles; i++) { - memcpy(gps->triangles[i].verts, tmp_triangles[i], sizeof(uint[3])); - } - - /* Copy UVs to bGPDspoint. */ - for (int i = 0; i < gps->totpoints; i++) { - copy_v2_v2(gps->points[i].uv_fill, uv[i]); - } - } - else { - /* No triangles needed - Free anything allocated previously */ - if (gps->triangles) { - MEM_freeN(gps->triangles); - } - - gps->triangles = NULL; - } - - /* clear memory */ - MEM_SAFE_FREE(tmp_triangles); - MEM_SAFE_FREE(points2d); - MEM_SAFE_FREE(uv); -} - -/* texture coordinate utilities */ -void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) -{ - if (gps == NULL || gps->totpoints == 0) { - return; - } - - bGPDspoint *pt = gps->points; - float totlen = 0.0f; - pt[0].uv_fac = totlen; - for (int i = 1; i < gps->totpoints; i++) { - totlen += len_v3v3(&pt[i - 1].x, &pt[i].x); - pt[i].uv_fac = totlen; - } -} - -/* Recalc the internal geometry caches for fill and uvs. */ -void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps) -{ - if (gps == NULL) { - return; - } - - if (gps->totpoints > 2) { - BKE_gpencil_stroke_fill_triangulate(gps); - } - else { - gps->tot_triangles = 0; - MEM_SAFE_FREE(gps->triangles); - } - - /* calc uv data along the stroke */ - BKE_gpencil_stroke_uv_update(gps); - - /* Calc stroke bounding box. */ - BKE_gpencil_stroke_boundingbox_calc(gps); -} - -float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) -{ - if (!gps->points || gps->totpoints < 2) { - return 0.0f; - } - float *last_pt = &gps->points[0].x; - int i; - bGPDspoint *pt; - float total_length = 0.0f; - for (i = 1; i < gps->totpoints; i++) { - pt = &gps->points[i]; - if (use_3d) { - total_length += len_v3v3(&pt->x, last_pt); - } - else { - total_length += len_v2v2(&pt->x, last_pt); - } - last_pt = &pt->x; - } - return total_length; -} - -/** - * Trim stroke to the first intersection or loop - * \param gps: Stroke data - */ -bool BKE_gpencil_stroke_trim(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->totpoints = newtot; - - MEM_SAFE_FREE(old_points); - MEM_SAFE_FREE(old_dvert); - } - - BKE_gpencil_stroke_geometry_update(gps); - - return intersect; -} - -/** - * Close stroke - * \param gps: Stroke to close - */ -bool BKE_gpencil_stroke_close(bGPDstroke *gps) -{ - bGPDspoint *pt1 = NULL; - bGPDspoint *pt2 = NULL; - - /* Only can close a stroke with 3 points or more. */ - if (gps->totpoints < 3) { - return false; - } - - /* Calc average distance between points to get same level of sampling. */ - float dist_tot = 0.0f; - for (int i = 0; i < gps->totpoints - 1; i++) { - pt1 = &gps->points[i]; - pt2 = &gps->points[i + 1]; - dist_tot += len_v3v3(&pt1->x, &pt2->x); - } - /* Calc the average distance. */ - float dist_avg = dist_tot / (gps->totpoints - 1); - - /* Calc distance between last and first point. */ - pt1 = &gps->points[gps->totpoints - 1]; - pt2 = &gps->points[0]; - float dist_close = len_v3v3(&pt1->x, &pt2->x); - - /* if the distance to close is very small, don't need add points and just enable cyclic. */ - if (dist_close <= dist_avg) { - gps->flag |= GP_STROKE_CYCLIC; - return true; - } - - /* Calc number of points required using the average distance. */ - int tot_newpoints = MAX2(dist_close / dist_avg, 1); - - /* Resize stroke array. */ - int old_tot = gps->totpoints; - gps->totpoints += tot_newpoints; - gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); - if (gps->dvert != NULL) { - gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); - } - - /* Generate new points */ - pt1 = &gps->points[old_tot - 1]; - pt2 = &gps->points[0]; - bGPDspoint *pt = &gps->points[old_tot]; - for (int i = 1; i < tot_newpoints + 1; i++, pt++) { - float step = (tot_newpoints > 1) ? ((float)i / (float)tot_newpoints) : 0.99f; - /* Clamp last point to be near, but not on top of first point. */ - if ((tot_newpoints > 1) && (i == tot_newpoints)) { - step *= 0.99f; - } - - /* Average point. */ - interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step); - pt->pressure = interpf(pt2->pressure, pt1->pressure, step); - pt->strength = interpf(pt2->strength, pt1->strength, step); - pt->flag = 0; - interp_v4_v4v4(pt->vert_color, pt1->vert_color, pt2->vert_color, step); - - /* Set weights. */ - if (gps->dvert != NULL) { - MDeformVert *dvert1 = &gps->dvert[old_tot - 1]; - MDeformWeight *dw1 = BKE_defvert_ensure_index(dvert1, 0); - float weight_1 = dw1 ? dw1->weight : 0.0f; - - MDeformVert *dvert2 = &gps->dvert[0]; - MDeformWeight *dw2 = BKE_defvert_ensure_index(dvert2, 0); - float weight_2 = dw2 ? dw2->weight : 0.0f; - - MDeformVert *dvert_final = &gps->dvert[old_tot + i - 1]; - dvert_final->totweight = 0; - MDeformWeight *dw = BKE_defvert_ensure_index(dvert_final, 0); - if (dvert_final->dw) { - dw->weight = interpf(weight_2, weight_1, step); - } - } - } - - /* Enable cyclic flag. */ - gps->flag |= GP_STROKE_CYCLIC; - - return true; -} -/* Dissolve points in stroke */ -void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag) -{ - bGPDspoint *pt; - MDeformVert *dvert = NULL; - int i; - - int tot = gps->totpoints; /* number of points in new buffer */ - /* first pass: count points to remove */ - /* Count how many points are selected (i.e. how many to remove) */ - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if (pt->flag & tag) { - /* selected point - one of the points to remove */ - tot--; - } - } - - /* if no points are left, we simply delete the entire stroke */ - if (tot <= 0) { - /* remove the entire stroke */ - 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 { - /* just copy all points to keep into a smaller buffer */ - bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); - bGPDspoint *npt = new_points; - - MDeformVert *new_dvert = NULL; - MDeformVert *ndvert = NULL; - - if (gps->dvert != NULL) { - new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); - ndvert = new_dvert; - } - - (gps->dvert != NULL) ? dvert = gps->dvert : NULL; - for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { - if ((pt->flag & tag) == 0) { - *npt = *pt; - npt++; - - if (gps->dvert != NULL) { - *ndvert = *dvert; - ndvert->dw = MEM_dupallocN(dvert->dw); - ndvert++; - } - } - if (gps->dvert != NULL) { - dvert++; - } - } - - /* free the old buffer */ - if (gps->points) { - MEM_freeN(gps->points); - } - if (gps->dvert) { - BKE_gpencil_free_stroke_weights(gps); - MEM_freeN(gps->dvert); - } - - /* save the new buffer */ - gps->points = new_points; - gps->dvert = new_dvert; - gps->totpoints = tot; - - /* triangles cache needs to be recalculated */ - BKE_gpencil_stroke_geometry_update(gps); - } -} - -/* Merge by distance ------------------------------------- */ -/* Reduce a series of points when the distance is below a threshold. - * Special case for first and last points (both are keeped) for other points, - * the merge point always is at first point. - * \param gpf: Grease Pencil frame - * \param gps: Grease Pencil stroke - * \param threshold: Distance between points - * \param use_unselected: Set to true to analyze all stroke and not only selected points - */ -void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, - bGPDstroke *gps, - const float threshold, - const bool use_unselected) -{ - bGPDspoint *pt = NULL; - bGPDspoint *pt_next = NULL; - float tagged = false; - /* Use square distance to speed up loop */ - const float th_square = threshold * threshold; - /* Need to have something to merge. */ - if (gps->totpoints < 2) { - return; - } - int i = 0; - int step = 1; - while ((i < gps->totpoints - 1) && (i + step < gps->totpoints)) { - pt = &gps->points[i]; - if (pt->flag & GP_SPOINT_TAG) { - i++; - step = 1; - continue; - } - pt_next = &gps->points[i + step]; - /* Do not recalc tagged points. */ - if (pt_next->flag & GP_SPOINT_TAG) { - step++; - continue; - } - /* Check if contiguous points are selected. */ - if (!use_unselected) { - if (((pt->flag & GP_SPOINT_SELECT) == 0) || ((pt_next->flag & GP_SPOINT_SELECT) == 0)) { - i++; - step = 1; - continue; - } - } - float len_square = len_squared_v3v3(&pt->x, &pt_next->x); - if (len_square <= th_square) { - tagged = true; - if (i != gps->totpoints - 1) { - /* Tag second point for delete. */ - pt_next->flag |= GP_SPOINT_TAG; - } - else { - pt->flag |= GP_SPOINT_TAG; - } - /* Jump to next pair of points, keeping first point segment equals.*/ - step++; - } - else { - /* Analyze next point. */ - i++; - step = 1; - } - } - - /* Always untag extremes. */ - pt = &gps->points[0]; - pt->flag &= ~GP_SPOINT_TAG; - pt = &gps->points[gps->totpoints - 1]; - pt->flag &= ~GP_SPOINT_TAG; - - /* Dissolve tagged points */ - if (tagged) { - BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); - } - - /* Calc geometry data. */ - BKE_gpencil_stroke_geometry_update(gps); -} - -/* Helper: Check materials with same color. */ -static int gpencil_check_same_material_color(Object *ob_gp, float color[4], Material **r_mat) -{ - Material *ma = NULL; - float color_cu[4]; - linearrgb_to_srgb_v3_v3(color_cu, color); - float hsv1[4]; - rgb_to_hsv_v(color_cu, hsv1); - hsv1[3] = color[3]; - - for (int i = 1; i <= ob_gp->totcol; i++) { - ma = BKE_object_material_get(ob_gp, i); - MaterialGPencilStyle *gp_style = ma->gp_style; - /* Check color with small tolerance (better in HSV). */ - float hsv2[4]; - rgb_to_hsv_v(gp_style->fill_rgba, hsv2); - hsv2[3] = gp_style->fill_rgba[3]; - if ((gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID) && - (compare_v4v4(hsv1, hsv2, 0.01f))) { - *r_mat = ma; - return i - 1; - } - } - - *r_mat = NULL; - return -1; -} - -/* Helper: Add gpencil material using curve material as base. */ -static Material *gpencil_add_from_curve_material(Main *bmain, - Object *ob_gp, - const float cu_color[4], - const bool gpencil_lines, - const bool fill, - int *r_idx) -{ - Material *mat_gp = BKE_gpencil_object_material_new( - bmain, ob_gp, (fill) ? "Material" : "Unassigned", r_idx); - MaterialGPencilStyle *gp_style = mat_gp->gp_style; - - /* Stroke color. */ - if (gpencil_lines) { - ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f); - gp_style->flag |= GP_MATERIAL_STROKE_SHOW; - } - else { - linearrgb_to_srgb_v4(gp_style->stroke_rgba, cu_color); - gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; - } - - /* Fill color. */ - linearrgb_to_srgb_v4(gp_style->fill_rgba, cu_color); - /* Fill is false if the original curve hasn't material assigned, so enable it. */ - if (fill) { - gp_style->flag |= GP_MATERIAL_FILL_SHOW; - } - - /* Check at least one is enabled. */ - if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) && - ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { - gp_style->flag |= GP_MATERIAL_STROKE_SHOW; - } - - return mat_gp; -} - -/* Helper: Create new stroke section. */ -static void gpencil_add_new_points(bGPDstroke *gps, - float *coord_array, - float pressure, - int init, - int totpoints, - const float init_co[3], - bool last) -{ - for (int i = 0; i < totpoints; i++) { - bGPDspoint *pt = &gps->points[i + init]; - copy_v3_v3(&pt->x, &coord_array[3 * i]); - /* Be sure the last point is not on top of the first point of the curve or - * the close of the stroke will produce glitches. */ - if ((last) && (i > 0) && (i == totpoints - 1)) { - float dist = len_v3v3(init_co, &pt->x); - if (dist < 0.1f) { - /* Interpolate between previous point and current to back slightly. */ - bGPDspoint *pt_prev = &gps->points[i + init - 1]; - interp_v3_v3v3(&pt->x, &pt_prev->x, &pt->x, 0.95f); - } - } - - pt->pressure = pressure; - pt->strength = 1.0f; - } -} - -/* Helper: Get the first collection that includes the object. */ -static Collection *gpencil_get_parent_collection(Scene *scene, Object *ob) -{ - Collection *mycol = NULL; - FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) { - for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) { - if ((mycol == NULL) && (cob->ob == ob)) { - mycol = collection; - } - } - } - FOREACH_SCENE_COLLECTION_END; - - return mycol; -} - -/* Helper: Convert one spline to grease pencil stroke. */ -static void gpencil_convert_spline(Main *bmain, - Object *ob_gp, - Object *ob_cu, - const bool gpencil_lines, - const bool only_stroke, - bGPDframe *gpf, - Nurb *nu) -{ - Curve *cu = (Curve *)ob_cu->data; - bool cyclic = true; - - /* Create Stroke. */ - bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke"); - gps->thickness = 1.0f; - gps->fill_opacity_fac = 1.0f; - gps->hardeness = 1.0f; - gps->uv_scale = 1.0f; - - ARRAY_SET_ITEMS(gps->aspect_ratio, 1.0f, 1.0f); - ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND); - gps->inittime = 0.0f; - - gps->flag &= ~GP_STROKE_SELECT; - gps->flag |= GP_STROKE_3DSPACE; - - gps->mat_nr = 0; - /* Count total points - * The total of points must consider that last point of each segment is equal to the first - * point of next segment. - */ - int totpoints = 0; - int segments = 0; - int resolu = nu->resolu + 1; - segments = nu->pntsu; - if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) { - segments--; - cyclic = false; - } - totpoints = (resolu * segments) - (segments - 1); - - /* Materials - * Notice: The color of the material is the color of viewport and not the final shader color. - */ - Material *mat_gp = NULL; - bool fill = true; - /* Check if grease pencil has a material with same color.*/ - float color[4]; - if ((cu->mat) && (*cu->mat)) { - Material *mat_cu = *cu->mat; - copy_v4_v4(color, &mat_cu->r); - } - else { - /* Gray (unassigned from SVG add-on) */ - zero_v4(color); - add_v3_fl(color, 0.6f); - color[3] = 1.0f; - fill = false; - } - - /* Special case: If the color was created by the SVG add-on and the name contains '_stroke' and - * there is only one color, the stroke must not be closed, fill to false and use for - * stroke the fill color. - */ - bool do_stroke = false; - if (ob_cu->totcol == 1) { - Material *ma_stroke = BKE_object_material_get(ob_cu, 1); - if ((ma_stroke) && (strstr(ma_stroke->id.name, "_stroke") != NULL)) { - do_stroke = true; - } - } - - int r_idx = gpencil_check_same_material_color(ob_gp, color, &mat_gp); - if ((ob_cu->totcol > 0) && (r_idx < 0)) { - Material *mat_curve = BKE_object_material_get(ob_cu, 1); - mat_gp = gpencil_add_from_curve_material(bmain, ob_gp, color, gpencil_lines, fill, &r_idx); - - if ((mat_curve) && (mat_curve->gp_style != NULL)) { - MaterialGPencilStyle *gp_style_cur = mat_curve->gp_style; - MaterialGPencilStyle *gp_style_gp = mat_gp->gp_style; - - copy_v4_v4(gp_style_gp->mix_rgba, gp_style_cur->mix_rgba); - gp_style_gp->fill_style = gp_style_cur->fill_style; - gp_style_gp->mix_factor = gp_style_cur->mix_factor; - } - - /* If object has more than 1 material, use second material for stroke color. */ - if ((!only_stroke) && (ob_cu->totcol > 1) && (BKE_object_material_get(ob_cu, 2))) { - mat_curve = BKE_object_material_get(ob_cu, 2); - if (mat_curve) { - linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r); - mat_gp->gp_style->stroke_rgba[3] = mat_curve->a; - } - } - else if ((only_stroke) || (do_stroke)) { - /* Also use the first color if the fill is none for stroke color. */ - if (ob_cu->totcol > 0) { - mat_curve = BKE_object_material_get(ob_cu, 1); - if (mat_curve) { - copy_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r); - mat_gp->gp_style->stroke_rgba[3] = mat_curve->a; - /* Set fill and stroke depending of curve type (3D or 2D). */ - if ((cu->flag & CU_3D) || ((cu->flag & (CU_FRONT | CU_BACK)) == 0)) { - mat_gp->gp_style->flag |= GP_MATERIAL_STROKE_SHOW; - mat_gp->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW; - } - else { - mat_gp->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; - mat_gp->gp_style->flag |= GP_MATERIAL_FILL_SHOW; - } - } - } - } - } - CLAMP_MIN(r_idx, 0); - - /* Assign material index to stroke. */ - gps->mat_nr = r_idx; - - /* Add stroke to frame.*/ - BLI_addtail(&gpf->strokes, gps); - - float *coord_array = NULL; - float init_co[3]; - - switch (nu->type) { - case CU_POLY: { - /* Allocate memory for storage points. */ - gps->totpoints = nu->pntsu; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - /* Increase thickness for this type. */ - gps->thickness = 10.0f; - - /* Get all curve points */ - for (int s = 0; s < gps->totpoints; s++) { - BPoint *bp = &nu->bp[s]; - bGPDspoint *pt = &gps->points[s]; - copy_v3_v3(&pt->x, bp->vec); - pt->pressure = bp->radius; - pt->strength = 1.0f; - } - break; - } - case CU_BEZIER: { - /* Allocate memory for storage points. */ - gps->totpoints = totpoints; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - - int init = 0; - resolu = nu->resolu + 1; - segments = nu->pntsu; - if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) { - segments--; - } - /* Get all interpolated curve points of Beziert */ - for (int s = 0; s < segments; s++) { - int inext = (s + 1) % nu->pntsu; - BezTriple *prevbezt = &nu->bezt[s]; - BezTriple *bezt = &nu->bezt[inext]; - bool last = (bool)(s == segments - 1); - - coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__); - - for (int j = 0; j < 3; j++) { - BKE_curve_forward_diff_bezier(prevbezt->vec[1][j], - prevbezt->vec[2][j], - bezt->vec[0][j], - bezt->vec[1][j], - coord_array + j, - resolu - 1, - 3 * sizeof(float)); - } - /* Save first point coordinates. */ - if (s == 0) { - copy_v3_v3(init_co, &coord_array[0]); - } - /* Add points to the stroke */ - gpencil_add_new_points(gps, coord_array, bezt->radius, init, resolu, init_co, last); - /* Free memory. */ - MEM_SAFE_FREE(coord_array); - - /* As the last point of segment is the first point of next segment, back one array - * element to avoid duplicated points on the same location. - */ - init += resolu - 1; - } - break; - } - case CU_NURBS: { - if (nu->pntsv == 1) { - - int nurb_points; - if (nu->flagu & CU_NURB_CYCLIC) { - resolu++; - nurb_points = nu->pntsu * resolu; - } - else { - nurb_points = (nu->pntsu - 1) * resolu; - } - /* Get all curve points. */ - coord_array = MEM_callocN(sizeof(float[3]) * nurb_points, __func__); - BKE_nurb_makeCurve(nu, coord_array, NULL, NULL, NULL, resolu, sizeof(float[3])); - - /* Allocate memory for storage points. */ - gps->totpoints = nurb_points - 1; - gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); - - /* Add points. */ - gpencil_add_new_points(gps, coord_array, 1.0f, 0, gps->totpoints, init_co, false); - - MEM_SAFE_FREE(coord_array); - } - break; - } - default: { - break; - } - } - /* Cyclic curve, close stroke. */ - if ((cyclic) && (!do_stroke)) { - BKE_gpencil_stroke_close(gps); - } - - /* Recalc fill geometry. */ - BKE_gpencil_stroke_geometry_update(gps); -} - -/* Convert a curve object to grease pencil stroke. - * - * \param bmain: Main thread pointer - * \param scene: Original scene. - * \param ob_gp: Grease pencil object to add strokes. - * \param ob_cu: Curve to convert. - * \param gpencil_lines: Use lines for strokes. - * \param use_collections: Create layers using collection names. - * \param only_stroke: The material must be only stroke without fill. - */ -void BKE_gpencil_convert_curve(Main *bmain, - Scene *scene, - Object *ob_gp, - Object *ob_cu, - const bool gpencil_lines, - const bool use_collections, - const bool only_stroke) -{ - if (ELEM(NULL, ob_gp, ob_cu) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) { - return; - } - - Curve *cu = (Curve *)ob_cu->data; - bGPdata *gpd = (bGPdata *)ob_gp->data; - bGPDlayer *gpl = NULL; - - /* If the curve is empty, cancel. */ - if (cu->nurb.first == NULL) { - return; - } - - /* Check if there is an active layer. */ - if (use_collections) { - Collection *collection = gpencil_get_parent_collection(scene, ob_cu); - if (collection != NULL) { - gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2); - if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true); - } - } - } - - if (gpl == NULL) { - gpl = BKE_gpencil_layer_active_get(gpd); - if (gpl == NULL) { - gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); - } - } - - /* Check if there is an active frame and add if needed. */ - bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_COPY); - - /* Read all splines of the curve and create a stroke for each. */ - for (Nurb *nu = cu->nurb.first; nu; nu = nu->next) { - gpencil_convert_spline(bmain, ob_gp, ob_cu, gpencil_lines, only_stroke, gpf, nu); - } - - /* Tag for recalculation */ - DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); -} - /* Create a default palette */ void BKE_gpencil_palette_ensure(Main *bmain, Scene *scene) { diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c new file mode 100644 index 00000000000..92054c5c79a --- /dev/null +++ b/source/blender/blenkernel/intern/gpencil_geom.c @@ -0,0 +1,2022 @@ +/* + * 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 +#include +#include +#include +#include + +#include "CLG_log.h" + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_math_vector.h" +#include "BLI_polyfill_2d.h" + +#include "BLT_translation.h" + +#include "DNA_gpencil_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_collection.h" +#include "BKE_curve.h" +#include "BKE_deform.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_object.h" + +#include "DEG_depsgraph_query.h" + +static CLG_LogRef LOG = {"bke_geom.gpencil"}; + +/* 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; + } + + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + bGPDframe *gpf = gpl->actframe; + + if (gpf != NULL) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + changed = BKE_gpencil_stroke_minmax(gps, false, r_min, r_max); + } + } + } + + return changed; +} + +/* 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); +} + +/* Compute stroke bounding box. */ +void BKE_gpencil_stroke_boundingbox_calc(bGPDstroke *gps) +{ + INIT_MINMAX(gps->boundbox_min, gps->boundbox_max); + BKE_gpencil_stroke_minmax(gps, false, gps->boundbox_min, gps->boundbox_max); +} + +/* 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; +} + +/* ************************************************** */ + +static int stroke_march_next_point(const bGPDstroke *gps, + const int index_next_pt, + const float *current, + const float dist, + float *result, + float *pressure, + float *strength, + float *vert_color, + float *ratio_result, + int *index_from, + int *index_to) +{ + float remaining_till_next = 0.0f; + float remaining_march = dist; + float step_start[3]; + float point[3]; + int next_point_index = index_next_pt; + bGPDspoint *pt = NULL; + + if (!(next_point_index < gps->totpoints)) { + return -1; + } + + copy_v3_v3(step_start, current); + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + + while (remaining_till_next < remaining_march) { + remaining_march -= remaining_till_next; + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + copy_v3_v3(step_start, point); + next_point_index++; + if (!(next_point_index < gps->totpoints)) { + next_point_index = gps->totpoints - 1; + break; + } + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + } + if (remaining_till_next < remaining_march) { + pt = &gps->points[next_point_index]; + copy_v3_v3(result, &pt->x); + *pressure = gps->points[next_point_index].pressure; + *strength = gps->points[next_point_index].strength; + memcpy(vert_color, gps->points[next_point_index].vert_color, sizeof(float) * 4); + + *index_from = next_point_index - 1; + *index_to = next_point_index; + *ratio_result = 1.0f; + + return 0; + } + else { + float ratio = remaining_march / remaining_till_next; + interp_v3_v3v3(result, step_start, point, ratio); + *pressure = interpf( + gps->points[next_point_index].pressure, gps->points[next_point_index - 1].pressure, ratio); + *strength = interpf( + gps->points[next_point_index].strength, gps->points[next_point_index - 1].strength, ratio); + interp_v4_v4v4(vert_color, + gps->points[next_point_index - 1].vert_color, + gps->points[next_point_index].vert_color, + ratio); + + *index_from = next_point_index - 1; + *index_to = next_point_index; + *ratio_result = ratio; + + return next_point_index; + } +} + +static int stroke_march_next_point_no_interp(const bGPDstroke *gps, + const int index_next_pt, + const float *current, + const float dist, + float *result) +{ + float remaining_till_next = 0.0f; + float remaining_march = dist; + float step_start[3]; + float point[3]; + int next_point_index = index_next_pt; + bGPDspoint *pt = NULL; + + if (!(next_point_index < gps->totpoints)) { + return -1; + } + + copy_v3_v3(step_start, current); + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + + while (remaining_till_next < remaining_march) { + remaining_march -= remaining_till_next; + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + copy_v3_v3(step_start, point); + next_point_index++; + if (!(next_point_index < gps->totpoints)) { + next_point_index = gps->totpoints - 1; + break; + } + pt = &gps->points[next_point_index]; + copy_v3_v3(point, &pt->x); + remaining_till_next = len_v3v3(point, step_start); + } + if (remaining_till_next < remaining_march) { + pt = &gps->points[next_point_index]; + copy_v3_v3(result, &pt->x); + return 0; + } + else { + float ratio = remaining_march / remaining_till_next; + interp_v3_v3v3(result, step_start, point, ratio); + return next_point_index; + } +} + +static int stroke_march_count(const bGPDstroke *gps, const float dist) +{ + int point_count = 0; + float point[3]; + int next_point_index = 1; + bGPDspoint *pt = NULL; + + pt = &gps->points[0]; + copy_v3_v3(point, &pt->x); + point_count++; + + while ((next_point_index = stroke_march_next_point_no_interp( + gps, next_point_index, point, dist, point)) > -1) { + point_count++; + if (next_point_index == 0) { + break; /* last point finished */ + } + } + return point_count; +} + +static void stroke_defvert_create_nr_list(MDeformVert *dv_list, + int count, + ListBase *result, + int *totweight) +{ + LinkData *ld; + MDeformVert *dv; + MDeformWeight *dw; + int i, j; + int tw = 0; + for (i = 0; i < count; i++) { + dv = &dv_list[i]; + + /* find def_nr in list, if not exist, then create one */ + for (j = 0; j < dv->totweight; j++) { + bool found = false; + dw = &dv->dw[j]; + for (ld = result->first; ld; ld = ld->next) { + if (ld->data == POINTER_FROM_INT(dw->def_nr)) { + found = true; + break; + } + } + if (!found) { + ld = MEM_callocN(sizeof(LinkData), "def_nr_item"); + ld->data = POINTER_FROM_INT(dw->def_nr); + BLI_addtail(result, ld); + tw++; + } + } + } + + *totweight = tw; +} + +static MDeformVert *stroke_defvert_new_count(int count, int totweight, ListBase *def_nr_list) +{ + int i, j; + LinkData *ld; + MDeformVert *dst = MEM_mallocN(count * sizeof(MDeformVert), "new_deformVert"); + + for (i = 0; i < count; i++) { + dst[i].dw = MEM_mallocN(sizeof(MDeformWeight) * totweight, "new_deformWeight"); + dst[i].totweight = totweight; + j = 0; + /* re-assign deform groups */ + for (ld = def_nr_list->first; ld; ld = ld->next) { + dst[i].dw[j].def_nr = POINTER_AS_INT(ld->data); + j++; + } + } + + return dst; +} + +static void stroke_interpolate_deform_weights( + bGPDstroke *gps, int index_from, int index_to, float ratio, MDeformVert *vert) +{ + const MDeformVert *vl = &gps->dvert[index_from]; + const MDeformVert *vr = &gps->dvert[index_to]; + int i; + + for (i = 0; i < vert->totweight; i++) { + float wl = BKE_defvert_find_weight(vl, vert->dw[i].def_nr); + float wr = BKE_defvert_find_weight(vr, vert->dw[i].def_nr); + vert->dw[i].weight = interpf(wr, wl, ratio); + } +} + +/** + * Resample a stroke + * \param gps: Stroke to sample + * \param dist: Distance of one segment + */ +bool BKE_gpencil_stroke_sample(bGPDstroke *gps, const float dist, const bool select) +{ + bGPDspoint *pt = gps->points; + bGPDspoint *pt1 = NULL; + bGPDspoint *pt2 = NULL; + int i; + LinkData *ld; + ListBase def_nr_list = {0}; + + if (gps->totpoints < 2 || dist < FLT_EPSILON) { + return false; + } + /* TODO: Implement feature point preservation. */ + int count = stroke_march_count(gps, dist); + + bGPDspoint *new_pt = MEM_callocN(sizeof(bGPDspoint) * count, "gp_stroke_points_sampled"); + MDeformVert *new_dv = NULL; + + int result_totweight; + + if (gps->dvert != NULL) { + stroke_defvert_create_nr_list(gps->dvert, gps->totpoints, &def_nr_list, &result_totweight); + new_dv = stroke_defvert_new_count(count, result_totweight, &def_nr_list); + } + + int next_point_index = 1; + i = 0; + float pressure, strength, ratio_result; + float vert_color[4]; + int index_from, index_to; + float last_coord[3]; + + /* 1st point is always at the start */ + pt1 = &gps->points[0]; + copy_v3_v3(last_coord, &pt1->x); + pt2 = &new_pt[i]; + copy_v3_v3(&pt2->x, last_coord); + new_pt[i].pressure = pt[0].pressure; + new_pt[i].strength = pt[0].strength; + memcpy(new_pt[i].vert_color, pt[0].vert_color, sizeof(float) * 4); + if (select) { + new_pt[i].flag |= GP_SPOINT_SELECT; + } + i++; + + if (new_dv) { + stroke_interpolate_deform_weights(gps, 0, 0, 0, &new_dv[0]); + } + + /* the rest */ + while ((next_point_index = stroke_march_next_point(gps, + next_point_index, + last_coord, + dist, + last_coord, + &pressure, + &strength, + vert_color, + &ratio_result, + &index_from, + &index_to)) > -1) { + pt2 = &new_pt[i]; + copy_v3_v3(&pt2->x, last_coord); + new_pt[i].pressure = pressure; + new_pt[i].strength = strength; + memcpy(new_pt[i].vert_color, vert_color, sizeof(float) * 4); + if (select) { + new_pt[i].flag |= GP_SPOINT_SELECT; + } + + if (new_dv) { + stroke_interpolate_deform_weights(gps, index_from, index_to, ratio_result, &new_dv[i]); + } + + i++; + if (next_point_index == 0) { + break; /* last point finished */ + } + } + + gps->points = new_pt; + /* Free original vertex list. */ + MEM_freeN(pt); + + if (new_dv) { + /* Free original weight data. */ + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + while ((ld = BLI_pophead(&def_nr_list))) { + MEM_freeN(ld); + } + + gps->dvert = new_dv; + } + + gps->totpoints = i; + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); + + return true; +} + +/** + * Backbone stretch similar to Freestyle. + * \param gps: Stroke to sample + * \param dist: Distance of one segment + * \param tip_length: Ignore tip jittering, set zero to use default value. + */ +bool BKE_gpencil_stroke_stretch(bGPDstroke *gps, const float dist, const float tip_length) +{ + bGPDspoint *pt = gps->points, *last_pt, *second_last, *next_pt; + int i; + float threshold = (tip_length == 0 ? 0.001f : tip_length); + + if (gps->totpoints < 2 || dist < FLT_EPSILON) { + return false; + } + + last_pt = &pt[gps->totpoints - 1]; + second_last = &pt[gps->totpoints - 2]; + next_pt = &pt[1]; + + float len1 = 0.0f; + float len2 = 0.0f; + + i = 1; + while (len1 < threshold && gps->totpoints > i) { + next_pt = &pt[i]; + len1 = len_v3v3(&next_pt->x, &pt->x); + i++; + } + + i = 2; + while (len2 < threshold && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + len2 = len_v3v3(&last_pt->x, &second_last->x); + i++; + } + + float extend1 = (len1 + dist) / len1; + float extend2 = (len2 + dist) / len2; + + float result1[3], result2[3]; + + interp_v3_v3v3(result1, &next_pt->x, &pt->x, extend1); + interp_v3_v3v3(result2, &second_last->x, &last_pt->x, extend2); + + copy_v3_v3(&pt->x, result1); + copy_v3_v3(&last_pt->x, result2); + + return true; +} + +/** + * Trim stroke to needed segments + * \param gps: Target stroke + * \param index_from: the index of the first point to be used in the trimmed result + * \param index_to: the index of the last point to be used in the trimmed result + */ +bool BKE_gpencil_stroke_trim_points(bGPDstroke *gps, const int index_from, const int index_to) +{ + bGPDspoint *pt = gps->points, *new_pt; + MDeformVert *dv, *new_dv; + + const int new_count = index_to - index_from + 1; + + if (new_count >= gps->totpoints) { + return false; + } + + if (new_count == 1) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->points); + gps->points = NULL; + gps->dvert = NULL; + gps->totpoints = 0; + return false; + } + + new_pt = MEM_callocN(sizeof(bGPDspoint) * new_count, "gp_stroke_points_trimmed"); + + for (int i = 0; i < new_count; i++) { + memcpy(&new_pt[i], &pt[i + index_from], sizeof(bGPDspoint)); + } + + if (gps->dvert) { + new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, "gp_stroke_dverts_trimmed"); + for (int i = 0; i < new_count; i++) { + dv = &gps->dvert[i + index_from]; + new_dv[i].flag = dv->flag; + new_dv[i].totweight = dv->totweight; + new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight, + "gp_stroke_dverts_dw_trimmed"); + for (int j = 0; j < dv->totweight; j++) { + new_dv[i].dw[j].weight = dv->dw[j].weight; + new_dv[i].dw[j].def_nr = dv->dw[j].def_nr; + } + } + MEM_freeN(gps->dvert); + gps->dvert = new_dv; + } + + MEM_freeN(gps->points); + gps->points = new_pt; + gps->totpoints = new_count; + + return true; +} + +bool BKE_gpencil_stroke_split(bGPDframe *gpf, + bGPDstroke *gps, + const int before_index, + bGPDstroke **remaining_gps) +{ + bGPDstroke *new_gps; + bGPDspoint *pt = gps->points, *new_pt; + MDeformVert *dv, *new_dv; + + if (before_index >= gps->totpoints || before_index == 0) { + return false; + } + + const int new_count = gps->totpoints - before_index; + const int old_count = before_index; + + /* Handle remaining segments first. */ + + new_gps = BKE_gpencil_stroke_add_existing_style( + gpf, gps, gps->mat_nr, new_count, gps->thickness); + + new_pt = new_gps->points; /* Allocated from above. */ + + for (int i = 0; i < new_count; i++) { + memcpy(&new_pt[i], &pt[i + before_index], sizeof(bGPDspoint)); + } + + if (gps->dvert) { + new_dv = MEM_callocN(sizeof(MDeformVert) * new_count, + "gp_stroke_dverts_remaining(MDeformVert)"); + for (int i = 0; i < new_count; i++) { + dv = &gps->dvert[i + before_index]; + new_dv[i].flag = dv->flag; + new_dv[i].totweight = dv->totweight; + new_dv[i].dw = MEM_callocN(sizeof(MDeformWeight) * dv->totweight, + "gp_stroke_dverts_dw_remaining(MDeformWeight)"); + for (int j = 0; j < dv->totweight; j++) { + new_dv[i].dw[j].weight = dv->dw[j].weight; + new_dv[i].dw[j].def_nr = dv->dw[j].def_nr; + } + } + new_gps->dvert = new_dv; + } + + (*remaining_gps) = new_gps; + + /* Trim the original stroke into a shorter one. + * Keep the end point. */ + + BKE_gpencil_stroke_trim_points(gps, 0, old_count); + BKE_gpencil_stroke_geometry_update(gps); + return true; +} + +/** + * Shrink the stroke by length. + * \param gps: Stroke to shrink + * \param dist: delta length + */ +bool BKE_gpencil_stroke_shrink(bGPDstroke *gps, const float dist) +{ + bGPDspoint *pt = gps->points, *second_last; + int i; + + if (gps->totpoints < 2 || dist < FLT_EPSILON) { + return false; + } + + second_last = &pt[gps->totpoints - 2]; + + float len1, this_len1, cut_len1; + float len2, this_len2, cut_len2; + int index_start, index_end; + + len1 = len2 = this_len1 = this_len2 = cut_len1 = cut_len2 = 0.0f; + + i = 1; + while (len1 < dist && gps->totpoints > i - 1) { + this_len1 = len_v3v3(&pt[i].x, &pt[i + 1].x); + len1 += this_len1; + cut_len1 = len1 - dist; + i++; + } + index_start = i - 2; + + i = 2; + while (len2 < dist && gps->totpoints >= i) { + second_last = &pt[gps->totpoints - i]; + this_len2 = len_v3v3(&second_last[1].x, &second_last->x); + len2 += this_len2; + cut_len2 = len2 - dist; + i++; + } + index_end = gps->totpoints - i + 2; + + if (len1 < dist || len2 < dist || index_end <= index_start) { + index_start = index_end = 0; /* empty stroke */ + } + + if ((index_end == index_start + 1) && (cut_len1 + cut_len2 > 1.0f)) { + index_start = index_end = 0; /* no length left to cut */ + } + + BKE_gpencil_stroke_trim_points(gps, index_start, index_end); + + if (gps->totpoints == 0) { + return false; + } + + pt = gps->points; + + float cut1 = cut_len1 / this_len1; + float cut2 = cut_len2 / this_len2; + + float result1[3], result2[3]; + + interp_v3_v3v3(result1, &pt[1].x, &pt[0].x, cut1); + interp_v3_v3v3(result2, &pt[gps->totpoints - 2].x, &pt[gps->totpoints - 1].x, cut2); + + copy_v3_v3(&pt[0].x, result1); + copy_v3_v3(&pt[gps->totpoints - 1].x, result2); + + return true; +} + +/** + * Apply smooth to stroke point + * \param gps: Stroke to smooth + * \param i: Point index + * \param inf: Amount of smoothing to apply + */ +bool BKE_gpencil_stroke_smooth(bGPDstroke *gps, int i, float inf) +{ + bGPDspoint *pt = &gps->points[i]; + 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_stroke_smooth_strength(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; + } + /* Only affect endpoints by a fraction of the normal influence */ + float inf = influence; + if ((point_index == 0) || (point_index == gps->totpoints - 1)) { + inf *= 0.01f; + } + /* Limit max influence to reduce pop effect. */ + CLAMP_MAX(inf, 0.98f); + + float total = 0.0f; + float max_strength = 0.0f; + const int steps = 4; + const float average_fac = 1.0f / (float)(steps * 2 + 1); + int step; + + /* add the point itself */ + total += ptb->strength * average_fac; + max_strength = ptb->strength; + + /* n-steps before/after current point */ + for (step = 1; step <= steps; step++) { + bGPDspoint *pt1, *pt2; + int before = point_index - step; + int after = point_index + 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) */ + total += pt1->strength * average_fac; + total += pt2->strength * average_fac; + /* Save max value. */ + if (max_strength < pt1->strength) { + max_strength = pt1->strength; + } + if (max_strength < pt2->strength) { + max_strength = pt2->strength; + } + } + + /* Based on influence factor, blend between original and optimal smoothed value. */ + ptb->strength = interpf(ptb->strength, total, inf); + /* Clamp to maximum stroke strength to avoid weird results. */ + CLAMP_MAX(ptb->strength, max_strength); + + return true; +} + +/** + * Apply smooth for thickness to stroke point (use pressure) */ +bool BKE_gpencil_stroke_smooth_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; + } + /* Only affect endpoints by a fraction of the normal influence */ + float inf = influence; + if ((point_index == 0) || (point_index == gps->totpoints - 1)) { + inf *= 0.01f; + } + /* Limit max influence to reduce pop effect. */ + CLAMP_MAX(inf, 0.98f); + + float total = 0.0f; + float max_pressure = 0.0f; + const int steps = 4; + const float average_fac = 1.0f / (float)(steps * 2 + 1); + int step; + + /* add the point itself */ + total += ptb->pressure * average_fac; + max_pressure = ptb->pressure; + + /* n-steps before/after current point */ + for (step = 1; step <= steps; step++) { + bGPDspoint *pt1, *pt2; + int before = point_index - step; + int after = point_index + 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) */ + total += pt1->pressure * average_fac; + total += pt2->pressure * average_fac; + /* Save max value. */ + if (max_pressure < pt1->pressure) { + max_pressure = pt1->pressure; + } + if (max_pressure < pt2->pressure) { + max_pressure = pt2->pressure; + } + } + + /* Based on influence factor, blend between original and optimal smoothed value. */ + ptb->pressure = interpf(ptb->pressure, total, inf); + /* Clamp to maximum stroke thickness to avoid weird results. */ + CLAMP_MAX(ptb->pressure, max_pressure); + return true; +} + +/** + * Apply smooth for UV rotation to stroke point (use pressure). + */ +bool BKE_gpencil_stroke_smooth_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 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]; +} + +/* calc texture coordinates using flat projected points */ +static void gpencil_calc_stroke_fill_uv(const float (*points2d)[2], + bGPDstroke *gps, + const float minv[2], + float maxv[2], + float (*r_uv)[2]) +{ + const float s = sin(gps->uv_rotation); + const float c = cos(gps->uv_rotation); + + /* Calc center for rotation. */ + float center[2] = {0.5f, 0.5f}; + float d[2]; + d[0] = maxv[0] - minv[0]; + d[1] = maxv[1] - minv[1]; + for (int i = 0; i < gps->totpoints; i++) { + r_uv[i][0] = (points2d[i][0] - minv[0]) / d[0]; + r_uv[i][1] = (points2d[i][1] - minv[1]) / d[1]; + + /* Apply translation. */ + add_v2_v2(r_uv[i], gps->uv_translation); + + /* Apply Rotation. */ + r_uv[i][0] -= center[0]; + r_uv[i][1] -= center[1]; + + float x = r_uv[i][0] * c - r_uv[i][1] * s; + float y = r_uv[i][0] * s + r_uv[i][1] * c; + + r_uv[i][0] = x + center[0]; + r_uv[i][1] = y + center[1]; + + /* Apply scale. */ + if (gps->uv_scale != 0.0f) { + mul_v2_fl(r_uv[i], 1.0f / gps->uv_scale); + } + } +} + +/* Triangulate stroke for high quality fill (this is done only if cache is null or stroke was + * modified) */ +void BKE_gpencil_stroke_fill_triangulate(bGPDstroke *gps) +{ + BLI_assert(gps->totpoints >= 3); + + /* allocate memory for temporary areas */ + gps->tot_triangles = gps->totpoints - 2; + uint(*tmp_triangles)[3] = MEM_mallocN(sizeof(*tmp_triangles) * gps->tot_triangles, + "GP Stroke temp triangulation"); + float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints, + "GP Stroke temp 2d points"); + float(*uv)[2] = MEM_mallocN(sizeof(*uv) * gps->totpoints, "GP Stroke temp 2d uv data"); + + int direction = 0; + + /* convert to 2d and triangulate */ + BKE_gpencil_stroke_2d_flat(gps->points, gps->totpoints, points2d, &direction); + BLI_polyfill_calc(points2d, (uint)gps->totpoints, direction, tmp_triangles); + + /* calc texture coordinates automatically */ + float minv[2]; + float maxv[2]; + /* first needs bounding box data */ + ARRAY_SET_ITEMS(minv, -1.0f, -1.0f); + ARRAY_SET_ITEMS(maxv, 1.0f, 1.0f); + + /* calc uv data */ + gpencil_calc_stroke_fill_uv(points2d, gps, minv, maxv, uv); + + /* Save triangulation data. */ + if (gps->tot_triangles > 0) { + MEM_SAFE_FREE(gps->triangles); + gps->triangles = MEM_callocN(sizeof(*gps->triangles) * gps->tot_triangles, + "GP Stroke triangulation"); + + for (int i = 0; i < gps->tot_triangles; i++) { + memcpy(gps->triangles[i].verts, tmp_triangles[i], sizeof(uint[3])); + } + + /* Copy UVs to bGPDspoint. */ + for (int i = 0; i < gps->totpoints; i++) { + copy_v2_v2(gps->points[i].uv_fill, uv[i]); + } + } + else { + /* No triangles needed - Free anything allocated previously */ + if (gps->triangles) { + MEM_freeN(gps->triangles); + } + + gps->triangles = NULL; + } + + /* clear memory */ + MEM_SAFE_FREE(tmp_triangles); + MEM_SAFE_FREE(points2d); + MEM_SAFE_FREE(uv); +} + +/* texture coordinate utilities */ +void BKE_gpencil_stroke_uv_update(bGPDstroke *gps) +{ + if (gps == NULL || gps->totpoints == 0) { + return; + } + + bGPDspoint *pt = gps->points; + float totlen = 0.0f; + pt[0].uv_fac = totlen; + for (int i = 1; i < gps->totpoints; i++) { + totlen += len_v3v3(&pt[i - 1].x, &pt[i].x); + pt[i].uv_fac = totlen; + } +} + +/* Recalc the internal geometry caches for fill and uvs. */ +void BKE_gpencil_stroke_geometry_update(bGPDstroke *gps) +{ + if (gps == NULL) { + return; + } + + if (gps->totpoints > 2) { + BKE_gpencil_stroke_fill_triangulate(gps); + } + else { + gps->tot_triangles = 0; + MEM_SAFE_FREE(gps->triangles); + } + + /* calc uv data along the stroke */ + BKE_gpencil_stroke_uv_update(gps); + + /* Calc stroke bounding box. */ + BKE_gpencil_stroke_boundingbox_calc(gps); +} + +float BKE_gpencil_stroke_length(const bGPDstroke *gps, bool use_3d) +{ + if (!gps->points || gps->totpoints < 2) { + return 0.0f; + } + float *last_pt = &gps->points[0].x; + int i; + bGPDspoint *pt; + float total_length = 0.0f; + for (i = 1; i < gps->totpoints; i++) { + pt = &gps->points[i]; + if (use_3d) { + total_length += len_v3v3(&pt->x, last_pt); + } + else { + total_length += len_v2v2(&pt->x, last_pt); + } + last_pt = &pt->x; + } + return total_length; +} + +/** + * Trim stroke to the first intersection or loop + * \param gps: Stroke data + */ +bool BKE_gpencil_stroke_trim(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->totpoints = newtot; + + MEM_SAFE_FREE(old_points); + MEM_SAFE_FREE(old_dvert); + } + + BKE_gpencil_stroke_geometry_update(gps); + + return intersect; +} + +/** + * Close stroke + * \param gps: Stroke to close + */ +bool BKE_gpencil_stroke_close(bGPDstroke *gps) +{ + bGPDspoint *pt1 = NULL; + bGPDspoint *pt2 = NULL; + + /* Only can close a stroke with 3 points or more. */ + if (gps->totpoints < 3) { + return false; + } + + /* Calc average distance between points to get same level of sampling. */ + float dist_tot = 0.0f; + for (int i = 0; i < gps->totpoints - 1; i++) { + pt1 = &gps->points[i]; + pt2 = &gps->points[i + 1]; + dist_tot += len_v3v3(&pt1->x, &pt2->x); + } + /* Calc the average distance. */ + float dist_avg = dist_tot / (gps->totpoints - 1); + + /* Calc distance between last and first point. */ + pt1 = &gps->points[gps->totpoints - 1]; + pt2 = &gps->points[0]; + float dist_close = len_v3v3(&pt1->x, &pt2->x); + + /* if the distance to close is very small, don't need add points and just enable cyclic. */ + if (dist_close <= dist_avg) { + gps->flag |= GP_STROKE_CYCLIC; + return true; + } + + /* Calc number of points required using the average distance. */ + int tot_newpoints = MAX2(dist_close / dist_avg, 1); + + /* Resize stroke array. */ + int old_tot = gps->totpoints; + gps->totpoints += tot_newpoints; + gps->points = MEM_recallocN(gps->points, sizeof(*gps->points) * gps->totpoints); + if (gps->dvert != NULL) { + gps->dvert = MEM_recallocN(gps->dvert, sizeof(*gps->dvert) * gps->totpoints); + } + + /* Generate new points */ + pt1 = &gps->points[old_tot - 1]; + pt2 = &gps->points[0]; + bGPDspoint *pt = &gps->points[old_tot]; + for (int i = 1; i < tot_newpoints + 1; i++, pt++) { + float step = (tot_newpoints > 1) ? ((float)i / (float)tot_newpoints) : 0.99f; + /* Clamp last point to be near, but not on top of first point. */ + if ((tot_newpoints > 1) && (i == tot_newpoints)) { + step *= 0.99f; + } + + /* Average point. */ + interp_v3_v3v3(&pt->x, &pt1->x, &pt2->x, step); + pt->pressure = interpf(pt2->pressure, pt1->pressure, step); + pt->strength = interpf(pt2->strength, pt1->strength, step); + pt->flag = 0; + interp_v4_v4v4(pt->vert_color, pt1->vert_color, pt2->vert_color, step); + + /* Set weights. */ + if (gps->dvert != NULL) { + MDeformVert *dvert1 = &gps->dvert[old_tot - 1]; + MDeformWeight *dw1 = BKE_defvert_ensure_index(dvert1, 0); + float weight_1 = dw1 ? dw1->weight : 0.0f; + + MDeformVert *dvert2 = &gps->dvert[0]; + MDeformWeight *dw2 = BKE_defvert_ensure_index(dvert2, 0); + float weight_2 = dw2 ? dw2->weight : 0.0f; + + MDeformVert *dvert_final = &gps->dvert[old_tot + i - 1]; + dvert_final->totweight = 0; + MDeformWeight *dw = BKE_defvert_ensure_index(dvert_final, 0); + if (dvert_final->dw) { + dw->weight = interpf(weight_2, weight_1, step); + } + } + } + + /* Enable cyclic flag. */ + gps->flag |= GP_STROKE_CYCLIC; + + return true; +} +/* Dissolve points in stroke */ +void BKE_gpencil_dissolve_points(bGPDframe *gpf, bGPDstroke *gps, const short tag) +{ + bGPDspoint *pt; + MDeformVert *dvert = NULL; + int i; + + int tot = gps->totpoints; /* number of points in new buffer */ + /* first pass: count points to remove */ + /* Count how many points are selected (i.e. how many to remove) */ + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if (pt->flag & tag) { + /* selected point - one of the points to remove */ + tot--; + } + } + + /* if no points are left, we simply delete the entire stroke */ + if (tot <= 0) { + /* remove the entire stroke */ + 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 { + /* just copy all points to keep into a smaller buffer */ + bGPDspoint *new_points = MEM_callocN(sizeof(bGPDspoint) * tot, "new gp stroke points copy"); + bGPDspoint *npt = new_points; + + MDeformVert *new_dvert = NULL; + MDeformVert *ndvert = NULL; + + if (gps->dvert != NULL) { + new_dvert = MEM_callocN(sizeof(MDeformVert) * tot, "new gp stroke weights copy"); + ndvert = new_dvert; + } + + (gps->dvert != NULL) ? dvert = gps->dvert : NULL; + for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) { + if ((pt->flag & tag) == 0) { + *npt = *pt; + npt++; + + if (gps->dvert != NULL) { + *ndvert = *dvert; + ndvert->dw = MEM_dupallocN(dvert->dw); + ndvert++; + } + } + if (gps->dvert != NULL) { + dvert++; + } + } + + /* free the old buffer */ + if (gps->points) { + MEM_freeN(gps->points); + } + if (gps->dvert) { + BKE_gpencil_free_stroke_weights(gps); + MEM_freeN(gps->dvert); + } + + /* save the new buffer */ + gps->points = new_points; + gps->dvert = new_dvert; + gps->totpoints = tot; + + /* triangles cache needs to be recalculated */ + BKE_gpencil_stroke_geometry_update(gps); + } +} + +/* Merge by distance ------------------------------------- */ +/* Reduce a series of points when the distance is below a threshold. + * Special case for first and last points (both are keeped) for other points, + * the merge point always is at first point. + * \param gpf: Grease Pencil frame + * \param gps: Grease Pencil stroke + * \param threshold: Distance between points + * \param use_unselected: Set to true to analyze all stroke and not only selected points + */ +void BKE_gpencil_stroke_merge_distance(bGPDframe *gpf, + bGPDstroke *gps, + const float threshold, + const bool use_unselected) +{ + bGPDspoint *pt = NULL; + bGPDspoint *pt_next = NULL; + float tagged = false; + /* Use square distance to speed up loop */ + const float th_square = threshold * threshold; + /* Need to have something to merge. */ + if (gps->totpoints < 2) { + return; + } + int i = 0; + int step = 1; + while ((i < gps->totpoints - 1) && (i + step < gps->totpoints)) { + pt = &gps->points[i]; + if (pt->flag & GP_SPOINT_TAG) { + i++; + step = 1; + continue; + } + pt_next = &gps->points[i + step]; + /* Do not recalc tagged points. */ + if (pt_next->flag & GP_SPOINT_TAG) { + step++; + continue; + } + /* Check if contiguous points are selected. */ + if (!use_unselected) { + if (((pt->flag & GP_SPOINT_SELECT) == 0) || ((pt_next->flag & GP_SPOINT_SELECT) == 0)) { + i++; + step = 1; + continue; + } + } + float len_square = len_squared_v3v3(&pt->x, &pt_next->x); + if (len_square <= th_square) { + tagged = true; + if (i != gps->totpoints - 1) { + /* Tag second point for delete. */ + pt_next->flag |= GP_SPOINT_TAG; + } + else { + pt->flag |= GP_SPOINT_TAG; + } + /* Jump to next pair of points, keeping first point segment equals.*/ + step++; + } + else { + /* Analyze next point. */ + i++; + step = 1; + } + } + + /* Always untag extremes. */ + pt = &gps->points[0]; + pt->flag &= ~GP_SPOINT_TAG; + pt = &gps->points[gps->totpoints - 1]; + pt->flag &= ~GP_SPOINT_TAG; + + /* Dissolve tagged points */ + if (tagged) { + BKE_gpencil_dissolve_points(gpf, gps, GP_SPOINT_TAG); + } + + /* Calc geometry data. */ + BKE_gpencil_stroke_geometry_update(gps); +} + +/* Helper: Check materials with same color. */ +static int gpencil_check_same_material_color(Object *ob_gp, float color[4], Material **r_mat) +{ + Material *ma = NULL; + float color_cu[4]; + linearrgb_to_srgb_v3_v3(color_cu, color); + float hsv1[4]; + rgb_to_hsv_v(color_cu, hsv1); + hsv1[3] = color[3]; + + for (int i = 1; i <= ob_gp->totcol; i++) { + ma = BKE_object_material_get(ob_gp, i); + MaterialGPencilStyle *gp_style = ma->gp_style; + /* Check color with small tolerance (better in HSV). */ + float hsv2[4]; + rgb_to_hsv_v(gp_style->fill_rgba, hsv2); + hsv2[3] = gp_style->fill_rgba[3]; + if ((gp_style->fill_style == GP_MATERIAL_FILL_STYLE_SOLID) && + (compare_v4v4(hsv1, hsv2, 0.01f))) { + *r_mat = ma; + return i - 1; + } + } + + *r_mat = NULL; + return -1; +} + +/* Helper: Add gpencil material using curve material as base. */ +static Material *gpencil_add_from_curve_material(Main *bmain, + Object *ob_gp, + const float cu_color[4], + const bool gpencil_lines, + const bool fill, + int *r_idx) +{ + Material *mat_gp = BKE_gpencil_object_material_new( + bmain, ob_gp, (fill) ? "Material" : "Unassigned", r_idx); + MaterialGPencilStyle *gp_style = mat_gp->gp_style; + + /* Stroke color. */ + if (gpencil_lines) { + ARRAY_SET_ITEMS(gp_style->stroke_rgba, 0.0f, 0.0f, 0.0f, 1.0f); + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + else { + linearrgb_to_srgb_v4(gp_style->stroke_rgba, cu_color); + gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; + } + + /* Fill color. */ + linearrgb_to_srgb_v4(gp_style->fill_rgba, cu_color); + /* Fill is false if the original curve hasn't material assigned, so enable it. */ + if (fill) { + gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + + /* Check at least one is enabled. */ + if (((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0) && + ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) { + gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + } + + return mat_gp; +} + +/* Helper: Create new stroke section. */ +static void gpencil_add_new_points(bGPDstroke *gps, + float *coord_array, + float pressure, + int init, + int totpoints, + const float init_co[3], + bool last) +{ + for (int i = 0; i < totpoints; i++) { + bGPDspoint *pt = &gps->points[i + init]; + copy_v3_v3(&pt->x, &coord_array[3 * i]); + /* Be sure the last point is not on top of the first point of the curve or + * the close of the stroke will produce glitches. */ + if ((last) && (i > 0) && (i == totpoints - 1)) { + float dist = len_v3v3(init_co, &pt->x); + if (dist < 0.1f) { + /* Interpolate between previous point and current to back slightly. */ + bGPDspoint *pt_prev = &gps->points[i + init - 1]; + interp_v3_v3v3(&pt->x, &pt_prev->x, &pt->x, 0.95f); + } + } + + pt->pressure = pressure; + pt->strength = 1.0f; + } +} + +/* Helper: Get the first collection that includes the object. */ +static Collection *gpencil_get_parent_collection(Scene *scene, Object *ob) +{ + Collection *mycol = NULL; + FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) { + for (CollectionObject *cob = collection->gobject.first; cob; cob = cob->next) { + if ((mycol == NULL) && (cob->ob == ob)) { + mycol = collection; + } + } + } + FOREACH_SCENE_COLLECTION_END; + + return mycol; +} + +/* Helper: Convert one spline to grease pencil stroke. */ +static void gpencil_convert_spline(Main *bmain, + Object *ob_gp, + Object *ob_cu, + const bool gpencil_lines, + const bool only_stroke, + bGPDframe *gpf, + Nurb *nu) +{ + Curve *cu = (Curve *)ob_cu->data; + bool cyclic = true; + + /* Create Stroke. */ + bGPDstroke *gps = MEM_callocN(sizeof(bGPDstroke), "bGPDstroke"); + gps->thickness = 1.0f; + gps->fill_opacity_fac = 1.0f; + gps->hardeness = 1.0f; + gps->uv_scale = 1.0f; + + ARRAY_SET_ITEMS(gps->aspect_ratio, 1.0f, 1.0f); + ARRAY_SET_ITEMS(gps->caps, GP_STROKE_CAP_ROUND, GP_STROKE_CAP_ROUND); + gps->inittime = 0.0f; + + gps->flag &= ~GP_STROKE_SELECT; + gps->flag |= GP_STROKE_3DSPACE; + + gps->mat_nr = 0; + /* Count total points + * The total of points must consider that last point of each segment is equal to the first + * point of next segment. + */ + int totpoints = 0; + int segments = 0; + int resolu = nu->resolu + 1; + segments = nu->pntsu; + if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) { + segments--; + cyclic = false; + } + totpoints = (resolu * segments) - (segments - 1); + + /* Materials + * Notice: The color of the material is the color of viewport and not the final shader color. + */ + Material *mat_gp = NULL; + bool fill = true; + /* Check if grease pencil has a material with same color.*/ + float color[4]; + if ((cu->mat) && (*cu->mat)) { + Material *mat_cu = *cu->mat; + copy_v4_v4(color, &mat_cu->r); + } + else { + /* Gray (unassigned from SVG add-on) */ + zero_v4(color); + add_v3_fl(color, 0.6f); + color[3] = 1.0f; + fill = false; + } + + /* Special case: If the color was created by the SVG add-on and the name contains '_stroke' and + * there is only one color, the stroke must not be closed, fill to false and use for + * stroke the fill color. + */ + bool do_stroke = false; + if (ob_cu->totcol == 1) { + Material *ma_stroke = BKE_object_material_get(ob_cu, 1); + if ((ma_stroke) && (strstr(ma_stroke->id.name, "_stroke") != NULL)) { + do_stroke = true; + } + } + + int r_idx = gpencil_check_same_material_color(ob_gp, color, &mat_gp); + if ((ob_cu->totcol > 0) && (r_idx < 0)) { + Material *mat_curve = BKE_object_material_get(ob_cu, 1); + mat_gp = gpencil_add_from_curve_material(bmain, ob_gp, color, gpencil_lines, fill, &r_idx); + + if ((mat_curve) && (mat_curve->gp_style != NULL)) { + MaterialGPencilStyle *gp_style_cur = mat_curve->gp_style; + MaterialGPencilStyle *gp_style_gp = mat_gp->gp_style; + + copy_v4_v4(gp_style_gp->mix_rgba, gp_style_cur->mix_rgba); + gp_style_gp->fill_style = gp_style_cur->fill_style; + gp_style_gp->mix_factor = gp_style_cur->mix_factor; + } + + /* If object has more than 1 material, use second material for stroke color. */ + if ((!only_stroke) && (ob_cu->totcol > 1) && (BKE_object_material_get(ob_cu, 2))) { + mat_curve = BKE_object_material_get(ob_cu, 2); + if (mat_curve) { + linearrgb_to_srgb_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r); + mat_gp->gp_style->stroke_rgba[3] = mat_curve->a; + } + } + else if ((only_stroke) || (do_stroke)) { + /* Also use the first color if the fill is none for stroke color. */ + if (ob_cu->totcol > 0) { + mat_curve = BKE_object_material_get(ob_cu, 1); + if (mat_curve) { + copy_v3_v3(mat_gp->gp_style->stroke_rgba, &mat_curve->r); + mat_gp->gp_style->stroke_rgba[3] = mat_curve->a; + /* Set fill and stroke depending of curve type (3D or 2D). */ + if ((cu->flag & CU_3D) || ((cu->flag & (CU_FRONT | CU_BACK)) == 0)) { + mat_gp->gp_style->flag |= GP_MATERIAL_STROKE_SHOW; + mat_gp->gp_style->flag &= ~GP_MATERIAL_FILL_SHOW; + } + else { + mat_gp->gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW; + mat_gp->gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + } + } + } + } + CLAMP_MIN(r_idx, 0); + + /* Assign material index to stroke. */ + gps->mat_nr = r_idx; + + /* Add stroke to frame.*/ + BLI_addtail(&gpf->strokes, gps); + + float *coord_array = NULL; + float init_co[3]; + + switch (nu->type) { + case CU_POLY: { + /* Allocate memory for storage points. */ + gps->totpoints = nu->pntsu; + gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + /* Increase thickness for this type. */ + gps->thickness = 10.0f; + + /* Get all curve points */ + for (int s = 0; s < gps->totpoints; s++) { + BPoint *bp = &nu->bp[s]; + bGPDspoint *pt = &gps->points[s]; + copy_v3_v3(&pt->x, bp->vec); + pt->pressure = bp->radius; + pt->strength = 1.0f; + } + break; + } + case CU_BEZIER: { + /* Allocate memory for storage points. */ + gps->totpoints = totpoints; + gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + + int init = 0; + resolu = nu->resolu + 1; + segments = nu->pntsu; + if (((nu->flagu & CU_NURB_CYCLIC) == 0) || (nu->pntsu == 2)) { + segments--; + } + /* Get all interpolated curve points of Beziert */ + for (int s = 0; s < segments; s++) { + int inext = (s + 1) % nu->pntsu; + BezTriple *prevbezt = &nu->bezt[s]; + BezTriple *bezt = &nu->bezt[inext]; + bool last = (bool)(s == segments - 1); + + coord_array = MEM_callocN((size_t)3 * resolu * sizeof(float), __func__); + + for (int j = 0; j < 3; j++) { + BKE_curve_forward_diff_bezier(prevbezt->vec[1][j], + prevbezt->vec[2][j], + bezt->vec[0][j], + bezt->vec[1][j], + coord_array + j, + resolu - 1, + 3 * sizeof(float)); + } + /* Save first point coordinates. */ + if (s == 0) { + copy_v3_v3(init_co, &coord_array[0]); + } + /* Add points to the stroke */ + gpencil_add_new_points(gps, coord_array, bezt->radius, init, resolu, init_co, last); + /* Free memory. */ + MEM_SAFE_FREE(coord_array); + + /* As the last point of segment is the first point of next segment, back one array + * element to avoid duplicated points on the same location. + */ + init += resolu - 1; + } + break; + } + case CU_NURBS: { + if (nu->pntsv == 1) { + + int nurb_points; + if (nu->flagu & CU_NURB_CYCLIC) { + resolu++; + nurb_points = nu->pntsu * resolu; + } + else { + nurb_points = (nu->pntsu - 1) * resolu; + } + /* Get all curve points. */ + coord_array = MEM_callocN(sizeof(float[3]) * nurb_points, __func__); + BKE_nurb_makeCurve(nu, coord_array, NULL, NULL, NULL, resolu, sizeof(float[3])); + + /* Allocate memory for storage points. */ + gps->totpoints = nurb_points - 1; + gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points"); + + /* Add points. */ + gpencil_add_new_points(gps, coord_array, 1.0f, 0, gps->totpoints, init_co, false); + + MEM_SAFE_FREE(coord_array); + } + break; + } + default: { + break; + } + } + /* Cyclic curve, close stroke. */ + if ((cyclic) && (!do_stroke)) { + BKE_gpencil_stroke_close(gps); + } + + /* Recalc fill geometry. */ + BKE_gpencil_stroke_geometry_update(gps); +} + +/* Convert a curve object to grease pencil stroke. + * + * \param bmain: Main thread pointer + * \param scene: Original scene. + * \param ob_gp: Grease pencil object to add strokes. + * \param ob_cu: Curve to convert. + * \param gpencil_lines: Use lines for strokes. + * \param use_collections: Create layers using collection names. + * \param only_stroke: The material must be only stroke without fill. + */ +void BKE_gpencil_convert_curve(Main *bmain, + Scene *scene, + Object *ob_gp, + Object *ob_cu, + const bool gpencil_lines, + const bool use_collections, + const bool only_stroke) +{ + if (ELEM(NULL, ob_gp, ob_cu) || (ob_gp->type != OB_GPENCIL) || (ob_gp->data == NULL)) { + return; + } + + Curve *cu = (Curve *)ob_cu->data; + bGPdata *gpd = (bGPdata *)ob_gp->data; + bGPDlayer *gpl = NULL; + + /* If the curve is empty, cancel. */ + if (cu->nurb.first == NULL) { + return; + } + + /* Check if there is an active layer. */ + if (use_collections) { + Collection *collection = gpencil_get_parent_collection(scene, ob_cu); + if (collection != NULL) { + gpl = BKE_gpencil_layer_named_get(gpd, collection->id.name + 2); + if (gpl == NULL) { + gpl = BKE_gpencil_layer_addnew(gpd, collection->id.name + 2, true); + } + } + } + + if (gpl == NULL) { + gpl = BKE_gpencil_layer_active_get(gpd); + if (gpl == NULL) { + gpl = BKE_gpencil_layer_addnew(gpd, DATA_("GP_Layer"), true); + } + } + + /* Check if there is an active frame and add if needed. */ + bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, CFRA, GP_GETFRAME_ADD_COPY); + + /* Read all splines of the curve and create a stroke for each. */ + for (Nurb *nu = cu->nurb.first; nu; nu = nu->next) { + gpencil_convert_spline(bmain, ob_gp, ob_cu, gpencil_lines, only_stroke, gpf, nu); + } + + /* Tag for recalculation */ + DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE); +} + +/* Apply Transforms */ +void BKE_gpencil_transform(bGPdata *gpd, float mat[4][4]) +{ + if (gpd == NULL) { + return; + } + + const float scalef = mat4_to_scale(mat); + LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) { + /* 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; + } + + LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) { + LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) { + bGPDspoint *pt; + int i; + + for (pt = gps->points, i = 0; i < gps->totpoints; pt++, i++) { + mul_m4_v3(mat, &pt->x); + pt->pressure *= scalef; + } + + /* Distortion may mean we need to re-triangulate. */ + BKE_gpencil_stroke_geometry_update(gps); + } + } + } +} +/** \} */ diff --git a/source/blender/blenkernel/intern/gpencil_modifier.c b/source/blender/blenkernel/intern/gpencil_modifier.c index e73070f117b..0bb6ce84b1b 100644 --- a/source/blender/blenkernel/intern/gpencil_modifier.c +++ b/source/blender/blenkernel/intern/gpencil_modifier.c @@ -40,6 +40,7 @@ #include "DNA_scene_types.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lattice.h" #include "BKE_lib_id.h" diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c index 7e1b21717b4..7a2e9583aa1 100644 --- a/source/blender/blenkernel/intern/object.c +++ b/source/blender/blenkernel/intern/object.c @@ -83,6 +83,7 @@ #include "BKE_font.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_hair.h" #include "BKE_icons.h" diff --git a/source/blender/blenloader/intern/versioning_280.c b/source/blender/blenloader/intern/versioning_280.c index b0fbe5c710b..4d472401521 100644 --- a/source/blender/blenloader/intern/versioning_280.c +++ b/source/blender/blenloader/intern/versioning_280.c @@ -77,6 +77,7 @@ #include "BKE_freestyle.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_idprop.h" #include "BKE_key.h" diff --git a/source/blender/draw/intern/draw_cache_impl_gpencil.c b/source/blender/draw/intern/draw_cache_impl_gpencil.c index afe47160e02..349eb6b00ae 100644 --- a/source/blender/draw/intern/draw_cache_impl_gpencil.c +++ b/source/blender/draw/intern/draw_cache_impl_gpencil.c @@ -27,6 +27,7 @@ #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "DRW_engine.h" #include "DRW_render.h" diff --git a/source/blender/editors/gpencil/gpencil_add_monkey.c b/source/blender/editors/gpencil/gpencil_add_monkey.c index f08188c48ce..23ca5241866 100644 --- a/source/blender/editors/gpencil/gpencil_add_monkey.c +++ b/source/blender/editors/gpencil/gpencil_add_monkey.c @@ -31,6 +31,7 @@ #include "BKE_brush.h" #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_add_stroke.c b/source/blender/editors/gpencil/gpencil_add_stroke.c index d4e17144ca2..60a4404beaf 100644 --- a/source/blender/editors/gpencil/gpencil_add_stroke.c +++ b/source/blender/editors/gpencil/gpencil_add_stroke.c @@ -31,6 +31,7 @@ #include "BKE_brush.h" #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c index bf1497fb2ed..83ecb3ab42f 100644 --- a/source/blender/editors/gpencil/gpencil_edit.c +++ b/source/blender/editors/gpencil/gpencil_edit.c @@ -51,6 +51,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_lib_id.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_fill.c b/source/blender/editors/gpencil/gpencil_fill.c index d487f76a103..f61572fffca 100644 --- a/source/blender/editors/gpencil/gpencil_fill.c +++ b/source/blender/editors/gpencil/gpencil_fill.c @@ -43,6 +43,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_image.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_interpolate.c b/source/blender/editors/gpencil/gpencil_interpolate.c index 5577a35c1fd..fef88007542 100644 --- a/source/blender/editors/gpencil/gpencil_interpolate.c +++ b/source/blender/editors/gpencil/gpencil_interpolate.c @@ -50,6 +50,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_report.h" #include "UI_interface.h" diff --git a/source/blender/editors/gpencil/gpencil_merge.c b/source/blender/editors/gpencil/gpencil_merge.c index 9a7ad8d7220..bd9daa83411 100644 --- a/source/blender/editors/gpencil/gpencil_merge.c +++ b/source/blender/editors/gpencil/gpencil_merge.c @@ -35,6 +35,7 @@ #include "BKE_brush.h" #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_report.h" diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c index bec080e398d..01b0fe80dfc 100644 --- a/source/blender/editors/gpencil/gpencil_paint.c +++ b/source/blender/editors/gpencil/gpencil_paint.c @@ -53,6 +53,7 @@ #include "BKE_deform.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_primitive.c b/source/blender/editors/gpencil/gpencil_primitive.c index 7470e7789af..2b30a415086 100644 --- a/source/blender/editors/gpencil/gpencil_primitive.c +++ b/source/blender/editors/gpencil/gpencil_primitive.c @@ -54,6 +54,7 @@ #include "BKE_deform.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_paint.h" diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c index 46867dcb11a..59b14a47d1d 100644 --- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c +++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c @@ -53,6 +53,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_main.h" #include "BKE_material.h" diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c index da6e5114822..276be071e0b 100644 --- a/source/blender/editors/gpencil/gpencil_utils.c +++ b/source/blender/editors/gpencil/gpencil_utils.c @@ -51,6 +51,7 @@ #include "BKE_context.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_main.h" #include "BKE_material.h" #include "BKE_object.h" diff --git a/source/blender/editors/gpencil/gpencil_uv.c b/source/blender/editors/gpencil/gpencil_uv.c index dac19e33235..1da32dcc537 100644 --- a/source/blender/editors/gpencil/gpencil_uv.c +++ b/source/blender/editors/gpencil/gpencil_uv.c @@ -30,6 +30,7 @@ #include "BKE_context.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_unit.h" #include "RNA_access.h" diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 576e8e0ec25..ac2958282c1 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -64,7 +64,7 @@ #include "BKE_displist.h" #include "BKE_effect.h" #include "BKE_font.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_hair.h" #include "BKE_key.h" #include "BKE_lattice.h" diff --git a/source/blender/editors/object/object_transform.c b/source/blender/editors/object/object_transform.c index a467667a4c7..0eab9c72227 100644 --- a/source/blender/editors/object/object_transform.c +++ b/source/blender/editors/object/object_transform.c @@ -45,6 +45,7 @@ #include "BKE_curve.h" #include "BKE_editmesh.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_idcode.h" #include "BKE_lattice.h" #include "BKE_layer.h" diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c index 92adc7d6756..2aff5482b1c 100644 --- a/source/blender/editors/space_view3d/view3d_edit.c +++ b/source/blender/editors/space_view3d/view3d_edit.c @@ -46,7 +46,7 @@ #include "BKE_camera.h" #include "BKE_context.h" #include "BKE_font.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_main.h" diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index ca0eb40e7b1..cb7071e0f49 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -69,7 +69,7 @@ #include "BKE_curve.h" #include "BKE_editmesh.h" #include "BKE_fcurve.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_lattice.h" #include "BKE_layer.h" #include "BKE_lib_id.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c index 09027c16619..59a41901a48 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c @@ -37,6 +37,7 @@ #include "DNA_scene_types.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lattice.h" #include "BKE_lib_query.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c index 68bac0ff701..314879927ff 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c @@ -43,6 +43,7 @@ #include "BKE_collection.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_layer.h" #include "BKE_lib_query.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c index 56779c1e23c..3263b78ba85 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilbuild.c @@ -37,6 +37,7 @@ #include "DNA_scene_types.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "DEG_depsgraph.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c index 97e3fca3f4d..76f22fc9a36 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c @@ -38,7 +38,7 @@ #include "BKE_action.h" #include "BKE_colortools.h" #include "BKE_deform.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_layer.h" #include "BKE_lib_query.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c index 2ef0cca97d4..73b3c332a2e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c @@ -33,7 +33,7 @@ #include "DNA_scene_types.h" #include "BKE_deform.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_lattice.h" #include "BKE_layer.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c index 4b4d5e9b511..eab9ffb4630 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmultiply.c @@ -41,6 +41,7 @@ #include "BKE_context.h" #include "BKE_global.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_layer.h" #include "BKE_lib_query.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c index f6537a1d1c0..5ed08e39197 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilnoise.c @@ -42,6 +42,7 @@ #include "BKE_colortools.h" #include "BKE_deform.h" #include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "BKE_object.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c index 67419add3ed..a78de314c21 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciloffset.c @@ -36,7 +36,7 @@ #include "BKE_colortools.h" #include "BKE_deform.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "DEG_depsgraph.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c index f6307253a49..7b914b2a3b0 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsimplify.c @@ -32,7 +32,7 @@ #include "DNA_scene_types.h" #include "DNA_vec_types.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "DEG_depsgraph.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c index 4533aad6fa3..fb9089ea6cb 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsmooth.c @@ -33,7 +33,7 @@ #include "BKE_colortools.h" #include "BKE_deform.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "DEG_depsgraph.h" diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c index 5d221904f1b..0fdc3694af2 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilsubdiv.c @@ -34,7 +34,7 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_gpencil_modifier.h" #include "DEG_depsgraph.h" diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c index f451126a7e0..deacd8e1cfc 100644 --- a/source/blender/makesrna/intern/rna_gpencil.c +++ b/source/blender/makesrna/intern/rna_gpencil.c @@ -138,6 +138,7 @@ static EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = { # include "BKE_action.h" # include "BKE_animsys.h" # include "BKE_gpencil.h" +# include "BKE_gpencil_geom.h" # include "BKE_icons.h" # include "DEG_depsgraph.h" diff --git a/source/blender/makesrna/intern/rna_object_api.c b/source/blender/makesrna/intern/rna_object_api.c index 1db44f4fdea..5104f4a66a1 100644 --- a/source/blender/makesrna/intern/rna_object_api.c +++ b/source/blender/makesrna/intern/rna_object_api.c @@ -36,7 +36,7 @@ #include "DNA_modifier_types.h" #include "DNA_object_types.h" -#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" #include "BKE_layer.h" #include "DEG_depsgraph.h" -- cgit v1.2.3