Fix #133267: Sculpt "Persistent Base" doesn't work for mesh and multires

These geometry types don't work properly with attributes currently,
so the persistent base isn't really "persistent" and doesn't last after
exiting sculpt mode, but it's still useful for it to work within a
sculpt session. Enable that by adding temporary array storage in
`SculptSession`. During the sculpt refactor project I mistakenly
assumed that this didn't work well enough that anyone would use it.

Pull Request: https://projects.blender.org/blender/blender/pulls/133410
This commit is contained in:
Hans Goudey
2025-01-22 22:33:13 +01:00
committed by Hans Goudey
parent 98addc0191
commit 0a2d5d5801
5 changed files with 148 additions and 51 deletions

View File

@@ -446,6 +446,12 @@ struct SculptSession : blender::NonCopyable, blender::NonMovable {
/* Boundary Brush Preview */
std::unique_ptr<SculptBoundaryPreview> boundary_preview;
/* "Persistent" positions and normals for multires. (For mesh the
* ".sculpt_persistent_co" attribute is used, etc.). */
blender::Array<blender::float3> sculpt_persistent_co;
blender::Array<blender::float3> sculpt_persistent_no;
blender::Array<float> sculpt_persistent_disp;
SculptVertexInfo vertex_info = {};
SculptFakeNeighbors fake_neighbors = {};

View File

@@ -2096,6 +2096,10 @@ void BKE_sculptsession_free_pbvh(Object &object)
ss->fake_neighbors.fake_neighbor_index = {};
ss->topology_island_cache.reset();
ss->sculpt_persistent_co = {};
ss->sculpt_persistent_no = {};
ss->sculpt_persistent_disp = {};
ss->clear_active_vert(false);
}

View File

@@ -32,6 +32,8 @@ namespace blender::ed::sculpt_paint {
inline namespace layer_cc {
struct LocalData {
Vector<float3> persistent_positions;
Vector<float3> persistent_normals;
Vector<float3> positions;
Vector<float> factors;
Vector<float> distances;
@@ -218,6 +220,9 @@ static void calc_grids(const Depsgraph &depsgraph,
const Sculpt &sd,
const Brush &brush,
Object &object,
const bool use_persistent_base,
const Span<float3> persistent_base_positions,
const Span<float3> persistent_base_normals,
bke::pbvh::GridsNode &node,
LocalData &tls,
MutableSpan<float> layer_displacement_factor)
@@ -250,33 +255,64 @@ static void calc_grids(const Depsgraph &depsgraph,
calc_brush_texture_factors(ss, brush, positions, factors);
if (subdiv_ccg.masks.is_empty()) {
tls.masks.clear();
}
else {
tls.masks.resize(positions.size());
gather_data_grids(subdiv_ccg, subdiv_ccg.masks.as_span(), grids, tls.masks.as_mutable_span());
}
const MutableSpan<float> masks = tls.masks;
const MutableSpan<float> displacement_factors = gather_data_grids(
subdiv_ccg, layer_displacement_factor.as_span(), grids, tls.displacement_factors);
offset_displacement_factors(displacement_factors, tls.factors, cache.bstrength);
if (!subdiv_ccg.masks.is_empty()) {
tls.masks.resize(positions.size());
mask::gather_mask_grids(subdiv_ccg, grids, tls.masks);
if (use_persistent_base) {
if (cache.invert) {
reset_displacement_factors(displacement_factors, tls.factors, cache.bstrength);
}
else {
offset_displacement_factors(displacement_factors, tls.factors, cache.bstrength);
}
clamp_displacement_factors(displacement_factors, masks);
scatter_data_grids(
subdiv_ccg, displacement_factors.as_span(), grids, layer_displacement_factor);
tls.translations.resize(positions.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations(
gather_data_grids(subdiv_ccg, persistent_base_positions, grids, tls.persistent_positions),
gather_data_grids(subdiv_ccg, persistent_base_normals, grids, tls.persistent_normals),
positions,
displacement_factors,
tls.factors,
brush.height,
translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, grids, subdiv_ccg);
}
else {
tls.masks.clear();
offset_displacement_factors(displacement_factors, tls.factors, cache.bstrength);
clamp_displacement_factors(displacement_factors, masks);
scatter_data_grids(
subdiv_ccg, displacement_factors.as_span(), grids, layer_displacement_factor);
tls.translations.resize(positions.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations(orig_data.positions,
orig_data.normals,
positions,
displacement_factors,
tls.factors,
brush.height,
translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, grids, subdiv_ccg);
}
clamp_displacement_factors(displacement_factors, tls.masks);
scatter_data_grids(subdiv_ccg, displacement_factors.as_span(), grids, layer_displacement_factor);
tls.translations.resize(positions.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations(orig_data.positions,
orig_data.normals,
positions,
displacement_factors,
tls.factors,
brush.height,
translations);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, grids, subdiv_ccg);
}
static void calc_bmesh(const Depsgraph &depsgraph,
@@ -414,14 +450,42 @@ void do_layer_brush(const Depsgraph &depsgraph,
case bke::pbvh::Type::Grids: {
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
MutableSpan<float3> positions = subdiv_ccg.positions;
if (ss.cache->layer_displacement_factor.is_empty()) {
ss.cache->layer_displacement_factor = Array<float>(positions.size(), 0.0f);
const Span<float3> persistent_position = ss.sculpt_persistent_co;
const Span<float3> persistent_normal = ss.sculpt_persistent_no;
bool use_persistent_base = false;
MutableSpan<float> displacement;
if (brush.flag & BRUSH_PERSISTENT) {
if (!persistent_position.is_empty() && !persistent_normal.is_empty()) {
if (ss.sculpt_persistent_disp.is_empty()) {
ss.sculpt_persistent_disp = Array<float>(positions.size(), 0.0f);
}
use_persistent_base = true;
displacement = ss.sculpt_persistent_disp;
}
}
const MutableSpan<float> displacement = ss.cache->layer_displacement_factor;
if (displacement.is_empty()) {
if (ss.cache->layer_displacement_factor.is_empty()) {
ss.cache->layer_displacement_factor = Array<float>(positions.size(), 0.0f);
}
displacement = ss.cache->layer_displacement_factor;
}
MutableSpan<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
node_mask.foreach_index(GrainSize(1), [&](const int i) {
LocalData &tls = all_tls.local();
calc_grids(depsgraph, sd, brush, object, nodes[i], tls, displacement);
calc_grids(depsgraph,
sd,
brush,
object,
use_persistent_base,
persistent_position,
persistent_normal,
nodes[i],
tls,
displacement);
bke::pbvh::update_node_bounds_grids(subdiv_ccg.grid_area, positions, nodes[i]);
});
break;

View File

@@ -571,6 +571,18 @@ void ensure_nodes_constraints(const Sculpt &sd,
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
Span<float3> init_positions;
Span<float3> persistent_position;
if (brush != nullptr && brush->flag & BRUSH_PERSISTENT) {
persistent_position = ss.sculpt_persistent_co;
}
if (persistent_position.is_empty()) {
init_positions = cloth_sim.init_pos;
}
else {
init_positions = persistent_position;
}
uninitialized_nodes.foreach_index([&](const int i) {
const Span<int> verts = calc_visible_vert_indices_grids(
key, grid_hidden, nodes[i].grids(), vert_indices);

View File

@@ -97,36 +97,47 @@ static int set_persistent_base_exec(bContext *C, wmOperator * /*op*/)
return OPERATOR_CANCELLED;
}
/* Only mesh geometry supports attributes properly. */
if (bke::object::pbvh_get(ob)->type() != bke::pbvh::Type::Mesh) {
return OPERATOR_CANCELLED;
}
BKE_sculpt_update_object_for_edit(depsgraph, &ob, false);
Mesh &mesh = *static_cast<Mesh *>(ob.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.remove(".sculpt_persistent_co");
attributes.remove(".sculpt_persistent_no");
attributes.remove(".sculpt_persistent_disp");
switch (bke::object::pbvh_get(ob)->type()) {
case bke::pbvh::Type::Mesh: {
Mesh &mesh = *static_cast<Mesh *>(ob.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
attributes.remove(".sculpt_persistent_co");
attributes.remove(".sculpt_persistent_no");
attributes.remove(".sculpt_persistent_disp");
const bke::AttributeReader positions = attributes.lookup<float3>("position");
if (positions.sharing_info && positions.varray.is_span()) {
attributes.add<float3>(".sculpt_persistent_co",
bke::AttrDomain::Point,
bke::AttributeInitShared(positions.varray.get_internal_span().data(),
*positions.sharing_info));
}
else {
attributes.add<float3>(".sculpt_persistent_co",
bke::AttrDomain::Point,
bke::AttributeInitVArray(positions.varray));
}
const bke::AttributeReader positions = attributes.lookup<float3>("position");
if (positions.sharing_info && positions.varray.is_span()) {
attributes.add<float3>(
".sculpt_persistent_co",
bke::AttrDomain::Point,
bke::AttributeInitShared(positions.varray.get_internal_span().data(),
*positions.sharing_info));
}
else {
attributes.add<float3>(".sculpt_persistent_co",
bke::AttrDomain::Point,
bke::AttributeInitVArray(positions.varray));
}
const Span<float3> vert_normals = bke::pbvh::vert_normals_eval(*depsgraph, ob);
attributes.add<float3>(".sculpt_persistent_no",
bke::AttrDomain::Point,
bke::AttributeInitVArray(VArray<float3>::ForSpan(vert_normals)));
const Span<float3> vert_normals = bke::pbvh::vert_normals_eval(*depsgraph, ob);
attributes.add<float3>(".sculpt_persistent_no",
bke::AttrDomain::Point,
bke::AttributeInitVArray(VArray<float3>::ForSpan(vert_normals)));
break;
}
case bke::pbvh::Type::Grids: {
const SubdivCCG &subdiv_ccg = *ss->subdiv_ccg;
ss->sculpt_persistent_co = subdiv_ccg.positions;
ss->sculpt_persistent_no = subdiv_ccg.normals;
ss->sculpt_persistent_disp = {};
break;
}
case bke::pbvh::Type::BMesh: {
return OPERATOR_CANCELLED;
}
}
return OPERATOR_FINISHED;
}