46 bool temp_eraser_ =
false;
48 bool keep_caps_ =
false;
49 float radius_ = 50.0f;
50 float strength_ = 0.1f;
52 bool active_layer_only_ =
false;
57 EraseOperation(
bool temp_use_eraser =
false) : temp_eraser_(temp_use_eraser) {}
136 const int64_t c = d_s0_center - radius_2;
170 const float mu0_f = -
b / (2.0f * a);
177 const float mu0_f = (-
b + i_sqrt) / (2.0f * a);
178 const float mu1_f = (-
b - i_sqrt) / (2.0f * a);
206 const int2 &point_after,
219 point, point_after, this->mouse_position_pixels, squared_radius, mu0, mu1);
221 if (nb_intersections != 2) {
226 r_mu0 = r_mu1 = -1.0f;
241 const int8_t side_mu0 = (mu0 <= 0) ? (-1) : ((mu0 >= segment_length) ? 1 : 0);
242 const int8_t side_mu1 = (mu1 <= 0) ? (-1) : ((mu1 >= segment_length) ? 1 : 0);
249 r_point_after_side = (mu0 == segment_length) ?
255 r_mu0 = mu0 /
float(segment_length);
256 r_mu1 = mu1 /
float(segment_length);
258 const bool is_mu0_inside = (side_mu0 == 0);
259 const bool is_mu1_inside = (side_mu1 == 0);
260 if (!is_mu0_inside && !is_mu1_inside) {
263 if (side_mu0 == side_mu1) {
276 if (is_mu0_inside && is_mu1_inside) {
285 const int8_t side_outside_intersection = is_mu0_inside ? side_mu1 : side_mu0;
289 r_point_after_side = (side_outside_intersection == 1) ? r_point_after_side :
293 std::swap(r_mu0, r_mu1);
321 MutableSpan<std::pair<int, PointCircleSide>> r_point_ring,
325 const int intersections_max_per_segment = rings.
size() * 2;
331 for (const int src_point : src_points) {
332 const float2 pos = screen_space_positions[src_point];
333 screen_space_positions_pixel[src_point] = int2(round_fl_to_int(pos[0]),
334 round_fl_to_int(pos[1]));
339 for (const int src_curve : src_curves) {
340 const IndexRange src_curve_points = src_points_by_curve[src_curve];
342 if (src_curve_points.size() == 1) {
345 for (const EraserRing &eraser_point : rings) {
346 const int src_point = src_curve_points.first();
347 const int64_t squared_distance = math::distance_squared(
348 this->mouse_position_pixels, screen_space_positions_pixel[src_point]);
352 if ((r_point_ring[src_point].first == -1) &&
353 (squared_distance <= eraser_point.squared_radius))
355 r_point_ring[src_point] = {ring_index, PointCircleSide::Inside};
362 for (const int src_point : src_curve_points.drop_back(1)) {
364 int intersection_offset = src_point * intersections_max_per_segment - 1;
366 for (const EraserRing &eraser_point : rings) {
367 SegmentCircleIntersection inter0;
368 SegmentCircleIntersection inter1;
370 inter0.ring_index = ring_index;
371 inter1.ring_index = ring_index;
373 PointCircleSide point_side;
374 PointCircleSide point_after_side;
376 const int8_t nb_inter = segment_intersections_and_points_sides(
377 screen_space_positions_pixel[src_point],
378 screen_space_positions_pixel[src_point + 1],
379 eraser_point.squared_radius,
390 if ((r_point_ring[src_point].first == -1) &&
391 ELEM(point_side, PointCircleSide::Inside, PointCircleSide::InsideOutsideBoundary))
393 r_point_ring[src_point] = {ring_index, point_side};
396 if (src_point + 1 == src_curve_points.last()) {
397 if ((r_point_ring[src_point + 1].first == -1) &&
398 ELEM(point_after_side,
399 PointCircleSide::Inside,
400 PointCircleSide::InsideOutsideBoundary))
402 r_point_ring[src_point + 1] = {ring_index, point_after_side};
407 inter0.inside_outside_intersection = (inter0.factor > inter1.factor);
408 r_intersections[++intersection_offset] = inter0;
411 inter1.inside_outside_intersection = true;
412 r_intersections[++intersection_offset] = inter1;
420 if (src_cyclic[src_curve]) {
422 const int src_last_point = src_curve_points.last();
423 const int src_first_point = src_curve_points.first();
425 int intersection_offset = src_last_point * intersections_max_per_segment - 1;
427 for (const EraserRing &eraser_point : rings) {
428 SegmentCircleIntersection inter0;
429 SegmentCircleIntersection inter1;
431 inter0.ring_index = ring_index;
432 inter1.ring_index = ring_index;
434 PointCircleSide point_side;
435 PointCircleSide point_after_side;
437 const int8_t nb_inter = segment_intersections_and_points_sides(
438 screen_space_positions_pixel[src_last_point],
439 screen_space_positions_pixel[src_first_point],
440 eraser_point.squared_radius,
450 inter0.inside_outside_intersection = (inter0.factor > inter1.factor);
451 r_intersections[++intersection_offset] = inter0;
454 inter1.inside_outside_intersection = true;
455 r_intersections[++intersection_offset] = inter1;
466 int total_intersections = 0;
467 for (
const SegmentCircleIntersection &intersection : r_intersections) {
468 if (intersection.is_valid()) {
469 total_intersections++;
473 return total_intersections;
485 &ob, stroke_material[src_curve] + 1);
491 for (
const int src_point : src_points) {
492 const int src_next_point = (src_point == src_points.
last()) ? src_points.
first() :
494 src_to_dst_points[src_point].append(
495 {src_point, src_next_point, 0.0f,
true,
false, point_opacity[src_point]});
508 const bool keep_caps)
const
516 1, {this->eraser_radius, this->eraser_squared_radius_pixels, 0.0f,
true});
517 const int intersections_max_per_segment = eraser_rings.
size() * 2;
523 intersections_max_per_segment);
525 src, screen_space_positions, eraser_rings, src_point_ring, src_intersections);
536 const IndexRange src_points = src_points_by_curve[src_curve];
539 ob, src_curve, src_points, stroke_material, point_opacity, src_to_dst_points))
544 for (
const int src_point : src_points) {
546 const int src_next_point = (src_point == src_points.
last()) ? src_points.
first() :
552 dst_points.
append({src_point,
560 const IndexRange src_point_intersections(src_point * intersections_max_per_segment,
561 intersections_max_per_segment);
563 src_intersections.
as_span().slice(src_point_intersections))
565 if (!intersection.is_valid()) {
569 dst_points.
append({src_point,
573 intersection.inside_outside_intersection});
598 const int step_pixels = 2;
599 int nb_samples =
math::round(this->eraser_radius / step_pixels);
601 for (
const int sample_index : eraser_rings.
index_range()) {
602 const int64_t sampled_distance = (sample_index + 1) * step_pixels;
604 EraserRing &ring = eraser_rings[sample_index];
605 ring.
radius = sampled_distance;
607 ring.
opacity = 1.0 - this->eraser_strength *
609 this->brush_,
float(sampled_distance), this->eraser_radius);
614 for (
const int sample_index : eraser_rings.
index_range()) {
617 if (sample_index == nb_samples - 1) {
624 EraserRing next_sample = eraser_rings[sample_index + 1];
636 const EraserRing &sample_after = eraser_rings[sample_index + 1];
645 sample.squared_radius = radius * radius;
650 for (
const int rev_sample_index : eraser_rings.
index_range()) {
651 const int sample_index = nb_samples - rev_sample_index - 1;
652 if (prune_sample[sample_index]) {
653 eraser_rings.
remove(sample_index);
658 nb_samples = eraser_rings.
size();
664 const EraserRing &sample_first = eraser_rings[first_index];
665 const EraserRing &sample_last = eraser_rings[last_index];
679 const float distance_threshold = 0.1f;
681 eraser_rings.
index_range(), distance_threshold, opacity_distance, simplify_sample);
683 for (
const int rev_sample_index : eraser_rings.
index_range()) {
684 const int sample_index = nb_samples - rev_sample_index - 1;
685 if (simplify_sample[sample_index]) {
686 eraser_rings.
remove(sample_index);
704 const bool keep_caps)
707 const std::string opacity_attr =
"opacity";
712 const int intersections_max_per_segment = eraser_rings.
size() * 2;
720 intersections_max_per_segment);
722 src, screen_space_positions, eraser_rings, src_point_ring, src_intersections);
730 const auto compute_opacity = [&](
const int src_point) {
732 this->mouse_position);
735 this->brush_,
distance, this->eraser_radius);
747 const IndexRange src_points = src_points_by_curve[src_curve];
750 ob, src_curve, src_points, stroke_material, src_opacity, src_to_dst_points))
754 for (
const int src_point : src_points) {
756 const int src_next_point = (src_point == src_points.
last()) ? src_points.
first() :
761 const int point_ring = src_point_ring[src_point].first;
762 const bool ring_is_cut = (point_ring != -1) && eraser_rings[point_ring].hard_erase;
765 const bool point_is_cut = ring_is_cut &&
770 {src_point, src_next_point, 0.0f,
true, point_is_cut, compute_opacity(src_point)});
773 const IndexRange src_point_intersections(src_point * intersections_max_per_segment,
774 intersections_max_per_segment);
776 std::sort(src_intersections.
begin() + src_point_intersections.
first(),
777 src_intersections.
begin() + src_point_intersections.
last() + 1,
779 return a.factor < b.factor;
784 src_intersections.
as_span().slice(src_point_intersections))
786 if (!intersection.is_valid()) {
791 const EraserRing &ring = eraser_rings[intersection.ring_index];
792 const bool is_cut = intersection.inside_outside_intersection && ring.
hard_erase;
794 src_opacity[src_point], src_opacity[src_next_point], intersection.factor);
799 if (is_cut && !dst_points.
is_empty() && dst_points.
last().is_cut) {
804 {src_point, src_next_point, intersection.factor,
false, is_cut, opacity});
810 src, dst, src_to_dst_points, keep_caps);
820 for (const int dst_point_index : dst_points_range) {
821 const ed::greasepencil::PointTransferData &dst_point = dst_points[dst_point_index];
822 dst_opacity.span[dst_point_index] = dst_point.opacity;
825 dst_opacity.finish();
833 for (const int dst_curve : dst_curves_range) {
834 IndexRange dst_points_range = dst_points_by_curve[dst_curve];
836 dst_inserted.span[dst_points_range.first()] = false;
837 dst_inserted.span[dst_points_range.last()] = false;
839 if (dst_points_range.size() < 3) {
843 for (const int dst_point_index : dst_points_range.drop_back(1).drop_front(1)) {
844 const ed::greasepencil::PointTransferData &dst_point = dst_points[dst_point_index];
845 dst_inserted.span[dst_point_index] |= !dst_point.is_src_point;
868 &ob, stroke_materials[src_curve] + 1);
874 const IndexRange src_curve_points = src_points_by_curve[src_curve];
877 if (src_curve_points.
size() == 1) {
878 const float2 &point_pos = screen_space_positions[src_curve_points.
first()];
879 const float dist_to_eraser =
math::distance(point_pos, this->mouse_position);
880 return !(dist_to_eraser < this->eraser_radius);
885 for (
const int src_point : src_curve_points.
drop_back(1)) {
887 this->mouse_position,
888 screen_space_positions[src_point],
889 screen_space_positions[src_point + 1]);
895 if (src_cyclic[src_curve]) {
897 this->mouse_position,
898 screen_space_positions[src_curve_points.
first()],
899 screen_space_positions[src_curve_points.
last()]);
908 if (strokes_to_keep.size() == src.
curves_num()) {
934 this->eraser_radius =
self.radius_;
935 this->eraser_strength =
self.strength_;
939 brush->gpencil_settings->curve_strength, 0, extension_sample.
pressure);
943 brush->gpencil_settings->curve_strength, 0, extension_sample.
pressure);
945 this->brush_ =
brush;
950 this->eraser_squared_radius_pixels = eraser_radius_pixels * eraser_radius_pixels;
955 bool changed =
false;
956 const auto execute_eraser_on_drawing = [&](
const int layer_index,
Drawing &drawing) {
957 const Layer &layer = grease_pencil.layer(layer_index);
963 ob_eval, *obact, drawing);
968 for (const int src_point : src_points) {
969 const int result = ED_view3d_project_float_global(
971 math::transform_point(layer.to_world_space(*ob_eval),
972 deformation.positions[src_point]),
973 screen_space_positions[src_point],
974 V3D_PROJ_TEST_CLIP_NEAR | V3D_PROJ_TEST_CLIP_FAR);
975 if (result != V3D_PROJ_RET_OK) {
979 screen_space_positions[src_point] = float2(1e20);
987 switch (
self.eraser_mode_) {
989 erased =
stroke_eraser(*obact, src, screen_space_positions, dst);
992 erased =
hard_eraser(*obact, src, screen_space_positions, dst,
self.keep_caps_);
995 erased =
soft_eraser(*obact, src, screen_space_positions, dst,
self.keep_caps_);
1001 drawing.geometry.wrap() = std::move(dst);
1002 drawing.tag_topology_changed();
1004 self.affected_drawings_.add(&drawing);
1008 if (
self.active_layer_only_) {
1010 if (!grease_pencil.has_active_layer()) {
1013 const Layer &active_layer = *grease_pencil.get_active_layer();
1014 Drawing *drawing = grease_pencil.get_editable_drawing_at(active_layer, scene->r.cfra);
1016 if (drawing ==
nullptr) {
1020 execute_eraser_on_drawing(*grease_pencil.get_layer_index(active_layer), *drawing);
1027 execute_eraser_on_drawing(info.layer_index, info.drawing);
1049 radius_ =
paint->eraser_brush->size / 2.0f;
1050 grease_pencil->
runtime->temp_eraser_size = radius_;
1051 grease_pencil->
runtime->temp_use_eraser =
true;
1056 radius_ =
brush->size / 2.0f;
1059 if (
brush->gpencil_settings ==
nullptr) {
1070 strength_ =
brush->alpha;
1076 executor.
execute(*
this,
C, extension_sample);
1081 const float epsilon)
1095 const float3 &s0 = positions[first_index];
1096 const float3 &s1 = positions[last_index];
1098 if (segment_length < 1
e-6) {
1101 const float t =
math::distance(s0, positions[index]) / segment_length;
1103 opacities[first_index], opacities[last_index], t);
1104 return math::abs(opacities[index] - linear_opacity);
1111 range_to_simplify, epsilon, opacity_distance, dissolve_points);
1116 curves.remove_points(points_to_dissolve, {});
1121 const float epsilon)
1126 return opacities[point] < epsilon;
1138 grease_pencil.
runtime->temp_use_eraser =
false;
1139 grease_pencil.
runtime->temp_eraser_size = 0.0f;
1154 affected_drawings_.clear();
1162 return std::make_unique<EraseOperation>(temp_eraser);
bool BKE_brush_use_alpha_pressure(const Brush *brush)
float BKE_brush_curve_strength(eBrushCurvePreset preset, const CurveMapping *cumap, float distance, float brush_radius)
bool BKE_brush_use_size_pressure(const Brush *brush)
void BKE_brush_init_gpencil_settings(Brush *brush)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Object * CTX_data_active_object(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
Low-level operations for curves.
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
MaterialGPencilStyle * BKE_gpencil_material_settings(Object *ob, short act)
Brush * BKE_paint_eraser_brush(Paint *paint)
Paint * BKE_paint_get_active_from_context(const bContext *C)
Brush * BKE_paint_brush(Paint *paint)
MINLINE int round_fl_to_int(float a)
float dist_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
#define IN_RANGE(a, b, c)
void DEG_id_tag_update(ID *id, unsigned int flags)
T * DEG_get_evaluated(const Depsgraph *depsgraph, T *id)
@ GP_BRUSH_ERASER_KEEP_CAPS
@ GP_BRUSH_ACTIVE_LAYER_ONLY
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
BPy_StructRNA * depsgraph
static IndexMask from_predicate(const IndexMask &universe, GrainSize grain_size, IndexMaskMemory &memory, Fn &&predicate)
static IndexMask from_bools(Span< bool > bools, IndexMaskMemory &memory)
Span< T > as_span() const
constexpr int64_t one_before_start() const
constexpr int64_t first() const
constexpr IndexRange drop_back(int64_t n) const
constexpr int64_t last(const int64_t n=0) const
constexpr int64_t size() const
constexpr int64_t size() const
void append(const T &value)
const T & last(const int64_t n=0) const
void remove(const int64_t index)
IndexRange index_range() const
GAttributeReader lookup_or_default(StringRef attribute_id, AttrDomain domain, AttrType data_type, const void *default_value=nullptr) const
bool contains(StringRef attribute_id) const
OffsetIndices< int > points_by_curve() const
IndexRange curves_range() const
MutableAttributeAccessor attributes_for_write()
IndexRange points_range() const
AttributeAccessor attributes() const
VArray< bool > cyclic() const
GSpanAttributeWriter lookup_or_add_for_write_span(StringRef attribute_id, AttrDomain domain, AttrType data_type, const AttributeInit &initializer=AttributeInitDefaultValue())
bool remove(const StringRef attribute_id)
bke::CurvesGeometry & strokes_for_write()
const bke::CurvesGeometry & strokes() const
void tag_topology_changed()
VArray< float > opacities() const
~EraseOperation() override=default
void on_stroke_done(const bContext &C) override
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
EraseOperation(bool temp_use_eraser=false)
friend struct EraseOperationExecutor
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
void foreach_range(Fn &&fn) const
float distance(VecOp< float, D >, VecOp< float, D >) RET
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval, const Object &ob_orig, const bke::greasepencil::Drawing &drawing_orig)
CurvesGeometry curves_copy_curve_selection(const CurvesGeometry &curves, const IndexMask &curves_to_copy, const AttributeFilter &attribute_filter)
int64_t ramer_douglas_peucker_simplify(const IndexRange range, const float epsilon, const FunctionRef< float(int64_t, int64_t, int64_t)> dist_function, MutableSpan< bool > points_to_delete)
Array< PointTransferData > compute_topology_change(const bke::CurvesGeometry &src, bke::CurvesGeometry &dst, const Span< Vector< PointTransferData > > src_to_dst_points, const bool keep_caps)
Vector< MutableDrawingInfo > retrieve_editable_drawings(const Scene &scene, GreasePencil &grease_pencil)
std::unique_ptr< GreasePencilStrokeOperation > new_erase_operation(const bool temp_eraser)
static void simplify_opacities(blender::bke::CurvesGeometry &curves, const VArray< float > &opacities, const float epsilon)
static void remove_points_with_low_opacity(blender::bke::CurvesGeometry &curves, const VArray< float > &opacities, const float epsilon)
bke::CurvesGeometry remove_points_and_split(const bke::CurvesGeometry &curves, const IndexMask &mask)
T clamp(const T &a, const T &min, const T &max)
T distance(const T &a, const T &b)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T min(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
T max(const T &a, const T &b)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< float, 3 > float3
static float brush_strength(const Sculpt &sd, const blender::ed::sculpt_paint::StrokeCache &cache, const float feather, const PaintModeSettings &)
GreasePencilRuntimeHandle * runtime
struct ToolSettings * toolsettings
static int8_t intersections_segment_circle_integers(const int2 &s0, const int2 &s1, const int2 ¢er, const int64_t radius_2, int64_t &r_mu0, int64_t &r_mu1)
static constexpr float opacity_threshold
Vector< EraserRing > compute_piecewise_linear_falloff() const
int64_t eraser_squared_radius_pixels
bool soft_eraser(Object &ob, const blender::bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, blender::bke::CurvesGeometry &dst, const bool keep_caps)
bool hard_eraser(Object &ob, const bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, bke::CurvesGeometry &dst, const bool keep_caps) const
void execute(EraseOperation &self, const bContext &C, const InputSample &extension_sample)
static bool skip_strokes_with_locked_material(Object &ob, const int src_curve, const IndexRange &src_points, const VArray< int > stroke_material, const VArray< float > &point_opacity, Array< Vector< ed::greasepencil::PointTransferData > > &src_to_dst_points)
int2 mouse_position_pixels
int8_t segment_intersections_and_points_sides(const int2 &point, const int2 &point_after, const int64_t squared_radius, float &r_mu0, float &r_mu1, PointCircleSide &r_point_side, PointCircleSide &r_point_after_side) const
int curves_intersections_and_points_sides(const bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, const Span< EraserRing > rings, MutableSpan< std::pair< int, PointCircleSide > > r_point_ring, MutableSpan< SegmentCircleIntersection > r_intersections) const
EraseOperationExecutor(const bContext &)
bool stroke_eraser(Object &ob, const bke::CurvesGeometry &src, const Span< float2 > screen_space_positions, bke::CurvesGeometry &dst) const
bool inside_outside_intersection
void WM_event_add_notifier(const bContext *C, uint type, void *reference)