Cleanup: Simplify sculpt affected node gathering

- Move functions to C++ namespace
- Use two functions with simpler responsibilities instead
- Use C++ math functions
- Remove arguments structs left over from before C++ transition
- Return ray distance precalculation by value
This commit is contained in:
Hans Goudey
2024-01-12 14:45:33 -05:00
parent daa0219a57
commit 1b6d93d16b
8 changed files with 150 additions and 218 deletions

View File

@@ -2368,16 +2368,13 @@ void clip_ray_ortho(
/* -------------------------------------------------------------------- */
struct FindNearestRayData {
DistRayAABB_Precalc dist_ray_to_aabb_precalc;
bool original;
};
static bool nearest_to_ray_aabb_dist_sq(PBVHNode *node, FindNearestRayData *rcd)
static bool nearest_to_ray_aabb_dist_sq(PBVHNode *node,
const DistRayAABB_Precalc &dist_ray_to_aabb_precalc,
const bool original)
{
const float *bb_min, *bb_max;
if (rcd->original) {
if (original) {
/* BKE_pbvh_node_get_original_BB */
bb_min = node->orig_vb.min;
bb_max = node->orig_vb.max;
@@ -2390,7 +2387,7 @@ static bool nearest_to_ray_aabb_dist_sq(PBVHNode *node, FindNearestRayData *rcd)
float co_dummy[3], depth;
node->tmin = dist_squared_ray_to_aabb_v3(
&rcd->dist_ray_to_aabb_precalc, bb_min, bb_max, co_dummy, &depth);
&dist_ray_to_aabb_precalc, bb_min, bb_max, co_dummy, &depth);
/* Ideally we would skip distances outside the range. */
return depth > 0.0f;
}
@@ -2399,15 +2396,17 @@ void find_nearest_to_ray(PBVH *pbvh,
const FunctionRef<void(PBVHNode &node, float *tmin)> fn,
const float ray_start[3],
const float ray_normal[3],
bool original)
const bool original)
{
FindNearestRayData ncd;
dist_squared_ray_to_aabb_v3_precalc(&ncd.dist_ray_to_aabb_precalc, ray_start, ray_normal);
ncd.original = original;
const DistRayAABB_Precalc ray_dist_precalc = dist_squared_ray_to_aabb_v3_precalc(ray_start,
ray_normal);
search_callback_occluded(
pbvh, [&](PBVHNode &node) { return nearest_to_ray_aabb_dist_sq(&node, &ncd); }, fn);
pbvh,
[&](PBVHNode &node) {
return nearest_to_ray_aabb_dist_sq(&node, ray_dist_precalc, original);
},
fn);
}
static bool pbvh_faces_node_nearest_to_ray(PBVH *pbvh,

View File

@@ -250,9 +250,8 @@ struct DistRayAABB_Precalc {
float ray_direction[3];
float ray_inv_dir[3];
};
void dist_squared_ray_to_aabb_v3_precalc(struct DistRayAABB_Precalc *neasrest_precalc,
const float ray_origin[3],
const float ray_direction[3]);
struct DistRayAABB_Precalc dist_squared_ray_to_aabb_v3_precalc(const float ray_origin[3],
const float ray_direction[3]);
/**
* Returns the distance from a ray to a bound-box (projected on ray)
*/

View File

@@ -655,18 +655,19 @@ void aabb_get_near_far_from_plane(const float plane_no[3],
/** \name dist_squared_to_ray_to_aabb and helpers
* \{ */
void dist_squared_ray_to_aabb_v3_precalc(DistRayAABB_Precalc *neasrest_precalc,
const float ray_origin[3],
const float ray_direction[3])
DistRayAABB_Precalc dist_squared_ray_to_aabb_v3_precalc(const float ray_origin[3],
const float ray_direction[3])
{
copy_v3_v3(neasrest_precalc->ray_origin, ray_origin);
copy_v3_v3(neasrest_precalc->ray_direction, ray_direction);
DistRayAABB_Precalc nearest_precalc{};
copy_v3_v3(nearest_precalc.ray_origin, ray_origin);
copy_v3_v3(nearest_precalc.ray_direction, ray_direction);
for (int i = 0; i < 3; i++) {
neasrest_precalc->ray_inv_dir[i] = (neasrest_precalc->ray_direction[i] != 0.0f) ?
(1.0f / neasrest_precalc->ray_direction[i]) :
FLT_MAX;
nearest_precalc.ray_inv_dir[i] = (nearest_precalc.ray_direction[i] != 0.0f) ?
(1.0f / nearest_precalc.ray_direction[i]) :
FLT_MAX;
}
return nearest_precalc;
}
float dist_squared_ray_to_aabb_v3(const DistRayAABB_Precalc *data,
@@ -765,8 +766,7 @@ float dist_squared_ray_to_aabb_v3_simple(const float ray_origin[3],
float r_point[3],
float *r_depth)
{
DistRayAABB_Precalc data;
dist_squared_ray_to_aabb_v3_precalc(&data, ray_origin, ray_direction);
const DistRayAABB_Precalc data = dist_squared_ray_to_aabb_v3_precalc(ray_origin, ray_direction);
return dist_squared_ray_to_aabb_v3(&data, bb_min, bb_max, r_point, r_depth);
}

View File

@@ -282,14 +282,9 @@ Vector<PBVHNode *> pbvh_gather_generic(Object *ob, VPaint *wp, Sculpt *sd, Brush
/* Build a list of all nodes that are potentially within the brush's area of influence */
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
SculptSearchSphereData data = {nullptr};
data.ss = ss;
data.sd = sd;
data.radius_squared = ss->cache->radius_squared;
data.original = true;
nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
nodes = bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_sphere(node, ss->cache->location, ss->cache->radius_squared, true);
});
if (use_normal) {
SCULPT_pbvh_calc_area_normal(brush, ob, nodes, ss->cache->sculpt_normal_symm);
@@ -299,18 +294,12 @@ Vector<PBVHNode *> pbvh_gather_generic(Object *ob, VPaint *wp, Sculpt *sd, Brush
}
}
else {
DistRayAABB_Precalc dist_ray_to_aabb_precalc;
dist_squared_ray_to_aabb_v3_precalc(
&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal);
SculptSearchCircleData data = {nullptr};
data.ss = ss;
data.sd = sd;
data.radius_squared = ss->cache->radius_squared;
data.original = true;
data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc;
nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_circle(&node, &data); });
const DistRayAABB_Precalc ray_dist_precalc = dist_squared_ray_to_aabb_v3_precalc(
ss->cache->location, ss->cache->view_normal);
nodes = bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_cylinder(
ray_dist_precalc, node, ss->cache->location, ss->cache->radius_squared, true);
});
if (use_normal) {
copy_v3_v3(ss->cache->sculpt_normal_symm, ss->cache->view_normal);

View File

@@ -906,22 +906,18 @@ PBVHVertRef SCULPT_nearest_vertex_get(
Sculpt *sd, Object *ob, const float co[3], float max_distance, bool use_original)
{
using namespace blender;
using namespace blender::ed::sculpt_paint;
SculptSession *ss = ob->sculpt;
SculptSearchSphereData data{};
data.sd = sd;
data.radius_squared = max_distance * max_distance;
data.original = use_original;
data.center = co;
const float max_distance_sq = max_distance * max_distance;
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_sphere(node, co, max_distance_sq, use_original);
});
if (nodes.is_empty()) {
return BKE_pbvh_make_vref(PBVH_REF_NONE);
}
const float max_distance_sq = max_distance * max_distance;
return threading::parallel_reduce(
nodes.index_range(),
1,
@@ -2481,69 +2477,49 @@ void SCULPT_calc_vertex_displacement(SculptSession *ss,
flip_v3_v3(r_offset, rgba, ss->cache->mirror_symmetry_pass);
}
bool SCULPT_search_sphere(PBVHNode *node, SculptSearchSphereData *data)
namespace blender::ed::sculpt_paint {
bool node_fully_masked_or_hidden(const PBVHNode &node)
{
using namespace blender;
const float *center;
float nearest[3];
if (data->center) {
center = data->center;
if (BKE_pbvh_node_fully_hidden_get(&node)) {
return true;
}
else {
center = data->ss->cache ? data->ss->cache->location : data->ss->cursor_location;
if (BKE_pbvh_node_fully_masked_get(&node)) {
return true;
}
float t[3];
if (data->ignore_fully_ineffective) {
if (BKE_pbvh_node_fully_hidden_get(node)) {
return false;
}
if (BKE_pbvh_node_fully_masked_get(node)) {
return false;
}
}
const Bounds<float3> bounds = (data->original) ? BKE_pbvh_node_get_original_BB(node) :
BKE_pbvh_node_get_BB(node);
for (int i = 0; i < 3; i++) {
if (bounds.min[i] > center[i]) {
nearest[i] = bounds.min[i];
}
else if (bounds.max[i] < center[i]) {
nearest[i] = bounds.max[i];
}
else {
nearest[i] = center[i];
}
}
sub_v3_v3v3(t, center, nearest);
return len_squared_v3(t) < data->radius_squared;
return false;
}
bool SCULPT_search_circle(PBVHNode *node, SculptSearchCircleData *data)
bool node_in_sphere(const PBVHNode &node,
const float3 &location,
const float radius_sq,
const bool original)
{
using namespace blender;
if (data->ignore_fully_ineffective) {
if (BKE_pbvh_node_fully_masked_get(node)) {
return false;
}
}
const Bounds<float3> bounds = original ? BKE_pbvh_node_get_original_BB(&node) :
BKE_pbvh_node_get_BB(&node);
const float3 nearest = math::clamp(location, bounds.min, bounds.max);
return math::distance_squared(location, nearest) < radius_sq;
}
const Bounds<float3> bounds = (data->original) ? BKE_pbvh_node_get_original_BB(node) :
BKE_pbvh_node_get_BB(node);
bool node_in_cylinder(const DistRayAABB_Precalc &ray_dist_precalc,
const PBVHNode &node,
const float3 &location,
float radius_sq,
bool original)
{
const Bounds<float3> bounds = (original) ? BKE_pbvh_node_get_original_BB(&node) :
BKE_pbvh_node_get_BB(&node);
float dummy_co[3], dummy_depth;
const float dist_sq = dist_squared_ray_to_aabb_v3(
data->dist_ray_to_aabb_precalc, bounds.min, bounds.max, dummy_co, &dummy_depth);
&ray_dist_precalc, bounds.min, bounds.max, dummy_co, &dummy_depth);
/* Seems like debug code.
* Maybe this function can just return true if the node is not fully masked. */
return dist_sq < data->radius_squared || true;
/* TODO: Solve issues and enable distance check. */
return dist_sq < radius_sq || true;
}
} // namespace blender::ed::sculpt_paint
void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3])
{
for (int i = 0; i < 3; i++) {
@@ -2575,23 +2551,20 @@ void SCULPT_clip(Sculpt *sd, SculptSession *ss, float co[3], const float val[3])
}
}
namespace blender::ed::sculpt_paint {
static Vector<PBVHNode *> sculpt_pbvh_gather_cursor_update(Object *ob,
Sculpt *sd,
bool use_original)
{
SculptSession *ss = ob->sculpt;
SculptSearchSphereData data{};
data.ss = ss;
data.sd = sd;
data.radius_squared = ss->cursor_radius;
data.original = use_original;
data.ignore_fully_ineffective = false;
data.center = nullptr;
return blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
const float3 center = ss->cache ? ss->cache->location : ss->cursor_location;
return bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &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 Vector<PBVHNode *> sculpt_pbvh_gather_generic_intern(Object *ob,
Sculpt *sd,
const Brush *brush,
@@ -2600,43 +2573,44 @@ static Vector<PBVHNode *> sculpt_pbvh_gather_generic_intern(Object *ob,
PBVHNodeFlags flag)
{
SculptSession *ss = ob->sculpt;
Vector<PBVHNode *> nodes;
PBVHNodeFlags leaf_flag = PBVH_Leaf;
PBVHNodeFlags leaf_flag = PBVH_Leaf;
if (flag & PBVH_TexLeaf) {
leaf_flag = PBVH_TexLeaf;
}
/* Build a list of all nodes that are potentially within the cursor or brush's area of influence.
*/
if (brush->falloff_shape == PAINT_FALLOFF_SHAPE_SPHERE) {
SculptSearchSphereData data{};
data.ss = ss;
data.sd = sd;
data.radius_squared = square_f(ss->cache->radius * radius_scale);
data.original = use_original;
data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK;
data.center = nullptr;
nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); }, leaf_flag);
}
else {
DistRayAABB_Precalc dist_ray_to_aabb_precalc;
dist_squared_ray_to_aabb_v3_precalc(
&dist_ray_to_aabb_precalc, ss->cache->location, ss->cache->view_normal);
SculptSearchCircleData data{};
data.ss = ss;
data.sd = sd;
data.radius_squared = ss->cache ? square_f(ss->cache->radius * radius_scale) :
ss->cursor_radius;
data.original = use_original;
data.dist_ray_to_aabb_precalc = &dist_ray_to_aabb_precalc;
data.ignore_fully_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK;
nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_circle(&node, &data); }, leaf_flag);
const float3 center = ss->cache->location;
const float radius_sq = math::square(ss->cache->radius * radius_scale);
const bool ignore_ineffective = brush->sculpt_tool != SCULPT_TOOL_MASK;
switch (brush->falloff_shape) {
case PAINT_FALLOFF_SHAPE_SPHERE: {
return bke::pbvh::search_gather(
ss->pbvh,
[&](PBVHNode &node) {
if (ignore_ineffective && node_fully_masked_or_hidden(node)) {
return false;
}
return node_in_sphere(node, center, radius_sq, use_original);
},
leaf_flag);
}
case PAINT_FALLOFF_SHAPE_TUBE: {
const DistRayAABB_Precalc ray_dist_precalc = dist_squared_ray_to_aabb_v3_precalc(
center, ss->cache->view_normal);
return bke::pbvh::search_gather(
ss->pbvh,
[&](PBVHNode &node) {
if (ignore_ineffective && node_fully_masked_or_hidden(node)) {
return false;
}
return node_in_cylinder(ray_dist_precalc, node, center, radius_sq, use_original);
},
leaf_flag);
}
}
return nodes;
return {};
}
static Vector<PBVHNode *> sculpt_pbvh_gather_generic(
@@ -2687,7 +2661,7 @@ static void calc_sculpt_normal(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes, f
static void update_sculpt_normal(Sculpt *sd, Object *ob, Span<PBVHNode *> nodes)
{
const Brush *brush = BKE_paint_brush(&sd->paint);
blender::ed::sculpt_paint::StrokeCache *cache = ob->sculpt->cache;
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_tool == SCULPT_TOOL_GRAB) &&
@@ -2735,7 +2709,7 @@ static void calc_brush_local_mat(const float rotation,
float local_mat[4][4],
float local_mat_inv[4][4])
{
const blender::ed::sculpt_paint::StrokeCache *cache = ob->sculpt->cache;
const StrokeCache *cache = ob->sculpt->cache;
float tmat[4][4];
float mat[4][4];
float scale[4][4];
@@ -2795,6 +2769,8 @@ static void calc_brush_local_mat(const float rotation,
invert_m4_m4(local_mat, tmat);
}
} // namespace blender::ed::sculpt_paint
#define SCULPT_TILT_SENSITIVITY 0.7f
void SCULPT_tilt_apply_to_normal(float r_normal[3],
blender::ed::sculpt_paint::StrokeCache *cache,
@@ -2822,7 +2798,8 @@ void SCULPT_tilt_effective_normal_get(const SculptSession *ss, const Brush *brus
static void update_brush_local_mat(Sculpt *sd, Object *ob)
{
blender::ed::sculpt_paint::StrokeCache *cache = ob->sculpt->cache;
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(&sd->paint);
@@ -4909,6 +4886,7 @@ bool SCULPT_cursor_geometry_info_update(bContext *C,
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);
Sculpt *sd = scene->toolsettings->sculpt;
@@ -5899,23 +5877,20 @@ static PBVHVertRef SCULPT_fake_neighbor_search(Sculpt *sd,
float max_distance)
{
using namespace blender;
using namespace blender::ed::sculpt_paint;
SculptSession *ss = ob->sculpt;
SculptSearchSphereData data{};
data.ss = ss;
data.sd = sd;
data.radius_squared = max_distance * max_distance;
data.original = false;
data.center = SCULPT_vertex_co_get(ss, vertex);
const float3 center = SCULPT_vertex_co_get(ss, vertex);
const float max_distance_sq = max_distance * max_distance;
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_sphere(node, center, max_distance_sq, false);
});
if (nodes.is_empty()) {
return BKE_pbvh_make_vref(PBVH_REF_NONE);
}
const float3 nearest_vertex_search_co = SCULPT_vertex_co_get(ss, vertex);
const float max_distance_sq = max_distance * max_distance;
NearestVertexFakeNeighborData nvtd;
nvtd.nearest_vertex.i = -1;
@@ -6197,6 +6172,7 @@ void SCULPT_topology_islands_ensure(Object *ob)
void SCULPT_cube_tip_init(Sculpt * /*sd*/, Object *ob, Brush *brush, float mat[4][4])
{
using namespace blender::ed::sculpt_paint;
SculptSession *ss = ob->sculpt;
float scale[4][4];
float tmat[4][4];

View File

@@ -79,31 +79,25 @@ Vector<PBVHNode *> brush_affected_nodes_gather(SculptSession *ss, Brush *brush)
switch (brush->cloth_simulation_area_type) {
case BRUSH_CLOTH_SIMULATION_AREA_LOCAL: {
SculptSearchSphereData data{};
data.ss = ss;
data.radius_squared = square_f(ss->cache->initial_radius * (1.0 + brush->cloth_sim_limit));
data.original = false;
data.ignore_fully_ineffective = false;
data.center = ss->cache->initial_location;
return bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
const float radius_squared = math::square(ss->cache->initial_radius *
(1.0 + brush->cloth_sim_limit));
return bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_sphere(node, ss->cache->initial_location, radius_squared, false);
});
}
case BRUSH_CLOTH_SIMULATION_AREA_GLOBAL:
return bke::pbvh::search_gather(ss->pbvh, {});
case BRUSH_CLOTH_SIMULATION_AREA_DYNAMIC: {
SculptSearchSphereData data{};
data.ss = ss;
data.radius_squared = square_f(ss->cache->radius * (1.0 + brush->cloth_sim_limit));
data.original = false;
data.ignore_fully_ineffective = false;
data.center = ss->cache->location;
return bke::pbvh::search_gather(
ss->pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &data); });
const float radius_squared = math::square(ss->cache->radius *
(1.0 + brush->cloth_sim_limit));
return bke::pbvh::search_gather(ss->pbvh, [&](PBVHNode &node) {
return node_in_sphere(node, ss->cache->location, radius_squared, false);
});
}
}
BLI_assert_unreachable();
return Vector<PBVHNode *>();
return {};
}
bool is_cloth_deform_brush(const Brush *brush)

View File

@@ -13,6 +13,7 @@
#include "BLI_hash.h"
#include "BLI_index_range.hh"
#include "BLI_math_base.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
@@ -115,15 +116,8 @@ void cache_init(bContext *C,
BKE_pbvh_ensure_node_loops(ss->pbvh);
}
const float center[3] = {0.0f};
SculptSearchSphereData search_data{};
search_data.original = true;
search_data.center = center;
search_data.radius_squared = FLT_MAX;
search_data.ignore_fully_ineffective = true;
ss->filter_cache->nodes = bke::pbvh::search_gather(
pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &search_data); });
pbvh, [&](PBVHNode &node) { return !node_fully_masked_or_hidden(node); });
for (PBVHNode *node : ss->filter_cache->nodes) {
BKE_pbvh_node_mark_normals_update(node);
@@ -177,14 +171,10 @@ void cache_init(bContext *C,
radius = paint_calc_object_space_radius(&vc, co, float(ups->size) * area_normal_radius);
}
SculptSearchSphereData search_data2{};
search_data2.original = true;
search_data2.center = co;
search_data2.radius_squared = radius * radius;
search_data2.ignore_fully_ineffective = true;
nodes = bke::pbvh::search_gather(
pbvh, [&](PBVHNode &node) { return SCULPT_search_sphere(&node, &search_data2); });
const float radius_sq = math::square(radius);
nodes = bke::pbvh::search_gather(pbvh, [&](PBVHNode &node) {
return !node_fully_masked_or_hidden(node) && node_in_sphere(node, co, radius_sq, true);
});
if (BKE_paint_brush(&sd->paint) &&
SCULPT_pbvh_calc_area_normal(brush, ob, nodes, ss->filter_cache->initial_normal))

View File

@@ -269,25 +269,6 @@ struct SculptBrushTest {
using SculptBrushTestFn = bool (*)(SculptBrushTest *test, const float co[3]);
struct SculptSearchSphereData {
Sculpt *sd;
SculptSession *ss;
float radius_squared;
const float *center;
bool original;
/* This ignores fully masked and fully hidden nodes. */
bool ignore_fully_ineffective;
};
struct SculptSearchCircleData {
Sculpt *sd;
SculptSession *ss;
float radius_squared;
bool original;
bool ignore_fully_ineffective;
DistRayAABB_Precalc *dist_ray_to_aabb_precalc;
};
/* Sculpt Filters */
enum SculptFilterOrientation {
SCULPT_FILTER_ORIENTATION_LOCAL = 0,
@@ -1106,14 +1087,18 @@ bool SCULPT_brush_test_cube(SculptBrushTest *test,
const float roundness,
const float tip_scale_x);
bool SCULPT_brush_test_circle_sq(SculptBrushTest *test, const float co[3]);
/**
* Test AABB against sphere.
*/
bool SCULPT_search_sphere(PBVHNode *node, SculptSearchSphereData *data);
/**
* 2D projection (distance to line).
*/
bool SCULPT_search_circle(PBVHNode *node, SculptSearchCircleData *data);
namespace blender::ed::sculpt_paint {
bool node_fully_masked_or_hidden(const PBVHNode &node);
bool node_in_sphere(const PBVHNode &node, const float3 &location, float radius_sq, bool original);
bool node_in_cylinder(const DistRayAABB_Precalc &dist_ray_precalc,
const PBVHNode &node,
const float3 &location,
float radius_sq,
bool original);
}
void SCULPT_combine_transform_proxies(Sculpt *sd, Object *ob);