Sculpt: Data oriented refactor for clay thumb brush

Part of #118145.
Since the initial commit (015d5eda88) there has been a logic
mistake in the code for this brush where a variable was unused. The code
has worked for years in that state, so I left it in. The next commit
will clean up the unused code.
This commit is contained in:
Hans Goudey
2024-07-02 09:09:04 -04:00
parent ee73b4f978
commit 93f38eaa9d
8 changed files with 388 additions and 172 deletions

View File

@@ -116,6 +116,7 @@ set(SRC
brushes/clay.cc
brushes/clay_strips.cc
brushes/clay_thumb.cc
brushes/crease.cc
brushes/draw_vector_displacement.cc
brushes/draw.cc

View File

@@ -0,0 +1,366 @@
/* 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_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_task.h"
#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 clay_thumb_cc {
struct LocalData {
Vector<float3> positions;
Vector<float3> local_positions;
Vector<float> factors;
Vector<float> distances;
Vector<float3> translations;
};
BLI_NOINLINE static void calc_translations(const Span<float3> positions,
const Span<float3> local_positions,
const float4 &plane,
const float4 &plane_tilt,
const MutableSpan<float3> translations)
{
for (const int i : positions.index_range()) {
float3 closest;
closest_to_plane_normalized_v3(closest, plane, positions[i]);
float3 closest_tilt;
closest_to_plane_normalized_v3(closest_tilt, plane_tilt, positions[i]);
const float tilt_mix = local_positions[i].y > 0.0f ? 0.0f : 1.0f;
const float3 mixed = math::interpolate(closest, closest_tilt, tilt_mix);
/* NOTE: This has always been unused since the initial implementation of the tool. */
UNUSED_VARS(mixed);
translations[i] = closest_tilt - positions[i];
}
}
static void calc_faces(const Sculpt &sd,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float4 &plane_tilt,
const float strength,
const Span<float3> positions_eval,
const Span<float3> vert_normals,
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.positions.reinitialize(verts.size());
MutableSpan<float3> positions = tls.positions;
array_utils::gather(positions_eval, verts, positions);
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, factors);
if (brush.flag & BRUSH_FRONTFACE) {
calc_front_face(cache.view_normal, vert_normals, verts, factors);
}
tls.distances.reinitialize(verts.size());
const MutableSpan<float> distances = tls.distances;
calc_distance_falloff(
ss, positions, eBrushFalloffShape(brush.falloff_shape), 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.local_positions.reinitialize(verts.size());
MutableSpan<float3> local_positions = tls.local_positions;
transform_positions(positions, mat, local_positions);
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations(positions, local_positions, plane, plane_tilt, translations);
scale_translations(translations, factors);
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
}
static void calc_grids(const Sculpt &sd,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float4 &plane_tilt,
const float strength,
const PBVHNode &node,
Object &object,
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);
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_distance_falloff(
ss, positions, eBrushFalloffShape(brush.falloff_shape), 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.local_positions.reinitialize(grid_verts_num);
MutableSpan<float3> local_positions = tls.local_positions;
transform_positions(positions, mat, local_positions);
tls.translations.reinitialize(grid_verts_num);
const MutableSpan<float3> translations = tls.translations;
calc_translations(positions, local_positions, plane, plane_tilt, 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,
const Brush &brush,
const float4x4 &mat,
const float4 &plane,
const float4 &plane_tilt,
const float strength,
Object &object,
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());
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_distance_falloff(
ss, positions, eBrushFalloffShape(brush.falloff_shape), 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.local_positions.reinitialize(verts.size());
MutableSpan<float3> local_positions = tls.local_positions;
transform_positions(positions, mat, local_positions);
tls.translations.reinitialize(verts.size());
const MutableSpan<float3> translations = tls.translations;
calc_translations(positions, local_positions, plane, plane_tilt, translations);
scale_translations(translations, factors);
clip_and_lock_translations(sd, ss, positions, translations);
apply_translations(translations, verts);
}
} // namespace clay_thumb_cc
void do_clay_thumb_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
{
const SculptSession &ss = *object.sculpt;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
const float3 &location = ss.cache->location;
/* Sampled geometry normal and area center. */
float3 area_no_sp;
float3 area_no;
float3 area_co_tmp;
float4x4 tmat;
calc_brush_plane(brush, object, nodes, area_no_sp, area_co_tmp);
if (brush.sculpt_plane != SCULPT_DISP_DIR_AREA || (brush.flag & BRUSH_ORIGINAL_NORMAL)) {
area_no = calc_area_normal(brush, object, nodes).value_or(float3(0));
}
else {
area_no = area_no_sp;
}
/* Delay the first daub because grab delta is not setup. */
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
ss.cache->clay_thumb_front_angle = 0.0f;
return;
}
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to the
* stroke. */
if (SCULPT_stroke_is_main_symmetry_pass(*ss.cache)) {
ss.cache->clay_thumb_front_angle += 0.8f;
ss.cache->clay_thumb_front_angle = clamp_f(ss.cache->clay_thumb_front_angle, 0.0f, 60.0f);
}
if (math::is_zero(ss.cache->grab_delta_symmetry)) {
return;
}
/* Initialize brush local-space matrix. */
float4x4 mat = float4x4::identity();
mat.x_axis() = math::cross(area_no, ss.cache->grab_delta_symmetry);
mat.y_axis() = math::cross(area_no, mat.x_axis());
mat.z_axis() = area_no;
mat.location() = ss.cache->location;
normalize_m4(mat.ptr());
/* Scale brush local space matrix. */
float4x4 scale = math::from_scale<float4x4>(float3(ss.cache->radius));
mul_m4_m4m4(tmat.ptr(), mat.ptr(), scale.ptr());
invert_m4_m4(mat.ptr(), tmat.ptr());
float clay_strength = ss.cache->bstrength * clay_thumb_get_stabilized_pressure(*ss.cache);
float4 plane_tilt;
float3 normal_tilt;
float4x4 imat;
invert_m4_m4(imat.ptr(), mat.ptr());
rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss.cache->clay_thumb_front_angle));
/* Plane aligned to the geometry normal (back part of the brush). */
float4 plane;
plane_from_point_normal_v3(plane, location, area_no_sp);
/* Tilted plane (front part of the brush). */
plane_from_point_normal_v3(plane_tilt, location, normal_tilt);
threading::EnumerableThreadSpecific<LocalData> all_tls;
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
case PBVH_FACES: {
Mesh &mesh = *static_cast<Mesh *>(object.data);
const PBVH &pbvh = *ss.pbvh;
const Span<float3> positions_eval = BKE_pbvh_get_vert_positions(pbvh);
const Span<float3> vert_normals = BKE_pbvh_get_vert_normals(pbvh);
MutableSpan<float3> positions_orig = mesh.vert_positions_for_write();
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
LocalData &tls = all_tls.local();
for (const int i : range) {
calc_faces(sd,
brush,
mat,
plane,
plane_tilt,
clay_strength,
positions_eval,
vert_normals,
*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, brush, mat, plane, plane_tilt, clay_strength, *nodes[i], object, 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, brush, mat, plane, plane_tilt, clay_strength, object, *nodes[i], tls);
}
});
break;
}
}
float clay_thumb_get_stabilized_pressure(const blender::ed::sculpt_paint::StrokeCache &cache)
{
float final_pressure = 0.0f;
for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) {
final_pressure += cache.clay_pressure_stabilizer[i];
}
return final_pressure / SCULPT_CLAY_STABILIZER_LEN;
}
} // namespace blender::ed::sculpt_paint

View File

@@ -64,17 +64,6 @@ static ScrapeSampleData join_samples(const ScrapeSampleData &a, const ScrapeSamp
return joined;
}
BLI_NOINLINE static void transform_positions(const Span<float3> src,
const float4x4 &transform,
const MutableSpan<float3> dst)
{
BLI_assert(src.size() == dst.size());
for (const int i : src.index_range()) {
dst[i] = math::transform_point(transform, src[i]);
}
}
BLI_NOINLINE static void filter_plane_side_factors(const Span<float3> positions,
const Span<float3> local_positions,
const std::array<float4, 2> &scrape_planes,

View File

@@ -15,6 +15,7 @@ namespace blender::ed::sculpt_paint {
void do_clay_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_clay_strips_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_clay_thumb_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
void do_crease_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);

View File

@@ -54,6 +54,7 @@ void scale_factors(MutableSpan<float> factors, float strength);
void translations_from_offset_and_factors(const float3 &offset,
Span<float> factors,
MutableSpan<float3> r_translations);
void transform_positions(Span<float3> src, const float4x4 &transform, MutableSpan<float3> dst);
/**
* Note on the various positions arrays:

View File

@@ -3846,7 +3846,7 @@ static void do_brush_action(const Scene &scene,
do_multiplane_scrape_brush(sd, ob, nodes);
break;
case SCULPT_TOOL_CLAY_THUMB:
SCULPT_do_clay_thumb_brush(sd, ob, nodes);
do_clay_thumb_brush(sd, ob, nodes);
break;
case SCULPT_TOOL_FILL:
if (invert && brush.flag & BRUSH_INVERT_TO_SCRAPE_FILL) {
@@ -4768,7 +4768,7 @@ static float sculpt_brush_dynamic_size_get(const Brush &brush,
case SCULPT_TOOL_CLAY_STRIPS:
return max_ff(initial_size * 0.30f, initial_size * powf(cache.pressure, 1.5f));
case SCULPT_TOOL_CLAY_THUMB: {
float clay_stabilized_pressure = SCULPT_clay_thumb_get_stabilized_pressure(cache);
float clay_stabilized_pressure = clay_thumb_get_stabilized_pressure(cache);
return initial_size * clay_stabilized_pressure;
}
default:
@@ -7356,6 +7356,7 @@ void scale_factors(const MutableSpan<float> factors, const float strength)
factor *= strength;
}
}
void translations_from_offset_and_factors(const float3 &offset,
const Span<float> factors,
const MutableSpan<float3> r_translations)
@@ -7367,6 +7368,17 @@ void translations_from_offset_and_factors(const float3 &offset,
}
}
void transform_positions(const Span<float3> src,
const float4x4 &transform,
const MutableSpan<float3> dst)
{
BLI_assert(src.size() == dst.size());
for (const int i : src.index_range()) {
dst[i] = math::transform_point(transform, src[i]);
}
}
OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes, Array<int> &node_data)
{
node_data.reinitialize(nodes.size() + 1);

View File

@@ -137,162 +137,6 @@ void sculpt_project_v3_normal_align(const SculptSession &ss,
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sculpt Clay Thumb Brush
* \{ */
static void do_clay_thumb_brush_task(Object &ob,
const Brush &brush,
float (*mat)[4],
const float *area_no_sp,
const float *area_co,
const float bstrength,
PBVHNode *node)
{
using namespace blender::ed::sculpt_paint;
SculptSession &ss = *ob.sculpt;
PBVHVertexIter vd;
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, *node).co;
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);
float plane_tilt[4];
float normal_tilt[3];
float imat[4][4];
invert_m4_m4(imat, mat);
rotate_v3_v3v3fl(normal_tilt, area_no_sp, imat[0], DEG2RADF(-ss.cache->clay_thumb_front_angle));
/* Plane aligned to the geometry normal (back part of the brush). */
plane_from_point_normal_v3(test.plane_tool, area_co, area_no_sp);
/* Tilted plane (front part of the brush). */
plane_from_point_normal_v3(plane_tilt, area_co, normal_tilt);
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;
}
float local_co[3];
mul_v3_m4v3(local_co, mat, vd.co);
float intr[3], intr_tilt[3];
float val[3];
closest_to_plane_normalized_v3(intr, test.plane_tool, vd.co);
closest_to_plane_normalized_v3(intr_tilt, plane_tilt, vd.co);
/* Mix the deformation of the aligned and the tilted plane based on the brush space vertex
* coordinates. */
/* We can also control the mix with a curve if it produces noticeable artifacts in the center
* of the brush. */
const float tilt_mix = local_co[1] > 0.0f ? 0.0f : 1.0f;
interp_v3_v3v3(intr, intr, intr_tilt, tilt_mix);
sub_v3_v3v3(val, intr_tilt, vd.co);
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);
mul_v3_v3fl(proxy[vd.i], val, fade);
}
BKE_pbvh_vertex_iter_end;
}
float SCULPT_clay_thumb_get_stabilized_pressure(
const blender::ed::sculpt_paint::StrokeCache &cache)
{
float final_pressure = 0.0f;
for (int i = 0; i < SCULPT_CLAY_STABILIZER_LEN; i++) {
final_pressure += cache.clay_pressure_stabilizer[i];
}
return final_pressure / SCULPT_CLAY_STABILIZER_LEN;
}
void SCULPT_do_clay_thumb_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
{
using namespace blender;
using namespace blender::ed::sculpt_paint;
SculptSession &ss = *ob.sculpt;
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
/* Sampled geometry normal and area center. */
float3 area_no_sp;
float3 area_no;
float3 area_co_tmp;
float mat[4][4];
float scale[4][4];
float tmat[4][4];
calc_brush_plane(brush, ob, nodes, area_no_sp, area_co_tmp);
if (brush.sculpt_plane != SCULPT_DISP_DIR_AREA || (brush.flag & BRUSH_ORIGINAL_NORMAL)) {
area_no = calc_area_normal(brush, ob, nodes).value_or(float3(0));
}
else {
area_no = area_no_sp;
}
/* Delay the first daub because grab delta is not setup. */
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
ss.cache->clay_thumb_front_angle = 0.0f;
return;
}
/* Simulate the clay accumulation by increasing the plane angle as more samples are added to the
* stroke. */
if (SCULPT_stroke_is_main_symmetry_pass(*ss.cache)) {
ss.cache->clay_thumb_front_angle += 0.8f;
ss.cache->clay_thumb_front_angle = clamp_f(ss.cache->clay_thumb_front_angle, 0.0f, 60.0f);
}
if (is_zero_v3(ss.cache->grab_delta_symmetry)) {
return;
}
/* Initialize brush local-space matrix. */
cross_v3_v3v3(mat[0], area_no, ss.cache->grab_delta_symmetry);
mat[0][3] = 0.0f;
cross_v3_v3v3(mat[1], area_no, mat[0]);
mat[1][3] = 0.0f;
copy_v3_v3(mat[2], area_no);
mat[2][3] = 0.0f;
copy_v3_v3(mat[3], ss.cache->location);
mat[3][3] = 1.0f;
normalize_m4(mat);
/* Scale brush local space matrix. */
scale_m4_fl(scale, ss.cache->radius);
mul_m4_m4m4(tmat, mat, scale);
invert_m4_m4(mat, tmat);
float clay_strength = ss.cache->bstrength * SCULPT_clay_thumb_get_stabilized_pressure(*ss.cache);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
do_clay_thumb_brush_task(
ob, brush, mat, area_no_sp, ss.cache->location, clay_strength, nodes[i]);
}
});
}
/** \} */
static void do_snake_hook_brush_task(Object &ob,
const Brush &brush,
SculptProjectVector *spvc,

View File

@@ -2036,10 +2036,12 @@ void SCULPT_do_paint_brush_image(PaintModeSettings &paint_mode_settings,
blender::Span<PBVHNode *> texnodes);
bool SCULPT_use_image_paint_brush(PaintModeSettings &settings, Object &ob);
float SCULPT_clay_thumb_get_stabilized_pressure(
const blender::ed::sculpt_paint::StrokeCache &cache);
namespace blender::ed::sculpt_paint {
float clay_thumb_get_stabilized_pressure(const blender::ed::sculpt_paint::StrokeCache &cache);
}
void SCULPT_do_clay_thumb_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_snake_hook_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_rotate_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);
void SCULPT_do_layer_brush(const Sculpt &sd, Object &ob, blender::Span<PBVHNode *> nodes);