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
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<float3> 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<float> 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<float> 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<float3> 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<float2> src, MutableSpan<float3> 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;
|
||||
|
||||
@@ -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<float4> 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<float3> 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<float> 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -233,6 +233,8 @@ class PaintOperation : public GreasePencilStrokeOperation {
|
||||
Vector<Vector<float2>> screen_space_curve_fitted_coords_;
|
||||
/* Temporary vector of screen space offsets */
|
||||
Vector<float2> screen_space_jitter_offsets_;
|
||||
/* Projection planes for every point in "Stroke" placement mode. */
|
||||
Vector<std::optional<float>> stroke_placement_depths_;
|
||||
|
||||
/* Screen space coordinates after smoothing. */
|
||||
Vector<float2> 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<float> last_stroke_placement_depth_;
|
||||
/* Point index of the last valid stroke placement. */
|
||||
std::optional<int> 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<int> 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<float> 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<float2> final_coords = self.screen_space_final_coords_.as_mutable_span().slice(
|
||||
active_window);
|
||||
MutableSpan<float3> 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<std::optional<float>> 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<float> 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<float> 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<float2>({new_position}));
|
||||
}
|
||||
if (self.placement_.use_project_to_stroke()) {
|
||||
const std::optional<float> 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<std::optional<float>> 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<float> 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<float3> 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<float3> 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<std::optional<float>> 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<float> 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<float> 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<int> 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<GreasePencil *>(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<int> 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<std::optional<float>> depths = stroke_placement_depths_.as_mutable_span().slice(
|
||||
active_curve_points);
|
||||
MutableSpan<float3> positions = drawing.strokes_for_write().positions_for_write().slice(
|
||||
active_points);
|
||||
const Span<float2> 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(
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user