From a75b7e15517f33cb30cfc660efb0c22de82e9586 Mon Sep 17 00:00:00 2001 From: Joseph Eagar Date: Fri, 14 Jul 2023 16:31:17 +0200 Subject: [PATCH] Sculpt: Split paint_vertex.cc into paint_weight.cc Functions shared between the two now live in a new `blender::editors::vwpaint` namespace. Pull Request: https://projects.blender.org/blender/blender/pulls/109996 --- .../editors/sculpt_paint/CMakeLists.txt | 1 + .../editors/sculpt_paint/paint_intern.hh | 51 + .../editors/sculpt_paint/paint_vertex.cc | 2529 +++-------------- .../editors/sculpt_paint/paint_weight.cc | 1970 +++++++++++++ 4 files changed, 2342 insertions(+), 2209 deletions(-) create mode 100644 source/blender/editors/sculpt_paint/paint_weight.cc diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index 903115694c0..e2f718a2f6b 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -59,6 +59,7 @@ set(SRC paint_vertex_proj.cc paint_vertex_weight_ops.cc paint_vertex_weight_utils.cc + paint_weight.cc sculpt.cc sculpt_automasking.cc sculpt_boundary.cc diff --git a/source/blender/editors/sculpt_paint/paint_intern.hh b/source/blender/editors/sculpt_paint/paint_intern.hh index 1221060cd38..662c4067e2b 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.hh +++ b/source/blender/editors/sculpt_paint/paint_intern.hh @@ -517,3 +517,54 @@ void paint_init_pivot(Object *ob, Scene *scene); /* paint curve defines */ #define PAINT_CURVE_NUM_SEGMENTS 40 + +namespace blender::ed::sculpt_paint::vwpaint { +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; +}; + +void view_angle_limits_init(NormalAnglePrecalc *a, float angle, bool do_mask_normal); +float view_angle_limits_apply_falloff(const NormalAnglePrecalc *a, float angle_cos, float *mask_p); +bool test_brush_angle_falloff(const Brush &brush, + const NormalAnglePrecalc &normal_angle_precalc, + const float angle_cos, + float *brush_strength); +bool use_normal(const VPaint *vp); + +bool brush_use_accumulate_ex(const Brush *brush, const int ob_mode); +bool brush_use_accumulate(const VPaint *vp); + +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); + +void init_stroke(Depsgraph *depsgraph, Object *ob); +void init_session_data(const ToolSettings *ts, Object *ob); +void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode); + +Vector pbvh_gather_generic(Object *ob, VPaint *wp, Sculpt *sd, Brush *brush); + +void mode_enter_generic( + Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, const eObjectMode mode_flag); +void mode_exit_generic(Object *ob, const eObjectMode mode_flag); +bool mode_toggle_poll_test(bContext *C); + +void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache); +void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache); + +void update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr); +void update_cache_invariants( + bContext *C, VPaint *vp, SculptSession *ss, wmOperator *op, const float mval[2]); +void last_stroke_update(Scene *scene, const float location[3]); +} // namespace blender::ed::sculpt_paint::vwpaint diff --git a/source/blender/editors/sculpt_paint/paint_vertex.cc b/source/blender/editors/sculpt_paint/paint_vertex.cc index 23db04d1bc5..f71fe5521f7 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.cc +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -72,6 +72,8 @@ using blender::IndexRange; using namespace blender; using namespace blender::color; +using namespace blender::ed::sculpt_paint; /* For vwpaint namespace. */ +using blender::ed::sculpt_paint::vwpaint::NormalAnglePrecalc; /* -------------------------------------------------------------------- */ /** \name Internal Utilities @@ -113,24 +115,13 @@ template struct VPaintAverageAccum { BlendType value[3]; }; -struct WPaintAverageAccum { - uint len; - double value; -}; +namespace blender::ed::sculpt_paint::vwpaint { -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; -}; +/* -------------------------------------------------------------------- */ +/** \name Shared vertex/weight paint code. + * \{ */ -static void view_angle_limits_init(NormalAnglePrecalc *a, float angle, bool do_mask_normal) +void view_angle_limits_init(NormalAnglePrecalc *a, float angle, bool do_mask_normal) { angle = RAD2DEGF(angle); a->do_mask_normal = do_mask_normal; @@ -154,9 +145,7 @@ static void view_angle_limits_init(NormalAnglePrecalc *a, float angle, bool do_m a->angle_inner__cos = cosf(a->angle_inner); } -static float view_angle_limits_apply_falloff(const NormalAnglePrecalc *a, - float angle_cos, - float *mask_p) +float view_angle_limits_apply_falloff(const NormalAnglePrecalc *a, float angle_cos, float *mask_p) { if (angle_cos <= a->angle__cos) { /* outsize the normal limit */ @@ -169,995 +158,53 @@ static float view_angle_limits_apply_falloff(const NormalAnglePrecalc *a, return true; } -static bool test_brush_angle_falloff(const Brush &brush, - const NormalAnglePrecalc &normal_angle_precalc, - const float angle_cos, - float *brush_strength) +bool test_brush_angle_falloff(const Brush &brush, + const NormalAnglePrecalc &normal_angle_precalc, + const float angle_cos, + float *brush_strength) { if (((brush.flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && ((brush.flag & BRUSH_FRONTFACE_FALLOFF) == 0 || - view_angle_limits_apply_falloff(&normal_angle_precalc, angle_cos, brush_strength))) + vwpaint::view_angle_limits_apply_falloff(&normal_angle_precalc, angle_cos, brush_strength))) { return true; } return false; } -static bool vwpaint_use_normal(const VPaint *vp) +bool 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) +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) +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) +void init_stroke(Depsgraph *depsgraph, Object *ob) { - const 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; -} + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, true); + SculptSession *ss = ob->sculpt; -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; -} - -bool vertex_paint_mode_poll(bContext *C) -{ - const Object *ob = CTX_data_active_object(C); - if (!ob) { - return false; - } - const Mesh *mesh = static_cast(ob->data); - - if (!(ob->mode == OB_MODE_VERTEX_PAINT && mesh->totpoly)) { - return false; - } - - if (!mesh->attributes().contains(mesh->active_color_attribute)) { - return false; - } - - return true; -} - -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) -{ - const Object *ob = CTX_data_active_object(C); - - return ob && ob->mode == OB_MODE_WEIGHT_PAINT && ((const Mesh *)ob->data)->totpoly; -} - -static bool weight_paint_poll_ex(bContext *C, bool check_tool) -{ - const Object *ob = CTX_data_active_object(C); - const ScrArea *area; - - if ((ob != nullptr) && (ob->mode & OB_MODE_WEIGHT_PAINT) && - (BKE_paint_brush(&CTX_data_tool_settings(C)->wpaint->paint) != nullptr) && - (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); -} - -static ColorPaint4f vpaint_get_current_col(Scene *scene, VPaint *vp, bool secondary) -{ - const Brush *brush = BKE_paint_brush_for_read(&vp->paint); - float color[4]; - const float *brush_color = secondary ? BKE_brush_secondary_color_get(scene, brush) : - BKE_brush_color_get(scene, brush); - IMB_colormanagement_srgb_to_scene_linear_v3(color, brush_color); - - color[3] = 1.0f; /* alpha isn't used, could even be removed to speedup paint a little */ - - return ColorPaint4f(color); -} - -/* wpaint has 'wpaint_blend' */ -template -static Color vpaint_blend(const VPaint *vp, - Color color_curr, - Color color_orig, - Color color_paint, - const typename Traits::ValueType alpha, - const typename Traits::BlendType brush_alpha_value) -{ - using Value = typename Traits::ValueType; - - const Brush *brush = vp->paint.brush; - const IMB_BlendMode blend = (IMB_BlendMode)brush->blend; - - const Color color_blend = BLI_mix_colors(blend, color_curr, color_paint, alpha); - - /* If no accumulate, clip color adding with `color_orig` & `color_test`. */ - if (!brush_use_accumulate(vp)) { - uint a; - Color color_test; - Value *cp, *ct, *co; - - color_test = BLI_mix_colors(blend, color_orig, color_paint, brush_alpha_value); - - cp = (Value *)&color_blend; - ct = (Value *)&color_test; - co = (Value *)&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)) - { - Value *cp, *cc; - cp = (Value *)&color_blend; - cc = (Value *)&color_curr; - cp[3] = cc[3]; - } - - return color_blend; -} - -/* vpaint has 'vpaint_blend' */ -static float wpaint_blend(const VPaint *wp, - float weight, - const float alpha, - float paintval, - const float /*brush_alpha_value*/, - const bool do_flip) -{ - const Brush *brush = wp->paint.brush; - IMB_BlendMode blend = (IMB_BlendMode)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 void paint_and_tex_color_alpha_intern(VPaint *vp, - const ViewContext *vc, - const float co[3], - float r_rgba[4]) -{ - const Brush *brush = BKE_paint_brush(&vp->paint); - const MTex *mtex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT); - BLI_assert(mtex->tex != nullptr); - if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { - BKE_brush_sample_tex_3d(vc->scene, brush, mtex, co, r_rgba, 0, nullptr); - } - else { - float co_ss[2]; /* screenspace */ - if (ED_view3d_project_float_object( - vc->region, - co, - co_ss, - (eV3DProjTest)(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, mtex, co_ss_3d, r_rgba, 0, nullptr); - } - else { - zero_v4(r_rgba); - } - } -} - -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 == nullptr) { - 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). + /* Ensure ss->cache is allocated. It will mostly be initialized in + * vwpaint::update_cache_invariants and vwpaint::update_cache_variants. */ - 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 */ -struct WeightPaintInfo { - - MutableSpan dvert; - - int defbase_tot; - - /* both must add up to 'defbase_tot' */ - int defbase_tot_sel; - int defbase_tot_unsel; - - 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() */ -}; - -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 = (Mesh *)ob->data; - MDeformVert *dv = &wpi->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, nullptr, 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 == nullptr) { - return; - } - - /* get the mirror def vars */ - if (index_mirr != -1) { - dv_mirr = &wpi->dvert[index_mirr]; - if (wp->flag & VP_FLAG_VGROUP_RESTRICT) { - dw_mirr = BKE_defvert_find_index(dv_mirr, vgroup_mirr); - - if (dw_mirr == nullptr) { - index_mirr = vgroup_mirr = -1; - dv_mirr = nullptr; - } - } - 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 = nullptr; - dw_mirr = nullptr; - } - - 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, wpi->dvert.data(), index); - if (index_mirr != -1) { - defweight_prev_init(dvert_prev, wpi->dvert.data(), 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 #26193, 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 = (Mesh *)ob->data; - MDeformVert *dv = &wpi->dvert[index]; - bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; - - /* mirror vars */ - int index_mirr = -1; - MDeformVert *dv_mirr = nullptr; - - /* 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, nullptr, index, topology); - - if (!ELEM(index_mirr, -1, index)) { - dv_mirr = &wpi->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, wpi->dvert.data(), index); - if (index_mirr != -1) { - defweight_prev_init(dvert_prev, wpi->dvert.data(), 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 != nullptr) { - 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 = nullptr; - } - 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 != nullptr) { - 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 != nullptr) { - 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); + if (!ss->cache) { + ss->cache = MEM_new(__func__); } } /* Toggle operator for turning vertex paint mode on or off (copied from sculpt.cc) */ -static void vertex_paint_init_session(Depsgraph *depsgraph, - Scene *scene, - Object *ob, - eObjectMode object_mode) +void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode) { /* Create persistent sculpt mode data */ BKE_sculpt_toolsettings_data_ensure(scene); @@ -1170,42 +217,7 @@ static void vertex_paint_init_session(Depsgraph *depsgraph, SCULPT_ensure_valid_pivot(ob, scene); } -static void vwpaint_init_stroke(Depsgraph *depsgraph, Object *ob) -{ - BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, true); - SculptSession *ss = ob->sculpt; - - /* Ensure ss->cache is allocated. It will mostly be initialized in - * vwpaint_update_cache_invariants and vwpaint_update_cache_variants. - */ - if (!ss->cache) { - ss->cache = MEM_new(__func__); - } -} - -static void vertex_paint_init_stroke(Scene *scene, Depsgraph *depsgraph, Object *ob) -{ - vwpaint_init_stroke(depsgraph, ob); - - SculptSession *ss = ob->sculpt; - ToolSettings *ts = scene->toolsettings; - - /* Allocate scratch array for previous colors if needed. */ - if (!brush_use_accumulate(ts->vpaint)) { - if (ss->cache->prev_colors_vpaint.is_empty()) { - const Mesh *mesh = BKE_object_get_original_mesh(ob); - const GVArray attribute = *mesh->attributes().lookup(mesh->active_color_attribute); - ss->cache->prev_colors_vpaint = GArray(attribute.type(), attribute.size()); - attribute.type().value_initialize_n(ss->cache->prev_colors_vpaint.data(), - ss->cache->prev_colors_vpaint.size()); - } - } - else { - ss->cache->prev_colors_vpaint = {}; - } -} - -static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) +void init_session_data(const ToolSettings *ts, Object *ob) { /* Create maps */ SculptVertexPaintGeomMap *gmap = nullptr; @@ -1236,7 +248,7 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) /* Create average brush arrays */ if (ob->mode == OB_MODE_WEIGHT_PAINT) { - if (!brush_use_accumulate(ts->wpaint)) { + if (!vwpaint::brush_use_accumulate(ts->wpaint)) { if (ob->sculpt->mode.wpaint.alpha_weight == nullptr) { ob->sculpt->mode.wpaint.alpha_weight = (float *)MEM_callocN(me->totvert * sizeof(float), __func__); @@ -1262,13 +274,53 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) } } -/** \} */ +Vector pbvh_gather_generic(Object *ob, VPaint *wp, Sculpt *sd, Brush *brush) +{ + SculptSession *ss = ob->sculpt; + const bool use_normal = vwpaint::use_normal(wp); + Vector nodes; -/* -------------------------------------------------------------------- */ -/** \name Enter Vertex/Weight Paint Mode - * \{ */ + /* 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 = {nullptr}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cache->radius_squared; + data.original = true; -static void ed_vwpaintmode_enter_generic( + nodes = blender::bke::pbvh::search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data); + + if (use_normal) { + SCULPT_pbvh_calc_area_normal(brush, ob, nodes, true, ss->cache->sculpt_normal_symm); + } + else { + zero_v3(ss->cache->sculpt_normal_symm); + } + } + else { + 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 = {nullptr}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cache->radius_squared; + data.original = true; + data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc; + + nodes = blender::bke::pbvh::search_gather(ss->pbvh, SCULPT_search_circle_cb, &data); + + 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; +} + +void mode_enter_generic( Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, const eObjectMode mode_flag) { ob->mode |= mode_flag; @@ -1313,43 +365,13 @@ static void ed_vwpaintmode_enter_generic( BKE_sculptsession_free(ob); } - vertex_paint_init_session(depsgraph, scene, ob, mode_flag); + vwpaint::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(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(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) +void mode_exit_generic(Object *ob, const eObjectMode mode_flag) { Mesh *me = BKE_mesh_from_object(ob); ob->mode &= ~mode_flag; @@ -1396,85 +418,7 @@ static void ed_vwpaintmode_exit_generic(Object *ob, const eObjectMode mode_flag) 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(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(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); - 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, (eObjectMode)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) +bool mode_toggle_poll_test(bContext *C) { Object *ob = CTX_data_active_object(C); if (ob == nullptr || ob->type != OB_MESH) { @@ -1486,72 +430,7 @@ static bool paint_mode_toggle_poll_test(bContext *C) 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; - NormalAnglePrecalc normal_angle_precalc; - - 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; -}; - -static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) -{ - Scene *scene = CTX_data_scene(C); - Brush *brush = paint->brush; - int cur_brush_size = BKE_brush_size_get(scene, brush); - - STRNCPY(cache->saved_active_brush_name, brush->id.name + 2); - - /* Switch to the blur (smooth) brush. */ - brush = BKE_paint_toolslots_brush_get(paint, WPAINT_TOOL_BLUR); - if (brush) { - BKE_paint_brush_set(paint, brush); - cache->saved_smooth_size = BKE_brush_size_get(scene, brush); - BKE_brush_size_set(scene, brush, cur_brush_size); - BKE_curvemapping_init(brush->curve); - } -} - -static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) +void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); @@ -1566,9 +445,8 @@ static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache BKE_paint_brush_set(paint, brush); } } - /* Initialize the stroke cache invariants from operator properties */ -static void vwpaint_update_cache_invariants( +void update_cache_invariants( bContext *C, VPaint *vp, SculptSession *ss, wmOperator *op, const float mval[2]) { StrokeCache *cache; @@ -1610,7 +488,7 @@ static void vwpaint_update_cache_invariants( } if (cache->alt_smooth) { - smooth_brush_toggle_on(C, &vp->paint, cache); + vwpaint::smooth_brush_toggle_on(C, &vp->paint, cache); } copy_v2_v2(cache->mouse, cache->initial_mouse); @@ -1638,7 +516,7 @@ static void vwpaint_update_cache_invariants( } /* Initialize the stroke cache variants from operator properties */ -static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr) +void update_cache_variants(bContext *C, VPaint *vp, Object *ob, PointerRNA *ptr) { Scene *scene = CTX_data_scene(C); SculptSession *ss = ob->sculpt; @@ -1684,168 +562,12 @@ static void vwpaint_update_cache_variants(bContext *C, VPaint *vp, Object *ob, P } } -static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) -{ - Scene *scene = CTX_data_scene(C); - PaintStroke *stroke = (PaintStroke *)op->customdata; - ToolSettings *ts = scene->toolsettings; - Object *ob = CTX_data_active_object(C); - Mesh *me = BKE_mesh_from_object(ob); - WPaintData *wpd; - 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 = (bDeformGroup *)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 = (bDeformGroup *)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 = (bDeformGroup *)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 = (WPaintData *)MEM_callocN(sizeof(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 != nullptr || - 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 = (bool *)MEM_dupallocN(wpd->vgroup_validmap); - - if (wpd->lock_flags) { - bool *locked = (bool *)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 = (bool *)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 ? (bool *)MEM_dupallocN(wpd->lock_flags) : - (bool *)MEM_callocN(sizeof(bool) * defbase_tot, __func__); - tmpflags[wpd->active.index] = true; - wpd->active.lock = tmpflags; - - tmpflags = wpd->lock_flags ? (bool *)MEM_dupallocN(wpd->lock_flags) : - (bool *)MEM_callocN(sizeof(bool) * defbase_tot, __func__); - tmpflags[(wpd->mirror.index != -1) ? wpd->mirror.index : wpd->active.index] = true; - wpd->mirror.lock = tmpflags; - } - - /* If not previously created, create vertex/weight paint mode session data */ - vwpaint_init_stroke(depsgraph, ob); - vwpaint_update_cache_invariants(C, vp, ss, op, mouse); - vertex_paint_init_session_data(ts, ob); - - if (ELEM(vp->paint.brush->weightpaint_tool, WPAINT_TOOL_SMEAR, WPAINT_TOOL_BLUR)) { - wpd->precomputed_weight = (float *)MEM_mallocN(sizeof(float) * me->totvert, __func__); - } - - if (ob->sculpt->mode.wpaint.dvert_prev != nullptr) { - 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 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) +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); @@ -1853,849 +575,234 @@ static void get_brush_alpha_data(const Scene *scene, *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) +void last_stroke_update(Scene *scene, const float location[3]) { - float weight; + UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings; + ups->average_stroke_counter++; + add_v3_v3(ups->average_stroke_accum, location); + ups->last_stroke_valid = true; +} - if (wpi->do_multipaint) { - weight = BKE_defvert_multipaint_collective_weight( - dv, wpi->defbase_tot, wpi->defbase_sel, wpi->defbase_tot_sel, wpi->is_normalized); +/* -------------------------------------------------------------------- */ +void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache) +{ + Scene *scene = CTX_data_scene(C); + Brush *brush = paint->brush; + int cur_brush_size = BKE_brush_size_get(scene, brush); + + STRNCPY(cache->saved_active_brush_name, brush->id.name + 2); + + /* Switch to the blur (smooth) brush. */ + brush = BKE_paint_toolslots_brush_get(paint, WPAINT_TOOL_BLUR); + if (brush) { + BKE_paint_brush_set(paint, brush); + cache->saved_smooth_size = BKE_brush_size_get(scene, brush); + BKE_brush_size_set(scene, brush, cur_brush_size); + BKE_curvemapping_init(brush->curve); + } +} +/** \} */ +} // namespace blender::editors::vwpaint + +bool vertex_paint_mode_poll(bContext *C) +{ + const Object *ob = CTX_data_active_object(C); + if (!ob) { + return false; + } + const Mesh *mesh = static_cast(ob->data); + + if (!(ob->mode == OB_MODE_VERTEX_PAINT && mesh->totpoly)) { + return false; + } + + if (!mesh->attributes().contains(mesh->active_color_attribute)) { + return false; + } + + return true; +} + +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); +} + +static ColorPaint4f vpaint_get_current_col(Scene *scene, VPaint *vp, bool secondary) +{ + const Brush *brush = BKE_paint_brush_for_read(&vp->paint); + float color[4]; + const float *brush_color = secondary ? BKE_brush_secondary_color_get(scene, brush) : + BKE_brush_color_get(scene, brush); + IMB_colormanagement_srgb_to_scene_linear_v3(color, brush_color); + + color[3] = 1.0f; /* alpha isn't used, could even be removed to speedup paint a little */ + + return ColorPaint4f(color); +} + +/* wpaint has 'wpaint_blend' */ +template +static Color vpaint_blend(const VPaint *vp, + Color color_curr, + Color color_orig, + Color color_paint, + const typename Traits::ValueType alpha, + const typename Traits::BlendType brush_alpha_value) +{ + using Value = typename Traits::ValueType; + + const Brush *brush = vp->paint.brush; + const IMB_BlendMode blend = (IMB_BlendMode)brush->blend; + + const Color color_blend = BLI_mix_colors(blend, color_curr, color_paint, alpha); + + /* If no accumulate, clip color adding with `color_orig` & `color_test`. */ + if (!vwpaint::brush_use_accumulate(vp)) { + uint a; + Color color_test; + Value *cp, *ct, *co; + + color_test = BLI_mix_colors(blend, color_orig, color_paint, brush_alpha_value); + + cp = (Value *)&color_blend; + ct = (Value *)&color_test; + co = (Value *)&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)) + { + Value *cp, *cc; + cp = (Value *)&color_blend; + cc = (Value *)&color_curr; + cp[3] = cc[3]; + } + + return color_blend; +} + +static void paint_and_tex_color_alpha_intern(VPaint *vp, + const ViewContext *vc, + const float co[3], + float r_rgba[4]) +{ + const Brush *brush = BKE_paint_brush(&vp->paint); + const MTex *mtex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT); + BLI_assert(mtex->tex != nullptr); + if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) { + BKE_brush_sample_tex_3d(vc->scene, brush, mtex, co, r_rgba, 0, nullptr); } 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 /*tls*/) -{ - SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; - const MDeformVert *dv = &data->wpi->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, 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; - data.C = C; - data.ob = ob; - data.wpd = wpd; - data.wpi = wpi; - data.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 /*tls*/) -{ - SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); - const 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); - - const blender::bke::AttributeAccessor attributes = data->me->attributes(); - const blender::VArray select_vert = *attributes.lookup_or_default( - ".select_vert", ATTR_DOMAIN_POINT, false); - - /* 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)) { - continue; - } - /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - /* If the vertex is selected */ - if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { - continue; - } - /* Get the average poly weight */ - int total_hit_loops = 0; - float weight_final = 0.0f; - for (const int p_index : gmap->vert_to_poly[v_index]) { - const blender::IndexRange poly = ss->polys[p_index]; - - total_hit_loops += poly.size(); - for (const int vert : ss->corner_verts.slice(poly)) { - weight_final += data->wpd->precomputed_weight[vert]; - } - } - - /* Apply the weight to the vertex. */ - if (total_hit_loops == 0) { - continue; - } - - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (!test_brush_angle_falloff( - *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) { - continue; - } - - 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 /*tls*/) -{ - SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); - const SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap; - - const Brush *brush = data->brush; - const Scene *scene = CTX_data_scene(data->C); - const StrokeCache *cache = ss->cache; - if (!cache->is_last_valid) { - return; - } - - 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 (normalize_v3(brush_dir) == 0.0f) { - return; - } - - const blender::bke::AttributeAccessor attributes = data->me->attributes(); - const blender::VArray select_vert = *attributes.lookup_or_default( - ".select_vert", ATTR_DOMAIN_POINT, false); - - 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)) { - continue; - } - - /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const float3 &mv_curr = ss->vert_positions[v_index]; - - /* If the vertex is selected */ - if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { - continue; - } - - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (!test_brush_angle_falloff( - *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) { - continue; - } - - 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 (const int p_index : gmap->vert_to_poly[v_index]) { - for (const int v_other_index : ss->corner_verts.slice(ss->polys[p_index])) { - if (v_other_index == v_index) { - continue; - } - - /* Get the direction from the selected vert to the neighbor. */ - float other_dir[3]; - sub_v3_v3v3(other_dir, mv_curr, ss->vert_positions[v_other_index]); - 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) { - continue; - } - 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 /*tls*/) -{ - SculptThreadedTaskData *data = (SculptThreadedTaskData *)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); - - const blender::bke::AttributeAccessor attributes = data->me->attributes(); - const blender::VArray select_vert = *attributes.lookup_or_default( - ".select_vert", ATTR_DOMAIN_POINT, false); - - /* 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)) { - continue; - } - /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - - /* If the vertex is selected */ - if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { - continue; - } - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (!test_brush_angle_falloff( - *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) { - continue; - } - 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 /*tls*/) -{ - SculptThreadedTaskData *data = (SculptThreadedTaskData *)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; - - WPaintAverageAccum *accum = (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); - - const blender::bke::AttributeAccessor attributes = data->me->attributes(); - const blender::VArray select_vert = *attributes.lookup_or_default( - ".select_vert", ATTR_DOMAIN_POINT, false); - - /* 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)) { - continue; - } - - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (angle_cos <= 0.0f || - BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) <= 0.0f) + float co_ss[2]; /* screenspace */ + if (ED_view3d_project_float_object( + vc->region, + co, + co_ss, + (eV3DProjTest)(V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR)) == V3D_PROJ_RET_OK) { - continue; - } - - const int v_index = has_grids ? ss->corner_verts[vd.grid_indices[vd.g]] : - vd.vert_indices[vd.i]; - /* If the vertex is selected. */ - if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { - continue; - } - - const MDeformVert *dv = &data->wpi->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, Span nodes) -{ - WPaintAverageAccum *accum = (WPaintAverageAccum *)MEM_mallocN(sizeof(*accum) * nodes.size(), - __func__); - data->custom_data = accum; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, nodes.size()); - BLI_task_parallel_range( - 0, nodes.size(), data, do_wpaint_brush_calc_average_weight_cb_ex, &settings); - - uint accum_len = 0; - double accum_weight = 0.0; - for (int i = 0; i < nodes.size(); 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, - WPaintData *wpd, - WeightPaintInfo *wpi, - Mesh *me, - Span nodes) -{ - Scene *scene = CTX_data_scene(C); - const Brush *brush = ob->sculpt->cache->brush; - - /* threaded loop over nodes */ - SculptThreadedTaskData data = {nullptr}; - data.C = C; - data.sd = sd; - data.ob = ob; - data.brush = brush; - data.nodes = nodes; - data.vp = vp; - data.wpd = wpd; - data.wpi = wpi; - data.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, nodes.size()); - - switch ((eBrushWeightPaintTool)brush->weightpaint_tool) { - case WPAINT_TOOL_AVERAGE: - calculate_average_weight(&data, nodes); - BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_draw_task_cb_ex, &settings); - break; - case WPAINT_TOOL_SMEAR: - BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_smear_task_cb_ex, &settings); - break; - case WPAINT_TOOL_BLUR: - BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_blur_task_cb_ex, &settings); - break; - case WPAINT_TOOL_DRAW: - BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_draw_task_cb_ex, &settings); - break; - } -} - -static Vector vwpaint_pbvh_gather_generic(Object *ob, - VPaint *wp, - Sculpt *sd, - Brush *brush) -{ - SculptSession *ss = ob->sculpt; - const bool use_normal = vwpaint_use_normal(wp); - Vector nodes; - - /* 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 = {nullptr}; - data.ss = ss; - data.sd = sd; - data.radius_squared = ss->cache->radius_squared; - data.original = true; - - nodes = blender::bke::pbvh::search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data); - - if (use_normal) { - SCULPT_pbvh_calc_area_normal(brush, ob, nodes, true, ss->cache->sculpt_normal_symm); + 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, mtex, co_ss_3d, r_rgba, 0, nullptr); } else { - zero_v3(ss->cache->sculpt_normal_symm); + zero_v4(r_rgba); + } + } +} + +static void vertex_paint_init_stroke(Scene *scene, Depsgraph *depsgraph, Object *ob) +{ + vwpaint::init_stroke(depsgraph, ob); + + SculptSession *ss = ob->sculpt; + ToolSettings *ts = scene->toolsettings; + + /* Allocate scratch array for previous colors if needed. */ + if (!vwpaint::brush_use_accumulate(ts->vpaint)) { + if (ss->cache->prev_colors_vpaint.is_empty()) { + const Mesh *mesh = BKE_object_get_original_mesh(ob); + const GVArray attribute = *mesh->attributes().lookup(mesh->active_color_attribute); + ss->cache->prev_colors_vpaint = GArray(attribute.type(), attribute.size()); + attribute.type().value_initialize_n(ss->cache->prev_colors_vpaint.data(), + ss->cache->prev_colors_vpaint.size()); } } else { - 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 = {nullptr}; - data.ss = ss; - data.sd = sd; - data.radius_squared = ss->cache->radius_squared; - data.original = true; - data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc; - - nodes = blender::bke::pbvh::search_gather(ss->pbvh, SCULPT_search_circle_cb, &data); - - 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, - WPaintData *wpd, - WeightPaintInfo *wpi, - Mesh *me, - Brush *brush, - const ePaintSymmetryFlags 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); - - Vector nodes = vwpaint_pbvh_gather_generic(ob, wp, sd, brush); - - wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes); -} - -static void wpaint_do_radial_symmetry(bContext *C, - Object *ob, - VPaint *wp, - Sculpt *sd, - WPaintData *wpd, - WeightPaintInfo *wpi, - Mesh *me, - Brush *brush, - const ePaintSymmetryFlags 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); + ss->cache->prev_colors_vpaint = {}; } } -/* near duplicate of: sculpt.cc'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, WPaintData *wpd, WeightPaintInfo *wpi) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Enter Vertex Paint Mode + * \{ */ + +void ED_object_vpaintmode_enter_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) { - Brush *brush = BKE_paint_brush(&wp->paint); - Mesh *me = (Mesh *)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 = ePaintSymmetryFlags(0); - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Y'); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(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 || !ELEM(i, 3, 5))) { - const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); - cache->mirror_symmetry_pass = symm; - cache->radial_symmetry_pass = 0; - SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); - - if (i & (1 << 0)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X'); - } - if (i & (1 << 1)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y'); - } - if (i & (1 << 2)) { - wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z', 0, 0); - wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z'); - } - } - } - copy_v3_v3(cache->true_last_location, cache->true_location); - cache->is_last_valid = true; + vwpaint::mode_enter_generic(bmain, depsgraph, scene, ob, OB_MODE_VERTEX_PAINT); } - -static void wpaint_stroke_update_step(bContext *C, - wmOperator * /*op*/, - PaintStroke *stroke, - PointerRNA *itemptr) +void ED_object_vpaintmode_enter(bContext *C, Depsgraph *depsgraph) { + Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); - ToolSettings *ts = CTX_data_tool_settings(C); - VPaint *wp = ts->wpaint; - Brush *brush = BKE_paint_brush(&wp->paint); - WPaintData *wpd = (WPaintData *)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 nullptr, make sure we initialize all members below */ - WeightPaintInfo wpi; - - /* cannot paint if there is no stroke data */ - if (wpd == nullptr) { - /* 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->object_to_world); - - Mesh *mesh = static_cast(ob->data); - - /* *** setup WeightPaintInfo - pass onto do_weight_paint_vertex *** */ - wpi.dvert = mesh->deform_verts_for_write(); - - 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") || ss->cache->invert; - wpi.do_multipaint = wpd->do_multipaint; - wpi.do_auto_normalize = ((ts->auto_normalize != 0) && (wpi.vgroup_validmap != nullptr) && - (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, mesh); - } - - 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->object_to_world, ss->cache->true_location); - paint_last_stroke_update(scene, loc_world); - - BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); - - DEG_id_tag_update(&mesh->id, 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); + ED_object_vpaintmode_enter_ex(bmain, depsgraph, scene, ob); } -static void wpaint_stroke_done(const bContext *C, PaintStroke *stroke) +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Exit Vertex Paint Mode + * \{ */ + +void ED_object_vpaintmode_exit_ex(Object *ob) +{ + vwpaint::mode_exit_generic(ob, OB_MODE_VERTEX_PAINT); +} +void ED_object_vpaintmode_exit(bContext *C) { Object *ob = CTX_data_active_object(C); - WPaintData *wpd = (WPaintData *)paint_stroke_mode_data(stroke); - - if (wpd) { - MEM_SAFE_FREE(wpd->defbase_sel); - MEM_SAFE_FREE(wpd->vgroup_validmap); - MEM_SAFE_FREE(wpd->vgroup_locked); - MEM_SAFE_FREE(wpd->vgroup_unlocked); - MEM_SAFE_FREE(wpd->lock_flags); - MEM_SAFE_FREE(wpd->active.lock); - MEM_SAFE_FREE(wpd->mirror.lock); - MEM_SAFE_FREE(wpd->precomputed_weight); - MEM_freeN(wpd); - } - - SculptSession *ss = ob->sculpt; - - if (ss->cache->alt_smooth) { - ToolSettings *ts = CTX_data_tool_settings(C); - VPaint *vp = ts->wpaint; - smooth_brush_toggle_off(C, &vp->paint, ss->cache); - } - - /* and particles too */ - if (ob->particlesystem.first) { - ParticleSystem *psys; - int i; - - for (psys = (ParticleSystem *)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((ID *)ob->data, 0); - - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - - SCULPT_cache_free(ob->sculpt->cache); - ob->sculpt->cache = nullptr; -} - -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, - nullptr, - wpaint_stroke_done, - event->type); - - if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op, (PaintStroke *)op->customdata); - 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, - nullptr, - wpaint_stroke_done, - 0); - - /* frees op->customdata */ - paint_stroke_exec(C, op, (PaintStroke *)op->customdata); - - 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 = nullptr; - } - - paint_stroke_cancel(C, op, (PaintStroke *)op->customdata); -} - -static int wpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) -{ - return paint_stroke_modal(C, op, event, (PaintStroke **)&op->customdata); -} - -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 = wpaint_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); + ED_object_vpaintmode_exit_ex(ob); } /** \} */ @@ -2760,7 +867,7 @@ void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot) /* api callbacks */ ot->exec = vpaint_mode_toggle_exec; - ot->poll = paint_mode_toggle_poll_test; + ot->poll = vwpaint::mode_toggle_poll_test; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -2843,9 +950,9 @@ static VPaintData *vpaint_init_vpaint(bContext *C, vpd->domain = domain; 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); + vwpaint::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)); @@ -2903,8 +1010,8 @@ static bool vpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo /* If not previously created, create vertex/weight paint mode session data */ vertex_paint_init_stroke(scene, depsgraph, ob); - vwpaint_update_cache_invariants(C, vp, ss, op, mouse); - vertex_paint_init_session_data(ts, ob); + vwpaint::update_cache_invariants(C, vp, ss, op, mouse); + vwpaint::init_session_data(ts, ob); return true; } @@ -2929,9 +1036,9 @@ static void do_vpaint_brush_blur_loops(bContext *C, const SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; const StrokeCache *cache = ss->cache; float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( + vwpaint::get_brush_alpha_data( scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(vp); + const bool use_normal = vwpaint::use_normal(vp); const bool use_vert_sel = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; @@ -2973,8 +1080,9 @@ static void do_vpaint_brush_blur_loops(bContext *C, float brush_strength = cache->bstrength; const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : 1.0f; - if (!test_brush_angle_falloff( - *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) { + if (!vwpaint::test_brush_angle_falloff( + *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) + { continue; } @@ -3079,9 +1187,9 @@ static void do_vpaint_brush_blur_verts(bContext *C, const SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; const StrokeCache *cache = ss->cache; float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( + vwpaint::get_brush_alpha_data( scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(vp); + const bool use_normal = vwpaint::use_normal(vp); const bool use_vert_sel = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; @@ -3123,8 +1231,9 @@ static void do_vpaint_brush_blur_verts(bContext *C, float brush_strength = cache->bstrength; const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : 1.0f; - if (!test_brush_angle_falloff( - *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) { + if (!vwpaint::test_brush_angle_falloff( + *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) + { continue; } const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); @@ -3222,9 +1331,9 @@ static void do_vpaint_brush_smear(bContext *C, float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( + vwpaint::get_brush_alpha_data( scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(vp); + const bool use_normal = vwpaint::use_normal(vp); const bool use_vert_sel = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; @@ -3274,8 +1383,9 @@ static void do_vpaint_brush_smear(bContext *C, float brush_strength = cache->bstrength; const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : 1.0f; - if (!test_brush_angle_falloff( - *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) { + if (!vwpaint::test_brush_angle_falloff( + *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) + { continue; } const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); @@ -3531,9 +1641,9 @@ static void vpaint_do_draw(bContext *C, const StrokeCache *cache = ss->cache; float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( + vwpaint::get_brush_alpha_data( scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(vp); + const bool use_normal = vwpaint::use_normal(vp); const bool use_vert_sel = (me->editflag & (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; @@ -3578,8 +1688,9 @@ static void vpaint_do_draw(bContext *C, float brush_strength = cache->bstrength; const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : 1.0f; - if (!test_brush_angle_falloff( - *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) { + if (!vwpaint::test_brush_angle_falloff( + *brush, vpd->normal_angle_precalc, angle_cos, &brush_strength)) + { continue; } const float brush_fade = BKE_brush_curve_strength(brush, sqrtf(test.dist), cache->radius); @@ -3727,7 +1838,7 @@ static void vpaint_do_paint(bContext *C, ss->cache->radial_symmetry_pass = i; SCULPT_cache_calc_brushdata_symm(ss->cache, symm, axis, angle); - Vector nodes = vwpaint_pbvh_gather_generic(ob, vp, sd, brush); + Vector nodes = vwpaint::pbvh_gather_generic(ob, vp, sd, brush); bke::GSpanAttributeWriter attribute = me->attributes_for_write().lookup_for_write_span( me->active_color_attribute); @@ -3819,7 +1930,7 @@ static void vpaint_stroke_update_step(bContext *C, SculptSession *ss = ob->sculpt; Sculpt *sd = CTX_data_tool_settings(C)->sculpt; - vwpaint_update_cache_variants(C, vp, ob, itemptr); + vwpaint::update_cache_variants(C, vp, ob, itemptr); float mat[4][4]; @@ -3844,7 +1955,7 @@ static void vpaint_stroke_update_step(bContext *C, * also needed for "Frame Selected" on last stroke. */ float loc_world[3]; mul_v3_m4v3(loc_world, ob->object_to_world, ss->cache->true_location); - paint_last_stroke_update(scene, loc_world); + vwpaint::last_stroke_update(scene, loc_world); ED_region_tag_redraw(vc->region); @@ -3867,7 +1978,7 @@ static void vpaint_stroke_done(const bContext *C, PaintStroke *stroke) if (ss->cache && ss->cache->alt_smooth) { ToolSettings *ts = CTX_data_tool_settings(C); VPaint *vp = ts->vpaint; - smooth_brush_toggle_off(C, &vp->paint, ss->cache); + vwpaint::smooth_brush_toggle_off(C, &vp->paint, ss->cache); } WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); diff --git a/source/blender/editors/sculpt_paint/paint_weight.cc b/source/blender/editors/sculpt_paint/paint_weight.cc new file mode 100644 index 00000000000..c09ba76d7a0 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_weight.cc @@ -0,0 +1,1970 @@ +/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \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_color.hh" +#include "BLI_color_mix.hh" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_vector.hh" + +#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_attribute.h" +#include "BKE_attribute.hh" +#include "BKE_brush.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.hh" +#include "BKE_mesh_mapping.h" +#include "BKE_object.h" +#include "BKE_object_deform.h" +#include "BKE_paint.h" +#include "BKE_report.h" + +#include "DEG_depsgraph.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_toolsystem.h" +#include "WM_types.h" + +#include "ED_image.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.hh" /* own include */ +#include "sculpt_intern.hh" + +using namespace blender; +using namespace blender::ed::sculpt_paint; +using blender::ed::sculpt_paint::vwpaint::NormalAnglePrecalc; + +struct WPaintAverageAccum { + uint len; + double value; +}; + +/** + * 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 WPaintData { + ViewContext vc; + NormalAnglePrecalc normal_angle_precalc; + + 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; +}; + +/* 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 */ +struct WeightPaintInfo { + + MutableSpan dvert; + + int defbase_tot; + + /* both must add up to 'defbase_tot' */ + int defbase_tot_sel; + int defbase_tot_unsel; + + 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() */ +}; + +static MDeformVert *defweight_prev_init(MDeformVert *dvert_prev, + MDeformVert *dvert_curr, + int index) +{ + const 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; +} + +/* vpaint has 'vpaint_blend' */ +static float wpaint_blend(const VPaint *wp, + float weight, + const float alpha, + float paintval, + const float /*brush_alpha_value*/, + const bool do_flip) +{ + const Brush *brush = wp->paint.brush; + IMB_BlendMode blend = (IMB_BlendMode)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 == nullptr) { + 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); + } + } + } +} + +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 = (Mesh *)ob->data; + MDeformVert *dv = &wpi->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, nullptr, 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 == nullptr) { + return; + } + + /* get the mirror def vars */ + if (index_mirr != -1) { + dv_mirr = &wpi->dvert[index_mirr]; + if (wp->flag & VP_FLAG_VGROUP_RESTRICT) { + dw_mirr = BKE_defvert_find_index(dv_mirr, vgroup_mirr); + + if (dw_mirr == nullptr) { + index_mirr = vgroup_mirr = -1; + dv_mirr = nullptr; + } + } + 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 = nullptr; + dw_mirr = nullptr; + } + + 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 (!vwpaint::brush_use_accumulate(wp)) { + MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, wpi->dvert.data(), index); + if (index_mirr != -1) { + defweight_prev_init(dvert_prev, wpi->dvert.data(), 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 #26193, 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 = (Mesh *)ob->data; + MDeformVert *dv = &wpi->dvert[index]; + bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; + + /* mirror vars */ + int index_mirr = -1; + MDeformVert *dv_mirr = nullptr; + + /* 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, nullptr, index, topology); + + if (!ELEM(index_mirr, -1, index)) { + dv_mirr = &wpi->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 (!vwpaint::brush_use_accumulate(wp)) { + MDeformVert *dvert_prev = ob->sculpt->mode.wpaint.dvert_prev; + MDeformVert *dv_prev = defweight_prev_init(dvert_prev, wpi->dvert.data(), index); + if (index_mirr != -1) { + defweight_prev_init(dvert_prev, wpi->dvert.data(), 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 != nullptr) { + 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 = nullptr; + } + 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 != nullptr) { + 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 != nullptr) { + 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); + } +} + +static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2]) +{ + Scene *scene = CTX_data_scene(C); + PaintStroke *stroke = (PaintStroke *)op->customdata; + ToolSettings *ts = scene->toolsettings; + Object *ob = CTX_data_active_object(C); + Mesh *me = BKE_mesh_from_object(ob); + WPaintData *wpd; + 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 = (bDeformGroup *)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 = (bDeformGroup *)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 = (bDeformGroup *)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 = (WPaintData *)MEM_callocN(sizeof(WPaintData), "WPaintData"); + paint_stroke_set_mode_data(stroke, wpd); + ED_view3d_viewcontext_init(C, &wpd->vc, depsgraph); + vwpaint::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 != nullptr || + 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 = (bool *)MEM_dupallocN(wpd->vgroup_validmap); + + if (wpd->lock_flags) { + bool *locked = (bool *)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 = (bool *)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 ? (bool *)MEM_dupallocN(wpd->lock_flags) : + (bool *)MEM_callocN(sizeof(bool) * defbase_tot, __func__); + tmpflags[wpd->active.index] = true; + wpd->active.lock = tmpflags; + + tmpflags = wpd->lock_flags ? (bool *)MEM_dupallocN(wpd->lock_flags) : + (bool *)MEM_callocN(sizeof(bool) * defbase_tot, __func__); + tmpflags[(wpd->mirror.index != -1) ? wpd->mirror.index : wpd->active.index] = true; + wpd->mirror.lock = tmpflags; + } + + /* If not previously created, create vertex/weight paint mode session data */ + vwpaint::init_stroke(depsgraph, ob); + vwpaint::update_cache_invariants(C, vp, ss, op, mouse); + vwpaint::init_session_data(ts, ob); + + if (ELEM(vp->paint.brush->weightpaint_tool, WPAINT_TOOL_SMEAR, WPAINT_TOOL_BLUR)) { + wpd->precomputed_weight = (float *)MEM_mallocN(sizeof(float) * me->totvert, __func__); + } + + if (ob->sculpt->mode.wpaint.dvert_prev != nullptr) { + 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 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 /*tls*/) +{ + SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; + const MDeformVert *dv = &data->wpi->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, WPaintData *wpd, WeightPaintInfo *wpi, Mesh *me) +{ + if (wpd->precomputed_weight_ready && !vwpaint::brush_use_accumulate_ex(brush, ob->mode)) { + return; + } + + /* threaded loop over vertices */ + SculptThreadedTaskData data; + data.C = C; + data.ob = ob; + data.wpd = wpd; + data.wpi = wpi; + data.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; +} + +/* -------------------------------------------------------------------- */ +/** \name Weight paint brushes. + * \{ */ + +static void do_wpaint_brush_blur_task_cb_ex(void *__restrict userdata, + const int n, + const TaskParallelTLS *__restrict /*tls*/) +{ + SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; + SculptSession *ss = data->ob->sculpt; + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); + const 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; + vwpaint::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); + + const blender::bke::AttributeAccessor attributes = data->me->attributes(); + const blender::VArray select_vert = *attributes.lookup_or_default( + ".select_vert", ATTR_DOMAIN_POINT, false); + + /* 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)) { + continue; + } + /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + /* If the vertex is selected */ + if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { + continue; + } + /* Get the average poly weight */ + int total_hit_loops = 0; + float weight_final = 0.0f; + for (const int p_index : gmap->vert_to_poly[v_index]) { + const blender::IndexRange poly = ss->polys[p_index]; + + total_hit_loops += poly.size(); + for (const int vert : ss->corner_verts.slice(poly)) { + weight_final += data->wpd->precomputed_weight[vert]; + } + } + + /* Apply the weight to the vertex. */ + if (total_hit_loops == 0) { + continue; + } + + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (!vwpaint::test_brush_angle_falloff( + *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) + { + continue; + } + + 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 /*tls*/) +{ + SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; + SculptSession *ss = data->ob->sculpt; + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); + const SculptVertexPaintGeomMap *gmap = &ss->mode.wpaint.gmap; + + const Brush *brush = data->brush; + const Scene *scene = CTX_data_scene(data->C); + const StrokeCache *cache = ss->cache; + if (!cache->is_last_valid) { + return; + } + + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + vwpaint::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 (normalize_v3(brush_dir) == 0.0f) { + return; + } + + const blender::bke::AttributeAccessor attributes = data->me->attributes(); + const blender::VArray select_vert = *attributes.lookup_or_default( + ".select_vert", ATTR_DOMAIN_POINT, false); + + 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)) { + continue; + } + + /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + const float3 &mv_curr = ss->vert_positions[v_index]; + + /* If the vertex is selected */ + if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { + continue; + } + + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (!vwpaint::test_brush_angle_falloff( + *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) + { + continue; + } + + 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 (const int p_index : gmap->vert_to_poly[v_index]) { + for (const int v_other_index : ss->corner_verts.slice(ss->polys[p_index])) { + if (v_other_index == v_index) { + continue; + } + + /* Get the direction from the selected vert to the neighbor. */ + float other_dir[3]; + sub_v3_v3v3(other_dir, mv_curr, ss->vert_positions[v_other_index]); + 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) { + continue; + } + 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 /*tls*/) +{ + SculptThreadedTaskData *data = (SculptThreadedTaskData *)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; + vwpaint::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); + + const blender::bke::AttributeAccessor attributes = data->me->attributes(); + const blender::VArray select_vert = *attributes.lookup_or_default( + ".select_vert", ATTR_DOMAIN_POINT, false); + + /* 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)) { + continue; + } + /* 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 ? ss->corner_verts[vd.grid_indices[vd.g]] : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + + /* If the vertex is selected */ + if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { + continue; + } + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (!vwpaint::test_brush_angle_falloff( + *brush, data->wpd->normal_angle_precalc, angle_cos, &brush_strength)) + { + continue; + } + 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 /*tls*/) +{ + SculptThreadedTaskData *data = (SculptThreadedTaskData *)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; + + WPaintAverageAccum *accum = (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); + + const blender::bke::AttributeAccessor attributes = data->me->attributes(); + const blender::VArray select_vert = *attributes.lookup_or_default( + ".select_vert", ATTR_DOMAIN_POINT, false); + + /* 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)) { + continue; + } + + const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (angle_cos <= 0.0f || + BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) <= 0.0f) + { + continue; + } + + const int v_index = has_grids ? ss->corner_verts[vd.grid_indices[vd.g]] : + vd.vert_indices[vd.i]; + /* If the vertex is selected. */ + if ((use_face_sel || use_vert_sel) && !select_vert[v_index]) { + continue; + } + + const MDeformVert *dv = &data->wpi->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, Span nodes) +{ + WPaintAverageAccum *accum = (WPaintAverageAccum *)MEM_mallocN(sizeof(*accum) * nodes.size(), + __func__); + data->custom_data = accum; + + TaskParallelSettings settings; + BKE_pbvh_parallel_range_settings(&settings, true, nodes.size()); + BLI_task_parallel_range( + 0, nodes.size(), data, do_wpaint_brush_calc_average_weight_cb_ex, &settings); + + uint accum_len = 0; + double accum_weight = 0.0; + for (int i = 0; i < nodes.size(); 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, + WPaintData *wpd, + WeightPaintInfo *wpi, + Mesh *me, + Span nodes) +{ + Scene *scene = CTX_data_scene(C); + const Brush *brush = ob->sculpt->cache->brush; + + /* threaded loop over nodes */ + SculptThreadedTaskData data = {nullptr}; + data.C = C; + data.sd = sd; + data.ob = ob; + data.brush = brush; + data.nodes = nodes; + data.vp = vp; + data.wpd = wpd; + data.wpi = wpi; + data.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, nodes.size()); + + switch ((eBrushWeightPaintTool)brush->weightpaint_tool) { + case WPAINT_TOOL_AVERAGE: + calculate_average_weight(&data, nodes); + BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_draw_task_cb_ex, &settings); + break; + case WPAINT_TOOL_SMEAR: + BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_smear_task_cb_ex, &settings); + break; + case WPAINT_TOOL_BLUR: + BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_blur_task_cb_ex, &settings); + break; + case WPAINT_TOOL_DRAW: + BLI_task_parallel_range(0, nodes.size(), &data, do_wpaint_brush_draw_task_cb_ex, &settings); + break; + } +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Enter Weight Paint Mode + * \{ */ + +void ED_object_wpaintmode_enter_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob) +{ + vwpaint::mode_enter_generic(bmain, depsgraph, scene, ob, OB_MODE_WEIGHT_PAINT); +} +void ED_object_wpaintmode_enter(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 Weight Paint Mode + * \{ */ + +void ED_object_wpaintmode_exit_ex(Object *ob) +{ + vwpaint::mode_exit_generic(ob, OB_MODE_WEIGHT_PAINT); +} +void ED_object_wpaintmode_exit(bContext *C) +{ + Object *ob = CTX_data_active_object(C); + ED_object_wpaintmode_exit_ex(ob); +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Toggle Weight Paint Operator + * \{ */ + +bool weight_paint_mode_poll(bContext *C) +{ + const Object *ob = CTX_data_active_object(C); + + return ob && ob->mode == OB_MODE_WEIGHT_PAINT && ((const Mesh *)ob->data)->totpoly; +} + +static bool weight_paint_poll_ex(bContext *C, bool check_tool) +{ + const Object *ob = CTX_data_active_object(C); + const ScrArea *area; + + if ((ob != nullptr) && (ob->mode & OB_MODE_WEIGHT_PAINT) && + (BKE_paint_brush(&CTX_data_tool_settings(C)->wpaint->paint) != nullptr) && + (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); +} + +/** + * \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); + 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, (eObjectMode)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; +} + +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 = vwpaint::mode_toggle_poll_test; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \name Weight Paint Operator + * \{ */ + +static void wpaint_do_paint(bContext *C, + Object *ob, + VPaint *wp, + Sculpt *sd, + WPaintData *wpd, + WeightPaintInfo *wpi, + Mesh *me, + Brush *brush, + const ePaintSymmetryFlags 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); + + Vector nodes = vwpaint::pbvh_gather_generic(ob, wp, sd, brush); + + wpaint_paint_leaves(C, ob, sd, wp, wpd, wpi, me, nodes); +} + +static void wpaint_do_radial_symmetry(bContext *C, + Object *ob, + VPaint *wp, + Sculpt *sd, + WPaintData *wpd, + WeightPaintInfo *wpi, + Mesh *me, + Brush *brush, + const ePaintSymmetryFlags 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.cc'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, WPaintData *wpd, WeightPaintInfo *wpi) +{ + Brush *brush = BKE_paint_brush(&wp->paint); + Mesh *me = (Mesh *)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 = ePaintSymmetryFlags(0); + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'X'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(0), 'Y'); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, ePaintSymmetryFlags(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 || !ELEM(i, 3, 5))) { + const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i); + cache->mirror_symmetry_pass = symm; + cache->radial_symmetry_pass = 0; + SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0); + + if (i & (1 << 0)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'X'); + } + if (i & (1 << 1)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Y'); + } + if (i & (1 << 2)) { + wpaint_do_paint(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z', 0, 0); + wpaint_do_radial_symmetry(C, ob, wp, sd, wpd, wpi, me, brush, symm, 'Z'); + } + } + } + copy_v3_v3(cache->true_last_location, cache->true_location); + cache->is_last_valid = true; +} + +static void wpaint_stroke_update_step(bContext *C, + wmOperator * /*op*/, + 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); + WPaintData *wpd = (WPaintData *)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 nullptr, make sure we initialize all members below */ + WeightPaintInfo wpi; + + /* cannot paint if there is no stroke data */ + if (wpd == nullptr) { + /* 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->object_to_world); + + Mesh *mesh = static_cast(ob->data); + + /* *** setup WeightPaintInfo - pass onto do_weight_paint_vertex *** */ + wpi.dvert = mesh->deform_verts_for_write(); + + 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") || ss->cache->invert; + wpi.do_multipaint = wpd->do_multipaint; + wpi.do_auto_normalize = ((ts->auto_normalize != 0) && (wpi.vgroup_validmap != nullptr) && + (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, mesh); + } + + 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->object_to_world, ss->cache->true_location); + vwpaint::last_stroke_update(scene, loc_world); + + BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); + + DEG_id_tag_update(&mesh->id, 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, PaintStroke *stroke) +{ + Object *ob = CTX_data_active_object(C); + WPaintData *wpd = (WPaintData *)paint_stroke_mode_data(stroke); + + if (wpd) { + MEM_SAFE_FREE(wpd->defbase_sel); + MEM_SAFE_FREE(wpd->vgroup_validmap); + MEM_SAFE_FREE(wpd->vgroup_locked); + MEM_SAFE_FREE(wpd->vgroup_unlocked); + MEM_SAFE_FREE(wpd->lock_flags); + MEM_SAFE_FREE(wpd->active.lock); + MEM_SAFE_FREE(wpd->mirror.lock); + MEM_SAFE_FREE(wpd->precomputed_weight); + MEM_freeN(wpd); + } + + SculptSession *ss = ob->sculpt; + + if (ss->cache->alt_smooth) { + ToolSettings *ts = CTX_data_tool_settings(C); + VPaint *vp = ts->wpaint; + vwpaint::smooth_brush_toggle_off(C, &vp->paint, ss->cache); + } + + /* and particles too */ + if (ob->particlesystem.first) { + ParticleSystem *psys; + int i; + + for (psys = (ParticleSystem *)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((ID *)ob->data, 0); + + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); + + SCULPT_cache_free(ob->sculpt->cache); + ob->sculpt->cache = nullptr; +} + +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, + nullptr, + wpaint_stroke_done, + event->type); + + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { + paint_stroke_free(C, op, (PaintStroke *)op->customdata); + 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, + nullptr, + wpaint_stroke_done, + 0); + + /* frees op->customdata */ + paint_stroke_exec(C, op, (PaintStroke *)op->customdata); + + 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 = nullptr; + } + + paint_stroke_cancel(C, op, (PaintStroke *)op->customdata); +} + +static int wpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + return paint_stroke_modal(C, op, event, (PaintStroke **)&op->customdata); +} + +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 = wpaint_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); +} +/** \} */