diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc index 270ec170f1a..6e0b8ae7c18 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -18,6 +18,7 @@ #include "BLI_math_base.hh" #include "BLI_math_color.h" #include "BLI_math_geom.h" +#include "BLI_rand.hh" #include "DEG_depsgraph_query.hh" @@ -125,15 +126,114 @@ static void morph_points_to_curve(Span src, Span target, Mutable dst.last() = src.last(); } +/** + * Creates a new curve with one point at the beginning or end. + * \note Does not initialize the new curve or points. + */ +static void create_blank_curve(bke::CurvesGeometry &curves, const bool on_back) +{ + if (!on_back) { + const int num_old_points = curves.points_num(); + curves.resize(curves.points_num() + 1, curves.curves_num() + 1); + curves.offsets_for_write().last(1) = num_old_points; + return; + } + + curves.resize(curves.points_num() + 1, curves.curves_num() + 1); + MutableSpan offsets = curves.offsets_for_write(); + offsets.first() = 0; + + /* Loop through backwards to not overwrite the data. */ + for (int i = curves.curves_num() - 2; i >= 0; i--) { + offsets[i + 1] = offsets[i] + 1; + } + + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + + attributes.for_all( + [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { + bke::GSpanAttributeWriter dst = attributes.lookup_for_write_span(id); + + GMutableSpan attribute_data = dst.span; + + bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { + using T = decltype(dummy); + MutableSpan span_data = attribute_data.typed(); + + /* Loop through backwards to not overwrite the data. */ + for (int i = span_data.size() - 2; i >= 0; i--) { + span_data[i + 1] = span_data[i]; + } + }); + dst.finish(); + return true; + }); +} + +/** + * Extends the first or last curve by `new_points_num` number of points. + * \note Does not initialize the new points. + */ +static void extend_curve(bke::CurvesGeometry &curves, const bool on_back, const int new_points_num) +{ + if (!on_back) { + curves.resize(curves.points_num() + new_points_num, curves.curves_num()); + curves.offsets_for_write().last() = curves.points_num(); + return; + } + + const int last_active_point = curves.points_by_curve()[0].last(); + + curves.resize(curves.points_num() + new_points_num, curves.curves_num()); + MutableSpan offsets = curves.offsets_for_write(); + + for (const int src_curve : curves.curves_range().drop_front(1)) { + offsets[src_curve] = offsets[src_curve] + new_points_num; + } + offsets.last() = curves.points_num(); + + bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); + + attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { + if (meta_data.domain != bke::AttrDomain::Point) { + return true; + } + + bke::GSpanAttributeWriter dst = attributes.lookup_for_write_span(id); + GMutableSpan attribute_data = dst.span; + + bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { + using T = decltype(dummy); + MutableSpan span_data = attribute_data.typed(); + + /* Loop through backwards to not overwrite the data. */ + for (int i = (span_data.size() - 1) - new_points_num; i >= last_active_point; i--) { + span_data[i + new_points_num] = span_data[i]; + } + }); + dst.finish(); + return true; + }); + + curves.tag_topology_changed(); +} + class PaintOperation : public GreasePencilStrokeOperation { private: /* Screen space coordinates from input samples. */ Vector screen_space_coords_orig_; + /* Temporary vector of curve fitted screen space coordinates per input sample from the active - * smoothing window. */ + * smoothing window. The length of this depends on `active_smooth_start_index_`. */ Vector> screen_space_curve_fitted_coords_; + /* Temporary vector of screen space offsets */ + Vector screen_space_jitter_offsets_; + /* Screen space coordinates after smoothing. */ Vector screen_space_smoothed_coords_; + /* Screen space coordinates after smoothing and jittering. */ + Vector screen_space_final_coords_; + /* The start index of the smoothing window. */ int active_smooth_start_index_ = 0; blender::float4x2 texture_space_ = float4x2::identity(); @@ -141,8 +241,10 @@ class PaintOperation : public GreasePencilStrokeOperation { /* Helper class to project screen space coordinates to 3d. */ ed::greasepencil::DrawingPlacement placement_; - /* Angle factor smoothed over time. */ - float smoothed_angle_factor_ = 1.0f; + /* Direction the pen is moving in smoothed over time. */ + float2 smoothed_pen_direction_ = float2(0.0f); + + RandomNumberGenerator rng; friend struct PaintOperationExecutor; @@ -167,6 +269,8 @@ struct PaintOperationExecutor { std::optional fill_color_; float softness_; + bool use_settings_random_; + bke::greasepencil::Drawing *drawing_; PaintOperationExecutor(const bContext &C) @@ -179,6 +283,7 @@ struct PaintOperationExecutor { brush_ = BKE_paint_brush(paint); settings_ = brush_->gpencil_settings; + use_settings_random_ = (settings_->flag & GP_BRUSH_GROUP_RANDOM) != 0; const bool use_vertex_color = (scene_->toolsettings->gp_paint->mode == GPPAINT_FLAG_USE_VERTEXCOLOR); if (use_vertex_color) { @@ -203,100 +308,6 @@ struct PaintOperationExecutor { BLI_assert(drawing_ != nullptr); } - /** - * Creates a new curve with one point at the beginning or end. - * \note Does not initialize the new curve or points. - */ - static void create_blank_curve(bke::CurvesGeometry &curves, const bool on_back) - { - if (!on_back) { - const int num_old_points = curves.points_num(); - curves.resize(curves.points_num() + 1, curves.curves_num() + 1); - curves.offsets_for_write().last(1) = num_old_points; - return; - } - - curves.resize(curves.points_num() + 1, curves.curves_num() + 1); - MutableSpan offsets = curves.offsets_for_write(); - offsets.first() = 0; - - /* Loop through backwards to not overwrite the data. */ - for (int i = curves.curves_num() - 2; i >= 0; i--) { - offsets[i + 1] = offsets[i] + 1; - } - - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - - attributes.for_all( - [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { - bke::GSpanAttributeWriter dst = attributes.lookup_for_write_span(id); - - GMutableSpan attribute_data = dst.span; - - bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { - using T = decltype(dummy); - MutableSpan span_data = attribute_data.typed(); - - /* Loop through backwards to not overwrite the data. */ - for (int i = span_data.size() - 2; i >= 0; i--) { - span_data[i + 1] = span_data[i]; - } - }); - dst.finish(); - return true; - }); - } - - /** - * Extends the first or last curve by `new_points_num` number of points. - * \note Does not initialize the new points. - */ - static void extend_curve(bke::CurvesGeometry &curves, - const bool on_back, - const int new_points_num) - { - if (!on_back) { - curves.resize(curves.points_num() + new_points_num, curves.curves_num()); - curves.offsets_for_write().last() = curves.points_num(); - return; - } - - const int last_active_point = curves.points_by_curve()[0].last(); - - curves.resize(curves.points_num() + new_points_num, curves.curves_num()); - MutableSpan offsets = curves.offsets_for_write(); - - for (const int src_curve : curves.curves_range().drop_front(1)) { - offsets[src_curve] = offsets[src_curve] + new_points_num; - } - offsets.last() = curves.points_num(); - - bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); - - attributes.for_all([&](const bke::AttributeIDRef &id, const bke::AttributeMetaData meta_data) { - if (meta_data.domain != bke::AttrDomain::Point) { - return true; - } - - bke::GSpanAttributeWriter dst = attributes.lookup_for_write_span(id); - GMutableSpan attribute_data = dst.span; - - bke::attribute_math::convert_to_static_type(attribute_data.type(), [&](auto dummy) { - using T = decltype(dummy); - MutableSpan span_data = attribute_data.typed(); - - /* Loop through backwards to not overwrite the data. */ - for (int i = (span_data.size() - 1) - new_points_num; i >= last_active_point; i--) { - span_data[i + new_points_num] = span_data[i]; - } - }); - dst.finish(); - return true; - }); - - curves.tag_topology_changed(); - } - /* Attributes that are defined explicitly and should not be copied from original geometry. */ Set skipped_attribute_ids(const bke::AttrDomain domain) const { @@ -352,7 +363,9 @@ struct PaintOperationExecutor { self.screen_space_coords_orig_.append(start_coords); self.screen_space_curve_fitted_coords_.append(Vector({start_coords})); + self.screen_space_jitter_offsets_.append(float2(0.0f)); self.screen_space_smoothed_coords_.append(start_coords); + self.screen_space_final_coords_.append(start_coords); /* Resize the curves geometry so there is one more curve with a single point. */ bke::CurvesGeometry &curves = drawing_->strokes_for_write(); @@ -419,9 +432,7 @@ struct PaintOperationExecutor { drawing_->tag_topology_changed(); } - void active_smoothing(PaintOperation &self, - const IndexRange smooth_window, - MutableSpan curve_positions) + void active_smoothing(PaintOperation &self, const IndexRange smooth_window) { const Span coords_to_smooth = self.screen_space_coords_orig_.as_span().slice( smooth_window); @@ -468,7 +479,6 @@ struct PaintOperationExecutor { MutableSpan window_coords = self.screen_space_smoothed_coords_.as_mutable_span().slice( smooth_window); - MutableSpan positions_slice = curve_positions.slice(smooth_window); const float converging_threshold_px = 0.1f; bool stop_counting_converged = false; int num_converged = 0; @@ -492,7 +502,6 @@ struct PaintOperationExecutor { /* Update the positions in the current cache. */ window_coords[window_i] = new_pos; - positions_slice[window_i] = self.placement_.project(new_pos); } /* Remove all the converged points from the active window and shrink the window accordingly. */ @@ -502,15 +511,48 @@ struct PaintOperationExecutor { } } + void active_jitter(PaintOperation &self, + const int new_points_num, + const float brush_radius_px, + const float pressure, + const IndexRange active_window, + MutableSpan curve_positions) + { + float jitter_factor = 1.0f; + if (settings_->flag & GP_BRUSH_USE_JITTER_PRESSURE) { + jitter_factor = BKE_curvemapping_evaluateF(settings_->curve_jitter, 0, pressure); + } + const float2 tangent = math::normalize(self.smoothed_pen_direction_); + const float2 cotangent = float2(-tangent.y, tangent.x); + for ([[maybe_unused]] const int _ : IndexRange(new_points_num)) { + const float rand = self.rng.get_float() * 2.0f - 1.0f; + const float factor = rand * settings_->draw_jitter * jitter_factor; + self.screen_space_jitter_offsets_.append(cotangent * factor * brush_radius_px); + } + const Span jitter_slice = self.screen_space_jitter_offsets_.as_mutable_span().slice( + active_window); + MutableSpan smoothed_coords = + self.screen_space_smoothed_coords_.as_mutable_span().slice(active_window); + MutableSpan final_coords = self.screen_space_final_coords_.as_mutable_span().slice( + active_window); + MutableSpan positions_slice = curve_positions.slice(active_window); + for (const int64_t window_i : active_window.index_range()) { + final_coords[window_i] = smoothed_coords[window_i] + jitter_slice[window_i]; + positions_slice[window_i] = self.placement_.project(final_coords[window_i]); + } + } + void process_extension_sample(PaintOperation &self, const bContext &C, const InputSample &extension_sample) { - const float2 coords = extension_sample.mouse_position; + Scene *scene = CTX_data_scene(&C); const RegionView3D *rv3d = CTX_wm_region_view3d(&C); const ARegion *region = CTX_wm_region(&C); + const bool on_back = (scene->toolsettings->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) != 0; - const float3 position = self.placement_.project(coords); + const float2 coords = extension_sample.mouse_position; + float3 position = self.placement_.project(coords); float radius = ed::greasepencil::radius_from_input_sample(rv3d, region, brush_, @@ -518,17 +560,18 @@ struct PaintOperationExecutor { position, self.placement_.to_world_space(), settings_); + const float brush_radius_px = brush_radius_to_pixel_radius( + rv3d, brush_, math::transform_point(self.placement_.to_world_space(), position)); const float opacity = ed::greasepencil::opacity_from_input_sample( extension_sample.pressure, brush_, settings_); - Scene *scene = CTX_data_scene(&C); - const bool on_back = (scene->toolsettings->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) != 0; bke::CurvesGeometry &curves = drawing_->strokes_for_write(); + OffsetIndices points_by_curve = curves.points_by_curve(); bke::MutableAttributeAccessor attributes = curves.attributes_for_write(); const int active_curve = on_back ? curves.curves_range().first() : curves.curves_range().last(); - const IndexRange curve_points = curves.points_by_curve()[active_curve]; + const IndexRange curve_points = points_by_curve[active_curve]; const int last_active_point = curve_points.last(); const float2 prev_coords = self.screen_space_coords_orig_.last(); @@ -536,29 +579,31 @@ struct PaintOperationExecutor { const float prev_opacity = drawing_->opacities()[last_active_point]; const ColorGeometry4f prev_vertex_color = drawing_->vertex_colors()[last_active_point]; + /* Use the vector from the previous to the next point and then interpolate it with the previous + * direction to get a smoothed value over time. */ + self.smoothed_pen_direction_ = math::interpolate( + self.smoothed_pen_direction_, coords - self.screen_space_coords_orig_.last(), 0.1f); + /* Approximate brush with non-circular shape by changing the radius based on the angle. */ if (settings_->draw_angle_factor > 0.0f) { const float angle = settings_->draw_angle; const float2 angle_vec = float2(math::cos(angle), math::sin(angle)); - const float2 vec = coords - self.screen_space_coords_orig_.last(); /* `angle_factor` is the angle to the horizontal line in screen space. */ - const float angle_factor = 1.0f - math::abs(math::dot(angle_vec, math::normalize(vec))); - /* Smooth the angle factor over time. */ - self.smoothed_angle_factor_ = math::interpolate( - self.smoothed_angle_factor_, angle_factor, 0.1f); + const float angle_factor = + 1.0f - math::abs(math::dot(angle_vec, math::normalize(self.smoothed_pen_direction_))); /* Influence is controlled by `draw_angle_factor`. */ const float radius_factor = math::interpolate( - 1.0f, self.smoothed_angle_factor_, settings_->draw_angle_factor); + 1.0f, angle_factor, settings_->draw_angle_factor); radius *= radius_factor; } /* Overwrite last point if it's very close. */ - const IndexRange points_range = curves.points_by_curve()[curves.curves_range().last()]; - const bool is_first_sample = (points_range.size() == 1); + const bool is_first_sample = (curve_points.size() == 1); constexpr float point_override_threshold_px = 2.0f; - if (math::distance(coords, prev_coords) < point_override_threshold_px) { + const float distance_px = math::distance(coords, prev_coords); + if (distance_px < point_override_threshold_px) { /* Don't move the first point of the stroke. */ if (!is_first_sample) { curves.positions_for_write()[last_active_point] = position; @@ -568,22 +613,19 @@ struct PaintOperationExecutor { return; } - /* If the next sample is far away, we subdivide the segment to add more points. */ - const float distance_px = math::distance(coords, prev_coords); - const float brush_radius_px = brush_radius_to_pixel_radius( - rv3d, brush_, math::transform_point(self.placement_.to_world_space(), position)); /* Clamp the number of points within a pixel in screen space. */ constexpr int max_points_per_pixel = 4; /* The value `brush_->spacing` is a percentage of the brush radius in pixels. */ const float max_spacing_px = math::max((float(brush_->spacing) / 100.0f) * float(brush_radius_px), 1.0f / float(max_points_per_pixel)); + /* If the next sample is far away, we subdivide the segment to add more points. */ const int new_points_num = (distance_px > max_spacing_px) ? int(math::floor(distance_px / max_spacing_px)) : 1; - /* Resize the curves geometry. */ extend_curve(curves, on_back, new_points_num); + /* Subdivide stroke in new_points. */ const IndexRange new_points = curves.points_by_curve()[active_curve].take_back(new_points_num); Array new_screen_space_coords(new_points_num); @@ -604,6 +646,7 @@ struct PaintOperationExecutor { /* Update screen space buffers with new points. */ self.screen_space_coords_orig_.extend(new_screen_space_coords); self.screen_space_smoothed_coords_.extend(new_screen_space_coords); + self.screen_space_final_coords_.extend(new_screen_space_coords); for (float2 new_position : new_screen_space_coords) { self.screen_space_curve_fitted_coords_.append(Vector({new_position})); } @@ -617,8 +660,29 @@ struct PaintOperationExecutor { } else { /* Active smoothing is done in a window at the end of the new stroke. */ - this->active_smoothing( - self, smooth_window, positions.slice(curves.points_by_curve()[active_curve])); + this->active_smoothing(self, smooth_window); + } + + MutableSpan curve_positions = positions.slice(curves.points_by_curve()[active_curve]); + if (use_settings_random_ && settings_->draw_jitter > 0.0f) { + this->active_jitter(self, + new_points_num, + brush_radius_px, + extension_sample.pressure, + smooth_window, + curve_positions); + } + else { + MutableSpan smoothed_coords = + self.screen_space_smoothed_coords_.as_mutable_span().slice(smooth_window); + MutableSpan final_coords = self.screen_space_final_coords_.as_mutable_span().slice( + smooth_window); + /* Not jitter, so we just copy the positions over. */ + final_coords.copy_from(smoothed_coords); + MutableSpan curve_positions_slice = curve_positions.slice(smooth_window); + for (const int64_t window_i : smooth_window.index_range()) { + curve_positions_slice[window_i] = self.placement_.project(final_coords[window_i]); + } } /* Initialize the rest of the attributes with default values. */ @@ -953,7 +1017,7 @@ void PaintOperation::on_stroke_done(const bContext &C) } if (settings->simplify_px > 0.0f) { simplify_stroke(drawing, - this->screen_space_smoothed_coords_.as_span().drop_back(num_points_removed), + this->screen_space_final_coords_.as_span().drop_back(num_points_removed), settings->simplify_px, active_curve); } diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index b5b770aa50f..9edd0ab541d 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -1490,8 +1490,9 @@ static void rna_def_gpencil_options(BlenderRNA *brna) /* Jitter factor for new strokes */ prop = RNA_def_property(srna, "pen_jitter", PROP_FLOAT, PROP_FACTOR); RNA_def_property_float_sdna(prop, nullptr, "draw_jitter"); - RNA_def_property_range(prop, 0.0f, 1.0f); - RNA_def_property_ui_text(prop, "Jitter", "Jitter factor for new strokes"); + RNA_def_property_range(prop, 0.0f, 100.0f); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.001, 3); + RNA_def_property_ui_text(prop, "Jitter", "Jitter factor of brush radius for new strokes"); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_BRUSH); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, nullptr);