This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/sculpt_paint/paint_vertex.c
Hans Goudey 3b6ee8cee7 Refactor: Move vertex group names to object data
This commit moves the storage of `bDeformGroup` and the active index
to `Mesh`, `Lattice`, and `bGPdata` instead of `Object`. Utility
functions are added to allow easy access to the vertex groups given
an object or an ID.

As explained in T88951, the list of vertex group names is currently
stored separately per object, even though vertex group data is stored
on the geometry. This tends to complicate code and cause bugs,
especially as geometry is created procedurally and tied less closely
to an object.

The "Copy Vertex Groups to Linked" operator is removed, since they
are stored on the geometry anyway.

This patch leaves the object-level python API for vertex groups in
place. Creating a geometry-level RNA API can be a separate step;
the changes in this commit are invasive enough as it is.

Note that opening a file saved in 3.0 in an earlier version means
the vertex groups will not be available.

Differential Revision: https://developer.blender.org/D11689
2021-07-13 12:10:34 -04:00

3533 lines
120 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) 2001-2002 by NaN Holding BV.
* All rights reserved.
*/
/** \file
* \ingroup edsculpt
*
* Used for vertex color & weight paint and mode switching.
*
* \note This file is already big,
* use `paint_vertex_color_ops.c` & `paint_vertex_weight_ops.c` for general purpose operators.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_rect.h"
#include "BLI_task.h"
#include "DNA_brush_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "DNA_scene_types.h"
#include "RNA_access.h"
#include "BKE_brush.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_object_deform.h"
#include "BKE_paint.h"
#include "BKE_report.h"
#include "BKE_subsurf.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "WM_message.h"
#include "WM_toolsystem.h"
#include "WM_types.h"
#include "ED_armature.h"
#include "ED_mesh.h"
#include "ED_object.h"
#include "ED_screen.h"
#include "ED_view3d.h"
/* For IMB_BlendMode only. */
#include "IMB_imbuf.h"
#include "BKE_ccg.h"
#include "bmesh.h"
#include "paint_intern.h" /* own include */
#include "sculpt_intern.h"
/* -------------------------------------------------------------------- */
/** \name Internal Utilities
* \{ */
/* Use for 'blur' brush, align with PBVH nodes, created and freed on each update. */
struct VPaintAverageAccum {
uint len;
uint value[3];
};
struct WPaintAverageAccum {
uint len;
double value;
};
struct NormalAnglePrecalc {
bool do_mask_normal;
/* what angle to mask at */
float angle;
/* cos(angle), faster to compare */
float angle__cos;
float angle_inner;
float angle_inner__cos;
/* difference between angle and angle_inner, for easy access */
float angle_range;
};
static void view_angle_limits_init(struct NormalAnglePrecalc *a, float angle, bool do_mask_normal)
{
angle = RAD2DEGF(angle);
a->do_mask_normal = do_mask_normal;
if (do_mask_normal) {
a->angle_inner = angle;
a->angle = (a->angle_inner + 90.0f) * 0.5f;
}
else {
a->angle_inner = a->angle = angle;
}
a->angle_inner *= (float)(M_PI_2 / 90);
a->angle *= (float)(M_PI_2 / 90);
a->angle_range = a->angle - a->angle_inner;
if (a->angle_range <= 0.0f) {
a->do_mask_normal = false; /* no need to do blending */
}
a->angle__cos = cosf(a->angle);
a->angle_inner__cos = cosf(a->angle_inner);
}
static float view_angle_limits_apply_falloff(const struct NormalAnglePrecalc *a,
float angle_cos,
float *mask_p)
{
if (angle_cos <= a->angle__cos) {
/* outsize the normal limit */
return false;
}
if (angle_cos < a->angle_inner__cos) {
*mask_p *= (a->angle - acosf(angle_cos)) / a->angle_range;
return true;
}
return true;
}
static bool vwpaint_use_normal(const VPaint *vp)
{
return ((vp->paint.brush->flag & BRUSH_FRONTFACE) != 0) ||
((vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0);
}
static bool brush_use_accumulate_ex(const Brush *brush, const int ob_mode)
{
return ((brush->flag & BRUSH_ACCUMULATE) != 0 ||
(ob_mode == OB_MODE_VERTEX_PAINT ? (brush->vertexpaint_tool == VPAINT_TOOL_SMEAR) :
(brush->weightpaint_tool == WPAINT_TOOL_SMEAR)));
}
static bool brush_use_accumulate(const VPaint *vp)
{
return brush_use_accumulate_ex(vp->paint.brush, vp->paint.runtime.ob_mode);
}
static MDeformVert *defweight_prev_init(MDeformVert *dvert_prev,
MDeformVert *dvert_curr,
int index)
{
MDeformVert *dv_curr = &dvert_curr[index];
MDeformVert *dv_prev = &dvert_prev[index];
if (dv_prev->flag == 1) {
dv_prev->flag = 0;
BKE_defvert_copy(dv_prev, dv_curr);
}
return dv_prev;
}
/* check if we can do partial updates and have them draw realtime
* (without evaluating modifiers) */
static bool vertex_paint_use_fast_update_check(Object *ob)
{
const Mesh *me_eval = BKE_object_get_evaluated_mesh(ob);
if (me_eval != NULL) {
Mesh *me = BKE_mesh_from_object(ob);
if (me && me->mloopcol) {
return (me->mloopcol == CustomData_get_layer(&me_eval->ldata, CD_MLOOPCOL));
}
}
return false;
}
static void paint_last_stroke_update(Scene *scene, const float location[3])
{
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
ups->average_stroke_counter++;
add_v3_v3(ups->average_stroke_accum, location);
ups->last_stroke_valid = true;
}
/* polling - retrieve whether cursor should be set or operator should be done */
/* Returns true if vertex paint mode is active */
bool vertex_paint_mode_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return ob && ob->mode == OB_MODE_VERTEX_PAINT && ((Mesh *)ob->data)->totpoly;
}
static bool vertex_paint_poll_ex(bContext *C, bool check_tool)
{
if (vertex_paint_mode_poll(C) && BKE_paint_brush(&CTX_data_tool_settings(C)->vpaint->paint)) {
ScrArea *area = CTX_wm_area(C);
if (area && area->spacetype == SPACE_VIEW3D) {
ARegion *region = CTX_wm_region(C);
if (region->regiontype == RGN_TYPE_WINDOW) {
if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) {
return true;
}
}
}
}
return false;
}
bool vertex_paint_poll(bContext *C)
{
return vertex_paint_poll_ex(C, true);
}
bool vertex_paint_poll_ignore_tool(bContext *C)
{
return vertex_paint_poll_ex(C, false);
}
bool weight_paint_mode_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return ob && ob->mode == OB_MODE_WEIGHT_PAINT && ((Mesh *)ob->data)->totpoly;
}
static bool weight_paint_poll_ex(bContext *C, bool check_tool)
{
Object *ob = CTX_data_active_object(C);
ScrArea *area;
if ((ob != NULL) && (ob->mode & OB_MODE_WEIGHT_PAINT) &&
(BKE_paint_brush(&CTX_data_tool_settings(C)->wpaint->paint) != NULL) &&
(area = CTX_wm_area(C)) && (area->spacetype == SPACE_VIEW3D)) {
ARegion *region = CTX_wm_region(C);
if (ELEM(region->regiontype, RGN_TYPE_WINDOW, RGN_TYPE_HUD)) {
if (!check_tool || WM_toolsystem_active_tool_is_brush(C)) {
return true;
}
}
}
return false;
}
bool weight_paint_poll(bContext *C)
{
return weight_paint_poll_ex(C, true);
}
bool weight_paint_poll_ignore_tool(bContext *C)
{
return weight_paint_poll_ex(C, false);
}
uint vpaint_get_current_col(Scene *scene, VPaint *vp, bool secondary)
{
Brush *brush = BKE_paint_brush(&vp->paint);
uchar col[4];
rgb_float_to_uchar(col,
secondary ? BKE_brush_secondary_color_get(scene, brush) :
BKE_brush_color_get(scene, brush));
col[3] = 255; /* alpha isn't used, could even be removed to speedup paint a little */
return *(uint *)col;
}
/* wpaint has 'wpaint_blend' */
static uint vpaint_blend(const VPaint *vp,
uint color_curr,
uint color_orig,
uint color_paint,
const int alpha_i,
/* pre scaled from [0-1] --> [0-255] */
const int brush_alpha_value_i)
{
const Brush *brush = vp->paint.brush;
const IMB_BlendMode blend = brush->blend;
uint color_blend = ED_vpaint_blend_tool(blend, color_curr, color_paint, alpha_i);
/* if no accumulate, clip color adding with colorig & orig alpha */
if (!brush_use_accumulate(vp)) {
uint color_test, a;
char *cp, *ct, *co;
color_test = ED_vpaint_blend_tool(blend, color_orig, color_paint, brush_alpha_value_i);
cp = (char *)&color_blend;
ct = (char *)&color_test;
co = (char *)&color_orig;
for (a = 0; a < 4; a++) {
if (ct[a] < co[a]) {
if (cp[a] < ct[a]) {
cp[a] = ct[a];
}
else if (cp[a] > co[a]) {
cp[a] = co[a];
}
}
else {
if (cp[a] < co[a]) {
cp[a] = co[a];
}
else if (cp[a] > ct[a]) {
cp[a] = ct[a];
}
}
}
}
if ((brush->flag & BRUSH_LOCK_ALPHA) &&
!ELEM(blend, IMB_BLEND_ERASE_ALPHA, IMB_BLEND_ADD_ALPHA)) {
char *cp, *cc;
cp = (char *)&color_blend;
cc = (char *)&color_curr;
cp[3] = cc[3];
}
return color_blend;
}
static void tex_color_alpha(VPaint *vp, const ViewContext *vc, const float co[3], float r_rgba[4])
{
const Brush *brush = BKE_paint_brush(&vp->paint);
BLI_assert(brush->mtex.tex != NULL);
if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) {
BKE_brush_sample_tex_3d(vc->scene, brush, co, r_rgba, 0, NULL);
}
else {
float co_ss[2]; /* screenspace */
if (ED_view3d_project_float_object(
vc->region, co, co_ss, V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) ==
V3D_PROJ_RET_OK) {
const float co_ss_3d[3] = {co_ss[0], co_ss[1], 0.0f}; /* we need a 3rd empty value */
BKE_brush_sample_tex_3d(vc->scene, brush, co_ss_3d, r_rgba, 0, NULL);
}
else {
zero_v4(r_rgba);
}
}
}
/* vpaint has 'vpaint_blend' */
static float wpaint_blend(const VPaint *wp,
float weight,
const float alpha,
float paintval,
const float UNUSED(brush_alpha_value),
const short do_flip)
{
const Brush *brush = wp->paint.brush;
IMB_BlendMode blend = brush->blend;
if (do_flip) {
switch (blend) {
case IMB_BLEND_MIX:
paintval = 1.0f - paintval;
break;
case IMB_BLEND_ADD:
blend = IMB_BLEND_SUB;
break;
case IMB_BLEND_SUB:
blend = IMB_BLEND_ADD;
break;
case IMB_BLEND_LIGHTEN:
blend = IMB_BLEND_DARKEN;
break;
case IMB_BLEND_DARKEN:
blend = IMB_BLEND_LIGHTEN;
break;
default:
break;
}
}
weight = ED_wpaint_blend_tool(blend, weight, paintval, alpha);
CLAMP(weight, 0.0f, 1.0f);
return weight;
}
static float wpaint_clamp_monotonic(float oldval, float curval, float newval)
{
if (newval < oldval) {
return MIN2(newval, curval);
}
if (newval > oldval) {
return MAX2(newval, curval);
}
return newval;
}
static float wpaint_undo_lock_relative(
float weight, float old_weight, float locked_weight, float free_weight, bool auto_normalize)
{
/* In auto-normalize mode, or when there is no unlocked weight,
* compute based on locked weight. */
if (auto_normalize || free_weight <= 0.0f) {
if (locked_weight < 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
weight *= (1.0f - locked_weight);
}
else {
weight = 0;
}
}
else {
/* When dealing with full unlocked weight, don't paint, as it is always displayed as 1. */
if (old_weight >= free_weight) {
weight = old_weight;
}
/* Try to compute a weight value that would produce the desired effect if normalized. */
else if (weight < 1.0f) {
weight = weight * (free_weight - old_weight) / (1 - weight);
}
else {
weight = 1.0f;
}
}
return weight;
}
/* ----------------------------------------------------- */
static void do_weight_paint_normalize_all(MDeformVert *dvert,
const int defbase_tot,
const bool *vgroup_validmap)
{
float sum = 0.0f, fac;
uint i, tot = 0;
MDeformWeight *dw;
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
tot++;
sum += dw->weight;
}
}
if ((tot == 0) || (sum == 1.0f)) {
return;
}
if (sum != 0.0f) {
fac = 1.0f / sum;
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
dw->weight *= fac;
}
}
}
else {
/* hrmf, not a factor in this case */
fac = 1.0f / tot;
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
dw->weight = fac;
}
}
}
}
/**
* A version of #do_weight_paint_normalize_all that includes locked weights
* but only changes unlocked weights.
*/
static bool do_weight_paint_normalize_all_locked(MDeformVert *dvert,
const int defbase_tot,
const bool *vgroup_validmap,
const bool *lock_flags)
{
float sum = 0.0f, fac;
float sum_unlock = 0.0f;
float lock_weight = 0.0f;
uint i, tot = 0;
MDeformWeight *dw;
if (lock_flags == NULL) {
do_weight_paint_normalize_all(dvert, defbase_tot, vgroup_validmap);
return true;
}
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
sum += dw->weight;
if (lock_flags[dw->def_nr]) {
lock_weight += dw->weight;
}
else {
tot++;
sum_unlock += dw->weight;
}
}
}
if (sum == 1.0f) {
return true;
}
if (tot == 0) {
return false;
}
if (lock_weight >= 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
/* locked groups make it impossible to fully normalize,
* zero out what we can and return false */
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
if (lock_flags[dw->def_nr] == false) {
dw->weight = 0.0f;
}
}
}
return (lock_weight == 1.0f);
}
if (sum_unlock != 0.0f) {
fac = (1.0f - lock_weight) / sum_unlock;
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
if (lock_flags[dw->def_nr] == false) {
dw->weight *= fac;
/* paranoid but possibly with float error */
CLAMP(dw->weight, 0.0f, 1.0f);
}
}
}
}
else {
/* hrmf, not a factor in this case */
fac = (1.0f - lock_weight) / tot;
/* paranoid but possibly with float error */
CLAMP(fac, 0.0f, 1.0f);
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && vgroup_validmap[dw->def_nr]) {
if (lock_flags[dw->def_nr] == false) {
dw->weight = fac;
}
}
}
}
return true;
}
/**
* \note same as function above except it does a second pass without active group
* if normalize fails with it.
*/
static void do_weight_paint_normalize_all_locked_try_active(MDeformVert *dvert,
const int defbase_tot,
const bool *vgroup_validmap,
const bool *lock_flags,
const bool *lock_with_active)
{
/* first pass with both active and explicitly locked groups restricted from change */
bool success = do_weight_paint_normalize_all_locked(
dvert, defbase_tot, vgroup_validmap, lock_with_active);
if (!success) {
/**
* Locks prevented the first pass from full completion,
* so remove restriction on active group; e.g:
*
* - With 1.0 weight painted into active:
* nonzero locked weight; first pass zeroed out unlocked weight; scale 1 down to fit.
* - With 0.0 weight painted into active:
* no unlocked groups; first pass did nothing; increase 0 to fit.
*/
do_weight_paint_normalize_all_locked(dvert, defbase_tot, vgroup_validmap, lock_flags);
}
}
#if 0 /* UNUSED */
static bool has_unselected_unlocked_bone_group(int defbase_tot,
bool *defbase_sel,
int selected,
const bool *lock_flags,
const bool *vgroup_validmap)
{
int i;
if (defbase_tot == selected) {
return false;
}
for (i = 0; i < defbase_tot; i++) {
if (vgroup_validmap[i] && !defbase_sel[i] && !lock_flags[i]) {
return true;
}
}
return false;
}
#endif
static void multipaint_clamp_change(MDeformVert *dvert,
const int defbase_tot,
const bool *defbase_sel,
float *change_p)
{
int i;
MDeformWeight *dw;
float val;
float change = *change_p;
/* verify that the change does not cause values exceeding 1 and clamp it */
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && defbase_sel[dw->def_nr]) {
if (dw->weight) {
val = dw->weight * change;
if (val > 1) {
change = 1.0f / dw->weight;
}
}
}
}
*change_p = change;
}
static bool multipaint_verify_change(MDeformVert *dvert,
const int defbase_tot,
float change,
const bool *defbase_sel)
{
int i;
MDeformWeight *dw;
float val;
/* in case the change is reduced, you need to recheck
* the earlier values to make sure they are not 0
* (precision error) */
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && defbase_sel[dw->def_nr]) {
if (dw->weight) {
val = dw->weight * change;
/* the value should never reach zero while multi-painting if it
* was nonzero beforehand */
if (val <= 0) {
return false;
}
}
}
}
return true;
}
static void multipaint_apply_change(MDeformVert *dvert,
const int defbase_tot,
float change,
const bool *defbase_sel)
{
int i;
MDeformWeight *dw;
/* apply the valid change */
for (i = dvert->totweight, dw = dvert->dw; i != 0; i--, dw++) {
if (dw->def_nr < defbase_tot && defbase_sel[dw->def_nr]) {
if (dw->weight) {
dw->weight = dw->weight * change;
CLAMP(dw->weight, 0.0f, 1.0f);
}
}
}
}
/**
* Variables stored both for 'active' and 'mirror' sides.
*/
struct WeightPaintGroupData {
/** index of active group or its mirror
*
* - 'active' is always `ob->actdef`.
* - 'mirror' is -1 when 'ME_EDIT_MIRROR_X' flag id disabled,
* otherwise this will be set to the mirror or the active group (if the group isn't mirrored).
*/
int index;
/** lock that includes the 'index' as locked too
*
* - 'active' is set of locked or active/selected groups
* - 'mirror' is set of locked or mirror groups
*/
const bool *lock;
};
/* struct to avoid passing many args each call to do_weight_paint_vertex()
* this _could_ be made a part of the operators 'WPaintData' struct, or at
* least a member, but for now keep its own struct, initialized on every
* paint stroke update - campbell */
typedef struct WeightPaintInfo {
int defbase_tot;
/* both must add up to 'defbase_tot' */
int defbase_tot_sel;
int defbase_tot_unsel;
struct WeightPaintGroupData active, mirror;
/* boolean array for locked bones,
* length of defbase_tot */
const bool *lock_flags;
/* boolean array for selected bones,
* length of defbase_tot, can't be const because of how it's passed */
const bool *defbase_sel;
/* same as WeightPaintData.vgroup_validmap,
* only added here for convenience */
const bool *vgroup_validmap;
/* same as WeightPaintData.vgroup_locked/unlocked,
* only added here for convenience */
const bool *vgroup_locked;
const bool *vgroup_unlocked;
bool do_flip;
bool do_multipaint;
bool do_auto_normalize;
bool do_lock_relative;
bool is_normalized;
float brush_alpha_value; /* result of BKE_brush_alpha_get() */
} WeightPaintInfo;
static void do_weight_paint_vertex_single(
/* vars which remain the same for every vert */
const VPaint *wp,
Object *ob,
const WeightPaintInfo *wpi,
/* vars which change on each stroke */
const uint index,
float alpha,
float paintweight)
{
Mesh *me = ob->data;
MDeformVert *dv = &me->dvert[index];
bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0;
MDeformWeight *dw;
float weight_prev, weight_cur;
float dw_rel_locked = 0.0f, dw_rel_free = 1.0f;
/* mirror vars */
int index_mirr;
int vgroup_mirr;
MDeformVert *dv_mirr;
MDeformWeight *dw_mirr;
/* Check if we should mirror vertex groups (X-axis). */
if (ME_USING_MIRROR_X_VERTEX_GROUPS(me)) {
index_mirr = mesh_get_x_mirror_vert(ob, NULL, index, topology);
vgroup_mirr = wpi->mirror.index;
/* another possible error - mirror group _and_ active group are the same (which is fine),
* but we also are painting onto a center vertex - this would paint the same weight twice */
if (index_mirr == index && vgroup_mirr == wpi->active.index) {
index_mirr = vgroup_mirr = -1;
}
}
else {
index_mirr = vgroup_mirr = -1;
}
/* Check if painting should create new deform weight entries. */
bool restrict_to_existing = (wp->flag & VP_FLAG_VGROUP_RESTRICT) != 0;
if (wpi->do_lock_relative || wpi->do_auto_normalize) {
/* Without do_lock_relative only dw_rel_locked is reliable, while dw_rel_free may be fake 0. */
dw_rel_free = BKE_defvert_total_selected_weight(dv, wpi->defbase_tot, wpi->vgroup_unlocked);
dw_rel_locked = BKE_defvert_total_selected_weight(dv, wpi->defbase_tot, wpi->vgroup_locked);
CLAMP(dw_rel_locked, 0.0f, 1.0f);
/* Do not create entries if there is not enough free weight to paint.
* This logic is the same as in wpaint_undo_lock_relative and auto-normalize. */
if (wpi->do_auto_normalize || dw_rel_free <= 0.0f) {
if (dw_rel_locked >= 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
restrict_to_existing = true;
}
}
}
if (restrict_to_existing) {
dw = BKE_defvert_find_index(dv, wpi->active.index);
}
else {
dw = BKE_defvert_ensure_index(dv, wpi->active.index);
}
if (dw == NULL) {
return;
}
/* get the mirror def vars */
if (index_mirr != -1) {
dv_mirr = &me->dvert[index_mirr];
if (wp->flag & VP_FLAG_VGROUP_RESTRICT) {
dw_mirr = BKE_defvert_find_index(dv_mirr, vgroup_mirr);
if (dw_mirr == NULL) {
index_mirr = vgroup_mirr = -1;
dv_mirr = NULL;
}
}
else {
if (index != index_mirr) {
dw_mirr = BKE_defvert_ensure_index(dv_mirr, vgroup_mirr);
}
else {
/* dv and dv_mirr are the same */
int totweight_prev = dv_mirr->totweight;
int dw_offset = (int)(dw - dv_mirr->dw);
dw_mirr = BKE_defvert_ensure_index(dv_mirr, vgroup_mirr);
/* if we added another, get our old one back */
if (totweight_prev != dv_mirr->totweight) {
dw = &dv_mirr->dw[dw_offset];
}
}
}
}
else {
dv_mirr = NULL;
dw_mirr = NULL;
}
weight_cur = dw->weight;
/* Handle weight caught up in locked defgroups for Lock Relative. */
if (wpi->do_lock_relative) {
weight_cur = BKE_defvert_calc_lock_relative_weight(weight_cur, dw_rel_locked, dw_rel_free);
}
if (!brush_use_accumulate(wp)) {
MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev;
MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index);
if (index_mirr != -1) {
defweight_prev_init(dvert_prev, me->dvert, index_mirr);
}
weight_prev = BKE_defvert_find_weight(dv_prev, wpi->active.index);
if (wpi->do_lock_relative) {
weight_prev = BKE_defvert_lock_relative_weight(
weight_prev, dv_prev, wpi->defbase_tot, wpi->vgroup_locked, wpi->vgroup_unlocked);
}
}
else {
weight_prev = weight_cur;
}
/* If there are no normalize-locks or multipaint,
* then there is no need to run the more complicated checks */
{
float new_weight = wpaint_blend(
wp, weight_prev, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip);
float weight = wpaint_clamp_monotonic(weight_prev, weight_cur, new_weight);
/* Undo the lock relative weight correction. */
if (wpi->do_lock_relative) {
if (index_mirr == index) {
/* When painting a center vertex with X Mirror and L/R pair,
* handle both groups together. This avoids weird fighting
* in the non-normalized weight mode. */
float orig_weight = dw->weight + dw_mirr->weight;
weight = 0.5f *
wpaint_undo_lock_relative(
weight * 2, orig_weight, dw_rel_locked, dw_rel_free, wpi->do_auto_normalize);
}
else {
weight = wpaint_undo_lock_relative(
weight, dw->weight, dw_rel_locked, dw_rel_free, wpi->do_auto_normalize);
}
CLAMP(weight, 0.0f, 1.0f);
}
dw->weight = weight;
/* WATCH IT: take care of the ordering of applying mirror -> normalize,
* can give wrong results T26193, least confusing if normalize is done last */
/* apply mirror */
if (index_mirr != -1) {
/* copy, not paint again */
dw_mirr->weight = dw->weight;
}
/* apply normalize */
if (wpi->do_auto_normalize) {
/* note on normalize - this used to be applied after painting and normalize all weights,
* in some ways this is good because there is feedback where the more weights involved would
* 'resist' so you couldn't instantly zero out other weights by painting 1.0 on the active.
*
* However this gave a problem since applying mirror, then normalize both verts
* the resulting weight won't match on both sides.
*
* If this 'resisting', slower normalize is nicer, we could call
* do_weight_paint_normalize_all() and only use...
* do_weight_paint_normalize_all_active() when normalizing the mirror vertex.
* - campbell
*/
do_weight_paint_normalize_all_locked_try_active(
dv, wpi->defbase_tot, wpi->vgroup_validmap, wpi->lock_flags, wpi->active.lock);
if (index_mirr != -1) {
/* only normalize if this is not a center vertex,
* else we get a conflict, normalizing twice */
if (index != index_mirr) {
do_weight_paint_normalize_all_locked_try_active(
dv_mirr, wpi->defbase_tot, wpi->vgroup_validmap, wpi->lock_flags, wpi->mirror.lock);
}
else {
/* This case accounts for:
* - Painting onto a center vertex of a mesh.
* - X-mirror is enabled.
* - Auto normalize is enabled.
* - The group you are painting onto has a L / R version.
*
* We want L/R vgroups to have the same weight but this can't be if both are over 0.5,
* We _could_ have special check for that, but this would need its own
* normalize function which holds 2 groups from changing at once.
*
* So! just balance out the 2 weights, it keeps them equal and everything normalized.
*
* While it won't hit the desired weight immediately as the user waggles their mouse,
* constant painting and re-normalizing will get there. this is also just simpler logic.
* - campbell */
dw_mirr->weight = dw->weight = (dw_mirr->weight + dw->weight) * 0.5f;
}
}
}
}
}
static void do_weight_paint_vertex_multi(
/* vars which remain the same for every vert */
const VPaint *wp,
Object *ob,
const WeightPaintInfo *wpi,
/* vars which change on each stroke */
const uint index,
float alpha,
float paintweight)
{
Mesh *me = ob->data;
MDeformVert *dv = &me->dvert[index];
bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0;
/* mirror vars */
int index_mirr = -1;
MDeformVert *dv_mirr = NULL;
/* weights */
float curw, curw_real, oldw, neww, change, curw_mirr, change_mirr;
float dw_rel_free, dw_rel_locked;
/* Check if we should mirror vertex groups (X-axis). */
if (ME_USING_MIRROR_X_VERTEX_GROUPS(me)) {
index_mirr = mesh_get_x_mirror_vert(ob, NULL, index, topology);
if (!ELEM(index_mirr, -1, index)) {
dv_mirr = &me->dvert[index_mirr];
}
else {
index_mirr = -1;
}
}
/* compute weight change by applying the brush to average or sum of group weights */
curw = curw_real = BKE_defvert_multipaint_collective_weight(
dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->is_normalized);
if (curw == 0.0f) {
/* NOTE: no weight to assign to this vertex, could add all groups? */
return;
}
/* Handle weight caught up in locked defgroups for Lock Relative. */
if (wpi->do_lock_relative) {
dw_rel_free = BKE_defvert_total_selected_weight(dv, wpi->defbase_tot, wpi->vgroup_unlocked);
dw_rel_locked = BKE_defvert_total_selected_weight(dv, wpi->defbase_tot, wpi->vgroup_locked);
CLAMP(dw_rel_locked, 0.0f, 1.0f);
curw = BKE_defvert_calc_lock_relative_weight(curw, dw_rel_locked, dw_rel_free);
}
if (!brush_use_accumulate(wp)) {
MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev;
MDeformVert *dv_prev = defweight_prev_init(dvert_prev, me->dvert, index);
if (index_mirr != -1) {
defweight_prev_init(dvert_prev, me->dvert, index_mirr);
}
oldw = BKE_defvert_multipaint_collective_weight(
dv_prev, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->is_normalized);
if (wpi->do_lock_relative) {
oldw = BKE_defvert_lock_relative_weight(
oldw, dv_prev, wpi->defbase_tot, wpi->vgroup_locked, wpi->vgroup_unlocked);
}
}
else {
oldw = curw;
}
neww = wpaint_blend(wp, oldw, alpha, paintweight, wpi->brush_alpha_value, wpi->do_flip);
neww = wpaint_clamp_monotonic(oldw, curw, neww);
if (wpi->do_lock_relative) {
neww = wpaint_undo_lock_relative(
neww, curw_real, dw_rel_locked, dw_rel_free, wpi->do_auto_normalize);
}
change = neww / curw_real;
/* verify for all groups that 0 < result <= 1 */
multipaint_clamp_change(dv, wpi->defbase_tot, wpi->defbase_sel, &change);
if (dv_mirr != NULL) {
curw_mirr = BKE_defvert_multipaint_collective_weight(
dv_mirr, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->is_normalized);
if (curw_mirr == 0.0f) {
/* can't mirror into a zero weight vertex */
dv_mirr = NULL;
}
else {
/* mirror is changed to achieve the same collective weight value */
float orig = change_mirr = curw_real * change / curw_mirr;
multipaint_clamp_change(dv_mirr, wpi->defbase_tot, wpi->defbase_sel, &change_mirr);
if (!multipaint_verify_change(dv_mirr, wpi->defbase_tot, change_mirr, wpi->defbase_sel)) {
return;
}
change *= change_mirr / orig;
}
}
if (!multipaint_verify_change(dv, wpi->defbase_tot, change, wpi->defbase_sel)) {
return;
}
/* apply validated change to vertex and mirror */
multipaint_apply_change(dv, wpi->defbase_tot, change, wpi->defbase_sel);
if (dv_mirr != NULL) {
multipaint_apply_change(dv_mirr, wpi->defbase_tot, change_mirr, wpi->defbase_sel);
}
/* normalize */
if (wpi->do_auto_normalize) {
do_weight_paint_normalize_all_locked_try_active(
dv, wpi->defbase_tot, wpi->vgroup_validmap, wpi->lock_flags, wpi->active.lock);
if (dv_mirr != NULL) {
do_weight_paint_normalize_all_locked_try_active(
dv_mirr, wpi->defbase_tot, wpi->vgroup_validmap, wpi->lock_flags, wpi->active.lock);
}
}
}
static void do_weight_paint_vertex(
/* vars which remain the same for every vert */
const VPaint *wp,
Object *ob,
const WeightPaintInfo *wpi,
/* vars which change on each stroke */
const uint index,
float alpha,
float paintweight)
{
if (wpi->do_multipaint) {
do_weight_paint_vertex_multi(wp, ob, wpi, index, alpha, paintweight);
}
else {
do_weight_paint_vertex_single(wp, ob, wpi, index, alpha, paintweight);
}
}
/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.c) */
static void vertex_paint_init_session(Depsgraph *depsgraph,
Scene *scene,
Object *ob,
eObjectMode object_mode)
{
/* Create persistent sculpt mode data */
BKE_sculpt_toolsettings_data_ensure(scene);
BLI_assert(ob->sculpt == NULL);
ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session");
ob->sculpt->mode_type = object_mode;
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
}
static void vertex_paint_init_stroke(Depsgraph *depsgraph, Object *ob)
{
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
}
static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob)
{
/* Create maps */
struct SculptVertexPaintGeomMap *gmap = NULL;
if (ob->mode == OB_MODE_VERTEX_PAINT) {
gmap = &ob->sculpt->mode.vpaint.gmap;
BLI_assert(ob->sculpt->mode_type == OB_MODE_VERTEX_PAINT);
}
else if (ob->mode == OB_MODE_WEIGHT_PAINT) {
gmap = &ob->sculpt->mode.wpaint.gmap;
BLI_assert(ob->sculpt->mode_type == OB_MODE_WEIGHT_PAINT);
}
else {
ob->sculpt->mode_type = 0;
BLI_assert(0);
return;
}
Mesh *me = ob->data;
if (gmap->vert_to_loop == NULL) {
gmap->vert_map_mem = NULL;
gmap->vert_to_loop = NULL;
gmap->poly_map_mem = NULL;
gmap->vert_to_poly = NULL;
BKE_mesh_vert_loop_map_create(&gmap->vert_to_loop,
&gmap->vert_map_mem,
me->mpoly,
me->mloop,
me->totvert,
me->totpoly,
me->totloop);
BKE_mesh_vert_poly_map_create(&gmap->vert_to_poly,
&gmap->poly_map_mem,
me->mpoly,
me->mloop,
me->totvert,
me->totpoly,
me->totloop);
}
/* Create average brush arrays */
if (ob->mode == OB_MODE_VERTEX_PAINT) {
if (!brush_use_accumulate(ts->vpaint)) {
if (ob->sculpt->mode.vpaint.previous_color == NULL) {
ob->sculpt->mode.vpaint.previous_color = MEM_callocN(me->totloop * sizeof(uint), __func__);
}
}
else {
MEM_SAFE_FREE(ob->sculpt->mode.vpaint.previous_color);
}
}
else if (ob->mode == OB_MODE_WEIGHT_PAINT) {
if (!brush_use_accumulate(ts->wpaint)) {
if (ob->sculpt->mode.wpaint.alpha_weight == NULL) {
ob->sculpt->mode.wpaint.alpha_weight = MEM_callocN(me->totvert * sizeof(float), __func__);
}
if (ob->sculpt->mode.wpaint.dvert_prev == NULL) {
ob->sculpt->mode.wpaint.dvert_prev = MEM_callocN(me->totvert * sizeof(MDeformVert),
__func__);
MDeformVert *dv = ob->sculpt->mode.wpaint.dvert_prev;
for (int i = 0; i < me->totvert; i++, dv++) {
/* Use to show this isn't initialized, never apply to the mesh data. */
dv->flag = 1;
}
}
}
else {
MEM_SAFE_FREE(ob->sculpt->mode.wpaint.alpha_weight);
if (ob->sculpt->mode.wpaint.dvert_prev != NULL) {
BKE_defvert_array_free_elems(ob->sculpt->mode.wpaint.dvert_prev, me->totvert);
MEM_freeN(ob->sculpt->mode.wpaint.dvert_prev);
ob->sculpt->mode.wpaint.dvert_prev = NULL;
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Enter Vertex/Weight Paint Mode
* \{ */
static void ed_vwpaintmode_enter_generic(
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, const eObjectMode mode_flag)
{
ob->mode |= mode_flag;
Mesh *me = BKE_mesh_from_object(ob);
/* Same as sculpt mode, make sure we don't have cached derived mesh which
* points to freed arrays.
*/
BKE_object_free_derived_caches(ob);
if (mode_flag == OB_MODE_VERTEX_PAINT) {
const ePaintMode paint_mode = PAINT_MODE_VERTEX;
ED_mesh_color_ensure(me, NULL);
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->vpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
paint_cursor_start(paint, vertex_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_VERTEX_PAINT);
}
else if (mode_flag == OB_MODE_WEIGHT_PAINT) {
const ePaintMode paint_mode = PAINT_MODE_WEIGHT;
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->wpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
paint_cursor_start(paint, weight_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_WEIGHT_PAINT);
/* weight paint specific */
ED_mesh_mirror_spatial_table_end(ob);
ED_vgroup_sync_from_pose(ob);
}
else {
BLI_assert(0);
}
/* Create vertex/weight paint mode session data */
if (ob->sculpt) {
if (ob->sculpt->cache) {
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
BKE_sculptsession_free(ob);
}
vertex_paint_init_session(depsgraph, scene, ob, mode_flag);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
void ED_object_vpaintmode_enter_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
ed_vwpaintmode_enter_generic(bmain, depsgraph, scene, ob, OB_MODE_VERTEX_PAINT);
}
void ED_object_vpaintmode_enter(struct bContext *C, Depsgraph *depsgraph)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
ED_object_vpaintmode_enter_ex(bmain, depsgraph, scene, ob);
}
void ED_object_wpaintmode_enter_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
ed_vwpaintmode_enter_generic(bmain, depsgraph, scene, ob, OB_MODE_WEIGHT_PAINT);
}
void ED_object_wpaintmode_enter(struct bContext *C, Depsgraph *depsgraph)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
ED_object_wpaintmode_enter_ex(bmain, depsgraph, scene, ob);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Exit Vertex/Weight Paint Mode
* \{ */
static void ed_vwpaintmode_exit_generic(Object *ob, const eObjectMode mode_flag)
{
Mesh *me = BKE_mesh_from_object(ob);
ob->mode &= ~mode_flag;
if (mode_flag == OB_MODE_VERTEX_PAINT) {
if (me->editflag & ME_EDIT_PAINT_FACE_SEL) {
BKE_mesh_flush_select_from_polys(me);
}
else if (me->editflag & ME_EDIT_PAINT_VERT_SEL) {
BKE_mesh_flush_select_from_verts(me);
}
}
else if (mode_flag == OB_MODE_WEIGHT_PAINT) {
if (me->editflag & ME_EDIT_PAINT_VERT_SEL) {
BKE_mesh_flush_select_from_verts(me);
}
else if (me->editflag & ME_EDIT_PAINT_FACE_SEL) {
BKE_mesh_flush_select_from_polys(me);
}
}
else {
BLI_assert(0);
}
/* If the cache is not released by a cancel or a done, free it now. */
if (ob->sculpt && ob->sculpt->cache) {
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
BKE_sculptsession_free(ob);
paint_cursor_delete_textures();
if (mode_flag == OB_MODE_WEIGHT_PAINT) {
ED_mesh_mirror_spatial_table_end(ob);
ED_mesh_mirror_topo_table_end(ob);
}
/* Never leave derived meshes behind. */
BKE_object_free_derived_caches(ob);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
void ED_object_vpaintmode_exit_ex(Object *ob)
{
ed_vwpaintmode_exit_generic(ob, OB_MODE_VERTEX_PAINT);
}
void ED_object_vpaintmode_exit(struct bContext *C)
{
Object *ob = CTX_data_active_object(C);
ED_object_vpaintmode_exit_ex(ob);
}
void ED_object_wpaintmode_exit_ex(Object *ob)
{
ed_vwpaintmode_exit_generic(ob, OB_MODE_WEIGHT_PAINT);
}
void ED_object_wpaintmode_exit(struct bContext *C)
{
Object *ob = CTX_data_active_object(C);
ED_object_wpaintmode_exit_ex(ob);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Toggle Weight Paint Operator
* \{ */
/**
* \note Keep in sync with #vpaint_mode_toggle_exec
*/
static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
struct wmMsgBus *mbus = CTX_wm_message_bus(C);
Object *ob = CTX_data_active_object(C);
const int mode_flag = OB_MODE_WEIGHT_PAINT;
const bool is_mode_set = (ob->mode & mode_flag) != 0;
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
if (!is_mode_set) {
if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
return OPERATOR_CANCELLED;
}
}
Mesh *me = BKE_mesh_from_object(ob);
if (is_mode_set) {
ED_object_wpaintmode_exit_ex(ob);
}
else {
Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C);
if (depsgraph) {
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_wpaintmode_enter_ex(bmain, depsgraph, scene, ob);
BKE_paint_toolslots_brush_validate(bmain, &ts->wpaint->paint);
}
/* Prepare armature posemode. */
ED_object_posemode_set_for_weight_paint(C, bmain, ob, is_mode_set);
/* Weight-paint works by overriding colors in mesh,
* so need to make sure we recalculate on enter and
* exit (exit needs doing regardless because we
* should re-deform).
*/
DEG_id_tag_update(&me->id, 0);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene);
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
WM_toolsystem_update_from_context_view3d(C);
return OPERATOR_FINISHED;
}
static bool paint_mode_toggle_poll_test(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if (ob == NULL || ob->type != OB_MESH) {
return false;
}
if (!ob->data || ID_IS_LINKED(ob->data)) {
return false;
}
return true;
}
void PAINT_OT_weight_paint_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Weight Paint Mode";
ot->idname = "PAINT_OT_weight_paint_toggle";
ot->description = "Toggle weight paint mode in 3D view";
/* api callbacks */
ot->exec = wpaint_mode_toggle_exec;
ot->poll = paint_mode_toggle_poll_test;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Weight Paint Operator
* \{ */
struct WPaintData {
ViewContext vc;
struct NormalAnglePrecalc normal_angle_precalc;
struct WeightPaintGroupData active, mirror;
/* variables for auto normalize */
const bool *vgroup_validmap; /* stores if vgroups tie to deforming bones or not */
const bool *lock_flags;
const bool *vgroup_locked; /* mask of locked defbones */
const bool *vgroup_unlocked; /* mask of unlocked defbones */
/* variables for multipaint */
const bool *defbase_sel; /* set of selected groups */
int defbase_tot_sel; /* number of selected groups */
bool do_multipaint; /* true if multipaint enabled and multiple groups selected */
bool do_lock_relative;
int defbase_tot;
/* original weight values for use in blur/smear */
float *precomputed_weight;
bool precomputed_weight_ready;
};
/* Initialize the stroke cache invariants from operator properties */
static void vwpaint_update_cache_invariants(
bContext *C, const VPaint *vp, SculptSession *ss, wmOperator *op, const float mouse[2])
{
StrokeCache *cache;
Scene *scene = CTX_data_scene(C);
UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings;
const Brush *brush = vp->paint.brush;
ViewContext *vc = paint_stroke_view_context(op->customdata);
Object *ob = CTX_data_active_object(C);
float mat[3][3];
float view_dir[3] = {0.0f, 0.0f, 1.0f};
int mode;
/* VW paint needs to allocate stroke cache before update is called. */
if (!ss->cache) {
cache = MEM_callocN(sizeof(StrokeCache), "stroke cache");
ss->cache = cache;
}
else {
cache = ss->cache;
}
/* Initial mouse location */
if (mouse) {
copy_v2_v2(cache->initial_mouse, mouse);
}
else {
zero_v2(cache->initial_mouse);
}
mode = RNA_enum_get(op->ptr, "mode");
cache->invert = mode == BRUSH_STROKE_INVERT;
cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH;
/* not very nice, but with current events system implementation
* we can't handle brush appearance inversion hotkey separately (sergey) */
if (cache->invert) {
ups->draw_inverted = true;
}
else {
ups->draw_inverted = false;
}
copy_v2_v2(cache->mouse, cache->initial_mouse);
/* Truly temporary data that isn't stored in properties */
cache->vc = vc;
cache->brush = brush;
cache->first_time = 1;
/* cache projection matrix */
ED_view3d_ob_project_mat_get(cache->vc->rv3d, ob, cache->projection_mat);
invert_m4_m4(ob->imat, ob->obmat);
copy_m3_m4(mat, cache->vc->rv3d->viewinv);
mul_m3_v3(mat, view_dir);
copy_m3_m4(mat, ob->imat);
mul_m3_v3(mat, view_dir);
normalize_v3_v3(cache->true_view_normal, view_dir);
copy_v3_v3(cache->view_normal, cache->true_view_normal);
cache->bstrength = BKE_brush_alpha_get(scene, brush);
cache->is_last_valid = false;
}
/* Initialize the stroke cache variants from operator properties */
static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr)
{
Scene *scene = CTX_data_scene(C);
SculptSession *ss = ob->sculpt;
StrokeCache *cache = ss->cache;
Brush *brush = BKE_paint_brush(&vp->paint);
/* This effects the actual brush radius, so things farther away
* are compared with a larger radius and vice versa. */
if (cache->first_time) {
RNA_float_get_array(ptr, "location", cache->true_location);
}
RNA_float_get_array(ptr, "mouse", cache->mouse);
/* XXX: Use pressure value from first brush step for brushes which don't
* support strokes (grab, thumb). They depends on initial state and
* brush coord/pressure/etc.
* It's more an events design issue, which doesn't split coordinate/pressure/angle
* changing events. We should avoid this after events system re-design */
if (paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT) || cache->first_time) {
cache->pressure = RNA_float_get(ptr, "pressure");
}
/* Truly temporary data that isn't stored in properties */
if (cache->first_time) {
cache->initial_radius = paint_calc_object_space_radius(
cache->vc, cache->true_location, BKE_brush_size_get(scene, brush));
BKE_brush_unprojected_radius_set(scene, brush, cache->initial_radius);
}
if (BKE_brush_use_size_pressure(brush) &&
paint_supports_dynamic_size(brush, PAINT_MODE_SCULPT)) {
cache->radius = cache->initial_radius * cache->pressure;
}
else {
cache->radius = cache->initial_radius;
}
cache->radius_squared = cache->radius * cache->radius;
if (ss->pbvh) {
BKE_pbvh_update_bounds(ss->pbvh, PBVH_UpdateRedraw | PBVH_UpdateBB);
}
}
static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2])
{
Scene *scene = CTX_data_scene(C);
struct PaintStroke *stroke = op->customdata;
ToolSettings *ts = scene->toolsettings;
Object *ob = CTX_data_active_object(C);
Mesh *me = BKE_mesh_from_object(ob);
struct WPaintData *wpd;
struct WPaintVGroupIndex vgroup_index;
int defbase_tot, defbase_tot_sel;
bool *defbase_sel;
SculptSession *ss = ob->sculpt;
VPaint *vp = CTX_data_tool_settings(C)->wpaint;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
if (ED_wpaint_ensure_data(C, op->reports, WPAINT_ENSURE_MIRROR, &vgroup_index) == false) {
return false;
}
{
/* check if we are attempting to paint onto a locked vertex group,
* and other options disallow it from doing anything useful */
bDeformGroup *dg;
dg = BLI_findlink(&me->vertex_group_names, vgroup_index.active);
if (dg->flag & DG_LOCK_WEIGHT) {
BKE_report(op->reports, RPT_WARNING, "Active group is locked, aborting");
return false;
}
if (vgroup_index.mirror != -1) {
dg = BLI_findlink(&me->vertex_group_names, vgroup_index.mirror);
if (dg->flag & DG_LOCK_WEIGHT) {
BKE_report(op->reports, RPT_WARNING, "Mirror group is locked, aborting");
return false;
}
}
}
/* check that multipaint groups are unlocked */
defbase_tot = BLI_listbase_count(&me->vertex_group_names);
defbase_sel = BKE_object_defgroup_selected_get(ob, defbase_tot, &defbase_tot_sel);
if (ts->multipaint && defbase_tot_sel > 1) {
int i;
bDeformGroup *dg;
if (ME_USING_MIRROR_X_VERTEX_GROUPS(me)) {
BKE_object_defgroup_mirror_selection(
ob, defbase_tot, defbase_sel, defbase_sel, &defbase_tot_sel);
}
for (i = 0; i < defbase_tot; i++) {
if (defbase_sel[i]) {
dg = BLI_findlink(&me->vertex_group_names, i);
if (dg->flag & DG_LOCK_WEIGHT) {
BKE_report(op->reports, RPT_WARNING, "Multipaint group is locked, aborting");
MEM_freeN(defbase_sel);
return false;
}
}
}
}
/* ALLOCATIONS! no return after this line */
/* make mode data storage */
wpd = MEM_callocN(sizeof(struct WPaintData), "WPaintData");
paint_stroke_set_mode_data(stroke, wpd);
ED_view3d_viewcontext_init(C, &wpd->vc, depsgraph);
view_angle_limits_init(&wpd->normal_angle_precalc,
vp->paint.brush->falloff_angle,
(vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0);
wpd->active.index = vgroup_index.active;
wpd->mirror.index = vgroup_index.mirror;
/* multipaint */
wpd->defbase_tot = defbase_tot;
wpd->defbase_sel = defbase_sel;
wpd->defbase_tot_sel = defbase_tot_sel > 1 ? defbase_tot_sel : 1;
wpd->do_multipaint = (ts->multipaint && defbase_tot_sel > 1);
/* set up auto-normalize, and generate map for detecting which
* vgroups affect deform bones */
wpd->lock_flags = BKE_object_defgroup_lock_flags_get(ob, wpd->defbase_tot);
if (ts->auto_normalize || ts->multipaint || wpd->lock_flags || ts->wpaint_lock_relative) {
wpd->vgroup_validmap = BKE_object_defgroup_validmap_get(ob, wpd->defbase_tot);
}
/* Compute the set of all locked deform groups when Lock Relative is active. */
if (ts->wpaint_lock_relative &&
BKE_object_defgroup_check_lock_relative(
wpd->lock_flags, wpd->vgroup_validmap, wpd->active.index) &&
(!wpd->do_multipaint || BKE_object_defgroup_check_lock_relative_multi(
defbase_tot, wpd->lock_flags, defbase_sel, defbase_tot_sel))) {
wpd->do_lock_relative = true;
}
if (wpd->do_lock_relative || (ts->auto_normalize && wpd->lock_flags && !wpd->do_multipaint)) {
bool *unlocked = MEM_dupallocN(wpd->vgroup_validmap);
if (wpd->lock_flags) {
bool *locked = MEM_mallocN(sizeof(bool) * wpd->defbase_tot, __func__);
BKE_object_defgroup_split_locked_validmap(
wpd->defbase_tot, wpd->lock_flags, wpd->vgroup_validmap, locked, unlocked);
wpd->vgroup_locked = locked;
}
wpd->vgroup_unlocked = unlocked;
}
if (wpd->do_multipaint && ts->auto_normalize) {
bool *tmpflags;
tmpflags = MEM_mallocN(sizeof(bool) * defbase_tot, __func__);
if (wpd->lock_flags) {
BLI_array_binary_or(tmpflags, wpd->defbase_sel, wpd->lock_flags, wpd->defbase_tot);
}
else {
memcpy(tmpflags, wpd->defbase_sel, sizeof(*tmpflags) * wpd->defbase_tot);
}
wpd->active.lock = tmpflags;
}
else if (ts->auto_normalize) {
bool *tmpflags;
tmpflags = wpd->lock_flags ? MEM_dupallocN(wpd->lock_flags) :
MEM_callocN(sizeof(bool) * defbase_tot, __func__);
tmpflags[wpd->active.index] = true;
wpd->active.lock = tmpflags;
tmpflags = wpd->lock_flags ? MEM_dupallocN(wpd->lock_flags) :
MEM_callocN(sizeof(bool) * defbase_tot, __func__);
tmpflags[(wpd->mirror.index != -1) ? wpd->mirror.index : wpd->active.index] = true;
wpd->mirror.lock = tmpflags;
}
if (ELEM(vp->paint.brush->weightpaint_tool, WPAINT_TOOL_SMEAR, WPAINT_TOOL_BLUR)) {
wpd->precomputed_weight = MEM_mallocN(sizeof(float) * me->totvert, __func__);
}
/* If not previously created, create vertex/weight paint mode session data */
vertex_paint_init_stroke(depsgraph, ob);
vwpaint_update_cache_invariants(C, vp, ss, op, mouse);
vertex_paint_init_session_data(ts, ob);
if (ob->sculpt->mode.wpaint.dvert_prev != NULL) {
MDeformVert *dv = ob->sculpt->mode.wpaint.dvert_prev;
for (int i = 0; i < me->totvert; i++, dv++) {
/* Use to show this isn't initialized, never apply to the mesh data. */
dv->flag = 1;
}
}
return true;
}
static float dot_vf3vs3(const float brushNormal[3], const short vertexNormal[3])
{
float normal[3];
normal_short_to_float_v3(normal, vertexNormal);
return dot_v3v3(brushNormal, normal);
}
static void get_brush_alpha_data(const Scene *scene,
const SculptSession *ss,
const Brush *brush,
float *r_brush_size_pressure,
float *r_brush_alpha_value,
float *r_brush_alpha_pressure)
{
*r_brush_size_pressure = BKE_brush_size_get(scene, brush) *
(BKE_brush_use_size_pressure(brush) ? ss->cache->pressure : 1.0f);
*r_brush_alpha_value = BKE_brush_alpha_get(scene, brush);
*r_brush_alpha_pressure = (BKE_brush_use_alpha_pressure(brush) ? ss->cache->pressure : 1.0f);
}
static float wpaint_get_active_weight(const MDeformVert *dv, const WeightPaintInfo *wpi)
{
float weight;
if (wpi->do_multipaint) {
weight = BKE_defvert_multipaint_collective_weight(
dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->is_normalized);
}
else {
weight = BKE_defvert_find_weight(dv, wpi->active.index);
}
if (wpi->do_lock_relative) {
weight = BKE_defvert_lock_relative_weight(
weight, dv, wpi->defbase_tot, wpi->vgroup_locked, wpi->vgroup_unlocked);
}
CLAMP(weight, 0.0f, 1.0f);
return weight;
}
static void do_wpaint_precompute_weight_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
const MDeformVert *dv = &data->me->dvert[n];
data->wpd->precomputed_weight[n] = wpaint_get_active_weight(dv, data->wpi);
}
static void precompute_weight_values(
bContext *C, Object *ob, Brush *brush, struct WPaintData *wpd, WeightPaintInfo *wpi, Mesh *me)
{
if (wpd->precomputed_weight_ready && !brush_use_accumulate_ex(brush, ob->mode)) {
return;
}
/* threaded loop over vertices */
SculptThreadedTaskData data = {
.C = C,
.ob = ob,
.wpd = wpd,
.wpi = wpi,
.me = me,
};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
BLI_task_parallel_range(0, me->totvert, &data, do_wpaint_precompute_weight_cb_ex, &settings);
wpd->precomputed_weight_ready = true;
}
static void do_wpaint_brush_blur_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap;
const Brush *brush = data->brush;
const StrokeCache *cache = ss->cache;
Scene *scene = CTX_data_scene(data->C);
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* For grid based pbvh, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const char v_flag = data->me->mvert[v_index].flag;
/* If the vertex is selected */
if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) {
/* Get the average poly weight */
int total_hit_loops = 0;
float weight_final = 0.0f;
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const MPoly *mp = &data->me->mpoly[p_index];
total_hit_loops += mp->totloop;
for (int k = 0; k < mp->totloop; k++) {
const int l_index = mp->loopstart + k;
const MLoop *ml = &data->me->mloop[l_index];
weight_final += data->wpd->precomputed_weight[ml->v];
}
}
/* Apply the weight to the vertex. */
if (total_hit_loops != 0) {
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
const float final_alpha = brush_fade * brush_strength * grid_alpha *
brush_alpha_pressure;
if ((brush->flag & BRUSH_ACCUMULATE) == 0) {
if (ss->mode.wpaint.alpha_weight[v_index] < final_alpha) {
ss->mode.wpaint.alpha_weight[v_index] = final_alpha;
}
else {
continue;
}
}
weight_final /= total_hit_loops;
/* Only paint visible verts */
do_weight_paint_vertex(
data->vp, data->ob, data->wpi, v_index, final_alpha, weight_final);
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_wpaint_brush_smear_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap;
const Brush *brush = data->brush;
const Scene *scene = CTX_data_scene(data->C);
const StrokeCache *cache = ss->cache;
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
float brush_dir[3];
sub_v3_v3v3(brush_dir, cache->location, cache->last_location);
project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal);
if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) {
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* For grid based pbvh, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const MVert *mv_curr = &data->me->mvert[v_index];
/* If the vertex is selected */
if (!(use_face_sel || use_vert_sel) || mv_curr->flag & SELECT) {
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) {
bool do_color = false;
/* Minimum dot product between brush direction and current
* to neighbor direction is 0.0, meaning orthogonal. */
float stroke_dot_max = 0.0f;
/* Get the color of the loop in the opposite direction of the brush movement
* (this callback is specifically for smear.) */
float weight_final = 0.0;
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const MPoly *mp = &data->me->mpoly[p_index];
const MLoop *ml_other = &data->me->mloop[mp->loopstart];
for (int k = 0; k < mp->totloop; k++, ml_other++) {
const uint v_other_index = ml_other->v;
if (v_other_index != v_index) {
const MVert *mv_other = &data->me->mvert[v_other_index];
/* Get the direction from the selected vert to the neighbor. */
float other_dir[3];
sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co);
project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal);
normalize_v3(other_dir);
const float stroke_dot = dot_v3v3(other_dir, brush_dir);
if (stroke_dot > stroke_dot_max) {
stroke_dot_max = stroke_dot;
weight_final = data->wpd->precomputed_weight[v_other_index];
do_color = true;
}
}
}
}
/* Apply weight to vertex */
if (do_color) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
const float final_alpha = brush_fade * brush_strength * grid_alpha *
brush_alpha_pressure;
if (final_alpha <= 0.0f) {
continue;
}
do_weight_paint_vertex(
data->vp, data->ob, data->wpi, v_index, final_alpha, (float)weight_final);
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
}
static void do_wpaint_brush_draw_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const Scene *scene = CTX_data_scene(data->C);
const Brush *brush = data->brush;
const StrokeCache *cache = ss->cache;
/* NOTE: normally `BKE_brush_weight_get(scene, brush)` is used,
* however in this case we calculate a new weight each time. */
const float paintweight = data->strength;
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* NOTE: grids are 1:1 with corners (aka loops).
* For multires, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const char v_flag = data->me->mvert[v_index].flag;
/* If the vertex is selected */
if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) {
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->wpd->normal_angle_precalc, angle_cos, &brush_strength))) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
const float final_alpha = brush_fade * brush_strength * grid_alpha *
brush_alpha_pressure;
if ((brush->flag & BRUSH_ACCUMULATE) == 0) {
if (ss->mode.wpaint.alpha_weight[v_index] < final_alpha) {
ss->mode.wpaint.alpha_weight[v_index] = final_alpha;
}
else {
continue;
}
}
do_weight_paint_vertex(data->vp, data->ob, data->wpi, v_index, final_alpha, paintweight);
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_wpaint_brush_calc_average_weight_cb_ex(
void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
StrokeCache *cache = ss->cache;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
const bool use_vert_sel = (data->me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0;
struct WPaintAverageAccum *accum = (struct WPaintAverageAccum *)data->custom_data + n;
accum->len = 0;
accum->value = 0.0;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
const float angle_cos = (use_normal && vd.no) ? dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (angle_cos > 0.0 &&
BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) > 0.0) {
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
// const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const char v_flag = data->me->mvert[v_index].flag;
/* If the vertex is selected. */
if (!(use_face_sel || use_vert_sel) || v_flag & SELECT) {
const MDeformVert *dv = &data->me->dvert[v_index];
accum->len += 1;
accum->value += wpaint_get_active_weight(dv, data->wpi);
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void calculate_average_weight(SculptThreadedTaskData *data,
PBVHNode **UNUSED(nodes),
int totnode)
{
struct WPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__);
data->custom_data = accum;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, data, do_wpaint_brush_calc_average_weight_cb_ex, &settings);
uint accum_len = 0;
double accum_weight = 0.0;
for (int i = 0; i < totnode; i++) {
accum_len += accum[i].len;
accum_weight += accum[i].value;
}
if (accum_len != 0) {
accum_weight /= accum_len;
data->strength = (float)accum_weight;
}
MEM_SAFE_FREE(data->custom_data); /* 'accum' */
}
static void wpaint_paint_leaves(bContext *C,
Object *ob,
Sculpt *sd,
VPaint *vp,
struct WPaintData *wpd,
WeightPaintInfo *wpi,
Mesh *me,
PBVHNode **nodes,
int totnode)
{
Scene *scene = CTX_data_scene(C);
const Brush *brush = ob->sculpt->cache->brush;
/* threaded loop over nodes */
SculptThreadedTaskData data = {
.C = C,
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.vp = vp,
.wpd = wpd,
.wpi = wpi,
.me = me,
};
/* Use this so average can modify its weight without touching the brush. */
data.strength = BKE_brush_weight_get(scene, brush);
/* NOTE: current mirroring code cannot be run in parallel */
TaskParallelSettings settings;
const bool use_threading = !ME_USING_MIRROR_X_VERTEX_GROUPS(me);
BKE_pbvh_parallel_range_settings(&settings, use_threading, totnode);
switch ((eBrushWeightPaintTool)brush->weightpaint_tool) {
case WPAINT_TOOL_AVERAGE:
calculate_average_weight(&data, nodes, totnode);
BLI_task_parallel_range(0, totnode, &data, do_wpaint_brush_draw_task_cb_ex, &settings);
break;
case WPAINT_TOOL_SMEAR:
BLI_task_parallel_range(0, totnode, &data, do_wpaint_brush_smear_task_cb_ex, &settings);
break;
case WPAINT_TOOL_BLUR:
BLI_task_parallel_range(0, totnode, &data, do_wpaint_brush_blur_task_cb_ex, &settings);
break;
case WPAINT_TOOL_DRAW:
BLI_task_parallel_range(0, totnode, &data, do_wpaint_brush_draw_task_cb_ex, &settings);
break;
}
}
static PBVHNode **vwpaint_pbvh_gather_generic(
Object *ob, VPaint *wp, Sculpt *sd, Brush *brush, int *r_totnode)
{
SculptSession *ss = ob->sculpt;
const bool use_normal = vwpaint_use_normal(wp);
PBVHNode **nodes = NULL;
/* Build a list of all nodes that are potentially within the brush's area of influence */
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
SculptSearchSphereData data = {
.ss = ss,
.sd = sd,
.radius_squared = ss->cache->radius_squared,
.original = true,
};
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode);
if (use_normal) {
SCULPT_pbvh_calc_area_normal(
brush, ob, nodes, *r_totnode, true, ss->cache->sculpt_normal_symm);
}
else {
zero_v3(ss->cache->sculpt_normal_symm);
}
}
else {
struct DistRayAABB_Precalc dist_ray_to_aabb_precalc;
dist_squared_ray_to_aabb_v3_precalc(
&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal);
SculptSearchCircleData data = {
.ss = ss,
.sd = sd,
.radius_squared = ss->cache->radius_squared,
.original = true,
.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc,
};
BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode);
if (use_normal) {
copy_v3_v3(ss->cache->sculpt_normal_symm, ss->cache->view_normal);
}
else {
zero_v3(ss->cache->sculpt_normal_symm);
}
}
return nodes;
}
static void wpaint_do_paint(bContext *C,
Object *ob,
VPaint *wp,
Sculpt *sd,
struct WPaintData *wpd,
WeightPaintInfo *wpi,
Mesh *me,
Brush *brush,
const char symm,
const int axis,
const int i,
const float angle)
{
SculptSession *ss = ob->sculpt;
ss->cache->radial_symmetry_pass = i;
SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle);
int totnode;
PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, wp, sd, brush, &totnode);
wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes, totnode);
if (nodes) {
MEM_freeN(nodes);
}
}
static void wpaint_do_radial_symmetry(bContext *C,
Object *ob,
VPaint *wp,
Sculpt *sd,
struct WPaintData *wpd,
WeightPaintInfo *wpi,
Mesh *me,
Brush *brush,
const char symm,
const int axis)
{
for (int i = 1; i < wp->radial_symm[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / wp->radial_symm[axis - 'X'];
wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, axis, i, angle);
}
}
/* near duplicate of: sculpt.c's,
* 'do_symmetrical_brush_actions' and 'vpaint_do_symmetrical_brush_actions'. */
static void wpaint_do_symmetrical_brush_actions(
bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi)
{
Brush *brush = BKE_paint_brush(&wp->paint);
Mesh *me = ob->data;
SculptSession *ss = ob->sculpt;
StrokeCache *cache = ss->cache;
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
int i = 0;
/* initial stroke */
cache->mirror_symmetry_pass = 0;
wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X', 0, 0);
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'X');
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Y');
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, 0, 'Z');
cache->symmetry = symm;
if (me->editflag & ME_EDIT_MIRROR_VERTEX_GROUPS) {
/* We don't do any symmetry strokes when mirroring vertex groups. */
copy_v3_v3(cache->true_last_location, cache->true_location);
cache->is_last_valid = true;
return;
}
/* symm is a bit combination of XYZ - 1 is mirror
* X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */
for (i = 1; i <= symm; i++) {
if ((symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5)))) {
cache->mirror_symmetry_pass = i;
cache->radial_symmetry_pass = 0;
SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0);
if (i & (1 << 0)) {
wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X', 0, 0);
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'X');
}
if (i & (1 << 1)) {
wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y', 0, 0);
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Y');
}
if (i & (1 << 2)) {
wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z', 0, 0);
wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, i, 'Z');
}
}
}
copy_v3_v3(cache->true_last_location, cache->true_location);
cache->is_last_valid = true;
}
static void wpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
VPaint *wp = ts->wpaint;
Brush *brush = BKE_paint_brush(&wp->paint);
struct WPaintData *wpd = paint_stroke_mode_data(stroke);
ViewContext *vc;
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
vwpaint_update_cache_variants(C, wp, ob, itemptr);
float mat[4][4];
const float brush_alpha_value = BKE_brush_alpha_get(scene, brush);
/* intentionally don't initialize as NULL, make sure we initialize all members below */
WeightPaintInfo wpi;
/* cannot paint if there is no stroke data */
if (wpd == NULL) {
/* XXX: force a redraw here, since even though we can't paint,
* at least view won't freeze until stroke ends */
ED_region_tag_redraw(CTX_wm_region(C));
return;
}
vc = &wpd->vc;
ob = vc->obact;
view3d_operator_needs_opengl(C);
ED_view3d_init_mats_rv3d(ob, vc->rv3d);
/* load projection matrix */
mul_m4_m4m4(mat, vc->rv3d->persmat, ob->obmat);
/* *** setup WeightPaintInfo - pass onto do_weight_paint_vertex *** */
wpi.defbase_tot = wpd->defbase_tot;
wpi.defbase_sel = wpd->defbase_sel;
wpi.defbase_tot_sel = wpd->defbase_tot_sel;
wpi.defbase_tot_unsel = wpi.defbase_tot - wpi.defbase_tot_sel;
wpi.active = wpd->active;
wpi.mirror = wpd->mirror;
wpi.lock_flags = wpd->lock_flags;
wpi.vgroup_validmap = wpd->vgroup_validmap;
wpi.vgroup_locked = wpd->vgroup_locked;
wpi.vgroup_unlocked = wpd->vgroup_unlocked;
wpi.do_flip = RNA_boolean_get(itemptr, "pen_flip");
wpi.do_multipaint = wpd->do_multipaint;
wpi.do_auto_normalize = ((ts->auto_normalize != 0) && (wpi.vgroup_validmap != NULL) &&
(wpi.do_multipaint || wpi.vgroup_validmap[wpi.active.index]));
wpi.do_lock_relative = wpd->do_lock_relative;
wpi.is_normalized = wpi.do_auto_normalize || wpi.do_lock_relative;
wpi.brush_alpha_value = brush_alpha_value;
/* *** done setting up WeightPaintInfo *** */
if (wpd->precomputed_weight) {
precompute_weight_values(C, ob, brush, wpd, &wpi, ob->data);
}
wpaint_do_symmetrical_brush_actions(C, ob, wp, sd, wpd, &wpi);
swap_m4m4(vc->rv3d->persmat, mat);
/* Calculate pivot for rotation around selection if needed.
* also needed for "Frame Selected" on last stroke. */
float loc_world[3];
mul_v3_m4v3(loc_world, ob->obmat, ss->cache->true_location);
paint_last_stroke_update(scene, loc_world);
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
DEG_id_tag_update(ob->data, 0);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
swap_m4m4(wpd->vc.rv3d->persmat, mat);
rcti r;
if (SCULPT_get_redraw_rect(vc->region, CTX_wm_region_view3d(C), ob, &r)) {
if (ss->cache) {
ss->cache->current_r = r;
}
/* previous is not set in the current cache else
* the partial rect will always grow */
if (ss->cache) {
if (!BLI_rcti_is_empty(&ss->cache->previous_r)) {
BLI_rcti_union(&r, &ss->cache->previous_r);
}
}
r.xmin += vc->region->winrct.xmin - 2;
r.xmax += vc->region->winrct.xmin + 2;
r.ymin += vc->region->winrct.ymin - 2;
r.ymax += vc->region->winrct.ymin + 2;
}
ED_region_tag_redraw_partial(vc->region, &r, true);
}
static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke)
{
Object *ob = CTX_data_active_object(C);
struct WPaintData *wpd = paint_stroke_mode_data(stroke);
if (wpd) {
if (wpd->defbase_sel) {
MEM_freeN((void *)wpd->defbase_sel);
}
if (wpd->vgroup_validmap) {
MEM_freeN((void *)wpd->vgroup_validmap);
}
if (wpd->vgroup_locked) {
MEM_freeN((void *)wpd->vgroup_locked);
}
if (wpd->vgroup_unlocked) {
MEM_freeN((void *)wpd->vgroup_unlocked);
}
if (wpd->lock_flags) {
MEM_freeN((void *)wpd->lock_flags);
}
if (wpd->active.lock) {
MEM_freeN((void *)wpd->active.lock);
}
if (wpd->mirror.lock) {
MEM_freeN((void *)wpd->mirror.lock);
}
if (wpd->precomputed_weight) {
MEM_freeN(wpd->precomputed_weight);
}
MEM_freeN(wpd);
}
/* and particles too */
if (ob->particlesystem.first) {
ParticleSystem *psys;
int i;
for (psys = ob->particlesystem.first; psys; psys = psys->next) {
for (i = 0; i < PSYS_TOT_VG; i++) {
if (psys->vgroup[i] == BKE_object_defgroup_active_index_get(ob)) {
psys->recalc |= ID_RECALC_PSYS_RESET;
break;
}
}
}
}
DEG_id_tag_update(ob->data, 0);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
int retval;
op->customdata = paint_stroke_new(C,
op,
SCULPT_stroke_get_location,
wpaint_stroke_test_start,
wpaint_stroke_update_step,
NULL,
wpaint_stroke_done,
event->type);
if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) {
paint_stroke_free(C, op);
return OPERATOR_FINISHED;
}
/* add modal handler */
WM_event_add_modal_handler(C, op);
OPERATOR_RETVAL_CHECK(retval);
BLI_assert(retval == OPERATOR_RUNNING_MODAL);
return OPERATOR_RUNNING_MODAL;
}
static int wpaint_exec(bContext *C, wmOperator *op)
{
op->customdata = paint_stroke_new(C,
op,
SCULPT_stroke_get_location,
wpaint_stroke_test_start,
wpaint_stroke_update_step,
NULL,
wpaint_stroke_done,
0);
/* frees op->customdata */
paint_stroke_exec(C, op);
return OPERATOR_FINISHED;
}
static void wpaint_cancel(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
if (ob->sculpt->cache) {
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
paint_stroke_cancel(C, op);
}
void PAINT_OT_weight_paint(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Weight Paint";
ot->idname = "PAINT_OT_weight_paint";
ot->description = "Paint a stroke in the current vertex group's weights";
/* api callbacks */
ot->invoke = wpaint_invoke;
ot->modal = paint_stroke_modal;
ot->exec = wpaint_exec;
ot->poll = weight_paint_poll;
ot->cancel = wpaint_cancel;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
paint_stroke_operator_properties(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Toggle Vertex Paint Operator
* \{ */
/**
* \note Keep in sync with #wpaint_mode_toggle_exec
*/
static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
struct wmMsgBus *mbus = CTX_wm_message_bus(C);
Object *ob = CTX_data_active_object(C);
const int mode_flag = OB_MODE_VERTEX_PAINT;
const bool is_mode_set = (ob->mode & mode_flag) != 0;
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
if (!is_mode_set) {
if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
return OPERATOR_CANCELLED;
}
}
Mesh *me = BKE_mesh_from_object(ob);
/* toggle: end vpaint */
if (is_mode_set) {
ED_object_vpaintmode_exit_ex(ob);
}
else {
Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C);
if (depsgraph) {
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_vpaintmode_enter_ex(bmain, depsgraph, scene, ob);
BKE_paint_toolslots_brush_validate(bmain, &ts->vpaint->paint);
}
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
/* update modifier stack for mapping requirements */
DEG_id_tag_update(&me->id, 0);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene);
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
WM_toolsystem_update_from_context_view3d(C);
return OPERATOR_FINISHED;
}
void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint Mode";
ot->idname = "PAINT_OT_vertex_paint_toggle";
ot->description = "Toggle the vertex paint mode in 3D view";
/* api callbacks */
ot->exec = vpaint_mode_toggle_exec;
ot->poll = paint_mode_toggle_poll_test;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Vertex Paint Operator
* \{ */
/* Implementation notes:
*
* Operator->invoke()
* - Validate context (add #Mesh.mloopcol).
* - Create custom-data storage.
* - Call paint once (mouse click).
* - Add modal handler.
*
* Operator->modal()
* - For every mouse-move, apply vertex paint.
* - Exit on mouse release, free custom-data.
* (return OPERATOR_FINISHED also removes handler and operator)
*
* For future:
* - implement a stroke event (or mouse-move with past positions).
* - revise whether op->customdata should be added in object, in set_vpaint.
*/
struct VPaintData {
ViewContext vc;
struct NormalAnglePrecalc normal_angle_precalc;
uint paintcol;
struct VertProjHandle *vp_handle;
struct CoNo *vertexcosnos;
/**
* Modify #Mesh.mloopcol directly, since the derived mesh is drawing from this
* array, otherwise we need to refresh the modifier stack.
*/
bool use_fast_update;
/* loops tagged as having been painted, to apply shared vertex color
* blending only to modified loops */
bool *mlooptag;
bool is_texbrush;
/* Special storage for smear brush, avoid feedback loop - update each step. */
struct {
uint *color_prev;
uint *color_curr;
} smear;
};
static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2])
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
struct PaintStroke *stroke = op->customdata;
VPaint *vp = ts->vpaint;
Brush *brush = BKE_paint_brush(&vp->paint);
struct VPaintData *vpd;
Object *ob = CTX_data_active_object(C);
Mesh *me;
SculptSession *ss = ob->sculpt;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
/* context checks could be a poll() */
me = BKE_mesh_from_object(ob);
if (me == NULL || me->totpoly == 0) {
return false;
}
ED_mesh_color_ensure(me, NULL);
if (me->mloopcol == NULL) {
return false;
}
/* make mode data storage */
vpd = MEM_callocN(sizeof(*vpd), "VPaintData");
paint_stroke_set_mode_data(stroke, vpd);
ED_view3d_viewcontext_init(C, &vpd->vc, depsgraph);
view_angle_limits_init(&vpd->normal_angle_precalc,
vp->paint.brush->falloff_angle,
(vp->paint.brush->flag & BRUSH_FRONTFACE_FALLOFF) != 0);
vpd->paintcol = vpaint_get_current_col(
scene, vp, (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_INVERT));
vpd->is_texbrush = !(brush->vertexpaint_tool == VPAINT_TOOL_BLUR) && brush->mtex.tex;
/* are we painting onto a modified mesh?,
* if not we can skip face map trickiness */
if (vertex_paint_use_fast_update_check(ob)) {
vpd->use_fast_update = true;
// printf("Fast update!\n");
}
else {
vpd->use_fast_update = false;
// printf("No fast update!\n");
}
/* to keep tracked of modified loops for shared vertex color blending */
if (brush->vertexpaint_tool == VPAINT_TOOL_BLUR) {
vpd->mlooptag = MEM_mallocN(sizeof(bool) * me->totloop, "VPaintData mlooptag");
}
if (brush->vertexpaint_tool == VPAINT_TOOL_SMEAR) {
vpd->smear.color_prev = MEM_mallocN(sizeof(uint) * me->totloop, __func__);
memcpy(vpd->smear.color_prev, me->mloopcol, sizeof(uint) * me->totloop);
vpd->smear.color_curr = MEM_dupallocN(vpd->smear.color_prev);
}
/* Create projection handle */
if (vpd->is_texbrush) {
ob->sculpt->building_vp_handle = true;
vpd->vp_handle = ED_vpaint_proj_handle_create(depsgraph, scene, ob, &vpd->vertexcosnos);
ob->sculpt->building_vp_handle = false;
}
/* If not previously created, create vertex/weight paint mode session data */
vertex_paint_init_stroke(depsgraph, ob);
vwpaint_update_cache_invariants(C, vp, ss, op, mouse);
vertex_paint_init_session_data(ts, ob);
if (ob->sculpt->mode.vpaint.previous_color != NULL) {
memset(ob->sculpt->mode.vpaint.previous_color, 0, sizeof(uint) * me->totloop);
}
return true;
}
static void do_vpaint_brush_calc_average_color_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
StrokeCache *cache = ss->cache;
uint *lcol = data->lcol;
char *col;
const bool use_vert_sel = (data->me->editflag &
(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
struct VPaintAverageAccum *accum = (struct VPaintAverageAccum *)data->custom_data + n;
accum->len = 0;
memset(accum->value, 0, sizeof(accum->value));
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
if (BKE_brush_curve_strength(data->brush, 0.0, cache->radius) > 0.0) {
/* If the vertex is selected for painting. */
const MVert *mv = &data->me->mvert[v_index];
if (!use_vert_sel || mv->flag & SELECT) {
accum->len += gmap->vert_to_loop[v_index].count;
/* if a vertex is within the brush region, then add its color to the blend. */
for (int j = 0; j < gmap->vert_to_loop[v_index].count; j++) {
const int l_index = gmap->vert_to_loop[v_index].indices[j];
col = (char *)(&lcol[l_index]);
/* Color is squared to compensate the sqrt color encoding. */
accum->value[0] += col[0] * col[0];
accum->value[1] += col[1] * col[1];
accum->value[2] += col[2] * col[2];
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static float tex_color_alpha_ubyte(SculptThreadedTaskData *data,
const float v_co[3],
uint *r_color)
{
float rgba[4];
float rgba_br[3];
tex_color_alpha(data->vp, &data->vpd->vc, v_co, rgba);
rgb_uchar_to_float(rgba_br, (const uchar *)&data->vpd->paintcol);
mul_v3_v3(rgba_br, rgba);
rgb_float_to_uchar((uchar *)r_color, rgba_br);
return rgba[3];
}
static void do_vpaint_brush_draw_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
const Brush *brush = data->brush;
const StrokeCache *cache = ss->cache;
uint *lcol = data->lcol;
const Scene *scene = CTX_data_scene(data->C);
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_vert_sel = (data->me->editflag &
(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* NOTE: Grids are 1:1 with corners (aka loops).
* For grid based pbvh, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const MVert *mv = &data->me->mvert[v_index];
/* If the vertex is selected for painting. */
if (!use_vert_sel || mv->flag & SELECT) {
/* Calc the dot prod. between ray norm on surf and current vert
* (ie splash prevention factor), and only paint front facing verts. */
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
uint color_final = data->vpd->paintcol;
/* If we're painting with a texture, sample the texture color and alpha. */
float tex_alpha = 1.0;
if (data->vpd->is_texbrush) {
/* NOTE: we may want to paint alpha as vertex color alpha. */
tex_alpha = tex_color_alpha_ubyte(
data, data->vpd->vertexcosnos[v_index].co, &color_final);
}
/* For each poly owning this vert, paint each loop belonging to this vert. */
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const int l_index = gmap->vert_to_loop[v_index].indices[j];
BLI_assert(data->me->mloop[l_index].v == v_index);
const MPoly *mp = &data->me->mpoly[p_index];
if (!use_face_sel || mp->flag & ME_FACE_SEL) {
uint color_orig = 0; /* unused when array is NULL */
if (ss->mode.vpaint.previous_color != NULL) {
/* Get the previous loop color */
if (ss->mode.vpaint.previous_color[l_index] == 0) {
ss->mode.vpaint.previous_color[l_index] = lcol[l_index];
}
color_orig = ss->mode.vpaint.previous_color[l_index];
}
const float final_alpha = 255 * brush_fade * brush_strength * tex_alpha *
brush_alpha_pressure * grid_alpha;
/* Mix the new color with the original based on final_alpha. */
lcol[l_index] = vpaint_blend(data->vp,
lcol[l_index],
color_orig,
color_final,
final_alpha,
255 * brush_strength);
}
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_vpaint_brush_blur_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
Scene *scene = CTX_data_scene(data->C);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
const Brush *brush = data->brush;
const StrokeCache *cache = ss->cache;
uint *lcol = data->lcol;
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_vert_sel = (data->me->editflag &
(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* For grid based pbvh, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const MVert *mv = &data->me->mvert[v_index];
/* If the vertex is selected for painting. */
if (!use_vert_sel || mv->flag & SELECT) {
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
/* Get the average poly color */
uint color_final = 0;
int total_hit_loops = 0;
uint blend[4] = {0};
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
int p_index = gmap->vert_to_poly[v_index].indices[j];
const MPoly *mp = &data->me->mpoly[p_index];
if (!use_face_sel || mp->flag & ME_FACE_SEL) {
total_hit_loops += mp->totloop;
for (int k = 0; k < mp->totloop; k++) {
const uint l_index = mp->loopstart + k;
const char *col = (const char *)(&lcol[l_index]);
/* Color is squared to compensate the sqrt color encoding. */
blend[0] += (uint)col[0] * (uint)col[0];
blend[1] += (uint)col[1] * (uint)col[1];
blend[2] += (uint)col[2] * (uint)col[2];
blend[3] += (uint)col[3] * (uint)col[3];
}
}
}
if (total_hit_loops != 0) {
/* Use rgb^2 color averaging. */
char *col = (char *)(&color_final);
col[0] = round_fl_to_uchar(sqrtf(divide_round_i(blend[0], total_hit_loops)));
col[1] = round_fl_to_uchar(sqrtf(divide_round_i(blend[1], total_hit_loops)));
col[2] = round_fl_to_uchar(sqrtf(divide_round_i(blend[2], total_hit_loops)));
col[3] = round_fl_to_uchar(sqrtf(divide_round_i(blend[3], total_hit_loops)));
/* For each poly owning this vert,
* paint each loop belonging to this vert. */
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const int l_index = gmap->vert_to_loop[v_index].indices[j];
BLI_assert(data->me->mloop[l_index].v == v_index);
const MPoly *mp = &data->me->mpoly[p_index];
if (!use_face_sel || mp->flag & ME_FACE_SEL) {
uint color_orig = 0; /* unused when array is NULL */
if (ss->mode.vpaint.previous_color != NULL) {
/* Get the previous loop color */
if (ss->mode.vpaint.previous_color[l_index] == 0) {
ss->mode.vpaint.previous_color[l_index] = lcol[l_index];
}
color_orig = ss->mode.vpaint.previous_color[l_index];
}
const float final_alpha = 255 * brush_fade * brush_strength *
brush_alpha_pressure * grid_alpha;
/* Mix the new color with the original
* based on the brush strength and the curve. */
lcol[l_index] = vpaint_blend(data->vp,
lcol[l_index],
color_orig,
*((uint *)col),
final_alpha,
255 * brush_strength);
}
}
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
static void do_vpaint_brush_smear_task_cb_ex(void *__restrict userdata,
const int n,
const TaskParallelTLS *__restrict UNUSED(tls))
{
SculptThreadedTaskData *data = userdata;
SculptSession *ss = data->ob->sculpt;
const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh);
const bool has_grids = (pbvh_type == PBVH_GRIDS);
Scene *scene = CTX_data_scene(data->C);
const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap;
const Brush *brush = data->brush;
const StrokeCache *cache = ss->cache;
uint *lcol = data->lcol;
float brush_size_pressure, brush_alpha_value, brush_alpha_pressure;
get_brush_alpha_data(
scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure);
float brush_dir[3];
const bool use_normal = vwpaint_use_normal(data->vp);
const bool use_vert_sel = (data->me->editflag &
(ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0;
const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0;
sub_v3_v3v3(brush_dir, cache->location, cache->last_location);
project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal);
if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) {
SculptBrushTest test;
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
ss, &test, data->brush->falloff_shape);
const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape(
ss, data->brush->falloff_shape);
/* For each vertex */
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
/* Test to see if the vertex coordinates are within the spherical brush region. */
if (sculpt_brush_test_sq_fn(&test, vd.co)) {
/* For grid based pbvh, take the vert whose loop corresponds to the current grid.
* Otherwise, take the current vert. */
const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v :
vd.vert_indices[vd.i];
const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f;
const MVert *mv_curr = &data->me->mvert[v_index];
/* if the vertex is selected for painting. */
if (!use_vert_sel || mv_curr->flag & SELECT) {
/* Calc the dot prod. between ray norm on surf and current vert
* (ie splash prevention factor), and only paint front facing verts. */
float brush_strength = cache->bstrength;
const float angle_cos = (use_normal && vd.no) ?
dot_vf3vs3(sculpt_normal_frontface, vd.no) :
1.0f;
if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) &&
((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 ||
view_angle_limits_apply_falloff(
&data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) {
const float brush_fade = BKE_brush_curve_strength(
brush, sqrtf(test.dist), cache->radius);
bool do_color = false;
/* Minimum dot product between brush direction and current
* to neighbor direction is 0.0, meaning orthogonal. */
float stroke_dot_max = 0.0f;
/* Get the color of the loop in the opposite
* direction of the brush movement */
uint color_final = 0;
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const int l_index = gmap->vert_to_loop[v_index].indices[j];
BLI_assert(data->me->mloop[l_index].v == v_index);
UNUSED_VARS_NDEBUG(l_index);
const MPoly *mp = &data->me->mpoly[p_index];
if (!use_face_sel || mp->flag & ME_FACE_SEL) {
const MLoop *ml_other = &data->me->mloop[mp->loopstart];
for (int k = 0; k < mp->totloop; k++, ml_other++) {
const uint v_other_index = ml_other->v;
if (v_other_index != v_index) {
const MVert *mv_other = &data->me->mvert[v_other_index];
/* Get the direction from the
* selected vert to the neighbor. */
float other_dir[3];
sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co);
project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal);
normalize_v3(other_dir);
const float stroke_dot = dot_v3v3(other_dir, brush_dir);
if (stroke_dot > stroke_dot_max) {
stroke_dot_max = stroke_dot;
color_final = data->vpd->smear.color_prev[mp->loopstart + k];
do_color = true;
}
}
}
}
}
if (do_color) {
const float final_alpha = 255 * brush_fade * brush_strength * brush_alpha_pressure *
grid_alpha;
/* For each poly owning this vert,
* paint each loop belonging to this vert. */
for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) {
const int p_index = gmap->vert_to_poly[v_index].indices[j];
const int l_index = gmap->vert_to_loop[v_index].indices[j];
BLI_assert(data->me->mloop[l_index].v == v_index);
const MPoly *mp = &data->me->mpoly[p_index];
if (!use_face_sel || mp->flag & ME_FACE_SEL) {
/* Get the previous loop color */
uint color_orig = 0; /* unused when array is NULL */
if (ss->mode.vpaint.previous_color != NULL) {
/* Get the previous loop color */
if (ss->mode.vpaint.previous_color[l_index] == 0) {
ss->mode.vpaint.previous_color[l_index] = lcol[l_index];
}
color_orig = ss->mode.vpaint.previous_color[l_index];
}
/* Mix the new color with the original
* based on the brush strength and the curve. */
lcol[l_index] = vpaint_blend(data->vp,
lcol[l_index],
color_orig,
color_final,
final_alpha,
255 * brush_strength);
data->vpd->smear.color_curr[l_index] = lcol[l_index];
}
}
}
}
}
}
}
BKE_pbvh_vertex_iter_end;
}
}
static void calculate_average_color(SculptThreadedTaskData *data,
PBVHNode **UNUSED(nodes),
int totnode)
{
struct VPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__);
data->custom_data = accum;
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
BLI_task_parallel_range(0, totnode, data, do_vpaint_brush_calc_average_color_cb_ex, &settings);
uint accum_len = 0;
uint accum_value[3] = {0};
uchar blend[4] = {0};
for (int i = 0; i < totnode; i++) {
accum_len += accum[i].len;
accum_value[0] += accum[i].value[0];
accum_value[1] += accum[i].value[1];
accum_value[2] += accum[i].value[2];
}
if (accum_len != 0) {
blend[0] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[0], accum_len)));
blend[1] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[1], accum_len)));
blend[2] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[2], accum_len)));
blend[3] = 255;
data->vpd->paintcol = *((uint *)blend);
}
MEM_SAFE_FREE(data->custom_data); /* 'accum' */
}
static void vpaint_paint_leaves(bContext *C,
Sculpt *sd,
VPaint *vp,
struct VPaintData *vpd,
Object *ob,
Mesh *me,
PBVHNode **nodes,
int totnode)
{
const Brush *brush = ob->sculpt->cache->brush;
SculptThreadedTaskData data = {
.C = C,
.sd = sd,
.ob = ob,
.brush = brush,
.nodes = nodes,
.vp = vp,
.vpd = vpd,
.lcol = (uint *)me->mloopcol,
.me = me,
};
TaskParallelSettings settings;
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
switch ((eBrushVertexPaintTool)brush->vertexpaint_tool) {
case VPAINT_TOOL_AVERAGE:
calculate_average_color(&data, nodes, totnode);
BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_draw_task_cb_ex, &settings);
break;
case VPAINT_TOOL_BLUR:
BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_blur_task_cb_ex, &settings);
break;
case VPAINT_TOOL_SMEAR:
BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_smear_task_cb_ex, &settings);
break;
case VPAINT_TOOL_DRAW:
BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_draw_task_cb_ex, &settings);
break;
}
}
static void vpaint_do_paint(bContext *C,
Sculpt *sd,
VPaint *vp,
struct VPaintData *vpd,
Object *ob,
Mesh *me,
Brush *brush,
const char symm,
const int axis,
const int i,
const float angle)
{
SculptSession *ss = ob->sculpt;
ss->cache->radial_symmetry_pass = i;
SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle);
int totnode;
PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, vp, sd, brush, &totnode);
/* Paint those leaves. */
vpaint_paint_leaves(C, sd, vp, vpd, ob, me, nodes, totnode);
if (nodes) {
MEM_freeN(nodes);
}
}
static void vpaint_do_radial_symmetry(bContext *C,
Sculpt *sd,
VPaint *vp,
struct VPaintData *vpd,
Object *ob,
Mesh *me,
Brush *brush,
const char symm,
const int axis)
{
for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) {
const float angle = (2.0 * M_PI) * i / vp->radial_symm[axis - 'X'];
vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, symm, axis, i, angle);
}
}
/* near duplicate of: sculpt.c's,
* 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */
static void vpaint_do_symmetrical_brush_actions(
bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob)
{
Brush *brush = BKE_paint_brush(&vp->paint);
Mesh *me = ob->data;
SculptSession *ss = ob->sculpt;
StrokeCache *cache = ss->cache;
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
int i = 0;
/* initial stroke */
cache->mirror_symmetry_pass = 0;
vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0);
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X');
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y');
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z');
cache->symmetry = symm;
/* symm is a bit combination of XYZ - 1 is mirror
* X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */
for (i = 1; i <= symm; i++) {
if (symm & i && (symm != 5 || i != 3) && (symm != 6 || (i != 3 && i != 5))) {
cache->mirror_symmetry_pass = i;
cache->radial_symmetry_pass = 0;
SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0);
if (i & (1 << 0)) {
vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0);
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X');
}
if (i & (1 << 1)) {
vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0);
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y');
}
if (i & (1 << 2)) {
vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0);
vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z');
}
}
}
copy_v3_v3(cache->true_last_location, cache->true_location);
cache->is_last_valid = true;
}
static void vpaint_stroke_update_step(bContext *C, struct PaintStroke *stroke, PointerRNA *itemptr)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
struct VPaintData *vpd = paint_stroke_mode_data(stroke);
VPaint *vp = ts->vpaint;
ViewContext *vc = &vpd->vc;
Object *ob = vc->obact;
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
vwpaint_update_cache_variants(C, vp, ob, itemptr);
float mat[4][4];
ED_view3d_init_mats_rv3d(ob, vc->rv3d);
/* load projection matrix */
mul_m4_m4m4(mat, vc->rv3d->persmat, ob->obmat);
swap_m4m4(vc->rv3d->persmat, mat);
vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob);
swap_m4m4(vc->rv3d->persmat, mat);
BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
if (vp->paint.brush->vertexpaint_tool == VPAINT_TOOL_SMEAR) {
memcpy(
vpd->smear.color_prev, vpd->smear.color_curr, sizeof(uint) * ((Mesh *)ob->data)->totloop);
}
/* Calculate pivot for rotation around selection if needed.
* also needed for "Frame Selected" on last stroke. */
float loc_world[3];
mul_v3_m4v3(loc_world, ob->obmat, ss->cache->true_location);
paint_last_stroke_update(scene, loc_world);
ED_region_tag_redraw(vc->region);
if (vpd->use_fast_update == false) {
/* recalculate modifier stack to get new colors, slow,
* avoid this if we can! */
DEG_id_tag_update(ob->data, 0);
}
else {
/* Flush changes through DEG. */
DEG_id_tag_update(ob->data, ID_RECALC_COPY_ON_WRITE);
}
}
static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke)
{
struct VPaintData *vpd = paint_stroke_mode_data(stroke);
ViewContext *vc = &vpd->vc;
Object *ob = vc->obact;
if (vpd->is_texbrush) {
ED_vpaint_proj_handle_free(vpd->vp_handle);
}
if (vpd->mlooptag) {
MEM_freeN(vpd->mlooptag);
}
if (vpd->smear.color_prev) {
MEM_freeN(vpd->smear.color_prev);
}
if (vpd->smear.color_curr) {
MEM_freeN(vpd->smear.color_curr);
}
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
MEM_freeN(vpd);
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
int retval;
op->customdata = paint_stroke_new(C,
op,
SCULPT_stroke_get_location,
vpaint_stroke_test_start,
vpaint_stroke_update_step,
NULL,
vpaint_stroke_done,
event->type);
if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) {
paint_stroke_free(C, op);
return OPERATOR_FINISHED;
}
/* add modal handler */
WM_event_add_modal_handler(C, op);
OPERATOR_RETVAL_CHECK(retval);
BLI_assert(retval == OPERATOR_RUNNING_MODAL);
return OPERATOR_RUNNING_MODAL;
}
static int vpaint_exec(bContext *C, wmOperator *op)
{
op->customdata = paint_stroke_new(C,
op,
SCULPT_stroke_get_location,
vpaint_stroke_test_start,
vpaint_stroke_update_step,
NULL,
vpaint_stroke_done,
0);
/* frees op->customdata */
paint_stroke_exec(C, op);
return OPERATOR_FINISHED;
}
static void vpaint_cancel(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
if (ob->sculpt->cache) {
SCULPT_cache_free(ob->sculpt->cache);
ob->sculpt->cache = NULL;
}
paint_stroke_cancel(C, op);
}
void PAINT_OT_vertex_paint(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Vertex Paint";
ot->idname = "PAINT_OT_vertex_paint";
ot->description = "Paint a stroke in the active vertex color layer";
/* api callbacks */
ot->invoke = vpaint_invoke;
ot->modal = paint_stroke_modal;
ot->exec = vpaint_exec;
ot->poll = vertex_paint_poll;
ot->cancel = vpaint_cancel;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
paint_stroke_operator_properties(ot);
}
/** \} */