Sculpt: Start data-oriented refactor for draw brush
This PR establishes the beginning of the transition to data-oriented code for sculpt brushes described in #118145. The final brush "API" design is still in progress, and further iteration will be required as more brushes are refactored and other areas can be cleaned up. Currently the main goal is making the code paths more obvious and easing future development, but this change itself may also give a performance improvement. In a simple test with a large mesh, that was about 14%. Co-authored-by: Sergey Sharybin <sergey@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/121835
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set(INC
|
||||
../..
|
||||
|
||||
../include
|
||||
../uvedit
|
||||
../../blenkernel
|
||||
@@ -108,9 +110,15 @@ set(SRC
|
||||
|
||||
curves_sculpt_intern.hh
|
||||
grease_pencil_intern.hh
|
||||
mesh_brush_common.hh
|
||||
grease_pencil_weight_paint.hh
|
||||
paint_intern.hh
|
||||
sculpt_intern.hh
|
||||
|
||||
brushes/draw.cc
|
||||
brushes/draw_vector_displacement.cc
|
||||
|
||||
brushes/types.hh
|
||||
)
|
||||
|
||||
set(LIB
|
||||
|
||||
241
source/blender/editors/sculpt_paint/brushes/draw.cc
Normal file
241
source/blender/editors/sculpt_paint/brushes/draw.cc
Normal file
@@ -0,0 +1,241 @@
|
||||
/* 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_enumerable_thread_specific.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#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 draw_cc {
|
||||
|
||||
struct LocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<float3> translations;
|
||||
};
|
||||
|
||||
static void calc_faces(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
const float3 &offset,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<float3> vert_normals,
|
||||
const PBVHNode &node,
|
||||
Object &object,
|
||||
LocalData &tls,
|
||||
const MutableSpan<float3> positions_sculpt,
|
||||
const MutableSpan<float3> positions_mesh)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
StrokeCache &cache = *ss.cache;
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(node);
|
||||
|
||||
tls.factors.reinitialize(verts.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(mesh, verts, factors);
|
||||
|
||||
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_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors);
|
||||
calc_brush_strength_factors(ss, brush, verts, distances, factors);
|
||||
|
||||
if (ss.cache->automasking) {
|
||||
auto_mask::calc_vert_factors(object, *ss.cache->automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions_eval, verts, factors);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
for (const int i : verts.index_range()) {
|
||||
translations[i] = offset * factors[i];
|
||||
}
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions_eval, verts, translations);
|
||||
|
||||
if (!ss.deform_imats.is_empty()) {
|
||||
apply_crazyspace_to_translations(ss.deform_imats, verts, translations);
|
||||
}
|
||||
|
||||
apply_translations(translations, verts, positions_sculpt);
|
||||
flush_positions_to_shape_keys(object, verts, positions_sculpt, positions_mesh);
|
||||
}
|
||||
|
||||
static void calc_grids(Object &object, const Brush &brush, const float3 &offset, PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const CCGKey key = *BKE_pbvh_get_grid_key(*ss.pbvh);
|
||||
const Span<CCGElem *> grids = subdiv_ccg.grids;
|
||||
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
||||
|
||||
/* TODO: Remove usage of proxies. */
|
||||
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
|
||||
int i = 0;
|
||||
for (const int grid : bke::pbvh::node_grid_indices(node)) {
|
||||
const int grid_verts_start = grid * key.grid_area;
|
||||
CCGElem *elem = grids[grid];
|
||||
for (const int j : IndexRange(key.grid_area)) {
|
||||
if (!grid_hidden.is_empty() && grid_hidden[grid][j]) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, CCG_elem_offset_co(key, elem, j))) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
auto_mask::node_update(automask_data, i);
|
||||
const float fade = SCULPT_brush_strength_factor(
|
||||
ss,
|
||||
brush,
|
||||
CCG_elem_offset_co(key, elem, j),
|
||||
math::sqrt(test.dist),
|
||||
CCG_elem_offset_no(key, elem, j),
|
||||
nullptr,
|
||||
key.has_mask ? CCG_elem_offset_mask(key, elem, j) : 0.0f,
|
||||
BKE_pbvh_make_vref(grid_verts_start + j),
|
||||
thread_id,
|
||||
&automask_data);
|
||||
proxy[i] = offset * fade;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_bmesh(Object &object, const Brush &brush, const float3 &offset, PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
const int mask_offset = CustomData_get_offset_named(
|
||||
&ss.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
||||
|
||||
/* TODO: Remove usage of proxies. */
|
||||
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
|
||||
int i = 0;
|
||||
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&node)) {
|
||||
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, vert->co)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
auto_mask::node_update(automask_data, *vert);
|
||||
const float mask = mask_offset == -1 ? 0.0f : BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
|
||||
const float fade = SCULPT_brush_strength_factor(ss,
|
||||
brush,
|
||||
vert->co,
|
||||
math::sqrt(test.dist),
|
||||
vert->no,
|
||||
nullptr,
|
||||
mask,
|
||||
BKE_pbvh_make_vref(intptr_t(vert)),
|
||||
thread_id,
|
||||
&automask_data);
|
||||
proxy[i] = offset * fade;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace draw_cc
|
||||
|
||||
void do_draw_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 float bstrength = ss.cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float3 effective_normal;
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
|
||||
const float3 offset = effective_normal * ss.cache->radius * ss.cache->scale * bstrength;
|
||||
|
||||
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
|
||||
case PBVH_FACES: {
|
||||
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
||||
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_sculpt = mesh_brush_positions_for_write(*object.sculpt, mesh);
|
||||
MutableSpan<float3> positions_mesh = 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,
|
||||
offset,
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
*nodes[i],
|
||||
object,
|
||||
tls,
|
||||
positions_sculpt,
|
||||
positions_mesh);
|
||||
BKE_pbvh_node_mark_positions_update(nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PBVH_GRIDS:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_grids(object, brush, offset, *nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_bmesh(object, brush, offset, *nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -0,0 +1,263 @@
|
||||
/* 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_enumerable_thread_specific.hh"
|
||||
#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 draw_vector_displacement_cc {
|
||||
|
||||
struct LocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<float4> colors;
|
||||
Vector<float3> translations;
|
||||
};
|
||||
|
||||
static void calc_brush_texture_colors(SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<int> verts,
|
||||
const Span<float> factors,
|
||||
const MutableSpan<float4> r_colors)
|
||||
{
|
||||
BLI_assert(verts.size() == r_colors.size());
|
||||
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
float texture_value;
|
||||
float4 texture_rgba;
|
||||
/* NOTE: This is not a thread-safe call. */
|
||||
sculpt_apply_texture(
|
||||
ss, brush, vert_positions[verts[i]], thread_id, &texture_value, texture_rgba);
|
||||
|
||||
r_colors[i] = texture_rgba * factors[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_faces(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<float3> vert_normals,
|
||||
const PBVHNode &node,
|
||||
Object &object,
|
||||
LocalData &tls,
|
||||
MutableSpan<float3> positions_orig,
|
||||
MutableSpan<float3> mesh_positions)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
StrokeCache &cache = *ss.cache;
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(node);
|
||||
|
||||
tls.factors.reinitialize(verts.size());
|
||||
const MutableSpan<float> factors = tls.factors;
|
||||
fill_factor_from_hide_and_mask(mesh, verts, factors);
|
||||
|
||||
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_eval, verts, eBrushFalloffShape(brush.falloff_shape), distances, factors);
|
||||
calc_brush_strength_factors(ss, brush, verts, distances, factors);
|
||||
|
||||
if (ss.cache->automasking) {
|
||||
auto_mask::calc_vert_factors(object, *ss.cache->automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
tls.colors.reinitialize(verts.size());
|
||||
const MutableSpan<float4> colors = tls.colors;
|
||||
calc_brush_texture_colors(ss, brush, positions_eval, verts, factors, colors);
|
||||
|
||||
tls.translations.reinitialize(verts.size());
|
||||
const MutableSpan<float3> translations = tls.translations;
|
||||
for (const int i : verts.index_range()) {
|
||||
SCULPT_calc_vertex_displacement(ss, brush, colors[i], translations[i]);
|
||||
}
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions_eval, verts, translations);
|
||||
|
||||
if (!ss.deform_imats.is_empty()) {
|
||||
apply_crazyspace_to_translations(ss.deform_imats, verts, translations);
|
||||
}
|
||||
|
||||
apply_translations(translations, verts, positions_orig);
|
||||
flush_positions_to_shape_keys(object, verts, positions_orig, mesh_positions);
|
||||
}
|
||||
|
||||
static void calc_grids(Object &object, const Brush &brush, PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const CCGKey key = *BKE_pbvh_get_grid_key(*ss.pbvh);
|
||||
const Span<CCGElem *> grids = subdiv_ccg.grids;
|
||||
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
/* TODO: Remove usage of proxies. */
|
||||
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
|
||||
int i = 0;
|
||||
for (const int grid : bke::pbvh::node_grid_indices(node)) {
|
||||
const int grid_verts_start = grid * key.grid_area;
|
||||
CCGElem *elem = grids[grid];
|
||||
for (const int j : IndexRange(key.grid_area)) {
|
||||
if (!grid_hidden.is_empty() && grid_hidden[grid][j]) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, CCG_elem_offset_co(key, elem, j))) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto_mask::node_update(automask_data, i);
|
||||
float r_rgba[4];
|
||||
SCULPT_brush_strength_color(ss,
|
||||
brush,
|
||||
CCG_elem_offset_co(key, elem, j),
|
||||
math::sqrt(test.dist),
|
||||
CCG_elem_offset_no(key, elem, j),
|
||||
nullptr,
|
||||
key.has_mask ? CCG_elem_offset_mask(key, elem, j) : 0.0f,
|
||||
BKE_pbvh_make_vref(grid_verts_start + j),
|
||||
thread_id,
|
||||
&automask_data,
|
||||
r_rgba);
|
||||
SCULPT_calc_vertex_displacement(ss, brush, r_rgba, proxy[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calc_bmesh(Object &object, const Brush &brush, PBVHNode &node)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
object, ss.cache->automasking.get(), node);
|
||||
|
||||
const int mask_offset = CustomData_get_offset_named(
|
||||
&ss.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
||||
|
||||
/* TODO: Remove usage of proxies. */
|
||||
const MutableSpan<float3> proxy = BKE_pbvh_node_add_proxy(*ss.pbvh, node).co;
|
||||
int i = 0;
|
||||
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&node)) {
|
||||
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, vert->co)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto_mask::node_update(automask_data, *vert);
|
||||
const float mask = mask_offset == -1 ? 0.0f : BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
|
||||
float r_rgba[4];
|
||||
SCULPT_brush_strength_color(ss,
|
||||
brush,
|
||||
vert->co,
|
||||
math::sqrt(test.dist),
|
||||
vert->no,
|
||||
nullptr,
|
||||
mask,
|
||||
BKE_pbvh_make_vref(intptr_t(vert)),
|
||||
thread_id,
|
||||
&automask_data,
|
||||
r_rgba);
|
||||
SCULPT_calc_vertex_displacement(ss, brush, r_rgba, proxy[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace draw_vector_displacement_cc
|
||||
|
||||
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
switch (BKE_pbvh_type(*object.sculpt->pbvh)) {
|
||||
case PBVH_FACES: {
|
||||
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
||||
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_brush_positions_for_write(*object.sculpt, mesh);
|
||||
MutableSpan<float3> mesh_positions = 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,
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
*nodes[i],
|
||||
object,
|
||||
tls,
|
||||
positions_orig,
|
||||
mesh_positions);
|
||||
BKE_pbvh_node_mark_positions_update(nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PBVH_GRIDS:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_grids(object, brush, *nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
calc_bmesh(object, brush, *nodes[i]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
20
source/blender/editors/sculpt_paint/brushes/types.hh
Normal file
20
source/blender/editors/sculpt_paint/brushes/types.hh
Normal file
@@ -0,0 +1,20 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
struct Sculpt;
|
||||
struct Object;
|
||||
struct PBVHNode;
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
/** 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. */
|
||||
void do_draw_vector_displacement_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
167
source/blender/editors/sculpt_paint/mesh_brush_common.hh
Normal file
167
source/blender/editors/sculpt_paint/mesh_brush_common.hh
Normal file
@@ -0,0 +1,167 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "DNA_brush_enums.h"
|
||||
|
||||
/**
|
||||
* This file contains common operations useful for the implementation of various different brush
|
||||
* tools. The design goals of the API are to always operate on more than one data element at a
|
||||
* time, to avoid unnecessary branching for constants, favor cache-friendly access patterns, enable
|
||||
* use of SIMD, and provide opportunities to avoid work where possible.
|
||||
*
|
||||
* API function arguments should favor passing raw data references rather than general catch-all
|
||||
* storage structs in order to clarify the scope of each function, structure the work around the
|
||||
* required data, and limit redundant data storage.
|
||||
*
|
||||
* Many functions calculate "factors" which describe how strong the brush influence should be
|
||||
* between 0 and 1. Most functions multiply with the existing factor value rather than assigning a
|
||||
* new value from scratch.
|
||||
*/
|
||||
|
||||
struct Brush;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
struct PBVHNode;
|
||||
struct Sculpt;
|
||||
struct SculptSession;
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
namespace auto_mask {
|
||||
struct Cache;
|
||||
};
|
||||
|
||||
/**
|
||||
* Note on the various positions arrays:
|
||||
* - positions_sculpt: The positions affected by brush strokes (maybe indirectly). Owned by the
|
||||
* PBVH or mesh.
|
||||
* - positions_mesh: Positions owned by the original mesh. Not the same as `positions_sculpt` if
|
||||
* there are deform modifiers.
|
||||
* - positions_eval: Positions after procedural deformation, used to build the PBVH. Translations
|
||||
* are built for these values, then applied to `positions_sculpt`.
|
||||
*
|
||||
* Only two of these arrays are actually necessary. The third comes from the fact that the PBVH
|
||||
* currently stores its own copy of positions when there are deformations. If that was removed, the
|
||||
* situation would be clearer.
|
||||
*
|
||||
* \todo Get rid of one of the arrays mentioned above to avoid the situation with evaluated
|
||||
* positions, original positions, and then a third copy that's just there because of historical
|
||||
* reasons. This would involve removing access to positions and normals from the PBVH structure,
|
||||
* which should only be concerned with splitting geometry into spacially contiguous chunks.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate initial influence factors based on vertex visibility and masking.
|
||||
*/
|
||||
void fill_factor_from_hide_and_mask(const Mesh &mesh,
|
||||
Span<int> vert_indices,
|
||||
MutableSpan<float> r_factors);
|
||||
|
||||
/**
|
||||
* Disable brush influence when vertex normals point away from the view.
|
||||
*/
|
||||
void calc_front_face(const float3 &view_normal,
|
||||
Span<float3> vert_normals,
|
||||
Span<int> vert_indices,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
/**
|
||||
* Modify influence factors based on the distance from the brush cursor and various other settings.
|
||||
* Also fill an array of distances from the brush cursor for "in bounds" vertices.
|
||||
*/
|
||||
void calc_distance_falloff(SculptSession &ss,
|
||||
Span<float3> vert_positions,
|
||||
Span<int> vert_indices,
|
||||
eBrushFalloffShape falloff_shape,
|
||||
MutableSpan<float> r_distances,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
/**
|
||||
* Modify the factors based on distances to the brush cursor, using various brush settings.
|
||||
*/
|
||||
void calc_brush_strength_factors(const SculptSession &ss,
|
||||
const Brush &brush,
|
||||
Span<int> vert_indices,
|
||||
Span<float> distances,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
/**
|
||||
* Modify brush influence factors to include sampled texture values.
|
||||
*/
|
||||
void calc_brush_texture_factors(SculptSession &ss,
|
||||
const Brush &brush,
|
||||
Span<float3> vert_positions,
|
||||
Span<int> vert_indices,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
namespace auto_mask {
|
||||
|
||||
/**
|
||||
* Calculate all auto-masking influence on each vertex.
|
||||
*
|
||||
* \todo Remove call to `undo::push_node` deep inside this function so the `object` argument can be
|
||||
* const. That may (hopefully) require pulling out the undo node push into the code for each brush.
|
||||
* That should help clarify the code path for brushes, and various optimizations will depend on
|
||||
* brush implementations doing their own undo pushes.
|
||||
*/
|
||||
void calc_vert_factors(Object &object,
|
||||
const Cache &cache,
|
||||
const PBVHNode &node,
|
||||
Span<int> verts,
|
||||
MutableSpan<float> factors);
|
||||
|
||||
} // namespace auto_mask
|
||||
|
||||
/**
|
||||
* Many brushes end up calculating translations from the original positions. Instead of applying
|
||||
* these directly to the modified values, it's helpful to process them separately to easily
|
||||
* calculate various effects like clipping. After they are processed, this function can be used to
|
||||
* simply add them to the final vertex positions.
|
||||
*/
|
||||
void apply_translations(Span<float3> translations, Span<int> verts, MutableSpan<float3> positions);
|
||||
|
||||
/**
|
||||
* Rotate translations to account for rotations from procedural deformation.
|
||||
*
|
||||
* \todo Don't invert `deform_imats` on object evaluation. Instead just invert them on-demand in
|
||||
* brush implementations. This would be better because only the inversions required for affected
|
||||
* vertices would be necessary.
|
||||
*/
|
||||
void apply_crazyspace_to_translations(Span<float3x3> deform_imats,
|
||||
Span<int> verts,
|
||||
MutableSpan<float3> translations);
|
||||
|
||||
/**
|
||||
* Modify translations based on sculpt mode axis locking and mirroring clipping.
|
||||
*/
|
||||
void clip_and_lock_translations(const Sculpt &sd,
|
||||
const SculptSession &ss,
|
||||
Span<float3> positions,
|
||||
Span<int> verts,
|
||||
MutableSpan<float3> translations);
|
||||
|
||||
/**
|
||||
* Retrieve the final mutable positions array to be modified.
|
||||
*
|
||||
* \note See the comment at the top of this file for context.
|
||||
*/
|
||||
MutableSpan<float3> mesh_brush_positions_for_write(SculptSession &ss, Mesh &mesh);
|
||||
|
||||
/**
|
||||
* Applying final positions to shape keys is non-trivial because the mesh positions and the active
|
||||
* shape key positions must be kept in sync, and shape keys dependent on the active key must also
|
||||
* be modified.
|
||||
*/
|
||||
void flush_positions_to_shape_keys(Object &object,
|
||||
Span<int> verts,
|
||||
Span<float3> positions,
|
||||
MutableSpan<float3> positions_mesh);
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_span.hh"
|
||||
@@ -85,6 +86,9 @@
|
||||
|
||||
#include "bmesh.hh"
|
||||
|
||||
#include "editors/sculpt_paint/brushes/types.hh"
|
||||
#include "mesh_brush_common.hh"
|
||||
|
||||
using blender::float3;
|
||||
using blender::MutableSpan;
|
||||
using blender::Set;
|
||||
@@ -1238,6 +1242,33 @@ void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, const PBVHVerte
|
||||
}
|
||||
}
|
||||
|
||||
void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, const BMVert &vert)
|
||||
{
|
||||
using namespace blender::ed::sculpt_paint;
|
||||
if (orig_data.unode->type == undo::Type::Position) {
|
||||
BM_log_original_vert_data(
|
||||
orig_data.bm_log, &const_cast<BMVert &>(vert), &orig_data.co, &orig_data.no);
|
||||
}
|
||||
else if (orig_data.unode->type == undo::Type::Mask) {
|
||||
orig_data.mask = BM_log_original_mask(orig_data.bm_log, &const_cast<BMVert &>(vert));
|
||||
}
|
||||
}
|
||||
|
||||
void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, const int i)
|
||||
{
|
||||
using namespace blender::ed::sculpt_paint;
|
||||
if (orig_data.unode->type == undo::Type::Position) {
|
||||
orig_data.co = orig_data.coords[i];
|
||||
orig_data.no = orig_data.normals[i];
|
||||
}
|
||||
else if (orig_data.unode->type == undo::Type::Color) {
|
||||
orig_data.col = orig_data.colors[i];
|
||||
}
|
||||
else if (orig_data.unode->type == undo::Type::Mask) {
|
||||
orig_data.mask = orig_data.vmasks[i];
|
||||
}
|
||||
}
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
static void sculpt_rake_data_update(SculptRakeData *srd, const float co[3])
|
||||
@@ -2389,12 +2420,12 @@ static float sculpt_apply_hardness(const SculptSession &ss, const float input_le
|
||||
return final_len;
|
||||
}
|
||||
|
||||
static void sculpt_apply_texture(SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const float brush_point[3],
|
||||
const int thread_id,
|
||||
float *r_value,
|
||||
float r_rgba[4])
|
||||
void sculpt_apply_texture(const SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const float brush_point[3],
|
||||
const int thread_id,
|
||||
float *r_value,
|
||||
float r_rgba[4])
|
||||
{
|
||||
const blender::ed::sculpt_paint::StrokeCache &cache = *ss.cache;
|
||||
const Scene *scene = cache.vc->scene;
|
||||
@@ -3516,9 +3547,17 @@ static void do_brush_action(const Sculpt &sd,
|
||||
|
||||
/* Apply one type of brush action. */
|
||||
switch (brush.sculpt_tool) {
|
||||
case SCULPT_TOOL_DRAW:
|
||||
SCULPT_do_draw_brush(sd, ob, nodes);
|
||||
case SCULPT_TOOL_DRAW: {
|
||||
const bool use_vector_displacement = (brush.flag2 & BRUSH_USE_COLOR_AS_DISPLACEMENT &&
|
||||
(brush.mtex.brush_map_mode == MTEX_MAP_MODE_AREA));
|
||||
if (use_vector_displacement) {
|
||||
ed::sculpt_paint::do_draw_vector_displacement_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
ed::sculpt_paint::do_draw_brush(sd, ob, nodes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SCULPT_TOOL_SMOOTH:
|
||||
if (brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
|
||||
smooth::do_smooth_brush(sd, ob, nodes);
|
||||
@@ -5622,12 +5661,16 @@ static void sculpt_stroke_update_step(bContext *C,
|
||||
*
|
||||
* Same applies to the DEG_id_tag_update() invoked from
|
||||
* sculpt_flush_update_step().
|
||||
*
|
||||
* For some brushes, flushing is done in the brush code itself.
|
||||
*/
|
||||
if (ss.deform_modifiers_active) {
|
||||
SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush.sculpt_tool));
|
||||
}
|
||||
else if (ss.shapekey_active) {
|
||||
sculpt_update_keyblock(ob);
|
||||
if (!(ELEM(brush.sculpt_tool, SCULPT_TOOL_DRAW) && BKE_pbvh_type(*ss.pbvh) == PBVH_FACES)) {
|
||||
if (ss.deform_modifiers_active) {
|
||||
SCULPT_flush_stroke_deform(sd, ob, sculpt_tool_is_proxy_used(brush.sculpt_tool));
|
||||
}
|
||||
else if (ss.shapekey_active) {
|
||||
sculpt_update_keyblock(ob);
|
||||
}
|
||||
}
|
||||
|
||||
ss.cache->first_time = false;
|
||||
@@ -6244,3 +6287,245 @@ void SCULPT_cube_tip_init(const Sculpt & /*sd*/,
|
||||
invert_m4_m4(mat, tmat);
|
||||
}
|
||||
/** \} */
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
|
||||
void fill_factor_from_hide_and_mask(const Mesh &mesh,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> r_factors)
|
||||
{
|
||||
BLI_assert(verts.size() == r_factors.size());
|
||||
|
||||
/* 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 : verts.index_range()) {
|
||||
r_factors[i] = 1.0f - span[verts[i]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
r_factors.fill(1.0f);
|
||||
}
|
||||
|
||||
if (const VArray hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point)) {
|
||||
const VArraySpan span(hide_vert);
|
||||
for (const int i : verts.index_range()) {
|
||||
r_factors[i] = span[verts[i]] ? 0.0f : r_factors[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calc_front_face(const float3 &view_normal,
|
||||
const Span<float3> vert_normals,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
const float dot = math::dot(view_normal, vert_normals[verts[i]]);
|
||||
factors[i] *= dot > 0.0f ? dot : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void calc_distance_falloff(SculptSession &ss,
|
||||
const Span<float3> positions,
|
||||
const Span<int> verts,
|
||||
const eBrushFalloffShape falloff_shape,
|
||||
const MutableSpan<float> r_distances,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
BLI_assert(verts.size() == r_distances.size());
|
||||
|
||||
SculptBrushTest test;
|
||||
const SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, falloff_shape);
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
if (factors[i] == 0.0f) {
|
||||
r_distances[i] = FLT_MAX;
|
||||
continue;
|
||||
}
|
||||
if (!sculpt_brush_test_sq_fn(test, positions[verts[i]])) {
|
||||
factors[i] = 0.0f;
|
||||
r_distances[i] = FLT_MAX;
|
||||
continue;
|
||||
}
|
||||
r_distances[i] = math::sqrt(test.dist);
|
||||
}
|
||||
}
|
||||
|
||||
void calc_brush_strength_factors(const SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const Span<int> verts,
|
||||
const Span<float> distances,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == distances.size());
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
if (factors[i] == 0.0f) {
|
||||
/* Skip already masked-out points, as they might be outside of the brush radius and be
|
||||
* unaffected anyway. Having such large values in the calculations below might lead to
|
||||
* non-finite values, leading to undesired results. */
|
||||
continue;
|
||||
}
|
||||
|
||||
const float hardness = sculpt_apply_hardness(ss, distances[i]);
|
||||
const float strength = BKE_brush_curve_strength(&brush, hardness, cache.radius);
|
||||
|
||||
factors[i] *= strength;
|
||||
}
|
||||
}
|
||||
|
||||
void calc_brush_texture_factors(SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
|
||||
const int thread_id = BLI_task_parallel_thread_id(nullptr);
|
||||
const MTex *mtex = BKE_brush_mask_texture_get(&brush, OB_MODE_SCULPT);
|
||||
if (!mtex->tex) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
float texture_value;
|
||||
float4 texture_rgba;
|
||||
/* NOTE: This is not a thread-safe call. */
|
||||
sculpt_apply_texture(
|
||||
ss, brush, vert_positions[verts[i]], thread_id, &texture_value, texture_rgba);
|
||||
|
||||
factors[i] *= texture_value;
|
||||
}
|
||||
}
|
||||
|
||||
void apply_translations(const Span<float3> translations,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float3> positions)
|
||||
{
|
||||
BLI_assert(verts.size() == translations.size());
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
const int vert = verts[i];
|
||||
positions[vert] += translations[i];
|
||||
}
|
||||
}
|
||||
|
||||
void apply_crazyspace_to_translations(const Span<float3x3> deform_imats,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
BLI_assert(verts.size() == translations.size());
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
translations[i] = math::transform_point(deform_imats[verts[i]], translations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void clip_and_lock_translations(const Sculpt &sd,
|
||||
const SculptSession &ss,
|
||||
const Span<float3> positions,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
BLI_assert(verts.size() == translations.size());
|
||||
|
||||
const StrokeCache *cache = ss.cache;
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
for (const int axis : IndexRange(3)) {
|
||||
if (sd.flags & (SCULPT_LOCK_X << axis)) {
|
||||
for (float3 &translation : translations) {
|
||||
translation[axis] = 0.0f;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(cache->flag & (CLIP_X << axis))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const float4x4 mirror(cache->clip_mirror_mtx);
|
||||
const float4x4 mirror_inverse = math::invert(mirror);
|
||||
for (const int i : verts.index_range()) {
|
||||
const int vert = verts[i];
|
||||
|
||||
/* Transform into the space of the mirror plane, check translations, then transform back. */
|
||||
float3 co_mirror = math::transform_point(mirror, positions[vert]);
|
||||
if (math::abs(co_mirror[axis]) > cache->clip_tolerance[axis]) {
|
||||
continue;
|
||||
}
|
||||
/* Clear the translation in the local space of the mirror object. */
|
||||
co_mirror[axis] = 0.0f;
|
||||
const float3 co_local = math::transform_point(mirror_inverse, co_mirror);
|
||||
translations[i][axis] = co_local[axis] - positions[vert][axis];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MutableSpan<float3> mesh_brush_positions_for_write(SculptSession &ss, Mesh & /*mesh*/)
|
||||
{
|
||||
/* TODO: Eventually this should retrieve mutable positions directly from the mesh or the active
|
||||
* shape key, instead of keeping a mutable reference to an array stored in the PBVH. That will
|
||||
* help avoid copies when user edits don't affect positions, will help to make code safer because
|
||||
* there will be less potentially-stale state, and will make it less confusing by avoiding
|
||||
* redundant data storage. */
|
||||
return BKE_pbvh_get_vert_positions(*ss.pbvh);
|
||||
}
|
||||
|
||||
void flush_positions_to_shape_keys(Object &object,
|
||||
const Span<int> verts,
|
||||
const Span<float3> positions,
|
||||
const MutableSpan<float3> positions_mesh)
|
||||
{
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
KeyBlock *active_key = BKE_keyblock_from_object(&object);
|
||||
if (!active_key) {
|
||||
return;
|
||||
}
|
||||
MutableSpan active_key_data(static_cast<float3 *>(active_key->data), active_key->totelem);
|
||||
|
||||
/* For relative keys editing of base should update other keys. */
|
||||
if (bool *dependent = BKE_keyblock_get_dependent_keys(mesh.key, object.shapenr - 1)) {
|
||||
/* TODO: Avoid allocation by using translations already calculated by brush. */
|
||||
Array<float3> offsets(verts.size());
|
||||
for (const int i : verts.index_range()) {
|
||||
offsets[i] = positions[verts[i]] - active_key_data[verts[i]];
|
||||
}
|
||||
|
||||
int i;
|
||||
LISTBASE_FOREACH_INDEX (KeyBlock *, other_key, &mesh.key->block, i) {
|
||||
if ((other_key != active_key) && dependent[i]) {
|
||||
MutableSpan<float3> data(static_cast<float3 *>(other_key->data), other_key->totelem);
|
||||
apply_translations(offsets, verts, data);
|
||||
}
|
||||
}
|
||||
|
||||
MEM_freeN(dependent);
|
||||
}
|
||||
|
||||
/* Modifying of basis key should update mesh. */
|
||||
if (active_key == mesh.key->refkey) {
|
||||
/* TODO: There are too many positions arrays getting passed around. We should have a better
|
||||
* naming system or not have to constantly update both the base shape key and original
|
||||
* positions. OTOH, maybe it's just a consequence of the bad design of shape keys. */
|
||||
apply_translations(positions, verts, positions_mesh);
|
||||
}
|
||||
|
||||
/* Apply new positions to active shape key. */
|
||||
for (const int vert : verts) {
|
||||
active_key_data[vert] = positions[vert];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "BKE_paint.hh"
|
||||
#include "BKE_pbvh_api.hh"
|
||||
|
||||
#include "mesh_brush_common.hh"
|
||||
#include "paint_intern.hh"
|
||||
#include "sculpt_intern.hh"
|
||||
|
||||
@@ -587,6 +588,38 @@ float factor_get(const Cache *automasking,
|
||||
return automasking_factor_end(ss, automasking, vert, mask);
|
||||
}
|
||||
|
||||
static void mesh_orig_vert_data_update(SculptOrigVertData &orig_data, const int vert)
|
||||
{
|
||||
if (orig_data.unode->type == undo::Type::Position) {
|
||||
orig_data.co = orig_data.coords[vert];
|
||||
orig_data.no = orig_data.normals[vert];
|
||||
}
|
||||
else if (orig_data.unode->type == undo::Type::Color) {
|
||||
orig_data.col = orig_data.colors[vert];
|
||||
}
|
||||
else if (orig_data.unode->type == undo::Type::Mask) {
|
||||
orig_data.mask = orig_data.vmasks[vert];
|
||||
}
|
||||
}
|
||||
|
||||
void calc_vert_factors(Object &object,
|
||||
const Cache &cache,
|
||||
const PBVHNode &node,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
NodeData data = node_begin(object, &cache, node);
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
if (data.orig_data) {
|
||||
mesh_orig_vert_data_update(*data.orig_data, i);
|
||||
}
|
||||
factors[i] *= factor_get(&cache, ss, BKE_pbvh_make_vref(verts[i]), &data);
|
||||
}
|
||||
}
|
||||
|
||||
NodeData node_begin(Object &object, const Cache *automasking, const PBVHNode &node)
|
||||
{
|
||||
if (!automasking) {
|
||||
@@ -602,13 +635,27 @@ NodeData node_begin(Object &object, const Cache *automasking, const PBVHNode &no
|
||||
return automask_data;
|
||||
}
|
||||
|
||||
void node_update(auto_mask::NodeData &automask_data, PBVHVertexIter &vd)
|
||||
void node_update(auto_mask::NodeData &automask_data, const PBVHVertexIter &vd)
|
||||
{
|
||||
if (automask_data.orig_data) {
|
||||
SCULPT_orig_vert_data_update(*automask_data.orig_data, vd);
|
||||
}
|
||||
}
|
||||
|
||||
void node_update(auto_mask::NodeData &automask_data, const BMVert &vert)
|
||||
{
|
||||
if (automask_data.orig_data) {
|
||||
SCULPT_orig_vert_data_update(*automask_data.orig_data, vert);
|
||||
}
|
||||
}
|
||||
|
||||
void node_update(auto_mask::NodeData &automask_data, const int i)
|
||||
{
|
||||
if (automask_data.orig_data) {
|
||||
SCULPT_orig_vert_data_update(*automask_data.orig_data, i);
|
||||
}
|
||||
}
|
||||
|
||||
struct AutomaskFloodFillData {
|
||||
float radius;
|
||||
bool use_radius;
|
||||
|
||||
@@ -151,95 +151,6 @@ static void sculpt_project_v3_normal_align(const SculptSession &ss,
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Draw Brush
|
||||
* \{ */
|
||||
|
||||
static void do_draw_brush_task(Object &ob, const Brush &brush, const float *offset, 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);
|
||||
|
||||
auto_mask::NodeData automask_data = auto_mask::node_begin(
|
||||
ob, ss.cache->automasking.get(), *node);
|
||||
|
||||
BKE_pbvh_vertex_iter_begin (*ss.pbvh, node, vd, PBVH_ITER_UNIQUE) {
|
||||
if (!sculpt_brush_test_sq_fn(test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto_mask::node_update(automask_data, vd);
|
||||
|
||||
/* Offset vertex. */
|
||||
if (ss.cache->brush->flag2 & BRUSH_USE_COLOR_AS_DISPLACEMENT &&
|
||||
(brush.mtex.brush_map_mode == MTEX_MAP_MODE_AREA))
|
||||
{
|
||||
float r_rgba[4];
|
||||
SCULPT_brush_strength_color(ss,
|
||||
brush,
|
||||
vd.co,
|
||||
sqrtf(test.dist),
|
||||
vd.no,
|
||||
vd.fno,
|
||||
vd.mask,
|
||||
vd.vertex,
|
||||
thread_id,
|
||||
&automask_data,
|
||||
r_rgba);
|
||||
SCULPT_calc_vertex_displacement(ss, brush, r_rgba, proxy[vd.i]);
|
||||
}
|
||||
else {
|
||||
float fade = 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], offset, fade);
|
||||
}
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
void SCULPT_do_draw_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
{
|
||||
using namespace blender;
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
float offset[3];
|
||||
const float bstrength = ss.cache->bstrength;
|
||||
|
||||
/* Offset with as much as possible factored in already. */
|
||||
float effective_normal[3];
|
||||
SCULPT_tilt_effective_normal_get(ss, brush, effective_normal);
|
||||
mul_v3_v3fl(offset, effective_normal, ss.cache->radius);
|
||||
mul_v3_v3(offset, ss.cache->scale);
|
||||
mul_v3_fl(offset, bstrength);
|
||||
|
||||
/* XXX: this shouldn't be necessary, but sculpting crashes in blender2.8 otherwise
|
||||
* initialize before threads so they can do curve mapping. */
|
||||
BKE_curvemapping_init(brush.curve);
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
do_draw_brush_task(ob, brush, offset, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Sculpt Fill Brush
|
||||
* \{ */
|
||||
|
||||
@@ -1011,6 +1011,9 @@ void SCULPT_orig_vert_data_init(SculptOrigVertData &data,
|
||||
* Update a #SculptOrigVertData for a particular vertex from the PBVH iterator.
|
||||
*/
|
||||
void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, const PBVHVertexIter &iter);
|
||||
void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, const BMVert &vert);
|
||||
void SCULPT_orig_vert_data_update(SculptOrigVertData &orig_data, int i);
|
||||
|
||||
/**
|
||||
* Initialize a #SculptOrigVertData for accessing original vertex data;
|
||||
* handles #BMesh, #Mesh, and multi-resolution.
|
||||
@@ -1138,6 +1141,14 @@ float SCULPT_brush_strength_factor(
|
||||
int thread_id,
|
||||
const blender::ed::sculpt_paint::auto_mask::NodeData *automask_data);
|
||||
|
||||
/** Sample the brush's texture value. */
|
||||
void sculpt_apply_texture(const SculptSession &ss,
|
||||
const Brush &brush,
|
||||
const float brush_point[3],
|
||||
const int thread_id,
|
||||
float *r_value,
|
||||
float r_rgba[4]);
|
||||
|
||||
/**
|
||||
* Return a color of a brush texture on a particular vertex multiplied by active masks.
|
||||
*/
|
||||
@@ -1323,7 +1334,13 @@ struct NodeData {
|
||||
NodeData node_begin(Object &object, const Cache *automasking, const PBVHNode &node);
|
||||
|
||||
/* Call before factor_get and SCULPT_brush_strength_factor. */
|
||||
void node_update(NodeData &automask_data, PBVHVertexIter &vd);
|
||||
void node_update(NodeData &automask_data, const PBVHVertexIter &vd);
|
||||
void node_update(NodeData &automask_data, const BMVert &vert);
|
||||
/**
|
||||
* Call before factor_get and SCULPT_brush_strength_factor. The index is in the range of the PBVH
|
||||
* node's vertex indices.
|
||||
*/
|
||||
void node_update(NodeData &automask_data, int i);
|
||||
|
||||
float factor_get(const Cache *automasking,
|
||||
SculptSession &ss,
|
||||
|
||||
Reference in New Issue
Block a user