Sculpt: Initial data oriented refactor of relax face set brush
Part of #118145. For each of the PBVH types, the refactored Relax Face Set brush performs the following steps: * Calculate all relevant element factors like other refactored brushes * Calculate the displacement by performing the following steps, using a displacement of 0 if certain conditions are unfulfilled (e.g. factor will have no effect, no relevant neighbor vertices, etc) * Find all neighbors for each element * Filter the neighbors based on if the current vert is or is not a boundary vert, looking at the face sets of each neighbor * From the relevant neighbors, calculate a smoothed average position. * For boundary verts, calculate the normal based on the summed direction towards each neighbor vertex * For non-boundary verts, retrieve the current vertex normal. * Use the current position and the retrieved normal to create a plane. * Find the closest point on the plane to the smoothed position, use this as the final displacement * Apply all of these displacements to the relevant elements Q: Why do we perform three different parallel loops for each method instead of a single one like other brushes? Because much of the processing time for these functions is spent in the neighbor calculation, we opt to process factors in an earlier loop to short-circuit neighbor calculation if we know it cannot possibly have an effect (i.e `factor[i] == 0.0f`) Q: Why are lambdas used in `filtered_neighbors` ? Simply put, reducing the overall amount of code duplication, otherwise this function would require 6 different methods to do the necessary filtering (boundary vs non boundary vertices for each of the three pbvh types) ### Potential Areas of Improvement * Unique face set calculation requires iterating over the `vert_to_face` map and happens multiple times per brush stroke (each stroke performs the vertex tasks 4 times in succession). Some form of cache or precomputed map similar to the `boundary_verts` `BitSpan` may improve performance. Pull Request: https://projects.blender.org/blender/blender/pulls/124135
This commit is contained in:
@@ -138,6 +138,7 @@ set(SRC
|
||||
brushes/multires_displacement_eraser.cc
|
||||
brushes/pinch.cc
|
||||
brushes/multires_displacement_smear.cc
|
||||
brushes/relax_face_sets.cc
|
||||
brushes/rotate.cc
|
||||
brushes/scrape.cc
|
||||
brushes/smooth.cc
|
||||
|
||||
1033
source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc
Normal file
1033
source/blender/editors/sculpt_paint/brushes/relax_face_sets.cc
Normal file
@@ -0,0 +1,1033 @@
|
||||
/* 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 "BKE_subdiv_ccg.hh"
|
||||
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_math_base.hh"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#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 relax_face_sets_cc {
|
||||
|
||||
struct MeshLocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<Vector<int>> vert_neighbors;
|
||||
};
|
||||
|
||||
struct GridLocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<Vector<SubdivCCGCoord>> vert_neighbors;
|
||||
};
|
||||
|
||||
struct BMeshLocalData {
|
||||
Vector<float> factors;
|
||||
Vector<float> distances;
|
||||
Vector<Vector<BMVert *>> vert_neighbors;
|
||||
};
|
||||
|
||||
static std::array<float, 4> iteration_strengths(const float strength, const int stroke_iteration)
|
||||
{
|
||||
if (stroke_iteration % 3 == 0) {
|
||||
return {strength, strength, strength, strength};
|
||||
}
|
||||
|
||||
/* This operations needs a strength tweak as the relax deformation is too weak by default. */
|
||||
const float modified_strength = strength * 1.5f;
|
||||
return {modified_strength, modified_strength, strength, strength};
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void filter_factors_on_face_sets_mesh(const GroupedSpan<int> vert_to_face_map,
|
||||
const int *face_sets,
|
||||
const bool relax_face_sets,
|
||||
const Span<int> verts,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
if (relax_face_sets ==
|
||||
face_set::vert_has_unique_face_set(vert_to_face_map, face_sets, verts[i]))
|
||||
{
|
||||
factors[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
BLI_NOINLINE static void filter_factors_on_face_sets_grids(const GroupedSpan<int> vert_to_face_map,
|
||||
const Span<int> corner_verts,
|
||||
const OffsetIndices<int> faces,
|
||||
const SubdivCCG &subdiv_ccg,
|
||||
const int *face_sets,
|
||||
const bool relax_face_sets,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
BLI_assert(grids.size() * key.grid_area == factors.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int start = i * key.grid_area;
|
||||
for (const int y : IndexRange(key.grid_size)) {
|
||||
for (const int x : IndexRange(key.grid_size)) {
|
||||
const int offset = CCG_grid_xy_to_index(key.grid_size, x, y);
|
||||
if (factors[start + offset] == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SubdivCCGCoord coord{};
|
||||
coord.grid_index = grids[i];
|
||||
coord.x = x;
|
||||
coord.y = y;
|
||||
if (relax_face_sets ==
|
||||
face_set::vert_has_unique_face_set(
|
||||
vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, coord))
|
||||
{
|
||||
factors[start + offset] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static void filter_factors_on_face_sets_bmesh(const bool relax_face_sets,
|
||||
const Set<BMVert *, 0> verts,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
|
||||
int i = 0;
|
||||
for (const BMVert *vert : verts) {
|
||||
if (relax_face_sets == face_set::vert_has_unique_face_set(vert)) {
|
||||
factors[i] = 0.0f;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
static float3 translation_to_plane(const float3 current_position,
|
||||
const float3 normal,
|
||||
const float3 smoothed_position)
|
||||
{
|
||||
float4 plane;
|
||||
plane_from_point_normal_v3(plane, current_position, normal);
|
||||
|
||||
float3 smooth_closest_plane;
|
||||
closest_to_plane_v3(smooth_closest_plane, plane, smoothed_position);
|
||||
|
||||
return smooth_closest_plane - current_position;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Relax Vertex
|
||||
* \{ */
|
||||
|
||||
static Vector<int, 16> filtered_neighbors(const Span<int> neighbors,
|
||||
const bool filter_boundary_face_sets,
|
||||
FunctionRef<bool(int)> is_unique_element_fn,
|
||||
FunctionRef<bool(int)> is_boundary_element_fn)
|
||||
{
|
||||
Vector<int, 16> result;
|
||||
for (const int vert : neighbors) {
|
||||
/* If we are filtering face sets, then we only want to affect vertices that have more than one
|
||||
* face set, i.e. are on the boundary of a face set and another face set. */
|
||||
if (filter_boundary_face_sets && is_unique_element_fn(vert)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
|
||||
* position. */
|
||||
if (is_boundary_element_fn && is_boundary_element_fn(vert)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append(vert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool get_normal_boundary(const float3 ¤t_position,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<int> neighbors,
|
||||
float3 &r_new_normal)
|
||||
{
|
||||
/* If we are not dealing with a corner vertex, skip this step.*/
|
||||
if (neighbors.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 normal(0.0f, 0.0f, 0.0f);
|
||||
for (const int vert : neighbors) {
|
||||
const float3 to_neighbor = vert_positions[vert] - current_position;
|
||||
normal += math::normalize(to_neighbor);
|
||||
}
|
||||
|
||||
r_new_normal = math::normalize(normal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_average_position(const Span<float3> vert_positions,
|
||||
const Span<int> neighbors,
|
||||
float3 &r_new_position)
|
||||
{
|
||||
if (neighbors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 average_position(0.0f, 0.0f, 0.0f);
|
||||
for (const int vert : neighbors) {
|
||||
average_position += vert_positions[vert];
|
||||
}
|
||||
|
||||
average_position *= math::rcp(float(neighbors.size()));
|
||||
r_new_position = average_position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BLI_NOINLINE static Vector<SubdivCCGCoord, 16> filtered_neighbors(
|
||||
const Span<SubdivCCGCoord> neighbors,
|
||||
const bool filter_boundary_face_sets,
|
||||
FunctionRef<bool(SubdivCCGCoord)> is_unique_element_fn,
|
||||
FunctionRef<bool(SubdivCCGCoord)> is_boundary_element_fn)
|
||||
{
|
||||
Vector<SubdivCCGCoord, 16> result;
|
||||
for (const SubdivCCGCoord coord : neighbors) {
|
||||
/* If we are filtering face sets, then we only want to affect vertices that have more than one
|
||||
* face set, i.e. are on the boundary of a face set and another face set. */
|
||||
if (filter_boundary_face_sets && is_unique_element_fn(coord)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
|
||||
* position. */
|
||||
if (is_boundary_element_fn && is_boundary_element_fn(coord)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append(coord);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
BLI_NOINLINE static bool get_normal_boundary(const CCGKey &key,
|
||||
const Span<CCGElem *> elems,
|
||||
const float3 ¤t_position,
|
||||
const Span<SubdivCCGCoord> neighbors,
|
||||
float3 &r_new_normal)
|
||||
{
|
||||
/* If we are not dealing with a corner vertex, skip this step.*/
|
||||
if (neighbors.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 normal(0.0f, 0.0f, 0.0f);
|
||||
for (const SubdivCCGCoord &coord : neighbors) {
|
||||
const float3 to_neighbor = CCG_grid_elem_co(key, elems[coord.grid_index], coord.x, coord.y) -
|
||||
current_position;
|
||||
normal += math::normalize(to_neighbor);
|
||||
}
|
||||
|
||||
r_new_normal = math::normalize(normal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BLI_NOINLINE static bool get_average_position(const CCGKey &key,
|
||||
const Span<CCGElem *> elems,
|
||||
const Span<float3> positions,
|
||||
const Span<SubdivCCGCoord> neighbors,
|
||||
const int current_grid,
|
||||
const int current_grid_start,
|
||||
float3 &r_new_position)
|
||||
{
|
||||
if (neighbors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 average_position(0.0f, 0.0f, 0.0f);
|
||||
for (const SubdivCCGCoord &coord : neighbors) {
|
||||
if (current_grid == coord.grid_index) {
|
||||
const int offset = CCG_grid_xy_to_index(key.grid_size, coord.x, coord.y);
|
||||
average_position += positions[current_grid_start + offset];
|
||||
}
|
||||
else {
|
||||
average_position += CCG_grid_elem_co(key, elems[coord.grid_index], coord.x, coord.y);
|
||||
}
|
||||
}
|
||||
|
||||
average_position *= math::rcp(float(neighbors.size()));
|
||||
r_new_position = average_position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Vector<BMVert *, 16> filtered_neighbors(
|
||||
const Span<BMVert *> neighbors,
|
||||
const bool filter_boundary_face_sets,
|
||||
FunctionRef<bool(const BMVert *)> is_unique_element_fn,
|
||||
FunctionRef<bool(const BMVert *)> is_boundary_element_fn)
|
||||
{
|
||||
Vector<BMVert *, 16> result;
|
||||
for (BMVert *vert : neighbors) {
|
||||
/* If we are filtering face sets, then we only want to affect vertices that have more than one
|
||||
* face set, i.e. are on the boundary of a face set and another face set. */
|
||||
if (filter_boundary_face_sets && is_unique_element_fn(vert)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* When the vertex to relax is boundary, use only connected boundary vertices for the average
|
||||
* position. */
|
||||
if (is_boundary_element_fn && is_boundary_element_fn(vert)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.append(vert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool get_normal_boundary(const float3 ¤t_position,
|
||||
const Span<BMVert *> neighbors,
|
||||
float3 &r_new_normal)
|
||||
{
|
||||
/* If we are not dealing with a corner vertex, skip this step.*/
|
||||
if (neighbors.size() != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 normal(0.0f, 0.0f, 0.0f);
|
||||
int i = 0;
|
||||
for (BMVert *vert : neighbors) {
|
||||
const float3 neighbor_pos = vert->co;
|
||||
const float3 to_neighbor = neighbor_pos - current_position;
|
||||
normal += math::normalize(to_neighbor);
|
||||
i++;
|
||||
}
|
||||
|
||||
r_new_normal = math::normalize(normal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_average_position(const Span<BMVert *> neighbors, float3 &r_new_position)
|
||||
{
|
||||
if (neighbors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
float3 average_position(0.0f, 0.0f, 0.0f);
|
||||
int i = 0;
|
||||
for (BMVert *vert : neighbors) {
|
||||
average_position += vert->co;
|
||||
i++;
|
||||
}
|
||||
|
||||
average_position *= math::rcp(float(neighbors.size()));
|
||||
r_new_position = average_position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
BLI_NOINLINE static void calc_factors_faces(const Brush &brush,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<float3> vert_normals,
|
||||
const PBVHNode &node,
|
||||
const float strength,
|
||||
const bool relax_face_sets,
|
||||
Object &object,
|
||||
MeshLocalData &tls,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
const Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
|
||||
const Span<int> verts = bke::pbvh::node_unique_verts(node);
|
||||
|
||||
fill_factor_from_hide_and_mask(mesh, verts, factors);
|
||||
filter_region_clip_factors(ss, positions_eval, 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_brush_distances(
|
||||
ss, positions_eval, verts, 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_vert_factors(object, *cache.automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions_eval, verts, factors);
|
||||
|
||||
filter_factors_on_face_sets_mesh(
|
||||
ss.vert_to_face_map, ss.face_sets, relax_face_sets, verts, factors);
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void calc_relaxed_translations_faces(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const int *face_sets,
|
||||
const GroupedSpan<int> vert_to_face_map,
|
||||
const BitSpan boundary_verts,
|
||||
const Span<bool> hide_poly,
|
||||
const Span<int> verts,
|
||||
const Span<float3> vert_positions,
|
||||
const Span<float3> vert_normals,
|
||||
const bool relax_face_sets,
|
||||
MeshLocalData &tls,
|
||||
const Span<float> factors,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
BLI_assert(verts.size() == translations.size());
|
||||
|
||||
tls.vert_neighbors.reinitialize(verts.size());
|
||||
calc_vert_neighbors_interior(
|
||||
faces, corner_verts, vert_to_face_map, boundary_verts, hide_poly, verts, tls.vert_neighbors);
|
||||
const Span<Vector<int>> vert_neighbors = tls.vert_neighbors;
|
||||
|
||||
for (const int i : verts.index_range()) {
|
||||
if (factors[i] == 0.0f) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't modify corner vertices */
|
||||
if (vert_neighbors[i].size() <= 2) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<int, 16> neighbors;
|
||||
if (boundary_verts[verts[i]]) {
|
||||
neighbors = filtered_neighbors(
|
||||
vert_neighbors[i],
|
||||
relax_face_sets,
|
||||
[&](const int vert) {
|
||||
return face_set::vert_has_unique_face_set(vert_to_face_map, face_sets, vert);
|
||||
},
|
||||
[&](const int vert) { return !boundary_verts[vert]; });
|
||||
}
|
||||
else {
|
||||
neighbors = filtered_neighbors(vert_neighbors[i],
|
||||
relax_face_sets,
|
||||
[&](const int vert) {
|
||||
return face_set::vert_has_unique_face_set(
|
||||
vert_to_face_map, face_sets, vert);
|
||||
},
|
||||
{});
|
||||
}
|
||||
|
||||
/* Smoothed position calculation */
|
||||
float3 smoothed_position;
|
||||
const bool has_new_position = get_average_position(
|
||||
vert_positions, neighbors, smoothed_position);
|
||||
|
||||
if (!has_new_position) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Normal Calculation */
|
||||
float3 normal;
|
||||
if (boundary_verts[verts[i]]) {
|
||||
bool has_boundary_normal = get_normal_boundary(
|
||||
vert_positions[verts[i]], vert_positions, neighbors, normal);
|
||||
|
||||
if (!has_boundary_normal) {
|
||||
normal = vert_normals[verts[i]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
normal = vert_normals[verts[i]];
|
||||
}
|
||||
|
||||
if (math::is_zero(normal)) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float3 translation = translation_to_plane(
|
||||
vert_positions[verts[i]], normal, smoothed_position);
|
||||
|
||||
translations[i] = translation * factors[i];
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_positions_faces(const Sculpt &sd,
|
||||
const Span<float3> positions_eval,
|
||||
const Span<int> verts,
|
||||
Object &object,
|
||||
const MutableSpan<float3> translations,
|
||||
const MutableSpan<float3> positions_orig)
|
||||
{
|
||||
write_translations(sd, object, positions_eval, verts, translations, positions_orig);
|
||||
}
|
||||
|
||||
static void do_relax_face_sets_brush_mesh(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const float strength,
|
||||
const bool relax_face_sets)
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
const VArraySpan hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
||||
|
||||
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();
|
||||
|
||||
Array<int> node_offset_data;
|
||||
const OffsetIndices<int> node_vert_offsets = create_node_vert_offsets(nodes, node_offset_data);
|
||||
|
||||
Array<float3> translations(node_vert_offsets.total_size());
|
||||
Array<float> factors(node_vert_offsets.total_size());
|
||||
|
||||
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) {
|
||||
calc_factors_faces(brush,
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
*nodes[i],
|
||||
strength,
|
||||
relax_face_sets,
|
||||
object,
|
||||
tls,
|
||||
factors.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
MeshLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_relaxed_translations_faces(faces,
|
||||
corner_verts,
|
||||
ss.face_sets,
|
||||
ss.vert_to_face_map,
|
||||
ss.vertex_info.boundary,
|
||||
hide_poly,
|
||||
bke::pbvh::node_unique_verts(*nodes[i]),
|
||||
positions_eval,
|
||||
vert_normals,
|
||||
relax_face_sets,
|
||||
tls,
|
||||
factors.as_span().slice(node_vert_offsets[i]),
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
apply_positions_faces(sd,
|
||||
positions_eval,
|
||||
bke::pbvh::node_unique_verts(*nodes[i]),
|
||||
object,
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
positions_orig);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void calc_factors_grids(const Brush &brush,
|
||||
const Span<int> corner_verts,
|
||||
const OffsetIndices<int> faces,
|
||||
const PBVHNode &node,
|
||||
const float strength,
|
||||
const bool relax_face_sets,
|
||||
Object &object,
|
||||
GridLocalData &tls,
|
||||
const MutableSpan<float3> positions,
|
||||
const MutableSpan<float> factors)
|
||||
{
|
||||
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;
|
||||
|
||||
gather_grids_positions(subdiv_ccg, grids, positions);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions, factors);
|
||||
|
||||
filter_factors_on_face_sets_grids(ss.vert_to_face_map,
|
||||
corner_verts,
|
||||
faces,
|
||||
subdiv_ccg,
|
||||
ss.face_sets,
|
||||
relax_face_sets,
|
||||
grids,
|
||||
factors);
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void calc_relaxed_translations_grids(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const int *face_sets,
|
||||
const GroupedSpan<int> vert_to_face_map,
|
||||
const BitSpan boundary_verts,
|
||||
const Span<int> grids,
|
||||
const bool relax_face_sets,
|
||||
Object &object,
|
||||
GridLocalData &tls,
|
||||
const Span<float> factors,
|
||||
const Span<float3> positions,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const Span<CCGElem *> elems = subdiv_ccg.grids;
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
|
||||
const int grid_verts_num = grids.size() * key.grid_area;
|
||||
BLI_assert(grid_verts_num == translations.size());
|
||||
BLI_assert(grid_verts_num == factors.size());
|
||||
|
||||
tls.vert_neighbors.reinitialize(grid_verts_num);
|
||||
calc_vert_neighbors_interior(
|
||||
faces, corner_verts, boundary_verts, subdiv_ccg, grids, tls.vert_neighbors);
|
||||
const Span<Vector<SubdivCCGCoord>> vert_neighbors = tls.vert_neighbors;
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
CCGElem *elem = elems[grids[i]];
|
||||
const int start = i * key.grid_area;
|
||||
for (const int y : IndexRange(key.grid_size)) {
|
||||
for (const int x : IndexRange(key.grid_size)) {
|
||||
const int offset = CCG_grid_xy_to_index(key.grid_size, x, y);
|
||||
const int grid_idx = start + offset;
|
||||
if (factors[grid_idx] == 0.0f) {
|
||||
translations[grid_idx] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
SubdivCCGCoord coord{};
|
||||
coord.grid_index = grids[i];
|
||||
coord.x = x;
|
||||
coord.y = y;
|
||||
|
||||
const Span<SubdivCCGCoord> vert_neighbor = vert_neighbors[start + offset];
|
||||
/* Don't modify corner vertices */
|
||||
if (vert_neighbor.size() <= 2) {
|
||||
translations[grid_idx] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_boundary = BKE_subdiv_ccg_coord_is_mesh_boundary(
|
||||
faces, corner_verts, boundary_verts, subdiv_ccg, coord);
|
||||
|
||||
Vector<SubdivCCGCoord, 16> neighbors;
|
||||
if (is_boundary) {
|
||||
neighbors = filtered_neighbors(
|
||||
vert_neighbor,
|
||||
relax_face_sets,
|
||||
[&](const SubdivCCGCoord &neighbor) {
|
||||
return face_set::vert_has_unique_face_set(
|
||||
vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, neighbor);
|
||||
},
|
||||
[&](const SubdivCCGCoord &neighbor) {
|
||||
return !BKE_subdiv_ccg_coord_is_mesh_boundary(
|
||||
faces, corner_verts, boundary_verts, subdiv_ccg, neighbor);
|
||||
});
|
||||
}
|
||||
else {
|
||||
neighbors = filtered_neighbors(
|
||||
vert_neighbor,
|
||||
relax_face_sets,
|
||||
[&](const SubdivCCGCoord &neighbor) {
|
||||
return face_set::vert_has_unique_face_set(
|
||||
vert_to_face_map, corner_verts, faces, face_sets, subdiv_ccg, neighbor);
|
||||
},
|
||||
{});
|
||||
}
|
||||
|
||||
/* Smoothed position calculation */
|
||||
float3 smoothed_position;
|
||||
const bool has_new_position = get_average_position(
|
||||
key, elems, positions, neighbors, grids[i], start, smoothed_position);
|
||||
|
||||
if (!has_new_position) {
|
||||
translations[grid_idx] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Normal Calculation */
|
||||
float3 normal;
|
||||
if (is_boundary) {
|
||||
bool has_boundary_normal = get_normal_boundary(
|
||||
key, elems, positions[grid_idx], neighbors, normal);
|
||||
|
||||
if (!has_boundary_normal) {
|
||||
normal = CCG_elem_offset_no(key, elem, offset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
normal = CCG_elem_offset_no(key, elem, offset);
|
||||
}
|
||||
|
||||
if (math::is_zero(normal)) {
|
||||
translations[grid_idx] = float3(0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float3 translation = translation_to_plane(
|
||||
positions[grid_idx], normal, smoothed_position);
|
||||
|
||||
translations[grid_idx] = translation * factors[grid_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_positions_grids(const Sculpt &sd,
|
||||
const Span<int> grids,
|
||||
Object &object,
|
||||
const Span<float3> positions,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions, translations);
|
||||
apply_translations(translations, grids, subdiv_ccg);
|
||||
}
|
||||
|
||||
static void do_relax_face_sets_brush_grids(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const float strength,
|
||||
const bool relax_face_sets)
|
||||
{
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
|
||||
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
||||
const OffsetIndices faces = mesh.faces();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
const VArraySpan hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
||||
|
||||
Array<int> node_offset_data;
|
||||
const OffsetIndices<int> node_vert_offsets = create_node_vert_offsets(
|
||||
nodes, key, node_offset_data);
|
||||
|
||||
Array<float3> current_positions(node_vert_offsets.total_size());
|
||||
Array<float3> translations(node_vert_offsets.total_size());
|
||||
Array<float> factors(node_vert_offsets.total_size());
|
||||
|
||||
threading::EnumerableThreadSpecific<GridLocalData> all_tls;
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
GridLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_factors_grids(brush,
|
||||
corner_verts,
|
||||
faces,
|
||||
*nodes[i],
|
||||
strength,
|
||||
relax_face_sets,
|
||||
object,
|
||||
tls,
|
||||
current_positions.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
factors.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
GridLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_relaxed_translations_grids(faces,
|
||||
corner_verts,
|
||||
ss.face_sets,
|
||||
ss.vert_to_face_map,
|
||||
ss.vertex_info.boundary,
|
||||
bke::pbvh::node_grid_indices(*nodes[i]),
|
||||
relax_face_sets,
|
||||
object,
|
||||
tls,
|
||||
factors.as_span().slice(node_vert_offsets[i]),
|
||||
current_positions.as_span().slice(node_vert_offsets[i]),
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
apply_positions_grids(sd,
|
||||
bke::pbvh::node_grid_indices(*nodes[i]),
|
||||
object,
|
||||
current_positions.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void calc_factors_bmesh(Object &object,
|
||||
const Brush &brush,
|
||||
PBVHNode &node,
|
||||
const float strength,
|
||||
const bool relax_face_sets,
|
||||
BMeshLocalData &tls,
|
||||
MutableSpan<float3> positions,
|
||||
MutableSpan<float> factors)
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
const StrokeCache &cache = *ss.cache;
|
||||
|
||||
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
|
||||
|
||||
gather_bmesh_positions(verts, positions);
|
||||
|
||||
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_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_vert_factors(object, *cache.automasking, node, verts, factors);
|
||||
}
|
||||
|
||||
scale_factors(factors, strength);
|
||||
|
||||
calc_brush_texture_factors(ss, brush, positions, factors);
|
||||
filter_factors_on_face_sets_bmesh(relax_face_sets, verts, factors);
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void calc_relaxed_translations_bmesh(const Set<BMVert *, 0> &verts,
|
||||
const Span<float3> positions,
|
||||
const bool relax_face_sets,
|
||||
BMeshLocalData &tls,
|
||||
const Span<float> factors,
|
||||
const MutableSpan<float3> translations)
|
||||
{
|
||||
BLI_assert(verts.size() == factors.size());
|
||||
BLI_assert(verts.size() == translations.size());
|
||||
|
||||
tls.vert_neighbors.reinitialize(verts.size());
|
||||
calc_vert_neighbors_interior(verts, tls.vert_neighbors);
|
||||
const Span<Vector<BMVert *>> vert_neighbors = tls.vert_neighbors;
|
||||
|
||||
int i = 0;
|
||||
for (const BMVert *vert : verts) {
|
||||
if (factors[i] == 0.0f) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Don't modify corner vertices */
|
||||
if (vert_neighbors[i].size() <= 2) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector<BMVert *, 16> neighbors;
|
||||
if (BM_vert_is_boundary(vert)) {
|
||||
neighbors = filtered_neighbors(
|
||||
vert_neighbors[i],
|
||||
relax_face_sets,
|
||||
[&](const BMVert *vert) { return face_set::vert_has_unique_face_set(vert); },
|
||||
[&](const BMVert *vert) { return !BM_vert_is_boundary(vert); });
|
||||
}
|
||||
else {
|
||||
neighbors = filtered_neighbors(
|
||||
vert_neighbors[i],
|
||||
relax_face_sets,
|
||||
[&](const BMVert *vert) { return face_set::vert_has_unique_face_set(vert); },
|
||||
{});
|
||||
}
|
||||
|
||||
/* Smoothed position calculation */
|
||||
float3 smoothed_position;
|
||||
const bool has_new_position = get_average_position(neighbors, smoothed_position);
|
||||
|
||||
if (!has_new_position) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Normal Calculation */
|
||||
float3 normal;
|
||||
if (BM_vert_is_boundary(vert)) {
|
||||
bool has_boundary_normal = get_normal_boundary(positions[i], neighbors, normal);
|
||||
|
||||
if (!has_boundary_normal) {
|
||||
normal = vert->no;
|
||||
}
|
||||
}
|
||||
else {
|
||||
normal = vert->no;
|
||||
}
|
||||
|
||||
if (math::is_zero(normal)) {
|
||||
translations[i] = float3(0.0f, 0.0f, 0.0f);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const float3 translation = translation_to_plane(positions[i], normal, smoothed_position);
|
||||
|
||||
translations[i] = translation * factors[i];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_NOINLINE static void apply_positions_bmesh(const Sculpt &sd,
|
||||
const Set<BMVert *, 0> verts,
|
||||
Object &object,
|
||||
const MutableSpan<float3> translations,
|
||||
const Span<float3> positions)
|
||||
|
||||
{
|
||||
SculptSession &ss = *object.sculpt;
|
||||
|
||||
clip_and_lock_translations(sd, ss, positions, translations);
|
||||
apply_translations(translations, verts);
|
||||
}
|
||||
|
||||
static void do_relax_face_sets_brush_bmesh(const Sculpt &sd,
|
||||
const Brush &brush,
|
||||
Object &object,
|
||||
const Span<PBVHNode *> nodes,
|
||||
const float strength,
|
||||
const bool relax_face_sets)
|
||||
{
|
||||
Array<int> node_offset_data;
|
||||
const OffsetIndices<int> node_vert_offsets = create_node_vert_offsets_bmesh(nodes,
|
||||
node_offset_data);
|
||||
|
||||
Array<float3> current_positions(node_vert_offsets.total_size());
|
||||
Array<float3> translations(node_vert_offsets.total_size());
|
||||
Array<float> factors(node_vert_offsets.total_size());
|
||||
|
||||
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) {
|
||||
calc_factors_bmesh(object,
|
||||
brush,
|
||||
*nodes[i],
|
||||
strength,
|
||||
relax_face_sets,
|
||||
tls,
|
||||
current_positions.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
factors.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
BMeshLocalData &tls = all_tls.local();
|
||||
for (const int i : range) {
|
||||
calc_relaxed_translations_bmesh(
|
||||
BKE_pbvh_bmesh_node_unique_verts(nodes[i]),
|
||||
current_positions.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
relax_face_sets,
|
||||
tls,
|
||||
factors.as_span().slice(node_vert_offsets[i]),
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
apply_positions_bmesh(sd,
|
||||
BKE_pbvh_bmesh_node_unique_verts(nodes[i]),
|
||||
object,
|
||||
translations.as_mutable_span().slice(node_vert_offsets[i]),
|
||||
current_positions.as_span().slice(node_vert_offsets[i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace relax_face_sets_cc
|
||||
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes)
|
||||
{
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
SCULPT_boundary_info_ensure(object);
|
||||
|
||||
const SculptSession &ss = *object.sculpt;
|
||||
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
||||
ss.cache->iteration_count);
|
||||
|
||||
/* On every third step of the stroke, behave more similarly to the Topology Relax brush */
|
||||
const bool relax_face_sets = !(ss.cache->iteration_count % 3 == 0);
|
||||
|
||||
for (const float strength : strengths) {
|
||||
switch (BKE_pbvh_type(*ss.pbvh)) {
|
||||
case PBVH_FACES:
|
||||
do_relax_face_sets_brush_mesh(
|
||||
sd, brush, object, nodes, strength * strength, relax_face_sets);
|
||||
break;
|
||||
case PBVH_GRIDS:
|
||||
do_relax_face_sets_brush_grids(
|
||||
sd, brush, object, nodes, strength * strength, relax_face_sets);
|
||||
break;
|
||||
case PBVH_BMESH:
|
||||
do_relax_face_sets_brush_bmesh(
|
||||
sd, brush, object, nodes, strength * strength, relax_face_sets);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace blender::ed::sculpt_paint
|
||||
@@ -41,6 +41,7 @@ void do_mask_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_multiplane_scrape_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_pinch_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_nudge_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_rotate_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
void do_scrape_brush(const Sculpt &sd, Object &object, Span<PBVHNode *> nodes);
|
||||
/** Smooth positions with neighboring vertices. */
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_subdiv_ccg.hh"
|
||||
|
||||
#include "DNA_brush_enums.h"
|
||||
|
||||
#include "sculpt_intern.hh"
|
||||
@@ -43,6 +45,8 @@ struct PBVHNode;
|
||||
struct Sculpt;
|
||||
struct SculptSession;
|
||||
struct SubdivCCG;
|
||||
struct SubdivCCGCoord;
|
||||
struct SubdivCCGNeighbors;
|
||||
|
||||
namespace blender::ed::sculpt_paint {
|
||||
struct StrokeCache;
|
||||
@@ -318,6 +322,10 @@ void write_translations(const Sculpt &sd,
|
||||
* new array.
|
||||
*/
|
||||
OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes, Array<int> &node_data);
|
||||
OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes,
|
||||
const CCGKey &key,
|
||||
Array<int> &node_data);
|
||||
OffsetIndices<int> create_node_vert_offsets_bmesh(Span<PBVHNode *> nodes, Array<int> &node_data);
|
||||
|
||||
/**
|
||||
* Find vertices connected to the indexed vertices across faces.
|
||||
@@ -354,6 +362,14 @@ void calc_vert_neighbors_interior(OffsetIndices<int> faces,
|
||||
Span<bool> hide_poly,
|
||||
Span<int> verts,
|
||||
MutableSpan<Vector<int>> result);
|
||||
void calc_vert_neighbors_interior(OffsetIndices<int> faces,
|
||||
Span<int> corner_verts,
|
||||
BitSpan boundary_verts,
|
||||
const SubdivCCG &subdiv_ccg,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<Vector<SubdivCCGCoord>> result);
|
||||
void calc_vert_neighbors_interior(const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<Vector<BMVert *>> result);
|
||||
|
||||
/** Find the translation from each vertex position to the closest point on the plane. */
|
||||
void calc_translations_to_plane(Span<float3> vert_positions,
|
||||
|
||||
@@ -4047,7 +4047,7 @@ static void do_brush_action(const Scene &scene,
|
||||
do_draw_face_sets_brush(sd, ob, nodes);
|
||||
}
|
||||
else {
|
||||
face_set::do_relax_face_sets_brush(sd, ob, nodes);
|
||||
do_relax_face_sets_brush(sd, ob, nodes);
|
||||
}
|
||||
break;
|
||||
case SCULPT_TOOL_DISPLACEMENT_ERASER:
|
||||
@@ -7480,6 +7480,26 @@ OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes, Array<int> &
|
||||
return offset_indices::accumulate_counts_to_offsets(node_data);
|
||||
}
|
||||
|
||||
OffsetIndices<int> create_node_vert_offsets(Span<PBVHNode *> nodes,
|
||||
const CCGKey &key,
|
||||
Array<int> &node_data)
|
||||
{
|
||||
node_data.reinitialize(nodes.size() + 1);
|
||||
for (const int i : nodes.index_range()) {
|
||||
node_data[i] = bke::pbvh::node_grid_indices(*nodes[i]).size() * key.grid_area;
|
||||
}
|
||||
return offset_indices::accumulate_counts_to_offsets(node_data);
|
||||
}
|
||||
|
||||
OffsetIndices<int> create_node_vert_offsets_bmesh(Span<PBVHNode *> nodes, Array<int> &node_data)
|
||||
{
|
||||
node_data.reinitialize(nodes.size() + 1);
|
||||
for (const int i : nodes.index_range()) {
|
||||
node_data[i] = BKE_pbvh_bmesh_node_unique_verts(nodes[i]).size();
|
||||
}
|
||||
return offset_indices::accumulate_counts_to_offsets(node_data);
|
||||
}
|
||||
|
||||
void calc_vert_neighbors(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const GroupedSpan<int> vert_to_face,
|
||||
@@ -7546,6 +7566,72 @@ void calc_vert_neighbors_interior(const OffsetIndices<int> faces,
|
||||
}
|
||||
}
|
||||
|
||||
void calc_vert_neighbors_interior(const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
const BitSpan boundary_verts,
|
||||
const SubdivCCG &subdiv_ccg,
|
||||
const Span<int> grids,
|
||||
const MutableSpan<Vector<SubdivCCGCoord>> result)
|
||||
{
|
||||
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
||||
|
||||
BLI_assert(grids.size() * key.grid_area == result.size());
|
||||
|
||||
for (const int i : grids.index_range()) {
|
||||
const int grid = grids[i];
|
||||
const int node_verts_start = i * key.grid_area;
|
||||
|
||||
/* TODO: This loop could be optimized in the future by skipping unnecessary logic for
|
||||
* non-boundary grid vertices. */
|
||||
for (const int y : IndexRange(key.grid_size)) {
|
||||
for (const int x : IndexRange(key.grid_size)) {
|
||||
const int offset = CCG_grid_xy_to_index(key.grid_size, x, y);
|
||||
const int node_vert_index = node_verts_start + offset;
|
||||
|
||||
SubdivCCGCoord coord{};
|
||||
coord.grid_index = grid;
|
||||
coord.x = x;
|
||||
coord.y = y;
|
||||
|
||||
SubdivCCGNeighbors neighbors;
|
||||
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, false, neighbors);
|
||||
|
||||
if (BKE_subdiv_ccg_coord_is_mesh_boundary(
|
||||
faces, corner_verts, boundary_verts, subdiv_ccg, coord))
|
||||
{
|
||||
if (neighbors.coords.size() == 2) {
|
||||
/* Do not include neighbors of corner vertices. */
|
||||
neighbors.coords.clear();
|
||||
}
|
||||
else {
|
||||
/* Only include other boundary vertices as neighbors of boundary vertices. */
|
||||
neighbors.coords.remove_if([&](const SubdivCCGCoord coord) {
|
||||
return !BKE_subdiv_ccg_coord_is_mesh_boundary(
|
||||
faces, corner_verts, boundary_verts, subdiv_ccg, coord);
|
||||
});
|
||||
}
|
||||
}
|
||||
result[node_vert_index] = neighbors.coords;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calc_vert_neighbors_interior(const Set<BMVert *, 0> &verts,
|
||||
MutableSpan<Vector<BMVert *>> result)
|
||||
{
|
||||
BLI_assert(verts.size() == result.size());
|
||||
Vector<BMVert *, 64> neighbor_data;
|
||||
|
||||
int i = 0;
|
||||
for (BMVert *vert : verts) {
|
||||
neighbor_data.clear();
|
||||
vert_neighbors_get_interior_bmesh(*vert, neighbor_data);
|
||||
result[i] = neighbor_data;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void calc_translations_to_plane(const Span<float3> vert_positions,
|
||||
const Span<int> verts,
|
||||
const float4 &plane,
|
||||
|
||||
@@ -207,89 +207,6 @@ Array<int> duplicate_face_sets(const Mesh &mesh)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Draw Face Sets Brush
|
||||
* \{ */
|
||||
|
||||
static void do_relax_face_sets_brush_task(Object &ob,
|
||||
const Brush &brush,
|
||||
const float strength,
|
||||
PBVHNode *node)
|
||||
{
|
||||
SculptSession &ss = *ob.sculpt;
|
||||
|
||||
PBVHVertexIter vd;
|
||||
|
||||
SculptBrushTest test;
|
||||
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
||||
ss, test, brush.falloff_shape);
|
||||
|
||||
const bool relax_face_sets = !(ss.cache->iteration_count % 3 == 0);
|
||||
|
||||
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) {
|
||||
auto_mask::node_update(automask_data, vd);
|
||||
|
||||
if (!sculpt_brush_test_sq_fn(test, vd.co)) {
|
||||
continue;
|
||||
}
|
||||
if (relax_face_sets == vert_has_unique_face_set(ss, vd.vertex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const 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);
|
||||
|
||||
smooth::relax_vertex(ss, &vd, fade * strength * strength, relax_face_sets, vd.co);
|
||||
}
|
||||
BKE_pbvh_vertex_iter_end;
|
||||
}
|
||||
|
||||
static std::array<float, 4> iteration_strengths(const float strength, const int stroke_iteration)
|
||||
{
|
||||
if (stroke_iteration % 3 == 0) {
|
||||
return {strength, strength, strength, strength};
|
||||
}
|
||||
|
||||
/* This operations needs a strength tweak as the relax deformation is too weak by default. */
|
||||
const float modified_strength = strength * 1.5f;
|
||||
return {modified_strength, modified_strength, strength, strength};
|
||||
}
|
||||
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes)
|
||||
{
|
||||
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
||||
|
||||
BKE_curvemapping_init(brush.curve);
|
||||
|
||||
SCULPT_boundary_info_ensure(ob);
|
||||
|
||||
const SculptSession &ss = *ob.sculpt;
|
||||
const std::array<float, 4> strengths = iteration_strengths(ss.cache->bstrength,
|
||||
ss.cache->iteration_count);
|
||||
for (const float strength : strengths) {
|
||||
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
do_relax_face_sets_brush_task(ob, brush, strength, nodes[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Global Mesh Operators
|
||||
* Operators that work on the mesh as a whole.
|
||||
|
||||
@@ -2003,12 +2003,6 @@ void multiplane_scrape_preview_draw(uint gpuattr,
|
||||
const float outline_col[3],
|
||||
float outline_alpha);
|
||||
|
||||
namespace face_set {
|
||||
|
||||
void do_relax_face_sets_brush(const Sculpt &sd, Object &ob, Span<PBVHNode *> nodes);
|
||||
|
||||
}
|
||||
|
||||
namespace color {
|
||||
|
||||
/* Swaps colors at each element in indices with values in colors. */
|
||||
|
||||
Reference in New Issue
Block a user