This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/gpencil/gpencil_utils.c
Antonio Vazquez 556c71a84a GPencil: New option to Merge All layers in active one
This new option allows to combine all layers in the active one. Also the merge down option has been improved.

Reviewed By: mendio, pablo vazquez (UI)

Differential Revision: https://developer.blender.org/D13054
2021-11-04 19:40:55 +01:00

3486 lines
107 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2014, Blender Foundation
*/
/** \file
* \ingroup edgpencil
*/
#include <math.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_hash.h"
#include "BLI_lasso_2d.h"
#include "BLI_math.h"
#include "BLI_rand.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "PIL_time.h"
#include "DNA_brush_types.h"
#include "DNA_collection_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_screen_types.h"
#include "DNA_space_types.h"
#include "DNA_view3d_types.h"
#include "BKE_action.h"
#include "BKE_brush.h"
#include "BKE_collection.h"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
#include "BKE_gpencil_geom.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_object.h"
#include "BKE_paint.h"
#include "BKE_tracking.h"
#include "WM_api.h"
#include "WM_toolsystem.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"
#include "UI_resources.h"
#include "UI_view2d.h"
#include "ED_clip.h"
#include "ED_gpencil.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_transform_snap_object_context.h"
#include "ED_view3d.h"
#include "GPU_immediate.h"
#include "GPU_immediate_util.h"
#include "GPU_state.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "gpencil_intern.h"
/* ******************************************************** */
/* Context Wrangling... */
/**
* Get pointer to active Grease Pencil data-block,
* and an RNA-pointer to trace back to whatever owns it,
* when context info is not available.
*/
bGPdata **ED_gpencil_data_get_pointers_direct(ScrArea *area, Object *ob, PointerRNA *r_ptr)
{
/* if there's an active area, check if the particular editor may
* have defined any special Grease Pencil context for editing...
*/
if (area) {
switch (area->spacetype) {
case SPACE_PROPERTIES: /* properties */
case SPACE_INFO: /* header info */
case SPACE_TOPBAR: /* Top-bar */
case SPACE_VIEW3D: /* 3D-View */
{
if (ob && (ob->type == OB_GPENCIL)) {
/* GP Object. */
if (r_ptr) {
RNA_id_pointer_create(&ob->id, r_ptr);
}
return (bGPdata **)&ob->data;
}
return NULL;
}
default: /* Unsupported space. */
return NULL;
}
}
return NULL;
}
/**
* Get pointer to active Grease Pencil data-block for annotations,
* and an RNA-pointer to trace back to whatever owns it,
* when context info is not available.
*/
bGPdata **ED_annotation_data_get_pointers_direct(ID *screen_id,
ScrArea *area,
Scene *scene,
PointerRNA *r_ptr)
{
/* If there's an active area, check if the particular editor may
* have defined any special Grease Pencil context for editing. */
if (area) {
SpaceLink *sl = area->spacedata.first;
switch (area->spacetype) {
case SPACE_PROPERTIES: /* properties */
case SPACE_INFO: /* header info */
{
return NULL;
}
case SPACE_TOPBAR: /* Top-bar */
case SPACE_VIEW3D: /* 3D-View */
{
if (r_ptr) {
RNA_id_pointer_create(&scene->id, r_ptr);
}
return &scene->gpd;
break;
}
case SPACE_NODE: /* Nodes Editor */
{
SpaceNode *snode = (SpaceNode *)sl;
/* return the GP data for the active node block/node */
if (snode && snode->nodetree) {
/* for now, as long as there's an active node tree,
* default to using that in the Nodes Editor */
if (r_ptr) {
RNA_id_pointer_create(&snode->nodetree->id, r_ptr);
}
return &snode->nodetree->gpd;
}
/* Even when there is no node-tree, don't allow this to flow to scene. */
return NULL;
}
case SPACE_SEQ: /* Sequencer */
{
SpaceSeq *sseq = (SpaceSeq *)sl;
/* For now, Grease Pencil data is associated with the space
* (actually preview region only). */
if (r_ptr) {
RNA_pointer_create(screen_id, &RNA_SpaceSequenceEditor, sseq, r_ptr);
}
return &sseq->gpd;
}
case SPACE_IMAGE: /* Image/UV Editor */
{
SpaceImage *sima = (SpaceImage *)sl;
/* For now, Grease Pencil data is associated with the space... */
if (r_ptr) {
RNA_pointer_create(screen_id, &RNA_SpaceImageEditor, sima, r_ptr);
}
return &sima->gpd;
}
case SPACE_CLIP: /* Nodes Editor */
{
SpaceClip *sc = (SpaceClip *)sl;
MovieClip *clip = ED_space_clip_get_clip(sc);
if (clip) {
if (sc->gpencil_src == SC_GPENCIL_SRC_TRACK) {
MovieTrackingTrack *track = BKE_tracking_track_get_active(&clip->tracking);
if (!track) {
return NULL;
}
if (r_ptr) {
RNA_pointer_create(&clip->id, &RNA_MovieTrackingTrack, track, r_ptr);
}
return &track->gpd;
}
if (r_ptr) {
RNA_id_pointer_create(&clip->id, r_ptr);
}
return &clip->gpd;
}
break;
}
default: /* unsupported space */
return NULL;
}
}
return NULL;
}
/**
* Get pointer to active Grease Pencil data-block,
* and an RNA-pointer to trace back to whatever owns it.
*/
bGPdata **ED_gpencil_data_get_pointers(const bContext *C, PointerRNA *r_ptr)
{
ScrArea *area = CTX_wm_area(C);
Object *ob = CTX_data_active_object(C);
return ED_gpencil_data_get_pointers_direct(area, ob, r_ptr);
}
/**
* Get pointer to active Grease Pencil data-block,
* and an RNA-pointer to trace back to whatever owns it.
*/
bGPdata **ED_annotation_data_get_pointers(const bContext *C, PointerRNA *r_ptr)
{
ID *screen_id = (ID *)CTX_wm_screen(C);
Scene *scene = CTX_data_scene(C);
ScrArea *area = CTX_wm_area(C);
return ED_annotation_data_get_pointers_direct(screen_id, area, scene, r_ptr);
}
/* -------------------------------------------------------- */
/* Get the active Grease Pencil data-block, when context is not available */
bGPdata *ED_gpencil_data_get_active_direct(ScrArea *area, Object *ob)
{
bGPdata **gpd_ptr = ED_gpencil_data_get_pointers_direct(area, ob, NULL);
return (gpd_ptr) ? *(gpd_ptr) : NULL;
}
/* Get the active Grease Pencil data-block, when context is not available */
bGPdata *ED_annotation_data_get_active_direct(ID *screen_id, ScrArea *area, Scene *scene)
{
bGPdata **gpd_ptr = ED_annotation_data_get_pointers_direct(screen_id, area, scene, NULL);
return (gpd_ptr) ? *(gpd_ptr) : NULL;
}
/**
* Get the active Grease Pencil data-block
*/
bGPdata *ED_gpencil_data_get_active(const bContext *C)
{
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
return NULL;
}
return ob->data;
}
/**
* Get the active Grease Pencil data-block
* \note This is the original (#G.main) copy of the data-block, stored in files.
* Do not use for reading evaluated copies of GP Objects data.
*/
bGPdata *ED_annotation_data_get_active(const bContext *C)
{
bGPdata **gpd_ptr = ED_annotation_data_get_pointers(C, NULL);
return (gpd_ptr) ? *(gpd_ptr) : NULL;
}
/**
* Get the evaluated copy of the active Grease Pencil data-block (where applicable)
* - For the 3D View (i.e. "GP Objects"), this gives the evaluated copy of the GP data-block
* (i.e. a copy of the active GP data-block for the active object, where modifiers have been
* applied). This is needed to correctly work with "Copy-on-Write".
* - For all other editors (i.e. "GP Annotations"), this just gives the active data-block
* like for #ED_gpencil_data_get_active()
*/
bGPdata *ED_gpencil_data_get_active_evaluated(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
const Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
return ED_gpencil_data_get_active_direct(area, ob_eval);
}
/* -------------------------------------------------------- */
/**
* Utility to check whether the r_ptr output of ED_gpencil_data_get_pointers()
* is for annotation usage.
*/
bool ED_gpencil_data_owner_is_annotation(PointerRNA *owner_ptr)
{
/* Key Assumption: If the pointer is an object, we're dealing with a GP Object's data.
* Otherwise, the GP data-block is being used for annotations (i.e. everywhere else). */
return ((owner_ptr) && (owner_ptr->type != &RNA_Object));
}
/* ******************************************************** */
/* Keyframe Indicator Checks */
/* Check whether there's an active GP keyframe on the current frame */
bool ED_gpencil_has_keyframe_v3d(Scene *UNUSED(scene), Object *ob, int cfra)
{
if (ob && ob->data && (ob->type == OB_GPENCIL)) {
bGPDlayer *gpl = BKE_gpencil_layer_active_get(ob->data);
if (gpl) {
if (gpl->actframe) {
/* XXX: assumes that frame has been fetched already */
return (gpl->actframe->framenum == cfra);
}
/* XXX: disabled as could be too much of a penalty */
// return BKE_gpencil_layer_frame_find(gpl, cfra);
}
}
return false;
}
/* ******************************************************** */
/* Poll Callbacks */
/* poll callback for adding data/layers - special */
bool gpencil_add_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if (ob == NULL) {
return false;
}
bGPdata *gpd = (bGPdata *)ob->data;
return (gpd != NULL);
}
/* poll callback for checking if there is an active layer */
bool gpencil_active_layer_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (ob->type != OB_GPENCIL)) {
return false;
}
bGPdata *gpd = (bGPdata *)ob->data;
bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd);
return (gpl != NULL);
}
/* poll callback for checking if there is an active brush */
bool gpencil_active_brush_poll(bContext *C)
{
ToolSettings *ts = CTX_data_tool_settings(C);
Paint *paint = &ts->gp_paint->paint;
if (paint) {
return (paint->brush != NULL);
}
return false;
}
/* ******************************************************** */
/* Dynamic Enums of GP Layers */
/* NOTE: These include an option to create a new layer and use that... */
/* Just existing layers */
const EnumPropertyItem *ED_gpencil_layers_enum_itemf(bContext *C,
PointerRNA *UNUSED(ptr),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl;
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
int i = 0;
if (ELEM(NULL, C, gpd)) {
return DummyRNA_DEFAULT_items;
}
/* Existing layers */
for (gpl = gpd->layers.first; gpl; gpl = gpl->next, i++) {
item_tmp.identifier = gpl->info;
item_tmp.name = gpl->info;
item_tmp.value = i;
if (gpl->flag & GP_LAYER_ACTIVE) {
item_tmp.icon = ICON_GREASEPENCIL;
}
else {
item_tmp.icon = ICON_NONE;
}
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
/* Existing + Option to add/use new layer */
const EnumPropertyItem *ED_gpencil_layers_with_new_enum_itemf(bContext *C,
PointerRNA *UNUSED(ptr),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
bGPdata *gpd = CTX_data_gpencil_data(C);
bGPDlayer *gpl;
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
int i = 0;
if (ELEM(NULL, C, gpd)) {
return DummyRNA_DEFAULT_items;
}
/* Create new layer */
/* TODO: have some way of specifying that we don't want this? */
const int tot = BLI_listbase_count(&gpd->layers);
/* Existing layers */
for (gpl = gpd->layers.last, i = 0; gpl; gpl = gpl->prev, i++) {
item_tmp.identifier = gpl->info;
item_tmp.name = gpl->info;
item_tmp.value = tot - i - 1;
if (gpl->flag & GP_LAYER_ACTIVE) {
item_tmp.icon = ICON_GREASEPENCIL;
}
else {
item_tmp.icon = ICON_NONE;
}
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
{
/* separator */
RNA_enum_item_add_separator(&item, &totitem);
/* "New Layer" entry */
item_tmp.identifier = "__CREATE__";
item_tmp.name = "New Layer";
item_tmp.value = -1;
item_tmp.icon = ICON_ADD;
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
/* Just existing Materials */
const EnumPropertyItem *ED_gpencil_material_enum_itemf(bContext *C,
PointerRNA *UNUSED(ptr),
PropertyRNA *UNUSED(prop),
bool *r_free)
{
Object *ob = CTX_data_active_object(C);
EnumPropertyItem *item = NULL, item_tmp = {0};
int totitem = 0;
int i = 0;
if (ELEM(NULL, C, ob)) {
return DummyRNA_DEFAULT_items;
}
/* Existing materials */
for (i = 1; i <= ob->totcol; i++) {
Material *ma = BKE_object_material_get(ob, i);
if (ma) {
item_tmp.identifier = ma->id.name + 2;
item_tmp.name = ma->id.name + 2;
item_tmp.value = i;
item_tmp.icon = ma->preview ? ma->preview->icon_id : ICON_NONE;
RNA_enum_item_add(&item, &totitem, &item_tmp);
}
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
/* ******************************************************** */
/* Brush Tool Core */
/**
* Check whether a given stroke segment is inside a circular brush
*
* \param mval: The current screen-space coordinates (midpoint) of the brush
* \param rad: The radius of the brush
*
* \param x0, y0: The screen-space x and y coordinates of the start of the stroke segment
* \param x1, y1: The screen-space x and y coordinates of the end of the stroke segment
*/
bool gpencil_stroke_inside_circle(const float mval[2], int rad, int x0, int y0, int x1, int y1)
{
/* simple within-radius check for now */
const float screen_co_a[2] = {x0, y0};
const float screen_co_b[2] = {x1, y1};
if (edge_inside_circle(mval, rad, screen_co_a, screen_co_b)) {
return true;
}
/* not inside */
return false;
}
/* ******************************************************** */
/* Selection Validity Testing */
bool ED_gpencil_frame_has_selected_stroke(const bGPDframe *gpf)
{
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_SELECT) {
return true;
}
}
return false;
}
bool ED_gpencil_layer_has_selected_stroke(const bGPDlayer *gpl, const bool is_multiedit)
{
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (ED_gpencil_frame_has_selected_stroke(gpf)) {
return true;
}
}
/* If not multi-edit, exit loop. */
if (!is_multiedit) {
break;
}
}
return false;
}
/* ******************************************************** */
/* Stroke Validity Testing */
/* Check whether given stroke can be edited given the supplied context */
/* TODO: do we need additional flags for screenspace vs dataspace? */
bool ED_gpencil_stroke_can_use_direct(const ScrArea *area, const bGPDstroke *gps)
{
/* sanity check */
if (ELEM(NULL, area, gps)) {
return false;
}
/* filter stroke types by flags + spacetype */
if (gps->flag & GP_STROKE_3DSPACE) {
/* 3D strokes - only in 3D view */
return (ELEM(area->spacetype, SPACE_VIEW3D, SPACE_PROPERTIES));
}
if (gps->flag & GP_STROKE_2DIMAGE) {
/* Special "image" strokes - only in Image Editor */
return (area->spacetype == SPACE_IMAGE);
}
if (gps->flag & GP_STROKE_2DSPACE) {
/* 2D strokes (dataspace) - for any 2D view (i.e. everything other than 3D view) */
return (area->spacetype != SPACE_VIEW3D);
}
/* view aligned - anything goes */
return true;
}
/* Check whether given stroke can be edited in the current context */
bool ED_gpencil_stroke_can_use(const bContext *C, const bGPDstroke *gps)
{
ScrArea *area = CTX_wm_area(C);
return ED_gpencil_stroke_can_use_direct(area, gps);
}
/* Check whether given stroke can be edited for the current color */
bool ED_gpencil_stroke_material_editable(Object *ob, const bGPDlayer *gpl, const bGPDstroke *gps)
{
/* check if the color is editable */
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if (gp_style != NULL) {
if (gp_style->flag & GP_MATERIAL_HIDE) {
return false;
}
if (((gpl->flag & GP_LAYER_UNLOCK_COLOR) == 0) && (gp_style->flag & GP_MATERIAL_LOCKED)) {
return false;
}
}
return true;
}
/* Check whether given stroke is visible for the current material. */
bool ED_gpencil_stroke_material_visible(Object *ob, const bGPDstroke *gps)
{
/* check if the color is editable */
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
if (gp_style != NULL) {
if (gp_style->flag & GP_MATERIAL_HIDE) {
return false;
}
}
return true;
}
/* ******************************************************** */
/* Space Conversion */
/**
* Init settings for stroke point space conversions
*
* \param r_gsc: [out] The space conversion settings struct, populated with necessary params
*/
void gpencil_point_conversion_init(bContext *C, GP_SpaceConversion *r_gsc)
{
ScrArea *area = CTX_wm_area(C);
ARegion *region = CTX_wm_region(C);
/* zero out the storage (just in case) */
memset(r_gsc, 0, sizeof(GP_SpaceConversion));
unit_m4(r_gsc->mat);
/* store settings */
r_gsc->scene = CTX_data_scene(C);
r_gsc->ob = CTX_data_active_object(C);
r_gsc->area = area;
r_gsc->region = region;
r_gsc->v2d = &region->v2d;
/* init region-specific stuff */
if (area->spacetype == SPACE_VIEW3D) {
wmWindow *win = CTX_wm_window(C);
Scene *scene = CTX_data_scene(C);
struct Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
View3D *v3d = (View3D *)CTX_wm_space_data(C);
RegionView3D *rv3d = region->regiondata;
/* init 3d depth buffers */
view3d_operator_needs_opengl(C);
view3d_region_operator_needs_opengl(win, region);
ED_view3d_depth_override(depsgraph, region, v3d, NULL, V3D_DEPTH_NO_GPENCIL, NULL);
/* for camera view set the subrect */
if (rv3d->persp == RV3D_CAMOB) {
ED_view3d_calc_camera_border(
scene, depsgraph, region, v3d, rv3d, &r_gsc->subrect_data, true);
r_gsc->subrect = &r_gsc->subrect_data;
}
}
}
/**
* Convert point to parent space
*
* \param pt: Original point
* \param diff_mat: Matrix with the difference between original parent matrix
* \param[out] r_pt: Pointer to new point after apply matrix
*/
void gpencil_point_to_parent_space(const bGPDspoint *pt,
const float diff_mat[4][4],
bGPDspoint *r_pt)
{
float fpt[3];
mul_v3_m4v3(fpt, diff_mat, &pt->x);
copy_v3_v3(&r_pt->x, fpt);
}
/**
* Change position relative to parent object
*/
void gpencil_apply_parent(Depsgraph *depsgraph, Object *obact, bGPDlayer *gpl, bGPDstroke *gps)
{
bGPDspoint *pt;
int i;
/* undo matrix */
float diff_mat[4][4];
float inverse_diff_mat[4][4];
float fpt[3];
BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat);
invert_m4_m4(inverse_diff_mat, diff_mat);
for (i = 0; i < gps->totpoints; i++) {
pt = &gps->points[i];
mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x);
copy_v3_v3(&pt->x, fpt);
}
}
/**
* Change point position relative to parent object
*/
void gpencil_apply_parent_point(Depsgraph *depsgraph,
Object *obact,
bGPDlayer *gpl,
bGPDspoint *pt)
{
/* undo matrix */
float diff_mat[4][4];
float inverse_diff_mat[4][4];
float fpt[3];
BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat);
invert_m4_m4(inverse_diff_mat, diff_mat);
mul_v3_m4v3(fpt, inverse_diff_mat, &pt->x);
copy_v3_v3(&pt->x, fpt);
}
/**
* Convert a Grease Pencil coordinate (i.e. can be 2D or 3D) to screenspace (2D)
*
* \param[out] r_x: The screen-space x-coordinate of the point
* \param[out] r_y: The screen-space y-coordinate of the point
*
* \warning This assumes that the caller has already checked
* whether the stroke in question can be drawn.
*/
void gpencil_point_to_xy(
const GP_SpaceConversion *gsc, const bGPDstroke *gps, const bGPDspoint *pt, int *r_x, int *r_y)
{
const ARegion *region = gsc->region;
const View2D *v2d = gsc->v2d;
const rctf *subrect = gsc->subrect;
int xyval[2];
/* sanity checks */
BLI_assert(!(gps->flag & GP_STROKE_3DSPACE) || (gsc->area->spacetype == SPACE_VIEW3D));
BLI_assert(!(gps->flag & GP_STROKE_2DSPACE) || (gsc->area->spacetype != SPACE_VIEW3D));
if (gps->flag & GP_STROKE_3DSPACE) {
if (ED_view3d_project_int_global(region, &pt->x, xyval, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) {
*r_x = xyval[0];
*r_y = xyval[1];
}
else {
*r_x = V2D_IS_CLIPPED;
*r_y = V2D_IS_CLIPPED;
}
}
else if (gps->flag & GP_STROKE_2DSPACE) {
float vec[3] = {pt->x, pt->y, 0.0f};
mul_m4_v3(gsc->mat, vec);
UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], r_x, r_y);
}
else {
if (subrect == NULL) {
/* normal 3D view (or view space) */
*r_x = (int)(pt->x / 100 * region->winx);
*r_y = (int)(pt->y / 100 * region->winy);
}
else {
/* camera view, use subrect */
*r_x = (int)((pt->x / 100) * BLI_rctf_size_x(subrect)) + subrect->xmin;
*r_y = (int)((pt->y / 100) * BLI_rctf_size_y(subrect)) + subrect->ymin;
}
}
}
/**
* Convert a Grease Pencil coordinate (i.e. can be 2D or 3D) to screenspace (2D).
*
* Just like #gpencil_point_to_xy(), except the resulting coordinates are floats not ints.
* Use this version to solve "stair-step" artifacts which may arise when
* roundtripping the calculations.
*
* \param r_x: The screen-space x-coordinate of the point.
* \param r_y: The screen-space y-coordinate of the point.
*
* \warning This assumes that the caller has already checked
* whether the stroke in question can be drawn.
*/
void gpencil_point_to_xy_fl(const GP_SpaceConversion *gsc,
const bGPDstroke *gps,
const bGPDspoint *pt,
float *r_x,
float *r_y)
{
const ARegion *region = gsc->region;
const View2D *v2d = gsc->v2d;
const rctf *subrect = gsc->subrect;
float xyval[2];
/* sanity checks */
BLI_assert(!(gps->flag & GP_STROKE_3DSPACE) || (gsc->area->spacetype == SPACE_VIEW3D));
BLI_assert(!(gps->flag & GP_STROKE_2DSPACE) || (gsc->area->spacetype != SPACE_VIEW3D));
if (gps->flag & GP_STROKE_3DSPACE) {
if (ED_view3d_project_float_global(region, &pt->x, xyval, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) {
*r_x = xyval[0];
*r_y = xyval[1];
}
else {
*r_x = 0.0f;
*r_y = 0.0f;
}
}
else if (gps->flag & GP_STROKE_2DSPACE) {
float vec[3] = {pt->x, pt->y, 0.0f};
int t_x, t_y;
mul_m4_v3(gsc->mat, vec);
UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], &t_x, &t_y);
if ((t_x == t_y) && (t_x == V2D_IS_CLIPPED)) {
/* XXX: Or should we just always use the values as-is? */
*r_x = 0.0f;
*r_y = 0.0f;
}
else {
*r_x = (float)t_x;
*r_y = (float)t_y;
}
}
else {
if (subrect == NULL) {
/* normal 3D view (or view space) */
*r_x = (pt->x / 100.0f * region->winx);
*r_y = (pt->y / 100.0f * region->winy);
}
else {
/* camera view, use subrect */
*r_x = ((pt->x / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
*r_y = ((pt->y / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
}
}
}
/**
* generic based on gpencil_point_to_xy_fl
*/
void gpencil_point_3d_to_xy(const GP_SpaceConversion *gsc,
const short flag,
const float pt[3],
float xy[2])
{
const ARegion *region = gsc->region;
const View2D *v2d = gsc->v2d;
const rctf *subrect = gsc->subrect;
float xyval[2];
/* sanity checks */
BLI_assert((gsc->area->spacetype == SPACE_VIEW3D));
if (flag & GP_STROKE_3DSPACE) {
if (ED_view3d_project_float_global(region, pt, xyval, V3D_PROJ_TEST_NOP) == V3D_PROJ_RET_OK) {
xy[0] = xyval[0];
xy[1] = xyval[1];
}
else {
xy[0] = 0.0f;
xy[1] = 0.0f;
}
}
else if (flag & GP_STROKE_2DSPACE) {
float vec[3] = {pt[0], pt[1], 0.0f};
int t_x, t_y;
mul_m4_v3(gsc->mat, vec);
UI_view2d_view_to_region_clip(v2d, vec[0], vec[1], &t_x, &t_y);
if ((t_x == t_y) && (t_x == V2D_IS_CLIPPED)) {
/* XXX: Or should we just always use the values as-is? */
xy[0] = 0.0f;
xy[1] = 0.0f;
}
else {
xy[0] = (float)t_x;
xy[1] = (float)t_y;
}
}
else {
if (subrect == NULL) {
/* normal 3D view (or view space) */
xy[0] = (pt[0] / 100.0f * region->winx);
xy[1] = (pt[1] / 100.0f * region->winy);
}
else {
/* camera view, use subrect */
xy[0] = ((pt[0] / 100.0f) * BLI_rctf_size_x(subrect)) + subrect->xmin;
xy[1] = ((pt[1] / 100.0f) * BLI_rctf_size_y(subrect)) + subrect->ymin;
}
}
}
/**
* Project screenspace coordinates to 3D-space
*
* For use with editing tools where it is easier to perform the operations in 2D,
* and then later convert the transformed points back to 3D.
*
* \param screen_co: The screenspace 2D coordinates to convert to
* \param r_out: The resulting 3D coordinates of the input point
*
* \note We include this as a utility function, since the standard method
* involves quite a few steps, which are invariably always the same
* for all GPencil operations. So, it's nicer to just centralize these.
*
* \warning Assumes that it is getting called in a 3D view only.
*/
bool gpencil_point_xy_to_3d(const GP_SpaceConversion *gsc,
Scene *scene,
const float screen_co[2],
float r_out[3])
{
const RegionView3D *rv3d = gsc->region->regiondata;
float rvec[3];
ED_gpencil_drawing_reference_get(scene, gsc->ob, scene->toolsettings->gpencil_v3d_align, rvec);
float zfac = ED_view3d_calc_zfac(rv3d, rvec, NULL);
float mval_f[2], mval_prj[2];
float dvec[3];
copy_v2_v2(mval_f, screen_co);
if (ED_view3d_project_float_global(gsc->region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) {
sub_v2_v2v2(mval_f, mval_prj, mval_f);
ED_view3d_win_to_delta(gsc->region, mval_f, dvec, zfac);
sub_v3_v3v3(r_out, rvec, dvec);
return true;
}
zero_v3(r_out);
return false;
}
/**
* Convert tGPspoint (temporary 2D/screenspace point data used by GP modal operators)
* to 3D coordinates.
*
* \param point2D: The screen-space 2D point data to convert.
* \param depth: Depth array (via #ED_view3d_depth_read_cached()).
* \param r_out: The resulting 2D point data.
*/
void gpencil_stroke_convertcoords_tpoint(Scene *scene,
ARegion *region,
Object *ob,
const tGPspoint *point2D,
float *depth,
float r_out[3])
{
ToolSettings *ts = scene->toolsettings;
int mval_i[2];
round_v2i_v2fl(mval_i, &point2D->x);
if ((depth != NULL) && (ED_view3d_autodist_simple(region, mval_i, r_out, 0, depth))) {
/* projecting onto 3D-Geometry
* - nothing more needs to be done here, since view_autodist_simple() has already done it
*/
}
else {
float mval_f[2] = {point2D->x, point2D->y};
float mval_prj[2];
float rvec[3], dvec[3];
float zfac;
/* Current method just converts each point in screen-coordinates to
* 3D-coordinates using the 3D-cursor as reference.
*/
ED_gpencil_drawing_reference_get(scene, ob, ts->gpencil_v3d_align, rvec);
zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL);
if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) {
sub_v2_v2v2(mval_f, mval_prj, mval_f);
ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
sub_v3_v3v3(r_out, rvec, dvec);
}
else {
zero_v3(r_out);
}
}
}
/**
* Get drawing reference point for conversion or projection of the stroke
* \param r_vec: Reference point found
*/
void ED_gpencil_drawing_reference_get(const Scene *scene,
const Object *ob,
char align_flag,
float r_vec[3])
{
const float *fp = scene->cursor.location;
/* if using a gpencil object at cursor mode, can use the location of the object */
if (align_flag & GP_PROJECT_VIEWSPACE) {
if (ob && (ob->type == OB_GPENCIL)) {
/* fallback (no strokes) - use cursor or object location */
if (align_flag & GP_PROJECT_CURSOR) {
/* use 3D-cursor */
copy_v3_v3(r_vec, fp);
}
else {
/* use object location */
copy_v3_v3(r_vec, ob->obmat[3]);
/* Apply layer offset. */
bGPdata *gpd = ob->data;
bGPDlayer *gpl = BKE_gpencil_layer_active_get(gpd);
if (gpl != NULL) {
add_v3_v3(r_vec, gpl->layer_mat[3]);
}
}
}
}
else {
/* use 3D-cursor */
copy_v3_v3(r_vec, fp);
}
}
void ED_gpencil_project_stroke_to_view(bContext *C, bGPDlayer *gpl, bGPDstroke *gps)
{
Scene *scene = CTX_data_scene(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
GP_SpaceConversion gsc = {NULL};
bGPDspoint *pt;
int i;
float diff_mat[4][4];
float inverse_diff_mat[4][4];
/* init space conversion stuff */
gpencil_point_conversion_init(C, &gsc);
BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
invert_m4_m4(inverse_diff_mat, diff_mat);
/* Adjust each point */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
float xy[2];
bGPDspoint pt2;
gpencil_point_to_parent_space(pt, diff_mat, &pt2);
gpencil_point_to_xy_fl(&gsc, gps, &pt2, &xy[0], &xy[1]);
/* Planar - All on same plane parallel to the viewplane */
gpencil_point_xy_to_3d(&gsc, scene, xy, &pt->x);
/* Unapply parent corrections */
mul_m4_v3(inverse_diff_mat, &pt->x);
}
}
/**
* Reproject all points of the stroke to a plane locked to axis to avoid stroke offset
*/
void ED_gpencil_project_stroke_to_plane(const Scene *scene,
const Object *ob,
const RegionView3D *rv3d,
bGPDlayer *gpl,
bGPDstroke *gps,
const float origin[3],
const int axis)
{
const ToolSettings *ts = scene->toolsettings;
const View3DCursor *cursor = &scene->cursor;
float plane_normal[3];
float vn[3];
float ray[3];
float rpoint[3];
/* Recalculate layer transform matrix. */
loc_eul_size_to_mat4(gpl->layer_mat, gpl->location, gpl->rotation, gpl->scale);
invert_m4_m4(gpl->layer_invmat, gpl->layer_mat);
/* normal vector for a plane locked to axis */
zero_v3(plane_normal);
if (axis < 0) {
/* if the axis is not locked, need a vector to the view direction
* in order to get the right size of the stroke.
*/
ED_view3d_global_to_vector(rv3d, origin, plane_normal);
}
else if (axis < 3) {
plane_normal[axis] = 1.0f;
/* if object, apply object rotation */
if (ob && (ob->type == OB_GPENCIL)) {
float mat[4][4];
copy_m4_m4(mat, ob->obmat);
/* move origin to cursor */
if ((ts->gpencil_v3d_align & GP_PROJECT_CURSOR) == 0) {
if (gpl != NULL) {
add_v3_v3(mat[3], gpl->location);
}
}
if (ts->gpencil_v3d_align & GP_PROJECT_CURSOR) {
copy_v3_v3(mat[3], cursor->location);
}
mul_mat3_m4_v3(mat, plane_normal);
}
if ((gpl != NULL) && (ts->gp_sculpt.lock_axis != GP_LOCKAXIS_CURSOR)) {
mul_mat3_m4_v3(gpl->layer_mat, plane_normal);
}
}
else {
const float scale[3] = {1.0f, 1.0f, 1.0f};
plane_normal[2] = 1.0f;
float mat[4][4];
loc_eul_size_to_mat4(mat, cursor->location, cursor->rotation_euler, scale);
mul_mat3_m4_v3(mat, plane_normal);
}
/* Reproject the points in the plane */
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
/* get a vector from the point with the current view direction of the viewport */
ED_view3d_global_to_vector(rv3d, &pt->x, vn);
/* calculate line extreme point to create a ray that cross the plane */
mul_v3_fl(vn, -50.0f);
add_v3_v3v3(ray, &pt->x, vn);
/* if the line never intersect, the point is not changed */
if (isect_line_plane_v3(rpoint, &pt->x, ray, origin, plane_normal)) {
copy_v3_v3(&pt->x, rpoint);
}
}
}
/* Reproject selected strokes */
void ED_gpencil_stroke_reproject(Depsgraph *depsgraph,
const GP_SpaceConversion *gsc,
SnapObjectContext *sctx,
bGPDlayer *gpl,
bGPDframe *gpf,
bGPDstroke *gps,
const eGP_ReprojectModes mode,
const bool keep_original)
{
ToolSettings *ts = gsc->scene->toolsettings;
ARegion *region = gsc->region;
RegionView3D *rv3d = region->regiondata;
/* Recalculate layer transform matrix. */
loc_eul_size_to_mat4(gpl->layer_mat, gpl->location, gpl->rotation, gpl->scale);
invert_m4_m4(gpl->layer_invmat, gpl->layer_mat);
float diff_mat[4][4], inverse_diff_mat[4][4];
BKE_gpencil_layer_transform_matrix_get(depsgraph, gsc->ob, gpl, diff_mat);
invert_m4_m4(inverse_diff_mat, diff_mat);
float origin[3];
if (mode != GP_REPROJECT_CURSOR) {
ED_gpencil_drawing_reference_get(gsc->scene, gsc->ob, ts->gpencil_v3d_align, origin);
}
else {
copy_v3_v3(origin, gsc->scene->cursor.location);
}
bGPDspoint *pt;
int i;
/* If keep original, do a copy. */
bGPDstroke *gps_active = gps;
/* if duplicate, deselect all points. */
if (keep_original) {
gps_active = BKE_gpencil_stroke_duplicate(gps, true, true);
gps_active->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps_active);
for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) {
pt->flag &= ~GP_SPOINT_SELECT;
}
/* Add to frame. */
BLI_addtail(&gpf->strokes, gps_active);
}
/* Adjust each point */
for (i = 0, pt = gps_active->points; i < gps_active->totpoints; i++, pt++) {
float xy[2];
/* 3D to Screen-space */
/* NOTE: We can't use gpencil_point_to_xy() here because that uses ints for the screen-space
* coordinates, resulting in lost precision, which in turn causes stair-stepping
* artifacts in the final points. */
bGPDspoint pt2;
gpencil_point_to_parent_space(pt, diff_mat, &pt2);
gpencil_point_to_xy_fl(gsc, gps_active, &pt2, &xy[0], &xy[1]);
/* Project stroke in one axis */
if (ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, GP_REPROJECT_TOP, GP_REPROJECT_CURSOR)) {
int axis = 0;
switch (mode) {
case GP_REPROJECT_FRONT: {
axis = 1;
break;
}
case GP_REPROJECT_SIDE: {
axis = 0;
break;
}
case GP_REPROJECT_TOP: {
axis = 2;
break;
}
case GP_REPROJECT_CURSOR: {
axis = 3;
break;
}
default: {
axis = 1;
break;
}
}
ED_gpencil_project_point_to_plane(gsc->scene, gsc->ob, gpl, rv3d, origin, axis, &pt2);
copy_v3_v3(&pt->x, &pt2.x);
/* apply parent again */
gpencil_apply_parent_point(depsgraph, gsc->ob, gpl, pt);
}
/* Project screen-space back to 3D space (from current perspective)
* so that all points have been treated the same way. */
else if (mode == GP_REPROJECT_VIEW) {
/* Planar - All on same plane parallel to the view-plane. */
gpencil_point_xy_to_3d(gsc, gsc->scene, xy, &pt->x);
}
else {
/* Geometry - Snap to surfaces of visible geometry */
float ray_start[3];
float ray_normal[3];
/* magic value for initial depth copied from the default
* value of Python's Scene.ray_cast function
*/
float depth = 1.70141e+38f;
float location[3] = {0.0f, 0.0f, 0.0f};
float normal[3] = {0.0f, 0.0f, 0.0f};
BLI_assert(gps->flag & GP_STROKE_3DSPACE);
BLI_assert(gsc->area && gsc->area->spacetype == SPACE_VIEW3D);
const View3D *v3d = gsc->area->spacedata.first;
ED_view3d_win_to_ray_clipped(
depsgraph, region, v3d, xy, &ray_start[0], &ray_normal[0], true);
if (ED_transform_snap_object_project_ray(sctx,
depsgraph,
v3d,
&(const struct SnapObjectParams){
.snap_select = SNAP_ALL,
},
&ray_start[0],
&ray_normal[0],
&depth,
&location[0],
&normal[0])) {
copy_v3_v3(&pt->x, location);
}
else {
/* Default to planar */
gpencil_point_xy_to_3d(gsc, gsc->scene, xy, &pt->x);
}
}
/* Unapply parent corrections */
if (!ELEM(mode, GP_REPROJECT_FRONT, GP_REPROJECT_SIDE, GP_REPROJECT_TOP)) {
mul_m4_v3(inverse_diff_mat, &pt->x);
}
}
}
/**
* Reproject given point to a plane locked to axis to avoid stroke offset
* \param pt: Point to affect (used for input & output).
*/
void ED_gpencil_project_point_to_plane(const Scene *scene,
const Object *ob,
bGPDlayer *gpl,
const RegionView3D *rv3d,
const float origin[3],
const int axis,
bGPDspoint *pt)
{
const ToolSettings *ts = scene->toolsettings;
const View3DCursor *cursor = &scene->cursor;
float plane_normal[3];
float vn[3];
float ray[3];
float rpoint[3];
/* normal vector for a plane locked to axis */
zero_v3(plane_normal);
if (axis < 0) {
/* if the axis is not locked, need a vector to the view direction
* in order to get the right size of the stroke.
*/
ED_view3d_global_to_vector(rv3d, origin, plane_normal);
}
else if (axis < 3) {
plane_normal[axis] = 1.0f;
/* if object, apply object rotation */
if (ob && (ob->type == OB_GPENCIL)) {
float mat[4][4];
copy_m4_m4(mat, ob->obmat);
if ((ts->gpencil_v3d_align & GP_PROJECT_CURSOR) == 0) {
if (gpl != NULL) {
add_v3_v3(mat[3], gpl->location);
}
}
/* move origin to cursor */
if (ts->gpencil_v3d_align & GP_PROJECT_CURSOR) {
copy_v3_v3(mat[3], cursor->location);
}
mul_mat3_m4_v3(mat, plane_normal);
/* Apply layer rotation (local transform). */
if ((gpl != NULL) && (ts->gp_sculpt.lock_axis != GP_LOCKAXIS_CURSOR)) {
mul_mat3_m4_v3(gpl->layer_mat, plane_normal);
}
}
}
else {
const float scale[3] = {1.0f, 1.0f, 1.0f};
plane_normal[2] = 1.0f;
float mat[4][4];
loc_eul_size_to_mat4(mat, cursor->location, cursor->rotation_euler, scale);
/* move origin to object */
if ((ts->gpencil_v3d_align & GP_PROJECT_CURSOR) == 0) {
copy_v3_v3(mat[3], ob->obmat[3]);
}
mul_mat3_m4_v3(mat, plane_normal);
}
/* Reproject the points in the plane */
/* get a vector from the point with the current view direction of the viewport */
ED_view3d_global_to_vector(rv3d, &pt->x, vn);
/* calculate line extreme point to create a ray that cross the plane */
mul_v3_fl(vn, -50.0f);
add_v3_v3v3(ray, &pt->x, vn);
/* if the line never intersect, the point is not changed */
if (isect_line_plane_v3(rpoint, &pt->x, ray, origin, plane_normal)) {
copy_v3_v3(&pt->x, rpoint);
}
}
/* ******************************************************** */
/* Stroke Operations */
/* XXX: Check if these functions duplicate stuff in blenkernel,
* and/or whether we should just deduplicate. */
/**
* Subdivide a stroke once, by adding a point half way between each pair of existing points
* \param gpd: Datablock
* \param gps: Stroke data
* \param subdivide: Number of times to subdivide
*/
void gpencil_subdivide_stroke(bGPdata *gpd, bGPDstroke *gps, const int subdivide)
{
bGPDspoint *temp_points;
int totnewpoints, oldtotpoints;
int i2;
/* loop as many times as levels */
for (int s = 0; s < subdivide; s++) {
totnewpoints = gps->totpoints - 1;
/* duplicate points in a temp area */
temp_points = MEM_dupallocN(gps->points);
oldtotpoints = gps->totpoints;
/* resize the points arrays */
gps->totpoints += totnewpoints;
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);
}
/* move points from last to first to new place */
i2 = gps->totpoints - 1;
for (int i = oldtotpoints - 1; i > 0; i--) {
bGPDspoint *pt = &temp_points[i];
bGPDspoint *pt_final = &gps->points[i2];
copy_v3_v3(&pt_final->x, &pt->x);
pt_final->pressure = pt->pressure;
pt_final->strength = pt->strength;
pt_final->time = pt->time;
pt_final->flag = pt->flag;
pt_final->uv_fac = pt->uv_fac;
pt_final->uv_rot = pt->uv_rot;
copy_v4_v4(pt_final->vert_color, pt->vert_color);
if (gps->dvert != NULL) {
MDeformVert *dvert = &gps->dvert[i];
MDeformVert *dvert_final = &gps->dvert[i2];
dvert_final->totweight = dvert->totweight;
dvert_final->dw = dvert->dw;
}
i2 -= 2;
}
/* interpolate mid points */
i2 = 1;
for (int i = 0; i < oldtotpoints - 1; i++) {
bGPDspoint *pt = &temp_points[i];
bGPDspoint *next = &temp_points[i + 1];
bGPDspoint *pt_final = &gps->points[i2];
/* add a half way point */
interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
pt_final->pressure = interpf(pt->pressure, next->pressure, 0.5f);
pt_final->strength = interpf(pt->strength, next->strength, 0.5f);
CLAMP(pt_final->strength, GPENCIL_STRENGTH_MIN, 1.0f);
pt_final->time = interpf(pt->time, next->time, 0.5f);
pt_final->uv_fac = interpf(pt->uv_fac, next->uv_fac, 0.5f);
pt_final->uv_rot = interpf(pt->uv_rot, next->uv_rot, 0.5f);
interp_v4_v4v4(pt_final->vert_color, pt->vert_color, next->vert_color, 0.5f);
if (gps->dvert != NULL) {
MDeformVert *dvert_final = &gps->dvert[i2];
dvert_final->totweight = 0;
dvert_final->dw = NULL;
}
i2 += 2;
}
MEM_SAFE_FREE(temp_points);
/* move points to smooth stroke */
/* duplicate points in a temp area with the new subdivide data */
temp_points = MEM_dupallocN(gps->points);
/* extreme points are not changed */
for (int i = 0; i < gps->totpoints - 2; i++) {
bGPDspoint *pt = &temp_points[i];
bGPDspoint *next = &temp_points[i + 1];
bGPDspoint *pt_final = &gps->points[i + 1];
/* move point */
interp_v3_v3v3(&pt_final->x, &pt->x, &next->x, 0.5f);
}
/* free temp memory */
MEM_SAFE_FREE(temp_points);
}
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
/* Reset parent matrix for all layers. */
void ED_gpencil_reset_layers_parent(Depsgraph *depsgraph, Object *obact, bGPdata *gpd)
{
bGPDspoint *pt;
int i;
float diff_mat[4][4];
float cur_mat[4][4];
float gpl_loc[3];
zero_v3(gpl_loc);
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
if (gpl->parent != NULL) {
/* calculate new matrix */
if (ELEM(gpl->partype, PAROBJECT, PARSKEL)) {
invert_m4_m4(cur_mat, gpl->parent->obmat);
copy_v3_v3(gpl_loc, obact->obmat[3]);
}
else if (gpl->partype == PARBONE) {
bPoseChannel *pchan = BKE_pose_channel_find_name(gpl->parent->pose, gpl->parsubstr);
if (pchan) {
float tmp_mat[4][4];
mul_m4_m4m4(tmp_mat, gpl->parent->obmat, pchan->pose_mat);
invert_m4_m4(cur_mat, tmp_mat);
copy_v3_v3(gpl_loc, obact->obmat[3]);
}
}
/* only redo if any change */
if (!equals_m4m4(gpl->inverse, cur_mat)) {
/* first apply current transformation to all strokes */
BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat);
/* undo local object */
sub_v3_v3(diff_mat[3], gpl_loc);
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
mul_m4_v3(diff_mat, &pt->x);
}
}
}
/* set new parent matrix */
copy_m4_m4(gpl->inverse, cur_mat);
}
}
}
}
/* ******************************************************** */
/* GP Object Stuff */
/* Helper function to create new OB_GPENCIL Object */
Object *ED_gpencil_add_object(bContext *C, const float loc[3], ushort local_view_bits)
{
const float rot[3] = {0.0f};
Object *ob = ED_object_add_type(C, OB_GPENCIL, NULL, loc, rot, false, local_view_bits);
/* create default brushes and colors */
ED_gpencil_add_defaults(C, ob);
return ob;
}
/* Helper function to create default colors and drawing brushes */
void ED_gpencil_add_defaults(bContext *C, Object *ob)
{
Main *bmain = CTX_data_main(C);
ToolSettings *ts = CTX_data_tool_settings(C);
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
Paint *paint = &ts->gp_paint->paint;
/* if not exist, create a new one */
if ((paint->brush == NULL) || (paint->brush->gpencil_settings == NULL)) {
/* create new brushes */
BKE_brush_gpencil_paint_presets(bmain, ts, true);
}
/* ensure a color exists and is assigned to object */
BKE_gpencil_object_material_ensure_from_active_input_toolsettings(bmain, ob, ts);
/* ensure multiframe falloff curve */
if (ts->gp_sculpt.cur_falloff == NULL) {
ts->gp_sculpt.cur_falloff = BKE_curvemapping_add(1, 0.0f, 0.0f, 1.0f, 1.0f);
CurveMapping *gp_falloff_curve = ts->gp_sculpt.cur_falloff;
BKE_curvemapping_init(gp_falloff_curve);
BKE_curvemap_reset(gp_falloff_curve->cm,
&gp_falloff_curve->clipr,
CURVE_PRESET_GAUSS,
CURVEMAP_SLOPE_POSITIVE);
}
}
/* ******************************************************** */
/* Vertex Groups */
/* assign points to vertex group */
void ED_gpencil_vgroup_assign(bContext *C, Object *ob, float weight)
{
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const int def_nr = gpd->vertex_group_active_index - 1;
if (!BLI_findlink(&gpd->vertex_group_names, def_nr)) {
return;
}
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
bGPDstroke *gps = NULL;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (gpf == NULL) {
continue;
}
for (gps = gpf->strokes.first; gps; gps = gps->next) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
if (gps->flag & GP_STROKE_SELECT) {
/* verify the weight array is created */
BKE_gpencil_dvert_ensure(gps);
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
MDeformVert *dvert = &gps->dvert[i];
if (pt->flag & GP_SPOINT_SELECT) {
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, def_nr);
if (dw) {
dw->weight = weight;
}
}
}
}
}
}
/* If not multi-edit, exit loop. */
if (!is_multiedit) {
break;
}
}
}
CTX_DATA_END;
}
/* remove points from vertex group */
void ED_gpencil_vgroup_remove(bContext *C, Object *ob)
{
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const int def_nr = gpd->vertex_group_active_index - 1;
if (!BLI_findlink(&gpd->vertex_group_names, def_nr)) {
return;
}
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
bGPDstroke *gps = NULL;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (gpf == NULL) {
continue;
}
for (gps = gpf->strokes.first; gps; gps = gps->next) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
if (gps->dvert == NULL) {
continue;
}
MDeformVert *dvert = &gps->dvert[i];
if ((pt->flag & GP_SPOINT_SELECT) && (dvert->totweight > 0)) {
MDeformWeight *dw = BKE_defvert_find_index(dvert, def_nr);
if (dw != NULL) {
BKE_defvert_remove_group(dvert, dw);
}
}
}
}
}
/* If not multi-edit, exit loop. */
if (!is_multiedit) {
break;
}
}
}
CTX_DATA_END;
}
/* select points of vertex group */
void ED_gpencil_vgroup_select(bContext *C, Object *ob)
{
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const int def_nr = gpd->vertex_group_active_index - 1;
if (!BLI_findlink(&gpd->vertex_group_names, def_nr)) {
return;
}
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
bGPDstroke *gps = NULL;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (gpf == NULL) {
continue;
}
for (gps = gpf->strokes.first; gps; gps = gps->next) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
if (gps->dvert == NULL) {
continue;
}
MDeformVert *dvert = &gps->dvert[i];
if (BKE_defvert_find_index(dvert, def_nr) != NULL) {
pt->flag |= GP_SPOINT_SELECT;
gps->flag |= GP_STROKE_SELECT;
}
}
if (gps->flag & GP_STROKE_SELECT) {
BKE_gpencil_stroke_select_index_set(gpd, gps);
}
}
}
/* If not multi-edit, exit loop. */
if (!is_multiedit) {
break;
}
}
}
CTX_DATA_END;
}
/* unselect points of vertex group */
void ED_gpencil_vgroup_deselect(bContext *C, Object *ob)
{
bGPdata *gpd = (bGPdata *)ob->data;
const bool is_multiedit = (bool)GPENCIL_MULTIEDIT_SESSIONS_ON(gpd);
const int def_nr = gpd->vertex_group_active_index - 1;
if (!BLI_findlink(&gpd->vertex_group_names, def_nr)) {
return;
}
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
bGPDframe *init_gpf = (is_multiedit) ? gpl->frames.first : gpl->actframe;
bGPDstroke *gps = NULL;
for (bGPDframe *gpf = init_gpf; gpf; gpf = gpf->next) {
if ((gpf == gpl->actframe) || ((gpf->flag & GP_FRAME_SELECT) && (is_multiedit))) {
if (gpf == NULL) {
continue;
}
for (gps = gpf->strokes.first; gps; gps = gps->next) {
/* skip strokes that are invalid for current view */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
if (gps->dvert == NULL) {
continue;
}
MDeformVert *dvert = &gps->dvert[i];
if (BKE_defvert_find_index(dvert, def_nr) != NULL) {
pt->flag &= ~GP_SPOINT_SELECT;
}
}
}
}
/* If not multi-edit, exit loop. */
if (!is_multiedit) {
break;
}
}
}
CTX_DATA_END;
}
/* ******************************************************** */
/* Cursor drawing */
/* check if cursor is in drawing region */
static bool gpencil_check_cursor_region(bContext *C, const int mval_i[2])
{
ARegion *region = CTX_wm_region(C);
ScrArea *area = CTX_wm_area(C);
Object *ob = CTX_data_active_object(C);
if ((ob == NULL) || (!ELEM(ob->mode,
OB_MODE_PAINT_GPENCIL,
OB_MODE_SCULPT_GPENCIL,
OB_MODE_WEIGHT_GPENCIL,
OB_MODE_VERTEX_GPENCIL))) {
return false;
}
/* TODO: add more spacetypes */
if (!ELEM(area->spacetype, SPACE_VIEW3D)) {
return false;
}
if ((region) && (region->regiontype != RGN_TYPE_WINDOW)) {
return false;
}
if (region) {
return BLI_rcti_isect_pt_v(&region->winrct, mval_i);
}
return false;
}
/* draw eraser cursor */
void ED_gpencil_brush_draw_eraser(Brush *brush, int x, int y)
{
short radius = (short)brush->size;
GPUVertFormat *format = immVertexFormat();
const uint shdr_pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
immUniformColor4ub(255, 100, 100, 20);
imm_draw_circle_fill_2d(shdr_pos, x, y, radius, 40);
immUnbindProgram();
immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
immUniformColor4f(1.0f, 0.39f, 0.39f, 0.78f);
immUniform1i("colors_len", 0); /* "simple" mode */
immUniform1f("dash_width", 12.0f);
immUniform1f("dash_factor", 0.5f);
imm_draw_circle_wire_2d(shdr_pos,
x,
y,
radius,
/* XXX Dashed shader gives bad results with sets of small segments
* currently, temp hack around the issue. :( */
max_ii(8, radius / 2)); /* was fixed 40 */
immUnbindProgram();
GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
static bool gpencil_brush_cursor_poll(bContext *C)
{
if (WM_toolsystem_active_tool_is_brush(C)) {
return true;
}
return false;
}
/**
* Helper callback for drawing the cursor itself.
*/
static void gpencil_brush_cursor_draw(bContext *C, int x, int y, void *customdata)
{
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
ARegion *region = CTX_wm_region(C);
Paint *paint = BKE_paint_get_active_from_context(C);
bGPdata *gpd = ED_gpencil_data_get_active(C);
Brush *brush = NULL;
Material *ma = NULL;
MaterialGPencilStyle *gp_style = NULL;
float *last_mouse_position = customdata;
/* default radius and color */
float color[3] = {1.0f, 1.0f, 1.0f};
float darkcolor[3];
float radius = 3.0f;
int mval_i[2] = {x, y};
/* Check if cursor is in drawing region and has valid data-block. */
if ((!gpencil_check_cursor_region(C, mval_i)) || (gpd == NULL)) {
return;
}
/* for paint use paint brush size and color */
if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
brush = scene->toolsettings->gp_paint->paint.brush;
if ((brush == NULL) || (brush->gpencil_settings == NULL)) {
return;
}
/* while drawing hide */
if ((gpd->runtime.sbuffer_used > 0) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE) == 0) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) == 0)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
/* eraser has special shape and use a different shader program */
if (brush->gpencil_tool == GPAINT_TOOL_ERASE) {
ED_gpencil_brush_draw_eraser(brush, x, y);
return;
}
/* get current drawing color */
ma = BKE_gpencil_object_material_from_brush_get(ob, brush);
if (ma) {
gp_style = ma->gp_style;
/* after some testing, display the size of the brush is not practical because
* is too disruptive and the size of cursor does not change with zoom factor.
* The decision was to use a fix size, instead of brush->thickness value.
*/
if ((gp_style) && (GPENCIL_PAINT_MODE(gpd)) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE) == 0) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) == 0) &&
(brush->gpencil_tool == GPAINT_TOOL_DRAW)) {
radius = 2.0f;
copy_v3_v3(color, gp_style->stroke_rgba);
}
else {
/* Only Tint tool must show big cursor. */
if (brush->gpencil_tool == GPAINT_TOOL_TINT) {
radius = brush->size;
copy_v3_v3(color, brush->rgb);
}
else {
radius = 5.0f;
copy_v3_v3(color, brush->add_col);
}
}
}
}
/* Sculpt use sculpt brush size */
if (GPENCIL_SCULPT_MODE(gpd)) {
brush = scene->toolsettings->gp_sculptpaint->paint.brush;
if ((brush == NULL) || (brush->gpencil_settings == NULL)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
radius = brush->size;
if (brush->gpencil_settings->sculpt_flag &
(GP_SCULPT_FLAG_INVERT | GP_SCULPT_FLAG_TMP_INVERT)) {
copy_v3_v3(color, brush->sub_col);
}
else {
copy_v3_v3(color, brush->add_col);
}
}
/* Weight Paint */
if (GPENCIL_WEIGHT_MODE(gpd)) {
brush = scene->toolsettings->gp_weightpaint->paint.brush;
if ((brush == NULL) || (brush->gpencil_settings == NULL)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
radius = brush->size;
if (brush->gpencil_settings->sculpt_flag &
(GP_SCULPT_FLAG_INVERT | GP_SCULPT_FLAG_TMP_INVERT)) {
copy_v3_v3(color, brush->sub_col);
}
else {
copy_v3_v3(color, brush->add_col);
}
}
/* For Vertex Paint use brush size. */
if (GPENCIL_VERTEX_MODE(gpd)) {
brush = scene->toolsettings->gp_vertexpaint->paint.brush;
if ((brush == NULL) || (brush->gpencil_settings == NULL)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
radius = brush->size;
copy_v3_v3(color, brush->rgb);
}
/* draw icon */
GPUVertFormat *format = immVertexFormat();
uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
/* Inner Ring: Color from UI panel */
immUniformColor4f(color[0], color[1], color[2], 0.8f);
if ((gp_style) && (GPENCIL_PAINT_MODE(gpd)) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE) == 0) &&
((brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP) == 0) &&
(brush->gpencil_tool == GPAINT_TOOL_DRAW)) {
imm_draw_circle_fill_2d(pos, x, y, radius, 40);
}
else {
imm_draw_circle_wire_2d(pos, x, y, radius, 40);
}
/* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
mul_v3_v3fl(darkcolor, color, 0.40f);
immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
imm_draw_circle_wire_2d(pos, x, y, radius + 1, 40);
GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
/* Draw line for lazy mouse */
if ((last_mouse_position) && (brush->gpencil_settings->flag & GP_BRUSH_STABILIZE_MOUSE_TEMP)) {
GPU_line_smooth(true);
GPU_blend(GPU_BLEND_ALPHA);
copy_v3_v3(color, brush->add_col);
immUniformColor4f(color[0], color[1], color[2], 0.8f);
immBegin(GPU_PRIM_LINES, 2);
immVertex2f(pos, x, y);
immVertex2f(pos,
last_mouse_position[0] + region->winrct.xmin,
last_mouse_position[1] + region->winrct.ymin);
immEnd();
GPU_blend(GPU_BLEND_NONE);
GPU_line_smooth(false);
}
immUnbindProgram();
}
/* Turn brush cursor in on/off */
void ED_gpencil_toggle_brush_cursor(bContext *C, bool enable, void *customdata)
{
Scene *scene = CTX_data_scene(C);
GP_Sculpt_Settings *gset = &scene->toolsettings->gp_sculpt;
float *lastpost = customdata;
if (gset->paintcursor && !enable) {
/* clear cursor */
WM_paint_cursor_end(gset->paintcursor);
gset->paintcursor = NULL;
}
else if (enable) {
/* in some situations cursor could be duplicated, so it is better disable first if exist */
if (gset->paintcursor) {
/* clear cursor */
WM_paint_cursor_end(gset->paintcursor);
gset->paintcursor = NULL;
}
/* enable cursor */
gset->paintcursor = WM_paint_cursor_activate(SPACE_TYPE_ANY,
RGN_TYPE_ANY,
gpencil_brush_cursor_poll,
gpencil_brush_cursor_draw,
(lastpost) ? customdata : NULL);
}
}
/* set object modes */
void ED_gpencil_setup_modes(bContext *C, bGPdata *gpd, int newmode)
{
if (!gpd) {
return;
}
switch (newmode) {
case OB_MODE_EDIT_GPENCIL:
gpd->flag |= GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE;
gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, false, NULL);
break;
case OB_MODE_PAINT_GPENCIL:
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag |= GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE;
gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, true, NULL);
break;
case OB_MODE_SCULPT_GPENCIL:
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag |= GP_DATA_STROKE_SCULPTMODE;
gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE;
gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, true, NULL);
break;
case OB_MODE_WEIGHT_GPENCIL:
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
gpd->flag |= GP_DATA_STROKE_WEIGHTMODE;
gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, true, NULL);
break;
case OB_MODE_VERTEX_GPENCIL:
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE;
gpd->flag |= GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, true, NULL);
break;
default:
gpd->flag &= ~GP_DATA_STROKE_EDITMODE;
gpd->flag &= ~GP_DATA_STROKE_PAINTMODE;
gpd->flag &= ~GP_DATA_STROKE_SCULPTMODE;
gpd->flag &= ~GP_DATA_STROKE_WEIGHTMODE;
gpd->flag &= ~GP_DATA_STROKE_VERTEXMODE;
ED_gpencil_toggle_brush_cursor(C, false, NULL);
break;
}
}
/**
* Helper to convert 2d to 3d for simple drawing buffer.
*/
static void gpencil_stroke_convertcoords(ARegion *region,
const tGPspoint *point2D,
const float origin[3],
float out[3])
{
float mval_f[2] = {(float)point2D->x, (float)point2D->y};
float mval_prj[2];
float rvec[3], dvec[3];
float zfac;
copy_v3_v3(rvec, origin);
zfac = ED_view3d_calc_zfac(region->regiondata, rvec, NULL);
if (ED_view3d_project_float_global(region, rvec, mval_prj, V3D_PROJ_TEST_NOP) ==
V3D_PROJ_RET_OK) {
sub_v2_v2v2(mval_f, mval_prj, mval_f);
ED_view3d_win_to_delta(region, mval_f, dvec, zfac);
sub_v3_v3v3(out, rvec, dvec);
}
else {
zero_v3(out);
}
}
/**
* Convert 2d #tGPspoint to 3d #bGPDspoint.
*/
void ED_gpencil_tpoint_to_point(ARegion *region,
float origin[3],
const tGPspoint *tpt,
bGPDspoint *pt)
{
float p3d[3];
/* conversion to 3d format */
gpencil_stroke_convertcoords(region, tpt, origin, p3d);
copy_v3_v3(&pt->x, p3d);
zero_v4(pt->vert_color);
pt->pressure = tpt->pressure;
pt->strength = tpt->strength;
pt->uv_fac = tpt->uv_fac;
pt->uv_rot = tpt->uv_rot;
}
/**
* Recalculate UV for any stroke using the material.
*/
void ED_gpencil_update_color_uv(Main *bmain, Material *mat)
{
Material *gps_ma = NULL;
/* Read all strokes. */
for (Object *ob = bmain->objects.first; ob; ob = ob->id.next) {
if (ob->type == OB_GPENCIL) {
bGPdata *gpd = ob->data;
if (gpd == NULL) {
continue;
}
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
/* only editable and visible layers are considered */
if (BKE_gpencil_layer_is_editable(gpl)) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
/* check if it is editable */
if (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false) {
continue;
}
gps_ma = BKE_gpencil_material(ob, gps->mat_nr + 1);
/* update */
if ((gps_ma) && (gps_ma == mat)) {
BKE_gpencil_stroke_uv_update(gps);
}
}
}
}
}
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
}
}
static bool gpencil_check_collision(bGPDstroke *gps,
bGPDstroke **gps_array,
GHash *all_2d,
int totstrokes,
const float p2d_a1[2],
const float p2d_a2[2],
float r_hit[2])
{
bool hit = false;
/* check segment with all segments of all strokes */
for (int s = 0; s < totstrokes; s++) {
bGPDstroke *gps_iter = gps_array[s];
if (gps_iter->totpoints < 2) {
continue;
}
/* get stroke 2d version */
float(*points2d)[2] = BLI_ghash_lookup(all_2d, gps_iter);
for (int i2 = 0; i2 < gps_iter->totpoints - 1; i2++) {
float p2d_b1[2], p2d_b2[2];
copy_v2_v2(p2d_b1, points2d[i2]);
copy_v2_v2(p2d_b2, points2d[i2 + 1]);
/* don't self check */
if (gps == gps_iter) {
if (equals_v2v2(p2d_a1, p2d_b1) || equals_v2v2(p2d_a1, p2d_b2)) {
continue;
}
if (equals_v2v2(p2d_a2, p2d_b1) || equals_v2v2(p2d_a2, p2d_b2)) {
continue;
}
}
/* check collision */
int check = isect_seg_seg_v2_point(p2d_a1, p2d_a2, p2d_b1, p2d_b2, r_hit);
if (check > 0) {
hit = true;
break;
}
}
if (hit) {
break;
}
}
if (!hit) {
zero_v2(r_hit);
}
return hit;
}
static void gpencil_copy_points(
bGPDstroke *gps, bGPDspoint *pt, bGPDspoint *pt_final, int i, int i2)
{
/* don't copy same point */
if (i == i2) {
return;
}
copy_v3_v3(&pt_final->x, &pt->x);
pt_final->pressure = pt->pressure;
pt_final->strength = pt->strength;
pt_final->time = pt->time;
pt_final->flag = pt->flag;
pt_final->uv_fac = pt->uv_fac;
pt_final->uv_rot = pt->uv_rot;
copy_v4_v4(pt_final->vert_color, pt->vert_color);
if (gps->dvert != NULL) {
MDeformVert *dvert = &gps->dvert[i];
MDeformVert *dvert_final = &gps->dvert[i2];
MEM_SAFE_FREE(dvert_final->dw);
dvert_final->totweight = dvert->totweight;
if (dvert->dw == NULL) {
dvert_final->dw = NULL;
dvert_final->totweight = 0;
}
else {
dvert_final->dw = MEM_dupallocN(dvert->dw);
}
}
}
static void gpencil_insert_point(bGPdata *gpd,
bGPDstroke *gps,
bGPDspoint *a_pt,
bGPDspoint *b_pt,
const float co_a[3],
const float co_b[3])
{
bGPDspoint *temp_points;
int totnewpoints, oldtotpoints;
totnewpoints = gps->totpoints;
if (a_pt) {
totnewpoints++;
}
if (b_pt) {
totnewpoints++;
}
/* duplicate points in a temp area */
temp_points = MEM_dupallocN(gps->points);
oldtotpoints = gps->totpoints;
/* look index of base points because memory is changed when resize points array */
int a_idx = -1;
int b_idx = -1;
for (int i = 0; i < oldtotpoints; i++) {
bGPDspoint *pt = &gps->points[i];
if (pt == a_pt) {
a_idx = i;
}
if (pt == b_pt) {
b_idx = i;
}
}
/* resize the points arrays */
gps->totpoints = totnewpoints;
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);
}
/* copy all points */
int i2 = 0;
for (int i = 0; i < oldtotpoints; i++) {
bGPDspoint *pt = &temp_points[i];
bGPDspoint *pt_final = &gps->points[i2];
gpencil_copy_points(gps, pt, pt_final, i, i2);
/* create new point duplicating point and copy location */
if (ELEM(i, a_idx, b_idx)) {
i2++;
pt_final = &gps->points[i2];
gpencil_copy_points(gps, pt, pt_final, i, i2);
copy_v3_v3(&pt_final->x, (i == a_idx) ? co_a : co_b);
/* Un-select. */
pt_final->flag &= ~GP_SPOINT_SELECT;
/* tag to avoid more checking with this point */
pt_final->flag |= GP_SPOINT_TAG;
}
i2++;
}
/* Calc geometry data. */
BKE_gpencil_stroke_geometry_update(gpd, gps);
MEM_SAFE_FREE(temp_points);
}
static float gpencil_calc_factor(const float p2d_a1[2],
const float p2d_a2[2],
const float r_hit2d[2])
{
float dist1 = len_squared_v2v2(p2d_a1, p2d_a2);
float dist2 = len_squared_v2v2(p2d_a1, r_hit2d);
float f = dist1 > 0.0f ? dist2 / dist1 : 0.0f;
/* apply a correction factor */
float v1[2];
interp_v2_v2v2(v1, p2d_a1, p2d_a2, f);
float dist3 = len_squared_v2v2(p2d_a1, v1);
float f1 = dist1 > 0.0f ? dist3 / dist1 : 0.0f;
f = f + (f - f1);
return f;
}
/* extend selection to stroke intersections */
int ED_gpencil_select_stroke_segment(bGPdata *gpd,
bGPDlayer *gpl,
bGPDstroke *gps,
bGPDspoint *pt,
bool select,
bool insert,
const float scale,
float r_hita[3],
float r_hitb[3])
{
if (gps->totpoints < 2) {
return 0;
}
const float min_factor = 0.0015f;
bGPDspoint *pta1 = NULL;
bGPDspoint *pta2 = NULL;
float f = 0.0f;
int i2 = 0;
bGPDlayer *gpl_orig = (gpl->runtime.gpl_orig) ? gpl->runtime.gpl_orig : gpl;
bGPDframe *gpf = gpl_orig->actframe;
if (gpf == NULL) {
return 0;
}
int memsize = BLI_listbase_count(&gpf->strokes);
bGPDstroke **gps_array = MEM_callocN(sizeof(bGPDstroke *) * memsize, __func__);
/* save points */
bGPDspoint *oldpoints = MEM_dupallocN(gps->points);
/* Save list of strokes to check */
int totstrokes = 0;
LISTBASE_FOREACH (bGPDstroke *, gps_iter, &gpf->strokes) {
if (gps_iter->totpoints < 2) {
continue;
}
gps_array[totstrokes] = gps_iter;
totstrokes++;
}
if (totstrokes == 0) {
return 0;
}
/* look for index of the current point */
int cur_idx = -1;
for (int i = 0; i < gps->totpoints; i++) {
pta1 = &gps->points[i];
if (pta1 == pt) {
cur_idx = i;
break;
}
}
if (cur_idx < 0) {
return 0;
}
/* Convert all gps points to 2d and save in a hash to avoid recalculation. */
int direction = 0;
float(*points2d)[2] = MEM_mallocN(sizeof(*points2d) * gps->totpoints,
"GP Stroke temp 2d points");
BKE_gpencil_stroke_2d_flat_ref(
gps->points, gps->totpoints, gps->points, gps->totpoints, points2d, scale, &direction);
GHash *all_2d = BLI_ghash_ptr_new(__func__);
for (int s = 0; s < totstrokes; s++) {
bGPDstroke *gps_iter = gps_array[s];
float(*points2d_iter)[2] = MEM_mallocN(sizeof(*points2d_iter) * gps_iter->totpoints, __func__);
/* the extremes of the stroke are scaled to improve collision detection
* for near lines */
BKE_gpencil_stroke_2d_flat_ref(gps->points,
gps->totpoints,
gps_iter->points,
gps_iter->totpoints,
points2d_iter,
scale,
&direction);
BLI_ghash_insert(all_2d, gps_iter, points2d_iter);
}
bool hit_a = false;
bool hit_b = false;
float p2d_a1[2] = {0.0f, 0.0f};
float p2d_a2[2] = {0.0f, 0.0f};
float r_hit2d[2];
bGPDspoint *hit_pointa = NULL;
bGPDspoint *hit_pointb = NULL;
/* analyze points before current */
if (cur_idx > 0) {
for (int i = cur_idx; i >= 0; i--) {
pta1 = &gps->points[i];
copy_v2_v2(p2d_a1, points2d[i]);
i2 = i - 1;
CLAMP_MIN(i2, 0);
pta2 = &gps->points[i2];
copy_v2_v2(p2d_a2, points2d[i2]);
hit_a = gpencil_check_collision(gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d);
if (select) {
pta1->flag |= GP_SPOINT_SELECT;
}
else {
pta1->flag &= ~GP_SPOINT_SELECT;
}
if (hit_a) {
f = gpencil_calc_factor(p2d_a1, p2d_a2, r_hit2d);
interp_v3_v3v3(r_hita, &pta1->x, &pta2->x, f);
if (f > min_factor) {
hit_pointa = pta2; /* first point is second (inverted loop) */
}
else {
pta1->flag &= ~GP_SPOINT_SELECT;
}
break;
}
}
}
/* analyze points after current */
for (int i = cur_idx; i < gps->totpoints; i++) {
pta1 = &gps->points[i];
copy_v2_v2(p2d_a1, points2d[i]);
i2 = i + 1;
CLAMP_MAX(i2, gps->totpoints - 1);
pta2 = &gps->points[i2];
copy_v2_v2(p2d_a2, points2d[i2]);
hit_b = gpencil_check_collision(gps, gps_array, all_2d, totstrokes, p2d_a1, p2d_a2, r_hit2d);
if (select) {
pta1->flag |= GP_SPOINT_SELECT;
}
else {
pta1->flag &= ~GP_SPOINT_SELECT;
}
if (hit_b) {
f = gpencil_calc_factor(p2d_a1, p2d_a2, r_hit2d);
interp_v3_v3v3(r_hitb, &pta1->x, &pta2->x, f);
if (f > min_factor) {
hit_pointb = pta1;
}
else {
pta1->flag &= ~GP_SPOINT_SELECT;
}
break;
}
}
/* insert new point in the collision points */
if (insert) {
gpencil_insert_point(gpd, gps, hit_pointa, hit_pointb, r_hita, r_hitb);
}
/* free memory */
if (all_2d) {
GHashIterator gh_iter;
GHASH_ITER (gh_iter, all_2d) {
float(*p2d)[2] = BLI_ghashIterator_getValue(&gh_iter);
MEM_SAFE_FREE(p2d);
}
BLI_ghash_free(all_2d, NULL, NULL);
}
/* if no hit, reset selection flag */
if ((!hit_a) && (!hit_b)) {
for (int i = 0; i < gps->totpoints; i++) {
pta1 = &gps->points[i];
pta2 = &oldpoints[i];
pta1->flag = pta2->flag;
}
}
MEM_SAFE_FREE(points2d);
MEM_SAFE_FREE(gps_array);
MEM_SAFE_FREE(oldpoints);
/* return type of hit */
if ((hit_a) && (hit_b)) {
return 3;
}
if (hit_a) {
return 1;
}
if (hit_b) {
return 2;
}
return 0;
}
void ED_gpencil_select_toggle_all(bContext *C, int action)
{
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = ob->data;
/* for "toggle", test for existing selected strokes */
if (action == SEL_TOGGLE) {
action = SEL_SELECT;
CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
if (gps->flag & GP_STROKE_SELECT) {
action = SEL_DESELECT;
break; /* XXX: this only gets out of the inner loop. */
}
}
CTX_DATA_END;
}
/* if deselecting, we need to deselect strokes across all frames
* - Currently, an exception is only given for deselection
* Selecting and toggling should only affect what's visible,
* while deselecting helps clean up unintended/forgotten
* stuff on other frames
*/
if (action == SEL_DESELECT) {
/* deselect strokes across editable layers
* NOTE: we limit ourselves to editable layers, since once a layer is "locked/hidden
* nothing should be able to touch it
*/
/* Set selection index to 0. */
gpd->select_last_index = 0;
CTX_DATA_BEGIN (C, bGPDlayer *, gpl, editable_gpencil_layers) {
/* deselect all strokes on all frames */
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
bGPDstroke *gps;
for (gps = gpf->strokes.first; gps; gps = gps->next) {
bGPDspoint *pt;
int i;
/* only edit strokes that are valid in this view... */
if (ED_gpencil_stroke_can_use(C, gps)) {
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
pt->flag &= ~GP_SPOINT_SELECT;
}
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
}
}
}
}
CTX_DATA_END;
}
else {
/* select or deselect all strokes */
CTX_DATA_BEGIN (C, bGPDstroke *, gps, editable_gpencil_strokes) {
bGPDspoint *pt;
int i;
bool selected = false;
/* Change selection status of all points, then make the stroke match */
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
switch (action) {
case SEL_SELECT:
pt->flag |= GP_SPOINT_SELECT;
break;
#if 0
case SEL_DESELECT:
pt->flag &= ~GP_SPOINT_SELECT;
break;
#endif
case SEL_INVERT:
pt->flag ^= GP_SPOINT_SELECT;
break;
}
if (pt->flag & GP_SPOINT_SELECT) {
selected = true;
}
}
/* Change status of stroke */
if (selected) {
gps->flag |= GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_set(gpd, gps);
}
else {
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
}
}
CTX_DATA_END;
}
}
void ED_gpencil_select_curve_toggle_all(bContext *C, int action)
{
/* if toggle, check if we need to select or deselect */
if (action == SEL_TOGGLE) {
action = SEL_SELECT;
GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc)
{
if (gpc->flag & GP_CURVE_SELECT) {
action = SEL_DESELECT;
}
}
GP_EDITABLE_CURVES_END(gps_iter);
}
if (action == SEL_DESELECT) {
/* Set selection index to 0. */
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = ob->data;
gpd->select_last_index = 0;
GP_EDITABLE_CURVES_BEGIN(gps_iter, C, gpl, gps, gpc)
{
for (int i = 0; i < gpc->tot_curve_points; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
BezTriple *bezt = &gpc_pt->bezt;
gpc_pt->flag &= ~GP_CURVE_POINT_SELECT;
BEZT_DESEL_ALL(bezt);
}
gpc->flag &= ~GP_CURVE_SELECT;
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
}
GP_EDITABLE_CURVES_END(gps_iter);
}
else {
GP_EDITABLE_STROKES_BEGIN (gps_iter, C, gpl, gps) {
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = ob->data;
bool selected = false;
/* Make sure stroke has an editcurve */
if (gps->editcurve == NULL) {
BKE_gpencil_stroke_editcurve_update(gpd, gpl, gps);
gps->flag |= GP_STROKE_NEEDS_CURVE_UPDATE;
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
bGPDcurve *gpc = gps->editcurve;
for (int i = 0; i < gpc->tot_curve_points; i++) {
bGPDcurve_point *gpc_pt = &gpc->curve_points[i];
BezTriple *bezt = &gpc_pt->bezt;
switch (action) {
case SEL_SELECT:
gpc_pt->flag |= GP_CURVE_POINT_SELECT;
BEZT_SEL_ALL(bezt);
break;
case SEL_INVERT:
gpc_pt->flag ^= GP_CURVE_POINT_SELECT;
BEZT_SEL_INVERT(bezt);
break;
default:
break;
}
if (gpc_pt->flag & GP_CURVE_POINT_SELECT) {
selected = true;
}
}
if (selected) {
gpc->flag |= GP_CURVE_SELECT;
gps->flag |= GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_set(gpd, gps);
}
else {
gpc->flag &= ~GP_CURVE_SELECT;
gps->flag &= ~GP_STROKE_SELECT;
BKE_gpencil_stroke_select_index_reset(gps);
}
}
GP_EDITABLE_STROKES_END(gps_iter);
}
}
/**
* Ensure the #tGPspoint buffer (while drawing stroke)
* size is enough to save all points of the stroke.
*/
tGPspoint *ED_gpencil_sbuffer_ensure(tGPspoint *buffer_array,
int *buffer_size,
int *buffer_used,
const bool clear)
{
tGPspoint *p = NULL;
/* By default a buffer is created with one block with a predefined number of free points,
* if the size is not enough, the cache is reallocated adding a new block of free points.
* This is done in order to keep cache small and improve speed. */
if (*buffer_used + 1 > *buffer_size) {
if ((*buffer_size == 0) || (buffer_array == NULL)) {
p = MEM_callocN(sizeof(struct tGPspoint) * GP_STROKE_BUFFER_CHUNK, "GPencil Sbuffer");
*buffer_size = GP_STROKE_BUFFER_CHUNK;
}
else {
*buffer_size += GP_STROKE_BUFFER_CHUNK;
p = MEM_recallocN(buffer_array, sizeof(struct tGPspoint) * *buffer_size);
}
if (p == NULL) {
*buffer_size = *buffer_used = 0;
}
buffer_array = p;
}
/* clear old data */
if (clear) {
*buffer_used = 0;
if (buffer_array != NULL) {
memset(buffer_array, 0, sizeof(tGPspoint) * *buffer_size);
}
}
return buffer_array;
}
void ED_gpencil_sbuffer_update_eval(bGPdata *gpd, Object *ob_eval)
{
bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
gpd_eval->runtime.sbuffer = gpd->runtime.sbuffer;
gpd_eval->runtime.sbuffer_sflag = gpd->runtime.sbuffer_sflag;
gpd_eval->runtime.sbuffer_used = gpd->runtime.sbuffer_used;
gpd_eval->runtime.sbuffer_size = gpd->runtime.sbuffer_size;
gpd_eval->runtime.tot_cp_points = gpd->runtime.tot_cp_points;
gpd_eval->runtime.cp_points = gpd->runtime.cp_points;
}
/**
* Tag all scene grease pencil object to update.
*/
void ED_gpencil_tag_scene_gpencil(Scene *scene)
{
/* Mark all grease pencil data-blocks of the scene. */
FOREACH_SCENE_COLLECTION_BEGIN (scene, collection) {
FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) {
if (ob->type == OB_GPENCIL) {
bGPdata *gpd = (bGPdata *)ob->data;
gpd->flag |= GP_DATA_CACHE_IS_DIRTY;
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
}
FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
}
FOREACH_SCENE_COLLECTION_END;
DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE);
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL);
}
void ED_gpencil_fill_vertex_color_set(ToolSettings *ts, Brush *brush, bGPDstroke *gps)
{
const bool is_vertex = (GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode != GP_BRUSH_MODE_MATERIAL)) ||
(!GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode == GP_BRUSH_MODE_VERTEXCOLOR));
if (is_vertex) {
copy_v3_v3(gps->vert_color_fill, brush->rgb);
gps->vert_color_fill[3] = brush->gpencil_settings->vertex_factor;
srgb_to_linearrgb_v4(gps->vert_color_fill, gps->vert_color_fill);
}
else {
zero_v4(gps->vert_color_fill);
}
}
void ED_gpencil_point_vertex_color_set(ToolSettings *ts,
Brush *brush,
bGPDspoint *pt,
tGPspoint *tpt)
{
const bool is_vertex = (GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode != GP_BRUSH_MODE_MATERIAL)) ||
(!GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode == GP_BRUSH_MODE_VERTEXCOLOR));
if (is_vertex) {
if (tpt == NULL) {
copy_v3_v3(pt->vert_color, brush->rgb);
pt->vert_color[3] = brush->gpencil_settings->vertex_factor;
srgb_to_linearrgb_v4(pt->vert_color, pt->vert_color);
}
else {
copy_v3_v3(pt->vert_color, tpt->vert_color);
pt->vert_color[3] = brush->gpencil_settings->vertex_factor;
}
}
else {
zero_v4(pt->vert_color);
}
}
void ED_gpencil_init_random_settings(Brush *brush,
const int mval[2],
GpRandomSettings *random_settings)
{
int seed = ((uint)(ceil(PIL_check_seconds_timer())) + 1) % 128;
/* Use mouse position to get randomness. */
int ix = mval[0] * seed;
int iy = mval[1] * seed;
int iz = ix + iy * seed;
zero_v3(random_settings->hsv);
BrushGpencilSettings *brush_settings = brush->gpencil_settings;
/* Random to Hue. */
if (brush_settings->random_hue > 0.0f) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(ix, iy)) * 2.0f - 1.0f;
random_settings->hsv[0] = rand * brush_settings->random_hue * 0.5f;
}
/* Random to Saturation. */
if (brush_settings->random_saturation > 0.0f) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(iy, ix)) * 2.0f - 1.0f;
random_settings->hsv[1] = rand * brush_settings->random_saturation;
}
/* Random to Value. */
if (brush_settings->random_value > 0.0f) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(ix * iz, iy * iz)) * 2.0f - 1.0f;
random_settings->hsv[2] = rand * brush_settings->random_value;
}
/* Random to pressure. */
if (brush_settings->draw_random_press > 0.0f) {
random_settings->pressure = BLI_hash_int_01(BLI_hash_int_2d(ix + iz, iy + iz)) * 2.0f - 1.0f;
}
/* Random to color strength. */
if (brush_settings->draw_random_strength) {
random_settings->strength = BLI_hash_int_01(BLI_hash_int_2d(ix + iy, iy + iz + ix)) * 2.0f -
1.0f;
}
/* Random to uv texture rotation. */
if (brush_settings->uv_random > 0.0f) {
random_settings->uv = BLI_hash_int_01(BLI_hash_int_2d(iy + iz, ix * iz)) * 2.0f - 1.0f;
}
}
static void gpencil_sbuffer_vertex_color_random(
bGPdata *gpd, Brush *brush, tGPspoint *tpt, const float random_color[3], float pen_pressure)
{
BrushGpencilSettings *brush_settings = brush->gpencil_settings;
if (brush_settings->flag & GP_BRUSH_GROUP_RANDOM) {
int seed = ((uint)(ceil(PIL_check_seconds_timer())) + 1) % 128;
int ix = (int)(tpt->x * seed);
int iy = (int)(tpt->y * seed);
int iz = ix + iy * seed;
float hsv[3];
float factor_value[3];
zero_v3(factor_value);
/* Apply randomness to Hue. */
if (brush_settings->random_hue > 0.0f) {
if ((brush_settings->flag2 & GP_BRUSH_USE_HUE_AT_STROKE) == 0) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(ix, gpd->runtime.sbuffer_used)) * 2.0f - 1.0f;
factor_value[0] = rand * brush_settings->random_hue * 0.5f;
}
else {
factor_value[0] = random_color[0];
}
/* Apply random curve. */
if (brush_settings->flag2 & GP_BRUSH_USE_HUE_RAND_PRESS) {
factor_value[0] *= BKE_curvemapping_evaluateF(
brush_settings->curve_rand_hue, 0, pen_pressure);
}
}
/* Apply randomness to Saturation. */
if (brush_settings->random_saturation > 0.0f) {
if ((brush_settings->flag2 & GP_BRUSH_USE_SAT_AT_STROKE) == 0) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(iy, gpd->runtime.sbuffer_used)) * 2.0f - 1.0f;
factor_value[1] = rand * brush_settings->random_saturation;
}
else {
factor_value[1] = random_color[1];
}
/* Apply random curve. */
if (brush_settings->flag2 & GP_BRUSH_USE_SAT_RAND_PRESS) {
factor_value[1] *= BKE_curvemapping_evaluateF(
brush_settings->curve_rand_saturation, 0, pen_pressure);
}
}
/* Apply randomness to Value. */
if (brush_settings->random_value > 0.0f) {
if ((brush_settings->flag2 & GP_BRUSH_USE_VAL_AT_STROKE) == 0) {
float rand = BLI_hash_int_01(BLI_hash_int_2d(iz, gpd->runtime.sbuffer_used)) * 2.0f - 1.0f;
factor_value[2] = rand * brush_settings->random_value;
}
else {
factor_value[2] = random_color[2];
}
/* Apply random curve. */
if (brush_settings->flag2 & GP_BRUSH_USE_VAL_RAND_PRESS) {
factor_value[2] *= BKE_curvemapping_evaluateF(
brush_settings->curve_rand_value, 0, pen_pressure);
}
}
rgb_to_hsv_v(tpt->vert_color, hsv);
add_v3_v3(hsv, factor_value);
/* For Hue need to cover all range, but for Saturation and Value
* is not logic because the effect is too hard, so the value is just clamped. */
if (hsv[0] < 0.0f) {
hsv[0] += 1.0f;
}
else if (hsv[0] > 1.0f) {
hsv[0] -= 1.0f;
}
CLAMP3(hsv, 0.0f, 1.0f);
hsv_to_rgb_v(hsv, tpt->vert_color);
}
}
void ED_gpencil_sbuffer_vertex_color_set(Depsgraph *depsgraph,
Object *ob,
ToolSettings *ts,
Brush *brush,
Material *material,
float random_color[3],
float pen_pressure)
{
bGPdata *gpd = (bGPdata *)ob->data;
Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
MaterialGPencilStyle *gp_style = material->gp_style;
const bool is_vertex_fill =
(GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode != GP_BRUSH_MODE_MATERIAL)) ||
(!GPENCIL_USE_VERTEX_COLOR_FILL(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode == GP_BRUSH_MODE_VERTEXCOLOR));
const bool is_vertex_stroke =
(GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode != GP_BRUSH_MODE_MATERIAL)) ||
(!GPENCIL_USE_VERTEX_COLOR_STROKE(ts, brush) &&
(brush->gpencil_settings->brush_draw_mode == GP_BRUSH_MODE_VERTEXCOLOR));
int idx = gpd->runtime.sbuffer_used;
tGPspoint *tpt = (tGPspoint *)gpd->runtime.sbuffer + idx;
float vertex_color[4];
copy_v3_v3(vertex_color, brush->rgb);
vertex_color[3] = brush->gpencil_settings->vertex_factor;
srgb_to_linearrgb_v4(vertex_color, vertex_color);
/* Copy fill vertex color. */
if (is_vertex_fill) {
copy_v4_v4(gpd->runtime.vert_color_fill, vertex_color);
}
else {
copy_v4_v4(gpd->runtime.vert_color_fill, gp_style->fill_rgba);
}
/* Copy stroke vertex color. */
if (is_vertex_stroke) {
copy_v4_v4(tpt->vert_color, vertex_color);
}
else {
copy_v4_v4(tpt->vert_color, gp_style->stroke_rgba);
}
/* Random Color. */
gpencil_sbuffer_vertex_color_random(gpd, brush, tpt, random_color, pen_pressure);
/* Copy to evaluate data because paint operators don't tag refresh until end for speedup
* painting. */
if (gpd_eval != NULL) {
copy_v4_v4(gpd_eval->runtime.vert_color_fill, gpd->runtime.vert_color_fill);
gpd_eval->runtime.matid = gpd->runtime.matid;
}
}
/* Get the bigger 2D bound box points. */
void ED_gpencil_projected_2d_bound_box(const GP_SpaceConversion *gsc,
const bGPDstroke *gps,
const float diff_mat[4][4],
float r_min[2],
float r_max[2])
{
float bounds[8][2];
BoundBox bb;
BKE_boundbox_init_from_minmax(&bb, gps->boundbox_min, gps->boundbox_max);
/* Project 8 vertices in 2D. */
for (int i = 0; i < 8; i++) {
bGPDspoint pt_dummy, pt_dummy_ps;
copy_v3_v3(&pt_dummy.x, bb.vec[i]);
gpencil_point_to_parent_space(&pt_dummy, diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &bounds[i][0], &bounds[i][1]);
}
/* Take extremes. */
INIT_MINMAX2(r_min, r_max);
for (int i = 0; i < 8; i++) {
minmax_v2v2_v2(r_min, r_max, bounds[i]);
}
/* Ensure the bounding box is oriented to axis. */
if (r_max[0] < r_min[0]) {
SWAP(float, r_min[0], r_max[0]);
}
if (r_max[1] < r_min[1]) {
SWAP(float, r_min[1], r_max[1]);
}
}
/* Check if the stroke collides with brush. */
bool ED_gpencil_stroke_check_collision(const GP_SpaceConversion *gsc,
bGPDstroke *gps,
const float mouse[2],
const int radius,
const float diff_mat[4][4])
{
const int offset = (int)ceil(sqrt((radius * radius) * 2));
float boundbox_min[2];
float boundbox_max[2];
/* Check we have something to use (only for old files). */
if (is_zero_v3(gps->boundbox_min)) {
BKE_gpencil_stroke_boundingbox_calc(gps);
}
ED_gpencil_projected_2d_bound_box(gsc, gps, diff_mat, boundbox_min, boundbox_max);
rcti rect_stroke = {boundbox_min[0], boundbox_max[0], boundbox_min[1], boundbox_max[1]};
/* For mouse, add a small offset to avoid false negative in corners. */
rcti rect_mouse = {mouse[0] - offset, mouse[0] + offset, mouse[1] - offset, mouse[1] + offset};
/* Check collision between both rectangles. */
return BLI_rcti_isect(&rect_stroke, &rect_mouse, NULL);
}
/**
* Check if a point is inside of the stroke.
*
* \param gps: Stroke to check.
* \param gsc: Space conversion data.
* \param mouse: Mouse position.
* \param diff_mat: View matrix.
* \return True if the point is inside.
*/
bool ED_gpencil_stroke_point_is_inside(const bGPDstroke *gps,
const GP_SpaceConversion *gsc,
const int mouse[2],
const float diff_mat[4][4])
{
bool hit = false;
if (gps->totpoints == 0) {
return hit;
}
int(*mcoords)[2] = NULL;
int len = gps->totpoints;
mcoords = MEM_mallocN(sizeof(int[2]) * len, __func__);
/* Convert stroke to 2D array of points. */
const bGPDspoint *pt;
int i;
for (i = 0, pt = gps->points; i < gps->totpoints; i++, pt++) {
bGPDspoint pt2;
gpencil_point_to_parent_space(pt, diff_mat, &pt2);
gpencil_point_to_xy(gsc, gps, &pt2, &mcoords[i][0], &mcoords[i][1]);
}
/* Compute bound-box of lasso (for faster testing later). */
rcti rect;
BLI_lasso_boundbox(&rect, mcoords, len);
/* Test if point inside stroke. */
hit = ((!ELEM(V2D_IS_CLIPPED, mouse[0], mouse[1])) &&
BLI_rcti_isect_pt(&rect, mouse[0], mouse[1]) &&
BLI_lasso_is_point_inside(mcoords, len, mouse[0], mouse[1], INT_MAX));
/* Free memory. */
MEM_SAFE_FREE(mcoords);
return hit;
}
/* Get extremes of stroke in 2D using current view. */
void ED_gpencil_stroke_extremes_to2d(const GP_SpaceConversion *gsc,
const float diff_mat[4][4],
bGPDstroke *gps,
float r_ctrl1[2],
float r_ctrl2[2])
{
bGPDspoint pt_dummy_ps;
gpencil_point_to_parent_space(&gps->points[0], diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl1[0], &r_ctrl1[1]);
gpencil_point_to_parent_space(&gps->points[gps->totpoints - 1], diff_mat, &pt_dummy_ps);
gpencil_point_to_xy_fl(gsc, gps, &pt_dummy_ps, &r_ctrl2[0], &r_ctrl2[1]);
}
bGPDstroke *ED_gpencil_stroke_nearest_to_ends(bContext *C,
const GP_SpaceConversion *gsc,
bGPDlayer *gpl,
bGPDframe *gpf,
bGPDstroke *gps,
const float ctrl1[2],
const float ctrl2[2],
const float radius,
int *r_index)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
bGPDstroke *gps_rtn = NULL;
const float radius_sqr = radius * radius;
/* calculate difference matrix object */
float diff_mat[4][4];
BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
/* Calculate the extremes of the stroke in 2D. */
bGPDspoint pt_parent;
float pt2d_start[2], pt2d_end[2];
bGPDspoint *pt = &gps->points[0];
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_start[0], &pt2d_start[1]);
pt = &gps->points[gps->totpoints - 1];
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_end[0], &pt2d_end[1]);
/* Loop all strokes of the active frame. */
float dist_min = FLT_MAX;
LISTBASE_FOREACH (bGPDstroke *, gps_target, &gpf->strokes) {
/* Check if the color is editable. */
if ((gps_target == gps) || (ED_gpencil_stroke_material_editable(ob, gpl, gps) == false)) {
continue;
}
/* Check if one of the ends is inside target stroke bounding box. */
if ((!ED_gpencil_stroke_check_collision(gsc, gps_target, pt2d_start, radius, diff_mat)) &&
(!ED_gpencil_stroke_check_collision(gsc, gps_target, pt2d_end, radius, diff_mat))) {
continue;
}
/* Check the distance of the ends with the ends of target stroke to avoid middle contact.
* All is done in 2D plane. */
float pt2d_target_start[2], pt2d_target_end[2];
pt = &gps_target->points[0];
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_start[0], &pt2d_target_start[1]);
pt = &gps_target->points[gps_target->totpoints - 1];
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d_target_end[0], &pt2d_target_end[1]);
/* If the distance to the original stroke extremes is too big, the stroke must not be joined.
*/
if ((len_squared_v2v2(ctrl1, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(ctrl1, pt2d_target_end) > radius_sqr) &&
(len_squared_v2v2(ctrl2, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(ctrl2, pt2d_target_end) > radius_sqr)) {
continue;
}
if ((len_squared_v2v2(pt2d_start, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(pt2d_start, pt2d_target_end) > radius_sqr) &&
(len_squared_v2v2(pt2d_end, pt2d_target_start) > radius_sqr) &&
(len_squared_v2v2(pt2d_end, pt2d_target_end) > radius_sqr)) {
continue;
}
/* Loop all points and check what is the nearest point. */
int i;
for (i = 0, pt = gps_target->points; i < gps_target->totpoints; i++, pt++) {
/* Convert point to 2D. */
float pt2d[2];
gpencil_point_to_parent_space(pt, diff_mat, &pt_parent);
gpencil_point_to_xy_fl(gsc, gps, &pt_parent, &pt2d[0], &pt2d[1]);
/* Check with Start point. */
float dist = len_squared_v2v2(pt2d, pt2d_start);
if ((dist <= radius_sqr) && (dist < dist_min)) {
*r_index = i;
dist_min = dist;
gps_rtn = gps_target;
}
/* Check with End point. */
dist = len_squared_v2v2(pt2d, pt2d_end);
if ((dist <= radius_sqr) && (dist < dist_min)) {
*r_index = i;
dist_min = dist;
gps_rtn = gps_target;
}
}
}
return gps_rtn;
}
/* Join two stroke using a contact point index and trimming the rest. */
bGPDstroke *ED_gpencil_stroke_join_and_trim(
bGPdata *gpd, bGPDframe *gpf, bGPDstroke *gps, bGPDstroke *gps_dst, const int pt_index)
{
if ((gps->totpoints < 1) || (gps_dst->totpoints < 1)) {
return NULL;
}
BLI_assert(pt_index >= 0 && pt_index < gps_dst->totpoints);
bGPDspoint *pt = NULL;
/* Cannot be cyclic. */
gps->flag &= ~GP_STROKE_CYCLIC;
gps_dst->flag &= ~GP_STROKE_CYCLIC;
/* Trim stroke. */
bGPDstroke *gps_final = gps_dst;
if ((pt_index > 0) && (pt_index < gps_dst->totpoints - 2)) {
/* Untag any pending operation. */
gps_dst->flag &= ~GP_STROKE_TAG;
for (int i = 0; i < gps_dst->totpoints; i++) {
gps_dst->points[i].flag &= ~GP_SPOINT_TAG;
}
/* Delete points of the shorter extreme */
pt = &gps_dst->points[0];
float dist_to_start = BKE_gpencil_stroke_segment_length(gps_dst, 0, pt_index, true);
pt = &gps_dst->points[gps_dst->totpoints - 1];
float dist_to_end = BKE_gpencil_stroke_segment_length(
gps_dst, pt_index, gps_dst->totpoints - 1, true);
if (dist_to_start < dist_to_end) {
for (int i = 0; i < pt_index; i++) {
gps_dst->points[i].flag |= GP_SPOINT_TAG;
}
}
else {
for (int i = pt_index + 1; i < gps_dst->totpoints; i++) {
gps_dst->points[i].flag |= GP_SPOINT_TAG;
}
}
/* Remove tagged points to trim stroke. */
gps_final = BKE_gpencil_stroke_delete_tagged_points(
gpd, gpf, gps_dst, gps_dst->next, GP_SPOINT_TAG, false, 0);
}
/* Join both strokes. */
int totpoint = gps_final->totpoints;
BKE_gpencil_stroke_join(gps_final, gps, false, true, true);
/* Select the join points and merge if the distance is very small. */
pt = &gps_final->points[totpoint - 1];
pt->flag |= GP_SPOINT_SELECT;
pt = &gps_final->points[totpoint];
pt->flag |= GP_SPOINT_SELECT;
BKE_gpencil_stroke_merge_distance(gpd, gpf, gps_final, 0.01f, false);
/* Unselect all points. */
for (int i = 0; i < gps_final->totpoints; i++) {
gps_final->points[i].flag &= ~GP_SPOINT_SELECT;
}
/* Delete old stroke. */
BLI_remlink(&gpf->strokes, gps);
BKE_gpencil_free_stroke(gps);
return gps_final;
}
/* Close if the distance between extremes is below threshold. */
void ED_gpencil_stroke_close_by_distance(bGPDstroke *gps, const float threshold)
{
if (gps == NULL) {
return;
}
bGPDspoint *pt_start = &gps->points[0];
bGPDspoint *pt_end = &gps->points[gps->totpoints - 1];
const float threshold_sqr = threshold * threshold;
float dist_to_close = len_squared_v3v3(&pt_start->x, &pt_end->x);
if (dist_to_close < threshold_sqr) {
gps->flag |= GP_STROKE_CYCLIC;
BKE_gpencil_stroke_close(gps);
}
}
/* Merge two layers. */
void ED_gpencil_layer_merge(bGPdata *gpd,
bGPDlayer *gpl_src,
bGPDlayer *gpl_dst,
const bool reverse)
{
/* Collect frames of gpl_dst in hash table to avoid O(n^2) lookups. */
GHash *gh_frames_dst = BLI_ghash_int_new_ex(__func__, 64);
LISTBASE_FOREACH (bGPDframe *, gpf_dst, &gpl_dst->frames) {
BLI_ghash_insert(gh_frames_dst, POINTER_FROM_INT(gpf_dst->framenum), gpf_dst);
}
/* Read all frames from merge layer and add any missing in destination layer,
* copying all previous strokes to keep the image equals.
* Need to do it in a separated loop to avoid strokes accumulation. */
LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) {
/* Try to find frame in destination layer hash table. */
bGPDframe *gpf_dst = BLI_ghash_lookup(gh_frames_dst, POINTER_FROM_INT(gpf_src->framenum));
if (!gpf_dst) {
gpf_dst = BKE_gpencil_layer_frame_get(gpl_dst, gpf_src->framenum, GP_GETFRAME_ADD_COPY);
/* Use same frame type. */
gpf_dst->key_type = gpf_src->key_type;
BLI_ghash_insert(gh_frames_dst, POINTER_FROM_INT(gpf_src->framenum), gpf_dst);
}
}
/* Read all frames from merge layer and add strokes. */
LISTBASE_FOREACH (bGPDframe *, gpf_src, &gpl_src->frames) {
/* Try to find frame in destination layer hash table. */
bGPDframe *gpf_dst = BLI_ghash_lookup(gh_frames_dst, POINTER_FROM_INT(gpf_src->framenum));
/* Add to tail all strokes. */
if (gpf_dst) {
if (reverse) {
BLI_movelisttolist_reverse(&gpf_dst->strokes, &gpf_src->strokes);
}
else {
BLI_movelisttolist(&gpf_dst->strokes, &gpf_src->strokes);
}
}
}
/* Add Masks to destination layer. */
LISTBASE_FOREACH (bGPDlayer_Mask *, mask, &gpl_src->mask_layers) {
/* Don't add merged layers or missing layer names. */
if (!BKE_gpencil_layer_named_get(gpd, mask->name) || STREQ(mask->name, gpl_src->info) ||
STREQ(mask->name, gpl_dst->info)) {
continue;
}
if (!BKE_gpencil_layer_mask_named_get(gpl_dst, mask->name)) {
bGPDlayer_Mask *mask_new = MEM_dupallocN(mask);
BLI_addtail(&gpl_dst->mask_layers, mask_new);
gpl_dst->act_mask++;
}
}
/* Set destination layer as active. */
BKE_gpencil_layer_active_set(gpd, gpl_dst);
/* Now delete merged layer. */
BKE_gpencil_layer_delete(gpd, gpl_src);
BLI_ghash_free(gh_frames_dst, NULL, NULL);
/* Reorder masking. */
if (gpl_dst->mask_layers.first) {
BKE_gpencil_layer_mask_sort(gpd, gpl_dst);
}
}