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 */