GPv3: Drawing Placements

This PR adds the drawing placement modes from GPv2.

The drawing placement defines the depth (origin, view, surface, etc.) and a plane (view, cursor, xz, etc.).

This introduces a new helper class `DrawingPlacement` that does all of the internals to find the correct projection and just exposes a simple function to project from screen space to the drawing plane/surface.

Note: Drawing on other strokes currently doesn't work, because GPv3 can't be rendered to image yet. We use the depth buffer of the grease pencil render result to find the right depth.

Pull Request: https://projects.blender.org/blender/blender/pulls/115602
This commit is contained in:
Falk David
2023-12-05 11:01:42 +01:00
committed by Falk David
parent 9e7a70d6c6
commit 5fea1eda36
8 changed files with 249 additions and 46 deletions
+12 -6
View File
@@ -910,18 +910,21 @@ class VIEW3D_HT_header(Header):
layout.separator_spacer()
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL'}:
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
# Grease pencil
if object_mode == 'PAINT_GPENCIL':
layout.prop_with_popover(
if object_mode in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
sub = layout.row(align=True)
sub.prop_with_popover(
tool_settings,
"gpencil_stroke_placement_view3d",
text="",
panel="VIEW3D_PT_gpencil_origin",
)
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL'}:
layout.prop_with_popover(
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
sub = layout.row(align=True)
sub.active = tool_settings.gpencil_stroke_placement_view3d != 'SURFACE'
sub.prop_with_popover(
tool_settings.gpencil_sculpt,
"lock_axis",
text="",
@@ -7647,7 +7650,10 @@ class VIEW3D_PT_gpencil_origin(Panel):
row = layout.row()
row.label(text="Offset")
row = layout.row()
row.prop(gpd, "zdepth_offset", text="")
if context.preferences.experimental.use_grease_pencil_version3:
row.prop(tool_settings, "gpencil_surface_offset", text="")
else:
row.prop(gpd, "zdepth_offset", text="")
if tool_settings.gpencil_stroke_placement_view3d == 'STROKE':
row = layout.row()
@@ -232,6 +232,8 @@ namespace blender::bke::greasepencil {
DrawingTransforms::DrawingTransforms(const Object &grease_pencil_ob)
{
/* TODO: For now layer space = object space. This needs to change once the layers have a
* transform. */
this->layer_space_to_world_space = float4x4_view(grease_pencil_ob.object_to_world);
this->world_space_to_layer_space = math::invert(this->layer_space_to_world_space);
}
@@ -10,9 +10,11 @@
#include "BKE_context.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_material.h"
#include "BKE_scene.h"
#include "BLI_bit_span_ops.hh"
#include "BLI_bit_vector.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.hh"
#include "BLI_vector_set.hh"
@@ -20,6 +22,7 @@
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "ED_curves.hh"
#include "ED_grease_pencil.hh"
@@ -27,6 +30,152 @@
namespace blender::ed::greasepencil {
DrawingPlacement::DrawingPlacement(const Scene &scene,
const ARegion &region,
const View3D &view3d,
const Object &object)
: region_(&region), view3d_(&view3d), transforms_(object)
{
/* Initialize DrawingPlacementPlane from toolsettings. */
switch (scene.toolsettings->gp_sculpt.lock_axis) {
case GP_LOCKAXIS_VIEW:
plane_ = DrawingPlacementPlane::View;
break;
case GP_LOCKAXIS_Y:
plane_ = DrawingPlacementPlane::Front;
placement_normal_ = float3(0, 1, 0);
break;
case GP_LOCKAXIS_X:
plane_ = DrawingPlacementPlane::Side;
placement_normal_ = float3(1, 0, 0);
break;
case GP_LOCKAXIS_Z:
plane_ = DrawingPlacementPlane::Top;
placement_normal_ = float3(0, 0, 1);
break;
case GP_LOCKAXIS_CURSOR: {
plane_ = DrawingPlacementPlane::Cursor;
float3x3 mat;
BKE_scene_cursor_rot_to_mat3(&scene.cursor, mat.ptr());
placement_normal_ = mat * float3(0, 0, 1);
break;
}
}
/* Initialize DrawingPlacementDepth from toolsettings. */
switch (scene.toolsettings->gpencil_v3d_align) {
case GP_PROJECT_VIEWSPACE:
depth_ = DrawingPlacementDepth::ObjectOrigin;
placement_loc_ = transforms_.layer_space_to_world_space.location();
break;
case (GP_PROJECT_VIEWSPACE | GP_PROJECT_CURSOR):
depth_ = DrawingPlacementDepth::Cursor;
placement_loc_ = float3(scene.cursor.location);
break;
case (GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_VIEW):
depth_ = DrawingPlacementDepth::Surface;
surface_offset_ = scene.toolsettings->gpencil_surface_offset;
break;
case (GP_PROJECT_VIEWSPACE | GP_PROJECT_DEPTH_STROKE):
depth_ = DrawingPlacementDepth::NearestStroke;
break;
}
if (ELEM(plane_,
DrawingPlacementPlane::Front,
DrawingPlacementPlane::Side,
DrawingPlacementPlane::Top,
DrawingPlacementPlane::Cursor) &&
ELEM(depth_, DrawingPlacementDepth::ObjectOrigin, DrawingPlacementDepth::Cursor))
{
plane_from_point_normal_v3(placement_plane_, placement_loc_, placement_normal_);
}
}
DrawingPlacement::~DrawingPlacement()
{
if (depth_cache_ != nullptr) {
ED_view3d_depths_free(depth_cache_);
}
}
bool DrawingPlacement::use_project_to_surface() const
{
return depth_ == DrawingPlacementDepth::Surface;
}
bool DrawingPlacement::use_project_to_nearest_stroke() const
{
return depth_ == DrawingPlacementDepth::NearestStroke;
}
void DrawingPlacement::cache_viewport_depths(Depsgraph *depsgraph, ARegion *region, View3D *view3d)
{
const eV3DDepthOverrideMode mode = (depth_ == DrawingPlacementDepth::Surface) ?
V3D_DEPTH_NO_GPENCIL :
V3D_DEPTH_GPENCIL_ONLY;
ED_view3d_depth_override(depsgraph, region, view3d, nullptr, mode, &this->depth_cache_);
}
void DrawingPlacement::set_origin_to_nearest_stroke(const float2 co)
{
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;
}
else {
/* If nothing was hit, use origin. */
placement_loc_ = transforms_.layer_space_to_world_space.location();
}
plane_from_point_normal_v3(placement_plane_, placement_loc_, placement_normal_);
}
float3 DrawingPlacement::project(const float2 co) const
{
float3 proj_point;
if (depth_ == DrawingPlacementDepth::Surface) {
/* Project using the viewport depth cache. */
BLI_assert(depth_cache_ != nullptr);
float depth;
if (ED_view3d_depth_read_cached(depth_cache_, int2(co), 4, &depth)) {
ED_view3d_depth_unproject_v3(region_, int2(co), depth, proj_point);
float3 normal;
ED_view3d_depth_read_cached_normal(region_, depth_cache_, int2(co), normal);
proj_point += normal * surface_offset_;
}
/* If we didn't hit anything, use the view plane for placement. */
else {
ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point);
}
}
else {
if (ELEM(plane_,
DrawingPlacementPlane::Front,
DrawingPlacementPlane::Side,
DrawingPlacementPlane::Top,
DrawingPlacementPlane::Cursor))
{
ED_view3d_win_to_3d_on_plane(region_, placement_plane_, co, false, proj_point);
}
else if (plane_ == DrawingPlacementPlane::View) {
ED_view3d_win_to_3d(view3d_, region_, placement_loc_, co, proj_point);
}
}
return math::transform_point(transforms_.world_space_to_layer_space, proj_point);
}
void DrawingPlacement::project(const Span<float2> src, MutableSpan<float3> dst) const
{
threading::parallel_for(src.index_range(), 1024, [&](const IndexRange range) {
for (const int i : range) {
dst[i] = this->project(src[i]);
}
});
}
static float3 drawing_origin(const Scene *scene, const Object *object, char align_flag)
{
BLI_assert(object != nullptr && object->type == OB_GREASE_PENCIL);
@@ -23,6 +23,9 @@ struct Object;
struct KeyframeEditData;
struct wmKeyConfig;
struct ToolSettings;
struct Scene;
struct ViewDepths;
struct View3D;
enum {
LAYER_REORDER_ABOVE,
@@ -51,6 +54,46 @@ eAttrDomain ED_grease_pencil_selection_domain_get(const ToolSettings *tool_setti
namespace blender::ed::greasepencil {
enum class DrawingPlacementDepth { ObjectOrigin, Cursor, Surface, NearestStroke };
enum class DrawingPlacementPlane { View, Front, Side, Top, Cursor };
class DrawingPlacement {
const ARegion *region_;
const View3D *view3d_;
DrawingPlacementDepth depth_;
DrawingPlacementPlane plane_;
bke::greasepencil::DrawingTransforms transforms_;
ViewDepths *depth_cache_ = nullptr;
float surface_offset_;
float3 placement_loc_;
float3 placement_normal_;
float4 placement_plane_;
public:
DrawingPlacement() = default;
DrawingPlacement(const Scene &scene,
const ARegion &region,
const View3D &view3d,
const Object &object);
~DrawingPlacement();
public:
bool use_project_to_surface() const;
bool use_project_to_nearest_stroke() const;
void cache_viewport_depths(Depsgraph *depsgraph, ARegion *region, View3D *view3d);
void set_origin_to_nearest_stroke(float2 co);
/**
* Projects a screen space coordinate to the local drawing space.
*/
float3 project(float2 co) const;
void project(Span<float2> src, MutableSpan<float3> dst) const;
};
void set_selected_frames_type(bke::greasepencil::Layer &layer,
const eBezTriple_KeyframeType key_type);
@@ -115,6 +115,9 @@ class PaintOperation : public GreasePencilStrokeOperation {
/* The start index of the smoothing window. */
int active_smooth_start_index_ = 0;
/* Helper class to project screen space coordinates to 3d. */
ed::greasepencil::DrawingPlacement placement_;
friend struct PaintOperationExecutor;
public:
@@ -133,7 +136,6 @@ class PaintOperation : public GreasePencilStrokeOperation {
*/
struct PaintOperationExecutor {
Scene *scene_;
ARegion *region_;
GreasePencil *grease_pencil_;
Brush *brush_;
@@ -143,14 +145,11 @@ struct PaintOperationExecutor {
bke::greasepencil::Drawing *drawing_;
bke::greasepencil::DrawingTransforms transforms_;
PaintOperationExecutor(const bContext &C)
{
scene_ = CTX_data_scene(&C);
region_ = CTX_wm_region(&C);
Object *object = CTX_data_active_object(&C);
grease_pencil_ = static_cast<GreasePencil *>(object->data);
GreasePencil *grease_pencil = static_cast<GreasePencil *>(object->data);
Paint *paint = &scene_->toolsettings->gp_paint->paint;
brush_ = BKE_paint_brush(paint);
@@ -170,36 +169,20 @@ struct PaintOperationExecutor {
// const bool use_vertex_color_fill = use_vertex_color && ELEM(
// brush->gpencil_settings->vertex_mode, GPPAINT_MODE_STROKE, GPPAINT_MODE_BOTH);
/* The object should have an active layer. */
BLI_assert(grease_pencil_->has_active_layer());
bke::greasepencil::Layer &active_layer = *grease_pencil_->get_active_layer_for_write();
const int drawing_index = active_layer.drawing_index_at(scene_->r.cfra);
/* Drawing should exist. */
BLI_assert(drawing_index >= 0);
drawing_ =
&reinterpret_cast<GreasePencilDrawing *>(grease_pencil_->drawing(drawing_index))->wrap();
transforms_ = bke::greasepencil::DrawingTransforms(*object);
BLI_assert(grease_pencil->has_active_layer());
drawing_ = grease_pencil->get_editable_drawing_at(grease_pencil->get_active_layer(),
scene_->r.cfra);
BLI_assert(drawing_ != nullptr);
}
float3 screen_space_to_drawing_plane(const float2 co)
{
/* TODO: Use correct plane/projection. */
float4 plane;
plane_from_point_normal_v3(
plane, transforms_.layer_space_to_world_space.location(), float3{0, -1, 0});
float3 proj_point;
ED_view3d_win_to_3d_on_plane(region_, plane, co, false, proj_point);
return math::transform_point(transforms_.world_space_to_layer_space, proj_point);
}
float radius_from_input_sample(const bContext &C, const InputSample &sample)
float radius_from_input_sample(PaintOperation &self,
const bContext &C,
const InputSample &sample)
{
ViewContext vc = ED_view3d_viewcontext_init(const_cast<bContext *>(&C),
CTX_data_depsgraph_pointer(&C));
float radius = calc_brush_radius(
&vc, brush_, scene_, screen_space_to_drawing_plane(sample.mouse_position));
&vc, brush_, scene_, self.placement_.project(sample.mouse_position));
if (BKE_brush_use_size_pressure(brush_)) {
radius *= BKE_curvemapping_evaluateF(settings_->curve_sensitivity, 0, sample.pressure);
}
@@ -221,7 +204,7 @@ struct PaintOperationExecutor {
const int material_index)
{
const float2 start_coords = start_sample.mouse_position;
const float start_radius = this->radius_from_input_sample(C, start_sample);
const float start_radius = this->radius_from_input_sample(self, C, start_sample);
const float start_opacity = this->opacity_from_input_sample(start_sample);
const ColorGeometry4f start_vertex_color = ColorGeometry4f(vertex_color_);
@@ -235,7 +218,7 @@ struct PaintOperationExecutor {
curves.resize(curves.points_num() + 1, curves.curves_num() + 1);
curves.offsets_for_write().last(1) = num_old_points;
curves.positions_for_write().last() = screen_space_to_drawing_plane(start_coords);
curves.positions_for_write().last() = self.placement_.project(start_coords);
drawing_->radii_for_write().last() = start_radius;
drawing_->opacities_for_write().last() = start_opacity;
drawing_->vertex_colors_for_write().last() = start_vertex_color;
@@ -352,7 +335,7 @@ struct PaintOperationExecutor {
/* Update the positions in the current cache. */
window_coords[window_i] = new_pos;
positions_slice[window_i] = screen_space_to_drawing_plane(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. */
@@ -367,7 +350,7 @@ struct PaintOperationExecutor {
const InputSample &extension_sample)
{
const float2 coords = extension_sample.mouse_position;
const float radius = this->radius_from_input_sample(C, extension_sample);
const float radius = this->radius_from_input_sample(self, C, extension_sample);
const float opacity = this->opacity_from_input_sample(extension_sample);
const ColorGeometry4f vertex_color = ColorGeometry4f(vertex_color_);
@@ -381,7 +364,7 @@ struct PaintOperationExecutor {
/* Overwrite last point if it's very close. */
if (math::distance(coords, prev_coords) < POINT_OVERRIDE_THRESHOLD_PX) {
curves.positions_for_write().last() = screen_space_to_drawing_plane(coords);
curves.positions_for_write().last() = self.placement_.project(coords);
drawing_->radii_for_write().last() = math::max(radius, prev_radius);
drawing_->opacities_for_write().last() = math::max(opacity, prev_opacity);
return;
@@ -426,9 +409,7 @@ struct PaintOperationExecutor {
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) {
for (const int64_t i : new_screen_space_coords.index_range()) {
new_positions[i] = screen_space_to_drawing_plane(new_screen_space_coords[i]);
}
self.placement_.project(new_screen_space_coords, new_positions);
}
else {
/* Active smoothing is done in a window at the end of the new stroke. */
@@ -459,6 +440,8 @@ struct PaintOperationExecutor {
void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start_sample)
{
ARegion *region = CTX_wm_region(&C);
View3D *view3d = CTX_wm_view3d(&C);
Scene *scene = CTX_data_scene(&C);
Object *object = CTX_data_active_object(&C);
GreasePencil *grease_pencil = static_cast<GreasePencil *>(object->data);
@@ -476,6 +459,16 @@ void PaintOperation::on_stroke_begin(const bContext &C, const InputSample &start
BKE_curvemapping_init(brush->gpencil_settings->curve_rand_saturation);
BKE_curvemapping_init(brush->gpencil_settings->curve_rand_value);
/* Initialize helper class for projecting screen space coordinates. */
placement_ = ed::greasepencil::DrawingPlacement(*scene, *region, *view3d, *object);
if (placement_.use_project_to_surface()) {
placement_.cache_viewport_depths(CTX_data_depsgraph_pointer(&C), region, view3d);
}
else if (placement_.use_project_to_nearest_stroke()) {
placement_.cache_viewport_depths(CTX_data_depsgraph_pointer(&C), region, view3d);
placement_.set_origin_to_nearest_stroke(start_sample.mouse_position);
}
Material *material = BKE_grease_pencil_object_material_ensure_from_active_input_brush(
CTX_data_main(&C), object, brush);
const int material_index = BKE_grease_pencil_object_material_index_get(object, material);
@@ -2421,7 +2421,7 @@ void ED_view3d_depth_override(Depsgraph *depsgraph,
switch (mode) {
case V3D_DEPTH_NO_GPENCIL:
DRW_draw_depth_loop(
depsgraph, region, v3d, viewport, false, true, (v3d->flag2 & V3D_HIDE_OVERLAYS) == 0);
depsgraph, region, v3d, viewport, false, true, false);
break;
case V3D_DEPTH_GPENCIL_ONLY:
DRW_draw_depth_loop(depsgraph, region, v3d, viewport, true, false, false);
+5 -2
View File
@@ -1574,18 +1574,21 @@ typedef struct ToolSettings {
char gpencil_v3d_align;
/** General 2D Editor. */
char gpencil_v2d_align;
char _pad0[2];
/* Annotations. */
/** Stroke placement settings - 3D View. */
char annotate_v3d_align;
/** Default stroke thickness for annotation strokes. */
short annotate_thickness;
/** Normal offset used when drawing on surfaces. */
float gpencil_surface_offset;
/** Stroke selection mode for Edit. */
char gpencil_selectmode_edit;
/** Stroke selection mode for Sculpt. */
char gpencil_selectmode_sculpt;
char _pad0[6];
/** Grease Pencil Sculpt. */
struct GP_Sculpt_Settings gp_sculpt;
@@ -3679,6 +3679,13 @@ static void rna_def_tool_settings(BlenderRNA *brna)
prop, "Only Endpoints", "Only use the first and last parts of the stroke for snapping");
RNA_def_property_update(prop, NC_GPENCIL | ND_DATA, nullptr);
prop = RNA_def_property(srna, "gpencil_surface_offset", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, nullptr, "gpencil_surface_offset");
RNA_def_property_ui_text(prop, "Surface Offset", "Offset along normal when drawing on surfaces");
RNA_def_property_range(prop, 0.0f, 1.0f);
RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.1f, 3);
RNA_def_property_float_default(prop, 0.150f);
/* Grease Pencil - Select mode Edit */
prop = RNA_def_property(srna, "gpencil_selectmode_edit", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "gpencil_selectmode_edit");