Fixes many instances of `-Wstringop-overread` warning on GCC 11 Differential Revision: https://developer.blender.org/D13672
3307 lines
100 KiB
C
3307 lines
100 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... */
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
/* -------------------------------------------------------- */
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/* -------------------------------------------------------- */
|
|
|
|
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 */
|
|
|
|
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 */
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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... */
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
|
|
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 */
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
|
|
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 = ®ion->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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void gpencil_stroke_convertcoords_tpoint(Scene *scene,
|
|
ARegion *region,
|
|
Object *ob,
|
|
const tGPspoint *point2D,
|
|
float *depth,
|
|
float r_out[3])
|
|
{
|
|
ToolSettings *ts = scene->toolsettings;
|
|
|
|
if (depth && (*depth == DEPTH_INVALID)) {
|
|
depth = NULL;
|
|
}
|
|
|
|
int mval_i[2];
|
|
round_v2i_v2fl(mval_i, point2D->m_xy);
|
|
|
|
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] = {UNPACK2(point2D->m_xy)};
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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. */
|
|
|
|
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);
|
|
}
|
|
|
|
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 */
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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(®ion->winrct, mval_i);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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] = {UNPACK2(point2D->m_xy)};
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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->m_xy[0] * seed);
|
|
int iy = (int)(tpt->m_xy[1] * 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;
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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, 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|