Fix #139194: Grease Pencil: Crazyspace deformation broken when evaluated layers don't match original

When the layer tree in the evaluated state of the Grease Pencil object changed,
the code would fail to get the crazyspace deformation.

Currently we rely on a 1 to 1 index mapping of the original and evaluated
layers. For obvious reasons, this is very weak and can easily break.

The new implementation works as follows:
* Caller that wants to get the crazyspace deformation passes the evaluated and original
   object + the original drawing to get the deformation of.
* Fallback deformation are the original positions.
* If there are drawing edit hints in the evaluated geoemtry set, then
  * find the edit hint that corresponds to the original drawing
  * use the positions in the edit hint.

To create the drawing edit hints, we need to know what evaluated layer corresponds
to which original layer. Currently, this simply stores the original layer index on the
evaluated layer runtime data.

The solution is not ideal and there are some possible improvements like:
* Find a way to solve the more general case, e.g. when there are multiple original
  IDs involved.
* Propagate the "mapping" to original layers even when the type of geometry is
  changed, like going to curve instances and back.

Pull Request: https://projects.blender.org/blender/blender/pulls/139285
This commit is contained in:
Falk David
2025-07-08 12:11:42 +02:00
committed by Falk David
parent 71cdb55953
commit a1893bf5e1
17 changed files with 122 additions and 139 deletions

View File

@@ -19,6 +19,10 @@ struct Object;
struct ReportList;
struct Scene;
namespace blender::bke::greasepencil {
class Drawing;
} // namespace blender::bke::greasepencil
namespace blender::bke::crazyspace {
/**
@@ -56,14 +60,12 @@ struct GeometryDeformation {
GeometryDeformation get_evaluated_curves_deformation(const Object *ob_eval, const Object &ob_orig);
GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph,
const Object &ob_orig);
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval,
const Object &ob_orig,
int layer_index,
int frame);
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph,
const Object &ob_orig,
int layer_index,
int frame);
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(
const Object *ob_eval, const Object &ob_orig, const bke::greasepencil::Drawing &drawing_orig);
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(
const Depsgraph &depsgraph,
const Object &ob_orig,
const bke::greasepencil::Drawing &drawing_orig);
} // namespace blender::bke::crazyspace

View File

@@ -430,6 +430,14 @@ class LayerRuntime {
*/
bool is_visibility_animated_;
/**
* For evaluated layers, the index of the corresponding original layer, or -1 if there is no
* original layer that could be mapped to. E.g. when the layer was created during evaluation.
*
* TODO: Find a way to store this information in #GreasePencilEditHints instead.
*/
int orig_layer_index_ = -1;
public:
/** Reset all runtime data. */
void clear();
@@ -968,7 +976,7 @@ class GreasePencilEditHints {
/**
* Array of #GreasePencilDrawingEditHints. There is one edit hint for each evaluated drawing.
* \note The index for each element is the layer index.
* \note The index for each element is the evaluated layer index.
*/
std::optional<Array<GreasePencilDrawingEditHints>> drawing_hints;
};

View File

@@ -624,25 +624,26 @@ GeometryDeformation get_evaluated_curves_deformation(const Depsgraph &depsgraph,
return get_evaluated_curves_deformation(ob_eval, ob_orig);
}
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object *ob_eval,
const Object &ob_orig,
const int layer_index,
const int frame)
static const GreasePencilDrawingEditHints *get_drawing_edit_hint_for_original_drawing(
const GreasePencilEditHints *edit_hints, const bke::greasepencil::Drawing &drawing_orig)
{
for (const GreasePencilDrawingEditHints &drawing_hint : *edit_hints->drawing_hints) {
if (drawing_hint.drawing_orig == &drawing_orig) {
return &drawing_hint;
}
}
return {};
}
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(
const Object *ob_eval, const Object &ob_orig, const bke::greasepencil::Drawing &drawing_orig)
{
BLI_assert(ob_orig.type == OB_GREASE_PENCIL);
const GreasePencil &grease_pencil_orig = *static_cast<const GreasePencil *>(ob_orig.data);
const Span<const bke::greasepencil::Layer *> layers_orig = grease_pencil_orig.layers();
const bke::greasepencil::Layer &layer_orig = grease_pencil_orig.layer(layer_index);
const bke::greasepencil::Drawing *drawing_orig = grease_pencil_orig.get_drawing_at(layer_orig,
frame);
if (drawing_orig == nullptr) {
return {};
}
GeometryDeformation deformation;
/* Use the undeformed positions by default. */
deformation.positions = drawing_orig->strokes().positions();
deformation.positions = drawing_orig.strokes().positions();
if (ob_eval == nullptr) {
return deformation;
@@ -652,8 +653,6 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object
return deformation;
}
bool has_deformed_positions = false;
/* If there are edit hints, use the positions of those. */
if (geometry_eval->has<GeometryComponentEditData>()) {
const GeometryComponentEditData &edit_component_eval =
@@ -662,37 +661,14 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object
if (edit_hints != nullptr && &edit_hints->grease_pencil_id_orig == &grease_pencil_orig &&
edit_hints->drawing_hints.has_value())
{
BLI_assert(edit_hints->drawing_hints->size() == layers_orig.size());
const GreasePencilDrawingEditHints &drawing_hints =
edit_hints->drawing_hints.value()[layer_index];
if (drawing_hints.positions()) {
deformation.positions = *drawing_hints.positions();
has_deformed_positions = true;
}
if (drawing_hints.deform_mats.has_value()) {
deformation.deform_mats = *drawing_hints.deform_mats;
}
}
}
if (has_deformed_positions) {
return deformation;
}
/* Otherwise use the positions of the evaluated drawing if the number of points match. */
if (const GreasePencilComponent *grease_pencil_component_eval =
geometry_eval->get_component<GreasePencilComponent>())
{
if (const GreasePencil *grease_pencil_eval = grease_pencil_component_eval->get()) {
Span<const bke::greasepencil::Layer *> layers_eval = grease_pencil_eval->layers();
if (layers_eval.size() == layers_orig.size()) {
const bke::greasepencil::Layer &layer_eval = *layers_eval[layer_index];
if (const bke::greasepencil::Drawing *drawing_eval = grease_pencil_eval->get_drawing_at(
layer_eval, frame))
{
if (drawing_eval->strokes().points_num() == drawing_orig->strokes().points_num()) {
deformation.positions = drawing_eval->strokes().positions();
has_deformed_positions = true;
}
if (const GreasePencilDrawingEditHints *drawing_hints =
get_drawing_edit_hint_for_original_drawing(edit_hints, drawing_orig))
{
if (drawing_hints->positions()) {
deformation.positions = *drawing_hints->positions();
}
if (drawing_hints->deform_mats.has_value()) {
deformation.deform_mats = *drawing_hints->deform_mats;
}
}
}
@@ -701,13 +677,13 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object
return deformation;
}
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Depsgraph &depsgraph,
const Object &ob_orig,
const int layer_index,
const int frame)
GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(
const Depsgraph &depsgraph,
const Object &ob_orig,
const bke::greasepencil::Drawing &drawing_orig)
{
const Object *ob_eval = DEG_get_evaluated(&depsgraph, &ob_orig);
return get_evaluated_grease_pencil_drawing_deformation(ob_eval, ob_orig, layer_index, frame);
return get_evaluated_grease_pencil_drawing_deformation(ob_eval, ob_orig, drawing_orig);
}
} // namespace blender::bke::crazyspace

View File

@@ -95,19 +95,19 @@ static void remember_deformed_grease_pencil_if_necessary(const GreasePencil *gre
const Span<const greasepencil::Layer *> layers = grease_pencil->layers();
const Span<const greasepencil::Layer *> orig_layers = orig_grease_pencil.layers();
const int layers_num = layers.size();
if (layers_num != orig_layers.size()) {
return;
}
edit_component.grease_pencil_edit_hints_->drawing_hints.emplace(layers_num);
MutableSpan<GreasePencilDrawingEditHints> all_hints =
*edit_component.grease_pencil_edit_hints_->drawing_hints;
for (const int layer_index : layers.index_range()) {
const greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
grease_pencil->layer(layer_index));
const greasepencil::Layer &orig_layer = *orig_layers[layer_index];
for (const int eval_layer_index : layers.index_range()) {
const greasepencil::Layer &eval_layer = *layers[eval_layer_index];
const greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(eval_layer);
if (eval_layer.runtime->orig_layer_index_ == -1) {
continue;
}
const greasepencil::Layer &orig_layer = *orig_layers[eval_layer.runtime->orig_layer_index_];
const greasepencil::Drawing *orig_drawing = orig_grease_pencil.get_drawing_at(
orig_layer, grease_pencil->runtime->eval_frame);
GreasePencilDrawingEditHints &drawing_hints = all_hints[layer_index];
GreasePencilDrawingEditHints &drawing_hints = all_hints[eval_layer_index];
if (!drawing || !orig_drawing) {
continue;
}

View File

@@ -2299,7 +2299,10 @@ static void grease_pencil_evaluate_layers(GreasePencil &grease_pencil)
* cache. This will only copy the pointers to the layers, not the layers themselves. */
Array<Layer *> layers = grease_pencil.layers_for_write();
for (Layer *layer : layers) {
for (const int layer_i : layers.index_range()) {
Layer *layer = layers[layer_i];
/* Store the original index of the layer. */
layer->runtime->orig_layer_index_ = layer_i;
/* When the visibility is animated, the layer should be retained even when it is invisible.
* Changing the visibility through the animation system does NOT create another evaluated copy,
* and thus the layer has to be kept for this future use. */

View File

@@ -36,7 +36,6 @@ static constexpr int BBOX_PADDING = 2;
* Apply the stroke trim to a drawing.
*/
static bool execute_trim_on_drawing(const int layer_index,
const int frame_number,
const Object &ob_eval,
Object &obact,
const ARegion &region,
@@ -50,8 +49,7 @@ static bool execute_trim_on_drawing(const int layer_index,
/* Get evaluated geometry. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
&ob_eval, obact, layer_index, frame_number);
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(&ob_eval, obact, drawing);
/* Compute screen space positions. */
Array<float2> screen_space_positions(src.points_num());
@@ -181,7 +179,6 @@ static wmOperatorStatus stroke_trim_execute(const bContext *C, const Span<int2>
ed::greasepencil::retrieve_editable_drawings_from_layer(*scene, grease_pencil, layer);
threading::parallel_for_each(drawings, [&](const ed::greasepencil::MutableDrawingInfo &info) {
if (execute_trim_on_drawing(info.layer_index,
info.frame_number,
*ob_eval,
*obact,
*region,
@@ -210,7 +207,6 @@ static wmOperatorStatus stroke_trim_execute(const bContext *C, const Span<int2>
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(rv3d, layer_to_world);
if (execute_trim_on_drawing(info.layer_index,
info.frame_number,
*ob_eval,
*obact,
*region,

View File

@@ -494,7 +494,7 @@ static wmOperatorStatus weight_sample_invoke(bContext *C,
/* Get deformation by modifiers. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *vc.obact, info.layer_index, info.frame_number);
ob_eval, *vc.obact, info.drawing);
IndexMaskMemory memory;
const IndexMask points = retrieve_visible_points(*vc.obact, info.drawing, memory);

View File

@@ -1891,7 +1891,7 @@ static wmOperatorStatus grease_pencil_erase_lasso_exec(bContext *C, wmOperator *
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const bke::CurvesGeometry &curves = info.drawing.strokes();
@@ -1999,7 +1999,7 @@ static wmOperatorStatus grease_pencil_erase_box_exec(bContext *C, wmOperator *op
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const bke::CurvesGeometry &curves = info.drawing.strokes();

View File

@@ -953,58 +953,57 @@ struct EraseOperationExecutor {
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(obact->data);
bool changed = false;
const auto execute_eraser_on_drawing =
[&](const int layer_index, const int frame_number, Drawing &drawing) {
const Layer &layer = grease_pencil.layer(layer_index);
const bke::CurvesGeometry &src = drawing.strokes();
const auto execute_eraser_on_drawing = [&](const int layer_index, Drawing &drawing) {
const Layer &layer = grease_pencil.layer(layer_index);
const bke::CurvesGeometry &src = drawing.strokes();
/* Evaluated geometry. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *obact, layer_index, frame_number);
/* Evaluated geometry. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *obact, drawing);
/* Compute screen space positions. */
Array<float2> screen_space_positions(src.points_num());
threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) {
for (const int src_point : src_points) {
const int result = ED_view3d_project_float_global(
region,
math::transform_point(layer.to_world_space(*ob_eval),
deformation.positions[src_point]),
screen_space_positions[src_point],
V3D_PROJ_TEST_CLIP_NEAR | V3D_PROJ_TEST_CLIP_FAR);
if (result != V3D_PROJ_RET_OK) {
/* Set the screen space position to a impossibly far coordinate for all the points
* that are outside near/far clipping planes, this is to prevent accidental
* intersections with strokes not visibly present in the camera. */
screen_space_positions[src_point] = float2(1e20);
}
}
});
/* Erasing operator. */
bke::CurvesGeometry dst;
bool erased = false;
switch (self.eraser_mode_) {
case GP_BRUSH_ERASER_STROKE:
erased = stroke_eraser(*obact, src, screen_space_positions, dst);
break;
case GP_BRUSH_ERASER_HARD:
erased = hard_eraser(*obact, src, screen_space_positions, dst, self.keep_caps_);
break;
case GP_BRUSH_ERASER_SOFT:
erased = soft_eraser(*obact, src, screen_space_positions, dst, self.keep_caps_);
break;
/* Compute screen space positions. */
Array<float2> screen_space_positions(src.points_num());
threading::parallel_for(src.points_range(), 4096, [&](const IndexRange src_points) {
for (const int src_point : src_points) {
const int result = ED_view3d_project_float_global(
region,
math::transform_point(layer.to_world_space(*ob_eval),
deformation.positions[src_point]),
screen_space_positions[src_point],
V3D_PROJ_TEST_CLIP_NEAR | V3D_PROJ_TEST_CLIP_FAR);
if (result != V3D_PROJ_RET_OK) {
/* Set the screen space position to a impossibly far coordinate for all the points
* that are outside near/far clipping planes, this is to prevent accidental
* intersections with strokes not visibly present in the camera. */
screen_space_positions[src_point] = float2(1e20);
}
}
});
if (erased) {
/* Set the new geometry. */
drawing.geometry.wrap() = std::move(dst);
drawing.tag_topology_changed();
changed = true;
self.affected_drawings_.add(&drawing);
}
};
/* Erasing operator. */
bke::CurvesGeometry dst;
bool erased = false;
switch (self.eraser_mode_) {
case GP_BRUSH_ERASER_STROKE:
erased = stroke_eraser(*obact, src, screen_space_positions, dst);
break;
case GP_BRUSH_ERASER_HARD:
erased = hard_eraser(*obact, src, screen_space_positions, dst, self.keep_caps_);
break;
case GP_BRUSH_ERASER_SOFT:
erased = soft_eraser(*obact, src, screen_space_positions, dst, self.keep_caps_);
break;
}
if (erased) {
/* Set the new geometry. */
drawing.geometry.wrap() = std::move(dst);
drawing.tag_topology_changed();
changed = true;
self.affected_drawings_.add(&drawing);
}
};
if (self.active_layer_only_) {
/* Erase only on the drawing at the current frame of the active layer. */
@@ -1018,15 +1017,14 @@ struct EraseOperationExecutor {
return;
}
execute_eraser_on_drawing(
*grease_pencil.get_layer_index(active_layer), scene->r.cfra, *drawing);
execute_eraser_on_drawing(*grease_pencil.get_layer_index(active_layer), *drawing);
}
else {
/* Erase on all editable drawings. */
const Vector<ed::greasepencil::MutableDrawingInfo> drawings =
ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
for (const ed::greasepencil::MutableDrawingInfo &info : drawings) {
execute_eraser_on_drawing(info.layer_index, info.frame_number, info.drawing);
execute_eraser_on_drawing(info.layer_index, info.drawing);
}
}

View File

@@ -880,7 +880,7 @@ static std::optional<Bounds<float2>> get_boundary_bounds(const ARegion &region,
const float4x4 layer_to_world = layer.to_world_space(object);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
&object_eval, object, info.layer_index, info.frame_number);
&object_eval, object, info.drawing);
const bool only_boundary_strokes = boundary_layers[info.layer_index];
const VArray<float> radii = info.drawing.radii();
const bke::CurvesGeometry &strokes = info.drawing.strokes();

View File

@@ -338,7 +338,7 @@ bke::crazyspace::GeometryDeformation get_drawing_deformation(
const GreasePencilStrokeParams &params)
{
return bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
&params.ob_eval, params.ob_orig, params.layer_index, params.frame_number);
&params.ob_eval, params.ob_orig, params.drawing);
}
Array<float2> calculate_view_positions(const GreasePencilStrokeParams &params,

View File

@@ -131,7 +131,7 @@ void TintOperation::on_stroke_begin(const bContext &C, const InputSample & /*sta
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *obact, drawing_info.layer_index, drawing_info.frame_number);
ob_eval, *obact, drawing_info.drawing);
for (const int point : strokes.points_range()) {
ED_view3d_project_float_global(

View File

@@ -244,7 +244,7 @@ class WeightPaintOperation : public GreasePencilStrokeOperation {
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *this->object, drawing_info.layer_index, drawing_info.frame_number);
ob_eval, *this->object, drawing_info.drawing);
drawing_weight_data.point_positions.reinitialize(deformation.positions.size());
threading::parallel_for(curves.points_range(), 1024, [&](const IndexRange point_range) {
for (const int point : point_range) {

View File

@@ -1188,7 +1188,7 @@ static bool do_lasso_select_grease_pencil(const ViewContext *vc,
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
const IndexMask visible_handle_elements =
ed::greasepencil::retrieve_visible_bezier_handle_elements(
*object, info.drawing, info.layer_index, selection_domain, memory);
@@ -3358,7 +3358,7 @@ static bool ed_grease_pencil_select_pick(bContext *C,
/* Get deformation by modifiers. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
IndexMaskMemory memory;
const IndexMask elements = ed::greasepencil::retrieve_editable_elements(
@@ -4419,7 +4419,7 @@ static bool do_grease_pencil_box_select(const ViewContext *vc,
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
const IndexMask visible_handle_elements =
ed::greasepencil::retrieve_visible_bezier_handle_elements(
*object, info.drawing, info.layer_index, selection_domain, memory);
@@ -5302,7 +5302,7 @@ static bool grease_pencil_circle_select(const ViewContext *vc,
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *object, info.layer_index, info.frame_number);
ob_eval, *object, info.drawing);
const IndexMask visible_handle_elements =
ed::greasepencil::retrieve_visible_bezier_handle_elements(
*object, info.drawing, info.layer_index, selection_domain, memory);

View File

@@ -1214,7 +1214,7 @@ bool ED_view3d_minmax_verts(const Scene *scene, Object *obedit, float r_min[3],
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
obedit, ob_orig, info.layer_index, info.frame_number);
obedit, ob_orig, info.drawing);
const bke::greasepencil::Layer &layer = grease_pencil.layer(info.layer_index);
const float4x4 layer_to_world = layer.to_world_space(*obedit);

View File

@@ -194,7 +194,7 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
bke::CurvesGeometry &curves = info.drawing.strokes_for_write();
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
*CTX_data_depsgraph_pointer(C), *object, info.layer_index, info.frame_number);
*CTX_data_depsgraph_pointer(C), *object, info.drawing);
std::optional<MutableSpan<float>> value_attribute;
if (t->mode == TFM_GPENCIL_OPACITY) {

View File

@@ -798,7 +798,7 @@ static int gizmo_3d_foreach_selected(const bContext *C,
const bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
*depsgraph, *ob, info.layer_index, info.frame_number);
*depsgraph, *ob, info.drawing);
const float4x4 layer_transform =
mat_local * grease_pencil.layer(info.layer_index).to_object_space(*ob_iter);