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:
parent
d1418dd151
commit
e0c8d0913b
Notes:
blender-bot
2023-02-14 06:46:23 +01:00
Referenced by issue #97255, Remove remaining test curves brush.
|
@ -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 {};
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue