269 lines
9.9 KiB
C++
269 lines
9.9 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "BKE_attribute.hh"
|
|
#include "BKE_brush.hh"
|
|
#include "BKE_colortools.hh"
|
|
#include "BKE_context.hh"
|
|
#include "BKE_curves.hh"
|
|
#include "BKE_grease_pencil.hh"
|
|
#include "BKE_material.h"
|
|
|
|
#include "BLI_bounds.hh"
|
|
#include "BLI_length_parameterize.hh"
|
|
#include "BLI_math_color.h"
|
|
#include "BLI_math_geom.h"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "ED_curves.hh"
|
|
#include "ED_grease_pencil.hh"
|
|
#include "ED_view3d.hh"
|
|
|
|
#include "WM_api.hh"
|
|
#include "WM_types.hh"
|
|
|
|
#include "grease_pencil_intern.hh"
|
|
|
|
namespace blender::ed::sculpt_paint::greasepencil {
|
|
|
|
using ed::greasepencil::MutableDrawingInfo;
|
|
|
|
class TintOperation : public GreasePencilStrokeOperation {
|
|
public:
|
|
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override;
|
|
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override;
|
|
void on_stroke_done(const bContext &C) override;
|
|
|
|
private:
|
|
float radius_;
|
|
float strength_;
|
|
bool active_layer_only_;
|
|
ColorGeometry4f color_;
|
|
Vector<MutableDrawingInfo> drawings_;
|
|
Array<Array<float2>> screen_positions_per_drawing_;
|
|
|
|
void execute_tint(const bContext &C, const InputSample &extension_sample);
|
|
};
|
|
|
|
void TintOperation::on_stroke_begin(const bContext &C, const InputSample & /*start_sample*/)
|
|
{
|
|
using namespace blender::bke::greasepencil;
|
|
Scene *scene = CTX_data_scene(&C);
|
|
Paint *paint = BKE_paint_get_active_from_context(&C);
|
|
Brush *brush = BKE_paint_brush(paint);
|
|
|
|
BKE_curvemapping_init(brush->gpencil_settings->curve_sensitivity);
|
|
BKE_curvemapping_init(brush->gpencil_settings->curve_strength);
|
|
|
|
if (brush->gpencil_settings == nullptr) {
|
|
BKE_brush_init_gpencil_settings(brush);
|
|
}
|
|
BLI_assert(brush->gpencil_settings != nullptr);
|
|
|
|
BKE_curvemapping_init(brush->curve);
|
|
|
|
radius_ = BKE_brush_size_get(scene, brush);
|
|
strength_ = BKE_brush_alpha_get(scene, brush);
|
|
active_layer_only_ = ((brush->gpencil_settings->flag & GP_BRUSH_ACTIVE_LAYER_ONLY) != 0);
|
|
|
|
float4 color_linear;
|
|
color_linear[3] = 1.0f;
|
|
srgb_to_linearrgb_v3_v3(color_linear, brush->rgb);
|
|
|
|
color_ = ColorGeometry4f(color_linear);
|
|
|
|
Object *obact = CTX_data_active_object(&C);
|
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
|
|
|
|
if (active_layer_only_) {
|
|
/* Tint only on the drawings of the active layer. */
|
|
const Layer *active_layer = grease_pencil.get_active_layer();
|
|
if (!active_layer) {
|
|
return;
|
|
}
|
|
drawings_ = ed::greasepencil::retrieve_editable_drawings_from_layer(
|
|
*scene, grease_pencil, *active_layer);
|
|
}
|
|
else {
|
|
/* Tint on all editable drawings. */
|
|
drawings_ = ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
|
|
}
|
|
|
|
if (drawings_.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
ARegion *region = CTX_wm_region(&C);
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
|
|
Object *ob_eval = DEG_get_evaluated_object(depsgraph, obact);
|
|
|
|
screen_positions_per_drawing_.reinitialize(drawings_.size());
|
|
|
|
threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &drawing_info) {
|
|
const int drawing_index = (&drawing_info - drawings_.data());
|
|
|
|
bke::CurvesGeometry &strokes = drawing_info.drawing.strokes_for_write();
|
|
const Layer &layer = *grease_pencil.layers()[drawing_info.layer_index];
|
|
|
|
screen_positions_per_drawing_[drawing_index].reinitialize(strokes.points_num());
|
|
|
|
bke::crazyspace::GeometryDeformation deformation =
|
|
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
|
|
ob_eval, *obact, drawing_info.layer_index, drawing_info.frame_number);
|
|
|
|
for (const int point : strokes.points_range()) {
|
|
ED_view3d_project_float_global(
|
|
region,
|
|
math::transform_point(layer.to_world_space(*ob_eval), deformation.positions[point]),
|
|
screen_positions_per_drawing_[drawing_index][point],
|
|
V3D_PROJ_TEST_NOP);
|
|
}
|
|
});
|
|
}
|
|
|
|
void TintOperation::execute_tint(const bContext &C, const InputSample &extension_sample)
|
|
{
|
|
if (drawings_.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
using namespace blender::bke::greasepencil;
|
|
Scene *scene = CTX_data_scene(&C);
|
|
Object *obact = CTX_data_active_object(&C);
|
|
|
|
Paint *paint = &scene->toolsettings->gp_paint->paint;
|
|
Brush *brush = BKE_paint_brush(paint);
|
|
|
|
/* Get the tool's data. */
|
|
const float2 mouse_position = extension_sample.mouse_position;
|
|
float radius = radius_;
|
|
float strength = strength_;
|
|
if (BKE_brush_use_size_pressure(brush)) {
|
|
radius *= BKE_curvemapping_evaluateF(
|
|
brush->gpencil_settings->curve_sensitivity, 0, extension_sample.pressure);
|
|
}
|
|
if (BKE_brush_use_alpha_pressure(brush)) {
|
|
strength *= BKE_curvemapping_evaluateF(
|
|
brush->gpencil_settings->curve_strength, 0, extension_sample.pressure);
|
|
}
|
|
/* Attenuate factor to get a smoother tinting. */
|
|
float fill_strength = strength / 100.0f;
|
|
|
|
strength = math::clamp(strength, 0.0f, 1.0f);
|
|
fill_strength = math::clamp(fill_strength, 0.0f, 1.0f);
|
|
|
|
const bool tint_strokes = ((brush->gpencil_settings->vertex_mode == GPPAINT_MODE_STROKE) ||
|
|
(brush->gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH));
|
|
const bool tint_fills = ((brush->gpencil_settings->vertex_mode == GPPAINT_MODE_FILL) ||
|
|
(brush->gpencil_settings->vertex_mode == GPPAINT_MODE_BOTH));
|
|
|
|
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
|
|
|
|
std::atomic<bool> changed = false;
|
|
const auto execute_tint_on_drawing = [&](Drawing &drawing, const int drawing_index) {
|
|
bke::CurvesGeometry &strokes = drawing.strokes_for_write();
|
|
|
|
MutableSpan<ColorGeometry4f> vertex_colors = drawing.vertex_colors_for_write();
|
|
bke::MutableAttributeAccessor stroke_attributes = strokes.attributes_for_write();
|
|
bke::SpanAttributeWriter<ColorGeometry4f> fill_colors =
|
|
stroke_attributes.lookup_or_add_for_write_span<ColorGeometry4f>(
|
|
"fill_color",
|
|
bke::AttrDomain::Curve,
|
|
bke::AttributeInitVArray(VArray<ColorGeometry4f>::ForSingle(
|
|
ColorGeometry4f(float4(0.0f)), strokes.curves_num())));
|
|
OffsetIndices<int> points_by_curve = strokes.points_by_curve();
|
|
|
|
const Span<float2> screen_space_positions =
|
|
screen_positions_per_drawing_[drawing_index].as_span();
|
|
|
|
auto point_inside_stroke = [&](const Span<float2> points, const float2 mouse) {
|
|
std::optional<Bounds<float2>> bbox = bounds::min_max(points);
|
|
if (!bbox.has_value()) {
|
|
return false;
|
|
}
|
|
Bounds<float2> &box = bbox.value();
|
|
if (mouse.x < box.min.x || mouse.x > box.max.x || mouse.y < box.min.y || mouse.y > box.max.y)
|
|
{
|
|
return false;
|
|
}
|
|
return isect_point_poly_v2(
|
|
mouse, reinterpret_cast<const float(*)[2]>(points.data()), points.size());
|
|
};
|
|
|
|
threading::parallel_for(strokes.curves_range(), 128, [&](const IndexRange range) {
|
|
for (const int curve : range) {
|
|
bool stroke_touched = false;
|
|
for (const int curve_point : points_by_curve[curve].index_range()) {
|
|
if (tint_strokes) {
|
|
const int point = curve_point + points_by_curve[curve].first();
|
|
const float distance = math::distance(screen_space_positions[point], mouse_position);
|
|
const float influence = strength * BKE_brush_curve_strength(brush, distance, radius);
|
|
if (influence > 0.0f) {
|
|
stroke_touched = true;
|
|
/* Manually do an alpha-over mix, not using `ColorGeometry4f::premultiply_alpha`
|
|
* since the vertex color in GPv3 is stored as straight alpha (which is technically
|
|
* `ColorPaint4f`). */
|
|
float4 premultiplied;
|
|
straight_to_premul_v4_v4(premultiplied, vertex_colors[point]);
|
|
float4 rgba = float4(
|
|
math::interpolate(float3(premultiplied), float3(color_), influence),
|
|
vertex_colors[point][3]);
|
|
rgba[3] = rgba[3] * (1.0f - influence) + influence;
|
|
premul_to_straight_v4_v4(vertex_colors[point], rgba);
|
|
}
|
|
}
|
|
}
|
|
if (tint_fills && !fill_colors.span.is_empty()) {
|
|
/* Will tint fill color when either the brush being inside the fill region or touching
|
|
* the stroke. */
|
|
const bool fill_effective = stroke_touched ||
|
|
point_inside_stroke(screen_space_positions.slice(
|
|
points_by_curve[curve].first(),
|
|
points_by_curve[curve].size()),
|
|
mouse_position);
|
|
if (fill_effective) {
|
|
float4 premultiplied;
|
|
straight_to_premul_v4_v4(premultiplied, fill_colors.span[curve]);
|
|
float4 rgba = float4(
|
|
math::interpolate(float3(premultiplied), float3(color_), fill_strength),
|
|
fill_colors.span[curve][3]);
|
|
rgba[3] = rgba[3] * (1.0f - fill_strength) + fill_strength;
|
|
premul_to_straight_v4_v4(fill_colors.span[curve], rgba);
|
|
stroke_touched = true;
|
|
}
|
|
}
|
|
if (stroke_touched) {
|
|
changed.store(true, std::memory_order_relaxed);
|
|
}
|
|
}
|
|
});
|
|
fill_colors.finish();
|
|
};
|
|
|
|
threading::parallel_for_each(drawings_, [&](const MutableDrawingInfo &info) {
|
|
const int drawing_index = (&info - drawings_.data());
|
|
execute_tint_on_drawing(info.drawing, drawing_index);
|
|
});
|
|
|
|
if (changed) {
|
|
DEG_id_tag_update(&grease_pencil.id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
|
|
}
|
|
}
|
|
|
|
void TintOperation::on_stroke_extended(const bContext &C, const InputSample &extension_sample)
|
|
{
|
|
execute_tint(C, extension_sample);
|
|
}
|
|
|
|
void TintOperation::on_stroke_done(const bContext & /*C*/) {}
|
|
|
|
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation()
|
|
{
|
|
return std::make_unique<TintOperation>();
|
|
}
|
|
|
|
} // namespace blender::ed::sculpt_paint::greasepencil
|