From cde64dbdd5df60885407850d6cc4ce18a70dfefa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20T=C3=B6nne?= Date: Tue, 21 Jan 2025 13:04:48 +0100 Subject: [PATCH] Fix #129145: Grease Pencil: Stroke depth detection and interpolation for drawing and primitives GPv2 used the depth buffer at the end of a stroke drawing operation to project points and interpolated between detected values. This does not work in GPv3 because the strokes are added directly to drawings. Depth projection has to happen continuously, updating points between the last depth value and the next when a new hit is recorded. Resolves #125258. Pull Request: https://projects.blender.org/blender/blender/pulls/131842 --- .../draw/engines/gpencil/gpencil_engine.h | 2 + .../draw/engines/gpencil/gpencil_engine_c.cc | 5 +- .../intern/grease_pencil_primitive.cc | 3 +- .../intern/grease_pencil_utils.cc | 100 ++++--- .../editors/include/ED_grease_pencil.hh | 25 +- .../sculpt_paint/grease_pencil_fill.cc | 6 +- .../sculpt_paint/grease_pencil_paint.cc | 259 +++++++++++++++++- source/blender/makesdna/DNA_view3d_types.h | 2 + 8 files changed, 339 insertions(+), 63 deletions(-) diff --git a/source/blender/draw/engines/gpencil/gpencil_engine.h b/source/blender/draw/engines/gpencil/gpencil_engine.h index a8263d4d2fa..0306fc03c14 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine.h +++ b/source/blender/draw/engines/gpencil/gpencil_engine.h @@ -327,6 +327,8 @@ typedef struct GPENCIL_PrivateData { int mask_invert; /* Vertex Paint opacity. */ float vertex_paint_opacity; + /* Force 3D depth rendering. */ + bool force_stroke_order_3d; } GPENCIL_PrivateData; /* geometry batch cache functions */ diff --git a/source/blender/draw/engines/gpencil/gpencil_engine_c.cc b/source/blender/draw/engines/gpencil/gpencil_engine_c.cc index 6e369ce2ac4..df609c1cac5 100644 --- a/source/blender/draw/engines/gpencil/gpencil_engine_c.cc +++ b/source/blender/draw/engines/gpencil/gpencil_engine_c.cc @@ -142,6 +142,7 @@ void GPENCIL_engine_init(void *ved) const bool shmode_xray_support = v3d->shading.type <= OB_SOLID; stl->pd->xray_alpha = (shmode_xray_support && XRAY_ENABLED(v3d)) ? XRAY_ALPHA(v3d) : 1.0f; + stl->pd->force_stroke_order_3d = v3d->gp_flag & V3D_GP_FORCE_STROKE_ORDER_3D; } else if (stl->pd->is_render) { use_scene_lights = true; @@ -149,6 +150,7 @@ void GPENCIL_engine_init(void *ved) stl->pd->use_multiedit_lines_only = false; stl->pd->xray_alpha = 1.0f; stl->pd->v3d_color_type = -1; + stl->pd->force_stroke_order_3d = false; } stl->pd->use_lighting = (v3d && v3d->shading.type > OB_SOLID) || stl->pd->is_render; @@ -379,7 +381,8 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate( const bool do_multi_frame = (((pd->scene->toolsettings->gpencil_flags & GP_USE_MULTI_FRAME_EDITING) != 0) && (ob->mode != OB_MODE_OBJECT)); - const bool use_stroke_order_3d = (grease_pencil.flag & GREASE_PENCIL_STROKE_ORDER_3D) != 0; + const bool use_stroke_order_3d = pd->force_stroke_order_3d || + ((grease_pencil.flag & GREASE_PENCIL_STROKE_ORDER_3D) != 0); GPENCIL_tObject *tgp_ob = gpencil_object_cache_add(pd, ob, use_stroke_order_3d, bounds); int mat_ofs = 0; diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_primitive.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_primitive.cc index a958cae0eaf..e535449d261 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_primitive.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_primitive.cc @@ -661,9 +661,8 @@ static int grease_pencil_primitive_invoke(bContext *C, wmOperator *op, const wmE if (placement.use_project_to_surface()) { placement.cache_viewport_depths(CTX_data_depsgraph_pointer(C), vc.region, view3d); } - else if (placement.use_project_to_nearest_stroke()) { + else if (placement.use_project_to_stroke()) { placement.cache_viewport_depths(CTX_data_depsgraph_pointer(C), vc.region, view3d); - placement.set_origin_to_nearest_stroke(start_coords); } ptd.placement = placement; diff --git a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc index 9fdc61c9ffe..de8851c3fd9 100644 --- a/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc +++ b/source/blender/editors/grease_pencil/intern/grease_pencil_utils.cc @@ -95,7 +95,7 @@ DrawingPlacement::DrawingPlacement(const Scene &scene, placement_loc_ = layer_space_to_world_space_.location(); } else if (align_flag & GP_PROJECT_DEPTH_STROKE) { - depth_ = DrawingPlacementDepth::NearestStroke; + depth_ = DrawingPlacementDepth::Stroke; surface_offset_ = 0.0f; /* Default to view placement with the object origin if we don't hit a stroke. */ placement_loc_ = layer_space_to_world_space_.location(); @@ -113,7 +113,8 @@ DrawingPlacement::DrawingPlacement(const Scene &scene, } if (plane_ != DrawingPlacementPlane::View) { - plane_from_point_normal_v3(placement_plane_, placement_loc_, placement_normal_); + placement_plane_ = float4(); + plane_from_point_normal_v3(*placement_plane_, placement_loc_, placement_normal_); } } @@ -190,7 +191,8 @@ DrawingPlacement::DrawingPlacement(const Scene &scene, } if (plane_ != DrawingPlacementPlane::View) { - plane_from_point_normal_v3(placement_plane_, placement_loc_, placement_normal_); + placement_plane_ = float4(); + plane_from_point_normal_v3(*placement_plane_, placement_loc_, placement_normal_); } } @@ -271,13 +273,14 @@ bool DrawingPlacement::use_project_to_surface() const return depth_ == DrawingPlacementDepth::Surface; } -bool DrawingPlacement::use_project_to_nearest_stroke() const +bool DrawingPlacement::use_project_to_stroke() const { - return depth_ == DrawingPlacementDepth::NearestStroke; + return depth_ == DrawingPlacementDepth::Stroke; } void DrawingPlacement::cache_viewport_depths(Depsgraph *depsgraph, ARegion *region, View3D *view3d) { + const short previous_gp_flag = view3d->gp_flag; eV3DDepthOverrideMode mode = V3D_DEPTH_GPENCIL_ONLY; if (use_project_to_surface()) { @@ -288,40 +291,52 @@ void DrawingPlacement::cache_viewport_depths(Depsgraph *depsgraph, ARegion *regi mode = V3D_DEPTH_NO_GPENCIL; } } + if (use_project_to_stroke()) { + /* Enforce render engine to use 3D stroke order, otherwise depth buffer values are not in 3D + * space. */ + view3d->gp_flag |= V3D_GP_FORCE_STROKE_ORDER_3D; + } + ED_view3d_depth_override(depsgraph, region, view3d, nullptr, mode, false, &this->depth_cache_); + + view3d->gp_flag = previous_gp_flag; } -void DrawingPlacement::set_origin_to_nearest_stroke(const float2 co) +std::optional DrawingPlacement::project_depth(const float2 co) const { - BLI_assert(depth_cache_ != nullptr); - float depth; - if (ED_view3d_depth_read_cached(depth_cache_, int2(co), 4, &depth)) { - float3 origin; - ED_view3d_depth_unproject_v3(region_, int2(co), depth, origin); - - placement_loc_ = origin; + std::optional depth = get_depth(co); + if (!depth) { + return std::nullopt; } - else { - /* If nothing was hit, use origin. */ - placement_loc_ = layer_space_to_world_space_.location(); - } - plane_from_point_normal_v3(placement_plane_, placement_loc_, placement_normal_); -} -float3 DrawingPlacement::project_depth(const float2 co) const -{ float3 proj_point; - float depth; - if (depth_cache_ != nullptr && ED_view3d_depth_read_cached(depth_cache_, int2(co), 4, &depth)) { - ED_view3d_depth_unproject_v3(region_, int2(co), depth, proj_point); + if (ED_view3d_depth_unproject_v3(region_, int2(co), *depth, proj_point)) { float3 view_normal; ED_view3d_win_to_vector(region_, co, view_normal); proj_point -= view_normal * surface_offset_; + return proj_point; } - else { - /* Fallback to `View` placement. */ - ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point); + return std::nullopt; +} + +std::optional DrawingPlacement::get_depth(float2 co) const +{ + float depth; + if (depth_cache_ != nullptr && ED_view3d_depth_read_cached(depth_cache_, int2(co), 4, &depth)) { + return depth; } + return std::nullopt; +} + +float3 DrawingPlacement::try_project_depth(const float2 co) const +{ + if (std::optional proj_point = this->project_depth(co)) { + return *proj_point; + } + + float3 proj_point; + /* Fallback to `View` placement. */ + ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point); return proj_point; } @@ -330,14 +345,14 @@ float3 DrawingPlacement::project(const float2 co) const float3 proj_point; if (depth_ == DrawingPlacementDepth::Surface) { /* Project using the viewport depth cache. */ - proj_point = this->project_depth(co); + proj_point = this->try_project_depth(co); } else { - if (plane_ == DrawingPlacementPlane::View) { - ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point); + if (placement_plane_) { + ED_view3d_win_to_3d_on_plane(region_, *placement_plane_, co, false, proj_point); } else { - ED_view3d_win_to_3d_on_plane(region_, placement_plane_, co, false, proj_point); + ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point); } } return math::transform_point(world_space_to_layer_space_, proj_point); @@ -348,14 +363,14 @@ float3 DrawingPlacement::project_with_shift(const float2 co) const float3 proj_point; if (depth_ == DrawingPlacementDepth::Surface) { /* Project using the viewport depth cache. */ - proj_point = this->project_depth(co); + proj_point = this->try_project_depth(co); } else { - if (plane_ == DrawingPlacementPlane::View) { - ED_view3d_win_to_3d_with_shift(view3d_, region_, placement_loc_, co, proj_point); + if (placement_plane_) { + ED_view3d_win_to_3d_on_plane(region_, *placement_plane_, co, false, proj_point); } else { - ED_view3d_win_to_3d_on_plane(region_, placement_plane_, co, false, proj_point); + ED_view3d_win_to_3d_with_shift(view3d_, region_, placement_loc_, co, proj_point); } } return math::transform_point(world_space_to_layer_space_, proj_point); @@ -370,6 +385,13 @@ void DrawingPlacement::project(const Span src, MutableSpan dst) }); } +float3 DrawingPlacement::place(const float2 co, const float depth) const +{ + float3 loc; + ED_view3d_unproject_v3(region_, co.x, co.y, depth, loc); + return math::transform_point(world_space_to_layer_space_, loc); +} + float3 DrawingPlacement::reproject(const float3 pos) const { const float3 world_pos = math::transform_point(layer_space_to_world_space_, pos); @@ -382,7 +404,7 @@ float3 DrawingPlacement::reproject(const float3 pos) const return pos; } /* Project using the viewport depth cache. */ - proj_point = this->project_depth(co); + proj_point = this->try_project_depth(co); } else { /* Reproject the point onto the `placement_plane_` from the current view. */ @@ -396,11 +418,11 @@ float3 DrawingPlacement::reproject(const float3 pos) const ray_no = -float3(rv3d->viewinv[2]); } float4 plane; - if (plane_ == DrawingPlacementPlane::View) { - plane_from_point_normal_v3(plane, placement_loc_, rv3d->viewinv[2]); + if (placement_plane_) { + plane = *placement_plane_; } else { - plane = placement_plane_; + plane_from_point_normal_v3(plane, placement_loc_, rv3d->viewinv[2]); } float lambda; diff --git a/source/blender/editors/include/ED_grease_pencil.hh b/source/blender/editors/include/ED_grease_pencil.hh index 07be388073b..0a9e241319a 100644 --- a/source/blender/editors/include/ED_grease_pencil.hh +++ b/source/blender/editors/include/ED_grease_pencil.hh @@ -114,7 +114,7 @@ namespace blender::ed::greasepencil { enum class ReprojectMode : int8_t { Front, Side, Top, View, Cursor, Surface, Keep }; -enum class DrawingPlacementDepth : int8_t { ObjectOrigin, Cursor, Surface, NearestStroke }; +enum class DrawingPlacementDepth : int8_t { ObjectOrigin, Cursor, Surface, Stroke }; enum class DrawingPlacementPlane : int8_t { View, Front, Side, Top, Cursor }; @@ -130,7 +130,8 @@ class DrawingPlacement { float3 placement_loc_; float3 placement_normal_; - float4 placement_plane_; + /* Optional explicit placement plane. */ + std::optional placement_plane_; float4x4 layer_space_to_world_space_; float4x4 world_space_to_layer_space_; @@ -162,10 +163,15 @@ class DrawingPlacement { public: bool use_project_to_surface() const; - bool use_project_to_nearest_stroke() const; + bool use_project_to_stroke() const; void cache_viewport_depths(Depsgraph *depsgraph, ARegion *region, View3D *view3d); - void set_origin_to_nearest_stroke(float2 co); + + /** + * Attempt to project from the depth buffer. + * \return Un-projected position if a valid depth is found at the screen position. + */ + std::optional project_depth(float2 co) const; /** * Projects a screen space coordinate to the local drawing space. @@ -177,6 +183,11 @@ class DrawingPlacement { */ float3 project_with_shift(float2 co) const; + /** + * Convert a screen space coordinate with depth to the local drawing space. + */ + float3 place(float2 co, float depth) const; + /** * Projects a 3D position (in local space) to the drawing plane. */ @@ -185,8 +196,12 @@ class DrawingPlacement { float4x4 to_world_space() const; + /** Return depth buffer if possible. */ + std::optional get_depth(float2 co) const; + private: - float3 project_depth(float2 co) const; + /** Return depth buffer projection if possible or "View" placement fallback. */ + float3 try_project_depth(float2 co) const; }; void set_selected_frames_type(bke::greasepencil::Layer &layer, diff --git a/source/blender/editors/sculpt_paint/grease_pencil_fill.cc b/source/blender/editors/sculpt_paint/grease_pencil_fill.cc index 8faa0834084..5c1bca1c253 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_fill.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_fill.cc @@ -1151,13 +1151,9 @@ bke::CurvesGeometry fill_strokes(const ViewContext &view_context, pixel_scale); ed::greasepencil::DrawingPlacement placement(scene, region, view3d, object_eval, &layer); - if (placement.use_project_to_surface()) { + if (placement.use_project_to_surface() || placement.use_project_to_stroke()) { placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); } - else if (placement.use_project_to_nearest_stroke()) { - placement.cache_viewport_depths(&depsgraph, ®ion, &view3d); - placement.set_origin_to_nearest_stroke(fill_point); - } Image *ima = render_strokes(view_context, brush, diff --git a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc index 12a1af4f7cb..282633bc828 100644 --- a/source/blender/editors/sculpt_paint/grease_pencil_paint.cc +++ b/source/blender/editors/sculpt_paint/grease_pencil_paint.cc @@ -233,6 +233,8 @@ class PaintOperation : public GreasePencilStrokeOperation { Vector> screen_space_curve_fitted_coords_; /* Temporary vector of screen space offsets */ Vector screen_space_jitter_offsets_; + /* Projection planes for every point in "Stroke" placement mode. */ + Vector> stroke_placement_depths_; /* Screen space coordinates after smoothing. */ Vector screen_space_smoothed_coords_; @@ -245,6 +247,10 @@ class PaintOperation : public GreasePencilStrokeOperation { /* Helper class to project screen space coordinates to 3d. */ ed::greasepencil::DrawingPlacement placement_; + /* Last valid stroke intersection, for use in Stroke projection mode. */ + std::optional last_stroke_placement_depth_; + /* Point index of the last valid stroke placement. */ + std::optional last_stroke_placement_point_; /* Direction the pen is moving in smoothed over time. */ float2 smoothed_pen_direction_ = float2(0.0f); @@ -278,6 +284,13 @@ class PaintOperation : public GreasePencilStrokeOperation { void on_stroke_done(const bContext &C) override; PaintOperation(const bool temp_draw = false) : temp_draw_(temp_draw) {} + + bool update_stroke_depth_placement(const bContext &C, const InputSample &sample); + /* Returns the range of actually reprojected points. */ + IndexRange interpolate_stroke_depth(const bContext &C, + std::optional start_point, + float from_depth, + float to_depth); }; /** @@ -494,7 +507,19 @@ struct PaintOperationExecutor { const RegionView3D *rv3d = CTX_wm_region_view3d(&C); const ARegion *region = CTX_wm_region(&C); - const float3 start_location = self.placement_.project(start_coords); + float3 start_location; + if (self.placement_.use_project_to_stroke()) { + const std::optional depth = self.placement_.get_depth(start_coords); + if (depth) { + start_location = self.placement_.place(start_coords, *depth); + } + else { + start_location = self.placement_.project(start_coords); + } + } + else { + start_location = self.placement_.project(start_coords); + } float start_radius = ed::greasepencil::radius_from_input_sample( rv3d, region, @@ -621,6 +646,14 @@ struct PaintOperationExecutor { curve_attributes_to_skip.add("curve_type"); curves.update_curve_types(); + if (self.placement_.use_project_to_stroke()) { + self.stroke_placement_depths_.append(self.stroke_placement_depths_.is_empty() ? + std::nullopt : + self.stroke_placement_depths_.last()); + /* Initialize the snap point. */ + self.update_stroke_depth_placement(C, start_sample); + } + /* Initialize the rest of the attributes with default values. */ bke::fill_attribute_range_default( attributes, @@ -740,9 +773,22 @@ struct PaintOperationExecutor { 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]); + if (self.placement_.use_project_to_stroke()) { + BLI_assert(self.stroke_placement_depths_.size() == self.screen_space_coords_orig_.size()); + const Span> stroke_depths = + self.stroke_placement_depths_.as_span().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]; + const std::optional depth = stroke_depths[window_i]; + positions_slice[window_i] = depth ? self.placement_.place(final_coords[window_i], *depth) : + self.placement_.project(final_coords[window_i]); + } + } + else { + 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]); + } } } @@ -756,7 +802,22 @@ struct PaintOperationExecutor { const bool on_back = (scene->toolsettings->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) != 0; const float2 coords = extension_sample.mouse_position; - float3 position = self.placement_.project(coords); + float3 position; + if (self.placement_.use_project_to_stroke()) { + const std::optional depth = self.stroke_placement_depths_.is_empty() ? + std::nullopt : + self.stroke_placement_depths_.last(); + if (depth) { + position = self.placement_.place(coords, *depth); + } + else { + position = self.placement_.project(coords); + } + } + else { + position = self.placement_.project(coords); + } + float radius = ed::greasepencil::radius_from_input_sample(rv3d, region, brush_, @@ -950,19 +1011,43 @@ struct PaintOperationExecutor { for (float2 new_position : new_screen_space_coords) { self.screen_space_curve_fitted_coords_.append(Vector({new_position})); } + if (self.placement_.use_project_to_stroke()) { + const std::optional last_depth = self.stroke_placement_depths_.is_empty() ? + std::nullopt : + self.stroke_placement_depths_.last(); + self.stroke_placement_depths_.append_n_times(last_depth, new_points_num); + } /* Only start smoothing if there are enough points. */ constexpr int64_t min_active_smoothing_points_num = 8; const IndexRange smooth_window = self.screen_space_coords_orig_.index_range().drop_front( self.active_smooth_start_index_); if (smooth_window.size() < min_active_smoothing_points_num) { - self.placement_.project(new_screen_space_coords, new_positions); + if (self.placement_.use_project_to_stroke()) { + const Span> new_depths = + self.stroke_placement_depths_.as_mutable_span().take_back(new_points_num); + for (const int64_t i : new_positions.index_range()) { + const std::optional depth = new_depths[i]; + if (depth) { + new_positions[i] = self.placement_.place(coords, *depth); + } + else { + new_positions[i] = self.placement_.project(coords); + } + } + } + else { + self.placement_.project(new_screen_space_coords, new_positions); + } } else { - /* Active smoothing is done in a window at the end of the new stroke. */ + /* Active smoothing is done in a window at the end of the new stroke. + * Final positions are written below. */ this->active_smoothing(self, smooth_window); } + /* Jitter uses smoothed coordinates as input. In case smoothing is not applied these are the + * unsmoothed original coordinates. */ MutableSpan curve_positions = positions.slice(curves.points_by_curve()[active_curve]); if (use_settings_random_ && settings_->draw_jitter > 0.0f) { this->active_jitter(self, @@ -980,9 +1065,28 @@ struct PaintOperationExecutor { /* 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]); + if (self.placement_.use_project_to_stroke()) { + BLI_assert(self.stroke_placement_depths_.size() == self.screen_space_coords_orig_.size()); + const Span> stroke_depths = + self.stroke_placement_depths_.as_mutable_span().slice(smooth_window); + for (const int64_t window_i : smooth_window.index_range()) { + const std::optional depth = stroke_depths[window_i]; + curve_positions_slice[window_i] = depth ? + self.placement_.place(final_coords[window_i], + *depth) : + self.placement_.project(final_coords[window_i]); + } } + else { + for (const int64_t window_i : smooth_window.index_range()) { + curve_positions_slice[window_i] = self.placement_.project(final_coords[window_i]); + } + } + } + + if (self.placement_.use_project_to_stroke()) { + /* Find a new snap point and apply projection to trailing points. */ + self.update_stroke_depth_placement(C, extension_sample); } /* Initialize the rest of the attributes with default values. */ @@ -1009,6 +1113,140 @@ struct PaintOperationExecutor { } }; +enum class StrokeSnapMode { + AllPoints, + EndPoints, + FirstPoint, +}; + +static StrokeSnapMode get_snap_mode(const bContext &C) +{ + /* gpencil_v3d_align is an awkward combination of multiple properties. If none of the non-zero + * flags are set the AllPoints mode is the default. */ + const Scene &scene = *CTX_data_scene(&C); + const char align_flags = scene.toolsettings->gpencil_v3d_align; + if (align_flags & GP_PROJECT_DEPTH_STROKE_ENDPOINTS) { + return StrokeSnapMode::EndPoints; + } + if (align_flags & GP_PROJECT_DEPTH_STROKE_FIRST) { + return StrokeSnapMode::FirstPoint; + } + return StrokeSnapMode::AllPoints; +} + +bool PaintOperation::update_stroke_depth_placement(const bContext &C, const InputSample &sample) +{ + BLI_assert(placement_.use_project_to_stroke()); + + const std::optional new_stroke_placement_depth = placement_.get_depth( + sample.mouse_position); + if (!new_stroke_placement_depth) { + return false; + } + + const StrokeSnapMode snap_mode = get_snap_mode(C); + switch (snap_mode) { + case StrokeSnapMode::AllPoints: { + const float start_depth = last_stroke_placement_depth_ ? *last_stroke_placement_depth_ : + *new_stroke_placement_depth; + const float end_depth = *new_stroke_placement_depth; + const IndexRange reprojected_points = this->interpolate_stroke_depth( + C, last_stroke_placement_point_, start_depth, end_depth); + /* Only reproject newly added points next time a hit point is found. */ + if (!reprojected_points.is_empty()) { + last_stroke_placement_point_ = reprojected_points.one_after_last(); + } + + last_stroke_placement_depth_ = new_stroke_placement_depth; + break; + } + case StrokeSnapMode::EndPoints: { + const float start_depth = last_stroke_placement_depth_ ? *last_stroke_placement_depth_ : + *new_stroke_placement_depth; + const float end_depth = *new_stroke_placement_depth; + const IndexRange reprojected_points = this->interpolate_stroke_depth( + C, last_stroke_placement_point_, start_depth, end_depth); + + /* Only update depth on the first hit. */ + if (!last_stroke_placement_depth_) { + /* Keep reprojecting all points from the first hit onward. */ + if (!reprojected_points.is_empty()) { + last_stroke_placement_point_ = reprojected_points.one_after_last(); + } + last_stroke_placement_depth_ = new_stroke_placement_depth; + } + break; + } + case StrokeSnapMode::FirstPoint: { + /* Only reproject once in "First Point" mode. */ + if (!last_stroke_placement_depth_) { + const float start_depth = *new_stroke_placement_depth; + const float end_depth = *new_stroke_placement_depth; + this->interpolate_stroke_depth(C, last_stroke_placement_point_, start_depth, end_depth); + + last_stroke_placement_depth_ = new_stroke_placement_depth; + break; + } + } + } + + return true; +} + +IndexRange PaintOperation::interpolate_stroke_depth(const bContext &C, + std::optional start_point, + const float from_depth, + const float to_depth) +{ + using namespace blender::bke; + + Scene *scene = CTX_data_scene(&C); + Object *object = CTX_data_active_object(&C); + GreasePencil &grease_pencil = *static_cast(object->data); + const bool on_back = (scene->toolsettings->gpencil_flags & GP_TOOL_FLAG_PAINT_ONBACK) != 0; + + /* Grease Pencil should have an active layer. */ + BLI_assert(grease_pencil.has_active_layer()); + bke::greasepencil::Layer &active_layer = *grease_pencil.get_active_layer(); + /* Drawing should exist. */ + bke::greasepencil::Drawing &drawing = *grease_pencil.get_editable_drawing_at(active_layer, + scene->r.cfra); + const int active_curve = on_back ? drawing.strokes().curves_range().first() : + drawing.strokes().curves_range().last(); + const offset_indices::OffsetIndices points_by_curve = drawing.strokes().points_by_curve(); + const IndexRange all_points = points_by_curve[active_curve]; + BLI_assert(screen_space_final_coords_.size() == all_points.size()); + if (all_points.is_empty()) { + return {}; + } + + IndexRange active_points = all_points; + if (start_point) { + active_points = IndexRange::from_begin_end_inclusive(*start_point, all_points.last()); + } + if (active_points.is_empty()) { + return {}; + } + + /* Point slice relative to the curve, valid for 2D coordinate array. */ + const IndexRange active_curve_points = active_points.shift(-all_points.start()); + + MutableSpan> depths = stroke_placement_depths_.as_mutable_span().slice( + active_curve_points); + MutableSpan positions = drawing.strokes_for_write().positions_for_write().slice( + active_points); + const Span final_coords = screen_space_final_coords_.as_span().slice( + active_curve_points); + const float step_size = 1.0f / std::max(int(active_points.size()) - 1, 1); + for (const int i : positions.index_range()) { + /* Update the placement depth for later reprojection (active smoothing). */ + depths[i] = math::interpolate(from_depth, to_depth, float(i) * step_size); + positions[i] = placement_.place(final_coords[i], *depths[i]); + } + + return active_points; +} + void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample) { Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C); @@ -1043,9 +1281,8 @@ void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start if (placement_.use_project_to_surface()) { placement_.cache_viewport_depths(depsgraph, region, view3d); } - else if (placement_.use_project_to_nearest_stroke()) { + else if (placement_.use_project_to_stroke()) { placement_.cache_viewport_depths(depsgraph, region, view3d); - placement_.set_origin_to_nearest_stroke(start_sample.mouse_position); } texture_space_ = ed::greasepencil::calculate_texture_space( diff --git a/source/blender/makesdna/DNA_view3d_types.h b/source/blender/makesdna/DNA_view3d_types.h index c39cae45e0b..09ef207598c 100644 --- a/source/blender/makesdna/DNA_view3d_types.h +++ b/source/blender/makesdna/DNA_view3d_types.h @@ -526,6 +526,8 @@ enum { V3D_GP_SHOW_MATERIAL_NAME = 1 << 8, /** Show Canvas Grid on Top. */ V3D_GP_SHOW_GRID_XRAY = 1 << 9, + /** Force 3D depth rendering and ignore per-object stroke depth mode. */ + V3D_GP_FORCE_STROKE_ORDER_3D = 1 << 10, }; /** #View3DShading.flag */