Derived from discussions on D8893 Pull Request: https://projects.blender.org/blender/blender/pulls/138417
7932 lines
283 KiB
C++
7932 lines
283 KiB
C++
/* SPDX-FileCopyrightText: 2006 by Nicholas Bishop. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
* Implements the Sculpt Mode Brushes.
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#include "BLI_array_utils.hh"
|
|
#include "BLI_atomic_disjoint_set.hh"
|
|
#include "BLI_dial_2d.h"
|
|
#include "BLI_enumerable_thread_specific.hh"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_math_axis_angle.hh"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_matrix.h"
|
|
#include "BLI_math_matrix.hh"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_set.hh"
|
|
#include "BLI_span.hh"
|
|
#include "BLI_task.h"
|
|
#include "BLI_task.hh"
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "DNA_brush_types.h"
|
|
#include "DNA_customdata_types.h"
|
|
#include "DNA_key_types.h"
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BKE_attribute.hh"
|
|
#include "BKE_brush.hh"
|
|
#include "BKE_ccg.hh"
|
|
#include "BKE_colortools.hh"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_global.hh"
|
|
#include "BKE_image.hh"
|
|
#include "BKE_key.hh"
|
|
#include "BKE_layer.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_modifier.hh"
|
|
#include "BKE_multires.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_object.hh"
|
|
#include "BKE_object_types.hh"
|
|
#include "BKE_paint.hh"
|
|
#include "BKE_paint_bvh.hh"
|
|
#include "BKE_report.hh"
|
|
#include "BKE_subdiv_ccg.hh"
|
|
#include "BKE_subsurf.hh"
|
|
#include "BLI_math_rotation_legacy.hh"
|
|
#include "BLI_math_vector.hh"
|
|
|
|
#include "NOD_texture.h"
|
|
|
|
#include "DEG_depsgraph.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_toolsystem.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "ED_gpencil_legacy.hh"
|
|
#include "ED_paint.hh"
|
|
#include "ED_screen.hh"
|
|
#include "ED_sculpt.hh"
|
|
#include "ED_view3d.hh"
|
|
|
|
#include "paint_intern.hh"
|
|
#include "sculpt_automask.hh"
|
|
#include "sculpt_boundary.hh"
|
|
#include "sculpt_cloth.hh"
|
|
#include "sculpt_color.hh"
|
|
#include "sculpt_dyntopo.hh"
|
|
#include "sculpt_face_set.hh"
|
|
#include "sculpt_filter.hh"
|
|
#include "sculpt_hide.hh"
|
|
#include "sculpt_intern.hh"
|
|
#include "sculpt_islands.hh"
|
|
#include "sculpt_pose.hh"
|
|
#include "sculpt_undo.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
#include "RNA_define.hh"
|
|
|
|
#include "bmesh.hh"
|
|
|
|
#include "editors/sculpt_paint/brushes/brushes.hh"
|
|
#include "mesh_brush_common.hh"
|
|
|
|
using blender::float3;
|
|
using blender::MutableSpan;
|
|
using blender::Set;
|
|
using blender::Span;
|
|
using blender::Vector;
|
|
|
|
static CLG_LogRef LOG = {"ed.sculpt_paint"};
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
float sculpt_calc_radius(const ViewContext &vc,
|
|
const Brush &brush,
|
|
const Scene &scene,
|
|
const float3 location)
|
|
{
|
|
if (!BKE_brush_use_locked_size(&scene, &brush)) {
|
|
return paint_calc_object_space_radius(vc, location, BKE_brush_size_get(&scene, &brush));
|
|
}
|
|
return BKE_brush_unprojected_radius_get(&scene, &brush);
|
|
}
|
|
|
|
bool report_if_shape_key_is_locked(const Object &ob, ReportList *reports)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
|
|
if (ss.shapekey_active && (ss.shapekey_active->flag & KEYBLOCK_LOCKED_SHAPE) != 0) {
|
|
if (reports) {
|
|
BKE_reportf(reports, RPT_ERROR, "The active shape key of %s is locked", ob.id.name + 2);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
void SCULPT_vertex_random_access_ensure(Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
if (blender::bke::object::pbvh_get(object)->type() == blender::bke::pbvh::Type::BMesh) {
|
|
BM_mesh_elem_index_ensure(ss.bm, BM_VERT);
|
|
BM_mesh_elem_table_ensure(ss.bm, BM_VERT);
|
|
}
|
|
}
|
|
|
|
int SCULPT_vertex_count_get(const Object &object)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
switch (blender::bke::object::pbvh_get(object)->type()) {
|
|
case blender::bke::pbvh::Type::Mesh:
|
|
BLI_assert(object.type == OB_MESH);
|
|
return static_cast<const Mesh *>(object.data)->verts_num;
|
|
case blender::bke::pbvh::Type::BMesh:
|
|
return BM_mesh_elem_count(ss.bm, BM_VERT);
|
|
case blender::bke::pbvh::Type::Grids:
|
|
return BKE_pbvh_get_grid_num_verts(object);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
Span<float3> vert_positions_for_grab_active_get(const Depsgraph &depsgraph, const Object &object)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
BLI_assert(bke::object::pbvh_get(object)->type() == bke::pbvh::Type::Mesh);
|
|
if (ss.shapekey_active) {
|
|
/* Always grab active shape key if the sculpt happens on shapekey. */
|
|
return bke::pbvh::vert_positions_eval(depsgraph, object);
|
|
}
|
|
/* Otherwise use the base mesh positions. */
|
|
const Mesh &mesh = *static_cast<const Mesh *>(object.data);
|
|
return mesh.vert_positions();
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
ePaintSymmetryFlags SCULPT_mesh_symmetry_xyz_get(const Object &object)
|
|
{
|
|
const Mesh *mesh = static_cast<const Mesh *>(object.data);
|
|
return ePaintSymmetryFlags(mesh->symmetry);
|
|
}
|
|
|
|
/* Sculpt Face Sets and Visibility. */
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
namespace face_set {
|
|
|
|
int active_face_set_get(const Object &object)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
switch (bke::object::pbvh_get(object)->type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(object.data);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArray face_sets = *attributes.lookup<int>(".sculpt_face_set", bke::AttrDomain::Face);
|
|
if (!face_sets || !ss.active_face_index) {
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
return face_sets[*ss.active_face_index];
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(object.data);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArray face_sets = *attributes.lookup<int>(".sculpt_face_set", bke::AttrDomain::Face);
|
|
if (!face_sets || !ss.active_grid_index) {
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
const int face_index = BKE_subdiv_ccg_grid_to_face_index(*ss.subdiv_ccg,
|
|
*ss.active_grid_index);
|
|
return face_sets[face_index];
|
|
}
|
|
case bke::pbvh::Type::BMesh:
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
|
|
} // namespace face_set
|
|
|
|
namespace face_set {
|
|
|
|
int vert_face_set_get(const GroupedSpan<int> vert_to_face_map,
|
|
const Span<int> face_sets,
|
|
const int vert)
|
|
{
|
|
int face_set = SCULPT_FACE_SET_NONE;
|
|
for (const int face : vert_to_face_map[vert]) {
|
|
face_set = std::max(face_sets[face], face_set);
|
|
}
|
|
return face_set;
|
|
}
|
|
|
|
int vert_face_set_get(const SubdivCCG &subdiv_ccg, const Span<int> face_sets, const int grid)
|
|
{
|
|
const int face = BKE_subdiv_ccg_grid_to_face_index(subdiv_ccg, grid);
|
|
return face_sets[face];
|
|
}
|
|
|
|
int vert_face_set_get(const int /*face_set_offset*/, const BMVert & /*vert*/)
|
|
{
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
|
|
bool vert_has_face_set(const GroupedSpan<int> vert_to_face_map,
|
|
const Span<int> face_sets,
|
|
const int vert,
|
|
const int face_set)
|
|
{
|
|
if (face_sets.is_empty()) {
|
|
return face_set == SCULPT_FACE_SET_NONE;
|
|
}
|
|
const Span<int> faces = vert_to_face_map[vert];
|
|
return std::any_of(
|
|
faces.begin(), faces.end(), [&](const int face) { return face_sets[face] == face_set; });
|
|
}
|
|
|
|
bool vert_has_face_set(const SubdivCCG &subdiv_ccg,
|
|
const Span<int> face_sets,
|
|
const int grid,
|
|
const int face_set)
|
|
{
|
|
if (face_sets.is_empty()) {
|
|
return face_set == SCULPT_FACE_SET_NONE;
|
|
}
|
|
const int face = BKE_subdiv_ccg_grid_to_face_index(subdiv_ccg, grid);
|
|
return face_sets[face] == face_set;
|
|
}
|
|
|
|
bool vert_has_face_set(const int face_set_offset, const BMVert &vert, const int face_set)
|
|
{
|
|
if (face_set_offset == -1) {
|
|
return face_set == SCULPT_FACE_SET_NONE;
|
|
}
|
|
BMIter iter;
|
|
BMFace *face;
|
|
BM_ITER_ELEM (face, &iter, &const_cast<BMVert &>(vert), BM_FACES_OF_VERT) {
|
|
if (BM_ELEM_CD_GET_INT(face, face_set_offset) == face_set) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool vert_has_unique_face_set(const GroupedSpan<int> vert_to_face_map,
|
|
const Span<int> face_sets,
|
|
int vert)
|
|
{
|
|
/* TODO: Move this check higher out of this function. */
|
|
if (face_sets.is_empty()) {
|
|
return true;
|
|
}
|
|
int face_set = -1;
|
|
for (const int face_index : vert_to_face_map[vert]) {
|
|
if (face_set == -1) {
|
|
face_set = face_sets[face_index];
|
|
}
|
|
else {
|
|
if (face_sets[face_index] != face_set) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks if the face sets of the adjacent faces to the edge between \a v1 and \a v2
|
|
* in the base mesh are equal.
|
|
*/
|
|
static bool sculpt_check_unique_face_set_for_edge_in_base_mesh(
|
|
const GroupedSpan<int> vert_to_face_map,
|
|
const Span<int> face_sets,
|
|
const Span<int> corner_verts,
|
|
const OffsetIndices<int> faces,
|
|
int v1,
|
|
int v2)
|
|
{
|
|
const Span<int> vert_map = vert_to_face_map[v1];
|
|
int p1 = -1, p2 = -1;
|
|
for (int i = 0; i < vert_map.size(); i++) {
|
|
const int face_i = vert_map[i];
|
|
for (const int corner : faces[face_i]) {
|
|
if (corner_verts[corner] == v2) {
|
|
if (p1 == -1) {
|
|
p1 = vert_map[i];
|
|
break;
|
|
}
|
|
|
|
if (p2 == -1) {
|
|
p2 = vert_map[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p1 != -1 && p2 != -1) {
|
|
return face_sets[p1] == face_sets[p2];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool vert_has_unique_face_set(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face_map,
|
|
const Span<int> face_sets,
|
|
const SubdivCCG &subdiv_ccg,
|
|
SubdivCCGCoord coord)
|
|
{
|
|
/* TODO: Move this check higher out of this function. */
|
|
if (face_sets.is_empty()) {
|
|
return true;
|
|
}
|
|
int v1, v2;
|
|
const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get(
|
|
subdiv_ccg, coord, corner_verts, faces, v1, v2);
|
|
switch (adjacency) {
|
|
case SUBDIV_CCG_ADJACENT_VERTEX:
|
|
return vert_has_unique_face_set(vert_to_face_map, face_sets, v1);
|
|
case SUBDIV_CCG_ADJACENT_EDGE:
|
|
return sculpt_check_unique_face_set_for_edge_in_base_mesh(
|
|
vert_to_face_map, face_sets, corner_verts, faces, v1, v2);
|
|
case SUBDIV_CCG_ADJACENT_NONE:
|
|
return true;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return true;
|
|
}
|
|
|
|
bool vert_has_unique_face_set(const int /*face_set_offset*/, const BMVert & /*vert*/)
|
|
{
|
|
/* TODO: Obviously not fully implemented yet. Needs to be implemented for Relax Face Sets brush
|
|
* to work. */
|
|
return true;
|
|
}
|
|
|
|
} // namespace face_set
|
|
|
|
Span<BMVert *> vert_neighbors_get_bmesh(BMVert &vert, BMeshNeighborVerts &r_neighbors)
|
|
{
|
|
r_neighbors.clear();
|
|
BMIter liter;
|
|
BMLoop *l;
|
|
BM_ITER_ELEM (l, &liter, &vert, BM_LOOPS_OF_VERT) {
|
|
for (BMVert *other_vert : {l->prev->v, l->next->v}) {
|
|
if (other_vert != &vert) {
|
|
r_neighbors.append(other_vert);
|
|
}
|
|
}
|
|
}
|
|
return r_neighbors;
|
|
}
|
|
|
|
Span<BMVert *> vert_neighbors_get_interior_bmesh(BMVert &vert, BMeshNeighborVerts &r_neighbors)
|
|
{
|
|
r_neighbors.clear();
|
|
BMIter liter;
|
|
BMLoop *l;
|
|
BM_ITER_ELEM (l, &liter, &vert, BM_LOOPS_OF_VERT) {
|
|
for (BMVert *other_vert : {l->prev->v, l->next->v}) {
|
|
if (other_vert != &vert) {
|
|
r_neighbors.append(other_vert);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BM_vert_is_boundary(&vert)) {
|
|
if (r_neighbors.size() == 2) {
|
|
/* Do not include neighbors of corner vertices. */
|
|
r_neighbors.clear();
|
|
}
|
|
else {
|
|
/* Only include other boundary vertices as neighbors of boundary vertices. */
|
|
r_neighbors.remove_if([&](const BMVert *vert) { return !BM_vert_is_boundary(vert); });
|
|
}
|
|
}
|
|
|
|
return r_neighbors;
|
|
}
|
|
|
|
Span<int> vert_neighbors_get_mesh(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const Span<bool> hide_poly,
|
|
const int vert,
|
|
Vector<int> &r_neighbors)
|
|
{
|
|
r_neighbors.clear();
|
|
|
|
for (const int face : vert_to_face[vert]) {
|
|
if (!hide_poly.is_empty() && hide_poly[face]) {
|
|
continue;
|
|
}
|
|
const int2 verts = bke::mesh::face_find_adjacent_verts(faces[face], corner_verts, vert);
|
|
r_neighbors.append_non_duplicates(verts[0]);
|
|
r_neighbors.append_non_duplicates(verts[1]);
|
|
}
|
|
|
|
return r_neighbors.as_span();
|
|
}
|
|
|
|
inline void append_neighbors_to_vector(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const Span<bool> hide_poly,
|
|
const int vert,
|
|
Vector<int> &r_data)
|
|
{
|
|
const int vert_start = r_data.size();
|
|
for (const int face : vert_to_face[vert]) {
|
|
if (!hide_poly.is_empty() && hide_poly[face]) {
|
|
continue;
|
|
}
|
|
/* In order to support non-manifold topology, both neighboring vertices are added for each
|
|
* face corner. That results in half being duplicates for any "normal" topology. */
|
|
const int2 neighbors = bke::mesh::face_find_adjacent_verts(faces[face], corner_verts, vert);
|
|
for (const int neighbor : {neighbors[0], neighbors[1]}) {
|
|
bool found = false;
|
|
for (int i = r_data.size() - 1; i >= vert_start; i--) {
|
|
if (r_data[i] == neighbor) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
continue;
|
|
}
|
|
r_data.append(neighbor);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace boundary {
|
|
|
|
bool vert_is_boundary(const GroupedSpan<int> vert_to_face_map,
|
|
const Span<bool> hide_poly,
|
|
const BitSpan boundary,
|
|
const int vert)
|
|
{
|
|
if (!hide::vert_all_faces_visible_get(hide_poly, vert_to_face_map, vert)) {
|
|
return true;
|
|
}
|
|
return boundary[vert].test();
|
|
}
|
|
|
|
bool vert_is_boundary(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const BitSpan boundary,
|
|
const SubdivCCG &subdiv_ccg,
|
|
const SubdivCCGCoord vert)
|
|
{
|
|
/* TODO: Unlike the base mesh implementation this method does NOT take into account face
|
|
* visibility. Either this should be noted as a intentional limitation or fixed. */
|
|
int v1, v2;
|
|
const SubdivCCGAdjacencyType adjacency = BKE_subdiv_ccg_coarse_mesh_adjacency_info_get(
|
|
subdiv_ccg, vert, corner_verts, faces, v1, v2);
|
|
switch (adjacency) {
|
|
case SUBDIV_CCG_ADJACENT_VERTEX:
|
|
return boundary[v1].test();
|
|
case SUBDIV_CCG_ADJACENT_EDGE:
|
|
return boundary[v1].test() && boundary[v2].test();
|
|
case SUBDIV_CCG_ADJACENT_NONE:
|
|
return false;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return false;
|
|
}
|
|
|
|
bool vert_is_boundary(BMVert *vert)
|
|
{
|
|
/* TODO: Unlike the base mesh implementation this method does NOT take into account face
|
|
* visibility. Either this should be noted as a intentional limitation or fixed. */
|
|
return BM_vert_is_boundary(vert);
|
|
}
|
|
|
|
} // namespace boundary
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
/* Utilities */
|
|
|
|
bool SCULPT_stroke_is_main_symmetry_pass(const blender::ed::sculpt_paint::StrokeCache &cache)
|
|
{
|
|
return cache.mirror_symmetry_pass == 0 && cache.radial_symmetry_pass == 0 &&
|
|
cache.tile_pass == 0;
|
|
}
|
|
|
|
bool SCULPT_stroke_is_first_brush_step(const blender::ed::sculpt_paint::StrokeCache &cache)
|
|
{
|
|
return cache.first_time && cache.mirror_symmetry_pass == 0 && cache.radial_symmetry_pass == 0 &&
|
|
cache.tile_pass == 0;
|
|
}
|
|
|
|
bool SCULPT_stroke_is_first_brush_step_of_symmetry_pass(
|
|
const blender::ed::sculpt_paint::StrokeCache &cache)
|
|
{
|
|
return cache.first_time;
|
|
}
|
|
|
|
bool SCULPT_check_vertex_pivot_symmetry(const float vco[3], const float pco[3], const char symm)
|
|
{
|
|
bool is_in_symmetry_area = true;
|
|
for (int i = 0; i < 3; i++) {
|
|
char symm_it = 1 << i;
|
|
if (symm & symm_it) {
|
|
if (pco[i] == 0.0f) {
|
|
if (vco[i] > 0.0f) {
|
|
is_in_symmetry_area = false;
|
|
}
|
|
}
|
|
if (vco[i] * pco[i] < 0.0f) {
|
|
is_in_symmetry_area = false;
|
|
}
|
|
}
|
|
}
|
|
return is_in_symmetry_area;
|
|
}
|
|
|
|
void sculpt_project_v3_normal_align(const SculptSession &ss,
|
|
const float normal_weight,
|
|
float grab_delta[3])
|
|
{
|
|
/* Signed to support grabbing in (to make a hole) as well as out. */
|
|
const float len_signed = dot_v3v3(ss.cache->sculpt_normal_symm, grab_delta);
|
|
|
|
/* This scale effectively projects the offset so dragging follows the cursor,
|
|
* as the normal points towards the view, the scale increases. */
|
|
float len_view_scale;
|
|
{
|
|
float view_aligned_normal[3];
|
|
project_plane_v3_v3v3(
|
|
view_aligned_normal, ss.cache->sculpt_normal_symm, ss.cache->view_normal_symm);
|
|
len_view_scale = fabsf(dot_v3v3(view_aligned_normal, ss.cache->sculpt_normal_symm));
|
|
len_view_scale = (len_view_scale > FLT_EPSILON) ? 1.0f / len_view_scale : 1.0f;
|
|
}
|
|
|
|
mul_v3_fl(grab_delta, 1.0f - normal_weight);
|
|
madd_v3_v3fl(
|
|
grab_delta, ss.cache->sculpt_normal_symm, (len_signed * normal_weight) * len_view_scale);
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
std::optional<int> nearest_vert_calc_mesh(const bke::pbvh::Tree &pbvh,
|
|
const Span<float3> vert_positions,
|
|
const Span<bool> hide_vert,
|
|
const float3 &location,
|
|
const float max_distance,
|
|
const bool use_original)
|
|
{
|
|
const float max_distance_sq = max_distance * max_distance;
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, use_original);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
struct NearestData {
|
|
int vert = -1;
|
|
float distance_sq = std::numeric_limits<float>::max();
|
|
};
|
|
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
const NearestData nearest = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestData(),
|
|
[&](const IndexRange range, NearestData nearest) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
for (const int vert : nodes[i].verts()) {
|
|
if (!hide_vert.is_empty() && hide_vert[vert]) {
|
|
continue;
|
|
}
|
|
const float distance_sq = math::distance_squared(vert_positions[vert], location);
|
|
if (distance_sq < nearest.distance_sq) {
|
|
nearest = {vert, distance_sq};
|
|
}
|
|
}
|
|
});
|
|
return nearest;
|
|
},
|
|
[](const NearestData a, const NearestData b) {
|
|
return a.distance_sq < b.distance_sq ? a : b;
|
|
});
|
|
return nearest.vert;
|
|
}
|
|
|
|
std::optional<SubdivCCGCoord> nearest_vert_calc_grids(const bke::pbvh::Tree &pbvh,
|
|
const SubdivCCG &subdiv_ccg,
|
|
const float3 &location,
|
|
const float max_distance,
|
|
const bool use_original)
|
|
{
|
|
const float max_distance_sq = max_distance * max_distance;
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, use_original);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
struct NearestData {
|
|
SubdivCCGCoord coord = {};
|
|
float distance_sq = std::numeric_limits<float>::max();
|
|
};
|
|
|
|
const BitGroupVector<> grid_hidden = subdiv_ccg.grid_hidden;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
const Span<float3> positions = subdiv_ccg.positions;
|
|
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
const NearestData nearest = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestData(),
|
|
[&](const IndexRange range, NearestData nearest) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
for (const int grid : nodes[i].grids()) {
|
|
const IndexRange grid_range = bke::ccg::grid_range(key, grid);
|
|
BKE_subdiv_ccg_foreach_visible_grid_vert(key, grid_hidden, grid, [&](const int i) {
|
|
const float distance_sq = math::distance_squared(positions[grid_range[i]], location);
|
|
if (distance_sq < nearest.distance_sq) {
|
|
SubdivCCGCoord coord{};
|
|
coord.grid_index = grid;
|
|
coord.x = i % key.grid_size;
|
|
coord.y = i / key.grid_size;
|
|
nearest = {coord, distance_sq};
|
|
}
|
|
});
|
|
}
|
|
});
|
|
return nearest;
|
|
},
|
|
[](const NearestData a, const NearestData b) {
|
|
return a.distance_sq < b.distance_sq ? a : b;
|
|
});
|
|
return nearest.coord;
|
|
}
|
|
|
|
std::optional<BMVert *> nearest_vert_calc_bmesh(const bke::pbvh::Tree &pbvh,
|
|
const float3 &location,
|
|
const float max_distance,
|
|
const bool use_original)
|
|
{
|
|
const float max_distance_sq = max_distance * max_distance;
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, use_original);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
struct NearestData {
|
|
BMVert *vert = nullptr;
|
|
float distance_sq = std::numeric_limits<float>::max();
|
|
};
|
|
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
const NearestData nearest = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestData(),
|
|
[&](const IndexRange range, NearestData nearest) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
for (BMVert *vert :
|
|
BKE_pbvh_bmesh_node_unique_verts(const_cast<bke::pbvh::BMeshNode *>(&nodes[i])))
|
|
{
|
|
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
|
continue;
|
|
}
|
|
const float distance_sq = math::distance_squared(float3(vert->co), location);
|
|
if (distance_sq < nearest.distance_sq) {
|
|
nearest = {vert, distance_sq};
|
|
}
|
|
}
|
|
});
|
|
return nearest;
|
|
},
|
|
[](const NearestData a, const NearestData b) {
|
|
return a.distance_sq < b.distance_sq ? a : b;
|
|
});
|
|
return nearest.vert;
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
bool SCULPT_is_vertex_inside_brush_radius_symm(const float vertex[3],
|
|
const float br_co[3],
|
|
float radius,
|
|
char symm)
|
|
{
|
|
for (char i = 0; i <= symm; ++i) {
|
|
if (!blender::ed::sculpt_paint::is_symmetry_iteration_valid(i, symm)) {
|
|
continue;
|
|
}
|
|
float3 location = blender::ed::sculpt_paint::symmetry_flip(br_co, ePaintSymmetryFlags(i));
|
|
if (len_squared_v3v3(location, vertex) < radius * radius) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SCULPT_tag_update_overlays(bContext *C)
|
|
{
|
|
ARegion *region = CTX_wm_region(C);
|
|
ED_region_tag_redraw(region);
|
|
|
|
Object &ob = *CTX_data_active_object(C);
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &ob);
|
|
|
|
DEG_id_tag_update(&ob.id, ID_RECALC_SHADING);
|
|
|
|
RegionView3D *rv3d = CTX_wm_region_view3d(C);
|
|
if (!BKE_sculptsession_use_pbvh_draw(&ob, rv3d)) {
|
|
DEG_id_tag_update(&ob.id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Brush Capabilities
|
|
*
|
|
* Avoid duplicate checks, internal logic only,
|
|
* share logic with #rna_def_sculpt_capabilities where possible.
|
|
* \{ */
|
|
|
|
static bool brush_type_needs_original(const char sculpt_brush_type)
|
|
{
|
|
return ELEM(sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_GRAB,
|
|
SCULPT_BRUSH_TYPE_ROTATE,
|
|
SCULPT_BRUSH_TYPE_THUMB,
|
|
SCULPT_BRUSH_TYPE_LAYER,
|
|
SCULPT_BRUSH_TYPE_DRAW_SHARP,
|
|
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM,
|
|
SCULPT_BRUSH_TYPE_SMOOTH,
|
|
SCULPT_BRUSH_TYPE_BOUNDARY,
|
|
SCULPT_BRUSH_TYPE_POSE);
|
|
}
|
|
|
|
static bool brush_uses_topology_rake(const SculptSession &ss, const Brush &brush)
|
|
{
|
|
return bke::brush::supports_topology_rake(brush) && (brush.topology_rake_factor > 0.0f) &&
|
|
(ss.bm != nullptr);
|
|
}
|
|
|
|
/**
|
|
* Test whether the #StrokeCache.sculpt_normal needs update in #do_brush_action
|
|
*/
|
|
static int sculpt_brush_needs_normal(const SculptSession &ss, const Brush &brush)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
const MTex *mask_tex = BKE_brush_mask_texture_get(&brush, OB_MODE_SCULPT);
|
|
return ((bke::brush::supports_normal_weight(brush) && (ss.cache->normal_weight > 0.0f)) ||
|
|
ELEM(brush.sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_BLOB,
|
|
SCULPT_BRUSH_TYPE_CREASE,
|
|
SCULPT_BRUSH_TYPE_DRAW,
|
|
SCULPT_BRUSH_TYPE_DRAW_SHARP,
|
|
SCULPT_BRUSH_TYPE_CLOTH,
|
|
SCULPT_BRUSH_TYPE_LAYER,
|
|
SCULPT_BRUSH_TYPE_NUDGE,
|
|
SCULPT_BRUSH_TYPE_ROTATE,
|
|
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM,
|
|
SCULPT_BRUSH_TYPE_THUMB) ||
|
|
|
|
(mask_tex->tex && mask_tex->brush_map_mode == MTEX_MAP_MODE_AREA)) ||
|
|
brush_uses_topology_rake(ss, brush) || BKE_brush_has_cube_tip(&brush, PaintMode::Sculpt);
|
|
}
|
|
|
|
static bool brush_needs_rake_rotation(const Brush &brush)
|
|
{
|
|
return bke::brush::supports_rake_factor(brush) && (brush.rake_factor != 0.0f);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
static void rake_data_update(SculptRakeData *srd, const float co[3])
|
|
{
|
|
float rake_dist = len_v3v3(srd->follow_co, co);
|
|
if (rake_dist > srd->follow_dist) {
|
|
interp_v3_v3v3(srd->follow_co, srd->follow_co, co, rake_dist - srd->follow_dist);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sculpt Dynamic Topology
|
|
* \{ */
|
|
|
|
namespace dyntopo {
|
|
|
|
bool stroke_is_dyntopo(const Object &object, const Brush &brush)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
return ((pbvh.type() == bke::pbvh::Type::BMesh) &&
|
|
|
|
(!ss.cache || (!ss.cache->alt_smooth)) &&
|
|
|
|
/* Requires mesh restore, which doesn't work with
|
|
* dynamic-topology. */
|
|
!(brush.flag & BRUSH_ANCHORED) && !(brush.flag & BRUSH_DRAG_DOT) &&
|
|
|
|
SCULPT_BRUSH_TYPE_HAS_DYNTOPO(brush.sculpt_brush_type));
|
|
}
|
|
|
|
} // namespace dyntopo
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sculpt Paint Mesh
|
|
* \{ */
|
|
|
|
namespace undo {
|
|
|
|
static void restore_mask_from_undo_step(Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = bke::pbvh::all_leaf_nodes(pbvh, memory);
|
|
|
|
Array<bool> node_changed(node_mask.min_array_size(), false);
|
|
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
MutableSpan<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
|
|
bke::SpanAttributeWriter<float> mask = attributes.lookup_or_add_for_write_span<float>(
|
|
".sculpt_mask", bke::AttrDomain::Point);
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
if (const std::optional<Span<float>> orig_data = orig_mask_data_lookup_mesh(object,
|
|
nodes[i]))
|
|
{
|
|
const Span<int> verts = nodes[i].verts();
|
|
scatter_data_mesh(*orig_data, verts, mask.span);
|
|
bke::pbvh::node_update_mask_mesh(mask.span, nodes[i]);
|
|
node_changed[i] = true;
|
|
}
|
|
});
|
|
mask.finish();
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
MutableSpan<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
const int offset = CustomData_get_offset_named(&ss.bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
|
if (offset != -1) {
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&nodes[i])) {
|
|
if (const float *orig_mask = BM_log_find_original_vert_mask(ss.bm_log, vert)) {
|
|
BM_ELEM_CD_SET_FLOAT(vert, offset, *orig_mask);
|
|
bke::pbvh::node_update_mask_bmesh(offset, nodes[i]);
|
|
node_changed[i] = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
MutableSpan<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
const BitGroupVector<> grid_hidden = subdiv_ccg.grid_hidden;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
MutableSpan<float> masks = subdiv_ccg.masks;
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
if (const std::optional<Span<float>> orig_data = orig_mask_data_lookup_grids(object,
|
|
nodes[i]))
|
|
{
|
|
int index = 0;
|
|
for (const int grid : nodes[i].grids()) {
|
|
const IndexRange grid_range = bke::ccg::grid_range(key, grid);
|
|
for (const int i : IndexRange(key.grid_area)) {
|
|
if (grid_hidden.is_empty() || !grid_hidden[grid][i]) {
|
|
masks[grid_range[i]] = (*orig_data)[index];
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
bke::pbvh::node_update_mask_grids(key, masks, nodes[i]);
|
|
node_changed[i] = true;
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
pbvh.tag_masks_changed(IndexMask::from_bools(node_changed, memory));
|
|
}
|
|
|
|
static void restore_color_from_undo_step(Object &object)
|
|
{
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
MutableSpan<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = IndexMask::from_predicate(
|
|
nodes.index_range(), GrainSize(64), memory, [&](const int i) {
|
|
return orig_color_data_lookup_mesh(object, nodes[i]).has_value();
|
|
});
|
|
|
|
BLI_assert(pbvh.type() == bke::pbvh::Type::Mesh);
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
const OffsetIndices<int> faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
const GroupedSpan<int> vert_to_face_map = mesh.vert_to_face_map();
|
|
bke::GSpanAttributeWriter color_attribute = color::active_color_attribute_for_write(mesh);
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
const Span<float4> orig_data = *orig_color_data_lookup_mesh(object, nodes[i]);
|
|
const Span<int> verts = nodes[i].verts();
|
|
for (const int i : verts.index_range()) {
|
|
color::color_vert_set(faces,
|
|
corner_verts,
|
|
vert_to_face_map,
|
|
color_attribute.domain,
|
|
verts[i],
|
|
orig_data[i],
|
|
color_attribute.span);
|
|
}
|
|
});
|
|
pbvh.tag_attribute_changed(node_mask, mesh.active_color_attribute);
|
|
color_attribute.finish();
|
|
}
|
|
|
|
static void restore_face_set_from_undo_step(Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = bke::pbvh::all_leaf_nodes(pbvh, memory);
|
|
|
|
Array<bool> node_changed(node_mask.min_array_size(), false);
|
|
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
MutableSpan<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
bke::SpanAttributeWriter<int> attribute = face_set::ensure_face_sets_mesh(
|
|
*static_cast<Mesh *>(object.data));
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
if (const std::optional<Span<int>> orig_data = orig_face_set_data_lookup_mesh(object,
|
|
nodes[i]))
|
|
{
|
|
scatter_data_mesh(*orig_data, nodes[i].faces(), attribute.span);
|
|
node_changed[i] = true;
|
|
}
|
|
});
|
|
attribute.finish();
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
MutableSpan<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
bke::SpanAttributeWriter<int> attribute = face_set::ensure_face_sets_mesh(
|
|
*static_cast<Mesh *>(object.data));
|
|
threading::EnumerableThreadSpecific<Vector<int>> all_tls;
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
Vector<int> &tls = all_tls.local();
|
|
if (const std::optional<Span<int>> orig_data = orig_face_set_data_lookup_grids(object,
|
|
nodes[i]))
|
|
{
|
|
const Span<int> faces = bke::pbvh::node_face_indices_calc_grids(
|
|
subdiv_ccg, nodes[i], tls);
|
|
scatter_data_mesh(*orig_data, faces, attribute.span);
|
|
node_changed[i] = true;
|
|
}
|
|
});
|
|
attribute.finish();
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh:
|
|
break;
|
|
}
|
|
|
|
pbvh.tag_face_sets_changed(IndexMask::from_bools(node_changed, memory));
|
|
}
|
|
|
|
void restore_position_from_undo_step(const Depsgraph &depsgraph, Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
IndexMaskMemory memory;
|
|
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
MutableSpan positions_eval = bke::pbvh::vert_positions_eval_for_write(depsgraph, object);
|
|
MutableSpan positions_orig = mesh.vert_positions_for_write();
|
|
|
|
const IndexMask node_mask = IndexMask::from_predicate(
|
|
nodes.index_range(), GrainSize(64), memory, [&](const int i) {
|
|
return orig_position_data_lookup_mesh(object, nodes[i]).has_value();
|
|
});
|
|
|
|
struct LocalData {
|
|
Vector<float3> translations;
|
|
};
|
|
|
|
std::optional<ShapeKeyData> shape_key_data = ShapeKeyData::from_object(object);
|
|
const bool need_translations = !ss.deform_imats.is_empty() || shape_key_data.has_value();
|
|
|
|
threading::EnumerableThreadSpecific<LocalData> all_tls;
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
threading::isolate_task([&] {
|
|
LocalData &tls = all_tls.local();
|
|
const OrigPositionData orig_data = *orig_position_data_lookup_mesh(object, nodes[i]);
|
|
const Span<int> verts = nodes[i].verts();
|
|
const Span<float3> undo_positions = orig_data.positions;
|
|
if (need_translations) {
|
|
/* Calculate translations from evaluated positions before they are changed. */
|
|
tls.translations.resize(verts.size());
|
|
translations_from_new_positions(
|
|
undo_positions, verts, positions_eval, tls.translations);
|
|
}
|
|
|
|
scatter_data_mesh(undo_positions, verts, positions_eval);
|
|
|
|
if (positions_eval.data() == positions_orig.data()) {
|
|
return;
|
|
}
|
|
|
|
const MutableSpan<float3> translations = tls.translations;
|
|
if (!ss.deform_imats.is_empty()) {
|
|
apply_crazyspace_to_translations(ss.deform_imats, verts, translations);
|
|
}
|
|
|
|
if (shape_key_data) {
|
|
for (MutableSpan<float3> data : shape_key_data->dependent_keys) {
|
|
apply_translations(translations, verts, data);
|
|
}
|
|
|
|
if (shape_key_data->basis_key_active) {
|
|
/* The basis key positions and the mesh positions are always kept in sync. */
|
|
apply_translations(translations, verts, positions_orig);
|
|
}
|
|
apply_translations(translations, verts, shape_key_data->active_key_data);
|
|
}
|
|
else {
|
|
apply_translations(translations, verts, positions_orig);
|
|
}
|
|
});
|
|
});
|
|
pbvh.tag_positions_changed(node_mask);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
MutableSpan<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
if (!undo::has_bmesh_log_entry()) {
|
|
return;
|
|
}
|
|
const IndexMask node_mask = bke::pbvh::all_leaf_nodes(pbvh, memory);
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(&nodes[i])) {
|
|
if (const float *orig_co = BM_log_find_original_vert_co(ss.bm_log, vert)) {
|
|
copy_v3_v3(vert->co, orig_co);
|
|
}
|
|
}
|
|
});
|
|
pbvh.tag_positions_changed(node_mask);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
|
|
const IndexMask node_mask = IndexMask::from_predicate(
|
|
nodes.index_range(), GrainSize(64), memory, [&](const int i) {
|
|
return orig_position_data_lookup_grids(object, nodes[i]).has_value();
|
|
});
|
|
|
|
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
const BitGroupVector<> grid_hidden = subdiv_ccg.grid_hidden;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
MutableSpan<float3> positions = subdiv_ccg.positions;
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
const OrigPositionData orig_data = *orig_position_data_lookup_grids(object, nodes[i]);
|
|
int index = 0;
|
|
for (const int grid : nodes[i].grids()) {
|
|
const IndexRange grid_range = bke::ccg::grid_range(key, grid);
|
|
for (const int i : IndexRange(key.grid_area)) {
|
|
if (grid_hidden.is_empty() || !grid_hidden[grid][i]) {
|
|
positions[grid_range[i]] = orig_data.positions[index];
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
});
|
|
pbvh.tag_positions_changed(node_mask);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void restore_from_undo_step(const Depsgraph &depsgraph, const Sculpt &sd, Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
switch (brush->sculpt_brush_type) {
|
|
case SCULPT_BRUSH_TYPE_MASK:
|
|
restore_mask_from_undo_step(object);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_PAINT:
|
|
case SCULPT_BRUSH_TYPE_SMEAR:
|
|
restore_color_from_undo_step(object);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_DRAW_FACE_SETS:
|
|
if (ss.cache->alt_smooth) {
|
|
restore_position_from_undo_step(depsgraph, object);
|
|
bke::pbvh::update_normals(depsgraph, object, *bke::object::pbvh_get(object));
|
|
}
|
|
else {
|
|
restore_face_set_from_undo_step(object);
|
|
}
|
|
break;
|
|
default:
|
|
restore_position_from_undo_step(depsgraph, object);
|
|
bke::pbvh::update_normals(depsgraph, object, *bke::object::pbvh_get(object));
|
|
break;
|
|
}
|
|
}
|
|
|
|
} // namespace undo
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
const float *SCULPT_brush_frontface_normal_from_falloff_shape(const SculptSession &ss,
|
|
char falloff_shape)
|
|
{
|
|
if (falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
|
|
return ss.cache->sculpt_normal_symm;
|
|
}
|
|
BLI_assert(falloff_shape == PAINT_FALLOFF_SHAPE_TUBE);
|
|
return ss.cache->view_normal_symm;
|
|
}
|
|
|
|
/* ===== Sculpting =====
|
|
*/
|
|
|
|
static float calc_overlap(const blender::ed::sculpt_paint::StrokeCache &cache,
|
|
const ePaintSymmetryFlags symm,
|
|
const char axis,
|
|
const float angle)
|
|
{
|
|
float3 mirror = blender::ed::sculpt_paint::symmetry_flip(cache.location, symm);
|
|
|
|
if (axis != 0) {
|
|
float mat[3][3];
|
|
axis_angle_to_mat3_single(mat, axis, angle);
|
|
mul_m3_v3(mat, mirror);
|
|
}
|
|
|
|
const float distsq = len_squared_v3v3(mirror, cache.location);
|
|
|
|
if (distsq <= 4.0f * (cache.radius_squared)) {
|
|
return (2.0f * (cache.radius) - sqrtf(distsq)) / (2.0f * (cache.radius));
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
static float calc_radial_symmetry_feather(const Sculpt &sd,
|
|
const blender::ed::sculpt_paint::StrokeCache &cache,
|
|
const ePaintSymmetryFlags symm,
|
|
const char axis)
|
|
{
|
|
float overlap = 0.0f;
|
|
|
|
for (int i = 1; i < sd.radial_symm[axis - 'X']; i++) {
|
|
const float angle = 2.0f * M_PI * i / sd.radial_symm[axis - 'X'];
|
|
overlap += calc_overlap(cache, symm, axis, angle);
|
|
}
|
|
|
|
return overlap;
|
|
}
|
|
|
|
static float calc_symmetry_feather(const Sculpt &sd,
|
|
const blender::ed::sculpt_paint::StrokeCache &cache)
|
|
{
|
|
if (!(sd.paint.symmetry_flags & PAINT_SYMMETRY_FEATHER)) {
|
|
return 1.0f;
|
|
}
|
|
float overlap;
|
|
const int symm = cache.symmetry;
|
|
|
|
overlap = 0.0f;
|
|
for (int i = 0; i <= symm; i++) {
|
|
if (!blender::ed::sculpt_paint::is_symmetry_iteration_valid(i, symm)) {
|
|
continue;
|
|
}
|
|
|
|
overlap += calc_overlap(cache, ePaintSymmetryFlags(i), 0, 0);
|
|
|
|
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'X');
|
|
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Y');
|
|
overlap += calc_radial_symmetry_feather(sd, cache, ePaintSymmetryFlags(i), 'Z');
|
|
}
|
|
return 1.0f / overlap;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Calculate Normal and Center
|
|
*
|
|
* Calculate geometry surrounding the brush center.
|
|
* (optionally using original coordinates).
|
|
*
|
|
* Functions are:
|
|
* - #calc_area_center
|
|
* - #calc_area_normal
|
|
* - #calc_area_normal_and_center
|
|
*
|
|
* \note These are all _very_ similar, when changing one, check others.
|
|
* \{ */
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
struct AreaNormalCenterData {
|
|
/* 0 = towards view, 1 = flipped */
|
|
std::array<float3, 2> area_cos;
|
|
std::array<int, 2> count_co;
|
|
|
|
std::array<float3, 2> area_nos;
|
|
std::array<int, 2> count_no;
|
|
};
|
|
|
|
static float area_normal_and_center_get_normal_radius(const SculptSession &ss, const Brush &brush)
|
|
{
|
|
float test_radius = ss.cache ? ss.cache->radius : ss.cursor_radius;
|
|
if (brush.ob_mode == OB_MODE_SCULPT) {
|
|
test_radius *= brush.normal_radius_factor;
|
|
}
|
|
return test_radius;
|
|
}
|
|
|
|
static float area_normal_and_center_get_position_radius(const SculptSession &ss,
|
|
const Brush &brush)
|
|
{
|
|
float test_radius = ss.cache ? ss.cache->radius : ss.cursor_radius;
|
|
if (brush.ob_mode == OB_MODE_SCULPT) {
|
|
/* Layer brush produces artifacts with normal and area radius */
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_PLANE && brush.area_radius_factor > 0.0f) {
|
|
test_radius *= brush.area_radius_factor;
|
|
if (ss.cache && brush.flag2 & BRUSH_AREA_RADIUS_PRESSURE) {
|
|
test_radius *= ss.cache->pressure;
|
|
}
|
|
}
|
|
else {
|
|
test_radius *= brush.normal_radius_factor;
|
|
}
|
|
}
|
|
return test_radius;
|
|
}
|
|
|
|
/* Weight the normals towards the center. */
|
|
static float area_normal_calc_weight(const float distance, const float radius_inv)
|
|
{
|
|
float p = 1.0f - (distance * radius_inv);
|
|
return std::clamp(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f);
|
|
}
|
|
|
|
/* Weight the coordinates towards the center. */
|
|
static float3 area_center_calc_weighted(const float3 &test_location,
|
|
const float distance,
|
|
const float radius_inv,
|
|
const float3 &co)
|
|
{
|
|
/* Weight the coordinates towards the center. */
|
|
float p = 1.0f - (distance * radius_inv);
|
|
const float afactor = std::clamp(3.0f * p * p - 2.0f * p * p * p, 0.0f, 1.0f);
|
|
|
|
const float3 disp = (co - test_location) * (1.0f - afactor);
|
|
return test_location + disp;
|
|
}
|
|
|
|
static void accumulate_area_center(const float3 &test_location,
|
|
const float3 &position,
|
|
const float distance,
|
|
const float radius_inv,
|
|
const int flip_index,
|
|
AreaNormalCenterData &anctd)
|
|
{
|
|
anctd.area_cos[flip_index] += area_center_calc_weighted(
|
|
test_location, distance, radius_inv, position);
|
|
anctd.count_co[flip_index] += 1;
|
|
}
|
|
|
|
static void accumulate_area_normal(const float3 &normal,
|
|
const float distance,
|
|
const float radius_inv,
|
|
const int flip_index,
|
|
AreaNormalCenterData &anctd)
|
|
{
|
|
anctd.area_nos[flip_index] += normal * area_normal_calc_weight(distance, radius_inv);
|
|
anctd.count_no[flip_index] += 1;
|
|
}
|
|
|
|
struct SampleLocalData {
|
|
Vector<float3> positions;
|
|
Vector<float> distances;
|
|
};
|
|
|
|
static void calc_area_normal_and_center_node_mesh(const Object &object,
|
|
const Span<float3> vert_positions,
|
|
const Span<float3> vert_normals,
|
|
const Span<bool> hide_vert,
|
|
const Brush &brush,
|
|
const bool use_area_nos,
|
|
const bool use_area_cos,
|
|
const bke::pbvh::MeshNode &node,
|
|
SampleLocalData &tls,
|
|
AreaNormalCenterData &anctd)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const float3 &location = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
const float3 &view_normal = ss.cache ? ss.cache->view_normal_symm : ss.cursor_view_normal;
|
|
const float position_radius = area_normal_and_center_get_position_radius(ss, brush);
|
|
const float position_radius_sq = position_radius * position_radius;
|
|
const float position_radius_inv = math::rcp(position_radius);
|
|
const float normal_radius = area_normal_and_center_get_normal_radius(ss, brush);
|
|
const float normal_radius_sq = normal_radius * normal_radius;
|
|
const float normal_radius_inv = math::rcp(normal_radius);
|
|
|
|
const Span<int> verts = node.verts();
|
|
|
|
if (ss.cache && !ss.cache->accum) {
|
|
if (const std::optional<OrigPositionData> orig_data = orig_position_data_lookup_mesh(object,
|
|
node))
|
|
{
|
|
const Span<float3> orig_positions = orig_data->positions;
|
|
const Span<float3> orig_normals = orig_data->normals;
|
|
|
|
tls.distances.reinitialize(verts.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, orig_positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
for (const int i : verts.index_range()) {
|
|
const int vert = verts[i];
|
|
if (!hide_vert.is_empty() && hide_vert[vert]) {
|
|
continue;
|
|
}
|
|
const bool normal_test_r = use_area_nos && distances_sq[i] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[i] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
continue;
|
|
}
|
|
const float3 &normal = orig_normals[i];
|
|
const float distance = std::sqrt(distances_sq[i]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, orig_positions[i], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
tls.distances.reinitialize(verts.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, vert_positions, verts, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
for (const int i : verts.index_range()) {
|
|
const int vert = verts[i];
|
|
if (!hide_vert.is_empty() && hide_vert[vert]) {
|
|
continue;
|
|
}
|
|
const bool normal_test_r = distances_sq[i] <= normal_radius_sq;
|
|
const bool area_test_r = distances_sq[i] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
continue;
|
|
}
|
|
const float3 &normal = vert_normals[vert];
|
|
const float distance = std::sqrt(distances_sq[i]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, vert_positions[vert], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void calc_area_normal_and_center_node_grids(const Object &object,
|
|
const Brush &brush,
|
|
const bool use_area_nos,
|
|
const bool use_area_cos,
|
|
const bke::pbvh::GridsNode &node,
|
|
SampleLocalData &tls,
|
|
AreaNormalCenterData &anctd)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const float3 &location = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
const float3 &view_normal = ss.cache ? ss.cache->view_normal_symm : ss.cursor_view_normal;
|
|
const float position_radius = area_normal_and_center_get_position_radius(ss, brush);
|
|
const float position_radius_sq = position_radius * position_radius;
|
|
const float position_radius_inv = math::rcp(position_radius);
|
|
const float normal_radius = area_normal_and_center_get_normal_radius(ss, brush);
|
|
const float normal_radius_sq = normal_radius * normal_radius;
|
|
const float normal_radius_inv = math::rcp(normal_radius);
|
|
|
|
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(*ss.subdiv_ccg);
|
|
const Span<float3> normals = subdiv_ccg.normals;
|
|
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
|
const Span<int> grids = node.grids();
|
|
|
|
if (ss.cache && !ss.cache->accum) {
|
|
if (const std::optional<OrigPositionData> orig_data = orig_position_data_lookup_grids(object,
|
|
node))
|
|
{
|
|
const Span<float3> orig_positions = orig_data->positions;
|
|
const Span<float3> orig_normals = orig_data->normals;
|
|
|
|
tls.distances.reinitialize(orig_positions.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, orig_positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const IndexRange grid_range_node = bke::ccg::grid_range(key, i);
|
|
const int grid = grids[i];
|
|
for (const int offset : IndexRange(key.grid_area)) {
|
|
if (!grid_hidden.is_empty() && grid_hidden[grid][offset]) {
|
|
continue;
|
|
}
|
|
const int node_vert = grid_range_node[offset];
|
|
|
|
const bool normal_test_r = use_area_nos && distances_sq[node_vert] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[node_vert] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
continue;
|
|
}
|
|
const float3 &normal = orig_normals[node_vert];
|
|
const float distance = std::sqrt(distances_sq[node_vert]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(location,
|
|
orig_positions[node_vert],
|
|
distance,
|
|
position_radius_inv,
|
|
flip_index,
|
|
anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
const Span<float3> positions = gather_grids_positions(subdiv_ccg, grids, tls.positions);
|
|
tls.distances.reinitialize(positions.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const IndexRange grid_range_node = bke::ccg::grid_range(key, i);
|
|
const int grid = grids[i];
|
|
const IndexRange grid_range = bke::ccg::grid_range(key, grid);
|
|
for (const int offset : IndexRange(key.grid_area)) {
|
|
if (!grid_hidden.is_empty() && grid_hidden[grid][offset]) {
|
|
continue;
|
|
}
|
|
const int node_vert = grid_range_node[offset];
|
|
const int vert = grid_range[offset];
|
|
|
|
const bool normal_test_r = use_area_nos && distances_sq[node_vert] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[node_vert] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
continue;
|
|
}
|
|
const float3 &normal = normals[vert];
|
|
const float distance = std::sqrt(distances_sq[node_vert]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, positions[node_vert], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void calc_area_normal_and_center_node_bmesh(const Object &object,
|
|
const Brush &brush,
|
|
const bool use_area_nos,
|
|
const bool use_area_cos,
|
|
const bool has_bm_orco,
|
|
const bke::pbvh::BMeshNode &node,
|
|
SampleLocalData &tls,
|
|
AreaNormalCenterData &anctd)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const float3 &location = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
const float3 &view_normal = ss.cache ? ss.cache->view_normal_symm : ss.cursor_view_normal;
|
|
const float position_radius = area_normal_and_center_get_position_radius(ss, brush);
|
|
const float position_radius_sq = position_radius * position_radius;
|
|
const float position_radius_inv = math::rcp(position_radius);
|
|
const float normal_radius = area_normal_and_center_get_normal_radius(ss, brush);
|
|
const float normal_radius_sq = normal_radius * normal_radius;
|
|
const float normal_radius_inv = math::rcp(normal_radius);
|
|
|
|
bool use_original = false;
|
|
if (ss.cache && !ss.cache->accum) {
|
|
use_original = undo::has_bmesh_log_entry();
|
|
}
|
|
|
|
/* When the mesh is edited we can't rely on original coords
|
|
* (original mesh may not even have verts in brush radius). */
|
|
if (use_original && has_bm_orco) {
|
|
Span<float3> orig_positions;
|
|
Span<int3> orig_tris;
|
|
BKE_pbvh_node_get_bm_orco_data(node, orig_positions, orig_tris);
|
|
|
|
tls.positions.resize(orig_tris.size());
|
|
const MutableSpan<float3> positions = tls.positions;
|
|
for (const int i : orig_tris.index_range()) {
|
|
const float *co_tri[3] = {
|
|
orig_positions[orig_tris[i][0]],
|
|
orig_positions[orig_tris[i][1]],
|
|
orig_positions[orig_tris[i][2]],
|
|
};
|
|
closest_on_tri_to_point_v3(positions[i], location, UNPACK3(co_tri));
|
|
}
|
|
|
|
tls.distances.reinitialize(positions.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
for (const int i : orig_tris.index_range()) {
|
|
const bool normal_test_r = use_area_nos && distances_sq[i] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[i] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
continue;
|
|
}
|
|
const float3 normal = math::normal_tri(float3(orig_positions[orig_tris[i][0]]),
|
|
float3(orig_positions[orig_tris[i][1]]),
|
|
float3(orig_positions[orig_tris[i][2]]));
|
|
|
|
const float distance = std::sqrt(distances_sq[i]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, positions[i], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(
|
|
&const_cast<bke::pbvh::BMeshNode &>(node));
|
|
if (use_original) {
|
|
tls.positions.resize(verts.size());
|
|
const MutableSpan<float3> positions = tls.positions;
|
|
Array<float3> normals(verts.size());
|
|
orig_position_data_gather_bmesh(*ss.bm_log, verts, positions, normals);
|
|
|
|
tls.distances.reinitialize(positions.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
int i = 0;
|
|
for (BMVert *vert : verts) {
|
|
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
|
i++;
|
|
continue;
|
|
}
|
|
const bool normal_test_r = use_area_nos && distances_sq[i] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[i] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
i++;
|
|
continue;
|
|
}
|
|
const float3 &normal = normals[i];
|
|
const float distance = std::sqrt(distances_sq[i]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, positions[i], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
i++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const Span<float3> positions = gather_bmesh_positions(verts, tls.positions);
|
|
|
|
tls.distances.reinitialize(positions.size());
|
|
const MutableSpan<float> distances_sq = tls.distances;
|
|
calc_brush_distances_squared(
|
|
ss, positions, eBrushFalloffShape(brush.falloff_shape), distances_sq);
|
|
|
|
int i = 0;
|
|
for (BMVert *vert : verts) {
|
|
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
|
i++;
|
|
continue;
|
|
}
|
|
const bool normal_test_r = use_area_nos && distances_sq[i] <= normal_radius_sq;
|
|
const bool area_test_r = use_area_cos && distances_sq[i] <= position_radius_sq;
|
|
if (!normal_test_r && !area_test_r) {
|
|
i++;
|
|
continue;
|
|
}
|
|
const float3 normal = vert->no;
|
|
const float distance = std::sqrt(distances_sq[i]);
|
|
const int flip_index = math::dot(view_normal, normal) <= 0.0f;
|
|
if (area_test_r) {
|
|
accumulate_area_center(
|
|
location, positions[i], distance, position_radius_inv, flip_index, anctd);
|
|
}
|
|
if (normal_test_r) {
|
|
accumulate_area_normal(normal, distance, normal_radius_inv, flip_index, anctd);
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static AreaNormalCenterData calc_area_normal_and_center_reduce(const AreaNormalCenterData &a,
|
|
const AreaNormalCenterData &b)
|
|
{
|
|
AreaNormalCenterData joined{};
|
|
|
|
joined.area_cos[0] = a.area_cos[0] + b.area_cos[0];
|
|
joined.area_cos[1] = a.area_cos[1] + b.area_cos[1];
|
|
joined.count_co[0] = a.count_co[0] + b.count_co[0];
|
|
joined.count_co[1] = a.count_co[1] + b.count_co[1];
|
|
|
|
joined.area_nos[0] = a.area_nos[0] + b.area_nos[0];
|
|
joined.area_nos[1] = a.area_nos[1] + b.area_nos[1];
|
|
joined.count_no[0] = a.count_no[0] + b.count_no[0];
|
|
joined.count_no[1] = a.count_no[1] + b.count_no[1];
|
|
|
|
return joined;
|
|
}
|
|
|
|
void calc_area_center(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &ob,
|
|
const IndexMask &node_mask,
|
|
float r_area_co[3])
|
|
{
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
const SculptSession &ss = *ob.sculpt;
|
|
int n;
|
|
|
|
AreaNormalCenterData anctd;
|
|
threading::EnumerableThreadSpecific<SampleLocalData> all_tls;
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
const Span<float3> vert_positions = bke::pbvh::vert_positions_eval(depsgraph, ob);
|
|
const Span<float3> vert_normals = bke::pbvh::vert_normals_eval(depsgraph, ob);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
|
|
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_mesh(ob,
|
|
vert_positions,
|
|
vert_normals,
|
|
hide_vert,
|
|
brush,
|
|
false,
|
|
true,
|
|
nodes[i],
|
|
tls,
|
|
anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
const bool has_bm_orco = ss.bm && dyntopo::stroke_is_dyntopo(ob, brush);
|
|
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_bmesh(
|
|
ob, brush, false, true, has_bm_orco, nodes[i], tls, anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_grids(ob, brush, false, true, nodes[i], tls, anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* For flatten center. */
|
|
for (n = 0; n < anctd.area_cos.size(); n++) {
|
|
if (anctd.count_co[n] == 0) {
|
|
continue;
|
|
}
|
|
|
|
mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]);
|
|
break;
|
|
}
|
|
|
|
if (n == 2) {
|
|
zero_v3(r_area_co);
|
|
}
|
|
|
|
if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) {
|
|
if (ss.cache) {
|
|
copy_v3_v3(r_area_co, ss.cache->location_symm);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<float3> calc_area_normal(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &ob,
|
|
const IndexMask &node_mask)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
AreaNormalCenterData anctd;
|
|
threading::EnumerableThreadSpecific<SampleLocalData> all_tls;
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
const Span<float3> vert_positions = bke::pbvh::vert_positions_eval(depsgraph, ob);
|
|
const Span<float3> vert_normals = bke::pbvh::vert_normals_eval(depsgraph, ob);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
|
|
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_mesh(ob,
|
|
vert_positions,
|
|
vert_normals,
|
|
hide_vert,
|
|
brush,
|
|
true,
|
|
false,
|
|
nodes[i],
|
|
tls,
|
|
anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
const bool has_bm_orco = ss.bm && dyntopo::stroke_is_dyntopo(ob, brush);
|
|
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_bmesh(
|
|
ob,
|
|
brush,
|
|
true,
|
|
false,
|
|
has_bm_orco,
|
|
static_cast<const blender::bke::pbvh::BMeshNode &>(nodes[i]),
|
|
tls,
|
|
anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_grids(ob, brush, true, false, nodes[i], tls, anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const int i : {0, 1}) {
|
|
if (anctd.count_no[i] != 0) {
|
|
if (!math::is_zero(anctd.area_nos[i])) {
|
|
return math::normalize(anctd.area_nos[i]);
|
|
}
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
/*
|
|
* Stabilizes the position (center) and orientation (normal) of the brush plane during a stroke.
|
|
* Implements a smoothing mechanism based on a weighted moving average for both the plane normal
|
|
* and the plane center.
|
|
*
|
|
* The stabilized normal (`r_stabilized_normal`) is computed as the average of the last
|
|
* `max_normal_index` plane normals, where `max_normal_index` is determined by the
|
|
* `stabilize_normal` parameter of the brush. Each new plane normal is interpolated with the
|
|
* previous plane normal, with `stabilize_normal` controlling the interpolation factor.
|
|
*
|
|
* The stabilized center (`r_stabilized_center`) is computed based on the signed distances
|
|
* of the stored plane centers from a reference plane defined by the current stroke step's center
|
|
* and the stabilized normal. The signed distances are averaged, and this average is used to
|
|
* adjust the position of the stabilized center such that it maintains the average offset of the
|
|
* stored centers relative to the reference plane.
|
|
*/
|
|
static void calc_stabilized_plane(const Brush &brush,
|
|
StrokeCache &cache,
|
|
const float3 &plane_normal,
|
|
const float3 &plane_center,
|
|
float3 &r_stabilized_normal,
|
|
float3 &r_stabilized_center)
|
|
{
|
|
auto &plane_cache = cache.plane_brush;
|
|
|
|
const float normal_weight = brush.stabilize_normal;
|
|
const float center_weight = brush.stabilize_plane;
|
|
|
|
float3 new_plane_normal;
|
|
float3 new_plane_center;
|
|
|
|
if (plane_cache.first_time) {
|
|
new_plane_normal = plane_normal;
|
|
new_plane_center = plane_center;
|
|
|
|
const int max_normal_index = int(1 +
|
|
normal_weight * (plane_brush_max_rolling_average_num - 1));
|
|
const int max_center_index = int(1 +
|
|
center_weight * (plane_brush_max_rolling_average_num - 1));
|
|
|
|
plane_cache.normals.reinitialize(max_normal_index);
|
|
plane_cache.centers.reinitialize(max_center_index);
|
|
plane_cache.normals.fill(plane_normal);
|
|
plane_cache.centers.fill(plane_center);
|
|
|
|
plane_cache.normal_index = 0;
|
|
plane_cache.center_index = 0;
|
|
plane_cache.first_time = false;
|
|
}
|
|
else {
|
|
const float3 last_normal = plane_cache.last_normal.value();
|
|
const float3 last_center = plane_cache.last_center.value();
|
|
|
|
/* Interpolate between `plane_normal` and the last plane normal. */
|
|
new_plane_normal = math::normalize(
|
|
math::interpolate(plane_normal, last_normal, normal_weight));
|
|
|
|
float4 last_plane;
|
|
plane_from_point_normal_v3(last_plane, last_center, last_normal);
|
|
|
|
/* Projection of `plane_center` on the last plane. */
|
|
float3 projected_plane_center;
|
|
closest_to_plane_normalized_v3(projected_plane_center, last_plane, plane_center);
|
|
|
|
new_plane_center = math::interpolate(plane_center, projected_plane_center, center_weight);
|
|
}
|
|
|
|
plane_cache.normals[plane_cache.normal_index] = new_plane_normal;
|
|
plane_cache.centers[plane_cache.center_index] = new_plane_center;
|
|
|
|
plane_cache.normal_index = (plane_cache.normal_index + 1) % plane_cache.normals.size();
|
|
plane_cache.center_index = (plane_cache.center_index + 1) % plane_cache.centers.size();
|
|
|
|
r_stabilized_normal = float3(0.0f);
|
|
|
|
for (const int i : plane_cache.normals.index_range()) {
|
|
r_stabilized_normal += plane_cache.normals[i];
|
|
}
|
|
r_stabilized_normal = math::normalize(r_stabilized_normal);
|
|
|
|
float4 reference_plane;
|
|
plane_from_point_normal_v3(reference_plane, new_plane_center, r_stabilized_normal);
|
|
float total_signed_distance = 0.0f;
|
|
|
|
for (const int i : plane_cache.centers.index_range()) {
|
|
float signed_distance = math::dot(r_stabilized_normal, plane_cache.centers[i]) -
|
|
reference_plane.w;
|
|
total_signed_distance += signed_distance;
|
|
}
|
|
|
|
const float avg_signed_distance = total_signed_distance / plane_cache.centers.size();
|
|
const float new_center_signed_distance = math::dot(r_stabilized_normal, new_plane_center) -
|
|
reference_plane.w;
|
|
const float adjusted_distance = new_center_signed_distance - avg_signed_distance;
|
|
r_stabilized_center = new_plane_center - r_stabilized_normal * adjusted_distance;
|
|
|
|
plane_cache.last_normal = r_stabilized_normal;
|
|
plane_cache.last_center = r_stabilized_center;
|
|
}
|
|
|
|
void calc_area_normal_and_center(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &ob,
|
|
const IndexMask &node_mask,
|
|
float r_area_no[3],
|
|
float r_area_co[3])
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
int n;
|
|
|
|
AreaNormalCenterData anctd;
|
|
threading::EnumerableThreadSpecific<SampleLocalData> all_tls;
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
const Span<float3> vert_positions = bke::pbvh::vert_positions_eval(depsgraph, ob);
|
|
const Span<float3> vert_normals = bke::pbvh::vert_normals_eval(depsgraph, ob);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
|
|
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_mesh(ob,
|
|
vert_positions,
|
|
vert_normals,
|
|
hide_vert,
|
|
brush,
|
|
true,
|
|
true,
|
|
nodes[i],
|
|
tls,
|
|
anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
const bool has_bm_orco = ss.bm && dyntopo::stroke_is_dyntopo(ob, brush);
|
|
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_bmesh(
|
|
ob, brush, true, true, has_bm_orco, nodes[i], tls, anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
anctd = threading::parallel_reduce(
|
|
node_mask.index_range(),
|
|
1,
|
|
AreaNormalCenterData{},
|
|
[&](const IndexRange range, AreaNormalCenterData anctd) {
|
|
SampleLocalData &tls = all_tls.local();
|
|
node_mask.slice(range).foreach_index([&](const int i) {
|
|
calc_area_normal_and_center_node_grids(ob, brush, true, true, nodes[i], tls, anctd);
|
|
});
|
|
return anctd;
|
|
},
|
|
calc_area_normal_and_center_reduce);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* For flatten center. */
|
|
for (n = 0; n < anctd.area_cos.size(); n++) {
|
|
if (anctd.count_co[n] == 0) {
|
|
continue;
|
|
}
|
|
|
|
mul_v3_v3fl(r_area_co, anctd.area_cos[n], 1.0f / anctd.count_co[n]);
|
|
break;
|
|
}
|
|
|
|
if (n == 2) {
|
|
zero_v3(r_area_co);
|
|
}
|
|
|
|
if (anctd.count_co[0] == 0 && anctd.count_co[1] == 0) {
|
|
if (ss.cache) {
|
|
copy_v3_v3(r_area_co, ss.cache->location_symm);
|
|
}
|
|
}
|
|
|
|
/* For area normal. */
|
|
for (n = 0; n < anctd.area_nos.size(); n++) {
|
|
if (normalize_v3_v3(r_area_no, anctd.area_nos[n]) != 0.0f) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_PLANE) {
|
|
float3 stabilized_normal;
|
|
float3 stabilized_center;
|
|
|
|
calc_stabilized_plane(
|
|
brush, *ss.cache, r_area_no, r_area_co, stabilized_normal, stabilized_center);
|
|
|
|
copy_v3_v3(r_area_no, stabilized_normal);
|
|
copy_v3_v3(r_area_co, stabilized_center);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Generic Brush Utilities
|
|
* \{ */
|
|
|
|
/**
|
|
* Calculates the sign of the direction of the brush stroke, typically indicates whether the stroke
|
|
* will deform a surface inwards or outwards along the brush normal.
|
|
*/
|
|
static float brush_flip(const Brush &brush, const blender::ed::sculpt_paint::StrokeCache &cache)
|
|
{
|
|
const float dir = (brush.flag & BRUSH_DIR_IN) ? -1.0f : 1.0f;
|
|
const float pen_flip = cache.pen_flip ? -1.0f : 1.0f;
|
|
const float invert = cache.invert ? -1.0f : 1.0f;
|
|
|
|
return dir * pen_flip * invert;
|
|
}
|
|
|
|
/**
|
|
* Return modified brush strength. Includes the direction of the brush, positive
|
|
* values pull vertices, negative values push. Uses tablet pressure and a
|
|
* special multiplier found experimentally to scale the strength factor.
|
|
*/
|
|
static float brush_strength(const Sculpt &sd,
|
|
const blender::ed::sculpt_paint::StrokeCache &cache,
|
|
const float feather,
|
|
const UnifiedPaintSettings &ups,
|
|
const PaintModeSettings & /*paint_mode_settings*/)
|
|
{
|
|
const Scene *scene = cache.vc->scene;
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
/* Primary strength input; square it to make lower values more sensitive. */
|
|
const float root_alpha = BKE_brush_alpha_get(scene, &brush);
|
|
const float alpha = root_alpha * root_alpha;
|
|
const float pressure = BKE_brush_use_alpha_pressure(&brush) ? cache.pressure : 1.0f;
|
|
float overlap = ups.overlap_factor;
|
|
/* Spacing is integer percentage of radius, divide by 50 to get
|
|
* normalized diameter. */
|
|
|
|
const float flip = brush_flip(brush, cache);
|
|
|
|
/* Pressure final value after being tweaked depending on the brush. */
|
|
float final_pressure;
|
|
|
|
switch (brush.sculpt_brush_type) {
|
|
case SCULPT_BRUSH_TYPE_CLAY:
|
|
final_pressure = pow4f(pressure);
|
|
overlap = (1.0f + overlap) / 2.0f;
|
|
return 0.25f * alpha * flip * final_pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_DRAW:
|
|
case SCULPT_BRUSH_TYPE_DRAW_SHARP:
|
|
case SCULPT_BRUSH_TYPE_LAYER:
|
|
return alpha * flip * pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_ERASER:
|
|
return alpha * pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_CLOTH:
|
|
if (brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) {
|
|
/* Grab deform uses the same falloff as a regular grab brush. */
|
|
return root_alpha * feather;
|
|
}
|
|
else if (brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK) {
|
|
return root_alpha * feather * pressure * overlap;
|
|
}
|
|
else if (brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_EXPAND) {
|
|
/* Expand is more sensible to strength as it keeps expanding the cloth when sculpting over
|
|
* the same vertices. */
|
|
return 0.1f * alpha * flip * pressure * overlap * feather;
|
|
}
|
|
else {
|
|
/* Multiply by 10 by default to get a larger range of strength depending on the size of the
|
|
* brush and object. */
|
|
return 10.0f * alpha * flip * pressure * overlap * feather;
|
|
}
|
|
case SCULPT_BRUSH_TYPE_DRAW_FACE_SETS:
|
|
return alpha * pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_SLIDE_RELAX:
|
|
return alpha * pressure * overlap * feather * 2.0f;
|
|
case SCULPT_BRUSH_TYPE_PAINT:
|
|
final_pressure = pressure * pressure;
|
|
return final_pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_SMEAR:
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR:
|
|
return alpha * pressure * overlap * feather;
|
|
case SCULPT_BRUSH_TYPE_CLAY_STRIPS:
|
|
/* Clay Strips needs less strength to compensate the curve. */
|
|
final_pressure = powf(pressure, 1.5f);
|
|
return alpha * flip * final_pressure * overlap * feather * 0.3f;
|
|
case SCULPT_BRUSH_TYPE_CLAY_THUMB:
|
|
final_pressure = pressure * pressure;
|
|
return alpha * flip * final_pressure * overlap * feather * 1.3f;
|
|
|
|
case SCULPT_BRUSH_TYPE_MASK:
|
|
overlap = (1.0f + overlap) / 2.0f;
|
|
switch ((BrushMaskTool)brush.mask_tool) {
|
|
case BRUSH_MASK_DRAW:
|
|
return alpha * flip * pressure * overlap * feather;
|
|
case BRUSH_MASK_SMOOTH:
|
|
return alpha * pressure * feather;
|
|
}
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CREASE:
|
|
case SCULPT_BRUSH_TYPE_BLOB:
|
|
return alpha * flip * pressure * overlap * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_INFLATE:
|
|
if (flip > 0.0f) {
|
|
return 0.250f * alpha * flip * pressure * overlap * feather;
|
|
}
|
|
else {
|
|
return 0.125f * alpha * flip * pressure * overlap * feather;
|
|
}
|
|
|
|
case SCULPT_BRUSH_TYPE_MULTIPLANE_SCRAPE:
|
|
overlap = (1.0f + overlap) / 2.0f;
|
|
return alpha * flip * pressure * overlap * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_PLANE:
|
|
if (flip > 0.0f || brush.plane_inversion_mode == BRUSH_PLANE_SWAP_HEIGHT_AND_DEPTH) {
|
|
overlap = (1.0f + overlap) / 2.0f;
|
|
return alpha * pressure * overlap * feather;
|
|
}
|
|
/* When the brush is inverted with the Invert Displacement mode (i.e. when the brush adds
|
|
* contrast), use a different formula that results in a lower strength. This is done because,
|
|
* from an artistic point of view, the contrast would otherwise generally be too strong. Note
|
|
* that this behavior is coherent with the way Fill, Scrape and Flatten work. See #136211. */
|
|
else {
|
|
return 0.5f * alpha * pressure * overlap * feather;
|
|
}
|
|
case SCULPT_BRUSH_TYPE_SMOOTH:
|
|
return flip * alpha * pressure * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_PINCH:
|
|
if (flip > 0.0f) {
|
|
return alpha * flip * pressure * overlap * feather;
|
|
}
|
|
else {
|
|
return 0.25f * alpha * flip * pressure * overlap * feather;
|
|
}
|
|
|
|
case SCULPT_BRUSH_TYPE_NUDGE:
|
|
overlap = (1.0f + overlap) / 2.0f;
|
|
return alpha * pressure * overlap * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_THUMB:
|
|
return alpha * pressure * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_SNAKE_HOOK:
|
|
return root_alpha * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_GRAB:
|
|
return root_alpha * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_ROTATE:
|
|
return alpha * pressure * feather;
|
|
|
|
case SCULPT_BRUSH_TYPE_ELASTIC_DEFORM:
|
|
case SCULPT_BRUSH_TYPE_POSE:
|
|
case SCULPT_BRUSH_TYPE_BOUNDARY:
|
|
return root_alpha * feather;
|
|
case SCULPT_BRUSH_TYPE_SIMPLIFY:
|
|
/* The Dyntopo Density brush does not use a normal brush workflow to calculate the effect,
|
|
* and this strength value is unused. */
|
|
return 0.0f;
|
|
}
|
|
BLI_assert_unreachable();
|
|
return 0.0f;
|
|
}
|
|
|
|
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;
|
|
const MTex *mtex = BKE_brush_mask_texture_get(&brush, OB_MODE_SCULPT);
|
|
|
|
if (!mtex->tex) {
|
|
*r_value = 1.0f;
|
|
copy_v4_fl(r_rgba, 1.0f);
|
|
return;
|
|
}
|
|
|
|
float point[3];
|
|
sub_v3_v3v3(point, brush_point, cache.plane_offset);
|
|
|
|
if (mtex->brush_map_mode == MTEX_MAP_MODE_3D) {
|
|
/* Get strength by feeding the vertex location directly into a texture. */
|
|
*r_value = BKE_brush_sample_tex_3d(scene, &brush, mtex, point, r_rgba, 0, ss.tex_pool);
|
|
}
|
|
else {
|
|
/* If the active area is being applied for symmetry, flip it
|
|
* across the symmetry axis and rotate it back to the original
|
|
* position in order to project it. This insures that the
|
|
* brush texture will be oriented correctly. */
|
|
if (cache.radial_symmetry_pass) {
|
|
mul_m4_v3(cache.symm_rot_mat_inv.ptr(), point);
|
|
}
|
|
float3 symm_point = blender::ed::sculpt_paint::symmetry_flip(point,
|
|
cache.mirror_symmetry_pass);
|
|
|
|
/* Still no symmetry supported for other paint modes.
|
|
* Sculpt does it DIY. */
|
|
if (mtex->brush_map_mode == MTEX_MAP_MODE_AREA) {
|
|
/* Similar to fixed mode, but projects from brush angle
|
|
* rather than view direction. */
|
|
|
|
mul_m4_v3(cache.brush_local_mat.ptr(), symm_point);
|
|
|
|
float x = symm_point[0];
|
|
float y = symm_point[1];
|
|
|
|
x *= mtex->size[0];
|
|
y *= mtex->size[1];
|
|
|
|
x += mtex->ofs[0];
|
|
y += mtex->ofs[1];
|
|
|
|
paint_get_tex_pixel(mtex, x, y, ss.tex_pool, thread_id, r_value, r_rgba);
|
|
|
|
add_v3_fl(r_rgba, brush.texture_sample_bias); // v3 -> Ignore alpha
|
|
*r_value -= brush.texture_sample_bias;
|
|
}
|
|
else {
|
|
const blender::float2 point_2d = ED_view3d_project_float_v2_m4(
|
|
cache.vc->region, symm_point, cache.projection_mat);
|
|
const float point_3d[3] = {point_2d[0], point_2d[1], 0.0f};
|
|
*r_value = BKE_brush_sample_tex_3d(scene, &brush, mtex, point_3d, r_rgba, 0, ss.tex_pool);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SCULPT_calc_vertex_displacement(const SculptSession &ss,
|
|
const Brush &brush,
|
|
float translation[3])
|
|
{
|
|
mul_v3_fl(translation, ss.cache->bstrength);
|
|
/* Handle brush inversion */
|
|
if (ss.cache->bstrength < 0) {
|
|
translation[0] *= -1;
|
|
translation[1] *= -1;
|
|
}
|
|
|
|
/* Apply texture size */
|
|
for (int i = 0; i < 3; ++i) {
|
|
translation[i] *= blender::math::safe_divide(1.0f, pow2f(brush.mtex.size[i]));
|
|
}
|
|
|
|
/* Transform vector to object space */
|
|
mul_mat3_m4_v3(ss.cache->brush_local_mat_inv.ptr(), translation);
|
|
|
|
/* Handle symmetry */
|
|
if (ss.cache->radial_symmetry_pass) {
|
|
mul_m4_v3(ss.cache->symm_rot_mat.ptr(), translation);
|
|
}
|
|
copy_v3_v3(
|
|
translation,
|
|
blender::ed::sculpt_paint::symmetry_flip(translation, ss.cache->mirror_symmetry_pass));
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
bool node_fully_masked_or_hidden(const bke::pbvh::Node &node)
|
|
{
|
|
if (BKE_pbvh_node_fully_hidden_get(node)) {
|
|
return true;
|
|
}
|
|
if (BKE_pbvh_node_fully_masked_get(node)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool node_in_sphere(const bke::pbvh::Node &node,
|
|
const float3 &location,
|
|
const float radius_sq,
|
|
const bool original)
|
|
{
|
|
const Bounds<float3> &bounds = original ? node.bounds_orig() : node.bounds();
|
|
const float3 nearest = math::clamp(location, bounds.min, bounds.max);
|
|
return math::distance_squared(location, nearest) < radius_sq;
|
|
}
|
|
|
|
bool node_in_cylinder(const DistRayAABB_Precalc &ray_dist_precalc,
|
|
const bke::pbvh::Node &node,
|
|
const float radius_sq,
|
|
const bool original)
|
|
{
|
|
const Bounds<float3> &bounds = original ? node.bounds_orig() : node.bounds();
|
|
|
|
float dummy_co[3], dummy_depth;
|
|
const float dist_sq = dist_squared_ray_to_aabb_v3(
|
|
&ray_dist_precalc, bounds.min, bounds.max, dummy_co, &dummy_depth);
|
|
|
|
/* TODO: Solve issues and enable distance check. */
|
|
return dist_sq < radius_sq || true;
|
|
}
|
|
|
|
static IndexMask pbvh_gather_cursor_update(Object &ob, bool use_original, IndexMaskMemory &memory)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
const float3 center = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
return bke::pbvh::search_nodes(pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, center, ss.cursor_radius, use_original);
|
|
});
|
|
}
|
|
|
|
/** \return All nodes that are potentially within the cursor or brush's area of influence. */
|
|
static IndexMask pbvh_gather_generic(Object &ob,
|
|
const Brush &brush,
|
|
const bool use_original,
|
|
const float radius_scale,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
const float3 center = ss.cache->location_symm;
|
|
const float radius_sq = math::square(ss.cache->radius * radius_scale);
|
|
const bool ignore_ineffective = brush.sculpt_brush_type != SCULPT_BRUSH_TYPE_MASK;
|
|
switch (brush.falloff_shape) {
|
|
case PAINT_FALLOFF_SHAPE_SPHERE: {
|
|
return bke::pbvh::search_nodes(pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
if (ignore_ineffective && node_fully_masked_or_hidden(node)) {
|
|
return false;
|
|
}
|
|
return node_in_sphere(node, center, radius_sq, use_original);
|
|
});
|
|
}
|
|
|
|
case PAINT_FALLOFF_SHAPE_TUBE: {
|
|
const DistRayAABB_Precalc ray_dist_precalc = dist_squared_ray_to_aabb_v3_precalc(
|
|
center, ss.cache->view_normal_symm);
|
|
return bke::pbvh::search_nodes(pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
if (ignore_ineffective && node_fully_masked_or_hidden(node)) {
|
|
return false;
|
|
}
|
|
return node_in_cylinder(ray_dist_precalc, node, radius_sq, use_original);
|
|
});
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
IndexMask gather_nodes(const bke::pbvh::Tree &pbvh,
|
|
const eBrushFalloffShape falloff_shape,
|
|
const bool use_original,
|
|
const float3 &location,
|
|
const float radius_sq,
|
|
const std::optional<float3> &ray_direction,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
switch (falloff_shape) {
|
|
case PAINT_FALLOFF_SHAPE_SPHERE: {
|
|
return bke::pbvh::search_nodes(pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
if (node_fully_masked_or_hidden(node)) {
|
|
return false;
|
|
}
|
|
return node_in_sphere(node, location, radius_sq, use_original);
|
|
});
|
|
}
|
|
|
|
case PAINT_FALLOFF_SHAPE_TUBE: {
|
|
BLI_assert(ray_direction);
|
|
const DistRayAABB_Precalc ray_dist_precalc = dist_squared_ray_to_aabb_v3_precalc(
|
|
location, ray_direction.value_or(float3(0.0f)));
|
|
return bke::pbvh::search_nodes(pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
if (node_fully_masked_or_hidden(node)) {
|
|
return false;
|
|
}
|
|
return node_in_cylinder(ray_dist_precalc, node, radius_sq, use_original);
|
|
});
|
|
}
|
|
}
|
|
BLI_assert_unreachable();
|
|
return {};
|
|
}
|
|
|
|
static IndexMask pbvh_gather_texpaint(Object &ob,
|
|
const Brush &brush,
|
|
const bool use_original,
|
|
const float radius_scale,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
return pbvh_gather_generic(ob, brush, use_original, radius_scale, memory);
|
|
}
|
|
|
|
/* Calculate primary direction of movement for many brushes. */
|
|
static float3 calc_sculpt_normal(const Depsgraph &depsgraph,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const IndexMask &node_mask)
|
|
{
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
const SculptSession &ss = *ob.sculpt;
|
|
switch (brush.sculpt_plane) {
|
|
case SCULPT_DISP_DIR_AREA:
|
|
return calc_area_normal(depsgraph, brush, ob, node_mask).value_or(float3(0));
|
|
case SCULPT_DISP_DIR_VIEW:
|
|
return ss.cache->view_normal;
|
|
case SCULPT_DISP_DIR_X:
|
|
return float3(1, 0, 0);
|
|
case SCULPT_DISP_DIR_Y:
|
|
return float3(0, 1, 0);
|
|
case SCULPT_DISP_DIR_Z:
|
|
return float3(0, 0, 1);
|
|
}
|
|
BLI_assert_unreachable();
|
|
return {};
|
|
}
|
|
|
|
static void update_sculpt_normal(const Depsgraph &depsgraph,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const brushes::CursorSampleResult &cursor_sample_result)
|
|
{
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
StrokeCache &cache = *ob.sculpt->cache;
|
|
/* Grab brush does not update the sculpt normal during a stroke. */
|
|
const bool update_normal = !(brush.flag & BRUSH_ORIGINAL_NORMAL) &&
|
|
!(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_GRAB) &&
|
|
!(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_THUMB &&
|
|
!(brush.flag & BRUSH_ANCHORED)) &&
|
|
!(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_ELASTIC_DEFORM) &&
|
|
!(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SNAKE_HOOK &&
|
|
cache.normal_weight > 0.0f);
|
|
|
|
if (cache.mirror_symmetry_pass == 0 && cache.radial_symmetry_pass == 0 &&
|
|
(SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) || update_normal))
|
|
{
|
|
if (cursor_sample_result.plane_normal) {
|
|
cache.sculpt_normal = *cursor_sample_result.plane_normal;
|
|
}
|
|
else {
|
|
cache.sculpt_normal = calc_sculpt_normal(depsgraph, sd, ob, cursor_sample_result.node_mask);
|
|
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
|
project_plane_v3_v3v3(cache.sculpt_normal, cache.sculpt_normal, cache.view_normal_symm);
|
|
normalize_v3(cache.sculpt_normal);
|
|
}
|
|
}
|
|
copy_v3_v3(cache.sculpt_normal_symm, cache.sculpt_normal);
|
|
}
|
|
else {
|
|
cache.sculpt_normal_symm = symmetry_flip(cache.sculpt_normal, cache.mirror_symmetry_pass);
|
|
mul_m4_v3(cache.symm_rot_mat.ptr(), cache.sculpt_normal_symm);
|
|
}
|
|
}
|
|
|
|
static void calc_local_from_screen(const ViewContext &vc,
|
|
const float center[3],
|
|
const float screen_dir[2],
|
|
float r_local_dir[3])
|
|
{
|
|
Object &ob = *vc.obact;
|
|
float loc[3];
|
|
|
|
mul_v3_m4v3(loc, ob.object_to_world().ptr(), center);
|
|
const float zfac = ED_view3d_calc_zfac(vc.rv3d, loc);
|
|
|
|
ED_view3d_win_to_delta(vc.region, screen_dir, zfac, r_local_dir);
|
|
normalize_v3(r_local_dir);
|
|
|
|
add_v3_v3(r_local_dir, ob.loc);
|
|
mul_m4_v3(ob.world_to_object().ptr(), r_local_dir);
|
|
}
|
|
|
|
static void calc_brush_local_mat(const float rotation,
|
|
const Object &ob,
|
|
float local_mat[4][4],
|
|
float local_mat_inv[4][4])
|
|
{
|
|
const StrokeCache *cache = ob.sculpt->cache;
|
|
float tmat[4][4];
|
|
float mat[4][4];
|
|
float scale[4][4];
|
|
float angle, v[3];
|
|
|
|
/* Ensure `ob.world_to_object` is up to date. */
|
|
invert_m4_m4(ob.runtime->world_to_object.ptr(), ob.object_to_world().ptr());
|
|
|
|
/* Initialize last column of matrix. */
|
|
mat[0][3] = 0.0f;
|
|
mat[1][3] = 0.0f;
|
|
mat[2][3] = 0.0f;
|
|
mat[3][3] = 1.0f;
|
|
|
|
/* Read rotation (user angle, rake, etc.) to find the view's movement direction (negative X of
|
|
* the brush). */
|
|
angle = rotation + cache->special_rotation;
|
|
/* By convention, motion direction points down the brush's Y axis, the angle represents the X
|
|
* axis, normal is a 90 deg CCW rotation of the motion direction. */
|
|
float motion_normal_screen[2];
|
|
motion_normal_screen[0] = cosf(angle);
|
|
motion_normal_screen[1] = sinf(angle);
|
|
/* Convert view's brush transverse direction to object-space,
|
|
* i.e. the normal of the plane described by the motion */
|
|
float motion_normal_local[3];
|
|
calc_local_from_screen(
|
|
*cache->vc, cache->location_symm, motion_normal_screen, motion_normal_local);
|
|
|
|
/* Calculate the movement direction for the local matrix.
|
|
* Note that there is a deliberate prioritization here: Our calculations are
|
|
* designed such that the _motion vector_ gets projected into the tangent space;
|
|
* in most cases this will be more intuitive than projecting the transverse
|
|
* direction (which is orthogonal to the motion direction and therefore less
|
|
* apparent to the user).
|
|
* The Y-axis of the brush-local frame has to lie in the intersection of the tangent plane
|
|
* and the motion plane. */
|
|
|
|
cross_v3_v3v3(v, cache->sculpt_normal, motion_normal_local);
|
|
normalize_v3_v3(mat[1], v);
|
|
|
|
/* Get other axes. */
|
|
cross_v3_v3v3(mat[0], mat[1], cache->sculpt_normal);
|
|
copy_v3_v3(mat[2], cache->sculpt_normal);
|
|
|
|
/* Set location. */
|
|
copy_v3_v3(mat[3], cache->location_symm);
|
|
|
|
/* Scale by brush radius. */
|
|
float radius = cache->radius;
|
|
|
|
normalize_m4(mat);
|
|
scale_m4_fl(scale, radius);
|
|
mul_m4_m4m4(tmat, mat, scale);
|
|
|
|
/* Return tmat as is (for converting from local area coords to model-space coords). */
|
|
copy_m4_m4(local_mat_inv, tmat);
|
|
/* Return inverse (for converting from model-space coords to local area coords). */
|
|
invert_m4_m4(local_mat, tmat);
|
|
}
|
|
|
|
float3 tilt_apply_to_normal(const Object &object,
|
|
const float4x4 &view_inverse,
|
|
const float3 &normal,
|
|
const float2 &tilt,
|
|
const float tilt_strength)
|
|
{
|
|
if (!USER_EXPERIMENTAL_TEST(&U, use_sculpt_tools_tilt)) {
|
|
return normal;
|
|
}
|
|
const float3 world_space = math::transform_direction(object.object_to_world(), normal);
|
|
|
|
/* Tweaked based on initial user feedback, with a value of 1.0, higher brush tilt strength
|
|
* lead to the stroke surface direction becoming inverted due to extreme rotations. */
|
|
constexpr float tilt_sensitivity = 0.7f;
|
|
const float rot_max = M_PI_2 * tilt_strength * tilt_sensitivity;
|
|
const float3 normal_tilt_y = math::rotate_direction_around_axis(
|
|
world_space, view_inverse.x_axis(), tilt.y * rot_max);
|
|
const float3 normal_tilt_xy = math::rotate_direction_around_axis(
|
|
normal_tilt_y, view_inverse.y_axis(), tilt.x * rot_max);
|
|
|
|
return math::normalize(math::transform_direction(object.world_to_object(), normal_tilt_xy));
|
|
}
|
|
|
|
float3 tilt_apply_to_normal(const float3 &normal,
|
|
const StrokeCache &cache,
|
|
const float tilt_strength)
|
|
{
|
|
return tilt_apply_to_normal(
|
|
*cache.vc->obact, float4x4(cache.vc->rv3d->viewinv), normal, cache.tilt, tilt_strength);
|
|
}
|
|
|
|
float3 tilt_effective_normal_get(const SculptSession &ss, const Brush &brush)
|
|
{
|
|
return tilt_apply_to_normal(ss.cache->sculpt_normal_symm, *ss.cache, brush.tilt_strength_factor);
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
static void update_brush_local_mat(const Sculpt &sd, Object &ob)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
StrokeCache *cache = ob.sculpt->cache;
|
|
|
|
if (cache->mirror_symmetry_pass == 0 && cache->radial_symmetry_pass == 0) {
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
const MTex *mask_tex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT);
|
|
calc_brush_local_mat(
|
|
mask_tex->rot, ob, cache->brush_local_mat.ptr(), cache->brush_local_mat_inv.ptr());
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Texture painting
|
|
* \{ */
|
|
|
|
static bool sculpt_needs_pbvh_pixels(PaintModeSettings &paint_mode_settings,
|
|
const Brush &brush,
|
|
Object &ob)
|
|
{
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT &&
|
|
USER_EXPERIMENTAL_TEST(&U, use_sculpt_texture_paint))
|
|
{
|
|
Image *image;
|
|
ImageUser *image_user;
|
|
return SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void sculpt_pbvh_update_pixels(const Depsgraph &depsgraph,
|
|
PaintModeSettings &paint_mode_settings,
|
|
Object &ob)
|
|
{
|
|
using namespace blender;
|
|
BLI_assert(ob.type == OB_MESH);
|
|
|
|
Image *image;
|
|
ImageUser *image_user;
|
|
if (!SCULPT_paint_image_canvas_get(paint_mode_settings, ob, &image, &image_user)) {
|
|
return;
|
|
}
|
|
|
|
bke::pbvh::build_pixels(depsgraph, ob, *image, *image_user);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Generic Brush Plane & Symmetry Utilities
|
|
* \{ */
|
|
|
|
struct SculptRaycastData {
|
|
Object *object;
|
|
SculptSession *ss;
|
|
const float *ray_start;
|
|
const float *ray_normal;
|
|
bool hit;
|
|
float depth;
|
|
bool original;
|
|
Span<blender::float3> vert_positions;
|
|
blender::OffsetIndices<int> faces;
|
|
Span<int> corner_verts;
|
|
Span<blender::int3> corner_tris;
|
|
blender::VArraySpan<bool> hide_poly;
|
|
|
|
const SubdivCCG *subdiv_ccg;
|
|
|
|
ActiveVert active_vertex = {};
|
|
float3 face_normal;
|
|
|
|
int active_face_grid_index;
|
|
|
|
IsectRayPrecalc isect_precalc;
|
|
};
|
|
|
|
struct SculptFindNearestToRayData {
|
|
Object *object;
|
|
SculptSession *ss;
|
|
const float *ray_start, *ray_normal;
|
|
bool hit;
|
|
float depth;
|
|
float dist_sq_to_ray;
|
|
bool original;
|
|
Span<float3> vert_positions;
|
|
blender::OffsetIndices<int> faces;
|
|
Span<int> corner_verts;
|
|
Span<blender::int3> corner_tris;
|
|
blender::VArraySpan<bool> hide_poly;
|
|
|
|
const SubdivCCG *subdiv_ccg;
|
|
};
|
|
|
|
ePaintSymmetryAreas SCULPT_get_vertex_symm_area(const float co[3])
|
|
{
|
|
ePaintSymmetryAreas symm_area = ePaintSymmetryAreas(PAINT_SYMM_AREA_DEFAULT);
|
|
if (co[0] < 0.0f) {
|
|
symm_area |= PAINT_SYMM_AREA_X;
|
|
}
|
|
if (co[1] < 0.0f) {
|
|
symm_area |= PAINT_SYMM_AREA_Y;
|
|
}
|
|
if (co[2] < 0.0f) {
|
|
symm_area |= PAINT_SYMM_AREA_Z;
|
|
}
|
|
return symm_area;
|
|
}
|
|
|
|
static void flip_qt_qt(float out[4], const float in[4], const ePaintSymmetryFlags symm)
|
|
{
|
|
float axis[3], angle;
|
|
|
|
quat_to_axis_angle(axis, &angle, in);
|
|
normalize_v3(axis);
|
|
|
|
if (symm & PAINT_SYMM_X) {
|
|
axis[0] *= -1.0f;
|
|
angle *= -1.0f;
|
|
}
|
|
if (symm & PAINT_SYMM_Y) {
|
|
axis[1] *= -1.0f;
|
|
angle *= -1.0f;
|
|
}
|
|
if (symm & PAINT_SYMM_Z) {
|
|
axis[2] *= -1.0f;
|
|
angle *= -1.0f;
|
|
}
|
|
|
|
axis_angle_normalized_to_quat(out, axis, angle);
|
|
}
|
|
|
|
static void flip_qt(float quat[4], const ePaintSymmetryFlags symm)
|
|
{
|
|
flip_qt_qt(quat, quat, symm);
|
|
}
|
|
|
|
float3 SCULPT_flip_v3_by_symm_area(const float3 &vector,
|
|
const ePaintSymmetryFlags symm,
|
|
const ePaintSymmetryAreas symmarea,
|
|
const float3 &pivot)
|
|
{
|
|
float3 result = vector;
|
|
for (int i = 0; i < 3; i++) {
|
|
ePaintSymmetryFlags symm_it = ePaintSymmetryFlags(1 << i);
|
|
if (!(symm & symm_it)) {
|
|
continue;
|
|
}
|
|
if (symmarea & symm_it) {
|
|
result = blender::ed::sculpt_paint::symmetry_flip(result, symm_it);
|
|
}
|
|
if (pivot[i] < 0.0f) {
|
|
result = blender::ed::sculpt_paint::symmetry_flip(result, symm_it);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void SCULPT_flip_quat_by_symm_area(float quat[4],
|
|
const ePaintSymmetryFlags symm,
|
|
const ePaintSymmetryAreas symmarea,
|
|
const float pivot[3])
|
|
{
|
|
for (int i = 0; i < 3; i++) {
|
|
ePaintSymmetryFlags symm_it = ePaintSymmetryFlags(1 << i);
|
|
if (!(symm & symm_it)) {
|
|
continue;
|
|
}
|
|
if (symmarea & symm_it) {
|
|
flip_qt(quat, symm_it);
|
|
}
|
|
if (pivot[i] < 0.0f) {
|
|
flip_qt(quat, symm_it);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
void calc_brush_plane(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
Object &ob,
|
|
const IndexMask &node_mask,
|
|
float3 &r_area_no,
|
|
float3 &r_area_co)
|
|
{
|
|
const SculptSession &ss = *ob.sculpt;
|
|
|
|
r_area_no = float3(0.0f);
|
|
r_area_co = float3(0.0f);
|
|
|
|
const bool use_original_plane = (brush.flag & BRUSH_ORIGINAL_PLANE) &&
|
|
brush.sculpt_brush_type != SCULPT_BRUSH_TYPE_PLANE;
|
|
const bool use_original_normal = (brush.flag & BRUSH_ORIGINAL_NORMAL) &&
|
|
brush.sculpt_brush_type != SCULPT_BRUSH_TYPE_PLANE;
|
|
|
|
const bool needs_recalculation = SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache) ||
|
|
!use_original_plane || !use_original_normal;
|
|
|
|
if (SCULPT_stroke_is_main_symmetry_pass(*ss.cache) && needs_recalculation) {
|
|
switch (brush.sculpt_plane) {
|
|
case SCULPT_DISP_DIR_VIEW:
|
|
r_area_no = ss.cache->view_normal;
|
|
break;
|
|
|
|
case SCULPT_DISP_DIR_X:
|
|
r_area_no = float3(1.0f, 0.0f, 0.0f);
|
|
break;
|
|
|
|
case SCULPT_DISP_DIR_Y:
|
|
r_area_no = float3(0.0f, 1.0f, 0.0f);
|
|
break;
|
|
|
|
case SCULPT_DISP_DIR_Z:
|
|
r_area_no = float3(0.0f, 0.0f, 1.0f);
|
|
break;
|
|
|
|
case SCULPT_DISP_DIR_AREA:
|
|
calc_area_normal_and_center(depsgraph, brush, ob, node_mask, r_area_no, r_area_co);
|
|
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
|
project_plane_v3_v3v3(r_area_no, r_area_no, ss.cache->view_normal_symm);
|
|
r_area_no = math::normalize(r_area_no);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Flatten center has not been calculated yet if we are not using the area normal. */
|
|
if (brush.sculpt_plane != SCULPT_DISP_DIR_AREA) {
|
|
BLI_assert(math::is_zero(r_area_co));
|
|
calc_area_center(depsgraph, brush, ob, node_mask, r_area_co);
|
|
}
|
|
|
|
if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache) && use_original_normal) {
|
|
r_area_no = ss.cache->sculpt_normal;
|
|
}
|
|
else {
|
|
ss.cache->sculpt_normal = r_area_no;
|
|
}
|
|
|
|
if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache) && use_original_plane) {
|
|
r_area_co = ss.cache->last_center;
|
|
}
|
|
else {
|
|
ss.cache->last_center = r_area_co;
|
|
}
|
|
}
|
|
else {
|
|
BLI_assert(math::is_zero(ss.cache->symm_rot_mat.location().xyz()));
|
|
|
|
r_area_no = ss.cache->sculpt_normal;
|
|
r_area_no = symmetry_flip(r_area_no, ss.cache->mirror_symmetry_pass);
|
|
r_area_no = math::transform_direction(ss.cache->symm_rot_mat, r_area_no);
|
|
|
|
r_area_co = ss.cache->last_center;
|
|
r_area_co = symmetry_flip(r_area_co, ss.cache->mirror_symmetry_pass);
|
|
r_area_co = math::transform_point(ss.cache->symm_rot_mat, r_area_co);
|
|
|
|
/* Shift the plane for the current tile. */
|
|
r_area_co += ss.cache->plane_offset;
|
|
}
|
|
}
|
|
|
|
float brush_plane_offset_get(const Brush &brush, const SculptSession &ss)
|
|
{
|
|
return brush.flag & BRUSH_OFFSET_PRESSURE ? brush.plane_offset * ss.cache->pressure :
|
|
brush.plane_offset;
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sculpt Brush Utilities
|
|
* \{ */
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
static void dynamic_topology_update(const Depsgraph &depsgraph,
|
|
const Scene & /*scene*/,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
UnifiedPaintSettings & /*ups*/,
|
|
PaintModeSettings & /*paint_mode_settings*/)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
/* Build a list of all nodes that are potentially within the brush's area of influence. */
|
|
const bool use_original = brush_type_needs_original(brush.sculpt_brush_type) ? true :
|
|
!ss.cache->accum;
|
|
const float radius_scale = 1.25f;
|
|
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = pbvh_gather_generic(ob, brush, use_original, radius_scale, memory);
|
|
if (node_mask.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
MutableSpan<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
|
|
/* Free index based vertex info as it will become invalid after modifying the topology during the
|
|
* stroke. */
|
|
ss.vertex_info.boundary.clear();
|
|
|
|
PBVHTopologyUpdateMode mode = PBVHTopologyUpdateMode(0);
|
|
float location[3];
|
|
|
|
if (!(sd.flags & SCULPT_DYNTOPO_DETAIL_MANUAL)) {
|
|
if (sd.flags & SCULPT_DYNTOPO_SUBDIVIDE) {
|
|
mode |= PBVH_Subdivide;
|
|
}
|
|
|
|
if ((sd.flags & SCULPT_DYNTOPO_COLLAPSE) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SIMPLIFY))
|
|
{
|
|
mode |= PBVH_Collapse;
|
|
}
|
|
}
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::Mask);
|
|
}
|
|
else {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::Position);
|
|
}
|
|
pbvh.tag_positions_changed(node_mask);
|
|
pbvh.tag_topology_changed(node_mask);
|
|
node_mask.foreach_index([&](const int i) { BKE_pbvh_node_mark_topology_update(nodes[i]); });
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
BKE_pbvh_bmesh_node_save_orig(ss.bm, ss.bm_log, &nodes[i], false);
|
|
});
|
|
|
|
float max_edge_len;
|
|
if (sd.flags & (SCULPT_DYNTOPO_DETAIL_CONSTANT | SCULPT_DYNTOPO_DETAIL_MANUAL)) {
|
|
max_edge_len = dyntopo::detail_size::constant_to_detail_size(sd.constant_detail, ob);
|
|
}
|
|
else if (sd.flags & SCULPT_DYNTOPO_DETAIL_BRUSH) {
|
|
max_edge_len = dyntopo::detail_size::brush_to_detail_size(sd.detail_percent, ss.cache->radius);
|
|
}
|
|
else {
|
|
max_edge_len = dyntopo::detail_size::relative_to_detail_size(
|
|
sd.detail_size, ss.cache->radius, ss.cache->dyntopo_pixel_radius, U.pixelsize);
|
|
}
|
|
const float min_edge_len = max_edge_len * dyntopo::detail_size::EDGE_LENGTH_MIN_FACTOR;
|
|
|
|
bke::pbvh::bmesh_update_topology(*ss.bm,
|
|
pbvh,
|
|
*ss.bm_log,
|
|
mode,
|
|
min_edge_len,
|
|
max_edge_len,
|
|
ss.cache->location_symm,
|
|
ss.cache->view_normal_symm,
|
|
ss.cache->radius,
|
|
(brush.flag & BRUSH_FRONTFACE) != 0,
|
|
(brush.falloff_shape != PAINT_FALLOFF_SHAPE_SPHERE));
|
|
|
|
/* Update average stroke position. */
|
|
copy_v3_v3(location, ss.cache->location);
|
|
mul_m4_v3(ob.object_to_world().ptr(), location);
|
|
}
|
|
|
|
static bool brush_type_needs_all_pbvh_nodes(const Brush &brush)
|
|
{
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_ELASTIC_DEFORM) {
|
|
/* Elastic deformations in any brush need all nodes to avoid artifacts as the effect
|
|
* of the Kelvinlet is not constrained by the radius. */
|
|
return true;
|
|
}
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_POSE) {
|
|
/* Pose needs all nodes because it applies all symmetry iterations at the same time
|
|
* and the IK chain can grow to any area of the model. */
|
|
/* TODO: This can be optimized by filtering the nodes after calculating the chain. */
|
|
return true;
|
|
}
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_BOUNDARY) {
|
|
/* Boundary needs all nodes because it is not possible to know where the boundary
|
|
* deformation is going to be propagated before calculating it. */
|
|
/* TODO: after calculating the boundary info in the first iteration, it should be
|
|
* possible to get the nodes that have vertices included in any boundary deformation
|
|
* and cache them. */
|
|
return true;
|
|
}
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SNAKE_HOOK &&
|
|
brush.snake_hook_deform_type == BRUSH_SNAKE_HOOK_DEFORM_ELASTIC)
|
|
{
|
|
/* Snake hook in elastic deform type has same requirements as the elastic deform brush. */
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Calculates the nodes that a brush will influence. */
|
|
static brushes::CursorSampleResult calc_brush_node_mask(const Depsgraph &depsgraph,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
IndexMaskMemory &memory)
|
|
{
|
|
const SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
const bool use_original = brush_type_needs_original(brush.sculpt_brush_type) ? true :
|
|
!ss.cache->accum;
|
|
/* Build a list of all nodes that are potentially within the brush's area of influence */
|
|
|
|
if (brush_type_needs_all_pbvh_nodes(brush)) {
|
|
/* These brushes need to update all nodes as they are not constrained by the brush radius */
|
|
return {all_leaf_nodes(pbvh, memory), std::nullopt, std::nullopt};
|
|
}
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_PLANE) {
|
|
return brushes::plane::calc_node_mask(depsgraph, ob, brush, memory);
|
|
}
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLAY_STRIPS) {
|
|
return brushes::clay_strips::calc_node_mask(depsgraph, ob, brush, memory);
|
|
}
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH) {
|
|
return {cloth::brush_affected_nodes_gather(ob, brush, memory), std::nullopt, std::nullopt};
|
|
}
|
|
|
|
float radius_scale = 1.0f;
|
|
/* Corners of square brushes can go outside the brush radius. */
|
|
if (BKE_brush_has_cube_tip(&brush, PaintMode::Sculpt)) {
|
|
radius_scale = M_SQRT2;
|
|
}
|
|
|
|
/* With these options enabled not all required nodes are inside the original brush radius, so
|
|
* the brush can produce artifacts in some situations. */
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW && brush.flag & BRUSH_ORIGINAL_NORMAL) {
|
|
radius_scale = 2.0f;
|
|
}
|
|
return {pbvh_gather_generic(ob, brush, use_original, radius_scale, memory),
|
|
std::nullopt,
|
|
std::nullopt};
|
|
}
|
|
|
|
static void push_undo_nodes(const Depsgraph &depsgraph,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
const IndexMask &node_mask)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
bool need_coords = ss.cache->supports_gravity;
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW_FACE_SETS) {
|
|
/* Draw face sets in smooth mode moves the vertices. */
|
|
if (ss.cache->alt_smooth) {
|
|
need_coords = true;
|
|
}
|
|
else {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::FaceSet);
|
|
}
|
|
}
|
|
else if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::Mask);
|
|
}
|
|
else if (brush_type_is_paint(brush.sculpt_brush_type)) {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::Color);
|
|
}
|
|
else {
|
|
need_coords = true;
|
|
}
|
|
|
|
if (need_coords) {
|
|
undo::push_nodes(depsgraph, ob, node_mask, undo::Type::Position);
|
|
}
|
|
}
|
|
|
|
static void do_brush_action(const Depsgraph &depsgraph,
|
|
const Scene &scene,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
UnifiedPaintSettings &ups,
|
|
PaintModeSettings &paint_mode_settings)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
IndexMaskMemory memory;
|
|
IndexMask texnode_mask;
|
|
|
|
const bool use_original = brush_type_needs_original(brush.sculpt_brush_type) ? true :
|
|
!ss.cache->accum;
|
|
const bool use_pixels = sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob);
|
|
|
|
if (sculpt_needs_pbvh_pixels(paint_mode_settings, brush, ob)) {
|
|
sculpt_pbvh_update_pixels(depsgraph, paint_mode_settings, ob);
|
|
|
|
texnode_mask = pbvh_gather_texpaint(ob, brush, use_original, 1.0f, memory);
|
|
|
|
if (texnode_mask.is_empty()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const brushes::CursorSampleResult cursor_sample_result = calc_brush_node_mask(
|
|
depsgraph, ob, brush, memory);
|
|
const IndexMask node_mask = cursor_sample_result.node_mask;
|
|
|
|
/* Draw Face Sets in draw mode makes a single undo push, in alt-smooth mode deforms the
|
|
* vertices and uses regular coords undo. */
|
|
/* It also assigns the paint_face_set here as it needs to be done regardless of the stroke type
|
|
* and the number of nodes under the brush influence. */
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW_FACE_SETS &&
|
|
SCULPT_stroke_is_first_brush_step(*ss.cache) && !ss.cache->alt_smooth)
|
|
{
|
|
if (ss.cache->invert) {
|
|
/* When inverting the brush, pick the paint face mask ID from the mesh. */
|
|
ss.cache->paint_face_set = face_set::active_face_set_get(ob);
|
|
}
|
|
else {
|
|
/* By default create a new Face Sets. */
|
|
ss.cache->paint_face_set = face_set::find_next_available_id(ob);
|
|
}
|
|
}
|
|
|
|
/* For anchored brushes with spherical falloff, we start off with zero radius, thus we have no
|
|
* bke::pbvh::Tree nodes on the first brush step. */
|
|
if (!node_mask.is_empty() ||
|
|
((brush.falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) && (brush.flag & BRUSH_ANCHORED)))
|
|
{
|
|
if (SCULPT_stroke_is_first_brush_step(*ss.cache)) {
|
|
/* Initialize auto-masking cache. */
|
|
if (auto_mask::is_enabled(sd, ob, &brush)) {
|
|
ss.cache->automasking = auto_mask::cache_init(depsgraph, sd, &brush, ob);
|
|
}
|
|
/* Initialize surface smooth cache. */
|
|
if ((brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SMOOTH) &&
|
|
(brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE))
|
|
{
|
|
BLI_assert(ss.cache->surface_smooth_laplacian_disp.is_empty());
|
|
ss.cache->surface_smooth_laplacian_disp = Array<float3>(SCULPT_vertex_count_get(ob),
|
|
float3(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Only act if some verts are inside the brush area. */
|
|
if (node_mask.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
if (auto_mask::is_enabled(sd, ob, &brush) && ss.cache->automasking &&
|
|
ss.cache->automasking->settings.flags & BRUSH_AUTOMASKING_CAVITY_ALL)
|
|
{
|
|
ss.cache->automasking->calc_cavity_factor(depsgraph, ob, node_mask);
|
|
}
|
|
|
|
if (!use_pixels) {
|
|
push_undo_nodes(depsgraph, ob, brush, node_mask);
|
|
}
|
|
|
|
if (sculpt_brush_needs_normal(ss, brush)) {
|
|
update_sculpt_normal(depsgraph, sd, ob, cursor_sample_result);
|
|
}
|
|
|
|
update_brush_local_mat(sd, ob);
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_POSE &&
|
|
SCULPT_stroke_is_first_brush_step(*ss.cache))
|
|
{
|
|
pose::pose_brush_init(depsgraph, ob, ss, brush);
|
|
}
|
|
|
|
if (brush.deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
|
|
if (!ss.cache->cloth_sim) {
|
|
ss.cache->cloth_sim = cloth::brush_simulation_create(
|
|
depsgraph, ob, 1.0f, 0.0f, 0.0f, false, true);
|
|
}
|
|
cloth::brush_store_simulation_state(depsgraph, ob, *ss.cache->cloth_sim);
|
|
cloth::ensure_nodes_constraints(sd,
|
|
ob,
|
|
node_mask,
|
|
*ss.cache->cloth_sim,
|
|
ss.cache->location_symm,
|
|
std::numeric_limits<float>::max());
|
|
}
|
|
|
|
bool invert = ss.cache->pen_flip || ss.cache->invert;
|
|
if (brush.flag & BRUSH_DIR_IN) {
|
|
invert = !invert;
|
|
}
|
|
|
|
/* Apply one type of brush action. */
|
|
switch (brush.sculpt_brush_type) {
|
|
case SCULPT_BRUSH_TYPE_DRAW: {
|
|
if (brush_uses_vector_displacement(brush)) {
|
|
brushes::do_draw_vector_displacement_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
else {
|
|
brushes::do_draw_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
break;
|
|
}
|
|
case SCULPT_BRUSH_TYPE_SMOOTH:
|
|
if (brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
|
|
/* NOTE: The enhance brush needs to initialize its state on the first brush step. The
|
|
* stroke strength can become 0 during the stroke, but it can not change sign (the sign is
|
|
* determined in the beginning of the stroke. So here it is important to not switch to
|
|
* enhance brush in the middle of the stroke. */
|
|
if (ss.cache->initial_direction_flipped) {
|
|
/* Invert mode, intensify details. */
|
|
brushes::do_enhance_details_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
else {
|
|
brushes::do_smooth_brush(
|
|
depsgraph, sd, ob, node_mask, std::clamp(ss.cache->bstrength, 0.0f, 1.0f));
|
|
}
|
|
}
|
|
else if (brush.smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
|
|
brushes::do_surface_smooth_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CREASE:
|
|
brushes::do_crease_brush(depsgraph, scene, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_BLOB:
|
|
brushes::do_blob_brush(depsgraph, scene, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_PINCH:
|
|
brushes::do_pinch_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_INFLATE:
|
|
brushes::do_inflate_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_GRAB:
|
|
brushes::do_grab_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_ROTATE:
|
|
brushes::do_rotate_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_SNAKE_HOOK:
|
|
brushes::do_snake_hook_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_NUDGE:
|
|
brushes::do_nudge_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_THUMB:
|
|
brushes::do_thumb_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_LAYER:
|
|
brushes::do_layer_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CLAY:
|
|
brushes::do_clay_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CLAY_STRIPS:
|
|
BLI_assert(cursor_sample_result.plane_normal && cursor_sample_result.plane_center);
|
|
brushes::do_clay_strips_brush(depsgraph,
|
|
sd,
|
|
ob,
|
|
node_mask,
|
|
*cursor_sample_result.plane_normal,
|
|
*cursor_sample_result.plane_center);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_MULTIPLANE_SCRAPE:
|
|
brushes::do_multiplane_scrape_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CLAY_THUMB:
|
|
brushes::do_clay_thumb_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_MASK:
|
|
switch ((BrushMaskTool)brush.mask_tool) {
|
|
case BRUSH_MASK_DRAW:
|
|
brushes::do_mask_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case BRUSH_MASK_SMOOTH:
|
|
brushes::do_smooth_mask_brush(depsgraph, sd, ob, node_mask, ss.cache->bstrength);
|
|
break;
|
|
}
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_POSE:
|
|
pose::do_pose_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_DRAW_SHARP:
|
|
brushes::do_draw_sharp_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_ELASTIC_DEFORM:
|
|
brushes::do_elastic_deform_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_SLIDE_RELAX:
|
|
if (ss.cache->alt_smooth) {
|
|
brushes::do_topology_relax_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
else {
|
|
brushes::do_topology_slide_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_BOUNDARY:
|
|
boundary::do_boundary_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_CLOTH:
|
|
cloth::do_cloth_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_DRAW_FACE_SETS:
|
|
if (!ss.cache->alt_smooth) {
|
|
brushes::do_draw_face_sets_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
else {
|
|
brushes::do_relax_face_sets_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_ERASER:
|
|
brushes::do_displacement_eraser_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR:
|
|
brushes::do_displacement_smear_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_PAINT:
|
|
color::do_paint_brush(
|
|
scene, depsgraph, paint_mode_settings, sd, ob, node_mask, texnode_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_SMEAR:
|
|
color::do_smear_brush(depsgraph, sd, ob, node_mask);
|
|
break;
|
|
case SCULPT_BRUSH_TYPE_PLANE:
|
|
BLI_assert(cursor_sample_result.plane_normal && cursor_sample_result.plane_center);
|
|
brushes::do_plane_brush(depsgraph,
|
|
sd,
|
|
ob,
|
|
node_mask,
|
|
*cursor_sample_result.plane_normal,
|
|
*cursor_sample_result.plane_center);
|
|
break;
|
|
}
|
|
|
|
if (!ELEM(brush.sculpt_brush_type, SCULPT_BRUSH_TYPE_SMOOTH, SCULPT_BRUSH_TYPE_MASK) &&
|
|
brush.autosmooth_factor > 0)
|
|
{
|
|
if (brush.flag & BRUSH_INVERSE_SMOOTH_PRESSURE) {
|
|
brushes::do_smooth_brush(
|
|
depsgraph, sd, ob, node_mask, brush.autosmooth_factor * (1.0f - ss.cache->pressure));
|
|
}
|
|
else {
|
|
brushes::do_smooth_brush(depsgraph, sd, ob, node_mask, brush.autosmooth_factor);
|
|
}
|
|
}
|
|
|
|
if (brush_uses_topology_rake(ss, brush)) {
|
|
brushes::do_bmesh_topology_rake_brush(
|
|
depsgraph, sd, ob, node_mask, brush.topology_rake_factor);
|
|
}
|
|
|
|
/* The cloth brush adds the gravity as a regular force and it is processed in the solver. */
|
|
if (ss.cache->supports_gravity && brush.sculpt_brush_type != SCULPT_BRUSH_TYPE_CLOTH) {
|
|
brushes::do_gravity_brush(depsgraph, sd, ob, node_mask);
|
|
}
|
|
|
|
if (brush.deform_target == BRUSH_DEFORM_TARGET_CLOTH_SIM) {
|
|
if (SCULPT_stroke_is_main_symmetry_pass(*ss.cache)) {
|
|
cloth::sim_activate_nodes(ob, *ss.cache->cloth_sim, node_mask);
|
|
cloth::do_simulation_step(depsgraph, sd, ob, *ss.cache->cloth_sim, node_mask);
|
|
}
|
|
}
|
|
|
|
/* Update average stroke position. */
|
|
const float3 world_location = math::project_point(ob.object_to_world(), ss.cache->location);
|
|
|
|
add_v3_v3(ups.average_stroke_accum, world_location);
|
|
ups.average_stroke_counter++;
|
|
/* Update last stroke position. */
|
|
ups.last_stroke_valid = true;
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
void SCULPT_cache_calc_brushdata_symm(blender::ed::sculpt_paint::StrokeCache &cache,
|
|
const ePaintSymmetryFlags symm,
|
|
const char axis,
|
|
const float angle)
|
|
{
|
|
using namespace blender;
|
|
cache.location_symm = ed::sculpt_paint::symmetry_flip(cache.location, symm);
|
|
cache.last_location_symm = ed::sculpt_paint::symmetry_flip(cache.last_location, symm);
|
|
cache.grab_delta_symm = ed::sculpt_paint::symmetry_flip(cache.grab_delta, symm);
|
|
cache.view_normal_symm = ed::sculpt_paint::symmetry_flip(cache.view_normal, symm);
|
|
|
|
cache.initial_location_symm = ed::sculpt_paint::symmetry_flip(cache.initial_location, symm);
|
|
cache.initial_normal_symm = ed::sculpt_paint::symmetry_flip(cache.initial_normal, symm);
|
|
|
|
/* XXX This reduces the length of the grab delta if it approaches the line of symmetry
|
|
* XXX However, a different approach appears to be needed. */
|
|
#if 0
|
|
if (sd->paint.symmetry_flags & PAINT_SYMMETRY_FEATHER) {
|
|
float frac = 1.0f / max_overlap_count(sd);
|
|
float reduce = (feather - frac) / (1.0f - frac);
|
|
|
|
printf("feather: %f frac: %f reduce: %f\n", feather, frac, reduce);
|
|
|
|
if (frac < 1.0f) {
|
|
mul_v3_fl(cache.grab_delta_symmetry, reduce);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
cache.symm_rot_mat = float4x4::identity();
|
|
cache.symm_rot_mat_inv = float4x4::identity();
|
|
zero_v3(cache.plane_offset);
|
|
|
|
/* Expects XYZ. */
|
|
if (axis) {
|
|
rotate_m4(cache.symm_rot_mat.ptr(), axis, angle);
|
|
rotate_m4(cache.symm_rot_mat_inv.ptr(), axis, -angle);
|
|
}
|
|
|
|
mul_m4_v3(cache.symm_rot_mat.ptr(), cache.location_symm);
|
|
mul_m4_v3(cache.symm_rot_mat.ptr(), cache.grab_delta_symm);
|
|
|
|
if (cache.supports_gravity) {
|
|
cache.gravity_direction_symm = ed::sculpt_paint::symmetry_flip(cache.gravity_direction, symm);
|
|
mul_m4_v3(cache.symm_rot_mat.ptr(), cache.gravity_direction_symm);
|
|
}
|
|
|
|
if (cache.rake_rotation) {
|
|
float4 new_quat;
|
|
float4 existing(cache.rake_rotation->w,
|
|
cache.rake_rotation->x,
|
|
cache.rake_rotation->y,
|
|
cache.rake_rotation->z);
|
|
flip_qt_qt(new_quat, existing, symm);
|
|
cache.rake_rotation_symm = math::Quaternion(new_quat);
|
|
}
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
using BrushActionFunc = void (*)(const Depsgraph &depsgraph,
|
|
const Scene &scene,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
UnifiedPaintSettings &ups,
|
|
PaintModeSettings &paint_mode_settings);
|
|
|
|
static void do_tiled(const Depsgraph &depsgraph,
|
|
const Scene &scene,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
UnifiedPaintSettings &ups,
|
|
PaintModeSettings &paint_mode_settings,
|
|
const BrushActionFunc action)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
StrokeCache *cache = ss.cache;
|
|
const float radius = cache->radius;
|
|
const Bounds<float3> bb = *BKE_object_boundbox_get(&ob);
|
|
const float *bbMin = bb.min;
|
|
const float *bbMax = bb.max;
|
|
const float *step = sd.paint.tile_offset;
|
|
|
|
/* These are integer locations, for real location: multiply with step and add orgLoc.
|
|
* So 0,0,0 is at orgLoc. */
|
|
int start[3];
|
|
int end[3];
|
|
int cur[3];
|
|
|
|
/* Position of the "prototype" stroke for tiling. */
|
|
float orgLoc[3];
|
|
float original_initial_location[3];
|
|
copy_v3_v3(orgLoc, cache->location_symm);
|
|
copy_v3_v3(original_initial_location, cache->initial_location_symm);
|
|
|
|
for (int dim = 0; dim < 3; dim++) {
|
|
if ((sd.paint.symmetry_flags & (PAINT_TILE_X << dim)) && step[dim] > 0) {
|
|
start[dim] = (bbMin[dim] - orgLoc[dim] - radius) / step[dim];
|
|
end[dim] = (bbMax[dim] - orgLoc[dim] + radius) / step[dim];
|
|
}
|
|
else {
|
|
start[dim] = end[dim] = 0;
|
|
}
|
|
}
|
|
|
|
/* First do the "un-tiled" position to initialize the stroke for this location. */
|
|
cache->tile_pass = 0;
|
|
action(depsgraph, scene, sd, ob, brush, ups, paint_mode_settings);
|
|
|
|
/* Now do it for all the tiles. */
|
|
copy_v3_v3_int(cur, start);
|
|
for (cur[0] = start[0]; cur[0] <= end[0]; cur[0]++) {
|
|
for (cur[1] = start[1]; cur[1] <= end[1]; cur[1]++) {
|
|
for (cur[2] = start[2]; cur[2] <= end[2]; cur[2]++) {
|
|
if (!cur[0] && !cur[1] && !cur[2]) {
|
|
/* Skip tile at orgLoc, this was already handled before all others. */
|
|
continue;
|
|
}
|
|
|
|
++cache->tile_pass;
|
|
|
|
for (int dim = 0; dim < 3; dim++) {
|
|
cache->location_symm[dim] = cur[dim] * step[dim] + orgLoc[dim];
|
|
cache->plane_offset[dim] = cur[dim] * step[dim];
|
|
cache->initial_location_symm[dim] = cur[dim] * step[dim] +
|
|
original_initial_location[dim];
|
|
}
|
|
action(depsgraph, scene, sd, ob, brush, ups, paint_mode_settings);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_radial_symmetry(const Depsgraph &depsgraph,
|
|
const Scene &scene,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const Brush &brush,
|
|
UnifiedPaintSettings &ups,
|
|
PaintModeSettings &paint_mode_settings,
|
|
const BrushActionFunc action,
|
|
const ePaintSymmetryFlags symm,
|
|
const int axis,
|
|
const float /*feather*/)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
|
|
for (int i = 1; i < sd.radial_symm[axis - 'X']; i++) {
|
|
const float angle = 2.0f * M_PI * i / sd.radial_symm[axis - 'X'];
|
|
ss.cache->radial_symmetry_pass = i;
|
|
SCULPT_cache_calc_brushdata_symm(*ss.cache, symm, axis, angle);
|
|
do_tiled(depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Noise texture gives different values for the same input coord; this
|
|
* can tear a multi-resolution mesh during sculpting so do a stitch in this case.
|
|
*/
|
|
static void sculpt_fix_noise_tear(const Sculpt &sd, Object &ob)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
const MTex *mtex = BKE_brush_mask_texture_get(&brush, OB_MODE_SCULPT);
|
|
|
|
if (ss.multires.active && mtex->tex && mtex->tex->type == TEX_NOISE) {
|
|
multires_stitch_grids(&ob);
|
|
}
|
|
}
|
|
|
|
static void do_symmetrical_brush_actions(const Depsgraph &depsgraph,
|
|
const Scene &scene,
|
|
const Sculpt &sd,
|
|
Object &ob,
|
|
const BrushActionFunc action,
|
|
UnifiedPaintSettings &ups,
|
|
PaintModeSettings &paint_mode_settings)
|
|
{
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
SculptSession &ss = *ob.sculpt;
|
|
StrokeCache &cache = *ss.cache;
|
|
const char symm = SCULPT_mesh_symmetry_xyz_get(ob);
|
|
|
|
float feather = calc_symmetry_feather(sd, *ss.cache);
|
|
|
|
cache.bstrength = brush_strength(sd, cache, feather, ups, paint_mode_settings);
|
|
cache.symmetry = symm;
|
|
|
|
/* `symm` is a bit combination of XYZ -
|
|
* 1 is mirror X; 2 is Y; 3 is XY; 4 is Z; 5 is XZ; 6 is YZ; 7 is XYZ */
|
|
for (int i = 0; i <= symm; i++) {
|
|
if (!is_symmetry_iteration_valid(i, symm)) {
|
|
continue;
|
|
}
|
|
const ePaintSymmetryFlags symm = ePaintSymmetryFlags(i);
|
|
cache.mirror_symmetry_pass = symm;
|
|
cache.radial_symmetry_pass = 0;
|
|
|
|
SCULPT_cache_calc_brushdata_symm(cache, symm, 0, 0);
|
|
do_tiled(depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action);
|
|
|
|
do_radial_symmetry(
|
|
depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'X', feather);
|
|
do_radial_symmetry(
|
|
depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'Y', feather);
|
|
do_radial_symmetry(
|
|
depsgraph, scene, sd, ob, brush, ups, paint_mode_settings, action, symm, 'Z', feather);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
bool SCULPT_mode_poll(bContext *C)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
return ob && ob->mode & OB_MODE_SCULPT;
|
|
}
|
|
|
|
bool SCULPT_mode_poll_view3d(bContext *C)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
return (SCULPT_mode_poll(C) && CTX_wm_region_view3d(C));
|
|
}
|
|
|
|
bool SCULPT_poll(bContext *C)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
return SCULPT_mode_poll(C) && blender::ed::sculpt_paint::paint_brush_tool_poll(C);
|
|
}
|
|
|
|
/**
|
|
* While most non-brush tools in sculpt mode do not use the brush cursor, the trim tools
|
|
* and the filter tools are expected to have the cursor visible so that some functionality is
|
|
* easier to visually estimate.
|
|
*
|
|
* See: #122856
|
|
*/
|
|
static bool is_brush_related_tool(bContext *C)
|
|
{
|
|
Paint *paint = BKE_paint_get_active_from_context(C);
|
|
Object *ob = CTX_data_active_object(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
if (paint && ob && BKE_paint_brush(paint) &&
|
|
(area && ELEM(area->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) &&
|
|
(region && region->regiontype == RGN_TYPE_WINDOW))
|
|
{
|
|
bToolRef *tref = area->runtime.tool;
|
|
if (tref && tref->runtime && tref->runtime->keymap[0]) {
|
|
std::array<wmOperatorType *, 7> trim_operators = {
|
|
WM_operatortype_find("SCULPT_OT_trim_box_gesture", false),
|
|
WM_operatortype_find("SCULPT_OT_trim_lasso_gesture", false),
|
|
WM_operatortype_find("SCULPT_OT_trim_line_gesture", false),
|
|
WM_operatortype_find("SCULPT_OT_trim_polyline_gesture", false),
|
|
WM_operatortype_find("SCULPT_OT_mesh_filter", false),
|
|
WM_operatortype_find("SCULPT_OT_cloth_filter", false),
|
|
WM_operatortype_find("SCULPT_OT_color_filter", false),
|
|
};
|
|
|
|
return std::any_of(trim_operators.begin(), trim_operators.end(), [tref](wmOperatorType *ot) {
|
|
PointerRNA ptr;
|
|
return WM_toolsystem_ref_properties_get_from_operator(tref, ot, &ptr);
|
|
});
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SCULPT_brush_cursor_poll(bContext *C)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
return SCULPT_mode_poll(C) && (paint_brush_cursor_poll(C) || is_brush_related_tool(C));
|
|
}
|
|
|
|
static const char *sculpt_brush_type_name(const Sculpt &sd)
|
|
{
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
switch (eBrushSculptType(brush.sculpt_brush_type)) {
|
|
case SCULPT_BRUSH_TYPE_DRAW:
|
|
return "Draw Brush";
|
|
case SCULPT_BRUSH_TYPE_SMOOTH:
|
|
return "Smooth Brush";
|
|
case SCULPT_BRUSH_TYPE_CREASE:
|
|
return "Crease Brush";
|
|
case SCULPT_BRUSH_TYPE_BLOB:
|
|
return "Blob Brush";
|
|
case SCULPT_BRUSH_TYPE_PINCH:
|
|
return "Pinch Brush";
|
|
case SCULPT_BRUSH_TYPE_INFLATE:
|
|
return "Inflate Brush";
|
|
case SCULPT_BRUSH_TYPE_GRAB:
|
|
return "Grab Brush";
|
|
case SCULPT_BRUSH_TYPE_NUDGE:
|
|
return "Nudge Brush";
|
|
case SCULPT_BRUSH_TYPE_THUMB:
|
|
return "Thumb Brush";
|
|
case SCULPT_BRUSH_TYPE_LAYER:
|
|
return "Layer Brush";
|
|
case SCULPT_BRUSH_TYPE_CLAY:
|
|
return "Clay Brush";
|
|
case SCULPT_BRUSH_TYPE_CLAY_STRIPS:
|
|
return "Clay Strips Brush";
|
|
case SCULPT_BRUSH_TYPE_CLAY_THUMB:
|
|
return "Clay Thumb Brush";
|
|
case SCULPT_BRUSH_TYPE_SNAKE_HOOK:
|
|
return "Snake Hook Brush";
|
|
case SCULPT_BRUSH_TYPE_ROTATE:
|
|
return "Rotate Brush";
|
|
case SCULPT_BRUSH_TYPE_MASK:
|
|
return "Mask Brush";
|
|
case SCULPT_BRUSH_TYPE_SIMPLIFY:
|
|
return "Simplify Brush";
|
|
case SCULPT_BRUSH_TYPE_DRAW_SHARP:
|
|
return "Draw Sharp Brush";
|
|
case SCULPT_BRUSH_TYPE_ELASTIC_DEFORM:
|
|
return "Elastic Deform Brush";
|
|
case SCULPT_BRUSH_TYPE_POSE:
|
|
return "Pose Brush";
|
|
case SCULPT_BRUSH_TYPE_MULTIPLANE_SCRAPE:
|
|
return "Multi-plane Scrape Brush";
|
|
case SCULPT_BRUSH_TYPE_SLIDE_RELAX:
|
|
return "Slide/Relax Brush";
|
|
case SCULPT_BRUSH_TYPE_BOUNDARY:
|
|
return "Boundary Brush";
|
|
case SCULPT_BRUSH_TYPE_CLOTH:
|
|
return "Cloth Brush";
|
|
case SCULPT_BRUSH_TYPE_DRAW_FACE_SETS:
|
|
return "Draw Face Sets";
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_ERASER:
|
|
return "Multires Displacement Eraser";
|
|
case SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR:
|
|
return "Multires Displacement Smear";
|
|
case SCULPT_BRUSH_TYPE_PAINT:
|
|
return "Paint Brush";
|
|
case SCULPT_BRUSH_TYPE_SMEAR:
|
|
return "Smear Brush";
|
|
case SCULPT_BRUSH_TYPE_PLANE:
|
|
return "Plane Brush";
|
|
}
|
|
|
|
return "Sculpting";
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
StrokeCache::StrokeCache() = default;
|
|
|
|
StrokeCache::~StrokeCache()
|
|
{
|
|
if (this->dial) {
|
|
BLI_dial_free(this->dial);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
enum class StrokeFlags : uint8_t {
|
|
ClipX = 1,
|
|
ClipY = 2,
|
|
ClipZ = 4,
|
|
};
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
/* Initialize mirror modifier clipping. */
|
|
static void sculpt_init_mirror_clipping(const Object &ob, const SculptSession &ss)
|
|
{
|
|
ss.cache->mirror_modifier_clip.mat = float4x4::identity();
|
|
|
|
LISTBASE_FOREACH (ModifierData *, md, &ob.modifiers) {
|
|
if (!(md->type == eModifierType_Mirror && (md->mode & eModifierMode_Realtime))) {
|
|
continue;
|
|
}
|
|
MirrorModifierData *mmd = (MirrorModifierData *)md;
|
|
|
|
if (!(mmd->flag & MOD_MIR_CLIPPING)) {
|
|
continue;
|
|
}
|
|
/* Check each axis for mirroring. */
|
|
for (int i = 0; i < 3; i++) {
|
|
if (!(mmd->flag & (MOD_MIR_AXIS_X << i))) {
|
|
continue;
|
|
}
|
|
/* Enable sculpt clipping. */
|
|
ss.cache->mirror_modifier_clip.flag |= uint8_t(StrokeFlags::ClipX) << i;
|
|
|
|
/* Update the clip tolerance. */
|
|
ss.cache->mirror_modifier_clip.tolerance[i] = std::max(
|
|
mmd->tolerance, ss.cache->mirror_modifier_clip.tolerance[i]);
|
|
|
|
/* Store matrix for mirror object clipping. */
|
|
if (mmd->mirror_ob) {
|
|
const float4x4 mirror_ob_inv = math::invert(mmd->mirror_ob->object_to_world());
|
|
mul_m4_m4m4(ss.cache->mirror_modifier_clip.mat.ptr(),
|
|
mirror_ob_inv.ptr(),
|
|
ob.object_to_world().ptr());
|
|
}
|
|
}
|
|
}
|
|
ss.cache->mirror_modifier_clip.mat_inv = math::invert(ss.cache->mirror_modifier_clip.mat);
|
|
}
|
|
|
|
static void smooth_brush_toggle_on(const bContext *C, Paint *paint, StrokeCache *cache)
|
|
{
|
|
Main *bmain = CTX_data_main(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
Brush *cur_brush = BKE_paint_brush(paint);
|
|
|
|
if (cur_brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
cache->saved_mask_brush_tool = cur_brush->mask_tool;
|
|
cur_brush->mask_tool = BRUSH_MASK_SMOOTH;
|
|
return;
|
|
}
|
|
|
|
if (ELEM(cur_brush->sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_SLIDE_RELAX,
|
|
SCULPT_BRUSH_TYPE_DRAW_FACE_SETS,
|
|
SCULPT_BRUSH_TYPE_PAINT,
|
|
SCULPT_BRUSH_TYPE_SMEAR))
|
|
{
|
|
/* Do nothing, this brush has its own smooth mode. */
|
|
return;
|
|
}
|
|
|
|
/* Switch to the smooth brush if possible. */
|
|
BKE_paint_brush_set_essentials(bmain, paint, "Smooth");
|
|
Brush *smooth_brush = BKE_paint_brush(paint);
|
|
|
|
if (!smooth_brush) {
|
|
BKE_paint_brush_set(paint, cur_brush);
|
|
CLOG_WARN(&LOG, "Switching to the smooth brush not possible, corresponding brush not");
|
|
cache->saved_active_brush = nullptr;
|
|
return;
|
|
}
|
|
|
|
int cur_brush_size = BKE_brush_size_get(scene, cur_brush);
|
|
|
|
cache->saved_active_brush = cur_brush;
|
|
|
|
cache->saved_smooth_size = BKE_brush_size_get(scene, smooth_brush);
|
|
BKE_brush_size_set(scene, smooth_brush, cur_brush_size);
|
|
BKE_curvemapping_init(smooth_brush->curve);
|
|
}
|
|
|
|
static void smooth_brush_toggle_off(const bContext *C, Paint *paint, StrokeCache *cache)
|
|
{
|
|
Brush &brush = *BKE_paint_brush(paint);
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
brush.mask_tool = cache->saved_mask_brush_tool;
|
|
return;
|
|
}
|
|
|
|
if (ELEM(brush.sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_SLIDE_RELAX,
|
|
SCULPT_BRUSH_TYPE_DRAW_FACE_SETS,
|
|
SCULPT_BRUSH_TYPE_PAINT,
|
|
SCULPT_BRUSH_TYPE_SMEAR))
|
|
{
|
|
/* Do nothing. */
|
|
return;
|
|
}
|
|
|
|
/* If saved_active_brush is not set, brush was not switched/affected in
|
|
* smooth_brush_toggle_on(). */
|
|
if (cache->saved_active_brush) {
|
|
Scene *scene = CTX_data_scene(C);
|
|
BKE_brush_size_set(scene, &brush, cache->saved_smooth_size);
|
|
BKE_paint_brush_set(paint, cache->saved_active_brush);
|
|
cache->saved_active_brush = nullptr;
|
|
}
|
|
}
|
|
|
|
/* Initialize the stroke cache invariants from operator properties. */
|
|
static void sculpt_update_cache_invariants(
|
|
bContext *C, Sculpt &sd, SculptSession &ss, const wmOperator &op, const float mval[2])
|
|
{
|
|
StrokeCache *cache = MEM_new<StrokeCache>(__func__);
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
UnifiedPaintSettings *ups = &tool_settings->unified_paint_settings;
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
ViewContext *vc = paint_stroke_view_context(static_cast<PaintStroke *>(op.customdata));
|
|
Object &ob = *CTX_data_active_object(C);
|
|
|
|
ss.cache = cache;
|
|
|
|
/* Set scaling adjustment. */
|
|
float max_scale = 0.0f;
|
|
for (int i = 0; i < 3; i++) {
|
|
max_scale = max_ff(max_scale, fabsf(ob.scale[i]));
|
|
}
|
|
cache->scale[0] = max_scale / ob.scale[0];
|
|
cache->scale[1] = max_scale / ob.scale[1];
|
|
cache->scale[2] = max_scale / ob.scale[2];
|
|
|
|
cache->plane_trim_squared = brush->plane_trim * brush->plane_trim;
|
|
|
|
cache->mirror_modifier_clip.flag = 0;
|
|
|
|
sculpt_init_mirror_clipping(ob, ss);
|
|
|
|
/* Initial mouse location. */
|
|
cache->initial_mouse = mval ? float2(mval) : float2(0.0f);
|
|
|
|
cache->initial_location_symm = ss.cursor_location;
|
|
cache->initial_location = ss.cursor_location;
|
|
|
|
cache->initial_normal_symm = ss.cursor_normal;
|
|
cache->initial_normal = ss.cursor_normal;
|
|
|
|
const int mode = RNA_enum_get(op.ptr, "mode");
|
|
cache->pen_flip = RNA_boolean_get(op.ptr, "pen_flip");
|
|
cache->invert = mode == BRUSH_STROKE_INVERT;
|
|
cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH;
|
|
|
|
cache->normal_weight = brush->normal_weight;
|
|
|
|
/* Interpret invert as following normal, for grab brushes. */
|
|
if (bke::brush::supports_normal_weight(*brush)) {
|
|
if (cache->invert) {
|
|
cache->invert = false;
|
|
cache->normal_weight = (cache->normal_weight == 0.0f);
|
|
}
|
|
}
|
|
|
|
/* Not very nice, but with current events system implementation
|
|
* we can't handle brush appearance inversion hotkey separately (sergey). */
|
|
if (cache->invert) {
|
|
ups->draw_inverted = true;
|
|
}
|
|
else {
|
|
ups->draw_inverted = false;
|
|
}
|
|
|
|
/* Alt-Smooth. */
|
|
if (cache->alt_smooth) {
|
|
smooth_brush_toggle_on(C, &sd.paint, cache);
|
|
/* Refresh the brush pointer in case we switched brush in the toggle function. */
|
|
brush = BKE_paint_brush(&sd.paint);
|
|
}
|
|
|
|
cache->mouse = cache->initial_mouse;
|
|
cache->mouse_event = cache->initial_mouse;
|
|
copy_v2_v2(ups->tex_mouse, cache->initial_mouse);
|
|
|
|
cache->initial_direction_flipped = brush_flip(*brush, *cache) < 0.0f;
|
|
|
|
/* Truly temporary data that isn't stored in properties. */
|
|
cache->vc = vc;
|
|
cache->brush = brush;
|
|
|
|
/* Cache projection matrix. */
|
|
cache->projection_mat = ED_view3d_ob_project_mat_get(cache->vc->rv3d, &ob);
|
|
|
|
const float3 z_axis = {0.0f, 0.0f, 1.0f};
|
|
ob.runtime->world_to_object = math::invert(ob.object_to_world());
|
|
cache->view_normal = math::normalize(math::transform_direction(
|
|
ob.world_to_object() * float4x4(cache->vc->rv3d->viewinv), z_axis));
|
|
|
|
cache->supports_gravity = bke::brush::supports_gravity(*brush) && sd.gravity_factor > 0.0f;
|
|
/* Get gravity vector in world space. */
|
|
if (cache->supports_gravity) {
|
|
if (sd.gravity_object) {
|
|
const Object *gravity_object = sd.gravity_object;
|
|
cache->gravity_direction = gravity_object->object_to_world().z_axis();
|
|
}
|
|
else {
|
|
cache->gravity_direction = {0.0f, 0.0f, 1.0f};
|
|
}
|
|
|
|
/* Transform to sculpted object space. */
|
|
cache->gravity_direction = math::normalize(
|
|
math::transform_direction(ob.world_to_object(), cache->gravity_direction));
|
|
}
|
|
|
|
cache->accum = true;
|
|
|
|
/* Make copies of the mesh vertex locations and normals for some brushes. */
|
|
if (brush->flag & BRUSH_ANCHORED) {
|
|
cache->accum = false;
|
|
}
|
|
|
|
/* Draw sharp does not need the original coordinates to produce the accumulate effect, so it
|
|
* should work the opposite way. */
|
|
if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW_SHARP) {
|
|
cache->accum = false;
|
|
}
|
|
|
|
if (bke::brush::supports_accumulate(*brush)) {
|
|
if (!(brush->flag & BRUSH_ACCUMULATE)) {
|
|
cache->accum = false;
|
|
if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW_SHARP) {
|
|
cache->accum = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Original coordinates require the sculpt undo system, which isn't used
|
|
* for image brushes. It's also not necessary, just disable it. */
|
|
if (brush && brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT &&
|
|
SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob))
|
|
{
|
|
cache->accum = true;
|
|
}
|
|
|
|
cache->first_time = true;
|
|
cache->plane_brush.first_time = true;
|
|
|
|
if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_ROTATE) {
|
|
constexpr int pixel_input_threshold = 5;
|
|
cache->dial = BLI_dial_init(cache->initial_mouse, pixel_input_threshold);
|
|
}
|
|
}
|
|
|
|
static float brush_dynamic_size_get(const Brush &brush,
|
|
const StrokeCache &cache,
|
|
float initial_size)
|
|
{
|
|
switch (brush.sculpt_brush_type) {
|
|
case SCULPT_BRUSH_TYPE_CLAY:
|
|
return max_ff(initial_size * 0.20f, initial_size * pow3f(cache.pressure));
|
|
case SCULPT_BRUSH_TYPE_CLAY_STRIPS:
|
|
return max_ff(initial_size * 0.30f, initial_size * powf(cache.pressure, 1.5f));
|
|
case SCULPT_BRUSH_TYPE_CLAY_THUMB: {
|
|
float clay_stabilized_pressure = brushes::clay_thumb_get_stabilized_pressure(cache);
|
|
return initial_size * clay_stabilized_pressure;
|
|
}
|
|
default:
|
|
return initial_size * cache.pressure;
|
|
}
|
|
}
|
|
|
|
/* In these brushes the grab delta is calculated always from the initial stroke location, which is
|
|
* generally used to create grab deformations. */
|
|
static bool need_delta_from_anchored_origin(const Brush &brush)
|
|
{
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SMEAR && (brush.flag & BRUSH_ANCHORED)) {
|
|
return true;
|
|
}
|
|
|
|
if (ELEM(brush.sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_GRAB,
|
|
SCULPT_BRUSH_TYPE_POSE,
|
|
SCULPT_BRUSH_TYPE_BOUNDARY,
|
|
SCULPT_BRUSH_TYPE_THUMB,
|
|
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM))
|
|
{
|
|
return true;
|
|
}
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH &&
|
|
brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* In these brushes the grab delta is calculated from the previous stroke location, which is used
|
|
* to calculate to orientate the brush tip and deformation towards the stroke direction. */
|
|
static bool need_delta_for_tip_orientation(const Brush &brush)
|
|
{
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH) {
|
|
return brush.cloth_deform_type != BRUSH_CLOTH_DEFORM_GRAB;
|
|
}
|
|
return ELEM(brush.sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_CLAY_STRIPS,
|
|
SCULPT_BRUSH_TYPE_PINCH,
|
|
SCULPT_BRUSH_TYPE_MULTIPLANE_SCRAPE,
|
|
SCULPT_BRUSH_TYPE_CLAY_THUMB,
|
|
SCULPT_BRUSH_TYPE_NUDGE,
|
|
SCULPT_BRUSH_TYPE_SNAKE_HOOK);
|
|
}
|
|
|
|
static void brush_delta_update(const Depsgraph &depsgraph,
|
|
UnifiedPaintSettings &ups,
|
|
const Object &ob,
|
|
const Brush &brush)
|
|
{
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
StrokeCache *cache = ss.cache;
|
|
const float mval[2] = {
|
|
cache->mouse_event[0],
|
|
cache->mouse_event[1],
|
|
};
|
|
int brush_type = brush.sculpt_brush_type;
|
|
|
|
if (!ELEM(brush_type,
|
|
SCULPT_BRUSH_TYPE_PAINT,
|
|
SCULPT_BRUSH_TYPE_GRAB,
|
|
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM,
|
|
SCULPT_BRUSH_TYPE_CLOTH,
|
|
SCULPT_BRUSH_TYPE_NUDGE,
|
|
SCULPT_BRUSH_TYPE_CLAY_STRIPS,
|
|
SCULPT_BRUSH_TYPE_PLANE,
|
|
SCULPT_BRUSH_TYPE_PINCH,
|
|
SCULPT_BRUSH_TYPE_MULTIPLANE_SCRAPE,
|
|
SCULPT_BRUSH_TYPE_CLAY_THUMB,
|
|
SCULPT_BRUSH_TYPE_SNAKE_HOOK,
|
|
SCULPT_BRUSH_TYPE_POSE,
|
|
SCULPT_BRUSH_TYPE_BOUNDARY,
|
|
SCULPT_BRUSH_TYPE_SMEAR,
|
|
SCULPT_BRUSH_TYPE_THUMB) &&
|
|
!brush_uses_topology_rake(ss, brush))
|
|
{
|
|
return;
|
|
}
|
|
float grab_location[3], imat[4][4], delta[3], loc[3];
|
|
|
|
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
|
|
if (brush_type == SCULPT_BRUSH_TYPE_GRAB && brush.flag & BRUSH_GRAB_ACTIVE_VERTEX) {
|
|
if (pbvh.type() == bke::pbvh::Type::Mesh) {
|
|
const Span<float3> positions = vert_positions_for_grab_active_get(depsgraph, ob);
|
|
cache->orig_grab_location = positions[std::get<int>(ss.active_vert())];
|
|
}
|
|
else {
|
|
cache->orig_grab_location = ss.active_vert_position(depsgraph, ob);
|
|
}
|
|
}
|
|
else {
|
|
copy_v3_v3(cache->orig_grab_location, cache->location);
|
|
}
|
|
}
|
|
else if (brush_type == SCULPT_BRUSH_TYPE_SNAKE_HOOK ||
|
|
(brush_type == SCULPT_BRUSH_TYPE_CLOTH &&
|
|
brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_SNAKE_HOOK))
|
|
{
|
|
add_v3_v3(cache->location, cache->grab_delta);
|
|
}
|
|
|
|
/* Compute 3d coordinate at same z from original location + mval. */
|
|
mul_v3_m4v3(loc, ob.object_to_world().ptr(), cache->orig_grab_location);
|
|
ED_view3d_win_to_3d(cache->vc->v3d, cache->vc->region, loc, mval, grab_location);
|
|
|
|
/* Compute delta to move verts by. */
|
|
if (!SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
|
|
if (need_delta_from_anchored_origin(brush)) {
|
|
sub_v3_v3v3(delta, grab_location, cache->old_grab_location);
|
|
invert_m4_m4(imat, ob.object_to_world().ptr());
|
|
mul_mat3_m4_v3(imat, delta);
|
|
add_v3_v3(cache->grab_delta, delta);
|
|
}
|
|
else if (need_delta_for_tip_orientation(brush)) {
|
|
if (brush.flag & BRUSH_ANCHORED) {
|
|
float orig[3];
|
|
mul_v3_m4v3(orig, ob.object_to_world().ptr(), cache->orig_grab_location);
|
|
sub_v3_v3v3(cache->grab_delta, grab_location, orig);
|
|
}
|
|
else {
|
|
sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location);
|
|
}
|
|
invert_m4_m4(imat, ob.object_to_world().ptr());
|
|
mul_mat3_m4_v3(imat, cache->grab_delta);
|
|
}
|
|
else {
|
|
/* Use for 'Brush.topology_rake_factor'. */
|
|
sub_v3_v3v3(cache->grab_delta, grab_location, cache->old_grab_location);
|
|
}
|
|
}
|
|
else {
|
|
zero_v3(cache->grab_delta);
|
|
}
|
|
|
|
if (brush.falloff_shape == PAINT_FALLOFF_SHAPE_TUBE) {
|
|
project_plane_v3_v3v3(cache->grab_delta, cache->grab_delta, ss.cache->view_normal);
|
|
}
|
|
|
|
copy_v3_v3(cache->old_grab_location, grab_location);
|
|
|
|
if (need_delta_from_anchored_origin(brush)) {
|
|
/* Location stays the same for finding vertices in brush radius. */
|
|
copy_v3_v3(cache->location, cache->orig_grab_location);
|
|
|
|
ups.draw_anchored = true;
|
|
copy_v2_v2(ups.anchored_initial_mouse, cache->initial_mouse);
|
|
ups.anchored_size = ups.pixel_radius;
|
|
}
|
|
|
|
/* Handle 'rake' */
|
|
cache->rake_rotation = std::nullopt;
|
|
cache->rake_rotation_symm = std::nullopt;
|
|
invert_m4_m4(imat, ob.object_to_world().ptr());
|
|
mul_mat3_m4_v3(imat, grab_location);
|
|
|
|
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
|
|
copy_v3_v3(cache->rake_data.follow_co, grab_location);
|
|
}
|
|
|
|
if (!brush_needs_rake_rotation(brush)) {
|
|
return;
|
|
}
|
|
cache->rake_data.follow_dist = cache->radius * SCULPT_RAKE_BRUSH_FACTOR;
|
|
|
|
if (!is_zero_v3(cache->grab_delta)) {
|
|
const float eps = 0.00001f;
|
|
|
|
float v1[3], v2[3];
|
|
|
|
copy_v3_v3(v1, cache->rake_data.follow_co);
|
|
copy_v3_v3(v2, cache->rake_data.follow_co);
|
|
sub_v3_v3(v2, cache->grab_delta);
|
|
|
|
sub_v3_v3(v1, grab_location);
|
|
sub_v3_v3(v2, grab_location);
|
|
|
|
if ((normalize_v3(v2) > eps) && (normalize_v3(v1) > eps) && (len_squared_v3v3(v1, v2) > eps)) {
|
|
const float rake_dist_sq = len_squared_v3v3(cache->rake_data.follow_co, grab_location);
|
|
const float rake_fade = (rake_dist_sq > square_f(cache->rake_data.follow_dist)) ?
|
|
1.0f :
|
|
sqrtf(rake_dist_sq) / cache->rake_data.follow_dist;
|
|
|
|
const math::AxisAngle between_vecs(v1, v2);
|
|
const math::AxisAngle rotated(between_vecs.axis(),
|
|
between_vecs.angle() * brush.rake_factor * rake_fade);
|
|
cache->rake_rotation = math::to_quaternion(rotated);
|
|
}
|
|
}
|
|
rake_data_update(&cache->rake_data, grab_location);
|
|
}
|
|
|
|
static void cache_paint_invariants_update(StrokeCache &cache, const Brush &brush)
|
|
{
|
|
cache.hardness = brush.hardness;
|
|
if (brush.paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) {
|
|
cache.hardness *= brush.paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ?
|
|
1.0f - cache.pressure :
|
|
cache.pressure;
|
|
}
|
|
|
|
cache.paint_brush.flow = brush.flow;
|
|
if (brush.paint_flags & BRUSH_PAINT_FLOW_PRESSURE) {
|
|
cache.paint_brush.flow *= brush.paint_flags & BRUSH_PAINT_FLOW_PRESSURE_INVERT ?
|
|
1.0f - cache.pressure :
|
|
cache.pressure;
|
|
}
|
|
|
|
cache.paint_brush.wet_mix = brush.wet_mix;
|
|
if (brush.paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE) {
|
|
cache.paint_brush.wet_mix *= brush.paint_flags & BRUSH_PAINT_WET_MIX_PRESSURE_INVERT ?
|
|
1.0f - cache.pressure :
|
|
cache.pressure;
|
|
|
|
/* This makes wet mix more sensible in higher values, which allows to create brushes that have
|
|
* a wider pressure range were they only blend colors without applying too much of the brush
|
|
* color. */
|
|
cache.paint_brush.wet_mix = 1.0f - pow2f(1.0f - cache.paint_brush.wet_mix);
|
|
}
|
|
|
|
cache.paint_brush.wet_persistence = brush.wet_persistence;
|
|
if (brush.paint_flags & BRUSH_PAINT_WET_PERSISTENCE_PRESSURE) {
|
|
cache.paint_brush.wet_persistence = brush.paint_flags &
|
|
BRUSH_PAINT_WET_PERSISTENCE_PRESSURE_INVERT ?
|
|
1.0f - cache.pressure :
|
|
cache.pressure;
|
|
}
|
|
|
|
cache.paint_brush.density = brush.density;
|
|
if (brush.paint_flags & BRUSH_PAINT_DENSITY_PRESSURE) {
|
|
cache.paint_brush.density = brush.paint_flags & BRUSH_PAINT_DENSITY_PRESSURE_INVERT ?
|
|
1.0f - cache.pressure :
|
|
cache.pressure;
|
|
}
|
|
}
|
|
|
|
/* Initialize the stroke cache variants from operator properties. */
|
|
static void sculpt_update_cache_variants(bContext *C, Sculpt &sd, Object &ob, PointerRNA *ptr)
|
|
{
|
|
Scene &scene = *CTX_data_scene(C);
|
|
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C);
|
|
UnifiedPaintSettings &ups = scene.toolsettings->unified_paint_settings;
|
|
SculptSession &ss = *ob.sculpt;
|
|
StrokeCache &cache = *ss.cache;
|
|
Brush &brush = *BKE_paint_brush(&sd.paint);
|
|
|
|
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(cache) ||
|
|
!((brush.flag & BRUSH_ANCHORED) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SNAKE_HOOK) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_ROTATE) ||
|
|
cloth::is_cloth_deform_brush(brush)))
|
|
{
|
|
RNA_float_get_array(ptr, "location", cache.location);
|
|
}
|
|
|
|
RNA_float_get_array(ptr, "mouse", cache.mouse);
|
|
RNA_float_get_array(ptr, "mouse_event", cache.mouse_event);
|
|
|
|
/* XXX: Use pressure value from first brush step for brushes which don't support strokes (grab,
|
|
* thumb). They depends on initial state and brush coord/pressure/etc.
|
|
* It's more an events design issue, which doesn't split coordinate/pressure/angle changing
|
|
* events. We should avoid this after events system re-design. */
|
|
if (paint_supports_dynamic_size(brush, PaintMode::Sculpt) || cache.first_time) {
|
|
cache.pressure = RNA_float_get(ptr, "pressure");
|
|
}
|
|
|
|
cache.tilt = {RNA_float_get(ptr, "x_tilt"), RNA_float_get(ptr, "y_tilt")};
|
|
|
|
/* Truly temporary data that isn't stored in properties. */
|
|
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
|
|
cache.initial_radius = sculpt_calc_radius(*cache.vc, brush, scene, cache.location);
|
|
|
|
if (!BKE_brush_use_locked_size(&scene, &brush)) {
|
|
BKE_brush_unprojected_radius_set(&scene, &brush, cache.initial_radius);
|
|
}
|
|
}
|
|
|
|
/* Clay stabilized pressure. */
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLAY_THUMB) {
|
|
if (SCULPT_stroke_is_first_brush_step_of_symmetry_pass(*ss.cache)) {
|
|
ss.cache->clay_thumb_brush.pressure_stabilizer.fill(0.0f);
|
|
ss.cache->clay_thumb_brush.stabilizer_index = 0;
|
|
}
|
|
else {
|
|
cache.clay_thumb_brush.pressure_stabilizer[cache.clay_thumb_brush.stabilizer_index] =
|
|
cache.pressure;
|
|
cache.clay_thumb_brush.stabilizer_index += 1;
|
|
if (cache.clay_thumb_brush.stabilizer_index >=
|
|
ss.cache->clay_thumb_brush.pressure_stabilizer.size())
|
|
{
|
|
cache.clay_thumb_brush.stabilizer_index = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BKE_brush_use_size_pressure(&brush) && paint_supports_dynamic_size(brush, PaintMode::Sculpt))
|
|
{
|
|
cache.radius = brush_dynamic_size_get(brush, cache, cache.initial_radius);
|
|
cache.dyntopo_pixel_radius = brush_dynamic_size_get(brush, cache, ups.initial_pixel_radius);
|
|
}
|
|
else {
|
|
cache.radius = cache.initial_radius;
|
|
cache.dyntopo_pixel_radius = ups.initial_pixel_radius;
|
|
}
|
|
|
|
cache_paint_invariants_update(cache, brush);
|
|
|
|
cache.radius_squared = cache.radius * cache.radius;
|
|
|
|
if (brush.flag & BRUSH_ANCHORED) {
|
|
/* True location has been calculated as part of the stroke system already here. */
|
|
if (brush.flag & BRUSH_EDGE_TO_EDGE) {
|
|
RNA_float_get_array(ptr, "location", cache.location);
|
|
}
|
|
|
|
cache.radius = paint_calc_object_space_radius(*cache.vc, cache.location, ups.pixel_radius);
|
|
cache.radius_squared = cache.radius * cache.radius;
|
|
}
|
|
|
|
brush_delta_update(depsgraph, ups, ob, brush);
|
|
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_ROTATE) {
|
|
cache.vertex_rotation = -BLI_dial_angle(cache.dial, cache.mouse) * cache.bstrength;
|
|
|
|
ups.draw_anchored = true;
|
|
copy_v2_v2(ups.anchored_initial_mouse, cache.initial_mouse);
|
|
ups.anchored_size = ups.pixel_radius;
|
|
}
|
|
|
|
cache.special_rotation = ups.brush_rotation;
|
|
|
|
cache.iteration_count++;
|
|
}
|
|
|
|
/* Returns true if any of the smoothing modes are active (currently
|
|
* one of smooth brush, autosmooth, mask smooth, or shift-key
|
|
* smooth). */
|
|
static bool sculpt_needs_connectivity_info(const Sculpt &sd,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
int stroke_mode)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
const bke::pbvh::Tree *pbvh = bke::object::pbvh_get(object);
|
|
if (pbvh && auto_mask::is_enabled(sd, object, &brush)) {
|
|
return true;
|
|
}
|
|
return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss.cache && ss.cache->alt_smooth) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SMOOTH) || (brush.autosmooth_factor > 0) ||
|
|
((brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) &&
|
|
(brush.mask_tool == BRUSH_MASK_SMOOTH)) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_POSE) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_BOUNDARY) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SLIDE_RELAX) ||
|
|
brush_type_is_paint(brush.sculpt_brush_type) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SMEAR) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_DRAW_FACE_SETS) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR) ||
|
|
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT));
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
void SCULPT_stroke_modifiers_check(const bContext *C, Object &ob, const Brush &brush)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
SculptSession &ss = *ob.sculpt;
|
|
RegionView3D *rv3d = CTX_wm_region_view3d(C);
|
|
const Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
|
|
bool need_pmap = sculpt_needs_connectivity_info(sd, brush, ob, 0);
|
|
if (ss.shapekey_active || ss.deform_modifiers_active ||
|
|
(!BKE_sculptsession_use_pbvh_draw(&ob, rv3d) && need_pmap))
|
|
{
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
BKE_sculpt_update_object_for_edit(
|
|
depsgraph, &ob, brush_type_is_paint(brush.sculpt_brush_type));
|
|
}
|
|
}
|
|
|
|
static void sculpt_raycast_cb(blender::bke::pbvh::Node &node, SculptRaycastData &srd, float *tmin)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::ed::sculpt_paint;
|
|
if (BKE_pbvh_node_get_tmin(&node) >= *tmin) {
|
|
return;
|
|
}
|
|
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*srd.object);
|
|
bool use_origco = false;
|
|
Span<float3> origco;
|
|
if (srd.original && srd.ss->cache) {
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh:
|
|
if (const std::optional<OrigPositionData> orig_data =
|
|
orig_position_data_lookup_mesh_all_verts(
|
|
*srd.object, static_cast<const bke::pbvh::MeshNode &>(node)))
|
|
{
|
|
use_origco = true;
|
|
origco = orig_data->positions;
|
|
}
|
|
break;
|
|
case bke::pbvh::Type::Grids:
|
|
if (const std::optional<OrigPositionData> orig_data = orig_position_data_lookup_grids(
|
|
*srd.object, static_cast<const bke::pbvh::GridsNode &>(node)))
|
|
{
|
|
use_origco = true;
|
|
origco = orig_data->positions;
|
|
}
|
|
break;
|
|
case bke::pbvh::Type::BMesh:
|
|
use_origco = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (node.flag_ & bke::pbvh::Node::FullyHidden) {
|
|
return;
|
|
}
|
|
|
|
bool hit = false;
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
int mesh_active_vert;
|
|
hit = bke::pbvh::node_raycast_mesh(static_cast<bke::pbvh::MeshNode &>(node),
|
|
origco,
|
|
srd.vert_positions,
|
|
srd.faces,
|
|
srd.corner_verts,
|
|
srd.corner_tris,
|
|
srd.hide_poly,
|
|
srd.ray_start,
|
|
srd.ray_normal,
|
|
&srd.isect_precalc,
|
|
&srd.depth,
|
|
mesh_active_vert,
|
|
srd.active_face_grid_index,
|
|
srd.face_normal);
|
|
if (hit) {
|
|
srd.active_vertex = mesh_active_vert;
|
|
}
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
SubdivCCGCoord grids_active_vert;
|
|
hit = bke::pbvh::node_raycast_grids(*srd.subdiv_ccg,
|
|
static_cast<bke::pbvh::GridsNode &>(node),
|
|
origco,
|
|
srd.ray_start,
|
|
srd.ray_normal,
|
|
&srd.isect_precalc,
|
|
&srd.depth,
|
|
grids_active_vert,
|
|
srd.active_face_grid_index,
|
|
srd.face_normal);
|
|
if (hit) {
|
|
srd.active_vertex = grids_active_vert.to_index(
|
|
BKE_subdiv_ccg_key_top_level(*srd.subdiv_ccg));
|
|
}
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
BMVert *bmesh_active_vert;
|
|
hit = bke::pbvh::node_raycast_bmesh(static_cast<bke::pbvh::BMeshNode &>(node),
|
|
srd.ray_start,
|
|
srd.ray_normal,
|
|
&srd.isect_precalc,
|
|
&srd.depth,
|
|
use_origco,
|
|
&bmesh_active_vert,
|
|
srd.face_normal);
|
|
if (hit) {
|
|
srd.active_vertex = bmesh_active_vert;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hit) {
|
|
srd.hit = true;
|
|
*tmin = srd.depth;
|
|
}
|
|
}
|
|
|
|
static void sculpt_find_nearest_to_ray_cb(blender::bke::pbvh::Node &node,
|
|
SculptFindNearestToRayData &srd,
|
|
float *tmin)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::ed::sculpt_paint;
|
|
if (BKE_pbvh_node_get_tmin(&node) >= *tmin) {
|
|
return;
|
|
}
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(*srd.object);
|
|
bool use_origco = false;
|
|
Span<float3> origco;
|
|
if (srd.original && srd.ss->cache) {
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh:
|
|
if (const std::optional<OrigPositionData> orig_data =
|
|
orig_position_data_lookup_mesh_all_verts(
|
|
*srd.object, static_cast<const bke::pbvh::MeshNode &>(node)))
|
|
{
|
|
use_origco = true;
|
|
origco = orig_data->positions;
|
|
}
|
|
break;
|
|
case bke::pbvh::Type::Grids:
|
|
if (const std::optional<OrigPositionData> orig_data = orig_position_data_lookup_grids(
|
|
*srd.object, static_cast<const bke::pbvh::GridsNode &>(node)))
|
|
{
|
|
use_origco = true;
|
|
origco = orig_data->positions;
|
|
}
|
|
|
|
break;
|
|
case bke::pbvh::Type::BMesh:
|
|
use_origco = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bke::pbvh::find_nearest_to_ray_node(pbvh,
|
|
node,
|
|
origco,
|
|
use_origco,
|
|
srd.vert_positions,
|
|
srd.faces,
|
|
srd.corner_verts,
|
|
srd.corner_tris,
|
|
srd.hide_poly,
|
|
srd.subdiv_ccg,
|
|
srd.ray_start,
|
|
srd.ray_normal,
|
|
&srd.depth,
|
|
&srd.dist_sq_to_ray))
|
|
{
|
|
srd.hit = true;
|
|
*tmin = srd.dist_sq_to_ray;
|
|
}
|
|
}
|
|
|
|
float SCULPT_raycast_init(ViewContext *vc,
|
|
const float mval[2],
|
|
float ray_start[3],
|
|
float ray_end[3],
|
|
float ray_normal[3],
|
|
bool original)
|
|
{
|
|
using namespace blender;
|
|
float obimat[4][4];
|
|
float dist;
|
|
Object &ob = *vc->obact;
|
|
RegionView3D *rv3d = vc->rv3d;
|
|
View3D *v3d = vc->v3d;
|
|
|
|
/* TODO: what if the segment is totally clipped? (return == 0). */
|
|
ED_view3d_win_to_segment_clipped(
|
|
vc->depsgraph, vc->region, vc->v3d, mval, ray_start, ray_end, true);
|
|
|
|
invert_m4_m4(obimat, ob.object_to_world().ptr());
|
|
mul_m4_v3(obimat, ray_start);
|
|
mul_m4_v3(obimat, ray_end);
|
|
|
|
sub_v3_v3v3(ray_normal, ray_end, ray_start);
|
|
dist = normalize_v3(ray_normal);
|
|
|
|
/* If the ray is clipped, don't adjust its start/end. */
|
|
if ((rv3d->is_persp == false) && !RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
|
/* Get the view origin without the addition
|
|
* of -ray_normal * clip_start that
|
|
* ED_view3d_win_to_segment_clipped gave us.
|
|
* This is necessary to avoid floating point overflow.
|
|
*/
|
|
ED_view3d_win_to_origin(vc->region, mval, ray_start);
|
|
mul_m4_v3(obimat, ray_start);
|
|
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
bke::pbvh::clip_ray_ortho(pbvh, original, ray_start, ray_end, ray_normal);
|
|
|
|
dist = len_v3v3(ray_start, ray_end);
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
bool SCULPT_cursor_geometry_info_update(bContext *C,
|
|
SculptCursorGeometryInfo *out,
|
|
const float mval[2],
|
|
bool use_sampled_normal)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::ed::sculpt_paint;
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
Scene *scene = CTX_data_scene(C);
|
|
const Brush &brush = *BKE_paint_brush_for_read(BKE_paint_get_active_from_context(C));
|
|
float ray_start[3], ray_end[3], ray_normal[3], depth, mat[3][3];
|
|
float viewDir[3] = {0.0f, 0.0f, 1.0f};
|
|
bool original = false;
|
|
|
|
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
|
|
|
|
Object &ob = *vc.obact;
|
|
SculptSession &ss = *ob.sculpt;
|
|
|
|
const View3D *v3d = CTX_wm_view3d(C);
|
|
const Base *base = CTX_data_active_base(C);
|
|
|
|
bke::pbvh::Tree *pbvh = bke::object::pbvh_get(ob);
|
|
|
|
if (!pbvh || !vc.rv3d || !BKE_base_is_visible(v3d, base)) {
|
|
zero_v3(out->location);
|
|
zero_v3(out->normal);
|
|
zero_v3(out->active_vertex_co);
|
|
ss.clear_active_vert(false);
|
|
return false;
|
|
}
|
|
|
|
/* bke::pbvh::Tree raycast to get active vertex and face normal. */
|
|
depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original);
|
|
SCULPT_stroke_modifiers_check(C, ob, brush);
|
|
|
|
SculptRaycastData srd{};
|
|
srd.original = original;
|
|
srd.object = &ob;
|
|
srd.ss = ob.sculpt;
|
|
srd.hit = false;
|
|
if (pbvh->type() == bke::pbvh::Type::Mesh) {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
srd.vert_positions = bke::pbvh::vert_positions_eval(*depsgraph, ob);
|
|
srd.faces = mesh.faces();
|
|
srd.corner_verts = mesh.corner_verts();
|
|
srd.corner_tris = mesh.corner_tris();
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
srd.hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
|
}
|
|
else if (pbvh->type() == bke::pbvh::Type::Grids) {
|
|
srd.subdiv_ccg = ss.subdiv_ccg;
|
|
}
|
|
SCULPT_vertex_random_access_ensure(ob);
|
|
srd.ray_start = ray_start;
|
|
srd.ray_normal = ray_normal;
|
|
srd.depth = depth;
|
|
|
|
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
|
|
bke::pbvh::raycast(
|
|
*pbvh,
|
|
[&](bke::pbvh::Node &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); },
|
|
ray_start,
|
|
ray_normal,
|
|
srd.original);
|
|
|
|
/* Cursor is not over the mesh, return default values. */
|
|
if (!srd.hit) {
|
|
zero_v3(out->location);
|
|
zero_v3(out->normal);
|
|
zero_v3(out->active_vertex_co);
|
|
ss.clear_active_vert(true);
|
|
return false;
|
|
}
|
|
|
|
/* Update the active vertex of the SculptSession. */
|
|
ss.set_active_vert(srd.active_vertex);
|
|
out->active_vertex_co = ss.active_vert_position(*depsgraph, ob);
|
|
|
|
switch (pbvh->type()) {
|
|
case bke::pbvh::Type::Mesh:
|
|
ss.active_face_index = srd.active_face_grid_index;
|
|
ss.active_grid_index = std::nullopt;
|
|
break;
|
|
case bke::pbvh::Type::Grids:
|
|
ss.active_face_index = std::nullopt;
|
|
ss.active_grid_index = srd.active_face_grid_index;
|
|
break;
|
|
case bke::pbvh::Type::BMesh:
|
|
ss.active_face_index = std::nullopt;
|
|
ss.active_grid_index = std::nullopt;
|
|
break;
|
|
}
|
|
|
|
copy_v3_v3(out->location, ray_normal);
|
|
mul_v3_fl(out->location, srd.depth);
|
|
add_v3_v3(out->location, ray_start);
|
|
|
|
/* Option to return the face normal directly for performance o accuracy reasons. */
|
|
if (!use_sampled_normal) {
|
|
copy_v3_v3(out->normal, srd.face_normal);
|
|
return srd.hit;
|
|
}
|
|
|
|
/* Sampled normal calculation. */
|
|
float radius;
|
|
|
|
/* Update cursor data in SculptSession. */
|
|
invert_m4_m4(ob.runtime->world_to_object.ptr(), ob.object_to_world().ptr());
|
|
copy_m3_m4(mat, vc.rv3d->viewinv);
|
|
mul_m3_v3(mat, viewDir);
|
|
copy_m3_m4(mat, ob.world_to_object().ptr());
|
|
mul_m3_v3(mat, viewDir);
|
|
normalize_v3_v3(ss.cursor_view_normal, viewDir);
|
|
copy_v3_v3(ss.cursor_normal, srd.face_normal);
|
|
copy_v3_v3(ss.cursor_location, out->location);
|
|
ss.rv3d = vc.rv3d;
|
|
ss.v3d = vc.v3d;
|
|
|
|
if (!BKE_brush_use_locked_size(scene, &brush)) {
|
|
radius = paint_calc_object_space_radius(vc, out->location, BKE_brush_size_get(scene, &brush));
|
|
}
|
|
else {
|
|
radius = BKE_brush_unprojected_radius_get(scene, &brush);
|
|
}
|
|
ss.cursor_radius = radius;
|
|
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = pbvh_gather_cursor_update(ob, original, memory);
|
|
|
|
/* In case there are no nodes under the cursor, return the face normal. */
|
|
if (node_mask.is_empty()) {
|
|
copy_v3_v3(out->normal, srd.face_normal);
|
|
return true;
|
|
}
|
|
|
|
bke::pbvh::update_normals(*depsgraph, ob, *pbvh);
|
|
|
|
/* Calculate the sampled normal. */
|
|
if (const std::optional<float3> sampled_normal = calc_area_normal(
|
|
*depsgraph, brush, ob, node_mask))
|
|
{
|
|
copy_v3_v3(out->normal, *sampled_normal);
|
|
copy_v3_v3(ss.cursor_sampled_normal, *sampled_normal);
|
|
}
|
|
else {
|
|
/* Use face normal when there are no vertices to sample inside the cursor radius. */
|
|
copy_v3_v3(out->normal, srd.face_normal);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SCULPT_stroke_get_location(bContext *C,
|
|
float out[3],
|
|
const float mval[2],
|
|
bool force_original)
|
|
{
|
|
const Brush *brush = BKE_paint_brush(BKE_paint_get_active_from_context(C));
|
|
bool check_closest = brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE;
|
|
|
|
return SCULPT_stroke_get_location_ex(C, out, mval, force_original, check_closest, true);
|
|
}
|
|
|
|
bool SCULPT_stroke_get_location_ex(bContext *C,
|
|
float out[3],
|
|
const float mval[2],
|
|
bool force_original,
|
|
bool check_closest,
|
|
bool limit_closest_radius)
|
|
{
|
|
using namespace blender;
|
|
using namespace blender::ed::sculpt_paint;
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
float ray_start[3], ray_end[3], ray_normal[3], depth;
|
|
|
|
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
|
|
|
|
Object &ob = *vc.obact;
|
|
|
|
SculptSession &ss = *ob.sculpt;
|
|
StrokeCache *cache = ss.cache;
|
|
bool original = force_original || ((cache) ? !cache->accum : false);
|
|
|
|
const Brush &brush = *BKE_paint_brush(BKE_paint_get_active_from_context(C));
|
|
|
|
SCULPT_stroke_modifiers_check(C, ob, brush);
|
|
|
|
depth = SCULPT_raycast_init(&vc, mval, ray_start, ray_end, ray_normal, original);
|
|
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
if (pbvh.type() == bke::pbvh::Type::BMesh) {
|
|
BM_mesh_elem_table_ensure(ss.bm, BM_VERT);
|
|
BM_mesh_elem_index_ensure(ss.bm, BM_VERT);
|
|
}
|
|
|
|
bool hit = false;
|
|
{
|
|
SculptRaycastData srd;
|
|
srd.object = &ob;
|
|
srd.ss = ob.sculpt;
|
|
srd.ray_start = ray_start;
|
|
srd.ray_normal = ray_normal;
|
|
srd.hit = false;
|
|
if (pbvh.type() == bke::pbvh::Type::Mesh) {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
srd.vert_positions = bke::pbvh::vert_positions_eval(*depsgraph, ob);
|
|
srd.faces = mesh.faces();
|
|
srd.corner_verts = mesh.corner_verts();
|
|
srd.corner_tris = mesh.corner_tris();
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
srd.hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
|
}
|
|
else if (pbvh.type() == bke::pbvh::Type::Grids) {
|
|
srd.subdiv_ccg = ss.subdiv_ccg;
|
|
}
|
|
SCULPT_vertex_random_access_ensure(ob);
|
|
srd.depth = depth;
|
|
srd.original = original;
|
|
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
|
|
|
|
bke::pbvh::raycast(
|
|
pbvh,
|
|
[&](bke::pbvh::Node &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); },
|
|
ray_start,
|
|
ray_normal,
|
|
srd.original);
|
|
if (srd.hit) {
|
|
hit = true;
|
|
copy_v3_v3(out, ray_normal);
|
|
mul_v3_fl(out, srd.depth);
|
|
add_v3_v3(out, ray_start);
|
|
}
|
|
}
|
|
|
|
if (hit || !check_closest) {
|
|
return hit;
|
|
}
|
|
|
|
SculptFindNearestToRayData srd{};
|
|
srd.original = original;
|
|
srd.object = &ob;
|
|
srd.ss = ob.sculpt;
|
|
srd.hit = false;
|
|
if (pbvh.type() == bke::pbvh::Type::Mesh) {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
srd.vert_positions = bke::pbvh::vert_positions_eval(*depsgraph, ob);
|
|
srd.faces = mesh.faces();
|
|
srd.corner_verts = mesh.corner_verts();
|
|
srd.corner_tris = mesh.corner_tris();
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
srd.hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
|
}
|
|
else if (pbvh.type() == bke::pbvh::Type::Grids) {
|
|
srd.subdiv_ccg = ss.subdiv_ccg;
|
|
}
|
|
srd.ray_start = ray_start;
|
|
srd.ray_normal = ray_normal;
|
|
srd.depth = std::numeric_limits<float>::max();
|
|
srd.dist_sq_to_ray = std::numeric_limits<float>::max();
|
|
|
|
bke::pbvh::find_nearest_to_ray(
|
|
pbvh,
|
|
[&](bke::pbvh::Node &node, float *tmin) { sculpt_find_nearest_to_ray_cb(node, srd, tmin); },
|
|
ray_start,
|
|
ray_normal,
|
|
srd.original);
|
|
if (srd.hit && srd.dist_sq_to_ray) {
|
|
hit = true;
|
|
copy_v3_v3(out, ray_normal);
|
|
mul_v3_fl(out, srd.depth);
|
|
add_v3_v3(out, ray_start);
|
|
}
|
|
|
|
float closest_radius_sq = std::numeric_limits<float>::max();
|
|
if (limit_closest_radius) {
|
|
closest_radius_sq = sculpt_calc_radius(vc, brush, *CTX_data_scene(C), out);
|
|
closest_radius_sq *= closest_radius_sq;
|
|
}
|
|
|
|
return hit && srd.dist_sq_to_ray < closest_radius_sq;
|
|
}
|
|
|
|
static void brush_init_tex(const Sculpt &sd, SculptSession &ss)
|
|
{
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
const MTex *mask_tex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT);
|
|
|
|
/* Init mtex nodes. */
|
|
if (mask_tex->tex && mask_tex->tex->nodetree) {
|
|
/* Has internal flag to detect it only does it once. */
|
|
ntreeTexBeginExecTree(mask_tex->tex->nodetree);
|
|
}
|
|
|
|
if (ss.tex_pool == nullptr) {
|
|
ss.tex_pool = BKE_image_pool_new();
|
|
}
|
|
}
|
|
|
|
static void brush_stroke_init(bContext *C)
|
|
{
|
|
Object &ob = *CTX_data_active_object(C);
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
const Sculpt &sd = *tool_settings->sculpt;
|
|
SculptSession &ss = *CTX_data_active_object(C)->sculpt;
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
if (!G.background) {
|
|
view3d_operator_needs_gpu(C);
|
|
}
|
|
brush_init_tex(sd, ss);
|
|
|
|
const bool needs_colors = blender::ed::sculpt_paint::brush_type_is_paint(
|
|
brush->sculpt_brush_type) &&
|
|
!SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob);
|
|
|
|
if (needs_colors) {
|
|
BKE_sculpt_color_layer_create_if_needed(&ob);
|
|
}
|
|
|
|
/* CTX_data_ensure_evaluated_depsgraph should be used at the end to include the updates of
|
|
* earlier steps modifying the data. */
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
BKE_sculpt_update_object_for_edit(
|
|
depsgraph, &ob, blender::ed::sculpt_paint::brush_type_is_paint(brush->sculpt_brush_type));
|
|
|
|
ED_image_paint_brush_type_update_sticky_shading_color(C, &ob);
|
|
}
|
|
|
|
static void restore_from_undo_step_if_necessary(const Depsgraph &depsgraph,
|
|
const Sculpt &sd,
|
|
Object &ob)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
SculptSession &ss = *ob.sculpt;
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
/* Brushes that use original coordinates and need a "restore" step. This has to happen separately
|
|
* rather than in the brush deformation calculation because that is called once for each symmetry
|
|
* pass, potentially within the same BVH node.
|
|
*
|
|
* NOTE: Despite the Cloth and Boundary brush using original coordinates, the brushes do not
|
|
* expect this restoration to happen on every stroke step. Performing this restoration causes
|
|
* issues with the cloth simulation mode for those brushes.
|
|
*/
|
|
if (ELEM(brush->sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM,
|
|
SCULPT_BRUSH_TYPE_GRAB,
|
|
SCULPT_BRUSH_TYPE_THUMB,
|
|
SCULPT_BRUSH_TYPE_ROTATE))
|
|
{
|
|
undo::restore_from_undo_step(depsgraph, sd, ob);
|
|
return;
|
|
}
|
|
|
|
/* For the cloth brush it makes more sense to not restore the mesh state to keep running the
|
|
* simulation from the previous state. */
|
|
if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH) {
|
|
return;
|
|
}
|
|
|
|
/* Restore the mesh before continuing with anchored stroke. */
|
|
if ((brush->flag & BRUSH_ANCHORED) ||
|
|
(ELEM(brush->sculpt_brush_type, SCULPT_BRUSH_TYPE_GRAB, SCULPT_BRUSH_TYPE_ELASTIC_DEFORM) &&
|
|
BKE_brush_use_size_pressure(brush)) ||
|
|
(brush->flag & BRUSH_DRAG_DOT))
|
|
{
|
|
|
|
undo::restore_from_undo_step(depsgraph, sd, ob);
|
|
|
|
if (ss.cache) {
|
|
/* Temporary data within the StrokeCache that is usually cleared at the end of the stroke
|
|
* needs to be invalidated here so that the brushes do not accumulate and apply extra data.
|
|
* See #129069. */
|
|
ss.cache->layer_displacement_factor = {};
|
|
ss.cache->paint_brush.mix_colors = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
static void tag_mesh_positions_changed(Object &object, const bool use_pbvh_draw)
|
|
{
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
|
|
/* Various operations inside sculpt mode can cause either the #MeshRuntimeData or the entire
|
|
* Mesh to be changed (e.g. Undoing the very first operation after opening a file, performing
|
|
* remesh, etc).
|
|
*
|
|
* This isn't an ideal fix for the core issue here, but to mitigate the drastic performance
|
|
* falloff, we refreeze the cache before we do any operation that would tag this runtime
|
|
* cache as dirty.
|
|
*
|
|
* See #130636. */
|
|
if (!mesh.runtime->corner_tris_cache.frozen) {
|
|
mesh.runtime->corner_tris_cache.freeze();
|
|
}
|
|
|
|
/* Updating mesh positions without marking caches dirty is generally not good, but since
|
|
* sculpt mode has special requirements and is expected to have sole ownership of the mesh it
|
|
* modifies, it's generally okay. */
|
|
if (use_pbvh_draw) {
|
|
/* When drawing from bke::pbvh::Tree is used, vertex and face normals are updated
|
|
* later in #bke::pbvh::update_normals. However, we update the mesh's bounds eagerly here
|
|
* since they are trivial to access from the bke::pbvh::Tree. Updating the
|
|
* object's evaluated geometry bounding box is necessary because sculpt strokes don't cause
|
|
* an object reevaluation. */
|
|
mesh.tag_positions_changed_no_normals();
|
|
/* Sculpt mode does not use or recalculate face corner normals, so they are cleared. */
|
|
mesh.runtime->corner_normals_cache.tag_dirty();
|
|
}
|
|
else {
|
|
/* Drawing happens from the modifier stack evaluation result.
|
|
* Tag both coordinates and normals as modified, as both needed for proper drawing and the
|
|
* modifier stack is not guaranteed to tag normals for update. */
|
|
mesh.tag_positions_changed();
|
|
}
|
|
|
|
if (const bke::pbvh::Tree *pbvh = bke::object::pbvh_get(object)) {
|
|
mesh.bounds_set_eager(bke::pbvh::bounds_get(*pbvh));
|
|
if (object.runtime->bounds_eval) {
|
|
object.runtime->bounds_eval = mesh.bounds_min_max();
|
|
}
|
|
}
|
|
}
|
|
|
|
void flush_update_step(const bContext *C, const UpdateType update_type)
|
|
{
|
|
Object &ob = *CTX_data_active_object(C);
|
|
RegionView3D *rv3d = CTX_wm_region_view3d(C);
|
|
|
|
if (rv3d) {
|
|
/* Mark for faster 3D viewport redraws. */
|
|
rv3d->rflag |= RV3D_PAINTING;
|
|
}
|
|
|
|
const SculptSession &ss = *ob.sculpt;
|
|
const MultiresModifierData *mmd = ss.multires.modifier;
|
|
if (mmd != nullptr) {
|
|
Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C);
|
|
multires_mark_as_modified(&depsgraph, &ob, MULTIRES_COORDS_MODIFIED);
|
|
}
|
|
|
|
ARegion ®ion = *CTX_wm_region(C);
|
|
if (update_type == UpdateType::Image) {
|
|
ED_region_tag_redraw(®ion);
|
|
if (update_type == UpdateType::Image) {
|
|
/* Early exit when only need to update the images. We don't want to tag any geometry updates
|
|
* that would rebuild the bke::pbvh::Tree. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
DEG_id_tag_update(&ob.id, ID_RECALC_SHADING);
|
|
|
|
const bool use_pbvh_draw = BKE_sculptsession_use_pbvh_draw(&ob, rv3d);
|
|
/* Only current viewport matters, slower update for all viewports will
|
|
* be done in sculpt_flush_update_done. */
|
|
if (!use_pbvh_draw) {
|
|
/* Slow update with full dependency graph update and all that comes with it.
|
|
* Needed when there are modifiers or full shading in the 3D viewport. */
|
|
DEG_id_tag_update(&ob.id, ID_RECALC_GEOMETRY);
|
|
}
|
|
|
|
ED_region_tag_redraw(®ion);
|
|
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
if (update_type == UpdateType::Position && !ss.shapekey_active) {
|
|
if (pbvh.type() == bke::pbvh::Type::Mesh) {
|
|
tag_mesh_positions_changed(ob, use_pbvh_draw);
|
|
}
|
|
}
|
|
}
|
|
|
|
void flush_update_done(const bContext *C, Object &ob, const UpdateType update_type)
|
|
{
|
|
/* After we are done drawing the stroke, check if we need to do a more
|
|
* expensive depsgraph tag to update geometry. */
|
|
const Mesh &mesh = *static_cast<Mesh *>(ob.data);
|
|
|
|
/* Always needed for linked duplicates. */
|
|
bool need_tag = ID_REAL_USERS(&mesh.id) > 1;
|
|
|
|
RegionView3D *current_rv3d = CTX_wm_region_view3d(C);
|
|
if (current_rv3d) {
|
|
current_rv3d->rflag &= ~RV3D_PAINTING;
|
|
}
|
|
|
|
const wmWindowManager &wm = *CTX_wm_manager(C);
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm.windows) {
|
|
const bScreen &screen = *WM_window_get_active_screen(win);
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen.areabase) {
|
|
const SpaceLink &sl = *static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl.spacetype != SPACE_VIEW3D) {
|
|
continue;
|
|
}
|
|
|
|
/* Tag all 3D viewports for redraw now that we are done. Other
|
|
* viewports did not get a full redraw, and anti-aliasing for the
|
|
* current viewport was deactivated. */
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
if (region->regiontype == RGN_TYPE_WINDOW) {
|
|
const RegionView3D *other_rv3d = static_cast<RegionView3D *>(region->regiondata);
|
|
if (other_rv3d != current_rv3d) {
|
|
need_tag |= !BKE_sculptsession_use_pbvh_draw(&ob, other_rv3d);
|
|
}
|
|
|
|
ED_region_tag_redraw(region);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (update_type == UpdateType::Image) {
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen.areabase) {
|
|
const SpaceLink &sl = *static_cast<SpaceLink *>(area->spacedata.first);
|
|
if (sl.spacetype != SPACE_IMAGE) {
|
|
continue;
|
|
}
|
|
ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW);
|
|
}
|
|
}
|
|
}
|
|
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
if (update_type == UpdateType::Position) {
|
|
bke::pbvh::store_bounds_orig(pbvh);
|
|
|
|
/* Coordinates were modified, so fake neighbors are not longer valid. */
|
|
SCULPT_fake_neighbors_free(ob);
|
|
}
|
|
|
|
if (update_type == UpdateType::Position) {
|
|
if (pbvh.type() == bke::pbvh::Type::BMesh) {
|
|
SculptSession &ss = *ob.sculpt;
|
|
BKE_pbvh_bmesh_after_stroke(*ss.bm, pbvh);
|
|
}
|
|
}
|
|
|
|
if (need_tag) {
|
|
DEG_id_tag_update(&ob.id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
|
|
/* Replace an entire attribute using implicit sharing to avoid copies when possible. */
|
|
static void replace_attribute(const bke::AttributeAccessor src_attributes,
|
|
const StringRef name,
|
|
const bke::AttrDomain domain,
|
|
const eCustomDataType data_type,
|
|
bke::MutableAttributeAccessor dst_attributes)
|
|
{
|
|
dst_attributes.remove(name);
|
|
bke::GAttributeReader src = src_attributes.lookup(name, domain, data_type);
|
|
if (!src) {
|
|
return;
|
|
}
|
|
if (src.sharing_info && src.varray.is_span()) {
|
|
const bke::AttributeInitShared init(src.varray.get_internal_span().data(), *src.sharing_info);
|
|
dst_attributes.add(name, domain, data_type, init);
|
|
}
|
|
else {
|
|
const bke::AttributeInitVArray init(*src);
|
|
dst_attributes.add(name, domain, data_type, init);
|
|
}
|
|
}
|
|
|
|
static bool attribute_matches(const bke::AttributeAccessor a,
|
|
const bke::AttributeAccessor b,
|
|
const StringRef name)
|
|
{
|
|
const bke::GAttributeReader a_attr = a.lookup(name);
|
|
const bke::GAttributeReader b_attr = b.lookup(name);
|
|
if (!a_attr.sharing_info || !b_attr.sharing_info) {
|
|
return false;
|
|
}
|
|
return a_attr.sharing_info == b_attr.sharing_info;
|
|
}
|
|
|
|
static bool topology_matches(const Mesh &a, const Mesh &b)
|
|
{
|
|
if (a.verts_num != b.verts_num || a.edges_num != b.edges_num || a.faces_num != b.faces_num ||
|
|
a.corners_num != b.corners_num)
|
|
{
|
|
return false;
|
|
}
|
|
if (a.runtime->face_offsets_sharing_info != b.runtime->face_offsets_sharing_info) {
|
|
return false;
|
|
}
|
|
const bke::AttributeAccessor a_attributes = a.attributes();
|
|
const bke::AttributeAccessor b_attributes = b.attributes();
|
|
if (!attribute_matches(a_attributes, b_attributes, ".edge_verts") ||
|
|
!attribute_matches(a_attributes, b_attributes, ".corner_vert") ||
|
|
!attribute_matches(a_attributes, b_attributes, ".corner_edge"))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void store_sculpt_entire_mesh(const wmOperator &op,
|
|
const Scene &scene,
|
|
Object &object,
|
|
Mesh *new_mesh)
|
|
{
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
sculpt_paint::undo::geometry_begin(scene, object, &op);
|
|
BKE_mesh_nomain_to_mesh(new_mesh, &mesh, &object);
|
|
sculpt_paint::undo::geometry_end(object);
|
|
BKE_sculptsession_free_pbvh(object);
|
|
}
|
|
|
|
void store_mesh_from_eval(const wmOperator &op,
|
|
const Scene &scene,
|
|
const Depsgraph &depsgraph,
|
|
const RegionView3D *rv3d,
|
|
Object &object,
|
|
Mesh *new_mesh)
|
|
{
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
const bool changed_topology = !topology_matches(mesh, *new_mesh);
|
|
const bool use_pbvh_draw = BKE_sculptsession_use_pbvh_draw(&object, rv3d);
|
|
bool entire_mesh_changed = false;
|
|
|
|
if (changed_topology) {
|
|
store_sculpt_entire_mesh(op, scene, object, new_mesh);
|
|
entire_mesh_changed = true;
|
|
}
|
|
else {
|
|
/* Detect attributes present in the new mesh which no longer match the original. */
|
|
VectorSet<StringRef> changed_attributes;
|
|
new_mesh->attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
|
|
if (ELEM(iter.name, ".edge_verts", ".corner_vert", ".corner_edge")) {
|
|
return;
|
|
}
|
|
const bke::GAttributeReader attribute = iter.get();
|
|
if (attribute_matches(new_mesh->attributes(), mesh.attributes(), iter.name)) {
|
|
return;
|
|
}
|
|
changed_attributes.add(iter.name);
|
|
});
|
|
/* Detect attributes that were removed in the new mesh. */
|
|
mesh.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
|
|
if (!new_mesh->attributes().contains(iter.name)) {
|
|
changed_attributes.add(iter.name);
|
|
}
|
|
});
|
|
|
|
/* Try to use the few specialized sculpt undo types that result in better performance, mainly
|
|
* because redo avoids clearing the BVH, but also because some other updates can be skipped. */
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
IndexMaskMemory memory;
|
|
const IndexMask leaf_nodes = bke::pbvh::all_leaf_nodes(pbvh, memory);
|
|
if (changed_attributes.as_span() == Span<StringRef>{"position"}) {
|
|
undo::push_begin(scene, object, &op);
|
|
undo::push_nodes(depsgraph, object, leaf_nodes, undo::Type::Position);
|
|
undo::push_end(object);
|
|
CustomData_free_layer_named(&mesh.vert_data, "position");
|
|
mesh.attributes_for_write().remove("position");
|
|
const bke::AttributeReader position = new_mesh->attributes().lookup<float3>("position");
|
|
if (position.sharing_info) {
|
|
/* Use lower level API to add the position attribute to avoid copying the array and to
|
|
* allow using #tag_positions_changed_no_normals instead of #tag_positions_changed (which
|
|
* would be called by the attribute API). */
|
|
CustomData_add_layer_named_with_data(
|
|
&mesh.vert_data,
|
|
CD_PROP_FLOAT3,
|
|
const_cast<float3 *>(position.varray.get_internal_span().data()),
|
|
mesh.verts_num,
|
|
"position",
|
|
position.sharing_info);
|
|
}
|
|
else {
|
|
mesh.vert_positions_for_write().copy_from(VArraySpan(*position));
|
|
}
|
|
|
|
pbvh.tag_positions_changed(leaf_nodes);
|
|
pbvh.update_bounds(depsgraph, object);
|
|
tag_mesh_positions_changed(object, use_pbvh_draw);
|
|
BKE_mesh_copy_parameters(&mesh, new_mesh);
|
|
BKE_id_free(nullptr, new_mesh);
|
|
}
|
|
else if (changed_attributes.as_span() == Span<StringRef>{".sculpt_mask"}) {
|
|
undo::push_begin(scene, object, &op);
|
|
undo::push_nodes(depsgraph, object, leaf_nodes, undo::Type::Mask);
|
|
undo::push_end(object);
|
|
replace_attribute(new_mesh->attributes(),
|
|
".sculpt_mask",
|
|
bke::AttrDomain::Point,
|
|
CD_PROP_FLOAT,
|
|
mesh.attributes_for_write());
|
|
pbvh.tag_masks_changed(leaf_nodes);
|
|
BKE_mesh_copy_parameters(&mesh, new_mesh);
|
|
BKE_id_free(nullptr, new_mesh);
|
|
}
|
|
else if (changed_attributes.as_span() == Span<StringRef>{".sculpt_face_set"}) {
|
|
undo::push_begin(scene, object, &op);
|
|
undo::push_nodes(depsgraph, object, leaf_nodes, undo::Type::FaceSet);
|
|
undo::push_end(object);
|
|
replace_attribute(new_mesh->attributes(),
|
|
".sculpt_face_set",
|
|
bke::AttrDomain::Face,
|
|
CD_PROP_INT32,
|
|
mesh.attributes_for_write());
|
|
pbvh.tag_face_sets_changed(leaf_nodes);
|
|
BKE_mesh_copy_parameters(&mesh, new_mesh);
|
|
BKE_id_free(nullptr, new_mesh);
|
|
}
|
|
else {
|
|
/* Non-geometry-type sculpt undo steps can only handle a single change at a time. When
|
|
* multiple attributes or attributes that don't have their own undo type are changed, we're
|
|
* forced to fall back to the slower geometry undo type. */
|
|
store_sculpt_entire_mesh(op, scene, object, new_mesh);
|
|
entire_mesh_changed = true;
|
|
}
|
|
}
|
|
DEG_id_tag_update(&mesh.id, ID_RECALC_SHADING);
|
|
if (!use_pbvh_draw || entire_mesh_changed) {
|
|
DEG_id_tag_update(&mesh.id, ID_RECALC_GEOMETRY);
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
/* Returns whether the mouse/stylus is over the mesh (1)
|
|
* or over the background (0). */
|
|
static bool over_mesh(bContext *C, wmOperator * /*op*/, const float mval[2])
|
|
{
|
|
float co_dummy[3];
|
|
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
bool check_closest = brush->falloff_shape == PAINT_FALLOFF_SHAPE_TUBE;
|
|
|
|
return SCULPT_stroke_get_location_ex(C, co_dummy, mval, false, check_closest, true);
|
|
}
|
|
|
|
static void stroke_undo_begin(const bContext *C, wmOperator *op)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
const Scene &scene = *CTX_data_scene(C);
|
|
Object &ob = *CTX_data_active_object(C);
|
|
const Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
const Brush *brush = BKE_paint_brush_for_read(&sd.paint);
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
|
|
/* Setup the correct undo system. Image painting and sculpting are mutual exclusive.
|
|
* Color attributes are part of the sculpting undo system. */
|
|
if (brush && brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT &&
|
|
SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob))
|
|
{
|
|
ED_image_undo_push_begin(op->type->name, PaintMode::Sculpt);
|
|
}
|
|
else {
|
|
undo::push_begin_ex(scene, ob, sculpt_brush_type_name(sd));
|
|
}
|
|
}
|
|
|
|
static void stroke_undo_end(const bContext *C, Brush *brush)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
Object &ob = *CTX_data_active_object(C);
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
|
|
if (brush && brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT &&
|
|
SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob))
|
|
{
|
|
ED_image_undo_push_end();
|
|
}
|
|
else {
|
|
undo::push_end(ob);
|
|
}
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
bool color_supported_check(const Scene &scene, Object &object, ReportList *reports)
|
|
{
|
|
if (const SculptSession &ss = *object.sculpt; ss.bm) {
|
|
BKE_report(reports, RPT_ERROR, "Not supported in dynamic topology mode");
|
|
return false;
|
|
}
|
|
if (BKE_sculpt_multires_active(&scene, &object)) {
|
|
BKE_report(reports, RPT_ERROR, "Not supported in multiresolution mode");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool stroke_test_start(bContext *C, wmOperator *op, const float mval[2])
|
|
{
|
|
/* Don't start the stroke until `mval` goes over the mesh.
|
|
* NOTE: `mval` will only be null when re-executing the saved stroke.
|
|
* We have exception for 'exec' strokes since they may not set `mval`,
|
|
* only 'location', see: #52195. */
|
|
if (((op->flag & OP_IS_INVOKE) == 0) || (mval == nullptr) || over_mesh(C, op, mval)) {
|
|
Object &ob = *CTX_data_active_object(C);
|
|
SculptSession &ss = *ob.sculpt;
|
|
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd.paint);
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
|
|
/* NOTE: This should be removed when paint mode is available. Paint mode can force based on the
|
|
* canvas it is painting on. (ref. use_sculpt_texture_paint). */
|
|
if (brush && brush_type_is_paint(brush->sculpt_brush_type) &&
|
|
!SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob))
|
|
{
|
|
View3D *v3d = CTX_wm_view3d(C);
|
|
if (v3d->shading.type == OB_SOLID) {
|
|
v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR;
|
|
}
|
|
}
|
|
|
|
ED_view3d_init_mats_rv3d(&ob, CTX_wm_region_view3d(C));
|
|
|
|
sculpt_update_cache_invariants(C, sd, ss, *op, mval);
|
|
|
|
SculptCursorGeometryInfo sgi;
|
|
SCULPT_cursor_geometry_info_update(C, &sgi, mval, false);
|
|
|
|
stroke_undo_begin(C, op);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void stroke_update_step(bContext *C,
|
|
wmOperator * /*op*/,
|
|
PaintStroke *stroke,
|
|
PointerRNA *itemptr)
|
|
{
|
|
UnifiedPaintSettings &ups = CTX_data_tool_settings(C)->unified_paint_settings;
|
|
const Scene &scene = *CTX_data_scene(C);
|
|
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C);
|
|
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
Object &ob = *CTX_data_active_object(C);
|
|
SculptSession &ss = *ob.sculpt;
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
ToolSettings &tool_settings = *CTX_data_tool_settings(C);
|
|
StrokeCache *cache = ss.cache;
|
|
cache->stroke_distance = paint_stroke_distance_get(stroke);
|
|
|
|
SCULPT_stroke_modifiers_check(C, ob, brush);
|
|
sculpt_update_cache_variants(C, sd, ob, itemptr);
|
|
restore_from_undo_step_if_necessary(depsgraph, sd, ob);
|
|
|
|
if (dyntopo::stroke_is_dyntopo(ob, brush)) {
|
|
do_symmetrical_brush_actions(
|
|
depsgraph, scene, sd, ob, dynamic_topology_update, ups, tool_settings.paint_mode);
|
|
}
|
|
|
|
do_symmetrical_brush_actions(
|
|
depsgraph, scene, sd, ob, do_brush_action, ups, tool_settings.paint_mode);
|
|
|
|
/* Hack to fix noise texture tearing mesh. */
|
|
sculpt_fix_noise_tear(sd, ob);
|
|
|
|
ss.cache->first_time = false;
|
|
copy_v3_v3(ss.cache->last_location, ss.cache->location);
|
|
|
|
/* Cleanup. */
|
|
if (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
flush_update_step(C, UpdateType::Mask);
|
|
}
|
|
else if (brush_type_is_paint(brush.sculpt_brush_type)) {
|
|
if (SCULPT_use_image_paint_brush(tool_settings.paint_mode, ob)) {
|
|
flush_update_step(C, UpdateType::Image);
|
|
}
|
|
else {
|
|
flush_update_step(C, UpdateType::Color);
|
|
}
|
|
}
|
|
else {
|
|
flush_update_step(C, UpdateType::Position);
|
|
}
|
|
}
|
|
|
|
static void brush_exit_tex(Sculpt &sd)
|
|
{
|
|
Brush *brush = BKE_paint_brush(&sd.paint);
|
|
const MTex *mask_tex = BKE_brush_mask_texture_get(brush, OB_MODE_SCULPT);
|
|
|
|
if (mask_tex->tex && mask_tex->tex->nodetree) {
|
|
ntreeTexEndExecTree(mask_tex->tex->nodetree->runtime->execdata);
|
|
}
|
|
}
|
|
|
|
static void stroke_done(const bContext *C, PaintStroke * /*stroke*/)
|
|
{
|
|
Object &ob = *CTX_data_active_object(C);
|
|
SculptSession &ss = *ob.sculpt;
|
|
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
ToolSettings *tool_settings = CTX_data_tool_settings(C);
|
|
|
|
/* Finished. */
|
|
if (!ss.cache) {
|
|
brush_exit_tex(sd);
|
|
return;
|
|
}
|
|
UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings;
|
|
Brush *brush = BKE_paint_brush(&sd.paint);
|
|
BLI_assert(brush == ss.cache->brush); /* const, so we shouldn't change. */
|
|
ups->draw_inverted = false;
|
|
|
|
SCULPT_stroke_modifiers_check(C, ob, *brush);
|
|
|
|
/* Alt-Smooth. */
|
|
if (ss.cache->alt_smooth) {
|
|
smooth_brush_toggle_off(C, &sd.paint, ss.cache);
|
|
/* Refresh the brush pointer in case we switched brush in the toggle function. */
|
|
brush = BKE_paint_brush(&sd.paint);
|
|
}
|
|
|
|
MEM_delete(ss.cache);
|
|
ss.cache = nullptr;
|
|
|
|
stroke_undo_end(C, brush);
|
|
|
|
if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) {
|
|
flush_update_done(C, ob, UpdateType::Mask);
|
|
}
|
|
else if (brush->sculpt_brush_type == SCULPT_BRUSH_TYPE_PAINT) {
|
|
if (SCULPT_use_image_paint_brush(tool_settings->paint_mode, ob)) {
|
|
flush_update_done(C, ob, UpdateType::Image);
|
|
}
|
|
else {
|
|
flush_update_done(C, ob, UpdateType::Color);
|
|
}
|
|
}
|
|
else {
|
|
flush_update_done(C, ob, UpdateType::Position);
|
|
}
|
|
|
|
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, &ob);
|
|
brush_exit_tex(sd);
|
|
}
|
|
|
|
static wmOperatorStatus sculpt_brush_stroke_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent *event)
|
|
{
|
|
PaintStroke *stroke;
|
|
int ignore_background_click;
|
|
Object &ob = *CTX_data_active_object(C);
|
|
Scene &scene = *CTX_data_scene(C);
|
|
const View3D *v3d = CTX_wm_view3d(C);
|
|
const Base *base = CTX_data_active_base(C);
|
|
/* Test that ob is visible; otherwise we won't be able to get evaluated data
|
|
* from the depsgraph. We do this here instead of SCULPT_mode_poll
|
|
* to avoid falling through to the translate operator in the
|
|
* global view3d keymap. */
|
|
if (!BKE_base_is_visible(v3d, base)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
brush_stroke_init(C);
|
|
|
|
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
Brush &brush = *BKE_paint_brush(&sd.paint);
|
|
|
|
if (brush_type_is_paint(brush.sculpt_brush_type) &&
|
|
!color_supported_check(scene, ob, op->reports))
|
|
{
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (brush_type_is_mask(brush.sculpt_brush_type)) {
|
|
MultiresModifierData *mmd = BKE_sculpt_multires_active(&scene, &ob);
|
|
BKE_sculpt_mask_layers_ensure(CTX_data_depsgraph_pointer(C), CTX_data_main(C), &ob, mmd);
|
|
}
|
|
if (!brush_type_is_attribute_only(brush.sculpt_brush_type) &&
|
|
report_if_shape_key_is_locked(ob, op->reports))
|
|
{
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (ELEM(brush.sculpt_brush_type,
|
|
SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR,
|
|
SCULPT_BRUSH_TYPE_DISPLACEMENT_ERASER))
|
|
{
|
|
const blender::bke::pbvh::Tree *pbvh = blender::bke::object::pbvh_get(ob);
|
|
if (!pbvh || pbvh->type() != bke::pbvh::Type::Grids) {
|
|
BKE_report(op->reports, RPT_ERROR, "Only supported in multiresolution mode");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
|
|
stroke = paint_stroke_new(C,
|
|
op,
|
|
SCULPT_stroke_get_location,
|
|
stroke_test_start,
|
|
stroke_update_step,
|
|
nullptr,
|
|
stroke_done,
|
|
event->type);
|
|
|
|
op->customdata = stroke;
|
|
|
|
/* For tablet rotation. */
|
|
ignore_background_click = RNA_boolean_get(op->ptr, "ignore_background_click");
|
|
const float mval[2] = {float(event->mval[0]), float(event->mval[1])};
|
|
if (ignore_background_click && !over_mesh(C, op, mval)) {
|
|
paint_stroke_free(C, op, static_cast<PaintStroke *>(op->customdata));
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
|
|
const wmOperatorStatus retval = op->type->modal(C, op, event);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
if (ELEM(retval, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
|
|
paint_stroke_free(C, op, static_cast<PaintStroke *>(op->customdata));
|
|
return retval;
|
|
}
|
|
/* Add modal handler. */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
BLI_assert(retval == OPERATOR_RUNNING_MODAL);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static wmOperatorStatus sculpt_brush_stroke_exec(bContext *C, wmOperator *op)
|
|
{
|
|
brush_stroke_init(C);
|
|
|
|
op->customdata = paint_stroke_new(C,
|
|
op,
|
|
SCULPT_stroke_get_location,
|
|
stroke_test_start,
|
|
stroke_update_step,
|
|
nullptr,
|
|
stroke_done,
|
|
0);
|
|
|
|
/* Frees op->customdata. */
|
|
paint_stroke_exec(C, op, static_cast<PaintStroke *>(op->customdata));
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static void sculpt_brush_stroke_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
const Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C);
|
|
Object &ob = *CTX_data_active_object(C);
|
|
SculptSession &ss = *ob.sculpt;
|
|
Sculpt &sd = *CTX_data_tool_settings(C)->sculpt;
|
|
const Brush &brush = *BKE_paint_brush_for_read(&sd.paint);
|
|
|
|
/* XXX Canceling strokes that way does not work with dynamic topology,
|
|
* user will have to do real undo for now. See #46456. */
|
|
if (ss.cache && !dyntopo::stroke_is_dyntopo(ob, brush)) {
|
|
undo::restore_from_undo_step(depsgraph, sd, ob);
|
|
}
|
|
|
|
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
|
|
|
|
MEM_delete(ss.cache);
|
|
ss.cache = nullptr;
|
|
|
|
brush_exit_tex(sd);
|
|
}
|
|
|
|
static wmOperatorStatus brush_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
return paint_stroke_modal(C, op, event, (PaintStroke **)&op->customdata);
|
|
}
|
|
|
|
static void redo_empty_ui(bContext * /*C*/, wmOperator * /*op*/) {}
|
|
|
|
void SCULPT_OT_brush_stroke(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Sculpt";
|
|
ot->idname = "SCULPT_OT_brush_stroke";
|
|
ot->description = "Sculpt a stroke into the geometry";
|
|
|
|
/* API callbacks. */
|
|
ot->invoke = sculpt_brush_stroke_invoke;
|
|
ot->modal = brush_stroke_modal;
|
|
ot->exec = sculpt_brush_stroke_exec;
|
|
ot->poll = SCULPT_poll;
|
|
ot->cancel = sculpt_brush_stroke_cancel;
|
|
ot->ui = redo_empty_ui;
|
|
|
|
/* Flags (sculpt does its own undo? (ton)). */
|
|
ot->flag = OPTYPE_BLOCKING;
|
|
|
|
/* Properties. */
|
|
|
|
paint_stroke_operator_properties(ot);
|
|
|
|
PropertyRNA *prop = RNA_def_boolean(
|
|
ot->srna,
|
|
"override_location",
|
|
false,
|
|
"Override Location",
|
|
"Override the given `location` array by recalculating object space positions from the "
|
|
"provided `mouse_event` positions");
|
|
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
|
|
|
|
RNA_def_boolean(ot->srna,
|
|
"ignore_background_click",
|
|
false,
|
|
"Ignore Background Click",
|
|
"Clicks on the background do not start the stroke");
|
|
}
|
|
|
|
/* Fake Neighbors. */
|
|
|
|
static void fake_neighbor_init(Object &object, const float max_dist)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
const int totvert = SCULPT_vertex_count_get(object);
|
|
ss.fake_neighbors.fake_neighbor_index = Array<int>(totvert, FAKE_NEIGHBOR_NONE);
|
|
ss.fake_neighbors.current_max_distance = max_dist;
|
|
}
|
|
|
|
static void pose_fake_neighbors_free(SculptSession &ss)
|
|
{
|
|
ss.fake_neighbors.fake_neighbor_index = {};
|
|
}
|
|
|
|
struct NearestVertData {
|
|
int vert = -1;
|
|
float distance_sq = std::numeric_limits<float>::max();
|
|
|
|
static NearestVertData join(const NearestVertData &a, const NearestVertData &b)
|
|
{
|
|
NearestVertData joined = a;
|
|
if (joined.vert == -1) {
|
|
joined.vert = b.vert;
|
|
joined.distance_sq = b.distance_sq;
|
|
}
|
|
else if (b.distance_sq < joined.distance_sq) {
|
|
joined.vert = b.vert;
|
|
joined.distance_sq = b.distance_sq;
|
|
}
|
|
return joined;
|
|
}
|
|
};
|
|
|
|
static void fake_neighbor_search_mesh(const SculptSession &ss,
|
|
const Span<float3> vert_positions,
|
|
const Span<bool> hide_vert,
|
|
const float3 &location,
|
|
const float max_distance_sq,
|
|
const int island_id,
|
|
const bke::pbvh::MeshNode &node,
|
|
NearestVertData &nvtd)
|
|
{
|
|
for (const int vert : node.verts()) {
|
|
if (!hide_vert.is_empty() && hide_vert[vert]) {
|
|
continue;
|
|
}
|
|
if (ss.fake_neighbors.fake_neighbor_index[vert] != FAKE_NEIGHBOR_NONE) {
|
|
continue;
|
|
}
|
|
if (islands::vert_id_get(ss, vert) == island_id) {
|
|
continue;
|
|
}
|
|
const float distance_sq = math::distance_squared(vert_positions[vert], location);
|
|
if (distance_sq < max_distance_sq && distance_sq < nvtd.distance_sq) {
|
|
nvtd.vert = vert;
|
|
nvtd.distance_sq = distance_sq;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fake_neighbor_search_grids(const SculptSession &ss,
|
|
const CCGKey &key,
|
|
const Span<float3> positions,
|
|
const BitGroupVector<> &grid_hidden,
|
|
const float3 &location,
|
|
const float max_distance_sq,
|
|
const int island_id,
|
|
const bke::pbvh::GridsNode &node,
|
|
NearestVertData &nvtd)
|
|
{
|
|
for (const int grid : node.grids()) {
|
|
const IndexRange grid_range = bke::ccg::grid_range(key, grid);
|
|
BKE_subdiv_ccg_foreach_visible_grid_vert(key, grid_hidden, grid, [&](const int offset) {
|
|
const int vert = grid_range[offset];
|
|
if (ss.fake_neighbors.fake_neighbor_index[vert] != FAKE_NEIGHBOR_NONE) {
|
|
return;
|
|
}
|
|
if (islands::vert_id_get(ss, vert) == island_id) {
|
|
return;
|
|
}
|
|
const float distance_sq = math::distance_squared(positions[vert], location);
|
|
if (distance_sq < max_distance_sq && distance_sq < nvtd.distance_sq) {
|
|
nvtd.vert = vert;
|
|
nvtd.distance_sq = distance_sq;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
static void fake_neighbor_search_bmesh(const SculptSession &ss,
|
|
const float3 &location,
|
|
const float max_distance_sq,
|
|
const int island_id,
|
|
const bke::pbvh::BMeshNode &node,
|
|
NearestVertData &nvtd)
|
|
{
|
|
for (const BMVert *bm_vert :
|
|
BKE_pbvh_bmesh_node_unique_verts(const_cast<bke::pbvh::BMeshNode *>(&node)))
|
|
{
|
|
if (BM_elem_flag_test(bm_vert, BM_ELEM_HIDDEN)) {
|
|
continue;
|
|
}
|
|
const int vert = BM_elem_index_get(bm_vert);
|
|
if (ss.fake_neighbors.fake_neighbor_index[vert] != FAKE_NEIGHBOR_NONE) {
|
|
continue;
|
|
}
|
|
if (islands::vert_id_get(ss, vert) == island_id) {
|
|
continue;
|
|
}
|
|
const float distance_sq = math::distance_squared(float3(bm_vert->co), location);
|
|
if (distance_sq < max_distance_sq && distance_sq < nvtd.distance_sq) {
|
|
nvtd.vert = vert;
|
|
nvtd.distance_sq = distance_sq;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fake_neighbor_search(const Depsgraph &depsgraph,
|
|
const Object &ob,
|
|
const float max_distance_sq,
|
|
MutableSpan<int> fake_neighbors)
|
|
{
|
|
/* NOTE: This algorithm is extremely slow, it has O(n^2) runtime for the entire mesh. This looks
|
|
* like the "closest pair of points" problem which should have far better solutions. */
|
|
SculptSession &ss = *ob.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(ob);
|
|
|
|
switch (pbvh.type()) {
|
|
case bke::pbvh::Type::Mesh: {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(ob.data);
|
|
const Span<float3> vert_positions = bke::pbvh::vert_positions_eval(depsgraph, ob);
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArraySpan<bool> hide_vert = *attributes.lookup<bool>(".hide_vert",
|
|
bke::AttrDomain::Point);
|
|
for (const int vert : vert_positions.index_range()) {
|
|
if (fake_neighbors[vert] != FAKE_NEIGHBOR_NONE) {
|
|
continue;
|
|
}
|
|
const int island_id = islands::vert_id_get(ss, vert);
|
|
const float3 &location = vert_positions[vert];
|
|
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, false);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
continue;
|
|
}
|
|
const Span<bke::pbvh::MeshNode> nodes = pbvh.nodes<bke::pbvh::MeshNode>();
|
|
const NearestVertData nvtd = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestVertData(),
|
|
[&](const IndexRange range, NearestVertData nvtd) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
fake_neighbor_search_mesh(ss,
|
|
vert_positions,
|
|
hide_vert,
|
|
location,
|
|
max_distance_sq,
|
|
island_id,
|
|
nodes[i],
|
|
nvtd);
|
|
});
|
|
return nvtd;
|
|
},
|
|
NearestVertData::join);
|
|
if (nvtd.vert == -1) {
|
|
continue;
|
|
}
|
|
fake_neighbors[vert] = nvtd.vert;
|
|
fake_neighbors[nvtd.vert] = vert;
|
|
}
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::Grids: {
|
|
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
const Span<float3> positions = subdiv_ccg.positions;
|
|
const BitGroupVector<> grid_hidden = subdiv_ccg.grid_hidden;
|
|
for (const int vert : positions.index_range()) {
|
|
if (fake_neighbors[vert] != FAKE_NEIGHBOR_NONE) {
|
|
continue;
|
|
}
|
|
const int island_id = islands::vert_id_get(ss, vert);
|
|
const float3 &location = positions[vert];
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, false);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
continue;
|
|
}
|
|
const Span<bke::pbvh::GridsNode> nodes = pbvh.nodes<bke::pbvh::GridsNode>();
|
|
const NearestVertData nvtd = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestVertData(),
|
|
[&](const IndexRange range, NearestVertData nvtd) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
fake_neighbor_search_grids(ss,
|
|
key,
|
|
positions,
|
|
grid_hidden,
|
|
location,
|
|
max_distance_sq,
|
|
island_id,
|
|
nodes[i],
|
|
nvtd);
|
|
});
|
|
return nvtd;
|
|
},
|
|
NearestVertData::join);
|
|
if (nvtd.vert == -1) {
|
|
continue;
|
|
}
|
|
fake_neighbors[vert] = nvtd.vert;
|
|
fake_neighbors[nvtd.vert] = vert;
|
|
}
|
|
break;
|
|
}
|
|
case bke::pbvh::Type::BMesh: {
|
|
const BMesh &bm = *ss.bm;
|
|
for (const int vert : IndexRange(bm.totvert)) {
|
|
if (fake_neighbors[vert] != FAKE_NEIGHBOR_NONE) {
|
|
continue;
|
|
}
|
|
const int island_id = islands::vert_id_get(ss, vert);
|
|
const float3 location = BM_vert_at_index(&const_cast<BMesh &>(bm), vert)->co;
|
|
IndexMaskMemory memory;
|
|
const IndexMask nodes_in_sphere = bke::pbvh::search_nodes(
|
|
pbvh, memory, [&](const bke::pbvh::Node &node) {
|
|
return node_in_sphere(node, location, max_distance_sq, false);
|
|
});
|
|
if (nodes_in_sphere.is_empty()) {
|
|
continue;
|
|
}
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
const NearestVertData nvtd = threading::parallel_reduce(
|
|
nodes_in_sphere.index_range(),
|
|
1,
|
|
NearestVertData(),
|
|
[&](const IndexRange range, NearestVertData nvtd) {
|
|
nodes_in_sphere.slice(range).foreach_index([&](const int i) {
|
|
fake_neighbor_search_bmesh(
|
|
ss, location, max_distance_sq, island_id, nodes[i], nvtd);
|
|
});
|
|
return nvtd;
|
|
},
|
|
NearestVertData::join);
|
|
if (nvtd.vert == -1) {
|
|
continue;
|
|
}
|
|
fake_neighbors[vert] = nvtd.vert;
|
|
fake_neighbors[nvtd.vert] = vert;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|
|
|
|
namespace blender::ed::sculpt_paint::boundary {
|
|
|
|
void ensure_boundary_info(Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
if (!ss.vertex_info.boundary.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
Mesh *base_mesh = BKE_mesh_from_object(&object);
|
|
|
|
ss.vertex_info.boundary.resize(base_mesh->verts_num);
|
|
Array<int> adjacent_faces_edge_count(base_mesh->edges_num, 0);
|
|
array_utils::count_indices(base_mesh->corner_edges(), adjacent_faces_edge_count);
|
|
|
|
const Span<int2> edges = base_mesh->edges();
|
|
for (const int e : edges.index_range()) {
|
|
if (adjacent_faces_edge_count[e] < 2) {
|
|
const int2 &edge = edges[e];
|
|
ss.vertex_info.boundary[edge[0]].set();
|
|
ss.vertex_info.boundary[edge[1]].set();
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint::boundary
|
|
|
|
Span<int> SCULPT_fake_neighbors_ensure(const Depsgraph &depsgraph,
|
|
Object &ob,
|
|
const float max_dist)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
SculptSession &ss = *ob.sculpt;
|
|
|
|
/* Fake neighbors were already initialized with the same distance, so no need to be
|
|
* recalculated. */
|
|
if (!ss.fake_neighbors.fake_neighbor_index.is_empty() &&
|
|
ss.fake_neighbors.current_max_distance == max_dist)
|
|
{
|
|
return ss.fake_neighbors.fake_neighbor_index;
|
|
}
|
|
|
|
islands::ensure_cache(ob);
|
|
fake_neighbor_init(ob, max_dist);
|
|
fake_neighbor_search(depsgraph, ob, max_dist * max_dist, ss.fake_neighbors.fake_neighbor_index);
|
|
|
|
return ss.fake_neighbors.fake_neighbor_index;
|
|
}
|
|
|
|
void SCULPT_fake_neighbors_free(Object &ob)
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
SculptSession &ss = *ob.sculpt;
|
|
pose_fake_neighbors_free(ss);
|
|
}
|
|
|
|
bool SCULPT_vertex_is_occluded(const Depsgraph &depsgraph,
|
|
const Object &object,
|
|
const float3 &position,
|
|
bool original)
|
|
{
|
|
using namespace blender;
|
|
SculptSession &ss = *object.sculpt;
|
|
float ray_start[3], ray_end[3], ray_normal[3];
|
|
|
|
ViewContext *vc = ss.cache ? ss.cache->vc : &ss.filter_cache->vc;
|
|
|
|
const blender::float2 mouse = ED_view3d_project_float_v2_m4(
|
|
vc->region, position, ss.cache ? ss.cache->projection_mat : ss.filter_cache->viewmat);
|
|
|
|
int depth = SCULPT_raycast_init(vc, mouse, ray_end, ray_start, ray_normal, original);
|
|
|
|
negate_v3(ray_normal);
|
|
|
|
copy_v3_v3(ray_start, position);
|
|
madd_v3_v3fl(ray_start, ray_normal, 0.002);
|
|
|
|
bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(const_cast<Object &>(object));
|
|
|
|
SculptRaycastData srd = {nullptr};
|
|
srd.original = original;
|
|
srd.object = &const_cast<Object &>(object);
|
|
srd.ss = &ss;
|
|
srd.hit = false;
|
|
srd.ray_start = ray_start;
|
|
srd.ray_normal = ray_normal;
|
|
srd.depth = depth;
|
|
if (pbvh.type() == bke::pbvh::Type::Mesh) {
|
|
const Mesh &mesh = *static_cast<const Mesh *>(object.data);
|
|
srd.vert_positions = bke::pbvh::vert_positions_eval(depsgraph, object);
|
|
srd.faces = mesh.faces();
|
|
srd.corner_verts = mesh.corner_verts();
|
|
srd.corner_tris = mesh.corner_tris();
|
|
}
|
|
else if (pbvh.type() == bke::pbvh::Type::Grids) {
|
|
srd.subdiv_ccg = ss.subdiv_ccg;
|
|
}
|
|
SCULPT_vertex_random_access_ensure(const_cast<Object &>(object));
|
|
|
|
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
|
|
bke::pbvh::raycast(
|
|
pbvh,
|
|
[&](bke::pbvh::Node &node, float *tmin) { sculpt_raycast_cb(node, srd, tmin); },
|
|
ray_start,
|
|
ray_normal,
|
|
srd.original);
|
|
|
|
return srd.hit;
|
|
}
|
|
|
|
namespace blender::ed::sculpt_paint::islands {
|
|
|
|
int vert_id_get(const SculptSession &ss, const int vert)
|
|
{
|
|
BLI_assert(ss.topology_island_cache);
|
|
if (!ss.topology_island_cache) {
|
|
/* The cache should be calculated whenever it's necessary.
|
|
* Still avoid crashing in release builds though. */
|
|
return 0;
|
|
}
|
|
const SculptTopologyIslandCache &cache = *ss.topology_island_cache;
|
|
if (!cache.vert_island_ids.is_empty()) {
|
|
return cache.vert_island_ids[vert];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void invalidate(SculptSession &ss)
|
|
{
|
|
ss.topology_island_cache.reset();
|
|
}
|
|
|
|
static SculptTopologyIslandCache vert_disjoint_set_to_islands(const AtomicDisjointSet &vert_sets,
|
|
const int verts_num)
|
|
{
|
|
Array<int> island_indices(verts_num);
|
|
const int islands_num = vert_sets.calc_reduced_ids(island_indices);
|
|
if (islands_num == 1) {
|
|
return {};
|
|
}
|
|
|
|
Array<uint8_t> island_ids(island_indices.size());
|
|
threading::parallel_for(island_ids.index_range(), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
island_ids[i] = uint8_t(island_indices[i]);
|
|
}
|
|
});
|
|
|
|
SculptTopologyIslandCache cache;
|
|
cache.vert_island_ids = std::move(island_ids);
|
|
return cache;
|
|
}
|
|
|
|
static SculptTopologyIslandCache calc_topology_islands_mesh(const Mesh &mesh)
|
|
{
|
|
const OffsetIndices<int> faces = mesh.faces();
|
|
const Span<int> corner_verts = mesh.corner_verts();
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
const VArraySpan<bool> hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
|
IndexMaskMemory memory;
|
|
const IndexMask visible_faces = hide_poly.is_empty() ?
|
|
IndexMask(faces.size()) :
|
|
IndexMask::from_bools_inverse(
|
|
faces.index_range(), hide_poly, memory);
|
|
|
|
AtomicDisjointSet disjoint_set(mesh.verts_num);
|
|
visible_faces.foreach_index(GrainSize(1024), [&](const int face) {
|
|
const Span<int> face_verts = corner_verts.slice(faces[face]);
|
|
for (const int i : face_verts.index_range().drop_front(1)) {
|
|
disjoint_set.join(face_verts.first(), face_verts[i]);
|
|
}
|
|
});
|
|
return vert_disjoint_set_to_islands(disjoint_set, mesh.verts_num);
|
|
}
|
|
|
|
/**
|
|
* \todo Take grid face visibility into account.
|
|
*/
|
|
static SculptTopologyIslandCache calc_topology_islands_grids(const Object &object)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
AtomicDisjointSet disjoint_set(subdiv_ccg.positions.size());
|
|
threading::parallel_for(IndexRange(subdiv_ccg.grids_num), 512, [&](const IndexRange range) {
|
|
for (const int grid : range) {
|
|
SubdivCCGNeighbors neighbors;
|
|
for (const short y : IndexRange(key.grid_size)) {
|
|
for (const short x : IndexRange(key.grid_size)) {
|
|
const SubdivCCGCoord coord{grid, x, y};
|
|
SubdivCCGNeighbors neighbors;
|
|
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, true, neighbors);
|
|
for (const SubdivCCGCoord neighbor : neighbors.coords) {
|
|
disjoint_set.join(coord.to_index(key), neighbor.to_index(key));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return vert_disjoint_set_to_islands(disjoint_set, subdiv_ccg.positions.size());
|
|
}
|
|
|
|
static SculptTopologyIslandCache calc_topology_islands_bmesh(const Object &object)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const bke::pbvh::Tree &pbvh = *bke::object::pbvh_get(object);
|
|
const Span<bke::pbvh::BMeshNode> nodes = pbvh.nodes<bke::pbvh::BMeshNode>();
|
|
BMesh &bm = *ss.bm;
|
|
BM_mesh_elem_index_ensure(&bm, BM_VERT);
|
|
|
|
IndexMaskMemory memory;
|
|
const IndexMask node_mask = bke::pbvh::all_leaf_nodes(pbvh, memory);
|
|
AtomicDisjointSet disjoint_set(bm.totvert);
|
|
node_mask.foreach_index(GrainSize(1), [&](const int i) {
|
|
for (const BMFace *face :
|
|
BKE_pbvh_bmesh_node_faces(&const_cast<bke::pbvh::BMeshNode &>(nodes[i])))
|
|
{
|
|
if (BM_elem_flag_test(face, BM_ELEM_HIDDEN)) {
|
|
continue;
|
|
}
|
|
disjoint_set.join(BM_elem_index_get(face->l_first->v),
|
|
BM_elem_index_get(face->l_first->next->v));
|
|
disjoint_set.join(BM_elem_index_get(face->l_first->v),
|
|
BM_elem_index_get(face->l_first->next->next->v));
|
|
}
|
|
});
|
|
|
|
return vert_disjoint_set_to_islands(disjoint_set, bm.totvert);
|
|
}
|
|
|
|
static SculptTopologyIslandCache calculate_cache(const Object &object)
|
|
{
|
|
switch (bke::object::pbvh_get(object)->type()) {
|
|
case bke::pbvh::Type::Mesh:
|
|
return calc_topology_islands_mesh(*static_cast<const Mesh *>(object.data));
|
|
case bke::pbvh::Type::Grids:
|
|
return calc_topology_islands_grids(object);
|
|
case bke::pbvh::Type::BMesh:
|
|
return calc_topology_islands_bmesh(object);
|
|
}
|
|
BLI_assert_unreachable();
|
|
return {};
|
|
}
|
|
|
|
void ensure_cache(Object &object)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
if (ss.topology_island_cache) {
|
|
return;
|
|
}
|
|
ss.topology_island_cache = std::make_unique<SculptTopologyIslandCache>(calculate_cache(object));
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint::islands
|
|
|
|
void SCULPT_cube_tip_init(const Sculpt & /*sd*/,
|
|
const Object &ob,
|
|
const Brush &brush,
|
|
float mat[4][4])
|
|
{
|
|
using namespace blender::ed::sculpt_paint;
|
|
SculptSession &ss = *ob.sculpt;
|
|
float scale[4][4];
|
|
float tmat[4][4];
|
|
float unused[4][4];
|
|
|
|
zero_m4(mat);
|
|
calc_brush_local_mat(0.0, ob, unused, mat);
|
|
|
|
/* NOTE: we ignore the radius scaling done inside of calc_brush_local_mat to
|
|
* duplicate prior behavior.
|
|
*
|
|
* TODO: try disabling this and check that all edge cases work properly.
|
|
*/
|
|
normalize_m4(mat);
|
|
|
|
scale_m4_fl(scale, ss.cache->radius);
|
|
mul_m4_m4m4(tmat, mat, scale);
|
|
mul_v3_fl(tmat[1], brush.tip_scale_x);
|
|
invert_m4_m4(mat, tmat);
|
|
}
|
|
/** \} */
|
|
|
|
namespace blender::ed::sculpt_paint {
|
|
|
|
MeshAttributeData::MeshAttributeData(const Mesh &mesh)
|
|
{
|
|
const bke::AttributeAccessor attributes = mesh.attributes();
|
|
this->mask = *attributes.lookup<float>(".sculpt_mask", bke::AttrDomain::Point);
|
|
this->hide_vert = *attributes.lookup<bool>(".hide_vert", bke::AttrDomain::Point);
|
|
this->hide_poly = *attributes.lookup<bool>(".hide_poly", bke::AttrDomain::Face);
|
|
this->face_sets = *attributes.lookup<int>(".sculpt_face_set", bke::AttrDomain::Face);
|
|
}
|
|
|
|
void gather_bmesh_positions(const Set<BMVert *, 0> &verts, const MutableSpan<float3> positions)
|
|
{
|
|
BLI_assert(verts.size() == positions.size());
|
|
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
positions[i] = vert->co;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void gather_grids_normals(const SubdivCCG &subdiv_ccg,
|
|
const Span<int> grids,
|
|
const MutableSpan<float3> normals)
|
|
{
|
|
gather_data_grids(subdiv_ccg, subdiv_ccg.normals.as_span(), grids, normals);
|
|
}
|
|
|
|
void gather_bmesh_normals(const Set<BMVert *, 0> &verts, const MutableSpan<float3> normals)
|
|
{
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
normals[i] = vert->no;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void gather_data_mesh(const Span<T> src, const Span<int> indices, const MutableSpan<T> dst)
|
|
{
|
|
BLI_assert(indices.size() == dst.size());
|
|
|
|
for (const int i : indices.index_range()) {
|
|
dst[i] = src[indices[i]];
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void gather_data_grids(const SubdivCCG &subdiv_ccg,
|
|
const Span<T> src,
|
|
const Span<int> grids,
|
|
const MutableSpan<T> node_data)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
BLI_assert(grids.size() * key.grid_area == node_data.size());
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const IndexRange grids_range = bke::ccg::grid_range(key, grids[i]);
|
|
const IndexRange node_range = bke::ccg::grid_range(key, i);
|
|
node_data.slice(node_range).copy_from(src.slice(grids_range));
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void gather_data_bmesh(const Span<T> src,
|
|
const Set<BMVert *, 0> &verts,
|
|
const MutableSpan<T> node_data)
|
|
{
|
|
BLI_assert(verts.size() == node_data.size());
|
|
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
node_data[i] = src[BM_elem_index_get(vert)];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void scatter_data_mesh(const Span<T> src, const Span<int> indices, const MutableSpan<T> dst)
|
|
{
|
|
BLI_assert(indices.size() == src.size());
|
|
|
|
for (const int i : indices.index_range()) {
|
|
dst[indices[i]] = src[i];
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void scatter_data_grids(const SubdivCCG &subdiv_ccg,
|
|
const Span<T> node_data,
|
|
const Span<int> grids,
|
|
const MutableSpan<T> dst)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
BLI_assert(grids.size() * key.grid_area == node_data.size());
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const IndexRange grids_range = bke::ccg::grid_range(key, grids[i]);
|
|
const IndexRange node_range = bke::ccg::grid_range(key, i);
|
|
dst.slice(grids_range).copy_from(node_data.slice(node_range));
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void scatter_data_bmesh(const Span<T> node_data,
|
|
const Set<BMVert *, 0> &verts,
|
|
const MutableSpan<T> dst)
|
|
{
|
|
BLI_assert(verts.size() == node_data.size());
|
|
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
dst[BM_elem_index_get(vert)] = node_data[i];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
template void gather_data_mesh<bool>(Span<bool>, Span<int>, MutableSpan<bool>);
|
|
template void gather_data_mesh<int>(Span<int>, Span<int>, MutableSpan<int>);
|
|
template void gather_data_mesh<float>(Span<float>, Span<int>, MutableSpan<float>);
|
|
template void gather_data_mesh<float3>(Span<float3>, Span<int>, MutableSpan<float3>);
|
|
template void gather_data_mesh<float4>(Span<float4>, Span<int>, MutableSpan<float4>);
|
|
template void gather_data_grids<int>(const SubdivCCG &, Span<int>, Span<int>, MutableSpan<int>);
|
|
template void gather_data_grids<float>(const SubdivCCG &,
|
|
Span<float>,
|
|
Span<int>,
|
|
MutableSpan<float>);
|
|
template void gather_data_grids<float3>(const SubdivCCG &,
|
|
Span<float3>,
|
|
Span<int>,
|
|
MutableSpan<float3>);
|
|
template void gather_data_bmesh<int>(Span<int>, const Set<BMVert *, 0> &, MutableSpan<int>);
|
|
template void gather_data_bmesh<float>(Span<float>, const Set<BMVert *, 0> &, MutableSpan<float>);
|
|
template void gather_data_bmesh<float3>(Span<float3>,
|
|
const Set<BMVert *, 0> &,
|
|
MutableSpan<float3>);
|
|
|
|
template void scatter_data_mesh<bool>(Span<bool>, Span<int>, MutableSpan<bool>);
|
|
template void scatter_data_mesh<int>(Span<int>, Span<int>, MutableSpan<int>);
|
|
template void scatter_data_mesh<float>(Span<float>, Span<int>, MutableSpan<float>);
|
|
template void scatter_data_mesh<float3>(Span<float3>, Span<int>, MutableSpan<float3>);
|
|
template void scatter_data_mesh<float4>(Span<float4>, Span<int>, MutableSpan<float4>);
|
|
template void scatter_data_grids<float>(const SubdivCCG &,
|
|
Span<float>,
|
|
Span<int>,
|
|
MutableSpan<float>);
|
|
template void scatter_data_grids<float3>(const SubdivCCG &,
|
|
Span<float3>,
|
|
Span<int>,
|
|
MutableSpan<float3>);
|
|
template void scatter_data_bmesh<float>(Span<float>, const Set<BMVert *, 0> &, MutableSpan<float>);
|
|
template void scatter_data_bmesh<float3>(Span<float3>,
|
|
const Set<BMVert *, 0> &,
|
|
MutableSpan<float3>);
|
|
|
|
void calc_factors_common_mesh_indexed(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const MeshAttributeData &attribute_data,
|
|
const Span<float3> vert_positions,
|
|
const Span<float3> vert_normals,
|
|
const bke::pbvh::MeshNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
const Span<int> verts = node.verts();
|
|
r_factors.resize(verts.size());
|
|
r_distances.resize(verts.size());
|
|
|
|
calc_factors_common_mesh_indexed(depsgraph,
|
|
brush,
|
|
object,
|
|
attribute_data,
|
|
vert_positions,
|
|
vert_normals,
|
|
node,
|
|
r_factors.as_mutable_span(),
|
|
r_distances.as_mutable_span());
|
|
}
|
|
void calc_factors_common_mesh_indexed(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const MeshAttributeData &attribute_data,
|
|
const Span<float3> vert_positions,
|
|
const Span<float3> vert_normals,
|
|
const bke::pbvh::MeshNode &node,
|
|
const MutableSpan<float> factors,
|
|
const MutableSpan<float> distances)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
|
|
const Span<int> verts = node.verts();
|
|
|
|
fill_factor_from_hide_and_mask(attribute_data.hide_vert, attribute_data.mask, verts, factors);
|
|
filter_region_clip_factors(ss, vert_positions, verts, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, vert_normals, verts, factors);
|
|
}
|
|
|
|
calc_brush_distances(
|
|
ss, vert_positions, 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);
|
|
|
|
auto_mask::calc_vert_factors(depsgraph, object, cache.automasking.get(), node, verts, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, vert_positions, verts, factors);
|
|
}
|
|
|
|
void calc_factors_common_mesh(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const MeshAttributeData &attribute_data,
|
|
const Span<float3> positions,
|
|
const Span<float3> vert_normals,
|
|
const bke::pbvh::MeshNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
|
|
const Span<int> verts = node.verts();
|
|
|
|
r_factors.resize(verts.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(attribute_data.hide_vert, attribute_data.mask, verts, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, vert_normals, verts, factors);
|
|
}
|
|
|
|
r_distances.resize(verts.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_vert_factors(depsgraph, object, cache.automasking.get(), node, verts, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void calc_factors_common_grids(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const Span<float3> positions,
|
|
const bke::pbvh::GridsNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
const SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
|
|
const Span<int> grids = node.grids();
|
|
|
|
r_factors.resize(positions.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(subdiv_ccg, grids, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, subdiv_ccg, grids, factors);
|
|
}
|
|
|
|
r_distances.resize(positions.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_grids_factors(depsgraph, object, cache.automasking.get(), node, grids, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void calc_factors_common_bmesh(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const Span<float3> positions,
|
|
bke::pbvh::BMeshNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
|
|
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
|
|
|
|
r_factors.resize(verts.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(*ss.bm, verts, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, verts, factors);
|
|
}
|
|
|
|
r_distances.resize(verts.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_vert_factors(depsgraph, object, cache.automasking.get(), node, verts, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void calc_factors_common_from_orig_data_mesh(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const MeshAttributeData &attribute_data,
|
|
const Span<float3> positions,
|
|
const Span<float3> normals,
|
|
const bke::pbvh::MeshNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
const SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
|
|
const Span<int> verts = node.verts();
|
|
|
|
r_factors.resize(verts.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(attribute_data.hide_vert, attribute_data.mask, verts, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, normals, factors);
|
|
}
|
|
|
|
r_distances.resize(verts.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_vert_factors(depsgraph, object, cache.automasking.get(), node, verts, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void calc_factors_common_from_orig_data_grids(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const Span<float3> positions,
|
|
const Span<float3> normals,
|
|
const bke::pbvh::GridsNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
SubdivCCG &subdiv_ccg = *ss.subdiv_ccg;
|
|
|
|
const Span<int> grids = node.grids();
|
|
|
|
r_factors.resize(positions.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(subdiv_ccg, grids, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, normals, factors);
|
|
}
|
|
|
|
r_distances.resize(positions.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_grids_factors(depsgraph, object, cache.automasking.get(), node, grids, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void calc_factors_common_from_orig_data_bmesh(const Depsgraph &depsgraph,
|
|
const Brush &brush,
|
|
const Object &object,
|
|
const Span<float3> positions,
|
|
const Span<float3> normals,
|
|
bke::pbvh::BMeshNode &node,
|
|
Vector<float> &r_factors,
|
|
Vector<float> &r_distances)
|
|
{
|
|
SculptSession &ss = *object.sculpt;
|
|
const StrokeCache &cache = *ss.cache;
|
|
|
|
const Set<BMVert *, 0> &verts = BKE_pbvh_bmesh_node_unique_verts(&node);
|
|
|
|
r_factors.resize(verts.size());
|
|
const MutableSpan<float> factors = r_factors;
|
|
fill_factor_from_hide_and_mask(*ss.bm, verts, factors);
|
|
filter_region_clip_factors(ss, positions, factors);
|
|
if (brush.flag & BRUSH_FRONTFACE) {
|
|
calc_front_face(cache.view_normal_symm, normals, factors);
|
|
}
|
|
|
|
r_distances.resize(verts.size());
|
|
const MutableSpan<float> distances = r_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);
|
|
|
|
auto_mask::calc_vert_factors(depsgraph, object, cache.automasking.get(), node, verts, factors);
|
|
|
|
calc_brush_texture_factors(ss, brush, positions, factors);
|
|
}
|
|
|
|
void fill_factor_from_hide(const Span<bool> hide_vert,
|
|
const Span<int> verts,
|
|
const MutableSpan<float> r_factors)
|
|
{
|
|
BLI_assert(verts.size() == r_factors.size());
|
|
|
|
if (!hide_vert.is_empty()) {
|
|
for (const int i : verts.index_range()) {
|
|
r_factors[i] = hide_vert[verts[i]] ? 0.0f : 1.0f;
|
|
}
|
|
}
|
|
else {
|
|
r_factors.fill(1.0f);
|
|
}
|
|
}
|
|
|
|
void fill_factor_from_hide(const SubdivCCG &subdiv_ccg,
|
|
const Span<int> grids,
|
|
const MutableSpan<float> r_factors)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
BLI_assert(grids.size() * key.grid_area == r_factors.size());
|
|
|
|
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
|
if (grid_hidden.is_empty()) {
|
|
r_factors.fill(1.0f);
|
|
return;
|
|
}
|
|
for (const int i : grids.index_range()) {
|
|
const BitSpan hidden = grid_hidden[grids[i]];
|
|
const int start = i * key.grid_area;
|
|
for (const int offset : IndexRange(key.grid_area)) {
|
|
r_factors[start + offset] = hidden[offset] ? 0.0f : 1.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fill_factor_from_hide(const Set<BMVert *, 0> &verts, const MutableSpan<float> r_factors)
|
|
{
|
|
BLI_assert(verts.size() == r_factors.size());
|
|
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
r_factors[i] = BM_elem_flag_test_bool(vert, BM_ELEM_HIDDEN) ? 0.0f : 1.0f;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void fill_factor_from_hide_and_mask(const Span<bool> hide_vert,
|
|
const Span<float> mask,
|
|
const Span<int> verts,
|
|
const MutableSpan<float> r_factors)
|
|
{
|
|
BLI_assert(verts.size() == r_factors.size());
|
|
|
|
if (!mask.is_empty()) {
|
|
for (const int i : verts.index_range()) {
|
|
r_factors[i] = 1.0f - mask[verts[i]];
|
|
}
|
|
}
|
|
else {
|
|
r_factors.fill(1.0f);
|
|
}
|
|
|
|
if (!hide_vert.is_empty()) {
|
|
for (const int i : verts.index_range()) {
|
|
if (hide_vert[verts[i]]) {
|
|
r_factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void fill_factor_from_hide_and_mask(const BMesh &bm,
|
|
const Set<BMVert *, 0> &verts,
|
|
const MutableSpan<float> r_factors)
|
|
{
|
|
BLI_assert(verts.size() == r_factors.size());
|
|
|
|
/* TODO: Avoid overhead of accessing attributes for every bke::pbvh::Tree node. */
|
|
const int mask_offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
r_factors[i] = (mask_offset == -1) ? 1.0f : 1.0f - BM_ELEM_CD_GET_FLOAT(vert, mask_offset);
|
|
if (BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
|
|
r_factors[i] = 0.0f;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void fill_factor_from_hide_and_mask(const SubdivCCG &subdiv_ccg,
|
|
const Span<int> grids,
|
|
const MutableSpan<float> r_factors)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
BLI_assert(grids.size() * key.grid_area == r_factors.size());
|
|
|
|
if (!subdiv_ccg.masks.is_empty()) {
|
|
const Span<float> masks = subdiv_ccg.masks;
|
|
for (const int i : grids.index_range()) {
|
|
const Span src = masks.slice(bke::ccg::grid_range(key, grids[i]));
|
|
MutableSpan dst = r_factors.slice(bke::ccg::grid_range(key, i));
|
|
for (const int offset : dst.index_range()) {
|
|
dst[offset] = 1.0f - src[offset];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
r_factors.fill(1.0f);
|
|
}
|
|
|
|
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
|
|
if (!grid_hidden.is_empty()) {
|
|
for (const int i : grids.index_range()) {
|
|
const BitSpan hidden = grid_hidden[grids[i]];
|
|
const int start = i * key.grid_area;
|
|
for (const int offset : IndexRange(key.grid_area)) {
|
|
if (hidden[offset]) {
|
|
r_factors[start + offset] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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] *= std::max(dot, 0.0f);
|
|
}
|
|
}
|
|
|
|
void calc_front_face(const float3 &view_normal,
|
|
const Span<float3> normals,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(normals.size() == factors.size());
|
|
|
|
for (const int i : normals.index_range()) {
|
|
const float dot = math::dot(view_normal, normals[i]);
|
|
factors[i] *= std::max(dot, 0.0f);
|
|
}
|
|
}
|
|
void calc_front_face(const float3 &view_normal,
|
|
const SubdivCCG &subdiv_ccg,
|
|
const Span<int> grids,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
const Span<float3> normals = subdiv_ccg.normals;
|
|
BLI_assert(grids.size() * key.grid_area == factors.size());
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const Span<float3> grid_normals = normals.slice(bke::ccg::grid_range(key, grids[i]));
|
|
MutableSpan<float> grid_factors = factors.slice(bke::ccg::grid_range(key, i));
|
|
for (const int offset : grid_factors.index_range()) {
|
|
const float dot = math::dot(view_normal, grid_normals[offset]);
|
|
grid_factors[offset] *= std::max(dot, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void calc_front_face(const float3 &view_normal,
|
|
const Set<BMVert *, 0> &verts,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(verts.size() == factors.size());
|
|
|
|
int i = 0;
|
|
for (const BMVert *vert : verts) {
|
|
const float dot = math::dot(view_normal, float3(vert->no));
|
|
factors[i] *= std::max(dot, 0.0f);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void calc_front_face(const float3 &view_normal,
|
|
const Set<BMFace *, 0> &faces,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(faces.size() == factors.size());
|
|
|
|
int i = 0;
|
|
for (const BMFace *face : faces) {
|
|
const float dot = math::dot(view_normal, float3(face->no));
|
|
factors[i] *= std::max(dot, 0.0f);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void filter_region_clip_factors(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const Span<int> verts,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(verts.size() == factors.size());
|
|
|
|
const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d;
|
|
const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d;
|
|
if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
|
return;
|
|
}
|
|
|
|
const ePaintSymmetryFlags mirror_symmetry_pass = ss.cache ? ss.cache->mirror_symmetry_pass :
|
|
ePaintSymmetryFlags(0);
|
|
const int radial_symmetry_pass = ss.cache ? ss.cache->radial_symmetry_pass : 0;
|
|
const float4x4 symm_rot_mat_inv = ss.cache ? ss.cache->symm_rot_mat_inv : float4x4::identity();
|
|
for (const int i : verts.index_range()) {
|
|
float3 symm_co = symmetry_flip(positions[verts[i]], mirror_symmetry_pass);
|
|
if (radial_symmetry_pass) {
|
|
symm_co = math::transform_point(symm_rot_mat_inv, symm_co);
|
|
}
|
|
if (ED_view3d_clipping_test(rv3d, symm_co, true)) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_region_clip_factors(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(positions.size() == factors.size());
|
|
|
|
const RegionView3D *rv3d = ss.cache ? ss.cache->vc->rv3d : ss.rv3d;
|
|
const View3D *v3d = ss.cache ? ss.cache->vc->v3d : ss.v3d;
|
|
if (!RV3D_CLIPPING_ENABLED(v3d, rv3d)) {
|
|
return;
|
|
}
|
|
|
|
const ePaintSymmetryFlags mirror_symmetry_pass = ss.cache ? ss.cache->mirror_symmetry_pass :
|
|
ePaintSymmetryFlags(0);
|
|
const int radial_symmetry_pass = ss.cache ? ss.cache->radial_symmetry_pass : 0;
|
|
const float4x4 symm_rot_mat_inv = ss.cache ? ss.cache->symm_rot_mat_inv : float4x4::identity();
|
|
for (const int i : positions.index_range()) {
|
|
float3 symm_co = symmetry_flip(positions[i], mirror_symmetry_pass);
|
|
if (radial_symmetry_pass) {
|
|
symm_co = math::transform_point(symm_rot_mat_inv, symm_co);
|
|
}
|
|
if (ED_view3d_clipping_test(rv3d, symm_co, true)) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void calc_brush_distances_squared(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const Span<int> verts,
|
|
const eBrushFalloffShape falloff_shape,
|
|
const MutableSpan<float> r_distances)
|
|
{
|
|
BLI_assert(verts.size() == r_distances.size());
|
|
|
|
const float3 &test_location = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE && (ss.cache || ss.filter_cache)) {
|
|
/* The tube falloff shape requires the cached view normal. */
|
|
const float3 &view_normal = ss.cache ? ss.cache->view_normal_symm :
|
|
ss.filter_cache->view_normal;
|
|
float4 test_plane;
|
|
plane_from_point_normal_v3(test_plane, test_location, view_normal);
|
|
for (const int i : verts.index_range()) {
|
|
float3 projected;
|
|
closest_to_plane_normalized_v3(projected, test_plane, positions[verts[i]]);
|
|
r_distances[i] = math::distance_squared(projected, test_location);
|
|
}
|
|
}
|
|
else {
|
|
for (const int i : verts.index_range()) {
|
|
r_distances[i] = math::distance_squared(test_location, positions[verts[i]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void calc_brush_distances(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const Span<int> verts,
|
|
const eBrushFalloffShape falloff_shape,
|
|
const MutableSpan<float> r_distances)
|
|
{
|
|
calc_brush_distances_squared(ss, positions, verts, falloff_shape, r_distances);
|
|
for (float &value : r_distances) {
|
|
value = std::sqrt(value);
|
|
}
|
|
}
|
|
|
|
void calc_brush_distances_squared(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const eBrushFalloffShape falloff_shape,
|
|
const MutableSpan<float> r_distances)
|
|
{
|
|
BLI_assert(positions.size() == r_distances.size());
|
|
|
|
const float3 &test_location = ss.cache ? ss.cache->location_symm : ss.cursor_location;
|
|
if (falloff_shape == PAINT_FALLOFF_SHAPE_TUBE && (ss.cache || ss.filter_cache)) {
|
|
/* The tube falloff shape requires the cached view normal. */
|
|
const float3 &view_normal = ss.cache ? ss.cache->view_normal_symm :
|
|
ss.filter_cache->view_normal;
|
|
float4 test_plane;
|
|
plane_from_point_normal_v3(test_plane, test_location, view_normal);
|
|
for (const int i : positions.index_range()) {
|
|
float3 projected;
|
|
closest_to_plane_normalized_v3(projected, test_plane, positions[i]);
|
|
r_distances[i] = math::distance_squared(projected, test_location);
|
|
}
|
|
}
|
|
else {
|
|
for (const int i : positions.index_range()) {
|
|
r_distances[i] = math::distance_squared(test_location, positions[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void calc_brush_distances(const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const eBrushFalloffShape falloff_shape,
|
|
const MutableSpan<float> r_distances)
|
|
{
|
|
calc_brush_distances_squared(ss, positions, falloff_shape, r_distances);
|
|
for (float &value : r_distances) {
|
|
value = std::sqrt(value);
|
|
}
|
|
}
|
|
|
|
void filter_distances_with_radius(const float radius,
|
|
const Span<float> distances,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
for (const int i : distances.index_range()) {
|
|
if (distances[i] >= radius) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void calc_brush_cube_distances(const Brush &brush,
|
|
const Span<T> positions,
|
|
const MutableSpan<float> r_distances)
|
|
{
|
|
BLI_assert(r_distances.size() == positions.size());
|
|
|
|
const float roundness = brush.tip_roundness;
|
|
const float roundness_rcp = math::safe_rcp(roundness);
|
|
const float hardness = 1.0f - roundness;
|
|
|
|
for (const int i : positions.index_range()) {
|
|
const T local = math::abs(positions[i]);
|
|
|
|
if (math::reduce_max(local) > 1.0f) {
|
|
r_distances[i] = std::numeric_limits<float>::max();
|
|
continue;
|
|
}
|
|
if (std::min(local.x, local.y) > hardness) {
|
|
/* Corner, distance to the center of the corner circle. */
|
|
r_distances[i] = math::distance(float2(hardness), float2(local)) * roundness_rcp;
|
|
continue;
|
|
}
|
|
if (std::max(local.x, local.y) > hardness) {
|
|
/* Side, distance to the square XY axis. */
|
|
r_distances[i] = (std::max(local.x, local.y) - hardness) * roundness_rcp;
|
|
continue;
|
|
}
|
|
|
|
/* Inside the square, constant distance. */
|
|
r_distances[i] = 0.0f;
|
|
}
|
|
}
|
|
template void calc_brush_cube_distances<float2>(const Brush &brush,
|
|
const Span<float2> positions,
|
|
MutableSpan<float> r_distances);
|
|
template void calc_brush_cube_distances<float3>(const Brush &brush,
|
|
const Span<float3> positions,
|
|
MutableSpan<float> r_distances);
|
|
|
|
void apply_hardness_to_distances(const float radius,
|
|
const float hardness,
|
|
const MutableSpan<float> distances)
|
|
{
|
|
if (hardness == 0.0f) {
|
|
return;
|
|
}
|
|
const float threshold = hardness * radius;
|
|
if (hardness == 1.0f) {
|
|
for (const int i : distances.index_range()) {
|
|
distances[i] = distances[i] < threshold ? 0.0f : radius;
|
|
}
|
|
return;
|
|
}
|
|
const float radius_inv = math::rcp(radius);
|
|
const float hardness_inv_rcp = math::rcp(1.0f - hardness);
|
|
for (const int i : distances.index_range()) {
|
|
if (distances[i] < threshold) {
|
|
distances[i] = 0.0f;
|
|
}
|
|
else {
|
|
const float radius_factor = (distances[i] * radius_inv - hardness) * hardness_inv_rcp;
|
|
distances[i] = radius_factor * radius;
|
|
}
|
|
}
|
|
}
|
|
|
|
void calc_brush_strength_factors(const StrokeCache &cache,
|
|
const Brush &brush,
|
|
const Span<float> distances,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BKE_brush_calc_curve_factors(
|
|
eBrushCurvePreset(brush.curve_preset), brush.curve, distances, cache.radius, factors);
|
|
}
|
|
|
|
void calc_brush_texture_factors(const 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()) {
|
|
if (factors[i] == 0.0f) {
|
|
continue;
|
|
}
|
|
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 calc_brush_texture_factors(const SculptSession &ss,
|
|
const Brush &brush,
|
|
const Span<float3> positions,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(positions.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 : positions.index_range()) {
|
|
if (factors[i] == 0.0f) {
|
|
continue;
|
|
}
|
|
float texture_value;
|
|
float4 texture_rgba;
|
|
/* NOTE: This is not a thread-safe call. */
|
|
sculpt_apply_texture(ss, brush, positions[i], thread_id, &texture_value, texture_rgba);
|
|
|
|
factors[i] *= texture_value;
|
|
}
|
|
}
|
|
|
|
void reset_translations_to_original(const MutableSpan<float3> translations,
|
|
const Span<float3> positions,
|
|
const Span<float3> orig_positions)
|
|
{
|
|
BLI_assert(translations.size() == orig_positions.size());
|
|
BLI_assert(translations.size() == positions.size());
|
|
for (const int i : translations.index_range()) {
|
|
const float3 prev_translation = positions[i] - orig_positions[i];
|
|
translations[i] -= prev_translation;
|
|
}
|
|
}
|
|
|
|
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_translations(const Span<float3> translations,
|
|
const Span<int> grids,
|
|
SubdivCCG &subdiv_ccg)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
MutableSpan<float3> positions = subdiv_ccg.positions;
|
|
BLI_assert(grids.size() * key.grid_area == translations.size());
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const Span<float3> grid_translations = translations.slice(bke::ccg::grid_range(key, i));
|
|
MutableSpan<float3> grid_positions = positions.slice(bke::ccg::grid_range(key, grids[i]));
|
|
for (const int offset : grid_positions.index_range()) {
|
|
grid_positions[offset] += grid_translations[offset];
|
|
}
|
|
}
|
|
}
|
|
|
|
void apply_translations(const Span<float3> translations, const Set<BMVert *, 0> &verts)
|
|
{
|
|
BLI_assert(verts.size() == translations.size());
|
|
|
|
int i = 0;
|
|
for (BMVert *vert : verts) {
|
|
add_v3_v3(vert->co, translations[i]);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void project_translations(const MutableSpan<float3> translations, const float3 &plane)
|
|
{
|
|
/* Equivalent to #project_plane_v3_v3v3. */
|
|
const float len_sq = math::length_squared(plane);
|
|
if (len_sq < std::numeric_limits<float>::epsilon()) {
|
|
return;
|
|
}
|
|
const float dot_factor = -math::rcp(len_sq);
|
|
for (const int i : translations.index_range()) {
|
|
translations[i] += plane * math::dot(translations[i], plane) * dot_factor;
|
|
}
|
|
}
|
|
|
|
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->mirror_modifier_clip.flag & (uint8_t(StrokeFlags::ClipX) << axis))) {
|
|
continue;
|
|
}
|
|
|
|
const float4x4 mirror(cache->mirror_modifier_clip.mat);
|
|
const float4x4 mirror_inverse(cache->mirror_modifier_clip.mat_inv);
|
|
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->mirror_modifier_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];
|
|
}
|
|
}
|
|
}
|
|
|
|
void clip_and_lock_translations(const Sculpt &sd,
|
|
const SculptSession &ss,
|
|
const Span<float3> positions,
|
|
const MutableSpan<float3> translations)
|
|
{
|
|
BLI_assert(positions.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->mirror_modifier_clip.flag & (uint8_t(StrokeFlags::ClipX) << axis))) {
|
|
continue;
|
|
}
|
|
|
|
const float4x4 mirror(cache->mirror_modifier_clip.mat);
|
|
const float4x4 mirror_inverse(cache->mirror_modifier_clip.mat_inv);
|
|
for (const int i : positions.index_range()) {
|
|
/* Transform into the space of the mirror plane, check translations, then transform back. */
|
|
float3 co_mirror = math::transform_point(mirror, positions[i]);
|
|
if (math::abs(co_mirror[axis]) > cache->mirror_modifier_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[i][axis];
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<ShapeKeyData> ShapeKeyData::from_object(Object &object)
|
|
{
|
|
Mesh &mesh = *static_cast<Mesh *>(object.data);
|
|
Key *keys = mesh.key;
|
|
if (!keys) {
|
|
return std::nullopt;
|
|
}
|
|
const int active_index = object.shapenr - 1;
|
|
const KeyBlock *active_key = BKE_keyblock_find_by_index(keys, active_index);
|
|
if (!active_key) {
|
|
return std::nullopt;
|
|
}
|
|
ShapeKeyData data;
|
|
data.active_key_data = {static_cast<float3 *>(active_key->data), active_key->totelem};
|
|
data.basis_key_active = active_key == keys->refkey;
|
|
if (const std::optional<Array<bool>> dependent = BKE_keyblock_get_dependent_keys(keys,
|
|
active_index))
|
|
{
|
|
int i;
|
|
LISTBASE_FOREACH_INDEX (KeyBlock *, other_key, &keys->block, i) {
|
|
if ((other_key != active_key) && (*dependent)[i]) {
|
|
data.dependent_keys.append({static_cast<float3 *>(other_key->data), other_key->totelem});
|
|
}
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
PositionDeformData::PositionDeformData(const Depsgraph &depsgraph, Object &object_orig)
|
|
{
|
|
Mesh &mesh = *static_cast<Mesh *>(object_orig.data);
|
|
this->eval = bke::pbvh::vert_positions_eval(depsgraph, object_orig);
|
|
|
|
if (!object_orig.sculpt->deform_imats.is_empty()) {
|
|
deform_imats_ = object_orig.sculpt->deform_imats;
|
|
}
|
|
orig_ = mesh.vert_positions_for_write();
|
|
|
|
MutableSpan eval_mut = bke::pbvh::vert_positions_eval_for_write(depsgraph, object_orig);
|
|
if (eval_mut.data() != orig_.data()) {
|
|
eval_mut_ = eval_mut;
|
|
}
|
|
|
|
shape_key_data_ = ShapeKeyData::from_object(object_orig);
|
|
}
|
|
|
|
void PositionDeformData::deform(MutableSpan<float3> translations, const Span<int> verts) const
|
|
{
|
|
if (eval_mut_) {
|
|
/* Apply translations to the evaluated mesh. This is necessary because multiple brush
|
|
* evaluations can happen in between object reevaluations (otherwise just deforming the
|
|
* original positions would be enough). */
|
|
apply_translations(translations, verts, *eval_mut_);
|
|
}
|
|
|
|
if (deform_imats_) {
|
|
/* Apply the reverse procedural deformation, since subsequent translation happens to the state
|
|
* from "before" deforming modifiers. */
|
|
apply_crazyspace_to_translations(*deform_imats_, verts, translations);
|
|
}
|
|
|
|
if (shape_key_data_) {
|
|
if (!shape_key_data_->dependent_keys.is_empty()) {
|
|
for (MutableSpan<float3> data : shape_key_data_->dependent_keys) {
|
|
apply_translations(translations, verts, data);
|
|
}
|
|
}
|
|
|
|
if (shape_key_data_->basis_key_active) {
|
|
/* The basis key positions and the mesh positions are always kept in sync. */
|
|
apply_translations(translations, verts, orig_);
|
|
}
|
|
apply_translations(translations, verts, shape_key_data_->active_key_data);
|
|
}
|
|
else {
|
|
apply_translations(translations, verts, orig_);
|
|
}
|
|
}
|
|
|
|
void scale_translations(const MutableSpan<float3> translations, const Span<float> factors)
|
|
{
|
|
for (const int i : translations.index_range()) {
|
|
translations[i] *= factors[i];
|
|
}
|
|
}
|
|
|
|
void scale_translations(const MutableSpan<float3> translations, const float factor)
|
|
{
|
|
if (factor == 1.0f) {
|
|
return;
|
|
}
|
|
for (const int i : translations.index_range()) {
|
|
translations[i] *= factor;
|
|
}
|
|
}
|
|
|
|
void scale_factors(const MutableSpan<float> factors, const float strength)
|
|
{
|
|
if (strength == 1.0f) {
|
|
return;
|
|
}
|
|
for (float &factor : factors) {
|
|
factor *= strength;
|
|
}
|
|
}
|
|
|
|
void scale_factors(const MutableSpan<float> factors, const Span<float> strengths)
|
|
{
|
|
BLI_assert(factors.size() == strengths.size());
|
|
|
|
for (const int i : factors.index_range()) {
|
|
factors[i] *= strengths[i];
|
|
}
|
|
}
|
|
|
|
void translations_from_offset_and_factors(const float3 &offset,
|
|
const Span<float> factors,
|
|
const MutableSpan<float3> r_translations)
|
|
{
|
|
BLI_assert(r_translations.size() == factors.size());
|
|
|
|
for (const int i : factors.index_range()) {
|
|
r_translations[i] = offset * factors[i];
|
|
}
|
|
}
|
|
|
|
void translations_from_new_positions(const Span<float3> new_positions,
|
|
const Span<int> verts,
|
|
const Span<float3> old_positions,
|
|
const MutableSpan<float3> translations)
|
|
{
|
|
BLI_assert(new_positions.size() == verts.size());
|
|
for (const int i : verts.index_range()) {
|
|
translations[i] = new_positions[i] - old_positions[verts[i]];
|
|
}
|
|
}
|
|
|
|
void translations_from_new_positions(const Span<float3> new_positions,
|
|
const Span<float3> old_positions,
|
|
const MutableSpan<float3> translations)
|
|
{
|
|
BLI_assert(new_positions.size() == old_positions.size());
|
|
for (const int i : new_positions.index_range()) {
|
|
translations[i] = new_positions[i] - old_positions[i];
|
|
}
|
|
}
|
|
|
|
void transform_positions(const Span<float3> src,
|
|
const float4x4 &transform,
|
|
const MutableSpan<float3> dst)
|
|
{
|
|
BLI_assert(src.size() == dst.size());
|
|
|
|
for (const int i : src.index_range()) {
|
|
dst[i] = math::transform_point(transform, src[i]);
|
|
}
|
|
}
|
|
|
|
void transform_positions(const float4x4 &transform, const MutableSpan<float3> positions)
|
|
{
|
|
for (const int i : positions.index_range()) {
|
|
positions[i] = math::transform_point(transform, positions[i]);
|
|
}
|
|
}
|
|
|
|
OffsetIndices<int> create_node_vert_offsets(const Span<bke::pbvh::MeshNode> nodes,
|
|
const IndexMask &node_mask,
|
|
Array<int> &node_data)
|
|
{
|
|
node_data.reinitialize(node_mask.size() + 1);
|
|
node_mask.foreach_index(
|
|
[&](const int i, const int pos) { node_data[pos] = nodes[i].verts().size(); });
|
|
return offset_indices::accumulate_counts_to_offsets(node_data);
|
|
}
|
|
|
|
OffsetIndices<int> create_node_vert_offsets(const CCGKey &key,
|
|
const Span<bke::pbvh::GridsNode> nodes,
|
|
const IndexMask &node_mask,
|
|
Array<int> &node_data)
|
|
{
|
|
node_data.reinitialize(node_mask.size() + 1);
|
|
node_mask.foreach_index([&](const int i, const int pos) {
|
|
node_data[pos] = nodes[i].grids().size() * key.grid_area;
|
|
});
|
|
return offset_indices::accumulate_counts_to_offsets(node_data);
|
|
}
|
|
|
|
OffsetIndices<int> create_node_vert_offsets_bmesh(const Span<bke::pbvh::BMeshNode> nodes,
|
|
const IndexMask &node_mask,
|
|
Array<int> &node_data)
|
|
{
|
|
node_data.reinitialize(node_mask.size() + 1);
|
|
node_mask.foreach_index([&](const int i, const int pos) {
|
|
node_data[pos] =
|
|
BKE_pbvh_bmesh_node_unique_verts(const_cast<bke::pbvh::BMeshNode *>(&nodes[i])).size();
|
|
});
|
|
return offset_indices::accumulate_counts_to_offsets(node_data);
|
|
}
|
|
|
|
GroupedSpan<int> calc_vert_neighbors(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const Span<bool> hide_poly,
|
|
const Span<int> verts,
|
|
Vector<int> &r_offset_data,
|
|
Vector<int> &r_data)
|
|
{
|
|
BLI_assert(corner_verts.size() == faces.total_size());
|
|
r_offset_data.resize(verts.size() + 1);
|
|
r_data.clear();
|
|
for (const int i : verts.index_range()) {
|
|
r_offset_data[i] = r_data.size();
|
|
append_neighbors_to_vector(faces, corner_verts, vert_to_face, hide_poly, verts[i], r_data);
|
|
}
|
|
r_offset_data.last() = r_data.size();
|
|
return GroupedSpan<int>(r_offset_data.as_span(), r_data.as_span());
|
|
}
|
|
|
|
GroupedSpan<int> calc_vert_neighbors(const SubdivCCG &subdiv_ccg,
|
|
const Span<int> grids,
|
|
Vector<int> &r_offset_data,
|
|
Vector<int> &r_data)
|
|
{
|
|
const CCGKey key = BKE_subdiv_ccg_key_top_level(subdiv_ccg);
|
|
SubdivCCGNeighbors neighbors;
|
|
|
|
r_offset_data.resize(key.grid_area * grids.size() + 1);
|
|
r_data.clear();
|
|
|
|
for (const int i : grids.index_range()) {
|
|
const int grid = grids[i];
|
|
const int node_verts_start = i * key.grid_area;
|
|
r_offset_data[node_verts_start] = r_data.size();
|
|
|
|
for (const short y : IndexRange(key.grid_size)) {
|
|
for (const short x : IndexRange(key.grid_size)) {
|
|
SubdivCCGCoord coord{};
|
|
coord.grid_index = grid;
|
|
coord.x = x;
|
|
coord.y = y;
|
|
BKE_subdiv_ccg_neighbor_coords_get(subdiv_ccg, coord, false, neighbors);
|
|
for (const SubdivCCGCoord neighbor : neighbors.coords) {
|
|
r_data.append(neighbor.to_index(key));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return GroupedSpan<int>(r_offset_data.as_span(), r_data.as_span());
|
|
}
|
|
|
|
GroupedSpan<BMVert *> calc_vert_neighbors(Set<BMVert *, 0> verts,
|
|
Vector<int> &r_offset_data,
|
|
Vector<BMVert *> &r_data)
|
|
{
|
|
r_offset_data.resize(verts.size() + 1);
|
|
r_data.clear();
|
|
|
|
BMeshNeighborVerts neighbor_data;
|
|
int i = 0;
|
|
for (BMVert *vert : verts) {
|
|
r_offset_data[i] = r_data.size();
|
|
r_data.extend(vert_neighbors_get_bmesh(*vert, neighbor_data));
|
|
i++;
|
|
}
|
|
r_offset_data.last() = r_data.size();
|
|
return GroupedSpan<BMVert *>(r_offset_data.as_span(), r_data.as_span());
|
|
}
|
|
|
|
template<bool use_factors>
|
|
static GroupedSpan<int> calc_vert_neighbors_interior_impl(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const BitSpan boundary_verts,
|
|
const Span<bool> hide_poly,
|
|
const Span<int> verts,
|
|
const Span<float> factors,
|
|
Vector<int> &r_offset_data,
|
|
Vector<int> &r_data)
|
|
{
|
|
BLI_assert(corner_verts.size() == faces.total_size());
|
|
if constexpr (use_factors) {
|
|
BLI_assert(verts.size() == factors.size());
|
|
}
|
|
|
|
r_offset_data.resize(verts.size() + 1);
|
|
r_data.clear();
|
|
|
|
for (const int i : verts.index_range()) {
|
|
const int vert = verts[i];
|
|
const int vert_start = r_data.size();
|
|
r_offset_data[i] = vert_start;
|
|
if constexpr (use_factors) {
|
|
if (factors[i] == 0.0f) {
|
|
continue;
|
|
}
|
|
}
|
|
append_neighbors_to_vector(faces, corner_verts, vert_to_face, hide_poly, vert, r_data);
|
|
|
|
if (boundary_verts[vert]) {
|
|
/* Do not include neighbors of corner vertices. */
|
|
if (r_data.size() == vert_start + 2) {
|
|
r_data.resize(vert_start);
|
|
}
|
|
else {
|
|
/* Only include other boundary vertices as neighbors of boundary vertices. */
|
|
for (int neighbor_i = r_data.size() - 1; neighbor_i >= vert_start; neighbor_i--) {
|
|
if (!boundary_verts[r_data[neighbor_i]]) {
|
|
r_data.remove_and_reorder(neighbor_i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
r_offset_data.last() = r_data.size();
|
|
return GroupedSpan<int>(r_offset_data.as_span(), r_data.as_span());
|
|
}
|
|
|
|
GroupedSpan<int> calc_vert_neighbors_interior(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const BitSpan boundary_verts,
|
|
const Span<bool> hide_poly,
|
|
const Span<int> verts,
|
|
const Span<float> factors,
|
|
Vector<int> &r_offset_data,
|
|
Vector<int> &r_data)
|
|
{
|
|
return calc_vert_neighbors_interior_impl<true>(faces,
|
|
corner_verts,
|
|
vert_to_face,
|
|
boundary_verts,
|
|
hide_poly,
|
|
verts,
|
|
factors,
|
|
r_offset_data,
|
|
r_data);
|
|
}
|
|
|
|
GroupedSpan<int> calc_vert_neighbors_interior(const OffsetIndices<int> faces,
|
|
const Span<int> corner_verts,
|
|
const GroupedSpan<int> vert_to_face,
|
|
const BitSpan boundary_verts,
|
|
const Span<bool> hide_poly,
|
|
const Span<int> verts,
|
|
Vector<int> &r_offset_data,
|
|
Vector<int> &r_data)
|
|
{
|
|
return calc_vert_neighbors_interior_impl<false>(faces,
|
|
corner_verts,
|
|
vert_to_face,
|
|
boundary_verts,
|
|
hide_poly,
|
|
verts,
|
|
{},
|
|
r_offset_data,
|
|
r_data);
|
|
}
|
|
|
|
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());
|
|
BMeshNeighborVerts neighbor_data;
|
|
|
|
int i = 0;
|
|
for (BMVert *vert : verts) {
|
|
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,
|
|
const MutableSpan<float3> translations)
|
|
{
|
|
for (const int i : verts.index_range()) {
|
|
const float3 &position = vert_positions[verts[i]];
|
|
float3 closest;
|
|
closest_to_plane_normalized_v3(closest, plane, position);
|
|
translations[i] = closest - position;
|
|
}
|
|
}
|
|
|
|
void calc_translations_to_plane(const Span<float3> positions,
|
|
const float4 &plane,
|
|
const MutableSpan<float3> translations)
|
|
{
|
|
for (const int i : positions.index_range()) {
|
|
const float3 &position = positions[i];
|
|
float3 closest;
|
|
closest_to_plane_normalized_v3(closest, plane, position);
|
|
translations[i] = closest - position;
|
|
}
|
|
}
|
|
|
|
void filter_verts_outside_symmetry_area(const Span<float3> positions,
|
|
const float3 &pivot,
|
|
const ePaintSymmetryFlags symm,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
BLI_assert(positions.size() == factors.size());
|
|
|
|
for (const int i : positions.index_range()) {
|
|
if (!SCULPT_check_vertex_pivot_symmetry(positions[i], pivot, symm)) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_plane_trim_limit_factors(const Brush &brush,
|
|
const StrokeCache &cache,
|
|
const Span<float3> translations,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
if (!(brush.flag & BRUSH_PLANE_TRIM)) {
|
|
return;
|
|
}
|
|
const float threshold = cache.radius_squared * cache.plane_trim_squared;
|
|
for (const int i : translations.index_range()) {
|
|
if (math::length_squared(translations[i]) > threshold) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_below_plane_factors(const Span<float3> vert_positions,
|
|
const Span<int> verts,
|
|
const float4 &plane,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
for (const int i : verts.index_range()) {
|
|
if (plane_point_side_v3(plane, vert_positions[verts[i]]) <= 0.0f) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_below_plane_factors(const Span<float3> positions,
|
|
const float4 &plane,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
for (const int i : positions.index_range()) {
|
|
if (plane_point_side_v3(plane, positions[i]) <= 0.0f) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_above_plane_factors(const Span<float3> vert_positions,
|
|
const Span<int> verts,
|
|
const float4 &plane,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
for (const int i : verts.index_range()) {
|
|
if (plane_point_side_v3(plane, vert_positions[verts[i]]) > 0.0f) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void filter_above_plane_factors(const Span<float3> positions,
|
|
const float4 &plane,
|
|
const MutableSpan<float> factors)
|
|
{
|
|
for (const int i : positions.index_range()) {
|
|
if (plane_point_side_v3(plane, positions[i]) > 0.0f) {
|
|
factors[i] = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint
|