Sculpt: Initial data-oriented refactor for smooth brush
Part of #118145. Like the draw brush, the overall API is still in progress, and in particular duplication will be reduced in the future. But this brush already reuses quite a bit of the utilities from the draw brush. A new utility is added for finding vertex neighbor indices as well. Overall I found this about 8% faster than the smooth brush in main already. Pull Request: https://projects.blender.org/blender/blender/pulls/122906
This commit is contained in:
@@ -121,6 +121,7 @@ set(SRC
|
||||
brushes/flatten.cc
|
||||
brushes/inflate.cc
|
||||
brushes/scrape.cc
|
||||
brushes/smooth.cc
|
||||
|
||||
brushes/types.hh
|
||||
)
|
||||
|
||||
352
source/blender/editors/sculpt_paint/brushes/smooth.cc
Normal file
352
source/blender/editors/sculpt_paint/brushes/smooth.cc
Normal file
@@ -0,0 +1,352 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "editors/sculpt_paint/brushes/types.hh"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_key.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_paint.hh"
|
||||
#include "BKE_pbvh.hh"
|
||||
#include "BKE_subdiv_ccg.hh"
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_virtual_array.hh"
|
||||
|
||||
#include "editors/sculpt_paint/mesh_brush_common.hh"
|
||||
#include "editors/sculpt_paint/sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
inline namespace smooth_cc {
|
||||
|
||||
static Vector<float> iteration_strengths(const float strength)
|
||||
{
|
||||
constexpr int max_iterations = 4;
|
||||
|
||||
BLI_assert(strength >= 0.0f);
|
||||
const float clamped_strength = std::min(strength, 1.0f);
|
||||
|
||||
const int count = int(clamped_strength * max_iterations);
|
||||
const float last = max_iterations * (clamped_strength - float(count) / max_iterations);
|
||||
Vector<float> result;
|
||||
result.append_n_times(1.0f, count);
|
||||
result.append(last);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct LocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<Vector<int>> vert_neighbors;
|
||||
Vector<float3> translations;
|
||||
};
|
||||
|
||||
static float3 average_positions(const Span<float3> positions, const Span<int> indices)
|
||||
{
|
||||
const float factor = math::rcp(float(indices.size()));
|
||||
float3 result(0);
|
||||
for (const int i : indices) {
|
||||
result += positions[i] * factor;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void calc_smooth_positions_faces(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const GroupedSpan<int> vert_to_face_map,
|
||||
const BitSpan boundary_verts,
|
||||
const Span<bool> hide_poly,
|
||||
const Span<int> verts,
|
||||
const Span<float3> positions,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> new_positions)
|
||||
{
|
||||
tls.vert_neighbors.reinitialize(verts.size());
|
||||
calc_vert_neighbors_interior(
|
||||
faces, corner_verts, vert_to_face_map, boundary_verts, hide_poly, verts, tls.vert_neighbors);
|
||||
const Span<Vector<int>> vert_neighbors = tls.vert_neighbors;
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
const Span<int> neighbors = vert_neighbors[i];
|
||||
if (neighbors.is_empty()) {
|
||||
new_positions[i] = positions[verts[i]];
|
||||
}
|
||||
else {
|
||||
new_positions[i] = average_positions(positions, neighbors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void translations_from_new_positions(const Span<float3> new_positions,
|
||||
const Span<int> verts,
|
||||
const Span<float3> old_positions,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
BLI_assert(new_positions.size() == verts.size());
|
||||
for (const int i : verts.index_range()) {
|
||||
translations[i] = new_positions[i] - old_positions[verts[i]];
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_positions_faces(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<float3> vert_normals,
|
||||
const PBVHNode &node,
|
||||
const float strength,
|
||||
Object &object,
|
||||
LocalData &tls,
|
||||
const Span<float3> new_positions,
|
||||
MutableSpan<float3> positions_orig)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
const Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(node);
|
||||
|
||||
tls.factors.reinitialize(verts.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(mesh, verts, factors);
|
||||
|
||||
if (brush.flag & BRUSH_FRONTFACE) {
|
||||
calc_front_face(cache.view_normal, vert_normals, verts, factors);
|
||||
}
|
||||
|
||||
tls.distances.reinitialize(verts.size());
|
||||
const MutableSpan<float> distances = tls.distances;
|
||||
calc_distance_falloff(
|
||||
ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors);
|
||||
calc_brush_strength_factors(ss, brush, verts, distances, factors);
|
||||
|
||||
if (ss.cache->automasking) {
|
||||
auto_mask::calc_vert_factors(object, *ss.cache->automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions_eval, verts, factors);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
translations_from_new_positions(new_positions, verts, positions_eval, translations);
|
||||
scale_translations(translations, factors);
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions_eval, verts, translations);
|
||||
|
||||
apply_translations_to_pbvh(*ss.pbvh, verts, translations);
|
||||
|
||||
if (!ss.deform_imats.is_empty()) {
|
||||
apply_crazyspace_to_translations(ss.deform_imats, verts, translations);
|
||||
}
|
||||
|
||||
apply_translations(translations, verts, positions_orig);
|
||||
apply_translations_to_shape_keys(object, verts, translations, positions_orig);
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void do_smooth_brush_mesh(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
Object &object,
|
||||
Span<PBVHNode *> nodes,
|
||||
const float brush_strength)
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
const VArraySpan hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
||||
|
||||
const PBVH &pbvh = *ss.pbvh;
|
||||
|
||||
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(pbvh);
|
||||
const Span<float3> vert_normals = BKE_pbvh_get_vert_normals(pbvh);
|
||||
MutableSpan<float3> positions_orig = mesh.vert_positions_for_write();
|
||||
|
||||
Array<int> node_vert_offset_data(nodes.size() + 1);
|
||||
for (const int i : nodes.index_range()) {
|
||||
node_vert_offset_data[i] = bke::pbvh::node_unique_verts(*nodes[i]).size();
|
||||
}
|
||||
const OffsetIndices<int> node_vert_offsets = offset_indices::accumulate_counts_to_offsets(
|
||||
node_vert_offset_data);
|
||||
Array<float3> new_positions(node_vert_offsets.total_size());
|
||||
|
||||
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
||||
for (const float strength : iteration_strengths(brush_strength)) {
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_smooth_positions_faces(faces,
|
||||
corner_verts,
|
||||
ss.vert_to_face_map,
|
||||
ss.vertex_info.boundary,
|
||||
hide_poly,
|
||||
bke::pbvh::node_unique_verts(*nodes[i]),
|
||||
positions_eval,
|
||||
tls,
|
||||
new_positions.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
apply_positions_faces(sd,
|
||||
brush,
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
*nodes[i],
|
||||
strength,
|
||||
object,
|
||||
tls,
|
||||
new_positions.as_span().slice(node_vert_offsets[i]),
|
||||
positions_orig);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_grids(const Sculpt &sd,
|
||||
Object &object,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
const PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const CCGKey key = *BKE_pbvh_get_grid_key(*ss.pbvh);
|
||||
const Span<CCGElem *> grids = subdiv_ccg.grids;
|
||||
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
||||
|
||||
for (const int grid : bke::pbvh::node_grid_indices(node)) {
|
||||
const int grid_verts_start = grid * key.grid_area;
|
||||
CCGElem *elem = grids[grid];
|
||||
for (const int j : IndexRange(key.grid_area)) {
|
||||
if (!grid_hidden.is_empty() && grid_hidden[grid][j]) {
|
||||
continue;
|
||||
}
|
||||
float3 &co = CCG_elem_offset_co(key, elem, j);
|
||||
if (!sculpt_brush_test_sq_fn(test, co)) {
|
||||
continue;
|
||||
}
|
||||
const float fade = strength * SCULPT_brush_strength_factor(
|
||||
ss,
|
||||
brush,
|
||||
co,
|
||||
sqrtf(test.dist),
|
||||
CCG_elem_offset_no(key, elem, j),
|
||||
nullptr,
|
||||
key.has_mask ? CCG_elem_offset_mask(key, elem, j) : 0.0f,
|
||||
BKE_pbvh_make_vref(grid_verts_start + j),
|
||||
thread_id,
|
||||
&automask_data);
|
||||
|
||||
float3 avg = smooth::neighbor_coords_average_interior(
|
||||
ss, BKE_pbvh_make_vref(grid_verts_start + j));
|
||||
float3 final_co = co + (avg - co) * fade;
|
||||
SCULPT_clip(sd, ss, co, final_co);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_bmesh(
|
||||
const Sculpt &sd, Object &object, const Brush &brush, const float strength, PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
const int mask_offset = CustomData_get_offset_named(
|
||||
&ss.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
||||
|
||||
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&node)) {
|
||||
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, vert->co)) {
|
||||
continue;
|
||||
}
|
||||
auto_mask::node_update(automask_data, *vert);
|
||||
const float mask = mask_offset == -1 ? 0.0f : BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
|
||||
const float fade = strength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vert->co,
|
||||
sqrtf(test.dist),
|
||||
vert->no,
|
||||
nullptr,
|
||||
mask,
|
||||
BKE_pbvh_make_vref(intptr_t(vert)),
|
||||
thread_id,
|
||||
&automask_data);
|
||||
|
||||
float3 avg = smooth::neighbor_coords_average_interior(ss, BKE_pbvh_make_vref(intptr_t(vert)));
|
||||
float3 final_co = float3(vert->co) + (avg - float3(vert->co)) * fade;
|
||||
SCULPT_clip(sd, ss, vert->co, final_co);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace smooth_cc
|
||||
|
||||
void do_smooth_brush(const Sculpt &sd,
|
||||
Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const float brush_strength)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
SCULPT_boundary_info_ensure(object);
|
||||
|
||||
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
|
||||
case PBVH_FACES:
|
||||
do_smooth_brush_mesh(sd, brush, object, nodes, brush_strength);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
for (const float strength : iteration_strengths(brush_strength)) {
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_grids(sd, object, brush, strength, *nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
BM_mesh_elem_index_ensure(ss.bm, BM_VERT);
|
||||
BM_mesh_elem_table_ensure(ss.bm, BM_VERT);
|
||||
for (const float strength : iteration_strengths(brush_strength)) {
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_bmesh(sd, object, brush, strength, *nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -17,6 +17,11 @@ void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_nudge_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
/** A simple normal-direction displacement based on image texture RGB/XYZ values. */
|
||||
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
/** Smooth positions with neighboring vertices. */
|
||||
void do_smooth_brush(const Sculpt &sd,
|
||||
Object &object,
|
||||
Span<PBVHNode *> nodes,
|
||||
float brush_strength);
|
||||
void do_scrape_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_fill_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_flatten_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "DNA_brush_enums.h"
|
||||
|
||||
@@ -152,6 +153,27 @@ void apply_translations_to_shape_keys(Object &object,
|
||||
*/
|
||||
void apply_translations_to_pbvh(PBVH &pbvh, Span<int> verts, Span<float3> positions_orig);
|
||||
|
||||
/**
|
||||
* Find vertices connected to the indexed vertices across faces. For boundary vertices (stored in
|
||||
* the \a boundary_verts argument), only include other boundary vertices. Also skip connectivity
|
||||
* accross hidden faces and skip neighbors of corner vertices.
|
||||
*
|
||||
* \note A vector allocated per element is typically not a good strategy for performance because
|
||||
* of each vector's 24 byte overhead, non-contiguous memory, and the possibility of further heap
|
||||
* allocations. However, it's done here for now for two reasons:
|
||||
* 1. In typical quad meshes there are just 4 neighbors, which fit in the inline buffer.
|
||||
* 2. We want to avoid using edges, and the remaining topology map we have access to is the
|
||||
* vertex to face map. That requires deduplication when building the neighbors, which
|
||||
* requires some intermediate data structure like a vector anyway.
|
||||
*/
|
||||
void calc_vert_neighbors_interior(OffsetIndices<int> faces,
|
||||
Span<int> corner_verts,
|
||||
GroupedSpan<int> vert_to_face,
|
||||
BitSpan boundary_verts,
|
||||
Span<bool> hide_poly,
|
||||
Span<int> verts,
|
||||
MutableSpan<Vector<int>> result);
|
||||
|
||||
void scrape_calc_translations(const Span<float3> vert_positions,
|
||||
const Span<int> verts,
|
||||
const float4 &plane,
|
||||
|
||||
@@ -3764,7 +3764,17 @@ static void do_brush_action(const Sculpt &sd,
|
||||
}
|
||||
case SCULPT_TOOL_SMOOTH:
|
||||
if (brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
|
||||
smooth::do_smooth_brush(sd, ob, nodes);
|
||||
/* NOTE: The enhance brush needs to initialize its state on the first brush step. The
|
||||
* stroke strength can become 0 during the stroke, but it can not change sign (the sign is
|
||||
* determined in the beginning of the stroke. So here it is important to not switch to
|
||||
* enhance brush in the middle of the stroke. */
|
||||
if (ss.cache->bstrength < 0.0f) {
|
||||
/* Invert mode, intensify details. */
|
||||
smooth::enhance_details_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
do_smooth_brush(sd, ob, nodes, ss.cache->bstrength);
|
||||
}
|
||||
}
|
||||
else if (brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
|
||||
smooth::do_surface_smooth_brush(sd, ob, nodes);
|
||||
@@ -3873,11 +3883,10 @@ static void do_brush_action(const Sculpt &sd,
|
||||
brush.autosmooth_factor > 0)
|
||||
{
|
||||
if (brush.flag & BRUSH_INVERSE_SMOOTH_PRESSURE) {
|
||||
smooth::do_smooth_brush(
|
||||
sd, ob, nodes, brush.autosmooth_factor * (1.0f - ss.cache->pressure));
|
||||
do_smooth_brush(sd, ob, nodes, brush.autosmooth_factor * (1.0f - ss.cache->pressure));
|
||||
}
|
||||
else {
|
||||
smooth::do_smooth_brush(sd, ob, nodes, brush.autosmooth_factor);
|
||||
do_smooth_brush(sd, ob, nodes, brush.autosmooth_factor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6747,4 +6756,43 @@ void scale_factors(const MutableSpan<float> factors, const float strength)
|
||||
}
|
||||
}
|
||||
|
||||
void calc_vert_neighbors_interior(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const GroupedSpan<int> vert_to_face,
|
||||
const BitSpan boundary_verts,
|
||||
const Span<bool> hide_poly,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<Vector<int>> result)
|
||||
{
|
||||
BLI_assert(result.size() == verts.size());
|
||||
BLI_assert(corner_verts.size() == faces.total_size());
|
||||
for (Vector<int> &vector : result) {
|
||||
vector.clear();
|
||||
}
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
const int vert = verts[i];
|
||||
Vector<int> &neighbors = result[i];
|
||||
for (const int face : vert_to_face[vert]) {
|
||||
if (!hide_poly.is_empty() && hide_poly[face]) {
|
||||
continue;
|
||||
}
|
||||
const int2 verts = bke::mesh::face_find_adjacent_verts(faces[face], corner_verts, vert);
|
||||
neighbors.append_non_duplicates(verts[0]);
|
||||
neighbors.append_non_duplicates(verts[1]);
|
||||
}
|
||||
|
||||
if (boundary_verts[vert]) {
|
||||
if (neighbors.size() == 2) {
|
||||
/* Do not include neighbors of corner vertices. */
|
||||
neighbors.clear();
|
||||
}
|
||||
else {
|
||||
/* Only include other boundary vertices as neighbors of boundary vertices. */
|
||||
neighbors.remove_if([&](const int vert) { return !boundary_verts[vert]; });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
|
||||
@@ -1591,8 +1591,7 @@ float4 neighbor_color_average(SculptSession &ss, PBVHVertRef vertex);
|
||||
*/
|
||||
float3 neighbor_coords_average_interior(const SculptSession &ss, PBVHVertRef vertex);
|
||||
|
||||
void do_smooth_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes, float bstrength);
|
||||
void do_smooth_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
void enhance_details_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
void do_smooth_mask_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes, float bstrength);
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ static void do_enhance_details_brush_task(Object &ob,
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static void enhance_details_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
void enhance_details_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
@@ -334,94 +334,6 @@ void do_smooth_mask_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes,
|
||||
}
|
||||
}
|
||||
|
||||
static void smooth_position_node(
|
||||
Object &ob, const Sculpt &sd, const Brush &brush, float bstrength, PBVHNode *node)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
CLAMP(bstrength, 0.0f, 1.0f);
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
ob, ss.cache->automasking.get(), *node);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (*ss.pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto_mask::node_update(automask_data, vd);
|
||||
|
||||
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask,
|
||||
vd.vertex,
|
||||
thread_id,
|
||||
&automask_data);
|
||||
|
||||
float val[3];
|
||||
const float3 avg = neighbor_coords_average_interior(ss, vd.vertex);
|
||||
sub_v3_v3v3(val, avg, vd.co);
|
||||
madd_v3_v3v3fl(val, vd.co, val, fade);
|
||||
SCULPT_clip(sd, ss, vd.co, val);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void do_smooth_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes, float bstrength)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
const int max_iterations = 4;
|
||||
const float fract = 1.0f / max_iterations;
|
||||
|
||||
CLAMP(bstrength, 0.0f, 1.0f);
|
||||
|
||||
const int count = int(bstrength * max_iterations);
|
||||
const float last = max_iterations * (bstrength - count * fract);
|
||||
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
|
||||
for (const int iteration : IndexRange(count + 1)) {
|
||||
const float strength = (iteration != count) ? 1.0f : last;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
smooth_position_node(ob, sd, brush, strength, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void do_smooth_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
|
||||
/* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke
|
||||
* strength can become 0 during the stroke, but it can not change sign (the sign is determined
|
||||
* in the beginning of the stroke. So here it is important to not switch to enhance brush in the
|
||||
* middle of the stroke. */
|
||||
if (ss.cache->bstrength < 0.0f) {
|
||||
/* Invert mode, intensify details. */
|
||||
enhance_details_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
/* Regular mode, smooth. */
|
||||
do_smooth_brush(sd, ob, nodes, ss.cache->bstrength);
|
||||
}
|
||||
}
|
||||
|
||||
/* HC Smooth Algorithm. */
|
||||
/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user