WIP: GPv3: Geometry-based Fill tool #114318

Draft
Sietse Brouwer wants to merge 39 commits from SietseB/blender:gp3-vector-fill into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
5 changed files with 147 additions and 87 deletions
Showing only changes of commit 3beb6ebe12 - Show all commits

View File

@ -755,8 +755,9 @@ static void update_gap_distance(FillData *fd, const float delta)
* in the tool settings.
*/
static void get_fill_edge_layers(FillData *fd,
Vector<GreasePencilDrawing *> &r_drawings,
Vector<int> &r_layer_index)
Vector<const bke::greasepencil::Drawing *> &r_drawings,
Vector<int> &r_layer_index,
const int frame_number)
{
using namespace bke::greasepencil;
@ -764,37 +765,36 @@ static void get_fill_edge_layers(FillData *fd,
int active_layer_index = -1;
const Layer *active_layer = fd->grease_pencil->get_active_layer();
Span<const Layer *> layers = fd->grease_pencil->layers();
for (int index = 0; index < layers.size(); index++) {
if (layers[index] == active_layer) {
active_layer_index = index;
for (int layer_index = 0; layer_index < layers.size(); layer_index++) {
if (layers[layer_index] == active_layer) {
active_layer_index = layer_index;
break;
}
}
/* Select layers based on position in the layer collection. */
Span<GreasePencilDrawingBase *> drawings = fd->grease_pencil->drawings();
for (int index = 0; index < layers.size(); index++) {
for (int layer_index = 0; layer_index < layers.size(); layer_index++) {
/* Skip invisible layers. */
if (!layers[index]->is_visible()) {
if (!layers[layer_index]->is_visible()) {
continue;
}
bool add = false;
switch (fd->brush->gpencil_settings->fill_layer_mode) {
case GP_FILL_GPLMODE_ACTIVE:
add = (index == active_layer_index);
add = (layer_index == active_layer_index);
break;
case GP_FILL_GPLMODE_ABOVE:
add = (index == active_layer_index + 1);
add = (layer_index == active_layer_index + 1);
break;
case GP_FILL_GPLMODE_BELOW:
add = (index == active_layer_index - 1);
add = (layer_index == active_layer_index - 1);
break;
case GP_FILL_GPLMODE_ALL_ABOVE:
add = (index > active_layer_index);
add = (layer_index > active_layer_index);
break;
case GP_FILL_GPLMODE_ALL_BELOW:
add = (index < active_layer_index);
add = (layer_index < active_layer_index);
break;
case GP_FILL_GPLMODE_VISIBLE:
add = true;
@ -804,20 +804,54 @@ static void get_fill_edge_layers(FillData *fd,
}
if (add) {
const int drawing_index = layers[index]->drawing_index_at(fd->vc.scene->r.cfra);
if (drawing_index == -1) {
continue;
}
GreasePencilDrawingBase *drawing_base = drawings[drawing_index];
if (drawing_base->type == GP_DRAWING) {
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
if (const Drawing *drawing = fd->grease_pencil->get_editable_drawing_at(layers[layer_index],
frame_number))
{
r_drawings.append(drawing);
r_layer_index.append(index);
r_layer_index.append(layer_index);
}
}
}
}
/**
* Get drawings and gap closure data for a given frame number.
*/
static bool get_drawings_and_gap_closures_at_frame(FillData *fd, const int frame_number)
{
/* Get layers according to tool settings (visible, above, below, etc.) */
Vector<const bke::greasepencil::Drawing *> drawings;
Vector<int> layer_indices;
get_fill_edge_layers(fd, drawings, layer_indices, frame_number);
if (drawings.is_empty()) {
return false;
}
/* Convert curves to viewport 2D space. */
fd->curves_2d = curves_in_2d_space_get(
&fd->vc, fd->vc.obact, drawings, layer_indices, frame_number, true);
/* Calculate epsilon values of all 2D curve segments, used to avoid floating point precision
* errors. */
get_curve_segment_epsilons(fd);
/* When using extensions of the curve ends to close gaps, build an array of those
* two-point 'curves'. */
if (fd->use_gap_close_extend) {
init_curve_end_extensions(fd);
get_curve_end_extensions(fd);
}
/* When using radii to close gaps, build KD tree of curve end points. */
if (fd->use_gap_close_radius) {
init_curve_end_radii(fd);
get_connected_curve_end_radii(fd);
}
return true;
}
/**
* Initialize the fill operator data.
*/
@ -857,36 +891,11 @@ static bool operator_init(bContext *C, wmOperator *op)
copy_v3_v3(fd->gap_closed_color, default_gap_closed_color);
copy_v4_v4(fd->gap_proximity_color, default_gap_proximity_color);
/* Get layers according to tool settings (visible, above, below, etc.) */
Vector<GreasePencilDrawing *> drawings;
Vector<int> layer_indices;
get_fill_edge_layers(fd, drawings, layer_indices);
if (drawings.is_empty()) {
/* Get drawings for current frame. */
if (!get_drawings_and_gap_closures_at_frame(fd, fd->vc.scene->r.cfra)) {
return false;
}
/* Convert curves to viewport 2D space. */
fd->curves_2d = curves_in_2d_space_get(
&fd->vc, fd->vc.obact, drawings, layer_indices, fd->vc.scene->r.cfra, true);
/* Calculate epsilon values of all 2D curve segments, used to avoid floating point precision
* errors. */
get_curve_segment_epsilons(fd);
/* When using extensions of the curve ends to close gaps, build an array of those
* two-point 'curves'. */
if (fd->use_gap_close_extend) {
init_curve_end_extensions(fd);
get_curve_end_extensions(fd);
}
/* When using radii to close gaps, build KD tree of curve end points. */
if (fd->use_gap_close_radius) {
init_curve_end_radii(fd);
get_connected_curve_end_radii(fd);
}
/* Activate 3D viewport overlay for showing gap closure visual aids. */
if ((fd->brush->gpencil_settings->flag & GP_BRUSH_FILL_SHOW_EXTENDLINES) != 0) {
fd->draw_handle = ED_region_draw_cb_activate(
@ -921,6 +930,61 @@ static void operator_exit(bContext *C, wmOperator *op)
}
}
static bool fill_do(FillData *fd)
{
/* DEBUG: measure time. */
auto t1 = std::chrono::high_resolution_clock::now();
/* Get latest toolsetting values. */
get_latest_toolsettings(fd);
/* Get the fill method. */
bool (*perform_fill)(FillData *) = nullptr;
if (fd->brush->gpencil_settings->fill_mode == GP_FILL_MODE_FLOOD) {
perform_fill = &flood_fill_do;
}
else if (fd->brush->gpencil_settings->fill_mode == GP_FILL_MODE_GEOMETRY) {
perform_fill = &vector_fill_do;
}
if (perform_fill == nullptr) {
return false;
}
/* Perform the fill operation for all selected frames. */
const bool use_multi_frame_editing = (fd->vc.scene->toolsettings->gpencil_flags &
GP_USE_MULTI_FRAME_EDITING) != 0;
const Array<int> frame_numbers = get_frame_numbers_for_layer(
fd->grease_pencil->get_active_layer()->wrap(),
fd->vc.scene->r.cfra,
use_multi_frame_editing);
bool get_drawings = false;
bool success = false;
for (const int frame_number : frame_numbers) {
/* For the current frame (first entry in the array) we already retrieved the drawings during
* the operator invoke, so no need to do it a second time. */
if (get_drawings) {
if (!get_drawings_and_gap_closures_at_frame(fd, frame_number)) {
continue;
}
}
get_drawings = true;
fd->frame_number = frame_number;
if (perform_fill(fd)) {
success = true;
}
}
/* DEBUG: measure time. */
auto t2 = std::chrono::high_resolution_clock::now();
auto delta_t = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
printf("Fill operator took %d ms.\n", int(delta_t.count()));
return success;
}
/**
* Modal handler for the fill operator:
* - Change gap closure radius
@ -966,27 +1030,8 @@ static int operator_modal(bContext *C, wmOperator *op, const wmEvent *event)
fd->mouse_pos[0] = float(event->mval[0]);
fd->mouse_pos[1] = float(event->mval[1]);
/* DEBUG: measure time. */
auto t1 = std::chrono::high_resolution_clock::now();
/* Get latest toolsetting values. */
get_latest_toolsettings(fd);
/* Perform the fill operation. */
bool success = false;
if (fd->brush->gpencil_settings->fill_mode == GP_FILL_MODE_FLOOD) {
success = flood_fill_do(fd);
}
else if (fd->brush->gpencil_settings->fill_mode == GP_FILL_MODE_GEOMETRY) {
success = vector_fill_do(fd);
}
/* DEBUG: measure time. */
auto t2 = std::chrono::high_resolution_clock::now();
auto delta_t = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
printf("Fill operator took %d ms.\n", int(delta_t.count()));
if (success) {
if (fill_do(fd)) {
modal_state = OPERATOR_FINISHED;
}
else {

View File

@ -67,6 +67,9 @@ struct FillData {
/* Mouse position of the fill operation click. */
float2 mouse_pos{};
/* Frame number to perform the fill operation on. */
int frame_number;
/* True when edge gaps are closed by extending the curve ends. */
bool use_gap_close_extend{};
/* True when edge gaps are closed by curve end radii. */

View File

@ -636,18 +636,26 @@ static void create_fill_geometry(FillData *fd)
{
/* Ensure active frame (autokey is on, we checked on operator invoke). */
const bke::greasepencil::Layer *active_layer = fd->grease_pencil->get_active_layer();
const int current_frame = fd->vc.scene->r.cfra;
if (!fd->grease_pencil->get_active_layer()->frames().contains(current_frame)) {
if (!fd->grease_pencil->get_active_layer()->frames().contains(fd->frame_number)) {
bke::greasepencil::Layer &active_layer = *fd->grease_pencil->get_active_layer_for_write();
/* For additive drawing, we duplicate the frame that's currently visible and insert it at the
* current frame. */
bool frame_created = false;
if (fd->additive_drawing) {
/* For additive drawing, we duplicate the frame that's currently visible and insert it at the
* current frame. */
fd->grease_pencil->insert_duplicate_frame(
active_layer, active_layer.frame_key_at(current_frame), current_frame, false);
if (!fd->grease_pencil->insert_duplicate_frame(
active_layer, active_layer.frame_key_at(fd->frame_number), fd->frame_number, false))
{
frame_created = true;
}
}
else {
if (!frame_created) {
/* Otherwise we just insert a blank keyframe. */
fd->grease_pencil->insert_blank_frame(active_layer, current_frame, 0, BEZT_KEYTYPE_KEYFRAME);
if (!fd->grease_pencil->insert_blank_frame(
active_layer, fd->frame_number, 0, BEZT_KEYTYPE_KEYFRAME))
{
return;
}
}
}
@ -658,7 +666,7 @@ static void create_fill_geometry(FillData *fd)
}
/* Create geometry. */
const int drawing_index = active_layer->drawing_index_at(fd->vc.scene->r.cfra);
const int drawing_index = active_layer->drawing_index_at(fd->frame_number);
bke::greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(
fd->grease_pencil->drawings()[drawing_index])
->wrap();
@ -1573,7 +1581,8 @@ static bool walk_along_curve(FillData *fd, EdgeSegment *segment, const int start
fd);
if (!segment->segment_ends.is_empty()) {
/* Sort intersections on distance and angle. This is important to find the narrowest edge. */
/* Sort intersections on distance and angle. This is important to find the narrowest edge.
*/
std::sort(segment->segment_ends.begin(),
segment->segment_ends.end(),
[](const SegmentEnd &a, const SegmentEnd &b) {

View File

@ -83,14 +83,14 @@ float brush_radius_world_space(bContext &C, int x, int y)
return radius;
}
static Array<int> get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer,
const int current_frame,
const bool use_multi_frame_editing)
Array<int> get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer,
const int current_frame,
const bool use_multi_frame_editing)
{
Vector<int> frame_numbers({current_frame});
if (use_multi_frame_editing) {
for (const auto [frame_number, frame] : layer.frames().items()) {
if (frame.is_selected()) {
if (frame.is_selected() && frame_number != current_frame) {
frame_numbers.append_unchecked(frame_number);
}
}
@ -155,7 +155,7 @@ Array<DrawingInfo> retrieve_visible_drawings(const Scene &scene, const GreasePen
Curves2DSpace curves_in_2d_space_get(ViewContext *vc,
Object *ob,
Vector<GreasePencilDrawing *> &drawings,
Vector<const bke::greasepencil::Drawing *> &drawings,
Vector<int> &layer_index,
const int frame_number,
const bool get_stroke_flag)
@ -163,7 +163,7 @@ Curves2DSpace curves_in_2d_space_get(ViewContext *vc,
/* Get viewport projection matrix and evaluated GP object. */
float4x4 projection;
ED_view3d_ob_project_mat_get(vc->rv3d, ob, projection.ptr());
const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, const_cast<Object *>(ob));
const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, ob);
/* Count total number of editable curves and points in given Grease Pencil drawings. */
Curves2DSpace cv2d;
@ -194,7 +194,7 @@ Curves2DSpace curves_in_2d_space_get(ViewContext *vc,
threading::parallel_for(cv2d.drawings.index_range(), 1, [&](const IndexRange range) {
for (const int drawing_i : range) {
/* Get deformed geomtry. */
const bke::CurvesGeometry &curves = cv2d.drawings[drawing_i]->geometry.wrap();
const bke::CurvesGeometry &curves = cv2d.drawings[drawing_i]->strokes();
const OffsetIndices points_by_curve = curves.points_by_curve();
const VArray<bool> cyclic = curves.cyclic();
const bke::crazyspace::GeometryDeformation deformation =

View File

@ -124,6 +124,9 @@ struct MutableDrawingInfo {
const int layer_index;
const int frame_number;
};
Array<int> get_frame_numbers_for_layer(const bke::greasepencil::Layer &layer,
const int current_frame,
const bool use_multi_frame_editing);
Array<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
GreasePencil &grease_pencil);
Array<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
@ -167,7 +170,7 @@ IndexMask polyline_detect_corners(Span<float2> points,
* first_point_of_curve = points_2d[point_contiguous]
*/
struct Curves2DSpace {
Vector<GreasePencilDrawing *> drawings;
Vector<const bke::greasepencil::Drawing *> drawings;
/* Curve offset for each drawing (layer). So when there are three drawings with 12, 15 and 8
* curves, the curve offsets will be [0, 12, 27 (= 12 + 15)]. */
Vector<int> curve_offset;
@ -194,7 +197,7 @@ struct Curves2DSpace {
*/
Curves2DSpace curves_in_2d_space_get(ViewContext *vc,
Object *ob,
Vector<GreasePencilDrawing *> &drawings,
Vector<const bke::greasepencil::Drawing *> &drawings,
Vector<int> &layer_index,
const int frame_number,
const bool get_stroke_flag = false);