Sculpt: Initial data oriented refactor for draw face sets brush
Part of #118145. Performance after this change results in an 156% speedup. This is measured on the function `stroke_update_step` on a mesh with 24k verts across 20k instances of the function being timed. In absolute values, this represents a 0.09ms speedup, from 0.16ms to 0.25ms. For BMesh, the brush now takes mask values into consideration by averaging all of the vert mask values for a face. Co-authored-by: Hans Goudey <hans@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/123811
This commit is contained in:
@@ -118,6 +118,7 @@ set(SRC
|
||||
brushes/clay_strips.cc
|
||||
brushes/clay_thumb.cc
|
||||
brushes/crease.cc
|
||||
brushes/draw_face_sets.cc
|
||||
brushes/draw_vector_displacement.cc
|
||||
brushes/draw.cc
|
||||
brushes/fill.cc
|
||||
|
||||
457
source/blender/editors/sculpt_paint/brushes/draw_face_sets.cc
Normal file
457
source/blender/editors/sculpt_paint/brushes/draw_face_sets.cc
Normal file
@@ -0,0 +1,457 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "editors/sculpt_paint/brushes/types.hh"
|
||||
#include "editors/sculpt_paint/mesh_brush_common.hh"
|
||||
|
||||
#include "DNA_brush_types.h"
|
||||
|
||||
#include "BKE_mesh.hh"
|
||||
#include "BKE_pbvh.hh"
|
||||
#include "BKE_subdiv_ccg.hh"
|
||||
|
||||
#include "BLI_array_utils.hh"
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_task.h"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "editors/sculpt_paint/sculpt_intern.hh"
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
inline namespace draw_face_sets_cc {
|
||||
|
||||
constexpr float FACE_SET_BRUSH_MIN_FADE = 0.05f;
|
||||
|
||||
struct MeshLocalData {
|
||||
Vector<int> face_indices;
|
||||
Vector<float3> positions;
|
||||
Vector<float3> normals;
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
};
|
||||
|
||||
static void calc_face_centers(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<int> face_indices,
|
||||
const MutableSpan<float3> positions)
|
||||
{
|
||||
BLI_assert(face_indices.size() == positions.size());
|
||||
|
||||
for (const int i : face_indices.index_range()) {
|
||||
positions[i] = bke::mesh::face_center_calc(vert_positions,
|
||||
corner_verts.slice(faces[face_indices[i]]));
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_face_normals(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<int> face_indices,
|
||||
const MutableSpan<float3> normals)
|
||||
{
|
||||
BLI_assert(face_indices.size() == normals.size());
|
||||
|
||||
for (const int i : face_indices.index_range()) {
|
||||
normals[i] = bke::mesh::face_normal_calc(vert_positions,
|
||||
corner_verts.slice(faces[face_indices[i]]));
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void fill_factor_from_hide_and_mask(const Mesh &mesh,
|
||||
const Span<int> face_indices,
|
||||
const MutableSpan<float> r_factors)
|
||||
{
|
||||
BLI_assert(face_indices.size() == r_factors.size());
|
||||
|
||||
const OffsetIndices<int> faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
|
||||
/* TODO: Avoid overhead of accessing attributes for every PBVH node. */
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
if (const VArray mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point)) {
|
||||
const VArraySpan span(mask);
|
||||
for (const int i : face_indices.index_range()) {
|
||||
const Span<int> face_verts = corner_verts.slice(faces[face_indices[i]]);
|
||||
const float inv_size = math::rcp(float(face_verts.size()));
|
||||
float sum = 0.0f;
|
||||
for (const int vert : face_verts) {
|
||||
sum += span[vert];
|
||||
}
|
||||
r_factors[i] = 1.0f - sum * inv_size;
|
||||
}
|
||||
}
|
||||
else {
|
||||
r_factors.fill(1.0f);
|
||||
}
|
||||
|
||||
if (const VArray hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Point)) {
|
||||
const VArraySpan span(hide_poly);
|
||||
for (const int i : face_indices.index_range()) {
|
||||
if (span[face_indices[i]]) {
|
||||
r_factors[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_face_set(const int face_set_id,
|
||||
const Span<int> face_indices,
|
||||
const Span<float> factors,
|
||||
const MutableSpan<int> face_sets)
|
||||
{
|
||||
BLI_assert(face_indices.size() == factors.size());
|
||||
|
||||
for (const int i : face_indices.index_range()) {
|
||||
if (factors[i] > FACE_SET_BRUSH_MIN_FADE) {
|
||||
face_sets[face_indices[i]] = face_set_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_faces(Object &object,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
const int face_set_id,
|
||||
Span<float3> positions_eval,
|
||||
const PBVHNode &node,
|
||||
const Span<int> face_indices,
|
||||
MeshLocalData &tls,
|
||||
const MutableSpan<int> face_sets)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const OffsetIndices<int> faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
|
||||
tls.positions.reinitialize(face_indices.size());
|
||||
const MutableSpan<float3> face_centers = tls.positions;
|
||||
calc_face_centers(faces, corner_verts, positions_eval, face_indices, face_centers);
|
||||
|
||||
tls.normals.reinitialize(face_indices.size());
|
||||
const MutableSpan<float3> face_normals = tls.normals;
|
||||
calc_face_normals(faces, corner_verts, positions_eval, face_indices, face_normals);
|
||||
|
||||
tls.factors.reinitialize(face_indices.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
|
||||
fill_factor_from_hide_and_mask(mesh, face_indices, factors);
|
||||
|
||||
filter_region_clip_factors(ss, face_centers, factors);
|
||||
if (brush.flag & BRUSH_FRONTFACE) {
|
||||
calc_front_face(cache.view_normal, face_normals, factors);
|
||||
}
|
||||
|
||||
tls.distances.reinitialize(face_indices.size());
|
||||
const MutableSpan<float> distances = tls.distances;
|
||||
calc_brush_distances(ss, face_centers, 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) {
|
||||
const OffsetIndices<int> faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
auto_mask::calc_face_factors(
|
||||
object, faces, corner_verts, *cache.automasking, node, face_indices, factors);
|
||||
}
|
||||
|
||||
calc_brush_texture_factors(ss, brush, face_centers, factors);
|
||||
scale_factors(factors, strength);
|
||||
|
||||
apply_face_set(face_set_id, face_indices, factors, face_sets);
|
||||
}
|
||||
|
||||
static void do_draw_face_sets_brush_mesh(Object &object,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
const PBVH &pbvh = *ss.pbvh;
|
||||
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(pbvh);
|
||||
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const Span<int> corner_tris = mesh.corner_tri_faces();
|
||||
|
||||
bke::SpanAttributeWriter<int> attribute = face_set::ensure_face_sets_mesh(object);
|
||||
MutableSpan<int> face_sets = attribute.span;
|
||||
|
||||
threading::EnumerableThreadSpecific<MeshLocalData> all_tls;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
MeshLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
const Span<int> face_indices = bke::pbvh::node_face_indices_calc_mesh(
|
||||
corner_tris, *nodes[i], tls.face_indices);
|
||||
|
||||
undo::push_node(object, nodes[i], undo::Type::FaceSet);
|
||||
|
||||
calc_faces(object,
|
||||
brush,
|
||||
ss.cache->bstrength,
|
||||
ss.cache->paint_face_set,
|
||||
positions_eval,
|
||||
*nodes[i],
|
||||
face_indices,
|
||||
tls,
|
||||
face_sets);
|
||||
}
|
||||
});
|
||||
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
struct GridLocalData {
|
||||
Vector<int> face_indices;
|
||||
Vector<float3> positions;
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
};
|
||||
|
||||
BLI_NOINLINE static void calc_face_indices_grids(const SubdivCCG &subdiv_ccg,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<int> face_indices)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
BLI_assert(grids.size() * key.grid_area == face_indices.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int start = i * key.grid_area;
|
||||
for (const int offset : IndexRange(key.grid_area)) {
|
||||
face_indices[start + offset] = BKE_subdiv_ccg_grid_to_face_index(subdiv_ccg, grids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_grids(Object &object,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
const int face_set_id,
|
||||
const PBVHNode &node,
|
||||
GridLocalData &tls,
|
||||
const MutableSpan<int> face_sets)
|
||||
{
|
||||
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);
|
||||
MutableSpan<float3> positions = tls.positions;
|
||||
gather_grids_positions(subdiv_ccg, grids, positions);
|
||||
|
||||
tls.factors.reinitialize(grid_verts_num);
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
blender::ed::sculpt_paint::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.face_indices.reinitialize(grid_verts_num);
|
||||
MutableSpan<int> face_indices = tls.face_indices;
|
||||
|
||||
calc_face_indices_grids(subdiv_ccg, grids, face_indices);
|
||||
apply_face_set(face_set_id, face_indices, factors, face_sets);
|
||||
}
|
||||
|
||||
static void do_draw_face_sets_brush_grids(Object &object,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
bke::SpanAttributeWriter<int> attribute = face_set::ensure_face_sets_mesh(object);
|
||||
MutableSpan<int> face_sets = attribute.span;
|
||||
|
||||
threading::EnumerableThreadSpecific<GridLocalData> all_tls;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
GridLocalData &tls = all_tls.local();
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
for (const int i : range) {
|
||||
undo::push_node(object, node, undo::Type::FaceSet);
|
||||
|
||||
calc_grids(object,
|
||||
brush,
|
||||
ss.cache->bstrength,
|
||||
ss.cache->paint_face_set,
|
||||
*nodes[i],
|
||||
tls,
|
||||
face_sets);
|
||||
}
|
||||
}
|
||||
});
|
||||
attribute.finish();
|
||||
}
|
||||
struct BMeshLocalData {
|
||||
Vector<float3> positions;
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
};
|
||||
|
||||
BLI_NOINLINE static void fill_factor_from_hide_and_mask(const BMesh &bm,
|
||||
const Set<BMFace *, 0L> &faces,
|
||||
const MutableSpan<float> r_factors)
|
||||
{
|
||||
BLI_assert(faces.size() == r_factors.size());
|
||||
|
||||
/* TODO: Avoid overhead of accessing attributes for every PBVH node. */
|
||||
const int mask_offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
||||
int i = 0;
|
||||
for (BMFace *f : faces) {
|
||||
if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
|
||||
r_factors[i] = 0.0f;
|
||||
continue;
|
||||
}
|
||||
if (mask_offset == -1) {
|
||||
r_factors[i] = 1.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
const BMLoop *l_iter = f->l_first = BM_FACE_FIRST_LOOP(f);
|
||||
int total_verts = 0;
|
||||
float sum;
|
||||
do {
|
||||
BMVert *vert = l_iter->v;
|
||||
sum += BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
|
||||
total_verts++;
|
||||
} while ((l_iter = l_iter->next) != f->l_first);
|
||||
r_factors[i] = 1.0f - sum * math::rcp(float(total_verts));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_face_centers(const Set<BMFace *, 0L> &faces, const MutableSpan<float3> centers)
|
||||
{
|
||||
BLI_assert(faces.size() == centers.size());
|
||||
|
||||
int i = 0;
|
||||
for (const BMFace *f : faces) {
|
||||
float3 face_center;
|
||||
BM_face_calc_center_median(f, face_center);
|
||||
|
||||
centers[i] = face_center;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_face_set(const int face_set_id,
|
||||
const Set<BMFace *, 0> &faces,
|
||||
const MutableSpan<float> factors,
|
||||
const int cd_offset)
|
||||
{
|
||||
int i = 0;
|
||||
for (BMFace *face : faces) {
|
||||
if (factors[i] > FACE_SET_BRUSH_MIN_FADE) {
|
||||
BM_ELEM_CD_SET_INT(face, cd_offset, face_set_id);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_bmesh(Object &object,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
const int face_set_id,
|
||||
PBVHNode &node,
|
||||
BMeshLocalData &tls,
|
||||
const int cd_offset)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
|
||||
const Set<BMFace *, 0> &faces = BKE_pbvh_bmesh_node_faces(&node);
|
||||
tls.positions.reinitialize(faces.size());
|
||||
const MutableSpan<float3> positions = tls.positions;
|
||||
calc_face_centers(faces, positions);
|
||||
|
||||
tls.factors.reinitialize(faces.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(*ss.bm, faces, factors);
|
||||
filter_region_clip_factors(ss, positions, factors);
|
||||
if (brush.flag & BRUSH_FRONTFACE) {
|
||||
calc_front_face(cache.view_normal, faces, factors);
|
||||
}
|
||||
|
||||
tls.distances.reinitialize(faces.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);
|
||||
|
||||
/* Disable auto-masking code path which rely on an undo step to access original data.
|
||||
*
|
||||
* This is because the dynamic topology uses BMesh Log based undo system, which creates a
|
||||
* single node for the undo step, and its type could be different for the needs of the
|
||||
* brush undo and the original data access.
|
||||
*
|
||||
* For the brushes like Draw the ss.cache->automasking is set to nullptr at the first step
|
||||
* of the brush, as there is an explicit check there for the brushes which support dynamic
|
||||
* topology. Do it locally here for the Draw Face Set brush here, to mimic the behavior of
|
||||
* the other brushes but without marking the brush as supporting dynamic topology. */
|
||||
auto_mask::node_begin(object, nullptr, node);
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions, factors);
|
||||
scale_factors(factors, strength);
|
||||
|
||||
apply_face_set(face_set_id, faces, factors, cd_offset);
|
||||
}
|
||||
|
||||
static void do_draw_face_sets_brush_bmesh(Object &object,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const int cd_offset = face_set::ensure_face_sets_bmesh(object);
|
||||
|
||||
threading::EnumerableThreadSpecific<BMeshLocalData> all_tls;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
BMeshLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
undo::push_node(object, nodes[i], undo::Type::FaceSet);
|
||||
calc_bmesh(
|
||||
object, brush, ss.cache->bstrength, ss.cache->paint_face_set, *nodes[i], tls, cd_offset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace draw_face_sets_cc
|
||||
|
||||
void do_draw_face_sets_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
switch (BKE_pbvh_type(*ss.pbvh)) {
|
||||
case PBVH_FACES:
|
||||
do_draw_face_sets_brush_mesh(object, brush, nodes);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
do_draw_face_sets_brush_grids(object, brush, nodes);
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
do_draw_face_sets_brush_bmesh(object, brush, nodes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -20,6 +20,7 @@ void do_crease_brush(const Scene &scene, const Sculpt &sd, Object &ob, Span<PBVH
|
||||
void do_blob_brush(const Scene &scene, const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
void do_displacement_eraser_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
void do_displacement_smear_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
void do_draw_face_sets_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
/** A simple normal-direction displacement. */
|
||||
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
/** A simple normal-direction displacement based on image texture RGB/XYZ values. */
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
struct BMesh;
|
||||
struct BMVert;
|
||||
struct BMFace;
|
||||
struct Brush;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
@@ -115,6 +116,9 @@ void calc_front_face(const float3 &view_normal,
|
||||
void calc_front_face(const float3 &view_normal,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
const MutableSpan<float> factors);
|
||||
void calc_front_face(const float3 &view_normal,
|
||||
const Set<BMFace *, 0> &faces,
|
||||
const MutableSpan<float> factors);
|
||||
|
||||
/**
|
||||
* When the 3D view's clipping planes are enabled, brushes shouldn't have any effect on vertices
|
||||
@@ -217,6 +221,17 @@ void calc_vert_factors(const Object &object,
|
||||
const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
/**
|
||||
* Calculate all auto-masking influence on each face.
|
||||
*/
|
||||
void calc_face_factors(const Object &object,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Cache &cache,
|
||||
const PBVHNode &node,
|
||||
const Span<int> face_indices,
|
||||
const MutableSpan<float> factors);
|
||||
|
||||
} // namespace auto_mask
|
||||
|
||||
/**
|
||||
|
||||
@@ -3893,7 +3893,12 @@ static void do_brush_action(const Scene &scene,
|
||||
cloth::do_cloth_brush(sd, ob, nodes);
|
||||
break;
|
||||
case SCULPT_TOOL_DRAW_FACE_SETS:
|
||||
face_set::do_draw_face_sets_brush(sd, ob, nodes);
|
||||
if (!ss.cache->alt_smooth) {
|
||||
do_draw_face_sets_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
face_set::do_relax_face_sets_brush(sd, ob, nodes);
|
||||
}
|
||||
break;
|
||||
case SCULPT_TOOL_DISPLACEMENT_ERASER:
|
||||
do_displacement_eraser_brush(sd, ob, nodes);
|
||||
@@ -6859,6 +6864,20 @@ void calc_front_face(const float3 &view_normal,
|
||||
}
|
||||
}
|
||||
|
||||
void calc_front_face(const float3 &view_normal,
|
||||
const Set<BMFace *, 0> &faces,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(faces.size() == factors.size());
|
||||
|
||||
int i = 0;
|
||||
for (const BMFace *face : faces) {
|
||||
const float dot = math::dot(view_normal, float3(face->no));
|
||||
factors[i] *= std::max(dot, 0.0f);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void filter_region_clip_factors(const SculptSession &ss,
|
||||
const Span<float3> positions,
|
||||
const Span<int> verts,
|
||||
@@ -6892,6 +6911,8 @@ void filter_region_clip_factors(const SculptSession &ss,
|
||||
const Span<float3> positions,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(positions.size() == factors.size());
|
||||
|
||||
const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d;
|
||||
const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d;
|
||||
if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_hash.h"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_base_safe.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
@@ -613,6 +614,34 @@ void calc_vert_factors(const Object &object,
|
||||
}
|
||||
}
|
||||
|
||||
void calc_face_factors(const Object &object,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const Cache &cache,
|
||||
const PBVHNode &node,
|
||||
const Span<int> face_indices,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
NodeData data = node_begin(object, &cache, node);
|
||||
/* NOTE: We explicitly nullify data.orig_data here as we currently cannot go from mesh vert index
|
||||
* to the undo node array index. The only brush this method is currently used for is the Draw
|
||||
* Face Set brush, which never modifies the position of the vertices in a brush stroke. This
|
||||
* needs to be implemented in the future if brushes that iterate over faces need original
|
||||
* position and normal data. */
|
||||
data.orig_data = std::nullopt;
|
||||
|
||||
for (const int i : face_indices.index_range()) {
|
||||
const Span<int> face_verts = corner_verts.slice(faces[face_indices[i]]);
|
||||
float sum = 0.0f;
|
||||
for (const int vert : face_verts) {
|
||||
sum += factor_get(&cache, ss, BKE_pbvh_make_vref(vert), &data);
|
||||
}
|
||||
factors[i] *= sum * math::rcp(float(face_verts.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void calc_grids_factors(const Object &object,
|
||||
const Cache &cache,
|
||||
const PBVHNode &node,
|
||||
|
||||
@@ -211,222 +211,6 @@ Array<int> duplicate_face_sets(const Mesh &mesh)
|
||||
/** \name Draw Face Sets Brush
|
||||
* \{ */
|
||||
|
||||
constexpr float FACE_SET_BRUSH_MIN_FADE = 0.05f;
|
||||
|
||||
static void do_draw_face_sets_brush_faces(Object &ob,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const Span<float3> positions = SCULPT_mesh_deformed_positions_get(ss);
|
||||
|
||||
Mesh &mesh = *static_cast<Mesh *>(ob.data);
|
||||
const OffsetIndices<int> faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
const VArraySpan<bool> hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
||||
|
||||
bke::SpanAttributeWriter<int> attribute = ensure_face_sets_mesh(ob);
|
||||
MutableSpan<int> face_sets = attribute.span;
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
const float bstrength = ss.cache->bstrength;
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
ob, ss.cache->automasking.get(), *node);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (*ss.pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
||||
auto_mask::node_update(automask_data, vd);
|
||||
|
||||
for (const int face_i : ss.vert_to_face_map[vd.index]) {
|
||||
const IndexRange face = faces[face_i];
|
||||
const float3 center = bke::mesh::face_center_calc(positions, corner_verts.slice(face));
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(test, center)) {
|
||||
continue;
|
||||
}
|
||||
if (!hide_poly.is_empty() && hide_poly[face_i]) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
|
||||
if (fade > FACE_SET_BRUSH_MIN_FADE) {
|
||||
face_sets[face_i] = ss.cache->paint_face_set;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
|
||||
if (changed) {
|
||||
undo::push_node(ob, node, undo::Type::FaceSet);
|
||||
}
|
||||
}
|
||||
});
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
static void do_draw_face_sets_brush_grids(Object &ob,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const float bstrength = ss.cache->bstrength;
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
|
||||
bke::SpanAttributeWriter<int> attribute = ensure_face_sets_mesh(ob);
|
||||
MutableSpan<int> face_sets = attribute.span;
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
ob, ss.cache->automasking.get(), *node);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
BKE_pbvh_vertex_iter_begin (*ss.pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
||||
auto_mask::node_update(automask_data, vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
|
||||
if (fade > FACE_SET_BRUSH_MIN_FADE) {
|
||||
const int face_index = BKE_subdiv_ccg_grid_to_face_index(subdiv_ccg,
|
||||
vd.grid_indices[vd.g]);
|
||||
face_sets[face_index] = ss.cache->paint_face_set;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
|
||||
if (changed) {
|
||||
undo::push_node(ob, node, undo::Type::FaceSet);
|
||||
}
|
||||
}
|
||||
});
|
||||
attribute.finish();
|
||||
}
|
||||
|
||||
static void do_draw_face_sets_brush_bmesh(Object &ob,
|
||||
const Brush &brush,
|
||||
const Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const float bstrength = ss.cache->bstrength;
|
||||
const int cd_offset = ensure_face_sets_bmesh(ob);
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
for (PBVHNode *node : nodes.slice(range)) {
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
|
||||
/* Disable auto-masking code path which rely on an undo step to access original data.
|
||||
*
|
||||
* This is because the dynamic topology uses BMesh Log based undo system, which creates a
|
||||
* single node for the undo step, and its type could be different for the needs of the brush
|
||||
* undo and the original data access.
|
||||
*
|
||||
* For the brushes like Draw the ss.cache->automasking is set to nullptr at the first step
|
||||
* of the brush, as there is an explicit check there for the brushes which support dynamic
|
||||
* topology. Do it locally here for the Draw Face Set brush here, to mimic the behavior of
|
||||
* the other brushes but without marking the brush as supporting dynamic topology. */
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(ob, nullptr, *node);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) {
|
||||
if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float3 face_center;
|
||||
BM_face_calc_center_median(f, face_center);
|
||||
|
||||
const BMLoop *l_iter = f->l_first = BM_FACE_FIRST_LOOP(f);
|
||||
do {
|
||||
if (!sculpt_brush_test_sq_fn(test, l_iter->v->co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BMVert *vert = l_iter->v;
|
||||
|
||||
/* There is no need to update the automasking data as it is disabled above. Additionally,
|
||||
* there is no access to the PBVHVertexIter as iteration happens over faces.
|
||||
*
|
||||
* The full auto-masking support would be very good to be implemented here, so keeping
|
||||
* the typical code flow for it here for the reference, and ease of looking at what needs
|
||||
* to be done for such integration.
|
||||
*
|
||||
* auto_mask::node_update(automask_data, vd); */
|
||||
|
||||
const float fade = bstrength *
|
||||
SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
face_center,
|
||||
sqrtf(test.dist),
|
||||
f->no,
|
||||
f->no,
|
||||
0.0f,
|
||||
BKE_pbvh_make_vref(intptr_t(vert)),
|
||||
thread_id,
|
||||
&automask_data);
|
||||
|
||||
if (fade <= FACE_SET_BRUSH_MIN_FADE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int &fset = *static_cast<int *>(POINTER_OFFSET(f->head.data, cd_offset));
|
||||
fset = ss.cache->paint_face_set;
|
||||
changed = true;
|
||||
break;
|
||||
|
||||
} while ((l_iter = l_iter->next) != f->l_first);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
undo::push_node(ob, node, undo::Type::FaceSet);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void do_relax_face_sets_brush_task(Object &ob,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
@@ -483,38 +267,24 @@ static std::array<float, 4> iteration_strengths(const float strength, const int
|
||||
return {modified_strength, modified_strength, strength, strength};
|
||||
}
|
||||
|
||||
void do_draw_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
BKE_curvemapping_init(brush.curve);
|
||||
|
||||
if (ss.cache->alt_smooth) {
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
const SculptSession &ss = *ob.sculpt;
|
||||
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
||||
ss.cache->iteration_count);
|
||||
for (const float strength : strengths) {
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
do_relax_face_sets_brush_task(ob, brush, strength, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (BKE_pbvh_type(*ss.pbvh)) {
|
||||
case PBVH_FACES:
|
||||
do_draw_face_sets_brush_faces(ob, brush, nodes);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
do_draw_face_sets_brush_grids(ob, brush, nodes);
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
do_draw_face_sets_brush_bmesh(ob, brush, nodes);
|
||||
break;
|
||||
}
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
|
||||
const SculptSession &ss = *ob.sculpt;
|
||||
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
||||
ss.cache->iteration_count);
|
||||
for (const float strength : strengths) {
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
do_relax_face_sets_brush_task(ob, brush, strength, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1968,7 +1968,7 @@ void multiplane_scrape_preview_draw(uint gpuattr,
|
||||
|
||||
namespace face_set {
|
||||
|
||||
void do_draw_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user