Sculpt: Data oriented refactor for enhance details brush
Part of #118145. This brush first stores an array of translations for each vertex and then reuses those translations for the rest of the stroke. I added a few utilities to simplify storing the data for all vertices for the multires and BMesh implementations. Compared to the old code, computing the translations is skipped for completely hidden or masked nodes. Also we don't skip hidden neighbors since that gives better results. That wasn't as easy with the old API. Pull Request: https://projects.blender.org/blender/blender/pulls/124569
This commit is contained in:
@@ -130,6 +130,7 @@ set(SRC
|
||||
brushes/draw_sharp.cc
|
||||
brushes/draw.cc
|
||||
brushes/elastic_deform.cc
|
||||
brushes/enhance_details.cc
|
||||
brushes/fill.cc
|
||||
brushes/flatten.cc
|
||||
brushes/grab.cc
|
||||
|
||||
380
source/blender/editors/sculpt_paint/brushes/enhance_details.cc
Normal file
380
source/blender/editors/sculpt_paint/brushes/enhance_details.cc
Normal file
@@ -0,0 +1,380 @@
|
||||
/* 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_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_array_utils.hh"
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "editors/sculpt_paint/mesh_brush_common.hh"
|
||||
#include "editors/sculpt_paint/sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
inline namespace enhance_details_cc {
|
||||
|
||||
struct LocalData {
|
||||
Vector<float3> positions;
|
||||
Vector<float3> new_positions;
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<Vector<int>> vert_neighbors;
|
||||
Vector<float3> translations;
|
||||
};
|
||||
|
||||
static void calc_faces(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<float3> vert_normals,
|
||||
const Span<float3> all_translations,
|
||||
const float strength,
|
||||
const PBVHNode &node,
|
||||
Object &object,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> positions_orig)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
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);
|
||||
filter_region_clip_factors(ss, positions_eval, 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_brush_distances(
|
||||
ss, positions_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances);
|
||||
filter_distances_with_radius(cache.radius, distances, factors);
|
||||
apply_hardness_to_distances(cache, distances);
|
||||
calc_brush_strength_factors(cache, brush, distances, factors);
|
||||
|
||||
if (cache.automasking) {
|
||||
auto_mask::calc_vert_factors(object, *cache.automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions_eval, verts, factors);
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
array_utils::gather(all_translations, verts, translations);
|
||||
scale_translations(translations, factors);
|
||||
|
||||
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
|
||||
}
|
||||
|
||||
static void calc_grids(const Sculpt &sd,
|
||||
Object &object,
|
||||
const Brush &brush,
|
||||
const Span<float3> all_translations,
|
||||
const float strength,
|
||||
const PBVHNode &node,
|
||||
LocalData &tls)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
|
||||
const Span<int> grids = bke::pbvh::node_grid_indices(node);
|
||||
const int grid_verts_num = grids.size() * key.grid_area;
|
||||
|
||||
tls.positions.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float3> positions = tls.positions;
|
||||
gather_grids_positions(subdiv_ccg, grids, positions);
|
||||
|
||||
tls.factors.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(subdiv_ccg, grids, factors);
|
||||
filter_region_clip_factors(ss, positions, factors);
|
||||
if (brush.flag & BRUSH_FRONTFACE) {
|
||||
calc_front_face(cache.view_normal, subdiv_ccg, grids, factors);
|
||||
}
|
||||
|
||||
tls.distances.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float> distances = tls.distances;
|
||||
calc_brush_distances(ss, positions, eBrushFalloffShape(brush.falloff_shape), distances);
|
||||
filter_distances_with_radius(cache.radius, distances, factors);
|
||||
apply_hardness_to_distances(cache, distances);
|
||||
calc_brush_strength_factors(cache, brush, distances, factors);
|
||||
|
||||
if (cache.automasking) {
|
||||
auto_mask::calc_grids_factors(object, *cache.automasking, node, grids, factors);
|
||||
}
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions, factors);
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
tls.translations.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
gather_data_grids(subdiv_ccg, all_translations, grids, translations);
|
||||
scale_translations(translations, factors);
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions, translations);
|
||||
apply_translations(translations, grids, subdiv_ccg);
|
||||
}
|
||||
|
||||
static void calc_bmesh(const Sculpt &sd,
|
||||
Object &object,
|
||||
const Brush &brush,
|
||||
const Span<float3> all_translations,
|
||||
const float strength,
|
||||
PBVHNode &node,
|
||||
LocalData &tls)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
|
||||
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
|
||||
|
||||
tls.positions.reinitialize(verts.size());
|
||||
const MutableSpan<float3> positions = tls.positions;
|
||||
gather_bmesh_positions(verts, positions);
|
||||
|
||||
tls.factors.reinitialize(verts.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(*ss.bm, verts, factors);
|
||||
filter_region_clip_factors(ss, positions, factors);
|
||||
if (brush.flag & BRUSH_FRONTFACE) {
|
||||
calc_front_face(cache.view_normal, verts, factors);
|
||||
}
|
||||
|
||||
tls.distances.reinitialize(verts.size());
|
||||
const MutableSpan<float> distances = tls.distances;
|
||||
calc_brush_distances(ss, positions, eBrushFalloffShape(brush.falloff_shape), distances);
|
||||
filter_distances_with_radius(cache.radius, distances, factors);
|
||||
apply_hardness_to_distances(cache, distances);
|
||||
calc_brush_strength_factors(cache, brush, distances, factors);
|
||||
|
||||
if (cache.automasking) {
|
||||
auto_mask::calc_vert_factors(object, *cache.automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions, factors);
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
gather_data_vert_bmesh(all_translations, verts, translations);
|
||||
scale_translations(translations, factors);
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions, translations);
|
||||
apply_translations(translations, verts);
|
||||
}
|
||||
|
||||
static void calc_translations_faces(const Span<float3> vert_positions,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const GroupedSpan<int> vert_to_face_map,
|
||||
const PBVHNode &node,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> all_translations)
|
||||
{
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(node);
|
||||
|
||||
tls.vert_neighbors.reinitialize(verts.size());
|
||||
const MutableSpan<Vector<int>> neighbors = tls.vert_neighbors;
|
||||
calc_vert_neighbors(faces, corner_verts, vert_to_face_map, {}, verts, neighbors);
|
||||
|
||||
tls.new_positions.reinitialize(verts.size());
|
||||
const MutableSpan<float3> new_positions = tls.new_positions;
|
||||
smooth::neighbor_position_average_mesh(vert_positions, verts, neighbors, new_positions);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
translations_from_new_positions(new_positions, verts, vert_positions, translations);
|
||||
array_utils::scatter(translations.as_span(), verts, all_translations);
|
||||
}
|
||||
|
||||
static void calc_translations_grids(const SubdivCCG &subdiv_ccg,
|
||||
const PBVHNode &node,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> all_translations)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
|
||||
const Span<int> grids = bke::pbvh::node_grid_indices(node);
|
||||
const int grid_verts_num = grids.size() * key.grid_area;
|
||||
|
||||
tls.positions.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float3> positions = tls.positions;
|
||||
gather_grids_positions(subdiv_ccg, grids, positions);
|
||||
|
||||
tls.new_positions.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float3> new_positions = tls.new_positions;
|
||||
smooth::neighbor_position_average_grids(subdiv_ccg, grids, new_positions);
|
||||
|
||||
tls.translations.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
translations_from_new_positions(new_positions, positions, translations);
|
||||
scatter_data_grids(subdiv_ccg, translations, grids, all_translations);
|
||||
}
|
||||
|
||||
static void calc_translations_bmesh(PBVHNode &node,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> all_translations)
|
||||
{
|
||||
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
|
||||
|
||||
tls.positions.reinitialize(verts.size());
|
||||
const MutableSpan<float3> positions = tls.positions;
|
||||
gather_bmesh_positions(verts, positions);
|
||||
|
||||
tls.new_positions.reinitialize(verts.size());
|
||||
const MutableSpan<float3> new_positions = tls.new_positions;
|
||||
smooth::neighbor_position_average_bmesh(verts, new_positions);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
translations_from_new_positions(new_positions, positions, translations);
|
||||
scatter_data_vert_bmesh(translations.as_span(), verts, all_translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brush uses translations calculated at the beginning of the stroke. They can't be calculated
|
||||
* dynamically because changing positions will influence neighboring translations. However we can
|
||||
* reduce the cost in some cases by skipping initializing values for vertices in hidden or masked
|
||||
* nodes.
|
||||
*/
|
||||
static void precalc_translations(Object &object, const MutableSpan<float3> translations)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
PBVH &pbvh = *ss.pbvh;
|
||||
|
||||
Vector<PBVHNode *> effective_nodes = bke::pbvh::search_gather(
|
||||
pbvh, [&](PBVHNode &node) { return !node_fully_masked_or_hidden(node); });
|
||||
|
||||
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
||||
switch (BKE_pbvh_type(pbvh)) {
|
||||
case PBVH_FACES: {
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(pbvh);
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
threading::parallel_for(effective_nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
threading::isolate_task([&]() {
|
||||
for (const int i : range) {
|
||||
calc_translations_faces(positions_eval,
|
||||
faces,
|
||||
corner_verts,
|
||||
ss.vert_to_face_map,
|
||||
*effective_nodes[i],
|
||||
tls,
|
||||
translations);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PBVH_GRIDS: {
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
threading::parallel_for(effective_nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_translations_grids(subdiv_ccg, *effective_nodes[i], tls, translations);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PBVH_BMESH:
|
||||
BM_mesh_elem_index_ensure(ss.bm, BM_VERT);
|
||||
BM_mesh_elem_table_ensure(ss.bm, BM_VERT);
|
||||
threading::parallel_for(effective_nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_translations_bmesh(*effective_nodes[i], tls, translations);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace enhance_details_cc
|
||||
|
||||
void do_enhance_details_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
PBVH &pbvh = *ss.pbvh;
|
||||
|
||||
if (SCULPT_stroke_is_first_brush_step(*ss.cache)) {
|
||||
ss.cache->detail_directions.reinitialize(SCULPT_vertex_count_get(ss));
|
||||
precalc_translations(object, ss.cache->detail_directions);
|
||||
}
|
||||
|
||||
const float strength = std::clamp(ss.cache->bstrength, -1.0f, 1.0f);
|
||||
MutableSpan<float3> translations = ss.cache->detail_directions;
|
||||
|
||||
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
||||
switch (BKE_pbvh_type(pbvh)) {
|
||||
case PBVH_FACES: {
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
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();
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
threading::isolate_task([&]() {
|
||||
for (const int i : range) {
|
||||
calc_faces(sd,
|
||||
brush,
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
translations,
|
||||
strength,
|
||||
*nodes[i],
|
||||
object,
|
||||
tls,
|
||||
positions_orig);
|
||||
BKE_pbvh_node_mark_positions_update(nodes[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PBVH_GRIDS:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_grids(sd, object, brush, translations, strength, *nodes[i], tls);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
LocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_bmesh(sd, object, brush, translations, strength, *nodes[i], tls);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -31,6 +31,7 @@ void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_draw_sharp_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_elastic_deform_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_enhance_details_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);
|
||||
void do_grab_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
@@ -96,6 +96,24 @@ void gather_grids_normals(const SubdivCCG &subdiv_ccg,
|
||||
MutableSpan<float3> normals);
|
||||
void gather_bmesh_normals(const Set<BMVert *, 0> &verts, MutableSpan<float3> normals);
|
||||
|
||||
/** Gather data from an array aligned with all geometry vertices. */
|
||||
void gather_data_grids(const SubdivCCG &subdiv_ccg,
|
||||
Span<float3> src,
|
||||
Span<int> grids,
|
||||
MutableSpan<float3> node_data);
|
||||
void gather_data_vert_bmesh(Span<float3> src,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<float3> node_data);
|
||||
|
||||
/** Scatter data from an array of the node's data to the referenced geometry vertices. */
|
||||
void scatter_data_grids(const SubdivCCG &subdiv_ccg,
|
||||
Span<float3> node_data,
|
||||
Span<int> grids,
|
||||
MutableSpan<float3> dst);
|
||||
void scatter_data_vert_bmesh(Span<float3> node_data,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<float3> dst);
|
||||
|
||||
/**
|
||||
* Calculate initial influence factors based on vertex visibility.
|
||||
*/
|
||||
|
||||
@@ -3938,7 +3938,7 @@ static void do_brush_action(const Scene &scene,
|
||||
* 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);
|
||||
do_enhance_details_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
do_smooth_brush(sd, ob, nodes, std::clamp(ss.cache->bstrength, 0.0f, 1.0f));
|
||||
@@ -6724,6 +6724,62 @@ void gather_bmesh_normals(const Set<BMVert *, 0> &verts, const MutableSpan<float
|
||||
}
|
||||
}
|
||||
|
||||
void gather_data_grids(const SubdivCCG &subdiv_ccg,
|
||||
const Span<float3> src,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<float3> node_data)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
BLI_assert(grids.size() * key.grid_area == node_data.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int node_start = i * key.grid_area;
|
||||
const int grids_start = grids[i] * key.grid_area;
|
||||
node_data.slice(node_start, key.grid_area).copy_from(src.slice(grids_start, key.grid_area));
|
||||
}
|
||||
}
|
||||
|
||||
void gather_data_vert_bmesh(const Span<float3> src,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
const MutableSpan<float3> node_data)
|
||||
{
|
||||
BLI_assert(verts.size() == node_data.size());
|
||||
|
||||
int i = 0;
|
||||
for (const BMVert *vert : verts) {
|
||||
node_data[i] = src[BM_elem_index_get(vert)];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void scatter_data_grids(const SubdivCCG &subdiv_ccg,
|
||||
const Span<float3> node_data,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<float3> dst)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
BLI_assert(grids.size() * key.grid_area == node_data.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int node_start = i * key.grid_area;
|
||||
const int grids_start = grids[i] * key.grid_area;
|
||||
dst.slice(grids_start, key.grid_area).copy_from(node_data.slice(node_start, key.grid_area));
|
||||
}
|
||||
}
|
||||
|
||||
void scatter_data_vert_bmesh(const Span<float3> node_data,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
const MutableSpan<float3> dst)
|
||||
{
|
||||
BLI_assert(verts.size() == node_data.size());
|
||||
|
||||
int i = 0;
|
||||
for (const BMVert *vert : verts) {
|
||||
dst[BM_elem_index_get(vert)] = node_data[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void fill_factor_from_hide(const Mesh &mesh,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> r_factors)
|
||||
|
||||
@@ -1565,6 +1565,9 @@ float4 neighbor_color_average(SculptSession &ss,
|
||||
*/
|
||||
float3 neighbor_coords_average_interior(const SculptSession &ss, PBVHVertRef vertex);
|
||||
|
||||
void neighbor_position_average_grids(const SubdivCCG &subdiv_ccg,
|
||||
Span<int> grids,
|
||||
MutableSpan<float3> new_positions);
|
||||
void neighbor_position_average_interior_grids(OffsetIndices<int> faces,
|
||||
Span<int> corner_verts,
|
||||
BitSpan boundary_verts,
|
||||
@@ -1572,6 +1575,8 @@ void neighbor_position_average_interior_grids(OffsetIndices<int> faces,
|
||||
Span<int> grids,
|
||||
MutableSpan<float3> new_positions);
|
||||
|
||||
void neighbor_position_average_bmesh(const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<float3> new_positions);
|
||||
void neighbor_position_average_interior_bmesh(const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<float3> new_positions);
|
||||
|
||||
@@ -1580,7 +1585,6 @@ void neighbor_position_average_mesh(Span<float3> positions,
|
||||
Span<Vector<int>> vert_neighbors,
|
||||
MutableSpan<float3> new_positions);
|
||||
|
||||
void enhance_details_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
/* Surface Smooth Brush. */
|
||||
|
||||
|
||||
@@ -67,6 +67,40 @@ static float3 average_positions(const CCGKey &key,
|
||||
return result;
|
||||
}
|
||||
|
||||
void neighbor_position_average_grids(const SubdivCCG &subdiv_ccg,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<float3> new_positions)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
const Span<CCGElem *> elems = subdiv_ccg.grids;
|
||||
|
||||
BLI_assert(grids.size() * key.grid_area == new_positions.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int grid = grids[i];
|
||||
const int node_verts_start = i * key.grid_area;
|
||||
|
||||
/* TODO: This loop could be optimized in the future by skipping unnecessary logic for
|
||||
* non-boundary grid vertices. */
|
||||
for (const int y : IndexRange(key.grid_size)) {
|
||||
for (const int x : IndexRange(key.grid_size)) {
|
||||
const int offset = CCG_grid_xy_to_index(key.grid_size, x, y);
|
||||
const int node_vert_index = node_verts_start + offset;
|
||||
|
||||
SubdivCCGCoord coord{};
|
||||
coord.grid_index = grid;
|
||||
coord.x = x;
|
||||
coord.y = y;
|
||||
|
||||
SubdivCCGNeighbors neighbors;
|
||||
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, false, neighbors);
|
||||
|
||||
new_positions[node_vert_index] = average_positions(key, elems, neighbors.coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void neighbor_position_average_interior_grids(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const BitSpan boundary_verts,
|
||||
@@ -136,6 +170,21 @@ static float3 average_positions(const Span<const BMVert *> verts)
|
||||
return result;
|
||||
}
|
||||
|
||||
void neighbor_position_average_bmesh(const Set<BMVert *, 0> &verts,
|
||||
const MutableSpan<float3> new_positions)
|
||||
{
|
||||
BLI_assert(verts.size() == new_positions.size());
|
||||
Vector<BMVert *, 64> neighbor_data;
|
||||
|
||||
int i = 0;
|
||||
for (BMVert *vert : verts) {
|
||||
neighbor_data.clear();
|
||||
const Span<BMVert *> neighbors = vert_neighbors_get_bmesh(*vert, neighbor_data);
|
||||
new_positions[i] = average_positions(neighbors);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void neighbor_position_average_interior_bmesh(const Set<BMVert *, 0> &verts,
|
||||
const MutableSpan<float3> new_positions)
|
||||
{
|
||||
@@ -326,77 +375,6 @@ float4 neighbor_color_average(SculptSession &ss,
|
||||
faces, corner_verts, vert_to_face_map, color_attribute, color_domain, vert);
|
||||
}
|
||||
|
||||
static void do_enhance_details_brush_task(Object &ob,
|
||||
const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
PBVHNode *node)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
float bstrength = ss.cache->bstrength;
|
||||
CLAMP(bstrength, -1.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 disp[3];
|
||||
madd_v3_v3v3fl(disp, vd.co, ss.cache->detail_directions[vd.index], fade);
|
||||
SCULPT_clip(sd, ss, vd.co, disp);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
SCULPT_vertex_random_access_ensure(ss);
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
|
||||
if (SCULPT_stroke_is_first_brush_step(*ss.cache)) {
|
||||
const int totvert = SCULPT_vertex_count_get(ss);
|
||||
ss.cache->detail_directions.reinitialize(totvert);
|
||||
|
||||
for (int i = 0; i < totvert; i++) {
|
||||
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(*ss.pbvh, i);
|
||||
const float3 avg = neighbor_coords_average(ss, vertex);
|
||||
sub_v3_v3v3(ss.cache->detail_directions[i], avg, SCULPT_vertex_co_get(ss, vertex));
|
||||
}
|
||||
}
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
do_enhance_details_brush_task(ob, sd, brush, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* HC Smooth Algorithm. */
|
||||
/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user