Files
goo-engine/source/blender/editors/gpencil_legacy/gpencil_weight_paint.cc
T

1683 lines
52 KiB
C++

/* SPDX-FileCopyrightText: 2015 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgpencil
* Brush based operators for editing Grease Pencil strokes.
*/
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_kdtree.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLT_translation.h"
#include "DNA_armature_types.h"
#include "DNA_brush_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_gpencil_modifier_types.h"
#include "BKE_action.h"
#include "BKE_brush.hh"
#include "BKE_colortools.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil_legacy.h"
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_object_deform.h"
#include "BKE_report.h"
#include "DNA_meshdata_types.h"
#include "WM_api.h"
#include "WM_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_prototypes.h"
#include "UI_view2d.h"
#include "ED_gpencil_legacy.h"
#include "ED_screen.h"
#include "ED_view3d.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "gpencil_intern.h"
/* ************************************************ */
/* General Brush Editing Context */
#define GP_SELECT_BUFFER_CHUNK 256
#define GP_FIND_NEAREST_BUFFER_CHUNK 1024
#define GP_FIND_NEAREST_EPSILON 1e-6f
#define GP_STROKE_HASH_BITSHIFT 16
/* Grid of Colors for Smear. */
struct tGP_Grid {
/** Lower right corner of rectangle of grid cell. */
float bottom[2];
/** Upper left corner of rectangle of grid cell. */
float top[2];
/** Average Color */
float color[4];
/** Total points included. */
int totcol;
};
/* List of points affected by brush. */
struct tGP_Selected {
/** Referenced stroke. */
bGPDstroke *gps;
/** Point index in points array. */
int pt_index;
/** Position. */
int pc[2];
/** Color. */
float color[4];
/** Weight. */
float weight;
};
/* Context for brush operators */
struct tGP_BrushWeightpaintData {
Main *bmain;
Scene *scene;
Object *object;
ARegion *region;
/* Current GPencil datablock */
bGPdata *gpd;
Brush *brush;
/* Space Conversion Data */
GP_SpaceConversion gsc;
/* Is the brush currently painting? */
bool is_painting;
/* Start of new paint */
bool first;
/* Is multi-frame editing enabled, and are we using falloff for that? */
bool is_multiframe;
bool use_multiframe_falloff;
/* Draw tool: add or subtract? */
bool subtract;
/* Auto-normalize weights of bone-deformed vertices? */
bool auto_normalize;
/* Active vertex group */
int vrgroup;
/* Brush Runtime Data: */
/* - position and pressure
* - the *_prev variants are the previous values
*/
float mouse[2], mouse_prev[2];
float pressure;
/* - Brush direction. */
float brush_dir[2];
bool brush_dir_is_set;
/* - Multi-frame falloff factor. */
float mf_falloff;
/* Brush geometry (bounding box). */
rcti brush_rect;
/* Temp data to save selected points */
/** Stroke buffer. */
tGP_Selected *pbuffer;
/** Number of elements currently used in cache. */
int pbuffer_used;
/** Number of total elements available in cache. */
int pbuffer_size;
/** Average weight of elements in cache (used for average tool). */
float pbuffer_avg_weight;
/* Temp data for find-nearest-points, used by blur and smear tool. */
bool use_find_nearest;
/** Buffer of stroke points during one mouse swipe. */
tGP_Selected *fn_pbuffer;
/** Hash table of added points (to avoid duplicate entries). */
GHash *fn_added;
/** KDtree for finding nearest points. */
KDTree_2d *fn_kdtree;
/** Number of points used in find-nearest set. */
uint fn_used;
/** Number of points available in find-nearest set. */
uint fn_size;
/** Flag for balancing kdtree. */
bool fn_do_balance;
/* Temp data for auto-normalize weights used by deforming bones. */
/** Boolean array of locked vertex groups. */
bool *vgroup_locked;
/** Boolean array of vertex groups deformed by bones. */
bool *vgroup_bone_deformed;
/** Number of vertex groups in object. */
int vgroup_tot;
};
/* Ensure the buffer to hold temp selected point size is enough to save all points selected. */
static void gpencil_select_buffer_ensure(tGP_BrushWeightpaintData *gso, const bool clear)
{
/* By default a buffer is created with one block with a predefined number of free slots,
* if the size is not enough, the cache is reallocated adding a new block of free slots.
* This is done in order to keep cache small and improve speed. */
if ((gso->pbuffer_used + 1) > gso->pbuffer_size) {
if ((gso->pbuffer_size == 0) || (gso->pbuffer == nullptr)) {
gso->pbuffer = static_cast<tGP_Selected *>(
MEM_callocN(sizeof(tGP_Selected) * GP_SELECT_BUFFER_CHUNK, __func__));
gso->pbuffer_size = GP_SELECT_BUFFER_CHUNK;
}
else {
gso->pbuffer_size += GP_SELECT_BUFFER_CHUNK;
gso->pbuffer = static_cast<tGP_Selected *>(
MEM_recallocN(gso->pbuffer, sizeof(tGP_Selected) * gso->pbuffer_size));
}
}
/* Clear old data. */
if (clear) {
gso->pbuffer_used = 0;
if (gso->pbuffer != nullptr) {
memset(gso->pbuffer, 0, sizeof(tGP_Selected) * gso->pbuffer_size);
}
}
/* Create or enlarge buffer for find-nearest-points. */
if (gso->use_find_nearest && ((gso->fn_used + 1) > gso->fn_size)) {
gso->fn_size += GP_FIND_NEAREST_BUFFER_CHUNK;
/* Stroke point buffer. */
if (gso->fn_pbuffer == nullptr) {
gso->fn_pbuffer = static_cast<tGP_Selected *>(
MEM_callocN(sizeof(tGP_Selected) * gso->fn_size, __func__));
}
else {
gso->fn_pbuffer = static_cast<tGP_Selected *>(
MEM_recallocN(gso->fn_pbuffer, sizeof(tGP_Selected) * gso->fn_size));
}
/* Stroke point hash table (for duplicate checking.) */
if (gso->fn_added == nullptr) {
gso->fn_added = BLI_ghash_int_new("GP weight paint find nearest");
}
/* KDtree of stroke points. */
bool do_tree_rebuild = false;
if (gso->fn_kdtree != nullptr) {
BLI_kdtree_2d_free(gso->fn_kdtree);
do_tree_rebuild = true;
}
gso->fn_kdtree = BLI_kdtree_2d_new(gso->fn_size);
if (do_tree_rebuild) {
for (int i = 0; i < gso->fn_used; i++) {
float pc_f[2];
copy_v2fl_v2i(pc_f, gso->fn_pbuffer[i].pc);
BLI_kdtree_2d_insert(gso->fn_kdtree, i, pc_f);
}
}
}
}
/* Ensure a vertex group for the weight paint brush. */
static void gpencil_vertex_group_ensure(tGP_BrushWeightpaintData *gso)
{
if (gso->vrgroup >= 0) {
return;
}
if (gso->object == nullptr) {
return;
}
DEG_relations_tag_update(gso->bmain);
gso->vrgroup = 0;
Object *ob_armature = BKE_modifiers_is_deformed_by_armature(gso->object);
if (ob_armature == nullptr) {
BKE_object_defgroup_add(gso->object);
return;
}
Bone *actbone = ((bArmature *)ob_armature->data)->act_bone;
if (actbone == nullptr) {
return;
}
bPoseChannel *pchan = BKE_pose_channel_find_name(ob_armature->pose, actbone->name);
if (pchan == nullptr) {
return;
}
bDeformGroup *dg = BKE_object_defgroup_find_name(gso->object, pchan->name);
if (dg == nullptr) {
BKE_object_defgroup_add_name(gso->object, pchan->name);
}
}
static void gpencil_select_buffer_avg_weight_set(tGP_BrushWeightpaintData *gso)
{
if (gso->pbuffer_used == 0) {
gso->pbuffer_avg_weight = 0.0f;
return;
}
float sum = 0;
for (int i = 0; i < gso->pbuffer_used; i++) {
tGP_Selected *selected = &gso->pbuffer[i];
sum += selected->weight;
}
gso->pbuffer_avg_weight = sum / gso->pbuffer_used;
CLAMP(gso->pbuffer_avg_weight, 0.0f, 1.0f);
}
/* Get boolean array of vertex groups deformed by GP armature modifiers.
* GP equivalent of `BKE_object_defgroup_validmap_get`.
*/
static bool *gpencil_vgroup_bone_deformed_map_get(Object *ob, const int defbase_tot)
{
bDeformGroup *dg;
bool *vgroup_bone_deformed;
GHash *gh;
int i;
const ListBase *defbase = BKE_object_defgroup_list(ob);
if (BLI_listbase_is_empty(defbase)) {
return nullptr;
}
/* Add all vertex group names to a hash table. */
gh = BLI_ghash_str_new_ex(__func__, defbase_tot);
LISTBASE_FOREACH (bDeformGroup *, dg, defbase) {
BLI_ghash_insert(gh, dg->name, nullptr);
}
BLI_assert(BLI_ghash_len(gh) == defbase_tot);
/* Now loop through the armature modifiers and identify deform bones. */
LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
if (md->type != eGpencilModifierType_Armature) {
continue;
}
ArmatureGpencilModifierData *amd = (ArmatureGpencilModifierData *)md;
if (amd->object && amd->object->pose) {
bPose *pose = amd->object->pose;
LISTBASE_FOREACH (bPoseChannel *, chan, &pose->chanbase) {
void **val_p;
if (chan->bone->flag & BONE_NO_DEFORM) {
continue;
}
val_p = BLI_ghash_lookup_p(gh, chan->name);
if (val_p) {
*val_p = POINTER_FROM_INT(1);
}
}
}
}
/* Mark vertex groups with reference in the bone hash table. */
vgroup_bone_deformed = static_cast<bool *>(
MEM_mallocN(sizeof(*vgroup_bone_deformed) * defbase_tot, __func__));
for (dg = static_cast<bDeformGroup *>(defbase->first), i = 0; dg; dg = dg->next, i++) {
vgroup_bone_deformed[i] = (BLI_ghash_lookup(gh, dg->name) != nullptr);
}
BLI_assert(i == BLI_ghash_len(gh));
BLI_ghash_free(gh, nullptr, nullptr);
return vgroup_bone_deformed;
}
/* ************************************************ */
/* Auto-normalize Operations
* This section defines the functions for auto-normalizing the sum of weights to 1.0
* for points in vertex groups deformed by bones.
* The logic is copied from `editors/sculpt_paint/paint_vertex.cc`. */
static bool do_weight_paint_normalize(MDeformVert *dvert,
const int defbase_tot,
const bool *vgroup_bone_deformed,
const bool *vgroup_locked,
const bool lock_active_vgroup,
const int active_vgroup)
{
float sum = 0.0f, fac;
float sum_unlock = 0.0f;
float sum_lock = 0.0f;
uint lock_tot = 0, unlock_tot = 0;
MDeformWeight *dw;
if (dvert->totweight <= 1) {
return true;
}
dw = dvert->dw;
for (int i = dvert->totweight; i != 0; i--, dw++) {
/* Auto-normalize is only applied on bone-deformed vertex groups that have weight already. */
if (dw->def_nr < defbase_tot && vgroup_bone_deformed[dw->def_nr] && dw->weight > FLT_EPSILON) {
sum += dw->weight;
if (vgroup_locked[dw->def_nr] || (lock_active_vgroup && active_vgroup == dw->def_nr)) {
lock_tot++;
sum_lock += dw->weight;
}
else {
unlock_tot++;
sum_unlock += dw->weight;
}
}
}
if (sum == 1.0f) {
return true;
}
if (unlock_tot == 0) {
/* There are no unlocked vertex groups to normalize. We don't need
* a second pass when there is only one locked group (the active group). */
return (lock_tot == 1);
}
if (sum_lock >= 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
/* Locked groups make it impossible to fully normalize,
* zero out what we can and return false. */
dw = dvert->dw;
for (int i = dvert->totweight; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_bone_deformed[dw->def_nr]) {
if ((vgroup_locked[dw->def_nr] == false) &&
!(lock_active_vgroup && active_vgroup == dw->def_nr)) {
dw->weight = 0.0f;
}
}
}
return (sum_lock == 1.0f);
}
if (sum_unlock != 0.0f) {
fac = (1.0f - sum_lock) / sum_unlock;
dw = dvert->dw;
for (int i = dvert->totweight; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_bone_deformed[dw->def_nr] && dw->weight > FLT_EPSILON)
{
if ((vgroup_locked[dw->def_nr] == false) &&
!(lock_active_vgroup && active_vgroup == dw->def_nr)) {
dw->weight *= fac;
CLAMP(dw->weight, 0.0f, 1.0f);
}
}
}
}
else {
fac = (1.0f - sum_lock) / unlock_tot;
CLAMP(fac, 0.0f, 1.0f);
dw = dvert->dw;
for (int i = dvert->totweight; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_bone_deformed[dw->def_nr] && dw->weight > FLT_EPSILON)
{
if ((vgroup_locked[dw->def_nr] == false) &&
!(lock_active_vgroup && active_vgroup == dw->def_nr)) {
dw->weight = fac;
}
}
}
}
return true;
}
/**
* A version of #do_weight_paint_normalize that only changes unlocked weights
* and does a second pass without the active vertex group locked when the first pass fails.
*/
static void do_weight_paint_normalize_try(MDeformVert *dvert, tGP_BrushWeightpaintData *gso)
{
/* First pass with both active and explicitly locked vertex groups restricted from change. */
bool succes = do_weight_paint_normalize(
dvert, gso->vgroup_tot, gso->vgroup_bone_deformed, gso->vgroup_locked, true, gso->vrgroup);
if (!succes) {
/* Second pass with active vertex group unlocked. */
do_weight_paint_normalize(
dvert, gso->vgroup_tot, gso->vgroup_bone_deformed, gso->vgroup_locked, false, -1);
}
}
/* Brush Operations ------------------------------- */
/* Compute strength of effect. */
static float brush_influence_calc(tGP_BrushWeightpaintData *gso, const int radius, const int co[2])
{
Brush *brush = gso->brush;
/* basic strength factor from brush settings */
float influence = brush->alpha;
/* use pressure? */
if (brush->gpencil_settings->flag & GP_BRUSH_USE_PRESSURE) {
influence *= gso->pressure;
}
/* distance fading */
int mouse_i[2];
round_v2i_v2fl(mouse_i, gso->mouse);
float distance = float(len_v2v2_int(mouse_i, co));
/* Apply Brush curve. */
float brush_falloff = BKE_brush_curve_strength(brush, distance, float(radius));
influence *= brush_falloff;
/* apply multi-frame falloff */
influence *= gso->mf_falloff;
/* return influence */
return influence;
}
/* Compute effect vector for directional brushes. */
static void brush_calc_brush_dir_2d(tGP_BrushWeightpaintData *gso)
{
sub_v2_v2v2(gso->brush_dir, gso->mouse, gso->mouse_prev);
/* Skip tiny changes in direction, we want the bigger movements only. */
if (len_squared_v2(gso->brush_dir) < 9.0f) {
return;
}
normalize_v2(gso->brush_dir);
gso->brush_dir_is_set = true;
copy_v2_v2(gso->mouse_prev, gso->mouse);
}
/* ************************************************ */
/* Brush Callbacks
* This section defines the callbacks used by each brush to perform their magic.
* These are called on each point within the brush's radius. */
/* Draw Brush */
static bool brush_draw_apply(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
int pt_index,
const int radius,
const int co[2])
{
MDeformVert *dvert = gps->dvert + pt_index;
/* Compute strength of effect. */
float inf = brush_influence_calc(gso, radius, co);
/* Get current weight. */
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup);
if (dw == nullptr) {
return false;
}
/* Apply brush weight. */
float bweight = (gso->subtract) ? -gso->brush->weight : gso->brush->weight;
dw->weight = interpf(bweight, dw->weight, inf);
CLAMP(dw->weight, 0.0f, 1.0f);
/* Perform auto-normalize. */
if (gso->auto_normalize) {
do_weight_paint_normalize_try(dvert, gso);
}
return true;
}
/* Average Brush */
static bool brush_average_apply(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
int pt_index,
const int radius,
const int co[2])
{
MDeformVert *dvert = gps->dvert + pt_index;
/* Compute strength of effect. */
float inf = brush_influence_calc(gso, radius, co);
/* Get current weight. */
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup);
if (dw == nullptr) {
return false;
}
/* Blend weight with average weight under the brush. */
dw->weight = interpf(gso->pbuffer_avg_weight, dw->weight, inf);
CLAMP(dw->weight, 0.0f, 1.0f);
/* Perform auto-normalize. */
if (gso->auto_normalize) {
do_weight_paint_normalize_try(dvert, gso);
}
return true;
}
/* Blur Brush */
static bool brush_blur_apply(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
int pt_index,
const int radius,
const int co[2])
{
MDeformVert *dvert = gps->dvert + pt_index;
/* Compute strength of effect. */
float inf = brush_influence_calc(gso, radius, co);
/* Get current weight. */
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup);
if (dw == nullptr) {
return false;
}
/* Find the 5 nearest points (this includes the to-be-blurred point itself). */
KDTreeNearest_2d nearest[5];
float pc_f[2];
copy_v2fl_v2i(pc_f, co);
const int tot = BLI_kdtree_2d_find_nearest_n(gso->fn_kdtree, pc_f, nearest, 5);
/* Calculate the average (=blurred) weight. */
float blur_weight = 0.0f, dist_sum = 0.0f;
int count = 0;
for (int i = 0; i < tot; i++) {
dist_sum += nearest[i].dist;
count++;
}
if (count <= 1) {
return false;
}
for (int i = 0; i < tot; i++) {
/* Weighted average, based on distance to point. */
blur_weight += (1.0f - nearest[i].dist / dist_sum) * gso->fn_pbuffer[nearest[i].index].weight;
}
blur_weight /= (count - 1);
/* Blend weight with blurred weight. */
dw->weight = interpf(blur_weight, dw->weight, inf);
CLAMP(dw->weight, 0.0f, 1.0f);
/* Perform auto-normalize. */
if (gso->auto_normalize) {
do_weight_paint_normalize_try(dvert, gso);
}
return true;
}
/* Smear Brush */
static bool brush_smear_apply(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
int pt_index,
const int radius,
const int co[2])
{
MDeformVert *dvert = gps->dvert + pt_index;
/* Get current weight. */
MDeformWeight *dw = BKE_defvert_ensure_index(dvert, gso->vrgroup);
if (dw == nullptr) {
return false;
}
/* Find the 8 nearest points (this includes the to-be-blurred point itself). */
KDTreeNearest_2d nearest[8];
float pc_f[2];
copy_v2fl_v2i(pc_f, co);
const int tot = BLI_kdtree_2d_find_nearest_n(gso->fn_kdtree, pc_f, nearest, 8);
/* For smearing a weight from point A to point B, we look for a point A 'behind' the brush,
* matching the brush angle best and with the shortest distance to B. */
float point_dot[8] = {0};
float point_dir[2];
float score_max = 0.0f, dist_min = FLT_MAX, dist_max = 0.0f;
int i_max = -1, count = 0;
for (int i = 0; i < tot; i++) {
/* Skip the point we are about to smear. */
if (nearest[i].dist > GP_FIND_NEAREST_EPSILON) {
sub_v2_v2v2(point_dir, pc_f, nearest[i].co);
normalize_v2(point_dir);
/* Match A-B direction with brush direction. */
point_dot[i] = dot_v2v2(point_dir, gso->brush_dir);
if (point_dot[i] > 0.0f) {
count++;
float dist = nearest[i].dist;
if (dist < dist_min) {
dist_min = dist;
}
if (dist > dist_max) {
dist_max = dist;
}
}
}
}
if (count == 0) {
return false;
}
/* Find best match in angle and distance. */
float dist_f = (dist_min == dist_max) ? 1.0f : 0.95f / (dist_max - dist_min);
for (int i = 0; i < tot; i++) {
if (point_dot[i] > 0.0f) {
float score = point_dot[i] * (1.0f - (nearest[i].dist - dist_min) * dist_f);
if (score > score_max) {
score_max = score;
i_max = i;
}
}
}
if (i_max == -1) {
return false;
}
/* Compute strength of effect. */
float inf = brush_influence_calc(gso, radius, co);
/* Smear the weight. */
dw->weight = interpf(gso->fn_pbuffer[nearest[i_max].index].weight, dw->weight, inf);
CLAMP(dw->weight, 0.0f, 1.0f);
/* Perform auto-normalize. */
if (gso->auto_normalize) {
do_weight_paint_normalize_try(dvert, gso);
}
return true;
}
/* ************************************************ */
/* Header Info */
static void gpencil_weightpaint_brush_header_set(bContext *C, tGP_BrushWeightpaintData *gso)
{
switch (gso->brush->gpencil_weight_tool) {
case GPWEIGHT_TOOL_DRAW:
ED_workspace_status_text(C, TIP_("GPencil Weight Paint: LMB to paint | RMB/Escape to Exit"));
break;
case GPWEIGHT_TOOL_BLUR:
ED_workspace_status_text(C, TIP_("GPencil Weight Blur: LMB to blur | RMB/Escape to Exit"));
break;
case GPWEIGHT_TOOL_AVERAGE:
ED_workspace_status_text(
C, TIP_("GPencil Weight Average: LMB to set average | RMB/Escape to Exit"));
break;
case GPWEIGHT_TOOL_SMEAR:
ED_workspace_status_text(C, TIP_("GPencil Weight Smear: LMB to smear | RMB/Escape to Exit"));
break;
}
}
/* ************************************************ */
/* Grease Pencil Weight Paint Operator */
/* Init/Exit ----------------------------------------------- */
static bool gpencil_weightpaint_brush_init(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
Object *ob = CTX_data_active_object(C);
Paint *paint = &ts->gp_weightpaint->paint;
/* set the brush using the tool */
tGP_BrushWeightpaintData *gso;
/* setup operator data */
gso = static_cast<tGP_BrushWeightpaintData *>(
MEM_callocN(sizeof(tGP_BrushWeightpaintData), "tGP_BrushWeightpaintData"));
op->customdata = gso;
gso->bmain = CTX_data_main(C);
gso->brush = paint->brush;
BKE_curvemapping_init(gso->brush->curve);
gso->is_painting = false;
gso->first = true;
gso->pbuffer = nullptr;
gso->pbuffer_size = 0;
gso->pbuffer_used = 0;
gso->fn_pbuffer = nullptr;
gso->fn_added = nullptr;
gso->fn_kdtree = nullptr;
gso->fn_used = 0;
gso->fn_size = 0;
gso->use_find_nearest = ELEM(
gso->brush->gpencil_weight_tool, GPWEIGHT_TOOL_BLUR, GPWEIGHT_TOOL_SMEAR);
gso->gpd = ED_gpencil_data_get_active(C);
gso->scene = scene;
gso->object = ob;
if (ob) {
gso->vrgroup = gso->gpd->vertex_group_active_index - 1;
if (!BLI_findlink(&gso->gpd->vertex_group_names, gso->vrgroup)) {
gso->vrgroup = -1;
}
}
else {
gso->vrgroup = -1;
}
gso->region = CTX_wm_region(C);
/* Multi-frame settings. */
gso->is_multiframe = bool(GPENCIL_MULTIEDIT_SESSIONS_ON(gso->gpd));
gso->use_multiframe_falloff = (ts->gp_sculpt.flag & GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
/* Init multi-edit falloff curve data before doing anything,
* so we won't have to do it again later. */
if (gso->is_multiframe) {
BKE_curvemapping_init(ts->gp_sculpt.cur_falloff);
}
/* Draw tool: add or subtract weight? */
gso->subtract = (gso->brush->gpencil_settings->sculpt_flag & BRUSH_DIR_IN);
/* Setup auto-normalize. */
gso->auto_normalize = (ts->auto_normalize && gso->vrgroup != -1);
if (gso->auto_normalize) {
gso->vgroup_tot = BLI_listbase_count(&gso->gpd->vertex_group_names);
/* Get boolean array of vertex groups deformed by bones. */
gso->vgroup_bone_deformed = gpencil_vgroup_bone_deformed_map_get(ob, gso->vgroup_tot);
if (gso->vgroup_bone_deformed != nullptr) {
/* Get boolean array of locked vertex groups. */
gso->vgroup_locked = BKE_object_defgroup_lock_flags_get(ob, gso->vgroup_tot);
if (gso->vgroup_locked == nullptr) {
gso->vgroup_locked = (bool *)MEM_callocN(sizeof(bool) * gso->vgroup_tot, __func__);
}
}
else {
gso->auto_normalize = false;
}
}
/* Setup space conversions. */
gpencil_point_conversion_init(C, &gso->gsc);
/* Update header. */
gpencil_weightpaint_brush_header_set(C, gso);
return true;
}
static void gpencil_weightpaint_brush_exit(bContext *C, wmOperator *op)
{
tGP_BrushWeightpaintData *gso = static_cast<tGP_BrushWeightpaintData *>(op->customdata);
/* Clear status bar text. */
ED_workspace_status_text(C, nullptr);
/* Free operator data */
MEM_SAFE_FREE(gso->vgroup_bone_deformed);
MEM_SAFE_FREE(gso->vgroup_locked);
if (gso->fn_kdtree != nullptr) {
BLI_kdtree_2d_free(gso->fn_kdtree);
}
if (gso->fn_added != nullptr) {
BLI_ghash_free(gso->fn_added, nullptr, nullptr);
}
MEM_SAFE_FREE(gso->fn_pbuffer);
MEM_SAFE_FREE(gso->pbuffer);
MEM_SAFE_FREE(gso);
op->customdata = nullptr;
}
/* Poll callback for stroke weight paint operator. */
static bool gpencil_weightpaint_brush_poll(bContext *C)
{
if (!ED_operator_regionactive(C)) {
CTX_wm_operator_poll_msg_set(C, "Active region not set");
return false;
}
ScrArea *area = CTX_wm_area(C);
if (area->spacetype != SPACE_VIEW3D) {
return false;
}
bGPdata *gpd = ED_gpencil_data_get_active(C);
if ((gpd == nullptr) || !GPENCIL_WEIGHT_MODE(gpd)) {
return false;
}
ToolSettings *ts = CTX_data_scene(C)->toolsettings;
if (!ts->gp_weightpaint->paint.brush) {
CTX_wm_operator_poll_msg_set(C, "Grease Pencil has no active paint tool");
return false;
}
/* NOTE: this is a bit slower, but is the most accurate... */
return CTX_DATA_COUNT(C, editable_gpencil_strokes) != 0;
}
/* Helper to save the points selected by the brush. */
static void gpencil_save_selected_point(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
const int gps_index,
const int index,
const int pc[2],
const bool within_brush)
{
tGP_Selected *selected;
bGPDspoint *pt = &gps->points[index];
/* Ensure the array to save the list of selected points is big enough. */
gpencil_select_buffer_ensure(gso, false);
/* Copy point data. */
if (within_brush) {
selected = &gso->pbuffer[gso->pbuffer_used];
selected->gps = gps;
selected->pt_index = index;
copy_v2_v2_int(selected->pc, pc);
copy_v4_v4(selected->color, pt->vert_color);
gso->pbuffer_used++;
}
/* Ensure vertex group and dvert. */
gpencil_vertex_group_ensure(gso);
BKE_gpencil_dvert_ensure(gps);
/* Copy current weight. */
MDeformVert *dvert = gps->dvert + index;
MDeformWeight *dw = BKE_defvert_find_index(dvert, gso->vrgroup);
if (within_brush && (dw != nullptr)) {
selected->weight = dw->weight;
}
/* Store point for finding nearest points (blur, smear). */
if (gso->use_find_nearest) {
/* Create hash key, assuming there are no more than 65536 strokes in a frame
* and 65536 points in a stroke. */
const int point_hash = (gps_index << GP_STROKE_HASH_BITSHIFT) + index;
/* Prevent duplicate points in buffer. */
if (!BLI_ghash_haskey(gso->fn_added, POINTER_FROM_INT(point_hash))) {
/* Add stroke point to find-nearest buffer. */
selected = &gso->fn_pbuffer[gso->fn_used];
copy_v2_v2_int(selected->pc, pc);
selected->weight = (dw == nullptr) ? 0.0f : dw->weight;
BLI_ghash_insert(
gso->fn_added, POINTER_FROM_INT(point_hash), POINTER_FROM_INT(gso->fn_used));
float pc_f[2];
copy_v2_fl2(pc_f, float(pc[0]), float(pc[1]));
BLI_kdtree_2d_insert(gso->fn_kdtree, gso->fn_used, pc_f);
gso->fn_do_balance = true;
gso->fn_used++;
}
else {
/* Update weight of point in buffer. */
int *idx = static_cast<int *>(BLI_ghash_lookup(gso->fn_added, POINTER_FROM_INT(point_hash)));
selected = &gso->fn_pbuffer[POINTER_AS_INT(idx)];
selected->weight = (dw == nullptr) ? 0.0f : dw->weight;
}
}
}
/* Select points in this stroke and add to an array to be used later. */
static void gpencil_weightpaint_select_stroke(tGP_BrushWeightpaintData *gso,
bGPDstroke *gps,
const int gps_index,
const float diff_mat[4][4],
const float bound_mat[4][4])
{
GP_SpaceConversion *gsc = &gso->gsc;
rcti *rect = &gso->brush_rect;
Brush *brush = gso->brush;
/* For the blur tool, look a bit wider than the brush itself,
* because we need the weight of surrounding points to perform the blur. */
const bool widen_brush = (gso->brush->gpencil_weight_tool == GPWEIGHT_TOOL_BLUR);
int radius_brush = (brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
gso->brush->size;
int radius_wide = (widen_brush) ? radius_brush * 1.3f : radius_brush;
bool within_brush = true;
bGPDstroke *gps_active = (gps->runtime.gps_orig) ? gps->runtime.gps_orig : gps;
bGPDspoint *pt_active = nullptr;
bGPDspoint *pt1, *pt2;
bGPDspoint *pt = nullptr;
int pc1[2] = {0};
int pc2[2] = {0};
int i;
int index;
bool include_last = false;
/* Check if the stroke collides with brush. */
if (!ED_gpencil_stroke_check_collision(gsc, gps, gso->mouse, radius_wide, bound_mat)) {
return;
}
if (gps->totpoints == 1) {
bGPDspoint pt_temp;
pt = &gps->points[0];
gpencil_point_to_world_space(gps->points, diff_mat, &pt_temp);
gpencil_point_to_xy(gsc, gps, &pt_temp, &pc1[0], &pc1[1]);
pt_active = (pt->runtime.pt_orig) ? pt->runtime.pt_orig : pt;
/* Do bound-box check first. */
if (!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1]) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) {
/* only check if point is inside */
int mouse_i[2];
round_v2i_v2fl(mouse_i, gso->mouse);
int mlen = len_v2v2_int(mouse_i, pc1);
if (mlen <= radius_wide) {
/* apply operation to this point */
if (pt_active != nullptr) {
if (widen_brush) {
within_brush = (mlen <= radius_brush);
}
gpencil_save_selected_point(gso, gps_active, gps_index, 0, pc1, within_brush);
}
}
}
}
else {
/* Loop over the points in the stroke, checking for intersections
* - an intersection means that we touched the stroke
*/
for (i = 0; (i + 1) < gps->totpoints; i++) {
/* Get points to work with */
pt1 = gps->points + i;
pt2 = gps->points + i + 1;
bGPDspoint npt;
gpencil_point_to_world_space(pt1, diff_mat, &npt);
gpencil_point_to_xy(gsc, gps, &npt, &pc1[0], &pc1[1]);
gpencil_point_to_world_space(pt2, diff_mat, &npt);
gpencil_point_to_xy(gsc, gps, &npt, &pc2[0], &pc2[1]);
/* Check that point segment of the bound-box of the selection stroke */
if ((!ELEM(V2D_IS_CLIPPED, pc1[0], pc1[1]) && BLI_rcti_isect_pt(rect, pc1[0], pc1[1])) ||
(!ELEM(V2D_IS_CLIPPED, pc2[0], pc2[1]) && BLI_rcti_isect_pt(rect, pc2[0], pc2[1])))
{
/* Check if point segment of stroke had anything to do with
* brush region (either within stroke painted, or on its lines)
* - this assumes that line-width is irrelevant.
*/
if (gpencil_stroke_inside_circle(gso->mouse, radius_wide, pc1[0], pc1[1], pc2[0], pc2[1]))
{
if (widen_brush) {
within_brush = gpencil_stroke_inside_circle(
gso->mouse, radius_brush, pc1[0], pc1[1], pc2[0], pc2[1]);
}
/* To each point individually... */
pt = &gps->points[i];
pt_active = pt->runtime.pt_orig;
if (pt_active != nullptr) {
index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
gpencil_save_selected_point(gso, gps_active, gps_index, index, pc1, within_brush);
}
/* Only do the second point if this is the last segment,
* and it is unlikely that the point will get handled
* otherwise.
*
* NOTE: There is a small risk here that the second point wasn't really
* actually in-range. In that case, it only got in because
* the line linking the points was!
*/
if (i + 1 == gps->totpoints - 1) {
pt = &gps->points[i + 1];
pt_active = pt->runtime.pt_orig;
if (pt_active != nullptr) {
index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i + 1;
gpencil_save_selected_point(gso, gps_active, gps_index, index, pc2, within_brush);
include_last = false;
}
}
else {
include_last = true;
}
}
else if (include_last) {
/* This case is for cases where for whatever reason the second vert (1st here)
* doesn't get included because the whole edge isn't in bounds,
* but it would've qualified since it did with the previous step
* (but wasn't added then, to avoid double-ups).
*/
pt = &gps->points[i];
pt_active = pt->runtime.pt_orig;
if (pt_active != nullptr) {
index = (pt->runtime.pt_orig) ? pt->runtime.idx_orig : i;
gpencil_save_selected_point(gso, gps_active, gps_index, index, pc1, true);
include_last = false;
}
}
}
}
}
}
/* Apply weight paint brushes to strokes in the given frame. */
static bool gpencil_weightpaint_brush_do_frame(bContext *C,
tGP_BrushWeightpaintData *gso,
bGPDframe *gpf,
const float diff_mat[4][4],
const float bound_mat[4][4])
{
Object *ob = CTX_data_active_object(C);
char tool = gso->brush->gpencil_weight_tool;
const int radius = (gso->brush->flag & GP_BRUSH_USE_PRESSURE) ?
gso->brush->size * gso->pressure :
gso->brush->size;
tGP_Selected *selected = nullptr;
gso->fn_do_balance = false;
/*---------------------------------------------------------------------
* First step: select the points affected. This step is required to have
* all selected points before apply the effect, because it could be
* required to do some step. Now is not used, but the operator is ready.
*--------------------------------------------------------------------- */
int gps_index;
LISTBASE_FOREACH_INDEX (bGPDstroke *, gps, &gpf->strokes, gps_index) {
/* Skip strokes that are invalid for current view. */
if (ED_gpencil_stroke_can_use(C, gps) == false) {
continue;
}
/* Check if the color is visible. */
if (ED_gpencil_stroke_material_visible(ob, gps) == false) {
continue;
}
/* Check points below the brush. */
gpencil_weightpaint_select_stroke(gso, gps, gps_index, diff_mat, bound_mat);
}
bDeformGroup *defgroup = static_cast<bDeformGroup *>(
BLI_findlink(&gso->gpd->vertex_group_names, gso->vrgroup));
if ((defgroup == nullptr) || (defgroup->flag & DG_LOCK_WEIGHT)) {
return false;
}
/*---------------------------------------------------------------------
* Second step: Calculations on selected points.
*--------------------------------------------------------------------- */
/* For average tool, get average weight of affected points. */
if (tool == GPWEIGHT_TOOL_AVERAGE) {
gpencil_select_buffer_avg_weight_set(gso);
}
/* Balance find-nearest kdtree. */
if (gso->use_find_nearest && gso->fn_do_balance) {
BLI_kdtree_2d_balance(gso->fn_kdtree);
}
/*---------------------------------------------------------------------
* Third step: Apply effect.
*--------------------------------------------------------------------- */
bool changed = false;
for (int i = 0; i < gso->pbuffer_used; i++) {
selected = &gso->pbuffer[i];
switch (tool) {
case GPWEIGHT_TOOL_DRAW: {
changed |= brush_draw_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
break;
}
case GPWEIGHT_TOOL_AVERAGE: {
changed |= brush_average_apply(
gso, selected->gps, selected->pt_index, radius, selected->pc);
break;
}
case GPWEIGHT_TOOL_BLUR: {
changed |= brush_blur_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
break;
}
case GPWEIGHT_TOOL_SMEAR: {
changed |= brush_smear_apply(gso, selected->gps, selected->pt_index, radius, selected->pc);
break;
}
default:
printf("ERROR: Unknown type of GPencil Weight Paint brush\n");
break;
}
}
/* Clear the selected array, but keep the memory allocation. */
gpencil_select_buffer_ensure(gso, true);
return changed;
}
/* Apply brush effect to all layers. */
static bool gpencil_weightpaint_brush_apply_to_layers(bContext *C, tGP_BrushWeightpaintData *gso)
{
ToolSettings *ts = CTX_data_tool_settings(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *obact = gso->object;
bool changed = false;
Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &obact->id);
bGPdata *gpd = (bGPdata *)ob_eval->data;
/* Find visible strokes, and perform operations on those if hit */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
/* If locked or no active frame, don't do anything. */
if (!BKE_gpencil_layer_is_editable(gpl) || (gpl->actframe == nullptr)) {
continue;
}
/* Calculate transform matrix. */
float diff_mat[4][4], bound_mat[4][4];
BKE_gpencil_layer_transform_matrix_get(depsgraph, obact, gpl, diff_mat);
copy_m4_m4(bound_mat, diff_mat);
mul_m4_m4m4(diff_mat, diff_mat, gpl->layer_invmat);
/* Active Frame or MultiFrame? */
if (gso->is_multiframe) {
/* init multi-frame falloff options */
int f_init = 0;
int f_end = 0;
if (gso->use_multiframe_falloff) {
BKE_gpencil_frame_range_selected(gpl, &f_init, &f_end);
}
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
/* Always do active frame; Otherwise, only include selected frames */
if ((gpf == gpl->actframe) || (gpf->flag & GP_FRAME_SELECT)) {
/* Compute multi-frame falloff factor. */
if (gso->use_multiframe_falloff) {
/* Falloff depends on distance to active frame
* (relative to the overall frame range). */
gso->mf_falloff = BKE_gpencil_multiframe_falloff_calc(
gpf, gpl->actframe->framenum, f_init, f_end, ts->gp_sculpt.cur_falloff);
}
else {
/* No falloff */
gso->mf_falloff = 1.0f;
}
/* affect strokes in this frame */
changed |= gpencil_weightpaint_brush_do_frame(C, gso, gpf, diff_mat, bound_mat);
}
}
}
else {
/* Apply to active frame's strokes */
gso->mf_falloff = 1.0f;
changed |= gpencil_weightpaint_brush_do_frame(C, gso, gpl->actframe, diff_mat, bound_mat);
}
}
return changed;
}
/* Calculate settings for applying brush */
static void gpencil_weightpaint_brush_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
{
tGP_BrushWeightpaintData *gso = static_cast<tGP_BrushWeightpaintData *>(op->customdata);
Brush *brush = gso->brush;
const int radius = ((brush->flag & GP_BRUSH_USE_PRESSURE) ? gso->brush->size * gso->pressure :
gso->brush->size);
float mousef[2];
int mouse[2];
bool changed = false;
/* Get latest mouse coordinates */
RNA_float_get_array(itemptr, "mouse", mousef);
gso->mouse[0] = mouse[0] = int(mousef[0]);
gso->mouse[1] = mouse[1] = int(mousef[1]);
gso->pressure = RNA_float_get(itemptr, "pressure");
/* Store coordinates as reference, if operator just started running */
if (gso->first) {
gso->mouse_prev[0] = gso->mouse[0];
gso->mouse_prev[1] = gso->mouse[1];
gso->brush_dir_is_set = false;
}
gso->first = false;
/* Update brush_rect, so that it represents the bounding rectangle of brush. */
gso->brush_rect.xmin = mouse[0] - radius;
gso->brush_rect.ymin = mouse[1] - radius;
gso->brush_rect.xmax = mouse[0] + radius;
gso->brush_rect.ymax = mouse[1] + radius;
/* Calculate brush direction. */
if (gso->brush->gpencil_weight_tool == GPWEIGHT_TOOL_SMEAR) {
brush_calc_brush_dir_2d(gso);
if (!gso->brush_dir_is_set) {
return;
}
}
/* Apply brush to layers. */
changed = gpencil_weightpaint_brush_apply_to_layers(C, gso);
/* Updates. */
if (changed) {
DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, nullptr);
}
}
/* Running --------------------------------------------- */
/* helper - a record stroke, and apply paint event */
static void gpencil_weightpaint_brush_apply_event(bContext *C,
wmOperator *op,
const wmEvent *event)
{
tGP_BrushWeightpaintData *gso = static_cast<tGP_BrushWeightpaintData *>(op->customdata);
PointerRNA itemptr;
float mouse[2];
mouse[0] = event->mval[0] + 1;
mouse[1] = event->mval[1] + 1;
/* fill in stroke */
RNA_collection_add(op->ptr, "stroke", &itemptr);
RNA_float_set_array(&itemptr, "mouse", mouse);
RNA_boolean_set(&itemptr, "is_start", gso->first);
/* Handle pressure sensitivity (which is supplied by tablets). */
float pressure = event->tablet.pressure;
CLAMP(pressure, 0.0f, 1.0f);
RNA_float_set(&itemptr, "pressure", pressure);
/* apply */
gpencil_weightpaint_brush_apply(C, op, &itemptr);
}
/* reapply */
static int gpencil_weightpaint_brush_exec(bContext *C, wmOperator *op)
{
if (!gpencil_weightpaint_brush_init(C, op)) {
return OPERATOR_CANCELLED;
}
RNA_BEGIN (op->ptr, itemptr, "stroke") {
gpencil_weightpaint_brush_apply(C, op, &itemptr);
}
RNA_END;
gpencil_weightpaint_brush_exit(C, op);
return OPERATOR_FINISHED;
}
/* start modal painting */
static int gpencil_weightpaint_brush_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
tGP_BrushWeightpaintData *gso = nullptr;
const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
const bool is_playing = ED_screen_animation_playing(CTX_wm_manager(C)) != nullptr;
/* the operator cannot work while play animation */
if (is_playing) {
BKE_report(op->reports, RPT_ERROR, "Cannot Paint while play animation");
return OPERATOR_CANCELLED;
}
/* init painting data */
if (!gpencil_weightpaint_brush_init(C, op)) {
return OPERATOR_CANCELLED;
}
gso = static_cast<tGP_BrushWeightpaintData *>(op->customdata);
/* register modal handler */
WM_event_add_modal_handler(C, op);
/* start drawing immediately? */
if (is_modal == false) {
ARegion *region = CTX_wm_region(C);
/* apply first dab... */
gso->is_painting = true;
gpencil_weightpaint_brush_apply_event(C, op, event);
/* redraw view with feedback */
ED_region_tag_redraw(region);
}
return OPERATOR_RUNNING_MODAL;
}
/* painting - handle events. */
static int gpencil_weightpaint_brush_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
tGP_BrushWeightpaintData *gso = static_cast<tGP_BrushWeightpaintData *>(op->customdata);
const bool is_modal = RNA_boolean_get(op->ptr, "wait_for_input");
bool redraw_region = false;
bool redraw_toolsettings = false;
/* The operator can be in 2 states: Painting and Idling. */
if (gso->is_painting) {
/* Painting. */
switch (event->type) {
/* Mouse Move: Apply somewhere else. */
case MOUSEMOVE:
case INBETWEEN_MOUSEMOVE:
/* Apply brush effect at new position. */
gpencil_weightpaint_brush_apply_event(C, op, event);
/* Force redraw, so that the cursor will at least be valid. */
redraw_region = true;
break;
/* Painting mouse-button release: Stop painting (back to idle). */
case LEFTMOUSE:
if (is_modal) {
/* go back to idling... */
gso->is_painting = false;
}
else {
/* end painting, since we're not modal. */
gso->is_painting = false;
gpencil_weightpaint_brush_exit(C, op);
return OPERATOR_FINISHED;
}
break;
/* Abort painting if any of the usual things are tried. */
case MIDDLEMOUSE:
case RIGHTMOUSE:
case EVT_ESCKEY:
gpencil_weightpaint_brush_exit(C, op);
return OPERATOR_FINISHED;
}
}
else {
/* Idling. */
BLI_assert(is_modal == true);
switch (event->type) {
/* Painting mouse-button press = Start painting (switch to painting state). */
case LEFTMOUSE:
/* do initial "click" apply. */
gso->is_painting = true;
gso->first = true;
gpencil_weightpaint_brush_apply_event(C, op, event);
break;
/* Exit modal operator, based on the "standard" ops. */
case RIGHTMOUSE:
case EVT_ESCKEY:
gpencil_weightpaint_brush_exit(C, op);
return OPERATOR_FINISHED;
/* MMB is often used for view manipulations. */
case MIDDLEMOUSE:
return OPERATOR_PASS_THROUGH;
/* Mouse movements should update the brush cursor - Just redraw the active region. */
case MOUSEMOVE:
case INBETWEEN_MOUSEMOVE:
redraw_region = true;
break;
/* Change Frame - Allowed. */
case EVT_LEFTARROWKEY:
case EVT_RIGHTARROWKEY:
case EVT_UPARROWKEY:
case EVT_DOWNARROWKEY:
return OPERATOR_PASS_THROUGH;
/* Camera/View Gizmo's - Allowed. */
/* See rationale in `gpencil_paint.cc`, #gpencil_draw_modal(). */
case EVT_PAD0:
case EVT_PAD1:
case EVT_PAD2:
case EVT_PAD3:
case EVT_PAD4:
case EVT_PAD5:
case EVT_PAD6:
case EVT_PAD7:
case EVT_PAD8:
case EVT_PAD9:
return OPERATOR_PASS_THROUGH;
/* Unhandled event. */
default:
break;
}
}
/* Redraw region? */
if (redraw_region) {
ED_region_tag_redraw(CTX_wm_region(C));
}
/* Redraw toolsettings (brush settings)? */
if (redraw_toolsettings) {
DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_SCENE | ND_TOOLSETTINGS, nullptr);
}
return OPERATOR_RUNNING_MODAL;
}
void GPENCIL_OT_weight_paint(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Stroke Weight Paint";
ot->idname = "GPENCIL_OT_weight_paint";
ot->description = "Draw weight on stroke points";
/* api callbacks */
ot->exec = gpencil_weightpaint_brush_exec;
ot->invoke = gpencil_weightpaint_brush_invoke;
ot->modal = gpencil_weightpaint_brush_modal;
ot->cancel = gpencil_weightpaint_brush_exit;
ot->poll = gpencil_weightpaint_brush_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
/* properties */
PropertyRNA *prop;
prop = RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "wait_for_input", true, "Wait for Input", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/* -------------------------------------------------------------------- */
/* Weight Toggle Add/Subtract Operator */
static int gpencil_weight_toggle_direction_invoke(bContext *C,
wmOperator * /*op*/,
const wmEvent * /*event*/)
{
ToolSettings *ts = CTX_data_tool_settings(C);
Paint *paint = &ts->gp_weightpaint->paint;
/* Toggle Add/Subtract flag. */
paint->brush->gpencil_settings->sculpt_flag ^= BRUSH_DIR_IN;
/* Update tool settings. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
void GPENCIL_OT_weight_toggle_direction(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Weight Paint Toggle Direction";
ot->idname = "GPENCIL_OT_weight_toggle_direction";
ot->description = "Toggle Add/Subtract for the weight paint draw tool";
/* api callbacks */
ot->invoke = gpencil_weight_toggle_direction_invoke;
ot->poll = gpencil_weightpaint_brush_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/* -------------------------------------------------------------------- */
/* Weight Sample Operator */
static int gpencil_weight_sample_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
/* Get mouse position. */
int mouse[2];
mouse[0] = event->mval[0] + 1;
mouse[1] = event->mval[1] + 1;
float mouse_f[2];
copy_v2fl_v2i(mouse_f, mouse);
/* Get active GP object. */
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *ob = CTX_data_active_object(C);
bGPdata *gpd = ED_gpencil_data_get_active(C);
if ((ob == nullptr) || (gpd == nullptr)) {
return OPERATOR_CANCELLED;
}
/* Get active vertex group. */
int vgroup = gpd->vertex_group_active_index - 1;
bDeformGroup *defgroup = static_cast<bDeformGroup *>(
BLI_findlink(&gpd->vertex_group_names, vgroup));
if (!defgroup) {
return OPERATOR_CANCELLED;
}
/* Init space conversion. */
GP_SpaceConversion gsc = {nullptr};
gpencil_point_conversion_init(C, &gsc);
/* Get evaluated GP object. */
Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id);
bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
/* Get brush radius. */
ToolSettings *ts = CTX_data_tool_settings(C);
Brush *brush = ts->gp_weightpaint->paint.brush;
const int radius = brush->size;
/* Init closest points. */
float closest_dist[2] = {FLT_MAX, FLT_MAX};
float closest_weight[2] = {0.0f, 0.0f};
int closest_count = 0;
int pc[2] = {0};
/* Inspect all layers. */
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
/* If no active frame, don't do anything. */
if (gpl->actframe == nullptr) {
continue;
}
/* Calculate transform matrix. */
float diff_mat[4][4], bound_mat[4][4];
BKE_gpencil_layer_transform_matrix_get(depsgraph, ob, gpl, diff_mat);
copy_m4_m4(bound_mat, diff_mat);
mul_m4_m4m4(diff_mat, diff_mat, gpl->layer_invmat);
/* Inspect all strokes in active frame. */
LISTBASE_FOREACH (bGPDstroke *, gps, &gpl->actframe->strokes) {
/* Look for strokes that collide with the brush. */
if (!ED_gpencil_stroke_check_collision(&gsc, gps, mouse_f, radius, bound_mat)) {
continue;
}
if (gps->dvert == nullptr) {
continue;
}
/* Look for two closest points. */
for (int i = 0; i < gps->totpoints; i++) {
bGPDspoint *pt = &gps->points[i];
bGPDspoint npt;
gpencil_point_to_world_space(pt, diff_mat, &npt);
gpencil_point_to_xy(&gsc, gps, &npt, &pc[0], &pc[1]);
float dist = len_v2v2_int(pc, mouse);
if ((dist < closest_dist[0]) || (dist < closest_dist[1])) {
/* Get weight. */
MDeformVert *dvert = &gps->dvert[i];
MDeformWeight *dw = BKE_defvert_find_index(dvert, vgroup);
if (dw == nullptr) {
continue;
}
if (dist < closest_dist[0]) {
closest_dist[1] = closest_dist[0];
closest_weight[1] = closest_weight[0];
closest_dist[0] = dist;
closest_weight[0] = dw->weight;
closest_count++;
}
else if (dist < closest_dist[1]) {
closest_dist[1] = dist;
closest_weight[1] = dw->weight;
closest_count++;
}
}
}
}
}
/* Set brush weight, based on points found. */
if (closest_count > 0) {
if (closest_count == 1) {
brush->weight = closest_weight[0];
}
else {
CLAMP_MIN(closest_dist[1], 1e-6f);
float dist_sum = closest_dist[0] + closest_dist[1];
brush->weight = (1.0f - closest_dist[0] / dist_sum) * closest_weight[0] +
(1.0f - closest_dist[1] / dist_sum) * closest_weight[1];
}
/* Update tool settings. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void GPENCIL_OT_weight_sample(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Weight Paint Sample Weight";
ot->idname = "GPENCIL_OT_weight_sample";
ot->description = "Use the mouse to sample a weight in the 3D view";
/* api callbacks */
ot->invoke = gpencil_weight_sample_invoke;
ot->poll = gpencil_weightpaint_brush_poll;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
}