diff options
author | Joseph Eagar <joeedh@gmail.com> | 2022-04-21 08:03:45 +0300 |
---|---|---|
committer | Joseph Eagar <joeedh@gmail.com> | 2022-04-21 08:14:03 +0300 |
commit | 575ade22d4de472ccf9e7d2dc1ffca37416c58f6 (patch) | |
tree | a3f3432ea1c1ad6c4792ec3cfeea55e97b9280b8 | |
parent | c6ed879f9aa7cd863f146cc7104f16cfe8a73e8a (diff) |
Commit D14179: Revamp Vertex Paint With C++
- Verrtex paint mode has been refactored into C++ templates.
It now works with both byte and float colors and point
& corner attribute domains.
- There is a new API for mixing colors (also based
on C++ templates). Unlike the existing APIs byte
and float colors are interpolated identically.
Interpolation does happen in a squared rgb space,
this may be changed in the future.
- Vertex paint now uses the sculpt undo system.
Reviewed By: Brecht Van Lommel.
Differential Revision: https://developer.blender.org/D14179
Ref D14179
18 files changed, 2373 insertions, 1275 deletions
diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject 63699f968344db7dc853d2c5972325beea44900 +Subproject 716dc02ec30c0810513f7b4adc4ae865ae50c4e diff --git a/release/scripts/addons b/release/scripts/addons -Subproject baa581415c7ed23d7c45ef87363174813567268 +Subproject 787ea78f7fa6f0373d80ba1247768402df93f8a diff --git a/source/blender/blenkernel/BKE_paint.h b/source/blender/blenkernel/BKE_paint.h index 4ae37095411..10043941948 100644 --- a/source/blender/blenkernel/BKE_paint.h +++ b/source/blender/blenkernel/BKE_paint.h @@ -615,9 +615,6 @@ typedef struct SculptSession { union { struct { struct SculptVertexPaintGeomMap gmap; - - /* For non-airbrush painting to re-apply from the original (MLoop aligned). */ - unsigned int *previous_color; } vpaint; struct { diff --git a/source/blender/blenkernel/intern/paint.c b/source/blender/blenkernel/intern/paint.c index fe8b002302c..472e2c7ada8 100644 --- a/source/blender/blenkernel/intern/paint.c +++ b/source/blender/blenkernel/intern/paint.c @@ -1341,8 +1341,6 @@ void BKE_sculptsession_free_vwpaint_data(struct SculptSession *ss) struct SculptVertexPaintGeomMap *gmap = NULL; if (ss->mode_type == OB_MODE_VERTEX_PAINT) { gmap = &ss->mode.vpaint.gmap; - - MEM_SAFE_FREE(ss->mode.vpaint.previous_color); } else if (ss->mode_type == OB_MODE_WEIGHT_PAINT) { gmap = &ss->mode.wpaint.gmap; diff --git a/source/blender/blenlib/BLI_color.hh b/source/blender/blenlib/BLI_color.hh index 0754221eb65..98fd7d0f15d 100644 --- a/source/blender/blenlib/BLI_color.hh +++ b/source/blender/blenlib/BLI_color.hh @@ -345,5 +345,7 @@ BLI_color_convert_to_theme4b(const ColorSceneLinear4f<eAlpha::Straight> &scene_l using ColorGeometry4f = ColorSceneLinear4f<eAlpha::Premultiplied>; using ColorGeometry4b = ColorSceneLinearByteEncoded4b<eAlpha::Premultiplied>; +using ColorPaint4f = ColorSceneLinear4f<eAlpha::Straight>; +using ColorPaint4b = ColorSceneLinearByteEncoded4b<eAlpha::Straight>; } // namespace blender diff --git a/source/blender/blenlib/BLI_color_mix.hh b/source/blender/blenlib/BLI_color_mix.hh new file mode 100644 index 00000000000..8398f2c3326 --- /dev/null +++ b/source/blender/blenlib/BLI_color_mix.hh @@ -0,0 +1,1053 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup blenlib + * + * Contains color mixing utilities. + * + */ + +#include "BLI_color.hh" +#include "BLI_math_base.h" +#include "BLI_math_color.h" +#include "BLI_sys_types.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" + +#include <type_traits> + +namespace blender::color { + +struct ByteTraits { + using ValueType = uchar; + using BlendType = int; + + inline static const uchar range = 255; /* Zero-based maximum value. */ + inline static const float frange = 255.0f; /* Convienent floating-point version of range. */ + inline static const int cmpRange = 254; + inline static const int expandedRange = 256; /* One-based maxium value. */ + + inline static const int bytes = 1; + inline static const float unit = 255.0f; + + static inline BlendType divide_round(BlendType a, BlendType b) + { + return divide_round_i(a, b); + } + + static inline BlendType min(BlendType a, BlendType b) + { + return min_ii(a, b); + } + + static inline BlendType max(BlendType a, BlendType b) + { + return max_ii(a, b); + } + /* Discretizes in steps of 1.0 / range */ + static inline ValueType round(float f) + { + return round_fl_to_uchar(f); + } +}; + +struct FloatTraits { + using ValueType = float; + using BlendType = float; + + inline const static float range = 1.0f; + inline const static float frange = 1.0f; + inline const static float cmpRange = 0.9999f; + inline static const int expandedRange = 1.0f; + + inline const static float unit = 1.0f; + inline const static int bytes = 4; + + static inline BlendType divide_round(BlendType a, BlendType b) + { + return a / b; + } + + static inline BlendType min(BlendType a, BlendType b) + { + return min_ff(a, b); + } + + static inline BlendType max(BlendType a, BlendType b) + { + return min_ff(a, b); + } + + /* Discretizes in steps of 1.0 / range */ + static inline ValueType round(float f) + { + return f; + } +}; + +static float get_luminance(ColorPaint4f c) +{ + return IMB_colormanagement_get_luminance(&c.r); +} + +static int get_luminance(ColorPaint4b c) +{ + return IMB_colormanagement_get_luminance_byte(&c.r); +} + +#define EPS_SATURATION 0.0005f + +/* -------------------------------------------------------------------- */ +/** \name Color Blending Modes + * \{ */ + +template<typename Color, typename Traits> +static Color mix_blend(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Color col_mix(0, 0, 0, 0); + Blend mfac; + + if (fac == 0) { + return col_src; + } + + if (fac >= Traits::range) { + return col_dst; + } + + mfac = Traits::range - fac; + + cp_src = &col_src.r; + cp_dst = &col_dst.r; + cp_mix = &col_mix.r; + + /* Updated to use the rgb squared color model which blends nicer. */ + Blend r1 = cp_src[0] * cp_src[0]; + Blend g1 = cp_src[1] * cp_src[1]; + Blend b1 = cp_src[2] * cp_src[2]; + Blend a1 = cp_src[3] * cp_src[3]; + + Blend r2 = cp_dst[0] * cp_dst[0]; + Blend g2 = cp_dst[1] * cp_dst[1]; + Blend b2 = cp_dst[2] * cp_dst[2]; + Blend a2 = cp_dst[3] * cp_dst[3]; + + cp_mix[0] = Traits::round(sqrtf(Traits::divide_round((mfac * r1 + fac * r2), Traits::range))); + cp_mix[1] = Traits::round(sqrtf(Traits::divide_round((mfac * g1 + fac * g2), Traits::range))); + cp_mix[2] = Traits::round(sqrtf(Traits::divide_round((mfac * b1 + fac * b2), Traits::range))); + cp_mix[3] = Traits::round(sqrtf(Traits::divide_round((mfac * a1 + fac * a2), Traits::range))); + return Color(col_mix[0], col_mix[1], col_mix[2], col_mix[3]); + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_add(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + cp_src = (Value *)&col_src.r; + cp_dst = (Value *)&col_dst.r; + cp_mix = (Value *)&col_mix.r; + + temp = cp_src[0] + Traits::divide_round((fac * cp_dst[0]), Traits::range); + cp_mix[0] = (temp > Traits::cmpRange) ? Traits::range : temp; + temp = cp_src[1] + Traits::divide_round((fac * cp_dst[1]), Traits::range); + cp_mix[1] = (temp > Traits::cmpRange) ? Traits::range : temp; + temp = cp_src[2] + Traits::divide_round((fac * cp_dst[2]), Traits::range); + cp_mix[2] = (temp > Traits::cmpRange) ? Traits::range : temp; + temp = cp_src[3] + Traits::divide_round((fac * cp_dst[3]), Traits::range); + cp_mix[3] = (temp > Traits::cmpRange) ? Traits::range : temp; + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_sub(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend temp; + Color col_mix(0, 0, 0, 0); + + cp_src = (Value *)&col_src.r; + cp_dst = (Value *)&col_dst.r; + cp_mix = (Value *)&col_mix.r; + + temp = cp_src[0] - Traits::divide_round((fac * cp_dst[0]), Traits::range); + cp_mix[0] = (temp < 0) ? 0 : temp; + temp = cp_src[1] - Traits::divide_round((fac * cp_dst[1]), Traits::range); + cp_mix[1] = (temp < 0) ? 0 : temp; + temp = cp_src[2] - Traits::divide_round((fac * cp_dst[2]), Traits::range); + cp_mix[2] = (temp < 0) ? 0 : temp; + temp = cp_src[3] - Traits::divide_round((fac * cp_dst[3]), Traits::range); + cp_mix[3] = (temp < 0) ? 0 : temp; + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_mul(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + /* first mul, then blend the fac */ + cp_mix[0] = Traits::divide_round(mfac * cp_src[0] * Traits::range + fac * cp_dst[0] * cp_src[0], + Traits::range * Traits::range); + cp_mix[1] = Traits::divide_round(mfac * cp_src[1] * Traits::range + fac * cp_dst[1] * cp_src[1], + Traits::range * Traits::range); + cp_mix[2] = Traits::divide_round(mfac * cp_src[2] * Traits::range + fac * cp_dst[2] * cp_src[2], + Traits::range * Traits::range); + cp_mix[3] = Traits::divide_round(mfac * cp_src[3] * Traits::range + fac * cp_dst[3] * cp_src[3], + Traits::range * Traits::range); + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_lighten(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + if (fac >= Traits::range) { + return col_dst; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + /* See if we're lighter, if so mix, else don't do anything. + * if the paint color is darker then the original, then ignore */ + if (get_luminance(cp_src) > get_luminance(cp_dst)) { + return col_src; + } + + cp_mix[0] = Traits::divide_round(mfac * cp_src[0] + fac * cp_dst[0], Traits::range); + cp_mix[1] = Traits::divide_round(mfac * cp_src[1] + fac * cp_dst[1], Traits::range); + cp_mix[2] = Traits::divide_round(mfac * cp_src[2] + fac * cp_dst[2], Traits::range); + cp_mix[3] = Traits::divide_round(mfac * cp_src[3] + fac * cp_dst[3], Traits::range); + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_darken(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + if (fac >= Traits::range) { + return col_dst; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + /* See if we're darker, if so mix, else don't do anything. + * if the paint color is brighter then the original, then ignore */ + if (get_luminance(cp_src) < get_luminance(cp_dst)) { + return col_src; + } + + cp_mix[0] = Traits::divide_round((mfac * cp_src[0] + fac * cp_dst[0]), Traits::range); + cp_mix[1] = Traits::divide_round((mfac * cp_src[1] + fac * cp_dst[1]), Traits::range); + cp_mix[2] = Traits::divide_round((mfac * cp_src[2] + fac * cp_dst[2]), Traits::range); + cp_mix[3] = Traits::divide_round((mfac * cp_src[3] + fac * cp_dst[3]), Traits::range); + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_colordodge(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + Blend dodgefac = (Blend)((float)Traits::range * 0.885f); /* ~225/255 */ + + temp = (cp_dst[0] == Traits::range) ? + Traits::range : + Traits::min((cp_src[0] * dodgefac) / (Traits::range - cp_dst[0]), Traits::range); + cp_mix[0] = (mfac * cp_src[0] + temp * fac) / Traits::range; + temp = (cp_dst[1] == Traits::range) ? + Traits::range : + Traits::min((cp_src[1] * dodgefac) / (Traits::range - cp_dst[1]), Traits::range); + cp_mix[1] = (mfac * cp_src[1] + temp * fac) / Traits::range; + temp = (cp_dst[2] == Traits::range) ? + Traits::range : + Traits::min((cp_src[2] * dodgefac) / (Traits::range - cp_dst[2]), Traits::range); + cp_mix[2] = (mfac * cp_src[2] + temp * fac) / Traits::range; + temp = (cp_dst[3] == Traits::range) ? + Traits::range : + Traits::min((cp_src[3] * dodgefac) / (Traits::range - cp_dst[3]), Traits::range); + cp_mix[3] = (mfac * cp_src[3] + temp * fac) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_difference(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + temp = abs(cp_src[0] - cp_dst[0]); + cp_mix[0] = (mfac * cp_src[0] + temp * fac) / Traits::range; + temp = abs(cp_src[1] - cp_dst[1]); + cp_mix[1] = (mfac * cp_src[1] + temp * fac) / Traits::range; + temp = abs(cp_src[2] - cp_dst[2]); + cp_mix[2] = (mfac * cp_src[2] + temp * fac) / Traits::range; + temp = abs(cp_src[3] - cp_dst[3]); + cp_mix[3] = (mfac * cp_src[3] + temp * fac) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_screen(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + temp = Traits::max(Traits::range - (((Traits::range - cp_src[0]) * (Traits::range - cp_dst[0])) / + Traits::range), + 0); + cp_mix[0] = (mfac * cp_src[0] + temp * fac) / Traits::range; + temp = Traits::max(Traits::range - (((Traits::range - cp_src[1]) * (Traits::range - cp_dst[1])) / + Traits::range), + 0); + cp_mix[1] = (mfac * cp_src[1] + temp * fac) / Traits::range; + temp = Traits::max(Traits::range - (((Traits::range - cp_src[2]) * (Traits::range - cp_dst[2])) / + Traits::range), + 0); + cp_mix[2] = (mfac * cp_src[2] + temp * fac) / Traits::range; + temp = Traits::max(Traits::range - (((Traits::range - cp_src[3]) * (Traits::range - cp_dst[3])) / + Traits::range), + 0); + cp_mix[3] = (mfac * cp_src[3] + temp * fac) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_hardlight(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + int i = 0; + + for (i = 0; i < 4; i++) { + if (cp_dst[i] > (Traits::range / 2)) { + temp = Traits::range - ((Traits::range - 2 * (cp_dst[i] - (Traits::range / 2))) * + (Traits::range - cp_src[i]) / Traits::range); + } + else { + temp = (2 * cp_dst[i] * cp_src[i]) / Traits::expandedRange; + } + cp_mix[i] = Traits::min((mfac * cp_src[i] + temp * fac) / Traits::range, Traits::range); + } + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_overlay(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + int i = 0; + + for (i = 0; i < 4; i++) { + if (cp_src[i] > (Traits::range / 2)) { + temp = Traits::range - ((Traits::range - 2 * (cp_src[i] - (Traits::range / 2))) * + (Traits::range - cp_dst[i]) / Traits::range); + } + else { + temp = (2 * cp_dst[i] * cp_src[i]) / Traits::expandedRange; + } + cp_mix[i] = Traits::min((mfac * cp_src[i] + temp * fac) / Traits::range, Traits::range); + } + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_softlight(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + /* Use divide_round so we don't alter original byte equations. */ + const int add = Traits::divide_round(Traits::range, 4); + + for (int i = 0; i < 4; i++) { + if (cp_src[i] < (Traits::range / 2)) { + temp = ((2 * ((cp_dst[i] / 2) + add)) * cp_src[i]) / Traits::range; + } + else { + temp = Traits::range - (2 * (Traits::range - ((cp_dst[i] / 2) + add)) * + (Traits::range - cp_src[i]) / Traits::range); + } + cp_mix[i] = (temp * fac + cp_src[i] * mfac) / Traits::range; + } + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_exclusion(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac, temp; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + int i = 0; + + for (i = 0; i < 4; i++) { + temp = (Traits::range / 2) - + ((2 * (cp_src[i] - (Traits::range / 2)) * (cp_dst[i] - (Traits::range / 2))) / + Traits::range); + cp_mix[i] = (temp * fac + cp_src[i] * mfac) / Traits::range; + } + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_luminosity(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp_src[0] / Traits::frange, + cp_src[1] / Traits::frange, + cp_src[2] / Traits::frange, + &h1, + &s1, + &v1); + rgb_to_hsv(cp_dst[0] / Traits::frange, + cp_dst[1] / Traits::frange, + cp_dst[2] / Traits::frange, + &h2, + &s2, + &v2); + + v1 = v2; + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp_mix[0] = ((Blend)(r * Traits::frange) * fac + mfac * cp_src[0]) / Traits::range; + cp_mix[1] = ((Blend)(g * Traits::frange) * fac + mfac * cp_src[1]) / Traits::range; + cp_mix[2] = ((Blend)(b * Traits::frange) * fac + mfac * cp_src[2]) / Traits::range; + cp_mix[3] = ((Blend)(cp_dst[3]) * fac + mfac * cp_src[3]) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_saturation(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp_src[0] / Traits::frange, + cp_src[1] / Traits::frange, + cp_src[2] / Traits::frange, + &h1, + &s1, + &v1); + rgb_to_hsv(cp_dst[0] / Traits::frange, + cp_dst[1] / Traits::frange, + cp_dst[2] / Traits::frange, + &h2, + &s2, + &v2); + + if (s1 > EPS_SATURATION) { + s1 = s2; + } + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp_mix[0] = ((Blend)(r * Traits::frange) * fac + mfac * cp_src[0]) / Traits::range; + cp_mix[1] = ((Blend)(g * Traits::frange) * fac + mfac * cp_src[1]) / Traits::range; + cp_mix[2] = ((Blend)(b * Traits::frange) * fac + mfac * cp_src[2]) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_hue(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + rgb_to_hsv(cp_src[0] / Traits::frange, + cp_src[1] / Traits::frange, + cp_src[2] / Traits::frange, + &h1, + &s1, + &v1); + rgb_to_hsv(cp_dst[0] / Traits::frange, + cp_dst[1] / Traits::frange, + cp_dst[2] / Traits::frange, + &h2, + &s2, + &v2); + + h1 = h2; + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp_mix[0] = ((Blend)(r * Traits::frange) * fac + mfac * cp_src[0]) / Traits::range; + cp_mix[1] = ((Blend)(g * Traits::frange) * fac + mfac * cp_src[1]) / Traits::range; + cp_mix[2] = ((Blend)(b * Traits::frange) * fac + mfac * cp_src[2]) / Traits::range; + cp_mix[3] = ((Blend)(cp_dst[3]) * fac + mfac * cp_src[3]) / Traits::range; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_alpha_add(Color col_src, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_mix; + Blend temp; + Color col_mix = col_src; + + if (fac == 0) { + return col_src; + } + + cp_src = (Value *)&col_src; + cp_mix = (Value *)&col_mix; + + temp = cp_src[3] + fac; + cp_mix[3] = (temp > Traits::cmpRange) ? Traits::range : temp; + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_alpha_sub(Color col_src, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_mix; + Blend temp; + Color col_mix = col_src; + + if (fac == 0) { + return col_src; + } + + cp_src = (Value *)&col_src; + cp_mix = (Value *)&col_mix; + + temp = cp_src[3] - fac; + cp_mix[3] = temp < 0 ? 0 : temp; + + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_pinlight(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + const Blend cmp = Traits::range / 2; + + int i = 3; + Blend temp; + + while (i--) { + if (cp_dst[i] > cmp) { + temp = Traits::max(2 * (cp_dst[i] - cmp), cp_src[i]); + } + else { + temp = Traits::min(2 * cp_dst[i], cp_src[i]); + } + cp_mix[i] = (Value)((Traits::min(temp, Traits::range) * fac + cp_src[i] * mfac) / + Traits::range); + } + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_linearlight(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + const Blend cmp = Traits::range / 2; + + int i = 3; + while (i--) { + Blend temp; + + if (cp_dst[i] > cmp) { + temp = Traits::min(cp_src[i] + 2 * (cp_dst[i] - cmp), Traits::range); + } + else { + temp = Traits::max(cp_src[i] + 2 * cp_dst[i] - Traits::range, 0); + } + + cp_mix[i] = (Value)((temp * fac + cp_src[i] * mfac) / Traits::range); + } + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_vividlight(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + const Blend cmp = Traits::range / 2; + + int i = 3; + + while (i--) { + Blend temp; + + if (cp_dst[i] == Traits::range) { + temp = (cp_src[i] == 0) ? cmp : Traits::range; + } + else if (cp_dst[i] == 0) { + temp = (cp_src[i] == Traits::range) ? cmp : 0; + } + else if (cp_dst[i] > cmp) { + temp = Traits::min(((cp_src[i]) * Traits::range) / (2 * (Traits::range - cp_dst[i])), + Traits::range); + } + else { + temp = Traits::max( + Traits::range - ((Traits::range - cp_src[i]) * Traits::range / (2 * cp_dst[i])), 0); + } + col_mix[i] = (Value)((temp * fac + cp_src[i] * mfac) / Traits::range); + } + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_color(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + float h1, s1, v1; + float h2, s2, v2; + float r, g, b; + + rgb_to_hsv(cp_src[0] / Traits::frange, + cp_src[1] / Traits::frange, + cp_src[2] / Traits::frange, + &h1, + &s1, + &v1); + rgb_to_hsv(cp_dst[0] / Traits::frange, + cp_dst[1] / Traits::frange, + cp_dst[2] / Traits::frange, + &h2, + &s2, + &v2); + + h1 = h2; + s1 = s2; + + hsv_to_rgb(h1, s1, v1, &r, &g, &b); + + cp_mix[0] = (Value)(((Blend)(r * Traits::frange) * fac + cp_src[0] * mfac) / Traits::range); + cp_mix[1] = (Value)(((Blend)(g * Traits::frange) * fac + cp_src[1] * mfac) / Traits::range); + cp_mix[2] = (Value)(((Blend)(b * Traits::frange) * fac + cp_src[2] * mfac) / Traits::range); + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_colorburn(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + int i = 3; + + while (i--) { + const Blend temp = + (cp_dst[i] == 0) ? + 0 : + Traits::max(Traits::range - ((Traits::range - cp_src[i]) * Traits::range) / cp_dst[i], + 0); + cp_mix[i] = (Value)((temp * fac + cp_src[i] * mfac) / Traits::range); + } + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +static Color mix_linearburn(Color col_src, Color col_dst, typename Traits::BlendType fac) +{ + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + Value *cp_src, *cp_dst, *cp_mix; + Blend mfac; + Color col_mix(0, 0, 0, 0); + + if (fac == 0) { + return col_src; + } + + mfac = Traits::range - fac; + + cp_src = (Value *)&col_src; + cp_dst = (Value *)&col_dst; + cp_mix = (Value *)&col_mix; + + int i = 3; + + while (i--) { + const Blend temp = Traits::max(cp_src[i] + cp_dst[i] - Traits::range, 0); + cp_mix[i] = (Value)((temp * fac + cp_src[i] * mfac) / Traits::range); + } + + col_mix.a = col_src.a; + return col_mix; +} + +template<typename Color, typename Traits> +BLI_INLINE Color BLI_mix_colors(const IMB_BlendMode tool, + const Color a, + const Color b, + const typename Traits::BlendType alpha) +{ + switch ((IMB_BlendMode)tool) { + case IMB_BLEND_MIX: + return mix_blend<Color, Traits>(a, b, alpha); + case IMB_BLEND_ADD: + return mix_add<Color, Traits>(a, b, alpha); + case IMB_BLEND_SUB: + return mix_sub<Color, Traits>(a, b, alpha); + case IMB_BLEND_MUL: + return mix_mul<Color, Traits>(a, b, alpha); + case IMB_BLEND_LIGHTEN: + return mix_lighten<Color, Traits>(a, b, alpha); + case IMB_BLEND_DARKEN: + return mix_darken<Color, Traits>(a, b, alpha); + case IMB_BLEND_COLORDODGE: + return mix_colordodge<Color, Traits>(a, b, alpha); + case IMB_BLEND_COLORBURN: + return mix_colorburn<Color, Traits>(a, b, alpha); + case IMB_BLEND_DIFFERENCE: + return mix_difference<Color, Traits>(a, b, alpha); + case IMB_BLEND_SCREEN: + return mix_screen<Color, Traits>(a, b, alpha); + case IMB_BLEND_HARDLIGHT: + return mix_hardlight<Color, Traits>(a, b, alpha); + case IMB_BLEND_OVERLAY: + return mix_overlay<Color, Traits>(a, b, alpha); + case IMB_BLEND_SOFTLIGHT: + return mix_softlight<Color, Traits>(a, b, alpha); + case IMB_BLEND_EXCLUSION: + return mix_exclusion<Color, Traits>(a, b, alpha); + case IMB_BLEND_LUMINOSITY: + return mix_luminosity<Color, Traits>(a, b, alpha); + case IMB_BLEND_SATURATION: + return mix_saturation<Color, Traits>(a, b, alpha); + case IMB_BLEND_HUE: + return mix_hue<Color, Traits>(a, b, alpha); + /* non-color */ + case IMB_BLEND_ERASE_ALPHA: + return mix_alpha_sub<Color, Traits>(a, alpha); + case IMB_BLEND_ADD_ALPHA: + return mix_alpha_add<Color, Traits>(a, alpha); + case IMB_BLEND_PINLIGHT: + return mix_pinlight<Color, Traits>(a, b, alpha); + case IMB_BLEND_LINEARLIGHT: + return mix_linearlight<Color, Traits>(a, b, alpha); + case IMB_BLEND_VIVIDLIGHT: + return mix_vividlight<Color, Traits>(a, b, alpha); + case IMB_BLEND_COLOR: + return mix_color<Color, Traits>(a, b, alpha); + default: + BLI_assert(0); + return Color(0, 0, 0, 0); + } +} +/** \} */ + +} // namespace blender::color diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index e8a3851e082..446a027b03b 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -171,6 +171,7 @@ set(SRC BLI_boxpack_2d.h BLI_buffer.h BLI_color.hh + BLI_color_mix.hh BLI_compiler_attrs.h BLI_compiler_compat.h BLI_compiler_typecheck.h diff --git a/source/blender/blenloader/intern/versioning_300.c b/source/blender/blenloader/intern/versioning_300.c index ddd9405c571..a9a63e1d4b9 100644 --- a/source/blender/blenloader/intern/versioning_300.c +++ b/source/blender/blenloader/intern/versioning_300.c @@ -2448,6 +2448,54 @@ void blo_do_versions_300(FileData *fd, Library *UNUSED(lib), Main *bmain) } } + /* Rebuild active/render color attribute references. */ + if (!MAIN_VERSION_ATLEAST(bmain, 302, 6)) { + LISTBASE_FOREACH (Brush *, br, &bmain->brushes) { + /* buggy code in wm_toolsystem broke smear in old files, + reset to defaults */ + if (br->sculpt_tool == SCULPT_TOOL_SMEAR) { + br->alpha = 1.0f; + br->spacing = 5; + br->flag &= ~BRUSH_ALPHA_PRESSURE; + br->flag &= ~BRUSH_SPACE_ATTEN; + br->curve_preset = BRUSH_CURVE_SPHERE; + } + } + + LISTBASE_FOREACH (Mesh *, me, &bmain->meshes) { + for (int step = 0; step < 2; step++) { + CustomDataLayer *actlayer = NULL; + + int vact1, vact2; + + if (step) { + vact1 = CustomData_get_render_layer_index(&me->vdata, CD_PROP_COLOR); + vact2 = CustomData_get_render_layer_index(&me->ldata, CD_PROP_BYTE_COLOR); + } + else { + vact1 = CustomData_get_active_layer_index(&me->vdata, CD_PROP_COLOR); + vact2 = CustomData_get_active_layer_index(&me->ldata, CD_PROP_BYTE_COLOR); + } + + if (vact1 != -1) { + actlayer = me->vdata.layers + vact1; + } + else if (vact2 != -1) { + actlayer = me->ldata.layers + vact2; + } + + if (actlayer) { + if (step) { + BKE_id_attributes_render_color_set(&me->id, actlayer); + } + else { + BKE_id_attributes_active_color_set(&me->id, actlayer); + } + } + } + } + } + if (!MAIN_VERSION_ATLEAST(bmain, 302, 7)) { /* Generate 'system' liboverrides IDs. * NOTE: This is a fairly rough process, based on very basic heuristics. Should be enough for a diff --git a/source/blender/draw/engines/workbench/workbench_engine.c b/source/blender/draw/engines/workbench/workbench_engine.c index 60919d0d028..23543e6bfec 100644 --- a/source/blender/draw/engines/workbench/workbench_engine.c +++ b/source/blender/draw/engines/workbench/workbench_engine.c @@ -271,6 +271,14 @@ static eV3DShadingColorType workbench_color_type_get(WORKBENCH_PrivateData *wpd, BKE_pbvh_is_drawing_set(ob->sculpt->pbvh, is_sculpt_pbvh); } + const CustomData *cd_vdata = workbench_mesh_get_vert_custom_data(me); + const CustomData *cd_ldata = workbench_mesh_get_loop_custom_data(me); + + bool has_color = (CustomData_has_layer(cd_vdata, CD_PROP_COLOR) || + CustomData_has_layer(cd_vdata, CD_PROP_BYTE_COLOR) || + CustomData_has_layer(cd_ldata, CD_PROP_COLOR) || + CustomData_has_layer(cd_ldata, CD_PROP_BYTE_COLOR)); + if (color_type == V3D_SHADING_TEXTURE_COLOR) { if (ob->dt < OB_TEXTURE) { color_type = V3D_SHADING_MATERIAL_COLOR; @@ -285,14 +293,6 @@ static eV3DShadingColorType workbench_color_type_get(WORKBENCH_PrivateData *wpd, color_type = V3D_SHADING_OBJECT_COLOR; } else { - const CustomData *cd_vdata = workbench_mesh_get_vert_custom_data(me); - const CustomData *cd_ldata = workbench_mesh_get_loop_custom_data(me); - - bool has_color = (CustomData_has_layer(cd_vdata, CD_PROP_COLOR) || - CustomData_has_layer(cd_vdata, CD_PROP_BYTE_COLOR) || - CustomData_has_layer(cd_ldata, CD_PROP_COLOR) || - CustomData_has_layer(cd_ldata, CD_PROP_BYTE_COLOR)); - if (!has_color) { color_type = V3D_SHADING_OBJECT_COLOR; } @@ -314,7 +314,7 @@ static eV3DShadingColorType workbench_color_type_get(WORKBENCH_PrivateData *wpd, *r_texpaint_mode = true; } } - else if (is_vertpaint_mode && me && CustomData_has_layer(ldata, CD_PROP_BYTE_COLOR)) { + else if (is_vertpaint_mode && me && has_color) { color_type = V3D_SHADING_VERTEX_COLOR; } } diff --git a/source/blender/editors/mesh/mesh_data.c b/source/blender/editors/mesh/mesh_data.c index 39dccb66899..9b069aae7e3 100644 --- a/source/blender/editors/mesh/mesh_data.c +++ b/source/blender/editors/mesh/mesh_data.c @@ -428,19 +428,19 @@ int ED_mesh_color_add( bool ED_mesh_color_ensure(struct Mesh *me, const char *name) { BLI_assert(me->edit_mesh == NULL); + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); - if (!me->mloopcol && me->totloop) { - CustomData_add_layer_named( - &me->ldata, CD_PROP_BYTE_COLOR, CD_DEFAULT, NULL, me->totloop, name); - int layer_i = CustomData_get_layer_index(&me->ldata, CD_PROP_BYTE_COLOR); + if (!layer) { + CustomData_add_layer_named(&me->ldata, CD_PROP_BYTE_COLOR, CD_DEFAULT, NULL, me->totloop, name); + layer = me->ldata.layers + CustomData_get_layer_index(&me->ldata, CD_PROP_BYTE_COLOR); - BKE_id_attributes_active_color_set(&me->id, me->ldata.layers + layer_i); + BKE_id_attributes_active_color_set(&me->id, layer); BKE_mesh_update_customdata_pointers(me, true); } DEG_id_tag_update(&me->id, 0); - return (me->mloopcol != NULL); + return (layer != NULL); } bool ED_mesh_color_remove_index(Mesh *me, const int n) diff --git a/source/blender/editors/sculpt_paint/CMakeLists.txt b/source/blender/editors/sculpt_paint/CMakeLists.txt index abdae5c44f9..08eed52f440 100644 --- a/source/blender/editors/sculpt_paint/CMakeLists.txt +++ b/source/blender/editors/sculpt_paint/CMakeLists.txt @@ -48,7 +48,7 @@ set(SRC paint_ops.c paint_stroke.c paint_utils.c - paint_vertex.c + paint_vertex.cc paint_vertex_color_ops.c paint_vertex_color_utils.c paint_vertex_proj.c diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index f784ebe4b4f..9cdd6a5edcc 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -133,7 +133,9 @@ void PAINT_OT_weight_gradient(struct wmOperatorType *ot); void PAINT_OT_vertex_paint_toggle(struct wmOperatorType *ot); void PAINT_OT_vertex_paint(struct wmOperatorType *ot); -unsigned int vpaint_get_current_col(struct Scene *scene, struct VPaint *vp, bool secondary); +unsigned int vpaint_get_current_color(struct Scene *scene, + struct VPaint *vp, + bool secondary); /* paint_vertex_color_utils.c */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.cc index 10d24af53ee..6bdd4cbc268 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.cc @@ -13,11 +13,14 @@ #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 "DNA_brush_types.h" #include "DNA_mesh_types.h" @@ -26,6 +29,7 @@ #include "DNA_scene_types.h" #include "RNA_access.h" +#include "RNA_prototypes.h" #include "BKE_attribute.h" #include "BKE_brush.h" @@ -66,14 +70,59 @@ #include "paint_intern.h" /* own include */ #include "sculpt_intern.h" +using blender::IndexRange; +using namespace blender; +using namespace blender::color; + /* -------------------------------------------------------------------- */ /** \name Internal Utilities * \{ */ +static ColorPaint4b uint2color(uint c) +{ + uchar *rgba = (uchar *)&c; + return ColorPaint4b(rgba[0], rgba[1], rgba[2], rgba[3]); +} + +static uint color2uint(ColorPaint4b c) +{ + return *(reinterpret_cast<uint *>(&c)); +} + +static bool isZero(ColorPaint4f c) +{ + return c.r == 0.0f && c.g == 0.0f && c.b == 0.0f && c.a == 0.0f; +} + +static bool isZero(ColorPaint4b c) +{ + return c.r == 0 && c.g == 0 && c.b == 0 && c.a == 0; +} + +template<typename Color = ColorPaint4f> static ColorPaint4f toFloat(const Color &c) +{ + if constexpr (std::is_same_v<Color, ColorPaint4b>) { + return c.decode(); + } + else { + return c; + } +} + +template<typename Color = ColorPaint4f> static Color fromFloat(const ColorPaint4f &c) +{ + if constexpr (std::is_same_v<Color, ColorPaint4b>) { + return c.encode(); + } + else { + return c; + } +} + /* Use for 'blur' brush, align with PBVH nodes, created and freed on each update. */ -struct VPaintAverageAccum { - uint len; - uint value[3]; +template<typename BlendType> struct VPaintAverageAccum { + BlendType len; + BlendType value[3]; }; struct WPaintAverageAccum { @@ -93,6 +142,26 @@ struct NormalAnglePrecalc { float angle_range; }; +/* Returns number of elements. */ +static int get_vcol_elements(Mesh *me, size_t *r_elem_size) +{ + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + AttributeDomain domain = BKE_id_attribute_domain(&me->id, layer); + + if (r_elem_size) { + *r_elem_size = layer->type == CD_PROP_COLOR ? sizeof(float) * 4ULL : 4ULL; + } + + switch (domain) { + case ATTR_DOMAIN_POINT: + return me->totvert; + case ATTR_DOMAIN_CORNER: + return me->totloop; + default: + return 0; + } +} + static void view_angle_limits_init(struct NormalAnglePrecalc *a, float angle, bool do_mask_normal) { angle = RAD2DEGF(angle); @@ -196,9 +265,8 @@ bool vertex_paint_mode_poll(bContext *C) } CustomDataLayer *layer = BKE_id_attributes_active_color_get((ID *)ob->data); - AttributeDomain domain = BKE_id_attribute_domain((ID *)ob->data, layer); - return layer && layer->type == CD_PROP_BYTE_COLOR && domain == ATTR_DOMAIN_CORNER; + return layer != nullptr; } static bool vertex_paint_poll_ex(bContext *C, bool check_tool) @@ -262,41 +330,54 @@ bool weight_paint_poll_ignore_tool(bContext *C) return weight_paint_poll_ex(C, false); } -uint vpaint_get_current_col(Scene *scene, VPaint *vp, bool secondary) +template<typename Color, typename Traits, AttributeDomain domain> +static Color vpaint_get_current_col(Scene *scene, VPaint *vp, bool secondary) { Brush *brush = BKE_paint_brush(&vp->paint); - uchar col[4]; - rgb_float_to_uchar(col, - secondary ? BKE_brush_secondary_color_get(scene, brush) : - BKE_brush_color_get(scene, brush)); - col[3] = 255; /* alpha isn't used, could even be removed to speedup paint a little */ - return *(uint *)col; + float color[4]; + const float *brush_color = secondary ? BKE_brush_secondary_color_get(scene, brush) : + BKE_brush_color_get(scene, brush); + copy_v3_v3(color, brush_color); + color[3] = 1.0f; /* alpha isn't used, could even be removed to speedup paint a little */ + + return fromFloat<Color>(ColorPaint4f(color)); +} + +uint vpaint_get_current_color(Scene *scene, VPaint *vp, bool secondary) +{ + ColorPaint4b col = vpaint_get_current_col<ColorPaint4b, ByteTraits, ATTR_DOMAIN_CORNER>( + scene, vp, secondary); + + return color2uint(col); } /* wpaint has 'wpaint_blend' */ -static uint vpaint_blend(const VPaint *vp, - uint color_curr, - uint color_orig, - uint color_paint, - const int alpha_i, - /* pre scaled from [0-1] --> [0-255] */ - const int brush_alpha_value_i) +template<typename Color, typename Traits> +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 = brush->blend; + const IMB_BlendMode blend = (IMB_BlendMode)brush->blend; - uint color_blend = ED_vpaint_blend_tool(blend, color_curr, color_paint, alpha_i); + Color color_blend = BLI_mix_colors<Color, Traits>(blend, color_curr, color_paint, alpha); /* If no accumulate, clip color adding with `color_orig` & `color_test`. */ if (!brush_use_accumulate(vp)) { - uint color_test, a; - char *cp, *ct, *co; + uint a; + Color color_test; + Value *cp, *ct, *co; - color_test = ED_vpaint_blend_tool(blend, color_orig, color_paint, brush_alpha_value_i); + color_test = BLI_mix_colors<Color, Traits>(blend, color_orig, color_paint, brush_alpha_value); - cp = (char *)&color_blend; - ct = (char *)&color_test; - co = (char *)&color_orig; + cp = (Value *)&color_blend; + ct = (Value *)&color_test; + co = (Value *)&color_orig; for (a = 0; a < 4; a++) { if (ct[a] < co[a]) { @@ -320,36 +401,15 @@ static uint vpaint_blend(const VPaint *vp, if ((brush->flag & BRUSH_LOCK_ALPHA) && !ELEM(blend, IMB_BLEND_ERASE_ALPHA, IMB_BLEND_ADD_ALPHA)) { - char *cp, *cc; - cp = (char *)&color_blend; - cc = (char *)&color_curr; + Value *cp, *cc; + cp = (Value *)&color_blend; + cc = (Value *)&color_curr; cp[3] = cc[3]; } return color_blend; } -static void tex_color_alpha(VPaint *vp, const ViewContext *vc, const float co[3], float r_rgba[4]) -{ - const Brush *brush = BKE_paint_brush(&vp->paint); - BLI_assert(brush->mtex.tex != NULL); - if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) { - BKE_brush_sample_tex_3d(vc->scene, brush, co, r_rgba, 0, NULL); - } - else { - float co_ss[2]; /* screenspace */ - if (ED_view3d_project_float_object( - vc->region, co, co_ss, V3D_PROJ_TEST_CLIP_BB | V3D_PROJ_TEST_CLIP_NEAR) == - V3D_PROJ_RET_OK) { - const float co_ss_3d[3] = {co_ss[0], co_ss[1], 0.0f}; /* we need a 3rd empty value */ - BKE_brush_sample_tex_3d(vc->scene, brush, co_ss_3d, r_rgba, 0, NULL); - } - else { - zero_v4(r_rgba); - } - } -} - /* vpaint has 'vpaint_blend' */ static float wpaint_blend(const VPaint *wp, float weight, @@ -359,7 +419,7 @@ static float wpaint_blend(const VPaint *wp, const short do_flip) { const Brush *brush = wp->paint.brush; - IMB_BlendMode blend = brush->blend; + IMB_BlendMode blend = (IMB_BlendMode)brush->blend; if (do_flip) { switch (blend) { @@ -390,6 +450,32 @@ static float wpaint_blend(const VPaint *wp, 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); + BLI_assert(brush->mtex.tex != NULL); + if (brush->mtex.brush_map_mode == MTEX_MAP_MODE_3D) { + BKE_brush_sample_tex_3d(vc->scene, brush, co, r_rgba, 0, NULL); + } + else { + float co_ss[2]; /* screenspace */ + if (ED_view3d_project_float_object( + vc->region, + co, + co_ss, + (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, co_ss_3d, r_rgba, 0, NULL); + } + else { + zero_v4(r_rgba); + } + } +} + static float wpaint_clamp_monotonic(float oldval, float curval, float newval) { if (newval < oldval) { @@ -746,7 +832,7 @@ static void do_weight_paint_vertex_single( float alpha, float paintweight) { - Mesh *me = ob->data; + Mesh *me = (Mesh *)ob->data; MDeformVert *dv = &me->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; @@ -959,7 +1045,7 @@ static void do_weight_paint_vertex_multi( float alpha, float paintweight) { - Mesh *me = ob->data; + Mesh *me = (Mesh *)ob->data; MDeformVert *dv = &me->dvert[index]; bool topology = (me->editflag & ME_EDIT_MIRROR_TOPO) != 0; @@ -1106,14 +1192,39 @@ static void vertex_paint_init_session(Depsgraph *depsgraph, BKE_sculpt_toolsettings_data_ensure(scene); BLI_assert(ob->sculpt == NULL); - ob->sculpt = MEM_callocN(sizeof(SculptSession), "sculpt session"); + ob->sculpt = (SculptSession *)MEM_callocN(sizeof(SculptSession), "sculpt session"); ob->sculpt->mode_type = object_mode; - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); } -static void vertex_paint_init_stroke(Depsgraph *depsgraph, Object *ob) +static void vertex_paint_init_stroke(Scene *scene, Depsgraph *depsgraph, Object *ob) { - BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false); + BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false); + ToolSettings *ts = scene->toolsettings; + 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 = (StrokeCache *)MEM_callocN(sizeof(StrokeCache), "stroke cache"); + } + + /* Allocate scratch array for previous colors if needed. */ + if (!brush_use_accumulate(ts->vpaint)) { + if (!ob->sculpt->cache->prev_colors_vpaint) { + Mesh *me = BKE_object_get_original_mesh(ob); + size_t elem_size; + int elem_num; + + elem_num = get_vcol_elements(me, &elem_size); + + ob->sculpt->cache->prev_colors_vpaint = (uint *)MEM_callocN(elem_num * elem_size, __func__); + } + } + else { + MEM_SAFE_FREE(ob->sculpt->cache->prev_colors_vpaint); + } } static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) @@ -1129,12 +1240,12 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) BLI_assert(ob->sculpt->mode_type == OB_MODE_WEIGHT_PAINT); } else { - ob->sculpt->mode_type = 0; + ob->sculpt->mode_type = (eObjectMode)0; BLI_assert(0); return; } - Mesh *me = ob->data; + Mesh *me = (Mesh *)ob->data; if (gmap->vert_to_loop == NULL) { gmap->vert_map_mem = NULL; @@ -1158,24 +1269,15 @@ static void vertex_paint_init_session_data(const ToolSettings *ts, Object *ob) } /* Create average brush arrays */ - if (ob->mode == OB_MODE_VERTEX_PAINT) { - if (!brush_use_accumulate(ts->vpaint)) { - if (ob->sculpt->mode.vpaint.previous_color == NULL) { - ob->sculpt->mode.vpaint.previous_color = MEM_callocN(me->totloop * sizeof(uint), __func__); - } - } - else { - MEM_SAFE_FREE(ob->sculpt->mode.vpaint.previous_color); - } - } - else if (ob->mode == OB_MODE_WEIGHT_PAINT) { + if (ob->mode == OB_MODE_WEIGHT_PAINT) { if (!brush_use_accumulate(ts->wpaint)) { if (ob->sculpt->mode.wpaint.alpha_weight == NULL) { - ob->sculpt->mode.wpaint.alpha_weight = MEM_callocN(me->totvert * sizeof(float), __func__); + ob->sculpt->mode.wpaint.alpha_weight = (float *)MEM_callocN(me->totvert * sizeof(float), + __func__); } if (ob->sculpt->mode.wpaint.dvert_prev == NULL) { - ob->sculpt->mode.wpaint.dvert_prev = MEM_callocN(me->totvert * sizeof(MDeformVert), - __func__); + ob->sculpt->mode.wpaint.dvert_prev = (MDeformVert *)MEM_callocN( + me->totvert * sizeof(MDeformVert), __func__); MDeformVert *dv = ob->sculpt->mode.wpaint.dvert_prev; for (int i = 0; i < me->totvert; i++, dv++) { /* Use to show this isn't initialized, never apply to the mesh data. */ @@ -1368,7 +1470,7 @@ static int wpaint_mode_toggle_exec(bContext *C, wmOperator *op) ToolSettings *ts = scene->toolsettings; if (!is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + if (!ED_object_mode_compat_set(C, ob, (eObjectMode)mode_flag, op->reports)) { return OPERATOR_CANCELLED; } } @@ -1412,7 +1514,7 @@ static bool paint_mode_toggle_poll_test(bContext *C) if (ob == NULL || ob->type != OB_MESH) { return false; } - if (!ob->data || ID_IS_LINKED(ob->data) || ID_IS_OVERRIDE_LIBRARY(ob->data)) { + if (!ob->data || ID_IS_LINKED(ob->data)) { return false; } return true; @@ -1507,7 +1609,7 @@ static void vwpaint_update_cache_invariants( StrokeCache *cache; Scene *scene = CTX_data_scene(C); UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings; - ViewContext *vc = paint_stroke_view_context(op->customdata); + ViewContext *vc = paint_stroke_view_context((PaintStroke *)op->customdata); Object *ob = CTX_data_active_object(C); float mat[3][3]; float view_dir[3] = {0.0f, 0.0f, 1.0f}; @@ -1515,7 +1617,7 @@ static void vwpaint_update_cache_invariants( /* VW paint needs to allocate stroke cache before update is called. */ if (!ss->cache) { - cache = MEM_callocN(sizeof(StrokeCache), "stroke cache"); + cache = (StrokeCache *)MEM_callocN(sizeof(StrokeCache), "stroke cache"); ss->cache = cache; } else { @@ -1618,7 +1720,7 @@ 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); - struct PaintStroke *stroke = op->customdata; + struct PaintStroke *stroke = (PaintStroke *)op->customdata; ToolSettings *ts = scene->toolsettings; Object *ob = CTX_data_active_object(C); Mesh *me = BKE_mesh_from_object(ob); @@ -1638,13 +1740,13 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo /* check if we are attempting to paint onto a locked vertex group, * and other options disallow it from doing anything useful */ bDeformGroup *dg; - dg = BLI_findlink(&me->vertex_group_names, vgroup_index.active); + 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 = BLI_findlink(&me->vertex_group_names, vgroup_index.mirror); + 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; @@ -1667,7 +1769,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo for (i = 0; i < defbase_tot; i++) { if (defbase_sel[i]) { - dg = BLI_findlink(&me->vertex_group_names, 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); @@ -1679,7 +1781,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo /* ALLOCATIONS! no return after this line */ /* make mode data storage */ - wpd = MEM_callocN(sizeof(struct WPaintData), "WPaintData"); + wpd = (WPaintData *)MEM_callocN(sizeof(struct WPaintData), "WPaintData"); paint_stroke_set_mode_data(stroke, wpd); ED_view3d_viewcontext_init(C, &wpd->vc, depsgraph); view_angle_limits_init(&wpd->normal_angle_precalc, @@ -1712,10 +1814,10 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo } if (wpd->do_lock_relative || (ts->auto_normalize && wpd->lock_flags && !wpd->do_multipaint)) { - bool *unlocked = MEM_dupallocN(wpd->vgroup_validmap); + bool *unlocked = (bool *)MEM_dupallocN(wpd->vgroup_validmap); if (wpd->lock_flags) { - bool *locked = MEM_mallocN(sizeof(bool) * wpd->defbase_tot, __func__); + 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; @@ -1726,7 +1828,7 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo if (wpd->do_multipaint && ts->auto_normalize) { bool *tmpflags; - tmpflags = MEM_mallocN(sizeof(bool) * defbase_tot, __func__); + 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); } @@ -1738,24 +1840,24 @@ static bool wpaint_stroke_test_start(bContext *C, wmOperator *op, const float mo else if (ts->auto_normalize) { bool *tmpflags; - tmpflags = wpd->lock_flags ? MEM_dupallocN(wpd->lock_flags) : - MEM_callocN(sizeof(bool) * defbase_tot, __func__); + tmpflags = wpd->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 ? MEM_dupallocN(wpd->lock_flags) : - MEM_callocN(sizeof(bool) * defbase_tot, __func__); + 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 */ - vertex_paint_init_stroke(depsgraph, ob); + vertex_paint_init_stroke(scene, 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 = MEM_mallocN(sizeof(float) * me->totvert, __func__); + wpd->precomputed_weight = (float *)MEM_mallocN(sizeof(float) * me->totvert, __func__); } if (ob->sculpt->mode.wpaint.dvert_prev != NULL) { @@ -1807,7 +1909,7 @@ static void do_wpaint_precompute_weight_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { - SculptThreadedTaskData *data = userdata; + SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; const MDeformVert *dv = &data->me->dvert[n]; data->wpd->precomputed_weight[n] = wpaint_get_active_weight(dv, data->wpi); @@ -1821,13 +1923,12 @@ static void precompute_weight_values( } /* threaded loop over vertices */ - SculptThreadedTaskData data = { - .C = C, - .ob = ob, - .wpd = wpd, - .wpi = wpi, - .me = me, - }; + 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); @@ -1840,7 +1941,7 @@ static void do_wpaint_brush_blur_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { - SculptThreadedTaskData *data = userdata; + 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); @@ -1931,7 +2032,7 @@ static void do_wpaint_brush_smear_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { - SculptThreadedTaskData *data = userdata; + 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); @@ -2041,7 +2142,7 @@ static void do_wpaint_brush_draw_task_cb_ex(void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { - SculptThreadedTaskData *data = userdata; + 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); @@ -2112,7 +2213,7 @@ static void do_wpaint_brush_draw_task_cb_ex(void *__restrict userdata, static void do_wpaint_brush_calc_average_weight_cb_ex( void *__restrict userdata, const int n, const TaskParallelTLS *__restrict UNUSED(tls)) { - SculptThreadedTaskData *data = userdata; + SculptThreadedTaskData *data = (SculptThreadedTaskData *)userdata; SculptSession *ss = data->ob->sculpt; StrokeCache *cache = ss->cache; const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); @@ -2143,7 +2244,6 @@ static void do_wpaint_brush_calc_average_weight_cb_ex( BKE_brush_curve_strength(data->brush, sqrtf(test.dist), cache->radius) > 0.0) { const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : vd.vert_indices[vd.i]; - // const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; const char v_flag = data->me->mvert[v_index].flag; /* If the vertex is selected. */ @@ -2162,7 +2262,8 @@ static void calculate_average_weight(SculptThreadedTaskData *data, PBVHNode **UNUSED(nodes), int totnode) { - struct WPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__); + struct WPaintAverageAccum *accum = (WPaintAverageAccum *)MEM_mallocN(sizeof(*accum) * totnode, + __func__); data->custom_data = accum; TaskParallelSettings settings; @@ -2197,17 +2298,16 @@ static void wpaint_paint_leaves(bContext *C, const Brush *brush = ob->sculpt->cache->brush; /* threaded loop over nodes */ - SculptThreadedTaskData data = { - .C = C, - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .vp = vp, - .wpd = wpd, - .wpi = wpi, - .me = me, - }; + SculptThreadedTaskData data = {0}; + 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); @@ -2243,12 +2343,12 @@ static PBVHNode **vwpaint_pbvh_gather_generic( /* Build a list of all nodes that are potentially within the brush's area of influence */ if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) { - SculptSearchSphereData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cache->radius_squared, - .original = true, - }; + SculptSearchSphereData data = {0}; + data.ss = ss; + data.sd = sd; + data.radius_squared = ss->cache->radius_squared; + data.original = true; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_sphere_cb, &data, &nodes, r_totnode); if (use_normal) { SCULPT_pbvh_calc_area_normal( @@ -2262,13 +2362,13 @@ static PBVHNode **vwpaint_pbvh_gather_generic( struct DistRayAABB_Precalc dist_ray_to_aabb_precalc; dist_squared_ray_to_aabb_v3_precalc( &dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal); - SculptSearchCircleData data = { - .ss = ss, - .sd = sd, - .radius_squared = ss->cache->radius_squared, - .original = true, - .dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc, - }; + SculptSearchCircleData data = {0}; + 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; + BKE_pbvh_search_gather(ss->pbvh, SCULPT_search_circle_cb, &data, &nodes, r_totnode); if (use_normal) { copy_v3_v3(ss->cache->sculpt_normal_symm, ss->cache->view_normal); @@ -2330,7 +2430,7 @@ static void wpaint_do_symmetrical_brush_actions( bContext *C, Object *ob, VPaint *wp, Sculpt *sd, struct WPaintData *wpd, WeightPaintInfo *wpi) { Brush *brush = BKE_paint_brush(&wp->paint); - Mesh *me = ob->data; + Mesh *me = (Mesh *)ob->data; SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; const char symm = SCULPT_mesh_symmetry_xyz_get(ob); @@ -2387,7 +2487,7 @@ static void wpaint_stroke_update_step(bContext *C, ToolSettings *ts = CTX_data_tool_settings(C); VPaint *wp = ts->wpaint; Brush *brush = BKE_paint_brush(&wp->paint); - struct WPaintData *wpd = paint_stroke_mode_data(stroke); + struct WPaintData *wpd = (WPaintData *)paint_stroke_mode_data(stroke); ViewContext *vc; Object *ob = CTX_data_active_object(C); @@ -2442,7 +2542,7 @@ static void wpaint_stroke_update_step(bContext *C, /* *** done setting up WeightPaintInfo *** */ if (wpd->precomputed_weight) { - precompute_weight_values(C, ob, brush, wpd, &wpi, ob->data); + precompute_weight_values(C, ob, brush, wpd, &wpi, (Mesh *)ob->data); } wpaint_do_symmetrical_brush_actions(C, ob, wp, sd, wpd, &wpi); @@ -2455,9 +2555,9 @@ static void wpaint_stroke_update_step(bContext *C, mul_v3_m4v3(loc_world, ob->obmat, ss->cache->true_location); paint_last_stroke_update(scene, loc_world); - BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + BKE_mesh_batch_cache_dirty_tag((Mesh *)ob->data, BKE_MESH_BATCH_DIRTY_ALL); - DEG_id_tag_update(ob->data, 0); + DEG_id_tag_update((ID *)ob->data, 0); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); swap_m4m4(wpd->vc.rv3d->persmat, mat); @@ -2486,7 +2586,7 @@ static void wpaint_stroke_update_step(bContext *C, static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) { Object *ob = CTX_data_active_object(C); - struct WPaintData *wpd = paint_stroke_mode_data(stroke); + struct WPaintData *wpd = (WPaintData *)paint_stroke_mode_data(stroke); if (wpd) { if (wpd->defbase_sel) { @@ -2530,7 +2630,7 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) ParticleSystem *psys; int i; - for (psys = ob->particlesystem.first; psys; psys = psys->next) { + 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; @@ -2540,7 +2640,7 @@ static void wpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) } } - DEG_id_tag_update(ob->data, 0); + DEG_id_tag_update((ID *)ob->data, 0); WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); @@ -2562,7 +2662,7 @@ static int wpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) event->type); if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op, op->customdata); + paint_stroke_free(C, op, (PaintStroke *)op->customdata); return OPERATOR_FINISHED; } /* add modal handler */ @@ -2586,7 +2686,7 @@ static int wpaint_exec(bContext *C, wmOperator *op) 0); /* frees op->customdata */ - paint_stroke_exec(C, op, op->customdata); + paint_stroke_exec(C, op, (PaintStroke *)op->customdata); return OPERATOR_FINISHED; } @@ -2599,7 +2699,7 @@ static void wpaint_cancel(bContext *C, wmOperator *op) ob->sculpt->cache = NULL; } - paint_stroke_cancel(C, op, op->customdata); + paint_stroke_cancel(C, op, (PaintStroke *)op->customdata); } static int wpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) @@ -2647,7 +2747,7 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) ToolSettings *ts = scene->toolsettings; if (!is_mode_set) { - if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) { + if (!ED_object_mode_compat_set(C, ob, (eObjectMode)mode_flag, op->reports)) { return OPERATOR_CANCELLED; } } @@ -2667,13 +2767,12 @@ static int vpaint_mode_toggle_exec(bContext *C, wmOperator *op) BKE_paint_toolslots_brush_validate(bmain, &ts->vpaint->paint); } - BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + BKE_mesh_batch_cache_dirty_tag((Mesh *)ob->data, BKE_MESH_BATCH_DIRTY_ALL); /* update modifier stack for mapping requirements */ DEG_id_tag_update(&me->id, 0); WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene); - WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode); WM_toolsystem_update_from_context_view3d(C); @@ -2720,11 +2819,17 @@ void PAINT_OT_vertex_paint_toggle(wmOperatorType *ot) * - revise whether op->customdata should be added in object, in set_vpaint. */ -struct VPaintData { +struct VPaintDataBase { ViewContext vc; + AttributeDomain domain; + CustomDataType type; +}; + +template<typename Color, typename Traits, AttributeDomain domain> +struct VPaintData : public VPaintDataBase { struct NormalAnglePrecalc normal_angle_precalc; - uint paintcol; + Color paintcol; struct VertProjHandle *vp_handle; struct CoNo *vertexcosnos; @@ -2743,44 +2848,43 @@ struct VPaintData { /* Special storage for smear brush, avoid feedback loop - update each step. */ struct { - uint *color_prev; - uint *color_curr; + void *color_prev; + void *color_curr; } smear; }; -static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +template<typename Color, typename Traits, AttributeDomain domain> +static void *vpaint_init_vpaint(bContext *C, + struct wmOperator *op, + Scene *scene, + Depsgraph *depsgraph, + VPaint *vp, + Object *ob, + Mesh *me, + const Brush *brush) { - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = scene->toolsettings; - struct PaintStroke *stroke = op->customdata; - VPaint *vp = ts->vpaint; - Brush *brush = BKE_paint_brush(&vp->paint); - struct VPaintData *vpd; - Object *ob = CTX_data_active_object(C); - Mesh *me; - SculptSession *ss = ob->sculpt; - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + VPaintData<Color, Traits, domain> *vpd; - /* context checks could be a poll() */ - me = BKE_mesh_from_object(ob); - if (me == NULL || me->totpoly == 0) { - return false; - } + size_t elem_size; + int elem_num = get_vcol_elements(me, &elem_size); + /* make mode data storage */ + vpd = MEM_new<VPaintData<Color, Traits, domain>>("VPaintData"); - ED_mesh_color_ensure(me, NULL); - if (me->mloopcol == NULL) { - return false; + if constexpr (std::is_same_v<Color, ColorPaint4f>) { + vpd->type = CD_PROP_COLOR; + } + else { + vpd->type = CD_PROP_BYTE_COLOR; } - /* make mode data storage */ - vpd = MEM_callocN(sizeof(*vpd), "VPaintData"); - paint_stroke_set_mode_data(stroke, vpd); + 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); - vpd->paintcol = vpaint_get_current_col( + vpd->paintcol = vpaint_get_current_col<Color, Traits, domain>( scene, vp, (RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_INVERT)); vpd->is_texbrush = !(brush->vertexpaint_tool == VPAINT_TOOL_BLUR) && brush->mtex.tex; @@ -2789,22 +2893,23 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f * if not we can skip face map trickiness */ if (vertex_paint_use_fast_update_check(ob)) { vpd->use_fast_update = true; - // printf("Fast update!\n"); } else { vpd->use_fast_update = false; - // printf("No fast update!\n"); } /* to keep tracked of modified loops for shared vertex color blending */ if (brush->vertexpaint_tool == VPAINT_TOOL_BLUR) { - vpd->mlooptag = MEM_mallocN(sizeof(bool) * me->totloop, "VPaintData mlooptag"); + vpd->mlooptag = (bool *)MEM_mallocN(sizeof(bool) * elem_num, "VPaintData mlooptag"); } if (brush->vertexpaint_tool == VPAINT_TOOL_SMEAR) { - vpd->smear.color_prev = MEM_mallocN(sizeof(uint) * me->totloop, __func__); - memcpy(vpd->smear.color_prev, me->mloopcol, sizeof(uint) * me->totloop); - vpd->smear.color_curr = MEM_dupallocN(vpd->smear.color_prev); + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + + vpd->smear.color_prev = MEM_malloc_arrayN(elem_num, elem_size, __func__); + memcpy(vpd->smear.color_prev, layer->data, elem_size * elem_num); + + vpd->smear.color_curr = (uint *)MEM_dupallocN(vpd->smear.color_prev); } /* Create projection handle */ @@ -2814,516 +2919,853 @@ static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const f ob->sculpt->building_vp_handle = false; } + return static_cast<void *>(vpd); +} + +static bool vpaint_stroke_test_start(bContext *C, struct wmOperator *op, const float mouse[2]) +{ + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + struct PaintStroke *stroke = (PaintStroke *)op->customdata; + VPaint *vp = ts->vpaint; + Brush *brush = BKE_paint_brush(&vp->paint); + Object *ob = CTX_data_active_object(C); + Mesh *me; + SculptSession *ss = ob->sculpt; + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + /* context checks could be a poll() */ + me = BKE_mesh_from_object(ob); + if (me == NULL || me->totpoly == 0) { + return false; + } + + ED_mesh_color_ensure(me, NULL); + + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + if (!layer) { + return false; + } + + AttributeDomain domain = BKE_id_attribute_domain(&me->id, layer); + void *vpd = nullptr; + + if (domain == ATTR_DOMAIN_POINT) { + if (layer->type == CD_PROP_COLOR) { + vpd = vpaint_init_vpaint<ColorPaint4f, FloatTraits, ATTR_DOMAIN_POINT>( + C, op, scene, depsgraph, vp, ob, me, brush); + } + else if (layer->type == CD_PROP_BYTE_COLOR) { + vpd = vpaint_init_vpaint<ColorPaint4b, ByteTraits, ATTR_DOMAIN_POINT>( + C, op, scene, depsgraph, vp, ob, me, brush); + } + } + else if (domain == ATTR_DOMAIN_CORNER) { + if (layer->type == CD_PROP_COLOR) { + vpd = vpaint_init_vpaint<ColorPaint4f, FloatTraits, ATTR_DOMAIN_CORNER>( + C, op, scene, depsgraph, vp, ob, me, brush); + } + else if (layer->type == CD_PROP_BYTE_COLOR) { + vpd = vpaint_init_vpaint<ColorPaint4b, ByteTraits, ATTR_DOMAIN_CORNER>( + C, op, scene, depsgraph, vp, ob, me, brush); + } + } + + BLI_assert(vpd != nullptr); + + paint_stroke_set_mode_data(stroke, vpd); + /* If not previously created, create vertex/weight paint mode session data */ - vertex_paint_init_stroke(depsgraph, ob); + vertex_paint_init_stroke(scene, depsgraph, ob); vwpaint_update_cache_invariants(C, vp, ss, op, mouse); vertex_paint_init_session_data(ts, ob); - if (ob->sculpt->mode.vpaint.previous_color != NULL) { - memset(ob->sculpt->mode.vpaint.previous_color, 0, sizeof(uint) * me->totloop); - } - return true; } -static void do_vpaint_brush_calc_average_color_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) +template<class Color = ColorPaint4b, typename Traits = ByteTraits> +static void do_vpaint_brush_blur_loops(bContext *C, + Sculpt *sd, + VPaint *vp, + VPaintData<Color, Traits, ATTR_DOMAIN_CORNER> *vpd, + Object *ob, + Mesh *me, + PBVHNode **nodes, + int totnode, + Color *lcol) { - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); - const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; - StrokeCache *cache = ss->cache; - uint *lcol = data->lcol; - char *col; - const bool use_vert_sel = (data->me->editflag & - (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + SculptSession *ss = ob->sculpt; - struct VPaintAverageAccum *accum = (struct VPaintAverageAccum *)data->custom_data + n; - accum->len = 0; - memset(accum->value, 0, sizeof(accum->value)); + const Brush *brush = ob->sculpt->cache->brush; + const Scene *scene = CTX_data_scene(C); + + Color *previous_color = static_cast<Color *>(ss->cache->prev_colors_vpaint); + + blender::threading::parallel_for(IndexRange(totnode), 1LL, [&](IndexRange range) { + for (int n : range) { + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); + + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + const StrokeCache *cache = ss->cache; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data( + scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(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; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape( + ss, brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop corresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv = &me->mvert[v_index]; + + /* If the vertex is selected for painting. */ + if (!use_vert_sel || mv->flag & SELECT) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff( + &vpd->normal_angle_precalc, angle_cos, &brush_strength))) { + const float brush_fade = BKE_brush_curve_strength( + brush, sqrtf(test.dist), cache->radius); - SculptBrushTest test; - SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( - ss, &test, data->brush->falloff_shape); + /* Get the average poly color */ + Color color_final(0, 0, 0, 0); - /* For each vertex */ - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - /* Test to see if the vertex coordinates are within the spherical brush region. */ - if (sculpt_brush_test_sq_fn(&test, vd.co)) { - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; - if (BKE_brush_curve_strength(data->brush, 0.0, cache->radius) > 0.0) { - /* If the vertex is selected for painting. */ - const MVert *mv = &data->me->mvert[v_index]; - if (!use_vert_sel || mv->flag & SELECT) { - accum->len += gmap->vert_to_loop[v_index].count; - /* if a vertex is within the brush region, then add its color to the blend. */ - for (int j = 0; j < gmap->vert_to_loop[v_index].count; j++) { - const int l_index = gmap->vert_to_loop[v_index].indices[j]; - col = (char *)(&lcol[l_index]); - /* Color is squared to compensate the sqrt color encoding. */ - accum->value[0] += col[0] * col[0]; - accum->value[1] += col[1] * col[1]; - accum->value[2] += col[2] * col[2]; + int total_hit_loops = 0; + Blend blend[4] = {0}; + + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + int p_index = gmap->vert_to_poly[v_index].indices[j]; + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + total_hit_loops += mp->totloop; + for (int k = 0; k < mp->totloop; k++) { + const uint l_index = mp->loopstart + k; + Color *col = lcol + l_index; + + /* Color is squared to compensate the sqrt color encoding. */ + blend[0] += (Blend)col->r * (Blend)col->r; + blend[1] += (Blend)col->g * (Blend)col->g; + blend[2] += (Blend)col->b * (Blend)col->b; + blend[3] += (Blend)col->a * (Blend)col->a; + } + } + } + + if (total_hit_loops != 0) { + /* Use rgb^2 color averaging. */ + Color *col = &color_final; + + color_final.r = Traits::round( + sqrtf(Traits::divide_round(blend[0], total_hit_loops))); + color_final.g = Traits::round( + sqrtf(Traits::divide_round(blend[1], total_hit_loops))); + color_final.b = Traits::round( + sqrtf(Traits::divide_round(blend[2], total_hit_loops))); + color_final.a = Traits::round( + sqrtf(Traits::divide_round(blend[3], total_hit_loops))); + + /* For each poly owning this vert, + * paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(me->mloop[l_index].v == v_index); + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + Color color_orig(0, 0, 0, 0); /* unused when array is NULL */ + + if (previous_color != NULL) { + /* Get the previous loop color */ + if (isZero(previous_color[l_index])) { + previous_color[l_index] = lcol[l_index]; + } + color_orig = previous_color[l_index]; + } + const float final_alpha = Traits::range * brush_fade * brush_strength * + brush_alpha_pressure * grid_alpha; + /* Mix the new color with the original + * based on the brush strength and the curve. */ + lcol[l_index] = vpaint_blend<Color, Traits>(vp, + lcol[l_index], + color_orig, + *col, + final_alpha, + Traits::range * brush_strength); + } + } + } + } } } } - } - } - BKE_pbvh_vertex_iter_end; + BKE_pbvh_vertex_iter_end; + }; + }); } -static float tex_color_alpha_ubyte(SculptThreadedTaskData *data, - const float v_co[3], - uint *r_color) +template<class Color = ColorPaint4b, typename Traits = ByteTraits> +static void do_vpaint_brush_blur_verts(bContext *C, + Sculpt *sd, + VPaint *vp, + VPaintData<Color, Traits, ATTR_DOMAIN_POINT> *vpd, + Object *ob, + Mesh *me, + PBVHNode **nodes, + int totnode, + Color *lcol) { - float rgba[4]; - float rgba_br[3]; - tex_color_alpha(data->vp, &data->vpd->vc, v_co, rgba); - rgb_uchar_to_float(rgba_br, (const uchar *)&data->vpd->paintcol); - mul_v3_v3(rgba_br, rgba); - rgb_float_to_uchar((uchar *)r_color, rgba_br); - return rgba[3]; -} + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; -static void do_vpaint_brush_draw_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); - const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + SculptSession *ss = ob->sculpt; - const Brush *brush = data->brush; - const StrokeCache *cache = ss->cache; - uint *lcol = data->lcol; - const Scene *scene = CTX_data_scene(data->C); - float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( - scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(data->vp); - const bool use_vert_sel = (data->me->editflag & - (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; - const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const Brush *brush = ob->sculpt->cache->brush; + const Scene *scene = CTX_data_scene(C); + + Color *previous_color = static_cast<Color *>(ss->cache->prev_colors_vpaint); + + blender::threading::parallel_for(IndexRange(totnode), 1LL, [&](IndexRange range) { + for (int n : range) { + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); + + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + const StrokeCache *cache = ss->cache; + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + get_brush_alpha_data( + scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + const bool use_normal = vwpaint_use_normal(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; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape( + ss, brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop corresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv = &me->mvert[v_index]; + + /* If the vertex is selected for painting. */ + if (!use_vert_sel || mv->flag & SELECT) { + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff( + &vpd->normal_angle_precalc, angle_cos, &brush_strength))) { + const float brush_fade = BKE_brush_curve_strength( + brush, sqrtf(test.dist), cache->radius); - 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); + /* Get the average poly color */ + Color color_final(0, 0, 0, 0); - /* For each vertex */ - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - /* Test to see if the vertex coordinates are within the spherical brush region. */ - if (sculpt_brush_test_sq_fn(&test, vd.co)) { - /* NOTE: Grids are 1:1 with corners (aka loops). - * For grid based pbvh, take the vert whose loop corresponds to the current grid. - * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv = &data->me->mvert[v_index]; + int total_hit_loops = 0; + Blend blend[4] = {0}; - /* If the vertex is selected for painting. */ - if (!use_vert_sel || mv->flag & SELECT) { - /* Calc the dot prod. between ray norm on surf and current vert - * (ie splash prevention factor), and only paint front facing verts. */ - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && - ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || - view_angle_limits_apply_falloff( - &data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) { - const float brush_fade = BKE_brush_curve_strength( - brush, sqrtf(test.dist), cache->radius); - uint color_final = data->vpd->paintcol; - - /* If we're painting with a texture, sample the texture color and alpha. */ - float tex_alpha = 1.0; - if (data->vpd->is_texbrush) { - /* NOTE: we may want to paint alpha as vertex color alpha. */ - tex_alpha = tex_color_alpha_ubyte( - data, data->vpd->vertexcosnos[v_index].co, &color_final); - } - /* For each poly owning this vert, paint each loop belonging to this vert. */ - for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { - const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(data->me->mloop[l_index].v == v_index); - const MPoly *mp = &data->me->mpoly[p_index]; - if (!use_face_sel || mp->flag & ME_FACE_SEL) { - uint color_orig = 0; /* unused when array is NULL */ - if (ss->mode.vpaint.previous_color != NULL) { - /* Get the previous loop color */ - if (ss->mode.vpaint.previous_color[l_index] == 0) { - ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + int p_index = gmap->vert_to_poly[v_index].indices[j]; + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + total_hit_loops += mp->totloop; + for (int k = 0; k < mp->totloop; k++) { + const uint l_index = mp->loopstart + k; + const uint v_index = me->mloop[l_index].v; + + Color *col = lcol + v_index; + + /* Color is squared to compensate the sqrt color encoding. */ + blend[0] += (Blend)col->r * (Blend)col->r; + blend[1] += (Blend)col->g * (Blend)col->g; + blend[2] += (Blend)col->b * (Blend)col->b; + blend[3] += (Blend)col->a * (Blend)col->a; + } + } + } + + if (total_hit_loops != 0) { + /* Use rgb^2 color averaging. */ + Color *col = &color_final; + + color_final.r = Traits::round( + sqrtf(Traits::divide_round(blend[0], total_hit_loops))); + color_final.g = Traits::round( + sqrtf(Traits::divide_round(blend[1], total_hit_loops))); + color_final.b = Traits::round( + sqrtf(Traits::divide_round(blend[2], total_hit_loops))); + color_final.a = Traits::round( + sqrtf(Traits::divide_round(blend[3], total_hit_loops))); + + /* For each poly owning this vert, + * paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + + BLI_assert(me->mloop[l_index].v == v_index); + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + Color color_orig(0, 0, 0, 0); /* unused when array is NULL */ + + if (previous_color != NULL) { + /* Get the previous loop color */ + if (isZero(previous_color[v_index])) { + previous_color[v_index] = lcol[v_index]; + } + color_orig = previous_color[v_index]; + } + const float final_alpha = Traits::range * brush_fade * brush_strength * + brush_alpha_pressure * grid_alpha; + /* Mix the new color with the original + * based on the brush strength and the curve. */ + lcol[v_index] = vpaint_blend<Color, Traits>(vp, + lcol[v_index], + color_orig, + *col, + final_alpha, + Traits::range * brush_strength); + } } - color_orig = ss->mode.vpaint.previous_color[l_index]; } - const float final_alpha = 255 * brush_fade * brush_strength * tex_alpha * - brush_alpha_pressure * grid_alpha; - - /* Mix the new color with the original based on final_alpha. */ - lcol[l_index] = vpaint_blend(data->vp, - lcol[l_index], - color_orig, - color_final, - final_alpha, - 255 * brush_strength); } } } } - } - } - BKE_pbvh_vertex_iter_end; + BKE_pbvh_vertex_iter_end; + }; + }); } -static void do_vpaint_brush_blur_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) +template<typename Color = ColorPaint4b, typename Traits, AttributeDomain domain> +static void do_vpaint_brush_smear(bContext *C, + Sculpt *sd, + VPaint *vp, + VPaintData<Color, Traits, domain> *vpd, + Object *ob, + Mesh *me, + PBVHNode **nodes, + int totnode, + Color *lcol) { - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); + using Value = typename Traits::ValueType; + using Blend = typename Traits::BlendType; + + SculptSession *ss = ob->sculpt; - Scene *scene = CTX_data_scene(data->C); const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; - const Brush *brush = data->brush; const StrokeCache *cache = ss->cache; - uint *lcol = data->lcol; - float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( - scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - const bool use_normal = vwpaint_use_normal(data->vp); - const bool use_vert_sel = (data->me->editflag & - (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; - const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); - 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 Brush *brush = ob->sculpt->cache->brush; + const Scene *scene = CTX_data_scene(C); + Color *color_curr = static_cast<Color *>(vpd->smear.color_curr); + Color *color_prev_smear = static_cast<Color *>(vpd->smear.color_prev); + Color *color_prev = reinterpret_cast<Color *>(ss->cache->prev_colors_vpaint); + + blender::threading::parallel_for(IndexRange(totnode), 1LL, [&](IndexRange range) { + for (int n : range) { + float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; + + get_brush_alpha_data( + scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + float brush_dir[3]; + const bool use_normal = vwpaint_use_normal(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; + + sub_v3_v3v3(brush_dir, cache->location, cache->last_location); + project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal); + + if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) { + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape( + ss, brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* For grid based pbvh, take the vert whose loop corresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv_curr = &me->mvert[v_index]; + + /* if the vertex is selected for painting. */ + if (!use_vert_sel || mv_curr->flag & SELECT) { + /* Calc the dot prod. between ray norm on surf and current vert + * (ie splash prevention factor), and only paint front facing verts. */ + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff( + &vpd->normal_angle_precalc, angle_cos, &brush_strength))) { + const float brush_fade = BKE_brush_curve_strength( + brush, sqrtf(test.dist), cache->radius); + + bool do_color = false; + /* Minimum dot product between brush direction and current + * to neighbor direction is 0.0, meaning orthogonal. */ + float stroke_dot_max = 0.0f; + + /* Get the color of the loop in the opposite + * direction of the brush movement */ + Color color_final(0, 0, 0, 0); + + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(me->mloop[l_index].v == v_index); + UNUSED_VARS_NDEBUG(l_index); + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + const MLoop *ml_other = &me->mloop[mp->loopstart]; + for (int k = 0; k < mp->totloop; k++, ml_other++) { + const uint v_other_index = ml_other->v; + if (v_other_index != v_index) { + const MVert *mv_other = &me->mvert[v_other_index]; + + /* Get the direction from the + * selected vert to the neighbor. */ + float other_dir[3]; + sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co); + project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal); + + normalize_v3(other_dir); + + const float stroke_dot = dot_v3v3(other_dir, brush_dir); + int elem_index; + + if constexpr (domain == ATTR_DOMAIN_POINT) { + elem_index = ml_other->v; + } + else { + elem_index = mp->loopstart + k; + } + + if (stroke_dot > stroke_dot_max) { + stroke_dot_max = stroke_dot; + color_final = color_prev_smear[elem_index]; + do_color = true; + } + } + } + } + } - /* For each vertex */ - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - /* Test to see if the vertex coordinates are within the spherical brush region. */ - if (sculpt_brush_test_sq_fn(&test, vd.co)) { - /* For grid based pbvh, take the vert whose loop corresponds to the current grid. - * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv = &data->me->mvert[v_index]; + if (do_color) { + const float final_alpha = Traits::range * brush_fade * brush_strength * + brush_alpha_pressure * grid_alpha; - /* If the vertex is selected for painting. */ - if (!use_vert_sel || mv->flag & SELECT) { - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && - ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || - view_angle_limits_apply_falloff( - &data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) { - const float brush_fade = BKE_brush_curve_strength( - brush, sqrtf(test.dist), cache->radius); + /* For each poly owning this vert, + * paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; - /* Get the average poly color */ - uint color_final = 0; - int total_hit_loops = 0; - uint blend[4] = {0}; - for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { - int p_index = gmap->vert_to_poly[v_index].indices[j]; - const MPoly *mp = &data->me->mpoly[p_index]; - if (!use_face_sel || mp->flag & ME_FACE_SEL) { - total_hit_loops += mp->totloop; - for (int k = 0; k < mp->totloop; k++) { - const uint l_index = mp->loopstart + k; - const char *col = (const char *)(&lcol[l_index]); - /* Color is squared to compensate the sqrt color encoding. */ - blend[0] += (uint)col[0] * (uint)col[0]; - blend[1] += (uint)col[1] * (uint)col[1]; - blend[2] += (uint)col[2] * (uint)col[2]; - blend[3] += (uint)col[3] * (uint)col[3]; + int elem_index; + if constexpr (domain == ATTR_DOMAIN_POINT) { + elem_index = v_index; + } + else { + elem_index = l_index; + } + + BLI_assert(me->mloop[l_index].v == v_index); + + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + /* Get the previous element color */ + Color color_orig(0, 0, 0, 0); /* unused when array is NULL */ + + if (color_prev != NULL) { + /* Get the previous element color */ + if (isZero(color_prev[elem_index])) { + color_prev[elem_index] = lcol[elem_index]; + } + color_orig = color_prev[elem_index]; + } + /* Mix the new color with the original + * based on the brush strength and the curve. */ + lcol[elem_index] = vpaint_blend<Color, Traits>(vp, + lcol[elem_index], + color_orig, + color_final, + final_alpha, + Traits::range * + brush_strength); + + color_curr[elem_index] = lcol[elem_index]; + } + } + } } } } - if (total_hit_loops != 0) { - /* Use rgb^2 color averaging. */ - char *col = (char *)(&color_final); - col[0] = round_fl_to_uchar(sqrtf(divide_round_i(blend[0], total_hit_loops))); - col[1] = round_fl_to_uchar(sqrtf(divide_round_i(blend[1], total_hit_loops))); - col[2] = round_fl_to_uchar(sqrtf(divide_round_i(blend[2], total_hit_loops))); - col[3] = round_fl_to_uchar(sqrtf(divide_round_i(blend[3], total_hit_loops))); - - /* For each poly owning this vert, - * paint each loop belonging to this vert. */ - for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { - const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(data->me->mloop[l_index].v == v_index); - const MPoly *mp = &data->me->mpoly[p_index]; - if (!use_face_sel || mp->flag & ME_FACE_SEL) { - uint color_orig = 0; /* unused when array is NULL */ - if (ss->mode.vpaint.previous_color != NULL) { - /* Get the previous loop color */ - if (ss->mode.vpaint.previous_color[l_index] == 0) { - ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; - } - color_orig = ss->mode.vpaint.previous_color[l_index]; + } + BKE_pbvh_vertex_iter_end; + } + } + }); +} + +template<typename Color, typename Traits, AttributeDomain domain> +static void calculate_average_color(VPaintData<Color, Traits, domain> *vpd, + Object *ob, + Mesh *me, + const Brush *brush, + Color *lcol, + PBVHNode **nodes, + int totnode) +{ + using Blend = typename Traits::BlendType; + using Value = typename Traits::ValueType; + + struct VPaintAverageAccum<Blend> *accum = (VPaintAverageAccum<Blend> *)MEM_mallocN( + sizeof(*accum) * totnode, __func__); + blender::threading::parallel_for(IndexRange(totnode), 1LL, [&](IndexRange range) { + for (int n : range) { + SculptSession *ss = ob->sculpt; + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); + const bool has_grids = (pbvh_type == PBVH_GRIDS); + const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; + + StrokeCache *cache = ss->cache; + const bool use_vert_sel = (me->editflag & + (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; + + struct VPaintAverageAccum<Blend> *accum2 = accum + n; + accum2->len = 0; + memset(accum2->value, 0, sizeof(accum2->value)); + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + vd.vert_indices[vd.i]; + if (BKE_brush_curve_strength(brush, 0.0, cache->radius) > 0.0) { + /* If the vertex is selected for painting. */ + const MVert *mv = &me->mvert[v_index]; + if (!use_vert_sel || mv->flag & SELECT) { + accum2->len += gmap->vert_to_loop[v_index].count; + /* if a vertex is within the brush region, then add its color to the blend. */ + for (int j = 0; j < gmap->vert_to_loop[v_index].count; j++) { + int elem_index; + + if constexpr (domain == ATTR_DOMAIN_CORNER) { + elem_index = gmap->vert_to_loop[v_index].indices[j]; + } + else { + elem_index = v_index; } - const float final_alpha = 255 * brush_fade * brush_strength * - brush_alpha_pressure * grid_alpha; - /* Mix the new color with the original - * based on the brush strength and the curve. */ - lcol[l_index] = vpaint_blend(data->vp, - lcol[l_index], - color_orig, - *((uint *)col), - final_alpha, - 255 * brush_strength); + + Color *col = lcol + elem_index; + + /* Color is squared to compensate the sqrt color encoding. */ + accum2->value[0] += col->r * col->r; + accum2->value[1] += col->g * col->g; + accum2->value[2] += col->b * col->b; } } } } } + BKE_pbvh_vertex_iter_end; } + }); + + Blend accum_len = 0; + Blend accum_value[3] = {0}; + Color blend(0, 0, 0, 0); + + for (int i = 0; i < totnode; i++) { + accum_len += accum[i].len; + accum_value[0] += accum[i].value[0]; + accum_value[1] += accum[i].value[1]; + accum_value[2] += accum[i].value[2]; + } + if (accum_len != 0) { + blend.r = Traits::round(sqrtf(Traits::divide_round(accum_value[0], accum_len))); + blend.g = Traits::round(sqrtf(Traits::divide_round(accum_value[1], accum_len))); + blend.b = Traits::round(sqrtf(Traits::divide_round(accum_value[2], accum_len))); + blend.a = Traits::range; + + vpd->paintcol = blend; } - BKE_pbvh_vertex_iter_end; } -static void do_vpaint_brush_smear_task_cb_ex(void *__restrict userdata, - const int n, - const TaskParallelTLS *__restrict UNUSED(tls)) +template<typename Color, typename Traits, AttributeDomain domain> +static float paint_and_tex_color_alpha(VPaint *vp, + VPaintData<Color, Traits, domain> *vpd, + const float v_co[3], + Color *r_color) { - SculptThreadedTaskData *data = userdata; - SculptSession *ss = data->ob->sculpt; - const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - const bool has_grids = (pbvh_type == PBVH_GRIDS); + using Value = typename Traits::ValueType; - Scene *scene = CTX_data_scene(data->C); - const struct SculptVertexPaintGeomMap *gmap = &ss->mode.vpaint.gmap; - const Brush *brush = data->brush; - const StrokeCache *cache = ss->cache; - uint *lcol = data->lcol; - float brush_size_pressure, brush_alpha_value, brush_alpha_pressure; - get_brush_alpha_data( - scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); - float brush_dir[3]; - const bool use_normal = vwpaint_use_normal(data->vp); - const bool use_vert_sel = (data->me->editflag & - (ME_EDIT_PAINT_FACE_SEL | ME_EDIT_PAINT_VERT_SEL)) != 0; - const bool use_face_sel = (data->me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + ColorPaint4f rgba; + ColorPaint4f rgba_br = toFloat(*r_color); - sub_v3_v3v3(brush_dir, cache->location, cache->last_location); - project_plane_v3_v3v3(brush_dir, brush_dir, cache->view_normal); + paint_and_tex_color_alpha_intern(vp, &vpd->vc, v_co, &rgba.r); - if (cache->is_last_valid && (normalize_v3(brush_dir) != 0.0f)) { + rgb_uchar_to_float(&rgba_br.r, (const uchar *)&vpd->paintcol); + mul_v3_v3(rgba_br, rgba); - 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); + *r_color = fromFloat<Color>(rgba_br); + return rgba[3]; +} - /* For each vertex */ - PBVHVertexIter vd; - BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) { - /* Test to see if the vertex coordinates are within the spherical brush region. */ - if (sculpt_brush_test_sq_fn(&test, vd.co)) { - /* For grid based pbvh, take the vert whose loop corresponds to the current grid. - * Otherwise, take the current vert. */ - const int v_index = has_grids ? data->me->mloop[vd.grid_indices[vd.g]].v : - vd.vert_indices[vd.i]; - const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; - const MVert *mv_curr = &data->me->mvert[v_index]; +template<typename Color, typename Traits, AttributeDomain domain> +static void vpaint_do_draw(bContext *C, + Sculpt *sd, + VPaint *vp, + VPaintData<Color, Traits, domain> *vpd, + Object *ob, + Mesh *me, + PBVHNode **nodes, + int totnode, + Color *lcol) +{ + using Value = typename Traits::ValueType; - /* if the vertex is selected for painting. */ - if (!use_vert_sel || mv_curr->flag & SELECT) { - /* Calc the dot prod. between ray norm on surf and current vert - * (ie splash prevention factor), and only paint front facing verts. */ - float brush_strength = cache->bstrength; - const float angle_cos = (use_normal && vd.no) ? - dot_v3v3(sculpt_normal_frontface, vd.no) : - 1.0f; - if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && - ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || - view_angle_limits_apply_falloff( - &data->vpd->normal_angle_precalc, angle_cos, &brush_strength))) { - const float brush_fade = BKE_brush_curve_strength( - brush, sqrtf(test.dist), cache->radius); + SculptSession *ss = ob->sculpt; + const PBVHType pbvh_type = BKE_pbvh_type(ss->pbvh); - 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; + const Brush *brush = ob->sculpt->cache->brush; + const Scene *scene = CTX_data_scene(C); + + Color *previous_color = static_cast<Color *>(ss->cache->prev_colors_vpaint); + + blender::threading::parallel_for(IndexRange(totnode), 1LL, [&](IndexRange range) { + for (int n : range) { + const bool has_grids = (pbvh_type == PBVH_GRIDS); + const struct 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( + scene, ss, brush, &brush_size_pressure, &brush_alpha_value, &brush_alpha_pressure); + 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; + + SculptBrushTest test; + SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape( + ss, &test, brush->falloff_shape); + const float *sculpt_normal_frontface = SCULPT_brush_frontface_normal_from_falloff_shape( + ss, brush->falloff_shape); + + Color paintcol = vpd->paintcol; + + /* For each vertex */ + PBVHVertexIter vd; + BKE_pbvh_vertex_iter_begin (ss->pbvh, nodes[n], vd, PBVH_ITER_UNIQUE) { + /* Test to see if the vertex coordinates are within the spherical brush region. */ + if (sculpt_brush_test_sq_fn(&test, vd.co)) { + /* NOTE: Grids are 1:1 with corners (aka loops). + * For grid based pbvh, take the vert whose loop corresponds to the current grid. + * Otherwise, take the current vert. */ + const int v_index = has_grids ? me->mloop[vd.grid_indices[vd.g]].v : + vd.vert_indices[vd.i]; + const float grid_alpha = has_grids ? 1.0f / vd.gridsize : 1.0f; + const MVert *mv = &me->mvert[v_index]; + + /* If the vertex is selected for painting. */ + if (!use_vert_sel || mv->flag & SELECT) { + /* Calc the dot prod. between ray norm on surf and current vert + * (ie splash prevention factor), and only paint front facing verts. */ + float brush_strength = cache->bstrength; + const float angle_cos = (use_normal && vd.no) ? + dot_v3v3(sculpt_normal_frontface, vd.no) : + 1.0f; + if (((brush->flag & BRUSH_FRONTFACE) == 0 || (angle_cos > 0.0f)) && + ((brush->flag & BRUSH_FRONTFACE_FALLOFF) == 0 || + view_angle_limits_apply_falloff( + &vpd->normal_angle_precalc, angle_cos, &brush_strength))) { + const float brush_fade = BKE_brush_curve_strength( + brush, sqrtf(test.dist), cache->radius); - /* Get the color of the loop in the opposite - * direction of the brush movement */ - uint color_final = 0; - for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { - const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(data->me->mloop[l_index].v == v_index); - UNUSED_VARS_NDEBUG(l_index); - const MPoly *mp = &data->me->mpoly[p_index]; - if (!use_face_sel || mp->flag & ME_FACE_SEL) { - const MLoop *ml_other = &data->me->mloop[mp->loopstart]; - for (int k = 0; k < mp->totloop; k++, ml_other++) { - const uint v_other_index = ml_other->v; - if (v_other_index != v_index) { - const MVert *mv_other = &data->me->mvert[v_other_index]; - - /* Get the direction from the - * selected vert to the neighbor. */ - float other_dir[3]; - sub_v3_v3v3(other_dir, mv_curr->co, mv_other->co); - project_plane_v3_v3v3(other_dir, other_dir, cache->view_normal); - - normalize_v3(other_dir); - - const float stroke_dot = dot_v3v3(other_dir, brush_dir); - - if (stroke_dot > stroke_dot_max) { - stroke_dot_max = stroke_dot; - color_final = data->vpd->smear.color_prev[mp->loopstart + k]; - do_color = true; - } - } - } + Color color_final = paintcol; + + /* If we're painting with a texture, sample the texture color and alpha. */ + float tex_alpha = 1.0; + if (vpd->is_texbrush) { + /* NOTE: we may want to paint alpha as vertex color alpha. */ + tex_alpha = paint_and_tex_color_alpha<Color, Traits, domain>( + vp, vpd, vpd->vertexcosnos[v_index].co, &color_final); } - } - if (do_color) { - const float final_alpha = 255 * brush_fade * brush_strength * brush_alpha_pressure * - grid_alpha; + Color color_orig(0, 0, 0, 0); - /* For each poly owning this vert, - * paint each loop belonging to this vert. */ - for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { - const int p_index = gmap->vert_to_poly[v_index].indices[j]; - const int l_index = gmap->vert_to_loop[v_index].indices[j]; - BLI_assert(data->me->mloop[l_index].v == v_index); - const MPoly *mp = &data->me->mpoly[p_index]; - if (!use_face_sel || mp->flag & ME_FACE_SEL) { + if constexpr (domain == ATTR_DOMAIN_POINT) { + int v_index = vd.index; + + if (previous_color != NULL) { /* Get the previous loop color */ - uint color_orig = 0; /* unused when array is NULL */ - if (ss->mode.vpaint.previous_color != NULL) { - /* Get the previous loop color */ - if (ss->mode.vpaint.previous_color[l_index] == 0) { - ss->mode.vpaint.previous_color[l_index] = lcol[l_index]; + if (isZero(previous_color[v_index])) { + previous_color[v_index] = lcol[v_index]; + } + color_orig = previous_color[v_index]; + } + const float final_alpha = Traits::frange * brush_fade * brush_strength * + tex_alpha * brush_alpha_pressure * grid_alpha; + + lcol[v_index] = vpaint_blend<Color, Traits>(vp, + lcol[v_index], + color_orig, + color_final, + final_alpha, + Traits::range * brush_strength); + } + else { + /* For each poly owning this vert, paint each loop belonging to this vert. */ + for (int j = 0; j < gmap->vert_to_poly[v_index].count; j++) { + const int p_index = gmap->vert_to_poly[v_index].indices[j]; + const int l_index = gmap->vert_to_loop[v_index].indices[j]; + BLI_assert(me->mloop[l_index].v == v_index); + const MPoly *mp = &me->mpoly[p_index]; + if (!use_face_sel || mp->flag & ME_FACE_SEL) { + Color color_orig = Color(0, 0, 0, 0); /* unused when array is NULL */ + + if (previous_color != NULL) { + /* Get the previous loop color */ + if (isZero(previous_color[l_index])) { + previous_color[l_index] = lcol[l_index]; + } + color_orig = previous_color[l_index]; } - color_orig = ss->mode.vpaint.previous_color[l_index]; + const float final_alpha = Traits::frange * brush_fade * brush_strength * + tex_alpha * brush_alpha_pressure * grid_alpha; + + /* Mix the new color with the original based on final_alpha. */ + lcol[l_index] = vpaint_blend<Color, Traits>(vp, + lcol[l_index], + color_orig, + color_final, + final_alpha, + Traits::range * brush_strength); } - /* Mix the new color with the original - * based on the brush strength and the curve. */ - lcol[l_index] = vpaint_blend(data->vp, - lcol[l_index], - color_orig, - color_final, - final_alpha, - 255 * brush_strength); - - data->vpd->smear.color_curr[l_index] = lcol[l_index]; } } } } } } + BKE_pbvh_vertex_iter_end; } - BKE_pbvh_vertex_iter_end; - } + }); } -static void calculate_average_color(SculptThreadedTaskData *data, - PBVHNode **UNUSED(nodes), - int totnode) +template<typename Color, typename Traits, AttributeDomain domain> +static void vpaint_do_blur(bContext *C, + Sculpt *sd, + VPaint *vp, + VPaintData<Color, Traits, domain> *vpd, + Object *ob, + Mesh *me, + PBVHNode **nodes, + int totnode, + Color *lcol) { - struct VPaintAverageAccum *accum = MEM_mallocN(sizeof(*accum) * totnode, __func__); - data->custom_data = accum; - - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); - BLI_task_parallel_range(0, totnode, data, do_vpaint_brush_calc_average_color_cb_ex, &settings); - - uint accum_len = 0; - uint accum_value[3] = {0}; - uchar blend[4] = {0}; - for (int i = 0; i < totnode; i++) { - accum_len += accum[i].len; - accum_value[0] += accum[i].value[0]; - accum_value[1] += accum[i].value[1]; - accum_value[2] += accum[i].value[2]; + if constexpr (domain == ATTR_DOMAIN_POINT) { + do_vpaint_brush_blur_verts<Color, Traits>(C, sd, vp, vpd, ob, me, nodes, totnode, lcol); } - if (accum_len != 0) { - blend[0] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[0], accum_len))); - blend[1] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[1], accum_len))); - blend[2] = round_fl_to_uchar(sqrtf(divide_round_i(accum_value[2], accum_len))); - blend[3] = 255; - data->vpd->paintcol = *((uint *)blend); + else { + do_vpaint_brush_blur_loops<Color, Traits>(C, sd, vp, vpd, ob, me, nodes, totnode, lcol); } - - MEM_SAFE_FREE(data->custom_data); /* 'accum' */ } +template<typename Color, typename Traits, AttributeDomain domain> static void vpaint_paint_leaves(bContext *C, Sculpt *sd, VPaint *vp, - struct VPaintData *vpd, + VPaintData<Color, Traits, domain> *vpd, Object *ob, Mesh *me, + Color *lcol, PBVHNode **nodes, int totnode) { + + for (int i : IndexRange(totnode)) { + SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COLOR); + } + const Brush *brush = ob->sculpt->cache->brush; - SculptThreadedTaskData data = { - .C = C, - .sd = sd, - .ob = ob, - .brush = brush, - .nodes = nodes, - .vp = vp, - .vpd = vpd, - .lcol = (uint *)me->mloopcol, - .me = me, - }; - TaskParallelSettings settings; - BKE_pbvh_parallel_range_settings(&settings, true, totnode); switch ((eBrushVertexPaintTool)brush->vertexpaint_tool) { case VPAINT_TOOL_AVERAGE: - calculate_average_color(&data, nodes, totnode); - BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_draw_task_cb_ex, &settings); + calculate_average_color<Color, Traits, domain>(vpd, ob, me, brush, lcol, nodes, totnode); + case VPAINT_TOOL_DRAW: + vpaint_do_draw<Color, Traits, domain>(C, sd, vp, vpd, ob, me, nodes, totnode, lcol); break; case VPAINT_TOOL_BLUR: - BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_blur_task_cb_ex, &settings); + vpaint_do_blur<Color, Traits, domain>(C, sd, vp, vpd, ob, me, nodes, totnode, lcol); break; case VPAINT_TOOL_SMEAR: - BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_smear_task_cb_ex, &settings); + do_vpaint_brush_smear<Color, Traits, domain>(C, sd, vp, vpd, ob, me, nodes, totnode, lcol); break; - case VPAINT_TOOL_DRAW: - BLI_task_parallel_range(0, totnode, &data, do_vpaint_brush_draw_task_cb_ex, &settings); + default: break; } } +template<typename Color, typename Traits, AttributeDomain domain> static void vpaint_do_paint(bContext *C, Sculpt *sd, VPaint *vp, - struct VPaintData *vpd, + VPaintData<Color, Traits, domain> *vpd, Object *ob, Mesh *me, Brush *brush, @@ -3339,18 +3781,22 @@ static void vpaint_do_paint(bContext *C, int totnode; PBVHNode **nodes = vwpaint_pbvh_gather_generic(ob, vp, sd, brush, &totnode); + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + Color *color_data = static_cast<Color *>(layer->data); + /* Paint those leaves. */ - vpaint_paint_leaves(C, sd, vp, vpd, ob, me, nodes, totnode); + vpaint_paint_leaves<Color, Traits, domain>(C, sd, vp, vpd, ob, me, color_data, nodes, totnode); if (nodes) { MEM_freeN(nodes); } } +template<typename Color, typename Traits, AttributeDomain domain> static void vpaint_do_radial_symmetry(bContext *C, Sculpt *sd, VPaint *vp, - struct VPaintData *vpd, + VPaintData<Color, Traits, domain> *vpd, Object *ob, Mesh *me, Brush *brush, @@ -3359,17 +3805,18 @@ static void vpaint_do_radial_symmetry(bContext *C, { for (int i = 1; i < vp->radial_symm[axis - 'X']; i++) { const float angle = (2.0 * M_PI) * i / vp->radial_symm[axis - 'X']; - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, symm, axis, i, angle); + vpaint_do_paint<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, symm, axis, i, angle); } } /* near duplicate of: sculpt.c's, * 'do_symmetrical_brush_actions' and 'wpaint_do_symmetrical_brush_actions'. */ +template<typename Color, typename Traits, AttributeDomain domain> static void vpaint_do_symmetrical_brush_actions( - bContext *C, Sculpt *sd, VPaint *vp, struct VPaintData *vpd, Object *ob) + bContext *C, Sculpt *sd, VPaint *vp, VPaintData<Color, Traits, domain> *vpd, Object *ob) { Brush *brush = BKE_paint_brush(&vp->paint); - Mesh *me = ob->data; + Mesh *me = (Mesh *)ob->data; SculptSession *ss = ob->sculpt; StrokeCache *cache = ss->cache; const char symm = SCULPT_mesh_symmetry_xyz_get(ob); @@ -3377,10 +3824,10 @@ static void vpaint_do_symmetrical_brush_actions( /* initial stroke */ cache->mirror_symmetry_pass = 0; - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + vpaint_do_paint<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'X'); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Z'); cache->symmetry = symm; @@ -3393,16 +3840,16 @@ static void vpaint_do_symmetrical_brush_actions( SCULPT_cache_calc_brushdata_symm(cache, i, 0, 0); if (i & (1 << 0)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'X'); + vpaint_do_paint<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'X', 0, 0); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'X'); } if (i & (1 << 1)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Y'); + vpaint_do_paint<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Y', 0, 0); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Y'); } if (i & (1 << 2)) { - vpaint_do_paint(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0); - vpaint_do_radial_symmetry(C, sd, vp, vpd, ob, me, brush, i, 'Z'); + vpaint_do_paint<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Z', 0, 0); + vpaint_do_radial_symmetry<Color, Traits, domain>(C, sd, vp, vpd, ob, me, brush, i, 'Z'); } } } @@ -3411,14 +3858,15 @@ static void vpaint_do_symmetrical_brush_actions( cache->is_last_valid = true; } -static void vpaint_stroke_update_step(bContext *C, - wmOperator *UNUSED(op), - struct PaintStroke *stroke, - PointerRNA *itemptr) +template<typename Color, typename Traits, AttributeDomain domain> +static void vpaint_stroke_update_step_intern(bContext *C, + struct PaintStroke *stroke, + PointerRNA *itemptr) { Scene *scene = CTX_data_scene(C); ToolSettings *ts = CTX_data_tool_settings(C); - struct VPaintData *vpd = paint_stroke_mode_data(stroke); + VPaintData<Color, Traits, domain> *vpd = static_cast<VPaintData<Color, Traits, domain> *>( + paint_stroke_mode_data(stroke)); VPaint *vp = ts->vpaint; ViewContext *vc = &vpd->vc; Object *ob = vc->obact; @@ -3436,15 +3884,20 @@ static void vpaint_stroke_update_step(bContext *C, swap_m4m4(vc->rv3d->persmat, mat); - vpaint_do_symmetrical_brush_actions(C, sd, vp, vpd, ob); + vpaint_do_symmetrical_brush_actions<Color, Traits, domain>(C, sd, vp, vpd, ob); swap_m4m4(vc->rv3d->persmat, mat); - BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL); + BKE_mesh_batch_cache_dirty_tag((Mesh *)ob->data, BKE_MESH_BATCH_DIRTY_ALL); if (vp->paint.brush->vertexpaint_tool == VPAINT_TOOL_SMEAR) { - memcpy( - vpd->smear.color_prev, vpd->smear.color_curr, sizeof(uint) * ((Mesh *)ob->data)->totloop); + Mesh *me = BKE_object_get_original_mesh(ob); + + size_t elem_size; + int elem_num; + + elem_num = get_vcol_elements(me, &elem_size); + memcpy(vpd->smear.color_prev, vpd->smear.color_curr, elem_size * elem_num); } /* Calculate pivot for rotation around selection if needed. @@ -3458,19 +3911,47 @@ static void vpaint_stroke_update_step(bContext *C, if (vpd->use_fast_update == false) { /* recalculate modifier stack to get new colors, slow, * avoid this if we can! */ - DEG_id_tag_update(ob->data, 0); + DEG_id_tag_update((ID *)ob->data, 0); } else { /* Flush changes through DEG. */ - DEG_id_tag_update(ob->data, ID_RECALC_COPY_ON_WRITE); + DEG_id_tag_update((ID *)ob->data, ID_RECALC_COPY_ON_WRITE); } } -static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) +static void vpaint_stroke_update_step(bContext *C, + wmOperator *UNUSED(op), + struct PaintStroke *stroke, + PointerRNA *itemptr) { - struct VPaintData *vpd = paint_stroke_mode_data(stroke); - ViewContext *vc = &vpd->vc; - Object *ob = vc->obact; + VPaintDataBase *vpd = static_cast<VPaintDataBase *>(paint_stroke_mode_data(stroke)); + + if (vpd->domain == ATTR_DOMAIN_POINT) { + if (vpd->type == CD_PROP_COLOR) { + vpaint_stroke_update_step_intern<ColorPaint4f, FloatTraits, ATTR_DOMAIN_POINT>( + C, stroke, itemptr); + } + else if (vpd->type == CD_PROP_BYTE_COLOR) { + vpaint_stroke_update_step_intern<ColorPaint4b, ByteTraits, ATTR_DOMAIN_POINT>( + C, stroke, itemptr); + } + } + else if (vpd->domain == ATTR_DOMAIN_CORNER) { + if (vpd->type == CD_PROP_COLOR) { + vpaint_stroke_update_step_intern<ColorPaint4f, FloatTraits, ATTR_DOMAIN_CORNER>( + C, stroke, itemptr); + } + else if (vpd->type == CD_PROP_BYTE_COLOR) { + vpaint_stroke_update_step_intern<ColorPaint4b, ByteTraits, ATTR_DOMAIN_CORNER>( + C, stroke, itemptr); + } + } +} + +template<typename Color, typename Traits, AttributeDomain domain> +static void vpaint_free_vpaintdata(Object *ob, void *_vpd) +{ + VPaintData<Color, Traits, domain> *vpd = static_cast<VPaintData<Color, Traits, domain> *>(_vpd); if (vpd->is_texbrush) { ED_vpaint_proj_handle_free(vpd->vp_handle); @@ -3486,6 +3967,41 @@ static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) MEM_freeN(vpd->smear.color_curr); } + MEM_delete<VPaintData<Color, Traits, domain>>(vpd); +} + +static ViewContext *vpaint_get_viewcontext(Object *ob, void *vpd_ptr) +{ + VPaintDataBase *vpd = static_cast<VPaintDataBase *>(vpd_ptr); + + return &vpd->vc; +} + +static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) +{ + void *vpd_ptr = paint_stroke_mode_data(stroke); + VPaintDataBase *vpd = static_cast<VPaintDataBase *>(vpd_ptr); + + ViewContext *vc = &vpd->vc; + Object *ob = vc->obact; + + if (vpd->domain == ATTR_DOMAIN_POINT) { + if (vpd->type == CD_PROP_COLOR) { + vpaint_free_vpaintdata<ColorPaint4f, FloatTraits, ATTR_DOMAIN_POINT>(ob, vpd); + } + else if (vpd->type == CD_PROP_BYTE_COLOR) { + vpaint_free_vpaintdata<ColorPaint4b, ByteTraits, ATTR_DOMAIN_POINT>(ob, vpd); + } + } + else if (vpd->domain == ATTR_DOMAIN_CORNER) { + if (vpd->type == CD_PROP_COLOR) { + vpaint_free_vpaintdata<ColorPaint4f, FloatTraits, ATTR_DOMAIN_CORNER>(ob, vpd); + } + else if (vpd->type == CD_PROP_BYTE_COLOR) { + vpaint_free_vpaintdata<ColorPaint4b, ByteTraits, ATTR_DOMAIN_CORNER>(ob, vpd); + } + } + SculptSession *ss = ob->sculpt; if (ss->cache->alt_smooth) { @@ -3496,7 +4012,7 @@ static void vpaint_stroke_done(const bContext *C, struct PaintStroke *stroke) WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob); - MEM_freeN(vpd); + SCULPT_undo_push_end(ob); SCULPT_cache_free(ob->sculpt->cache); ob->sculpt->cache = NULL; @@ -3515,8 +4031,16 @@ static int vpaint_invoke(bContext *C, wmOperator *op, const wmEvent *event) vpaint_stroke_done, event->type); + Object *ob = CTX_data_active_object(C); + + if (SCULPT_has_loop_colors(ob) && ob->sculpt->pbvh) { + BKE_pbvh_ensure_node_loops(ob->sculpt->pbvh); + } + + SCULPT_undo_push_begin(ob, "Vertex Paint"); + if ((retval = op->type->modal(C, op, event)) == OPERATOR_FINISHED) { - paint_stroke_free(C, op, op->customdata); + paint_stroke_free(C, op, (PaintStroke *)op->customdata); return OPERATOR_FINISHED; } @@ -3541,7 +4065,7 @@ static int vpaint_exec(bContext *C, wmOperator *op) 0); /* frees op->customdata */ - paint_stroke_exec(C, op, op->customdata); + paint_stroke_exec(C, op, (PaintStroke *)op->customdata); return OPERATOR_FINISHED; } @@ -3554,7 +4078,7 @@ static void vpaint_cancel(bContext *C, wmOperator *op) ob->sculpt->cache = NULL; } - paint_stroke_cancel(C, op, op->customdata); + paint_stroke_cancel(C, op, (PaintStroke *)op->customdata); } static int vpaint_modal(bContext *C, wmOperator *op, const wmEvent *event) @@ -3582,4 +4106,112 @@ void PAINT_OT_vertex_paint(wmOperatorType *ot) paint_stroke_operator_properties(ot); } +/* -------------------------------------------------------------------- */ +/** \name Set Vertex Colors Operator + * \{ */ + +template<typename Color, typename Traits, AttributeDomain domain> +static bool vertex_color_set(Object *ob, ColorPaint4f paintcol_in, Color *color_layer) +{ + Mesh *me; + if (((me = BKE_mesh_from_object(ob)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) { + return false; + } + + Color paintcol = fromFloat<Color>(paintcol_in); + + const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; + const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; + + const MPoly *mp = me->mpoly; + for (int i = 0; i < me->totpoly; i++, mp++) { + if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { + continue; + } + + int j = 0; + do { + uint vidx = me->mloop[mp->loopstart + j].v; + + if (!(use_vert_sel && !(me->mvert[vidx].flag & SELECT))) { + if constexpr (domain == ATTR_DOMAIN_CORNER) { + color_layer[mp->loopstart + j] = paintcol; + } + else { + color_layer[vidx] = paintcol; + } + } + j++; + } while (j < mp->totloop); + } + + /* remove stale me->mcol, will be added later */ + BKE_mesh_tessface_clear(me); + + DEG_id_tag_update(&me->id, ID_RECALC_COPY_ON_WRITE); + + /* NOTE: Original mesh is used for display, so tag it directly here. */ + BKE_mesh_batch_cache_dirty_tag(me, BKE_MESH_BATCH_DIRTY_ALL); + + return true; +} + +static int vertex_color_set_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Scene *scene = CTX_data_scene(C); + Object *obact = CTX_data_active_object(C); + Mesh *me = BKE_object_get_original_mesh(obact); + + // uint paintcol = vpaint_get_current_color(scene, scene->toolsettings->vpaint, false); + ColorPaint4f paintcol = vpaint_get_current_col<ColorPaint4f, FloatTraits, ATTR_DOMAIN_POINT>( + scene, scene->toolsettings->vpaint, false); + + bool ok = false; + + CustomDataLayer *layer = BKE_id_attributes_active_color_get(&me->id); + AttributeDomain domain = BKE_id_attribute_domain(&me->id, layer); + + if (domain == ATTR_DOMAIN_POINT) { + if (layer->type == CD_PROP_COLOR) { + ok = vertex_color_set<ColorPaint4f, FloatTraits, ATTR_DOMAIN_POINT>( + obact, paintcol, static_cast<ColorPaint4f *>(layer->data)); + } + else if (layer->type == CD_PROP_BYTE_COLOR) { + ok = vertex_color_set<ColorPaint4b, ByteTraits, ATTR_DOMAIN_POINT>( + obact, paintcol, static_cast<ColorPaint4b *>(layer->data)); + } + } + else { + if (layer->type == CD_PROP_COLOR) { + ok = vertex_color_set<ColorPaint4f, FloatTraits, ATTR_DOMAIN_CORNER>( + obact, paintcol, static_cast<ColorPaint4f *>(layer->data)); + } + else if (layer->type == CD_PROP_BYTE_COLOR) { + ok = vertex_color_set<ColorPaint4b, ByteTraits, ATTR_DOMAIN_CORNER>( + obact, paintcol, static_cast<ColorPaint4b *>(layer->data)); + } + } + + if (ok) { + WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void PAINT_OT_vertex_color_set(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Vertex Colors"; + ot->idname = "PAINT_OT_vertex_color_set"; + ot->description = "Fill the active vertex color layer with the current paint color"; + + /* api callbacks */ + ot->exec = vertex_color_set_exec; + ot->poll = vertex_paint_mode_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + /** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c index 619607408e5..9f337d8d789 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_ops.c @@ -47,75 +47,6 @@ static void tag_object_after_update(Object *object) BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL); } -/* -------------------------------------------------------------------- */ -/** \name Set Vertex Colors Operator - * \{ */ - -static bool vertex_color_set(Object *ob, uint paintcol) -{ - Mesh *me; - if (((me = BKE_mesh_from_object(ob)) == NULL) || (ED_mesh_color_ensure(me, NULL) == false)) { - return false; - } - - const bool use_face_sel = (me->editflag & ME_EDIT_PAINT_FACE_SEL) != 0; - const bool use_vert_sel = (me->editflag & ME_EDIT_PAINT_VERT_SEL) != 0; - - const MPoly *mp = me->mpoly; - for (int i = 0; i < me->totpoly; i++, mp++) { - MLoopCol *lcol = me->mloopcol + mp->loopstart; - - if (use_face_sel && !(mp->flag & ME_FACE_SEL)) { - continue; - } - - int j = 0; - do { - uint vidx = me->mloop[mp->loopstart + j].v; - if (!(use_vert_sel && !(me->mvert[vidx].flag & SELECT))) { - *(int *)lcol = paintcol; - } - lcol++; - j++; - } while (j < mp->totloop); - } - - /* remove stale me->mcol, will be added later */ - BKE_mesh_tessface_clear(me); - - tag_object_after_update(ob); - - return true; -} - -static int vertex_color_set_exec(bContext *C, wmOperator *UNUSED(op)) -{ - Scene *scene = CTX_data_scene(C); - Object *obact = CTX_data_active_object(C); - uint paintcol = vpaint_get_current_col(scene, scene->toolsettings->vpaint, false); - - if (vertex_color_set(obact, paintcol)) { - WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obact); - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void PAINT_OT_vertex_color_set(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Set Vertex Colors"; - ot->idname = "PAINT_OT_vertex_color_set"; - ot->description = "Fill the active vertex color layer with the current paint color"; - - /* api callbacks */ - ot->exec = vertex_color_set_exec; - ot->poll = vertex_paint_mode_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c b/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c index 8d5f39fd56e..cd099f71ccd 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c +++ b/source/blender/editors/sculpt_paint/paint_vertex_color_utils.c @@ -73,570 +73,3 @@ bool ED_vpaint_color_transform(struct Object *ob, return true; } - -/* -------------------------------------------------------------------- */ -/** \name Color Blending Modes - * \{ */ - -BLI_INLINE uint mcol_blend(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - if (fac >= 255) { - return col_dst; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - /* Updated to use the rgb squared color model which blends nicer. */ - int r1 = cp_src[0] * cp_src[0]; - int g1 = cp_src[1] * cp_src[1]; - int b1 = cp_src[2] * cp_src[2]; - int a1 = cp_src[3] * cp_src[3]; - - int r2 = cp_dst[0] * cp_dst[0]; - int g2 = cp_dst[1] * cp_dst[1]; - int b2 = cp_dst[2] * cp_dst[2]; - int a2 = cp_dst[3] * cp_dst[3]; - - cp_mix[0] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * r1 + fac * r2), 255))); - cp_mix[1] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * g1 + fac * g2), 255))); - cp_mix[2] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * b1 + fac * b2), 255))); - cp_mix[3] = round_fl_to_uchar(sqrtf(divide_round_i((mfac * a1 + fac * a2), 255))); - - return col_mix; -} - -BLI_INLINE uint mcol_add(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - temp = cp_src[0] + divide_round_i((fac * cp_dst[0]), 255); - cp_mix[0] = (temp > 254) ? 255 : temp; - temp = cp_src[1] + divide_round_i((fac * cp_dst[1]), 255); - cp_mix[1] = (temp > 254) ? 255 : temp; - temp = cp_src[2] + divide_round_i((fac * cp_dst[2]), 255); - cp_mix[2] = (temp > 254) ? 255 : temp; - temp = cp_src[3] + divide_round_i((fac * cp_dst[3]), 255); - cp_mix[3] = (temp > 254) ? 255 : temp; - - return col_mix; -} - -BLI_INLINE uint mcol_sub(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - temp = cp_src[0] - divide_round_i((fac * cp_dst[0]), 255); - cp_mix[0] = (temp < 0) ? 0 : temp; - temp = cp_src[1] - divide_round_i((fac * cp_dst[1]), 255); - cp_mix[1] = (temp < 0) ? 0 : temp; - temp = cp_src[2] - divide_round_i((fac * cp_dst[2]), 255); - cp_mix[2] = (temp < 0) ? 0 : temp; - temp = cp_src[3] - divide_round_i((fac * cp_dst[3]), 255); - cp_mix[3] = (temp < 0) ? 0 : temp; - - return col_mix; -} - -BLI_INLINE uint mcol_mul(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - /* first mul, then blend the fac */ - cp_mix[0] = divide_round_i(mfac * cp_src[0] * 255 + fac * cp_dst[0] * cp_src[0], 255 * 255); - cp_mix[1] = divide_round_i(mfac * cp_src[1] * 255 + fac * cp_dst[1] * cp_src[1], 255 * 255); - cp_mix[2] = divide_round_i(mfac * cp_src[2] * 255 + fac * cp_dst[2] * cp_src[2], 255 * 255); - cp_mix[3] = divide_round_i(mfac * cp_src[3] * 255 + fac * cp_dst[3] * cp_src[3], 255 * 255); - - return col_mix; -} - -BLI_INLINE uint mcol_lighten(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - if (fac >= 255) { - return col_dst; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - /* See if we're lighter, if so mix, else don't do anything. - * if the paint color is darker then the original, then ignore */ - if (IMB_colormanagement_get_luminance_byte(cp_src) > - IMB_colormanagement_get_luminance_byte(cp_dst)) { - return col_src; - } - - cp_mix[0] = divide_round_i(mfac * cp_src[0] + fac * cp_dst[0], 255); - cp_mix[1] = divide_round_i(mfac * cp_src[1] + fac * cp_dst[1], 255); - cp_mix[2] = divide_round_i(mfac * cp_src[2] + fac * cp_dst[2], 255); - cp_mix[3] = divide_round_i(mfac * cp_src[3] + fac * cp_dst[3], 255); - - return col_mix; -} - -BLI_INLINE uint mcol_darken(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - if (fac >= 255) { - return col_dst; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - /* See if we're darker, if so mix, else don't do anything. - * if the paint color is brighter then the original, then ignore */ - if (IMB_colormanagement_get_luminance_byte(cp_src) < - IMB_colormanagement_get_luminance_byte(cp_dst)) { - return col_src; - } - - cp_mix[0] = divide_round_i((mfac * cp_src[0] + fac * cp_dst[0]), 255); - cp_mix[1] = divide_round_i((mfac * cp_src[1] + fac * cp_dst[1]), 255); - cp_mix[2] = divide_round_i((mfac * cp_src[2] + fac * cp_dst[2]), 255); - cp_mix[3] = divide_round_i((mfac * cp_src[3] + fac * cp_dst[3]), 255); - return col_mix; -} - -BLI_INLINE uint mcol_colordodge(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - temp = (cp_dst[0] == 255) ? 255 : min_ii((cp_src[0] * 225) / (255 - cp_dst[0]), 255); - cp_mix[0] = (mfac * cp_src[0] + temp * fac) / 255; - temp = (cp_dst[1] == 255) ? 255 : min_ii((cp_src[1] * 225) / (255 - cp_dst[1]), 255); - cp_mix[1] = (mfac * cp_src[1] + temp * fac) / 255; - temp = (cp_dst[2] == 255) ? 255 : min_ii((cp_src[2] * 225) / (255 - cp_dst[2]), 255); - cp_mix[2] = (mfac * cp_src[2] + temp * fac) / 255; - temp = (cp_dst[3] == 255) ? 255 : min_ii((cp_src[3] * 225) / (255 - cp_dst[3]), 255); - cp_mix[3] = (mfac * cp_src[3] + temp * fac) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_difference(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - temp = abs(cp_src[0] - cp_dst[0]); - cp_mix[0] = (mfac * cp_src[0] + temp * fac) / 255; - temp = abs(cp_src[1] - cp_dst[1]); - cp_mix[1] = (mfac * cp_src[1] + temp * fac) / 255; - temp = abs(cp_src[2] - cp_dst[2]); - cp_mix[2] = (mfac * cp_src[2] + temp * fac) / 255; - temp = abs(cp_src[3] - cp_dst[3]); - cp_mix[3] = (mfac * cp_src[3] + temp * fac) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_screen(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - temp = max_ii(255 - (((255 - cp_src[0]) * (255 - cp_dst[0])) / 255), 0); - cp_mix[0] = (mfac * cp_src[0] + temp * fac) / 255; - temp = max_ii(255 - (((255 - cp_src[1]) * (255 - cp_dst[1])) / 255), 0); - cp_mix[1] = (mfac * cp_src[1] + temp * fac) / 255; - temp = max_ii(255 - (((255 - cp_src[2]) * (255 - cp_dst[2])) / 255), 0); - cp_mix[2] = (mfac * cp_src[2] + temp * fac) / 255; - temp = max_ii(255 - (((255 - cp_src[3]) * (255 - cp_dst[3])) / 255), 0); - cp_mix[3] = (mfac * cp_src[3] + temp * fac) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_hardlight(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - int i = 0; - - for (i = 0; i < 4; i++) { - if (cp_dst[i] > 127) { - temp = 255 - ((255 - 2 * (cp_dst[i] - 127)) * (255 - cp_src[i]) / 255); - } - else { - temp = (2 * cp_dst[i] * cp_src[i]) >> 8; - } - cp_mix[i] = min_ii((mfac * cp_src[i] + temp * fac) / 255, 255); - } - return col_mix; -} - -BLI_INLINE uint mcol_overlay(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - int i = 0; - - for (i = 0; i < 4; i++) { - if (cp_src[i] > 127) { - temp = 255 - ((255 - 2 * (cp_src[i] - 127)) * (255 - cp_dst[i]) / 255); - } - else { - temp = (2 * cp_dst[i] * cp_src[i]) >> 8; - } - cp_mix[i] = min_ii((mfac * cp_src[i] + temp * fac) / 255, 255); - } - return col_mix; -} - -BLI_INLINE uint mcol_softlight(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - for (int i = 0; i < 4; i++) { - if (cp_src[i] < 127) { - temp = ((2 * ((cp_dst[i] / 2) + 64)) * cp_src[i]) / 255; - } - else { - temp = 255 - (2 * (255 - ((cp_dst[i] / 2) + 64)) * (255 - cp_src[i]) / 255); - } - cp_mix[i] = (temp * fac + cp_src[i] * mfac) / 255; - } - return col_mix; -} - -BLI_INLINE uint mcol_exclusion(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac, temp; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - int i = 0; - - for (i = 0; i < 4; i++) { - temp = 127 - ((2 * (cp_src[i] - 127) * (cp_dst[i] - 127)) / 255); - cp_mix[i] = (temp * fac + cp_src[i] * mfac) / 255; - } - return col_mix; -} - -BLI_INLINE uint mcol_luminosity(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - float h1, s1, v1; - float h2, s2, v2; - float r, g, b; - rgb_to_hsv(cp_src[0] / 255.0f, cp_src[1] / 255.0f, cp_src[2] / 255.0f, &h1, &s1, &v1); - rgb_to_hsv(cp_dst[0] / 255.0f, cp_dst[1] / 255.0f, cp_dst[2] / 255.0f, &h2, &s2, &v2); - - v1 = v2; - - hsv_to_rgb(h1, s1, v1, &r, &g, &b); - - cp_mix[0] = ((int)(r * 255.0f) * fac + mfac * cp_src[0]) / 255; - cp_mix[1] = ((int)(g * 255.0f) * fac + mfac * cp_src[1]) / 255; - cp_mix[2] = ((int)(b * 255.0f) * fac + mfac * cp_src[2]) / 255; - cp_mix[3] = ((int)(cp_dst[3]) * fac + mfac * cp_src[3]) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_saturation(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - float h1, s1, v1; - float h2, s2, v2; - float r, g, b; - rgb_to_hsv(cp_src[0] / 255.0f, cp_src[1] / 255.0f, cp_src[2] / 255.0f, &h1, &s1, &v1); - rgb_to_hsv(cp_dst[0] / 255.0f, cp_dst[1] / 255.0f, cp_dst[2] / 255.0f, &h2, &s2, &v2); - - if (s1 > EPS_SATURATION) { - s1 = s2; - } - - hsv_to_rgb(h1, s1, v1, &r, &g, &b); - - cp_mix[0] = ((int)(r * 255.0f) * fac + mfac * cp_src[0]) / 255; - cp_mix[1] = ((int)(g * 255.0f) * fac + mfac * cp_src[1]) / 255; - cp_mix[2] = ((int)(b * 255.0f) * fac + mfac * cp_src[2]) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_hue(uint col_src, uint col_dst, int fac) -{ - uchar *cp_src, *cp_dst, *cp_mix; - int mfac; - uint col_mix = 0; - - if (fac == 0) { - return col_src; - } - - mfac = 255 - fac; - - cp_src = (uchar *)&col_src; - cp_dst = (uchar *)&col_dst; - cp_mix = (uchar *)&col_mix; - - float h1, s1, v1; - float h2, s2, v2; - float r, g, b; - rgb_to_hsv(cp_src[0] / 255.0f, cp_src[1] / 255.0f, cp_src[2] / 255.0f, &h1, &s1, &v1); - rgb_to_hsv(cp_dst[0] / 255.0f, cp_dst[1] / 255.0f, cp_dst[2] / 255.0f, &h2, &s2, &v2); - - h1 = h2; - - hsv_to_rgb(h1, s1, v1, &r, &g, &b); - - cp_mix[0] = ((int)(r * 255.0f) * fac + mfac * cp_src[0]) / 255; - cp_mix[1] = ((int)(g * 255.0f) * fac + mfac * cp_src[1]) / 255; - cp_mix[2] = ((int)(b * 255.0f) * fac + mfac * cp_src[2]) / 255; - cp_mix[3] = ((int)(cp_dst[3]) * fac + mfac * cp_src[3]) / 255; - return col_mix; -} - -BLI_INLINE uint mcol_alpha_add(uint col_src, int fac) -{ - uchar *cp_src, *cp_mix; - int temp; - uint col_mix = col_src; - - if (fac == 0) { - return col_src; - } - - cp_src = (uchar *)&col_src; - cp_mix = (uchar *)&col_mix; - - temp = cp_src[3] + fac; - cp_mix[3] = (temp > 254) ? 255 : temp; - - return col_mix; -} - -BLI_INLINE uint mcol_alpha_sub(uint col_src, int fac) -{ - uchar *cp_src, *cp_mix; - int temp; - uint col_mix = col_src; - - if (fac == 0) { - return col_src; - } - - cp_src = (uchar *)&col_src; - cp_mix = (uchar *)&col_mix; - - temp = cp_src[3] - fac; - cp_mix[3] = temp < 0 ? 0 : temp; - - return col_mix; -} - -uint ED_vpaint_blend_tool(const int tool, const uint col, const uint paintcol, const int alpha_i) -{ - switch ((IMB_BlendMode)tool) { - case IMB_BLEND_MIX: - return mcol_blend(col, paintcol, alpha_i); - case IMB_BLEND_ADD: - return mcol_add(col, paintcol, alpha_i); - case IMB_BLEND_SUB: - return mcol_sub(col, paintcol, alpha_i); - case IMB_BLEND_MUL: - return mcol_mul(col, paintcol, alpha_i); - case IMB_BLEND_LIGHTEN: - return mcol_lighten(col, paintcol, alpha_i); - case IMB_BLEND_DARKEN: - return mcol_darken(col, paintcol, alpha_i); - case IMB_BLEND_COLORDODGE: - return mcol_colordodge(col, paintcol, alpha_i); - case IMB_BLEND_DIFFERENCE: - return mcol_difference(col, paintcol, alpha_i); - case IMB_BLEND_SCREEN: - return mcol_screen(col, paintcol, alpha_i); - case IMB_BLEND_HARDLIGHT: - return mcol_hardlight(col, paintcol, alpha_i); - case IMB_BLEND_OVERLAY: - return mcol_overlay(col, paintcol, alpha_i); - case IMB_BLEND_SOFTLIGHT: - return mcol_softlight(col, paintcol, alpha_i); - case IMB_BLEND_EXCLUSION: - return mcol_exclusion(col, paintcol, alpha_i); - case IMB_BLEND_LUMINOSITY: - return mcol_luminosity(col, paintcol, alpha_i); - case IMB_BLEND_SATURATION: - return mcol_saturation(col, paintcol, alpha_i); - case IMB_BLEND_HUE: - return mcol_hue(col, paintcol, alpha_i); - /* non-color */ - case IMB_BLEND_ERASE_ALPHA: - return mcol_alpha_sub(col, alpha_i); - case IMB_BLEND_ADD_ALPHA: - return mcol_alpha_add(col, alpha_i); - default: - BLI_assert(0); - return 0; - } -} - -/** \} */ diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c index d576d529ce5..ff13d755971 100644 --- a/source/blender/editors/sculpt_paint/sculpt.c +++ b/source/blender/editors/sculpt_paint/sculpt.c @@ -4025,6 +4025,7 @@ void SCULPT_cache_free(StrokeCache *cache) MEM_SAFE_FREE(cache->detail_directions); MEM_SAFE_FREE(cache->prev_displacement); MEM_SAFE_FREE(cache->limit_surface_co); + MEM_SAFE_FREE(cache->prev_colors_vpaint); if (cache->pose_ik_chain) { SCULPT_pose_ik_chain_free(cache->pose_ik_chain); diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h index 3839c0e71e4..bd95bb4666b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.h +++ b/source/blender/editors/sculpt_paint/sculpt_intern.h @@ -219,7 +219,6 @@ typedef struct SculptThreadedTaskData { int totnode; struct VPaint *vp; - struct VPaintData *vpd; struct WPaintData *wpd; struct WeightPaintInfo *wpi; unsigned int *lcol; @@ -493,6 +492,7 @@ typedef struct StrokeCache { float mouse_event[2]; float (*prev_colors)[4]; + void *prev_colors_vpaint; /* Multires Displacement Smear. */ float (*prev_displacement)[3]; diff --git a/source/tools b/source/tools -Subproject 4c1e01e3e309282beb1af3b1eddb2c7f9a666b5 +Subproject 1e658ca996f11e5ff3398d89bd81f5b719304a5 |