Curves: separate sculpt brushes into separate files

This makes it easier to work on these brushes in parallel.
This commit is contained in:
Jacques Lucke 2022-03-17 10:03:46 +01:00
parent 884b167f74
commit b6702aa604
6 changed files with 473 additions and 309 deletions

View File

@ -27,7 +27,10 @@ set(INC
)
set(SRC
curves_sculpt_comb.cc
curves_sculpt_delete.cc
curves_sculpt_ops.cc
curves_sculpt_snake_hook.cc
paint_cursor.c
paint_curve.c
paint_curve_undo.c
@ -72,6 +75,7 @@ set(SRC
sculpt_uv.c
curves_sculpt_intern.h
curves_sculpt_intern.hh
paint_intern.h
sculpt_intern.h
)

View File

@ -0,0 +1,167 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include "curves_sculpt_intern.hh"
#include "BLI_float4x4.hh"
#include "BLI_index_mask_ops.hh"
#include "BLI_kdtree.h"
#include "BLI_rand.hh"
#include "BLI_vector.hh"
#include "PIL_time.h"
#include "DEG_depsgraph.h"
#include "BKE_attribute_math.hh"
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
#include "BKE_spline.hh"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "ED_screen.h"
#include "ED_view3d.h"
namespace blender::ed::sculpt_paint {
using blender::bke::CurvesGeometry;
/**
* Moves individual points under the brush and does a length preservation step afterwards.
*/
class CombOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; });
if (stroke_extension.is_first) {
return;
}
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);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
const float brush_strength = BKE_brush_alpha_get(&scene, &brush);
const float4x4 ob_mat = object.obmat;
const float4x4 ob_imat = ob_mat.inverted();
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_prev = last_mouse_position_;
const float2 mouse_cur = stroke_extension.mouse_position;
const float2 mouse_diff = mouse_cur - mouse_prev;
const float mouse_diff_len = math::length(mouse_diff);
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
for (const int curve_i : curves_range) {
const IndexRange curve_points = curves.range_for_curve(curve_i);
/* Compute lengths of the segments. Those are used to make sure that the lengths don't
* change. */
Vector<float, 16> segment_lengths(curve_points.size() - 1);
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
const float3 &p2 = positions[curve_points[segment_i] + 1];
const float length = math::distance(p1, p2);
segment_lengths[segment_i] = length;
}
bool curve_changed = false;
for (const int point_i : curve_points.drop_front(1)) {
const float3 old_position = positions[point_i];
/* Find the position of the point in screen space. */
float2 old_position_screen;
ED_view3d_project_float_v2_m4(
region, old_position, old_position_screen, projection.values);
/* Project the point onto the line drawn by the mouse. Note, it's projected on the
* infinite line, not only on the line segment. */
float2 old_position_screen_proj;
/* t is 0 when the point is closest to the previous mouse position and 1 when it's
* closest to the current mouse position. */
const float t = closest_to_line_v2(
old_position_screen_proj, old_position_screen, mouse_prev, mouse_cur);
/* Compute the distance to the mouse line segment. */
const float2 old_position_screen_proj_segment = mouse_prev +
std::clamp(t, 0.0f, 1.0f) * mouse_diff;
const float distance_screen = math::distance(old_position_screen,
old_position_screen_proj_segment);
if (distance_screen > brush_radius) {
/* Ignore the point because it's too far away. */
continue;
}
/* Compute a falloff that is based on how far along the point along the last stroke
* segment is. */
const float t_overshoot = brush_radius / mouse_diff_len;
const float t_falloff = 1.0f - std::max(t, 0.0f) / (1.0f + t_overshoot);
/* A falloff that is based on how far away the point is from the stroke. */
const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius);
/* Combine the different falloffs and brush strength. */
const float weight = brush_strength * t_falloff * radius_falloff;
/* Offset the old point position in screen space and transform it back into 3D space. */
const float2 new_position_screen = old_position_screen + mouse_diff * weight;
float3 new_position;
ED_view3d_win_to_3d(
v3d, region, ob_mat * old_position, new_position_screen, new_position);
new_position = ob_imat * new_position;
positions[point_i] = new_position;
curve_changed = true;
}
if (!curve_changed) {
continue;
}
/* Ensure that the length of each segment stays the same. */
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
float3 &p2 = positions[curve_points[segment_i] + 1];
const float3 direction = math::normalize(p2 - p1);
const float desired_length = segment_lengths[segment_i];
p2 = p1 + direction * desired_length;
}
}
});
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
}
};
std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation()
{
return std::make_unique<CombOperation>();
}
} // namespace blender::ed::sculpt_paint

View File

@ -0,0 +1,106 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include "curves_sculpt_intern.hh"
#include "BLI_float4x4.hh"
#include "BLI_index_mask_ops.hh"
#include "BLI_kdtree.h"
#include "BLI_rand.hh"
#include "BLI_vector.hh"
#include "PIL_time.h"
#include "DEG_depsgraph.h"
#include "BKE_attribute_math.hh"
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
#include "BKE_spline.hh"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "ED_screen.h"
#include "ED_view3d.h"
namespace blender::ed::sculpt_paint {
using blender::bke::CurvesGeometry;
class DeleteOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
Scene &scene = *CTX_data_scene(C);
Object &object = *CTX_data_active_object(C);
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_start = stroke_extension.is_first ? stroke_extension.mouse_position :
last_mouse_position_;
const float2 mouse_end = stroke_extension.mouse_position;
/* Find indices of curves that have to be removed. */
Vector<int64_t> indices;
const IndexMask curves_to_remove = index_mask_ops::find_indices_based_on_predicate(
curves.curves_range(), 512, indices, [&](const int curve_i) {
const IndexRange point_range = curves.range_for_curve(curve_i);
for (const int segment_i : IndexRange(point_range.size() - 1)) {
const float3 pos1 = positions[point_range[segment_i]];
const float3 pos2 = positions[point_range[segment_i + 1]];
float2 pos1_proj, pos2_proj;
ED_view3d_project_float_v2_m4(region, pos1, pos1_proj, projection.values);
ED_view3d_project_float_v2_m4(region, pos2, pos2_proj, projection.values);
const float dist = dist_seg_seg_v2(pos1_proj, pos2_proj, mouse_start, mouse_end);
if (dist <= brush_radius) {
return true;
}
}
return false;
});
curves.remove_curves(curves_to_remove);
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
last_mouse_position_ = stroke_extension.mouse_position;
}
};
std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation()
{
return std::make_unique<DeleteOperation>();
}
} // namespace blender::ed::sculpt_paint

View File

@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "curves_sculpt_intern.h"
#include "BLI_math_vector.hh"
namespace blender::ed::sculpt_paint {
struct StrokeExtension {
bool is_first;
float2 mouse_position;
};
/**
* Base class for stroke based operations in curves sculpt mode.
*/
class CurvesSculptStrokeOperation {
public:
virtual ~CurvesSculptStrokeOperation() = default;
virtual void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) = 0;
};
std::unique_ptr<CurvesSculptStrokeOperation> new_comb_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_delete_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
} // namespace blender::ed::sculpt_paint

View File

@ -40,6 +40,7 @@
#include "PIL_time.h"
#include "curves_sculpt_intern.h"
#include "curves_sculpt_intern.hh"
#include "paint_intern.h"
/* -------------------------------------------------------------------- */
@ -74,312 +75,6 @@ using blender::fn::CPPType;
/** \name * SCULPT_CURVES_OT_brush_stroke
* \{ */
struct StrokeExtension {
bool is_first;
float2 mouse_position;
};
/**
* Base class for stroke based operations in curves sculpt mode.
*/
class CurvesSculptStrokeOperation {
public:
virtual ~CurvesSculptStrokeOperation() = default;
virtual void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) = 0;
};
class DeleteOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
Scene &scene = *CTX_data_scene(C);
Object &object = *CTX_data_active_object(C);
ARegion *region = CTX_wm_region(C);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_start = stroke_extension.is_first ? stroke_extension.mouse_position :
last_mouse_position_;
const float2 mouse_end = stroke_extension.mouse_position;
/* Find indices of curves that have to be removed. */
Vector<int64_t> indices;
const IndexMask curves_to_remove = index_mask_ops::find_indices_based_on_predicate(
curves.curves_range(), 512, indices, [&](const int curve_i) {
const IndexRange point_range = curves.range_for_curve(curve_i);
for (const int segment_i : IndexRange(point_range.size() - 1)) {
const float3 pos1 = positions[point_range[segment_i]];
const float3 pos2 = positions[point_range[segment_i + 1]];
float2 pos1_proj, pos2_proj;
ED_view3d_project_float_v2_m4(region, pos1, pos1_proj, projection.values);
ED_view3d_project_float_v2_m4(region, pos2, pos2_proj, projection.values);
const float dist = dist_seg_seg_v2(pos1_proj, pos2_proj, mouse_start, mouse_end);
if (dist <= brush_radius) {
return true;
}
}
return false;
});
curves.remove_curves(curves_to_remove);
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
last_mouse_position_ = stroke_extension.mouse_position;
}
};
/**
* Moves individual points under the brush and does a length preservation step afterwards.
*/
class CombOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; });
if (stroke_extension.is_first) {
return;
}
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);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
const float brush_strength = BKE_brush_alpha_get(&scene, &brush);
const float4x4 ob_mat = object.obmat;
const float4x4 ob_imat = ob_mat.inverted();
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_prev = last_mouse_position_;
const float2 mouse_cur = stroke_extension.mouse_position;
const float2 mouse_diff = mouse_cur - mouse_prev;
const float mouse_diff_len = math::length(mouse_diff);
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
for (const int curve_i : curves_range) {
const IndexRange curve_points = curves.range_for_curve(curve_i);
/* Compute lengths of the segments. Those are used to make sure that the lengths don't
* change. */
Vector<float, 16> segment_lengths(curve_points.size() - 1);
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
const float3 &p2 = positions[curve_points[segment_i] + 1];
const float length = math::distance(p1, p2);
segment_lengths[segment_i] = length;
}
bool curve_changed = false;
for (const int point_i : curve_points.drop_front(1)) {
const float3 old_position = positions[point_i];
/* Find the position of the point in screen space. */
float2 old_position_screen;
ED_view3d_project_float_v2_m4(
region, old_position, old_position_screen, projection.values);
/* Project the point onto the line drawn by the mouse. Note, it's projected on the
* infinite line, not only on the line segment. */
float2 old_position_screen_proj;
/* t is 0 when the point is closest to the previous mouse position and 1 when it's
* closest to the current mouse position. */
const float t = closest_to_line_v2(
old_position_screen_proj, old_position_screen, mouse_prev, mouse_cur);
/* Compute the distance to the mouse line segment. */
const float2 old_position_screen_proj_segment = mouse_prev +
std::clamp(t, 0.0f, 1.0f) * mouse_diff;
const float distance_screen = math::distance(old_position_screen,
old_position_screen_proj_segment);
if (distance_screen > brush_radius) {
/* Ignore the point because it's too far away. */
continue;
}
/* Compute a falloff that is based on how far along the point along the last stroke
* segment is. */
const float t_overshoot = brush_radius / mouse_diff_len;
const float t_falloff = 1.0f - std::max(t, 0.0f) / (1.0f + t_overshoot);
/* A falloff that is based on how far away the point is from the stroke. */
const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius);
/* Combine the different falloffs and brush strength. */
const float weight = brush_strength * t_falloff * radius_falloff;
/* Offset the old point position in screen space and transform it back into 3D space. */
const float2 new_position_screen = old_position_screen + mouse_diff * weight;
float3 new_position;
ED_view3d_win_to_3d(
v3d, region, ob_mat * old_position, new_position_screen, new_position);
new_position = ob_imat * new_position;
positions[point_i] = new_position;
curve_changed = true;
}
if (!curve_changed) {
continue;
}
/* Ensure that the length of each segment stays the same. */
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
float3 &p2 = positions[curve_points[segment_i] + 1];
const float3 direction = math::normalize(p2 - p1);
const float desired_length = segment_lengths[segment_i];
p2 = p1 + direction * desired_length;
}
}
});
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
}
};
/**
* Drags the tip point of each curve and resamples the rest of the curve.
*/
class SnakeHookOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; });
if (stroke_extension.is_first) {
return;
}
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);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
const float brush_strength = BKE_brush_alpha_get(&scene, &brush);
const float4x4 ob_mat = object.obmat;
const float4x4 ob_imat = ob_mat.inverted();
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_prev = last_mouse_position_;
const float2 mouse_cur = stroke_extension.mouse_position;
const float2 mouse_diff = mouse_cur - mouse_prev;
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
for (const int curve_i : curves_range) {
const IndexRange curve_points = curves.range_for_curve(curve_i);
const int last_point_i = curve_points.last();
const float3 old_position = positions[last_point_i];
float2 old_position_screen;
ED_view3d_project_float_v2_m4(
region, old_position, old_position_screen, projection.values);
const float distance_screen = math::distance(old_position_screen, mouse_prev);
if (distance_screen > brush_radius) {
continue;
}
const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius);
const float weight = brush_strength * radius_falloff;
const float2 new_position_screen = old_position_screen + mouse_diff * weight;
float3 new_position;
ED_view3d_win_to_3d(v3d, region, ob_mat * old_position, new_position_screen, new_position);
new_position = ob_imat * new_position;
this->move_last_point_and_resample(positions, curve_points, new_position);
}
});
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
}
void move_last_point_and_resample(MutableSpan<float3> positions,
const IndexRange curve_points,
const float3 &new_last_point_position) const
{
Vector<float> old_lengths;
old_lengths.append(0.0f);
/* Used to (1) normalize the segment sizes over time and (2) support making zero-length
* segments */
const float extra_length = 0.001f;
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
const float3 &p2 = positions[curve_points[segment_i] + 1];
const float length = math::distance(p1, p2);
old_lengths.append(old_lengths.last() + length + extra_length);
}
Vector<float> point_factors;
for (float &old_length : old_lengths) {
point_factors.append(old_length / old_lengths.last());
}
PolySpline new_spline;
new_spline.resize(curve_points.size());
MutableSpan<float3> new_spline_positions = new_spline.positions();
for (const int i : IndexRange(curve_points.size() - 1)) {
new_spline_positions[i] = positions[curve_points[i]];
}
new_spline_positions.last() = new_last_point_position;
new_spline.mark_cache_invalid();
for (const int i : IndexRange(curve_points.size())) {
const float factor = point_factors[i];
const Spline::LookupResult lookup = new_spline.lookup_evaluated_factor(factor);
const float index_factor = lookup.evaluated_index + lookup.factor;
float3 p;
new_spline.sample_with_index_factors<float3>(
new_spline_positions, {&index_factor, 1}, {&p, 1});
positions[curve_points[i]] = p;
}
}
};
/**
* Resamples the curves to a shorter length.
*/
@ -932,11 +627,11 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(bConte
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
switch (brush.curves_sculpt_tool) {
case CURVES_SCULPT_TOOL_COMB:
return std::make_unique<CombOperation>();
return new_comb_operation();
case CURVES_SCULPT_TOOL_DELETE:
return std::make_unique<DeleteOperation>();
return new_delete_operation();
case CURVES_SCULPT_TOOL_SNAKE_HOOK:
return std::make_unique<SnakeHookOperation>();
return new_snake_hook_operation();
case CURVES_SCULPT_TOOL_TEST1:
return std::make_unique<AddOperation>();
case CURVES_SCULPT_TOOL_TEST2:

View File

@ -0,0 +1,163 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <algorithm>
#include "curves_sculpt_intern.hh"
#include "BLI_float4x4.hh"
#include "BLI_index_mask_ops.hh"
#include "BLI_kdtree.h"
#include "BLI_rand.hh"
#include "BLI_vector.hh"
#include "PIL_time.h"
#include "DEG_depsgraph.h"
#include "BKE_attribute_math.hh"
#include "BKE_brush.h"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
#include "BKE_curves.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_paint.h"
#include "BKE_spline.hh"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
#include "DNA_curves_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "ED_screen.h"
#include "ED_view3d.h"
namespace blender::ed::sculpt_paint {
using blender::bke::CurvesGeometry;
/**
* Drags the tip point of each curve and resamples the rest of the curve.
*/
class SnakeHookOperation : public CurvesSculptStrokeOperation {
private:
float2 last_mouse_position_;
public:
void on_stroke_extended(bContext *C, const StrokeExtension &stroke_extension) override
{
BLI_SCOPED_DEFER([&]() { last_mouse_position_ = stroke_extension.mouse_position; });
if (stroke_extension.is_first) {
return;
}
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);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
Brush &brush = *BKE_paint_brush(&curves_sculpt.paint);
const float brush_radius = BKE_brush_size_get(&scene, &brush);
const float brush_strength = BKE_brush_alpha_get(&scene, &brush);
const float4x4 ob_mat = object.obmat;
const float4x4 ob_imat = ob_mat.inverted();
float4x4 projection;
ED_view3d_ob_project_mat_get(rv3d, &object, projection.values);
Curves &curves_id = *static_cast<Curves *>(object.data);
CurvesGeometry &curves = CurvesGeometry::wrap(curves_id.geometry);
MutableSpan<float3> positions = curves.positions();
const float2 mouse_prev = last_mouse_position_;
const float2 mouse_cur = stroke_extension.mouse_position;
const float2 mouse_diff = mouse_cur - mouse_prev;
threading::parallel_for(curves.curves_range(), 256, [&](const IndexRange curves_range) {
for (const int curve_i : curves_range) {
const IndexRange curve_points = curves.range_for_curve(curve_i);
const int last_point_i = curve_points.last();
const float3 old_position = positions[last_point_i];
float2 old_position_screen;
ED_view3d_project_float_v2_m4(
region, old_position, old_position_screen, projection.values);
const float distance_screen = math::distance(old_position_screen, mouse_prev);
if (distance_screen > brush_radius) {
continue;
}
const float radius_falloff = pow2f(1.0f - distance_screen / brush_radius);
const float weight = brush_strength * radius_falloff;
const float2 new_position_screen = old_position_screen + mouse_diff * weight;
float3 new_position;
ED_view3d_win_to_3d(v3d, region, ob_mat * old_position, new_position_screen, new_position);
new_position = ob_imat * new_position;
this->move_last_point_and_resample(positions, curve_points, new_position);
}
});
curves.tag_positions_changed();
DEG_id_tag_update(&curves_id.id, ID_RECALC_GEOMETRY);
ED_region_tag_redraw(region);
}
void move_last_point_and_resample(MutableSpan<float3> positions,
const IndexRange curve_points,
const float3 &new_last_point_position) const
{
Vector<float> old_lengths;
old_lengths.append(0.0f);
/* Used to (1) normalize the segment sizes over time and (2) support making zero-length
* segments */
const float extra_length = 0.001f;
for (const int segment_i : IndexRange(curve_points.size() - 1)) {
const float3 &p1 = positions[curve_points[segment_i]];
const float3 &p2 = positions[curve_points[segment_i] + 1];
const float length = math::distance(p1, p2);
old_lengths.append(old_lengths.last() + length + extra_length);
}
Vector<float> point_factors;
for (float &old_length : old_lengths) {
point_factors.append(old_length / old_lengths.last());
}
PolySpline new_spline;
new_spline.resize(curve_points.size());
MutableSpan<float3> new_spline_positions = new_spline.positions();
for (const int i : IndexRange(curve_points.size() - 1)) {
new_spline_positions[i] = positions[curve_points[i]];
}
new_spline_positions.last() = new_last_point_position;
new_spline.mark_cache_invalid();
for (const int i : IndexRange(curve_points.size())) {
const float factor = point_factors[i];
const Spline::LookupResult lookup = new_spline.lookup_evaluated_factor(factor);
const float index_factor = lookup.evaluated_index + lookup.factor;
float3 p;
new_spline.sample_with_index_factors<float3>(
new_spline_positions, {&index_factor, 1}, {&p, 1});
positions[curve_points[i]] = p;
}
}
};
std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation()
{
return std::make_unique<SnakeHookOperation>();
}
} // namespace blender::ed::sculpt_paint