diff options
Diffstat (limited to 'source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc')
-rw-r--r-- | source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc new file mode 100644 index 00000000000..8d57a3d9152 --- /dev/null +++ b/source/blender/editors/sculpt_paint/paint_image_2d_curve_mask.cc @@ -0,0 +1,188 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup ed + */ + +#include "BLI_math.h" + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" + +#include "BKE_brush.h" + +#include "paint_intern.h" + +namespace blender::ed::sculpt_paint { + +constexpr int AntiAliasingSamplesPerTexelAxisMin = 3; +constexpr int AntiAliasingSamplesPerTexelAxisMax = 16; +/** + * \brief Number of samples to use between 0..1. + */ +constexpr int CurveSamplesBaseLen = 1024; +/** + * \brief Number of samples to store in the cache. + * + * M_SQRT2 is used as brushes are circles and the curve_mask is square. + * + 1 to fix floating rounding issues. + */ +constexpr int CurveSamplesLen = M_SQRT2 * CurveSamplesBaseLen + 1; + +static int aa_samples_per_texel_axis(const Brush *brush, const float radius) +{ + int aa_samples = 1.0f / (radius * 0.20f); + if (brush->sampling_flag & BRUSH_PAINT_ANTIALIASING) { + aa_samples = clamp_i( + aa_samples, AntiAliasingSamplesPerTexelAxisMin, AntiAliasingSamplesPerTexelAxisMax); + } + else { + aa_samples = 1; + } + return aa_samples; +} + +/* create a mask with the falloff strength */ +static void update_curve_mask(CurveMaskCache *curve_mask_cache, + const Brush *brush, + const int diameter, + const float radius, + const float cursor_position[2]) +{ + BLI_assert(curve_mask_cache->curve_mask != nullptr); + int offset = (int)floorf(diameter / 2.0f); + + unsigned short *m = curve_mask_cache->curve_mask; + + const int aa_samples = aa_samples_per_texel_axis(brush, radius); + const float aa_offset = 1.0f / (2.0f * (float)aa_samples); + const float aa_step = 1.0f / (float)aa_samples; + + float bpos[2]; + bpos[0] = cursor_position[0] - floorf(cursor_position[0]) + offset; + bpos[1] = cursor_position[1] - floorf(cursor_position[1]) + offset; + + float weight_factor = 65535.0f / (float)(aa_samples * aa_samples); + + for (int y = 0; y < diameter; y++) { + for (int x = 0; x < diameter; x++, m++) { + float pixel_xy[2]; + pixel_xy[0] = static_cast<float>(x) + aa_offset; + float total_weight = 0; + + for (int i = 0; i < aa_samples; i++) { + pixel_xy[1] = static_cast<float>(y) + aa_offset; + for (int j = 0; j < aa_samples; j++) { + const float len = len_v2v2(pixel_xy, bpos); + const int sample_index = min_ii((len / radius) * CurveSamplesBaseLen, + CurveSamplesLen - 1); + const float sample_weight = curve_mask_cache->sampled_curve[sample_index]; + + total_weight += sample_weight; + + pixel_xy[1] += aa_step; + } + pixel_xy[0] += aa_step; + } + *m = (unsigned short)(total_weight * weight_factor); + } + } +} + +static bool is_sampled_curve_valid(const CurveMaskCache *curve_mask_cache, const Brush *brush) +{ + if (curve_mask_cache->sampled_curve == nullptr) { + return false; + } + return curve_mask_cache->last_curve_timestamp == brush->curve->changed_timestamp; +} + +static void sampled_curve_free(CurveMaskCache *curve_mask_cache) +{ + MEM_SAFE_FREE(curve_mask_cache->sampled_curve); + curve_mask_cache->last_curve_timestamp = 0; +} + +static void update_sampled_curve(CurveMaskCache *curve_mask_cache, const Brush *brush) +{ + if (curve_mask_cache->sampled_curve == nullptr) { + curve_mask_cache->sampled_curve = static_cast<float *>( + MEM_mallocN(CurveSamplesLen * sizeof(float), __func__)); + } + + for (int i = 0; i < CurveSamplesLen; i++) { + const float len = i / float(CurveSamplesBaseLen); + const float sample_weight = BKE_brush_curve_strength_clamped(brush, len, 1.0f); + curve_mask_cache->sampled_curve[i] = sample_weight; + } + curve_mask_cache->last_curve_timestamp = brush->curve->changed_timestamp; +} + +static size_t diameter_to_curve_mask_size(const int diameter) +{ + return diameter * diameter * sizeof(ushort); +} + +static bool is_curve_mask_size_valid(const CurveMaskCache *curve_mask_cache, const int diameter) +{ + return curve_mask_cache->curve_mask_size == diameter_to_curve_mask_size(diameter); +} + +static void curve_mask_free(CurveMaskCache *curve_mask_cache) +{ + curve_mask_cache->curve_mask_size = 0; + MEM_SAFE_FREE(curve_mask_cache->curve_mask); +} + +static void curve_mask_allocate(CurveMaskCache *curve_mask_cache, const int diameter) +{ + const size_t curve_mask_size = diameter_to_curve_mask_size(diameter); + curve_mask_cache->curve_mask = static_cast<unsigned short *>( + MEM_mallocN(curve_mask_size, __func__)); + curve_mask_cache->curve_mask_size = curve_mask_size; +} + +} // namespace blender::ed::sculpt_paint + +using namespace blender::ed::sculpt_paint; + +void paint_curve_mask_cache_free_data(CurveMaskCache *curve_mask_cache) +{ + sampled_curve_free(curve_mask_cache); + curve_mask_free(curve_mask_cache); +} + +void paint_curve_mask_cache_update(CurveMaskCache *curve_mask_cache, + const Brush *brush, + const int diameter, + const float radius, + const float cursor_position[2]) +{ + if (!is_sampled_curve_valid(curve_mask_cache, brush)) { + update_sampled_curve(curve_mask_cache, brush); + } + + if (!is_curve_mask_size_valid(curve_mask_cache, diameter)) { + curve_mask_free(curve_mask_cache); + curve_mask_allocate(curve_mask_cache, diameter); + } + update_curve_mask(curve_mask_cache, brush, diameter, radius, cursor_position); +} |