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_strips.cc
|
||||||
brushes/clay_thumb.cc
|
brushes/clay_thumb.cc
|
||||||
brushes/crease.cc
|
brushes/crease.cc
|
||||||
|
brushes/draw_face_sets.cc
|
||||||
brushes/draw_vector_displacement.cc
|
brushes/draw_vector_displacement.cc
|
||||||
brushes/draw.cc
|
brushes/draw.cc
|
||||||
brushes/fill.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_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_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_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. */
|
/** A simple normal-direction displacement. */
|
||||||
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
void do_draw_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||||
/** A simple normal-direction displacement based on image texture RGB/XYZ values. */
|
/** A simple normal-direction displacement based on image texture RGB/XYZ values. */
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
struct BMesh;
|
struct BMesh;
|
||||||
struct BMVert;
|
struct BMVert;
|
||||||
|
struct BMFace;
|
||||||
struct Brush;
|
struct Brush;
|
||||||
struct Mesh;
|
struct Mesh;
|
||||||
struct Object;
|
struct Object;
|
||||||
@@ -115,6 +116,9 @@ void calc_front_face(const float3 &view_normal,
|
|||||||
void calc_front_face(const float3 &view_normal,
|
void calc_front_face(const float3 &view_normal,
|
||||||
const Set<BMVert *, 0> &verts,
|
const Set<BMVert *, 0> &verts,
|
||||||
const MutableSpan<float> factors);
|
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
|
* 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,
|
const Set<BMVert *, 0> &verts,
|
||||||
MutableSpan<float> factors);
|
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
|
} // namespace auto_mask
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3893,7 +3893,12 @@ static void do_brush_action(const Scene &scene,
|
|||||||
cloth::do_cloth_brush(sd, ob, nodes);
|
cloth::do_cloth_brush(sd, ob, nodes);
|
||||||
break;
|
break;
|
||||||
case SCULPT_TOOL_DRAW_FACE_SETS:
|
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;
|
break;
|
||||||
case SCULPT_TOOL_DISPLACEMENT_ERASER:
|
case SCULPT_TOOL_DISPLACEMENT_ERASER:
|
||||||
do_displacement_eraser_brush(sd, ob, nodes);
|
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,
|
void filter_region_clip_factors(const SculptSession &ss,
|
||||||
const Span<float3> positions,
|
const Span<float3> positions,
|
||||||
const Span<int> verts,
|
const Span<int> verts,
|
||||||
@@ -6892,6 +6911,8 @@ void filter_region_clip_factors(const SculptSession &ss,
|
|||||||
const Span<float3> positions,
|
const Span<float3> positions,
|
||||||
const MutableSpan<float> factors)
|
const MutableSpan<float> factors)
|
||||||
{
|
{
|
||||||
|
BLI_assert(positions.size() == factors.size());
|
||||||
|
|
||||||
const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d;
|
const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d;
|
||||||
const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d;
|
const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d;
|
||||||
if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "BLI_array.hh"
|
#include "BLI_array.hh"
|
||||||
#include "BLI_hash.h"
|
#include "BLI_hash.h"
|
||||||
#include "BLI_index_range.hh"
|
#include "BLI_index_range.hh"
|
||||||
|
#include "BLI_math_base.hh"
|
||||||
#include "BLI_math_base_safe.h"
|
#include "BLI_math_base_safe.h"
|
||||||
#include "BLI_math_vector.h"
|
#include "BLI_math_vector.h"
|
||||||
#include "BLI_math_vector_types.hh"
|
#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,
|
void calc_grids_factors(const Object &object,
|
||||||
const Cache &cache,
|
const Cache &cache,
|
||||||
const PBVHNode &node,
|
const PBVHNode &node,
|
||||||
|
|||||||
@@ -211,222 +211,6 @@ Array<int> duplicate_face_sets(const Mesh &mesh)
|
|||||||
/** \name Draw Face Sets Brush
|
/** \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,
|
static void do_relax_face_sets_brush_task(Object &ob,
|
||||||
const Brush &brush,
|
const Brush &brush,
|
||||||
const float strength,
|
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};
|
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);
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||||
|
|
||||||
BKE_curvemapping_init(brush.curve);
|
BKE_curvemapping_init(brush.curve);
|
||||||
|
|
||||||
if (ss.cache->alt_smooth) {
|
SCULPT_boundary_info_ensure(ob);
|
||||||
SCULPT_boundary_info_ensure(ob);
|
|
||||||
const SculptSession &ss = *ob.sculpt;
|
const SculptSession &ss = *ob.sculpt;
|
||||||
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
||||||
ss.cache->iteration_count);
|
ss.cache->iteration_count);
|
||||||
for (const float strength : strengths) {
|
for (const float strength : strengths) {
|
||||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
|
||||||
for (const int i : range) {
|
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||||
do_relax_face_sets_brush_task(ob, brush, strength, nodes[i]);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1968,7 +1968,7 @@ void multiplane_scrape_preview_draw(uint gpuattr,
|
|||||||
|
|
||||||
namespace face_set {
|
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