/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 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)); int clamped_radius = max_ff(radius, 1.0); ushort *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] = float(x) + aa_offset; float total_weight = 0; for (int i = 0; i < aa_samples; i++) { pixel_xy[1] = 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 / clamped_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 = ushort(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( 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(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); }