Curves: remove Test1 brush

This was one of multiple placeholder brushes to simplify development.
Having it is not necessary anymore.

It was a brush that could add new curves according to a specific density.
This functionality will be brought back as a new brush later.

Ref T97255.
This commit is contained in:
Jacques Lucke 2022-04-13 09:27:54 +02:00
parent d1418dd151
commit e0c8d0913b
Notes: blender-bot 2023-02-14 06:46:23 +01:00
Referenced by issue #97255, Remove remaining test curves brush.
3 changed files with 0 additions and 429 deletions

View File

@ -74,431 +74,6 @@ using blender::bke::CurvesGeometry;
/** \name * SCULPT_CURVES_OT_brush_stroke
* \{ */
class DensityAddOperation : public CurvesSculptStrokeOperation {
private:
/** Contains the root points of the curves that existed before this operation started. */
KDTree_3d *old_kdtree_ = nullptr;
/** Number of points in the kdtree above. */
int old_kdtree_size_ = 0;
/**
* Indicates that the corresponding curve has already been created and can't be changed by this
* operation anymore.
*/
static constexpr int ExistsAlreadyIndex = INT32_MAX;
struct NewPointsData {
Vector<float3> bary_coords;
Vector<int> looptri_indices;
Vector<float3> positions;
Vector<float3> normals;
};
public:
~DensityAddOperation() override
{
if (old_kdtree_ != nullptr) {
BLI_kdtree_3d_free(old_kdtree_);
}
}
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
Depsgraph &depsgraph = *CTX_data_depsgraph_pointer(C);
Scene &scene = *CTX_data_scene(C);
Object &object = *CTX_data_active_object(C);
ARegion *region = CTX_wm_region(C);
View3D *v3d = CTX_wm_view3d(C);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
if (curves_id.surface == nullptr || curves_id.surface->type != OB_MESH) {
return;
}
const Object &surface_ob = *curves_id.surface;
const Mesh &surface = *static_cast<const Mesh *>(surface_ob.data);
const float4x4 surface_ob_mat = surface_ob.obmat;
const float4x4 surface_ob_imat = surface_ob_mat.inverted();
ToolSettings &tool_settings = *scene.toolsettings;
CurvesSculpt &curves_sculpt = *tool_settings.curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius_screen = BKE_brush_size_get(&scene, &brush);
const float strength = BKE_brush_alpha_get(&scene, &brush);
const float minimum_distance = curves_sculpt.distance;
/* This is the main ray that is used to determine the brush position in 3D space. */
float3 ray_start, ray_end;
ED_view3d_win_to_segment_clipped(
&depsgraph, region, v3d, stroke_extension.mouse_position, ray_start, ray_end, true);
ray_start = surface_ob_imat * ray_start;
ray_end = surface_ob_imat * ray_end;
const float3 ray_direction = math::normalize(ray_end - ray_start);
/* This ray is used to determine the brush radius in 3d space. */
float3 offset_ray_start, offset_ray_end;
ED_view3d_win_to_segment_clipped(&depsgraph,
region,
v3d,
stroke_extension.mouse_position +
float2(0, brush_radius_screen),
offset_ray_start,
offset_ray_end,
true);
offset_ray_start = surface_ob_imat * offset_ray_start;
offset_ray_end = surface_ob_imat * offset_ray_end;
float4x4 ob_imat;
invert_m4_m4(ob_imat.values, object.obmat);
const float4x4 transform = ob_imat * surface_ob_mat;
BVHTreeFromMesh bvhtree;
BKE_bvhtree_from_mesh_get(&bvhtree, &surface, BVHTREE_FROM_LOOPTRI, 2);
/* Do a raycast against the surface object to find the brush position. */
BVHTreeRayHit ray_hit;
ray_hit.dist = FLT_MAX;
ray_hit.index = -1;
BLI_bvhtree_ray_cast(bvhtree.tree,
ray_start,
ray_direction,
0.0f,
&ray_hit,
bvhtree.raycast_callback,
&bvhtree);
if (ray_hit.index == -1) {
/* The ray did not hit the surface. */
free_bvhtree_from_mesh(&bvhtree);
return;
}
/* Brush position in the space of the surface object. */
const float3 brush_pos_3d_surface = ray_hit.co;
const float brush_radius_3d_surface = dist_to_line_v3(
brush_pos_3d_surface, offset_ray_start, offset_ray_end);
/* Brush position in the space of the curves object. */
const float3 brush_pos_3d_curves = transform * brush_pos_3d_surface;
const float brush_radius_3d_curves = dist_to_line_v3(
brush_pos_3d_curves, transform * offset_ray_start, transform * offset_ray_end);
Vector<int> looptri_indices = this->find_looptri_indices_to_consider(
bvhtree, brush_pos_3d_surface, brush_radius_3d_surface);
free_bvhtree_from_mesh(&bvhtree);
if (old_kdtree_ == nullptr && minimum_distance > 0.0f) {
old_kdtree_ = this->kdtree_from_curve_roots_and_positions(curves, curves.curves_range(), {});
old_kdtree_size_ = curves.curves_num();
}
float density;
if (minimum_distance > 0.0f) {
/* Estimate the sampling density based on the target minimum distance. */
density = strength * pow2f(1.0f / minimum_distance);
}
else {
/* Sample a somewhat constant amount of points based on the strength. */
const float brush_circle_area_3d = M_PI * pow2f(brush_radius_3d_curves);
density = strength * 100.0f / brush_circle_area_3d;
}
NewPointsData new_points = this->sample_new_points(density,
minimum_distance,
brush_radius_3d_curves,
brush_pos_3d_curves,
looptri_indices,
transform,
surface);
if (minimum_distance > 0.0f) {
this->eliminate_too_close_points(new_points, curves, minimum_distance);
}
this->insert_new_curves(new_points, curves);
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
}
private:
Vector<int> find_looptri_indices_to_consider(BVHTreeFromMesh &bvhtree,
const float3 &brush_pos,
const float brush_radius_3d)
{
Vector<int> looptri_indices;
struct RangeQueryUserData {
Vector<int> &indices;
} range_query_user_data = {looptri_indices};
BLI_bvhtree_range_query(
bvhtree.tree,
brush_pos,
brush_radius_3d,
[](void *userdata, int index, const float co[3], float dist_sq) {
UNUSED_VARS(co, dist_sq);
RangeQueryUserData &data = *static_cast<RangeQueryUserData *>(userdata);
data.indices.append(index);
},
&range_query_user_data);
return looptri_indices;
}
KDTree_3d *kdtree_from_curve_roots_and_positions(const CurvesGeometry &curves,
const IndexRange curves_range,
Span<float3> extra_positions)
{
const int tot_points = curves_range.size() + extra_positions.size();
KDTree_3d *kdtree = BLI_kdtree_3d_new(tot_points);
for (const int curve_i : curves_range) {
const int first_point_i = curves.offsets()[curve_i];
const float3 root_position = curves.positions()[first_point_i];
BLI_kdtree_3d_insert(kdtree, ExistsAlreadyIndex, root_position);
}
for (const int i : extra_positions.index_range()) {
BLI_kdtree_3d_insert(kdtree, i, extra_positions[i]);
}
BLI_kdtree_3d_balance(kdtree);
return kdtree;
}
bool is_too_close_to_existing_point(const float3 position, const float minimum_distance) const
{
if (old_kdtree_ == nullptr) {
return false;
}
KDTreeNearest_3d nearest;
nearest.index = -1;
BLI_kdtree_3d_find_nearest(old_kdtree_, position, &nearest);
if (nearest.index >= 0 && nearest.dist < minimum_distance) {
return true;
}
return false;
}
NewPointsData sample_new_points(const float density,
const float minimum_distance,
const float brush_radius_3d,
const float3 &brush_pos,
const Span<int> looptri_indices,
const float4x4 &transform,
const Mesh &surface)
{
const float brush_radius_3d_sq = brush_radius_3d * brush_radius_3d;
const float area_threshold = M_PI * brush_radius_3d_sq;
const Span<MLoopTri> looptris{BKE_mesh_runtime_looptri_ensure(&surface),
BKE_mesh_runtime_looptri_len(&surface)};
threading::EnumerableThreadSpecific<NewPointsData> new_points_per_thread;
const double time = PIL_check_seconds_timer();
const uint64_t time_as_int = *reinterpret_cast<const uint64_t *>(&time);
const uint32_t rng_base_seed = time_as_int ^ (time_as_int >> 32);
RandomNumberGenerator rng{rng_base_seed};
threading::parallel_for(looptri_indices.index_range(), 512, [&](const IndexRange range) {
RandomNumberGenerator looptri_rng{rng_base_seed + (uint32_t)range.start()};
for (const int looptri_index : looptri_indices.slice(range)) {
const MLoopTri &looptri = looptris[looptri_index];
const float3 &v0 = transform * float3(surface.mvert[surface.mloop[looptri.tri[0]].v].co);
const float3 &v1 = transform * float3(surface.mvert[surface.mloop[looptri.tri[1]].v].co);
const float3 &v2 = transform * float3(surface.mvert[surface.mloop[looptri.tri[2]].v].co);
const float looptri_area = area_tri_v3(v0, v1, v2);
float3 normal;
normal_tri_v3(normal, v0, v1, v2);
/* Use a different sampling strategy depending on whether the triangle is large or small
* compared to the brush size. When the triangle is small, points are distributed within
* the triangle directly. If the triangle is larger than the brush, distribute new points
* in a circle on the triangle plane. */
if (looptri_area < area_threshold) {
const int amount = looptri_rng.round_probabilistic(looptri_area * density);
threading::parallel_for(IndexRange(amount), 512, [&](const IndexRange amount_range) {
RandomNumberGenerator point_rng{rng_base_seed + looptri_index * 1000 +
(uint32_t)amount_range.start()};
NewPointsData &new_points = new_points_per_thread.local();
for ([[maybe_unused]] const int i : amount_range) {
const float3 bary_coord = point_rng.get_barycentric_coordinates();
const float3 point_pos = attribute_math::mix3(bary_coord, v0, v1, v2);
if (math::distance(point_pos, brush_pos) > brush_radius_3d) {
continue;
}
if (minimum_distance > 0.0f &&
this->is_too_close_to_existing_point(point_pos, minimum_distance)) {
continue;
}
new_points.bary_coords.append(bary_coord);
new_points.looptri_indices.append(looptri_index);
new_points.positions.append(point_pos);
new_points.normals.append(normal);
}
});
}
else {
float3 hit_pos_proj = brush_pos;
project_v3_plane(hit_pos_proj, normal, v0);
const float proj_distance_sq = math::distance_squared(hit_pos_proj, brush_pos);
const float brush_radius_factor_sq = 1.0f -
std::min(1.0f,
proj_distance_sq / brush_radius_3d_sq);
const float radius_proj_sq = brush_radius_3d_sq * brush_radius_factor_sq;
const float radius_proj = std::sqrt(radius_proj_sq);
const float circle_area = M_PI * radius_proj_sq;
const int amount = rng.round_probabilistic(circle_area * density);
const float3 axis_1 = math::normalize(v1 - v0) * radius_proj;
const float3 axis_2 = math::normalize(
math::cross(axis_1, math::cross(axis_1, v2 - v0))) *
radius_proj;
threading::parallel_for(IndexRange(amount), 512, [&](const IndexRange amount_range) {
RandomNumberGenerator point_rng{rng_base_seed + looptri_index * 1000 +
(uint32_t)amount_range.start()};
NewPointsData &new_points = new_points_per_thread.local();
for ([[maybe_unused]] const int i : amount_range) {
const float r = std::sqrt(rng.get_float());
const float angle = rng.get_float() * 2 * M_PI;
const float x = r * std::cos(angle);
const float y = r * std::sin(angle);
const float3 point_pos = hit_pos_proj + axis_1 * x + axis_2 * y;
if (!isect_point_tri_prism_v3(point_pos, v0, v1, v2)) {
continue;
}
if (minimum_distance > 0.0f &&
this->is_too_close_to_existing_point(point_pos, minimum_distance)) {
continue;
}
float3 bary_coord;
interp_weights_tri_v3(bary_coord, v0, v1, v2, point_pos);
new_points.bary_coords.append(bary_coord);
new_points.looptri_indices.append(looptri_index);
new_points.positions.append(point_pos);
new_points.normals.append(normal);
}
});
}
}
});
NewPointsData new_points;
for (const NewPointsData &local_new_points : new_points_per_thread) {
new_points.bary_coords.extend(local_new_points.bary_coords);
new_points.looptri_indices.extend(local_new_points.looptri_indices);
new_points.positions.extend(local_new_points.positions);
new_points.normals.extend(local_new_points.normals);
}
return new_points;
}
void eliminate_too_close_points(NewPointsData &points,
const CurvesGeometry &curves,
const float minimum_distance)
{
Array<bool> elimination_mask(points.positions.size(), false);
const int curves_added_previously = curves.curves_num() - old_kdtree_size_;
KDTree_3d *new_points_kdtree = this->kdtree_from_curve_roots_and_positions(
curves, IndexRange(old_kdtree_size_, curves_added_previously), points.positions);
Array<Vector<int>> points_in_range(points.positions.size());
threading::parallel_for(points.positions.index_range(), 256, [&](const IndexRange range) {
for (const int point_i : range) {
const float3 query_position = points.positions[point_i];
struct CallbackData {
int point_i;
Vector<int> &found_indices;
MutableSpan<bool> elimination_mask;
} callback_data = {point_i, points_in_range[point_i], elimination_mask};
BLI_kdtree_3d_range_search_cb(
new_points_kdtree,
query_position,
minimum_distance,
[](void *user_data, int index, const float *UNUSED(co), float UNUSED(dist_sq)) {
CallbackData &data = *static_cast<CallbackData *>(user_data);
if (index == data.point_i) {
/* Ignore self. */
return true;
}
if (index == ExistsAlreadyIndex) {
/* An already existing point is too close, so this new point will be eliminated. */
data.elimination_mask[data.point_i] = true;
return false;
}
data.found_indices.append(index);
return true;
},
&callback_data);
}
});
for (const int point_i : points.positions.index_range()) {
if (elimination_mask[point_i]) {
/* Point is eliminated already. */
continue;
}
for (const int other_point_i : points_in_range[point_i]) {
elimination_mask[other_point_i] = true;
}
}
BLI_kdtree_3d_free(new_points_kdtree);
for (int i = points.positions.size() - 1; i >= 0; i--) {
if (elimination_mask[i]) {
points.positions.remove_and_reorder(i);
points.bary_coords.remove_and_reorder(i);
points.looptri_indices.remove_and_reorder(i);
points.normals.remove_and_reorder(i);
}
}
}
void insert_new_curves(const NewPointsData &new_points, CurvesGeometry &curves)
{
const int tot_new_curves = new_points.positions.size();
const int points_per_curve = 8;
curves.resize(curves.points_num() + tot_new_curves * points_per_curve,
curves.curves_num() + tot_new_curves);
MutableSpan<int> offsets = curves.offsets_for_write();
MutableSpan<float3> positions = curves.positions_for_write();
for (const int i : IndexRange(tot_new_curves)) {
const int curve_i = curves.curves_num() - tot_new_curves + i;
const int first_point_i = offsets[curve_i];
offsets[curve_i + 1] = offsets[curve_i] + points_per_curve;
const float3 root = new_points.positions[i];
const float3 tip = root + 0.1f * new_points.normals[i];
for (const int j : IndexRange(points_per_curve)) {
positions[first_point_i + j] = math::interpolate(
root, tip, j / (float)(points_per_curve - 1));
}
}
}
};
static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bContext *C,
wmOperator *op)
{
@ -518,8 +93,6 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte
return new_add_operation();
case CURVES_SCULPT_TOOL_GROW_SHRINK:
return new_grow_shrink_operation(mode, C);
case CURVES_SCULPT_TOOL_TEST1:
return std::make_unique<DensityAddOperation>();
}
BLI_assert_unreachable();
return {};

View File

@ -462,7 +462,6 @@ typedef enum eBrushCurvesSculptTool {
CURVES_SCULPT_TOOL_SNAKE_HOOK = 2,
CURVES_SCULPT_TOOL_ADD = 3,
CURVES_SCULPT_TOOL_GROW_SHRINK = 4,
CURVES_SCULPT_TOOL_TEST1 = 5,
} eBrushCurvesSculptTool;
/** When #BRUSH_ACCUMULATE is used */

View File

@ -249,7 +249,6 @@ const EnumPropertyItem rna_enum_brush_curves_sculpt_tool_items[] = {
{CURVES_SCULPT_TOOL_SNAKE_HOOK, "SNAKE_HOOK", ICON_NONE, "Snake Hook", ""},
{CURVES_SCULPT_TOOL_ADD, "ADD", ICON_NONE, "Add", ""},
{CURVES_SCULPT_TOOL_GROW_SHRINK, "GROW_SHRINK", ICON_NONE, "Grow / Shrink", ""},
{CURVES_SCULPT_TOOL_TEST1, "TEST1", ICON_NONE, "Test 1", ""},
{0, NULL, 0, NULL, NULL},
};