Fix #130945: Grease Pencil: Crazyspace support in sculpt mode

Grease Pencil v3 did not have crazyspace support yet. Without this sculpting on
deformed geometry (e.g. on top of an armature modifier) will yield incorrect
offsets because the tool writes to original data based on deformed positions.

This patch adds computation of local deformation matrices which are stored in
the `GeometryComponentEditData`. Those matrices are then used to convert local
deformation of the evaluated geometry back to a deformation of the original
geometry. All the relevant sculpt tools support the crazyspace correction now,
using the `compute_orig_delta` helper function.

Computing the deformation matrices should happen alongside modifier evaluation
for any deforming modifier. This has been implemented for the armature modifier,
others can be added.

A fallback implementation for curves could also be added for modifiers that
don't have an easy way to calculate local transformation. A "natural"
orientation for both the original and deformed positions is calculated, then the
difference yields deform matrices. For meshes the approach is to use the surface
normal and a stable tangent space. For curves the common local coordinate frame
based on parallel transport might be used.

Currently crazyspace correction for the _Clone_ tool does not work because of
#131496.

Pull Request: https://projects.blender.org/blender/blender/pulls/131499
This commit is contained in:
Lukas Tönne
2024-12-09 12:33:44 +01:00
committed by Falk David
parent 186804eabc
commit 0d8f040c8b
17 changed files with 185 additions and 27 deletions

View File

@@ -644,7 +644,7 @@ void BKE_armature_deform_coords_with_curves(
const Object &ob_target,
const ListBase *defbase,
blender::MutableSpan<blender::float3> vert_coords,
std::optional<blender::MutableSpan<blender::float3>> vert_coords_prev,
std::optional<blender::Span<blender::float3>> vert_coords_prev,
std::optional<blender::MutableSpan<blender::float3x3>> vert_deform_mats,
blender::Span<MDeformVert> dverts,
int deformflag,

View File

@@ -362,6 +362,10 @@ struct GeometrySet {
* Returns read-only curve edit hints or null.
*/
const CurvesEditHints *get_curve_edit_hints() const;
/**
* Returns read-only Grease Pencil edit hints or null.
*/
const GreasePencilEditHints *get_grease_pencil_edit_hints() const;
/**
* Returns read-only gizmo edit hints or null.
*/
@@ -395,6 +399,10 @@ struct GeometrySet {
* Returns mutable curve edit hints or null.
*/
CurvesEditHints *get_curve_edit_hints_for_write();
/**
* Returns mutable Grease Pencil edit hints or null.
*/
GreasePencilEditHints *get_grease_pencil_edit_hints_for_write();
/**
* Returns mutable gizmo edit hints or null.
*/

View File

@@ -929,6 +929,12 @@ class GreasePencilDrawingEditHints {
const greasepencil::Drawing *drawing_orig;
ImplicitSharingPtrAndData positions_data;
/**
* Matrices which transform point movement vectors from original data to corresponding movements
* of evaluated data.
*/
std::optional<Array<float3x3>> deform_mats;
std::optional<Span<float3>> positions() const;
std::optional<MutableSpan<float3>> positions_for_write();
};

View File

@@ -637,7 +637,7 @@ void BKE_armature_deform_coords_with_curves(
const Object &ob_target,
const ListBase *defbase,
blender::MutableSpan<blender::float3> vert_coords,
std::optional<blender::MutableSpan<blender::float3>> vert_coords_prev,
std::optional<blender::Span<blender::float3>> vert_coords_prev,
std::optional<blender::MutableSpan<blender::float3x3>> vert_deform_mats,
blender::Span<MDeformVert> dverts,
int deformflag,
@@ -647,6 +647,9 @@ void BKE_armature_deform_coords_with_curves(
* used for Grease Pencil layers as well. */
BLI_assert(dverts.size() == vert_coords.size());
/* const_cast for old positions for the C API, these are not actually written. */
blender::float3 *vert_coords_prev_data = const_cast<blender::float3 *>(vert_coords_prev->data());
armature_deform_coords_impl(
&ob_arm,
&ob_target,
@@ -655,7 +658,7 @@ void BKE_armature_deform_coords_with_curves(
vert_deform_mats ? reinterpret_cast<float(*)[3][3]>(vert_deform_mats->data()) : nullptr,
vert_coords.size(),
deformflag,
vert_coords_prev ? reinterpret_cast<float(*)[3]>(vert_coords_prev->data()) : nullptr,
vert_coords_prev ? reinterpret_cast<float(*)[3]>(vert_coords_prev_data) : nullptr,
defgrp_name.c_str(),
dverts,
nullptr,

View File

@@ -652,6 +652,8 @@ 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 =
@@ -663,12 +665,18 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object
BLI_assert(edit_hints->drawing_hints->size() == layers_orig.size());
const GreasePencilDrawingEditHints &drawing_hints =
edit_hints->drawing_hints.value()[layer_index];
if (const std::optional<Span<float3>> positions = drawing_hints.positions()) {
deformation.positions = *positions;
return deformation;
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 =
@@ -682,7 +690,7 @@ GeometryDeformation get_evaluated_grease_pencil_drawing_deformation(const Object
layer_eval, frame))
if (drawing_eval->strokes().points_num() == drawing_orig->strokes().points_num()) {
deformation.positions = drawing_eval->strokes().positions();
return deformation;
has_deformed_positions = true;
}
}
}

View File

@@ -347,6 +347,12 @@ const CurvesEditHints *GeometrySet::get_curve_edit_hints() const
return (component == nullptr) ? nullptr : component->curves_edit_hints_.get();
}
const GreasePencilEditHints *GeometrySet::get_grease_pencil_edit_hints() const
{
const GeometryComponentEditData *component = this->get_component<GeometryComponentEditData>();
return (component == nullptr) ? nullptr : component->grease_pencil_edit_hints_.get();
}
const GizmoEditHints *GeometrySet::get_gizmo_edit_hints() const
{
const GeometryComponentEditData *component = this->get_component<GeometryComponentEditData>();
@@ -576,6 +582,16 @@ CurvesEditHints *GeometrySet::get_curve_edit_hints_for_write()
return component.curves_edit_hints_.get();
}
GreasePencilEditHints *GeometrySet::get_grease_pencil_edit_hints_for_write()
{
if (!this->has<GeometryComponentEditData>()) {
return nullptr;
}
GeometryComponentEditData &component =
this->get_component_for_write<GeometryComponentEditData>();
return component.grease_pencil_edit_hints_.get();
}
GizmoEditHints *GeometrySet::get_gizmo_edit_hints_for_write()
{
if (!this->has<GeometryComponentEditData>()) {

View File

@@ -2192,9 +2192,9 @@ void BKE_grease_pencil_data_update(Depsgraph *depsgraph, Scene *scene, Object *o
if (layer_attributes.contains("tint_color") || layer_attributes.contains("radius_offset")) {
grease_pencil_do_layer_adjustments(*geometry_set.get_grease_pencil_for_write());
}
/* Only add the edit hint component in edit mode for now so users can properly select deformed
* drawings. */
if (object->mode == OB_MODE_EDIT) {
/* Only add the edit hint component in edit mode or sculpt mode for now so users can properly
* select deformed drawings. */
if (ELEM(object->mode, OB_MODE_EDIT, OB_MODE_SCULPT_GREASE_PENCIL)) {
GeometryComponentEditData &edit_component =
geometry_set.get_component_for_write<GeometryComponentEditData>();
edit_component.grease_pencil_edit_hints_ = std::make_unique<GreasePencilEditHints>(

View File

@@ -150,6 +150,17 @@ DeltaProjectionFunc get_screen_projection_fn(const GreasePencilStrokeParams &par
const Object &object,
const bke::greasepencil::Layer &layer);
/**
* Compute position offset for a point in the original geometry
* from a screen offset and crazyspace deformation info.
* \param projection_fn Projection from screen space to the evaluated object.
* \param deformation Converts evaluated position delta to original geometry.
*/
float3 compute_orig_delta(const DeltaProjectionFunc &projection_fn,
const bke::crazyspace::GeometryDeformation &deformation,
int index,
const float2 &screen_delta);
bool do_vertex_color_points(const Brush &brush);
bool do_vertex_color_fill(const Brush &brush);

View File

@@ -272,6 +272,19 @@ DeltaProjectionFunc get_screen_projection_fn(const GreasePencilStrokeParams &par
return [](const float3 &, const float2 &) { return float3(); };
}
float3 compute_orig_delta(const DeltaProjectionFunc &projection_fn,
const bke::crazyspace::GeometryDeformation &deformation,
const int index,
const float2 &screen_delta)
{
const float3 old_position_eval = deformation.positions[index];
const float3 new_position_eval = projection_fn(old_position_eval, screen_delta);
const float3 translation_eval = new_position_eval - old_position_eval;
const float3 translation_orig = deformation.translation_from_deformed_to_original(
index, translation_eval);
return translation_orig;
}
GreasePencilStrokeParams GreasePencilStrokeParams::from_context(
const Scene &scene,
Depsgraph &depsgraph,

View File

@@ -81,7 +81,8 @@ void CloneOperation::on_stroke_begin(const bContext &C, const InputSample &start
MutableSpan<float3> positions = curves.positions_for_write();
threading::parallel_for(pasted_points, 4096, [&](const IndexRange range) {
for (const int point_i : range) {
positions[point_i] = projection_fn(deformation.positions[point_i], mouse_delta);
positions[point_i] = compute_orig_delta(
projection_fn, deformation, point_i, mouse_delta);
}
});
params.drawing.tag_positions_changed();

View File

@@ -204,8 +204,8 @@ void GrabOperation::on_stroke_extended(const bContext &C, const InputSample &ext
MutableSpan<float3> positions = curves.positions_for_write();
mask.foreach_index(GrainSize(4096), [&](const int point_i, const int index) {
/* Translate the point with the influence factor. */
positions[point_i] = projection_fn(deformation.positions[point_i],
mouse_delta_win * weights[index]);
positions[point_i] += compute_orig_delta(
projection_fn, deformation, point_i, mouse_delta_win * weights[index]);
});
params.drawing.tag_positions_changed();

View File

@@ -70,8 +70,8 @@ void PinchOperation::on_stroke_extended(const bContext &C, const InputSample &ex
const float influence_squared = influence * influence / 25.0f;
const float influence_final = invert ? 1.0 + influence_squared :
1.0f - influence_squared;
positions[point_i] = projection_fn(deformation.positions[point_i],
(target - co) * (1.0f - influence_final));
positions[point_i] += compute_orig_delta(
projection_fn, deformation, point_i, (target - co) * (1.0f - influence_final));
});
params.drawing.tag_positions_changed();

View File

@@ -67,8 +67,8 @@ void PushOperation::on_stroke_extended(const bContext &C, const InputSample &ext
return;
}
positions[point_i] = projection_fn(deformation.positions[point_i],
mouse_delta * influence);
positions[point_i] += compute_orig_delta(
projection_fn, deformation, point_i, mouse_delta * influence);
});
params.drawing.tag_positions_changed();

View File

@@ -96,8 +96,8 @@ void RandomizeOperation::on_stroke_extended(const bContext &C, const InputSample
return;
}
const float noise = 2.0f * hash_rng(seed, 5678, point_i) - 1.0f;
positions[point_i] = projection_fn(deformation.positions[point_i],
sideways * influence * noise);
positions[point_i] += compute_orig_delta(
projection_fn, deformation, point_i, sideways * influence * noise);
});
params.drawing.tag_positions_changed();

View File

@@ -79,9 +79,11 @@ void TwistOperation::on_stroke_extended(const bContext &C, const InputSample &ex
const float angle = DEG2RADF(invert ? -1.0f : 1.0f) * influence;
const float2 radial_offset = co - mouse_pos;
positions[point_i] = projection_fn(deformation.positions[point_i],
rotate_by_angle(radial_offset, angle) -
radial_offset);
positions[point_i] += compute_orig_delta(projection_fn,
deformation,
point_i,
rotate_by_angle(radial_offset, angle) -
radial_offset);
});
params.drawing.tag_positions_changed();

View File

@@ -194,6 +194,33 @@ static void transform_curve_edit_hints(bke::CurvesEditHints &edit_hints, const f
}
}
static void transform_grease_pencil_edit_hints(bke::GreasePencilEditHints &edit_hints,
const float4x4 &transform)
{
if (!edit_hints.drawing_hints) {
return;
}
for (bke::GreasePencilDrawingEditHints &drawing_hints : *edit_hints.drawing_hints) {
if (const std::optional<MutableSpan<float3>> positions = drawing_hints.positions_for_write()) {
transform_positions(*positions, transform);
}
float3x3 deform_mat = transform.view<3, 3>();
if (drawing_hints.deform_mats.has_value()) {
MutableSpan<float3x3> deform_mats = *drawing_hints.deform_mats;
threading::parallel_for(deform_mats.index_range(), 1024, [&](const IndexRange range) {
for (const int64_t i : range) {
deform_mats[i] = deform_mat * deform_mats[i];
}
});
}
else {
drawing_hints.deform_mats.emplace(drawing_hints.drawing_orig->strokes().points_num(),
deform_mat);
}
}
}
static void transform_gizmo_edit_hints(bke::GizmoEditHints &edit_hints, const float4x4 &transform)
{
for (float4x4 &m : edit_hints.gizmo_transforms.values()) {
@@ -268,6 +295,11 @@ std::optional<TransformGeometryErrors> transform_geometry(bke::GeometrySet &geom
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
transform_curve_edit_hints(*curve_edit_hints, transform);
}
if (bke::GreasePencilEditHints *grease_pencil_edit_hints =
geometry.get_grease_pencil_edit_hints_for_write())
{
transform_grease_pencil_edit_hints(*grease_pencil_edit_hints, transform);
}
if (bke::GizmoEditHints *gizmo_edit_hints = geometry.get_gizmo_edit_hints_for_write()) {
transform_gizmo_edit_hints(*gizmo_edit_hints, transform);
}

View File

@@ -8,6 +8,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_array_utils.hh"
#include "BLI_math_matrix.hh"
#include "DNA_defaults.h"
@@ -104,7 +105,22 @@ static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphCont
DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Armature Modifier");
}
static void modify_curves(ModifierData &md, const ModifierEvalContext &ctx, Drawing &drawing)
static ImplicitSharingPtrAndData save_shared_attribute(const bke::GAttributeReader &attribute)
{
if (attribute.sharing_info && attribute.varray.is_span()) {
const void *data = attribute.varray.get_internal_span().data();
attribute.sharing_info->add_user();
return {ImplicitSharingPtr(attribute.sharing_info), data};
}
auto *data = new ImplicitSharedValue<GArray<>>(attribute.varray.type(), attribute.varray.size());
attribute.varray.materialize(data->data.data());
return {ImplicitSharingPtr<>(data), data->data.data()};
}
static void modify_curves(ModifierData &md,
const ModifierEvalContext &ctx,
Drawing &drawing,
bke::GreasePencilDrawingEditHints *edit_hints)
{
auto &amd = reinterpret_cast<GreasePencilArmatureModifierData &>(md);
modifier::greasepencil::ensure_no_bezier_curves(drawing);
@@ -130,15 +146,35 @@ static void modify_curves(ModifierData &md, const ModifierEvalContext &ctx, Draw
return;
}
ImplicitSharingPtrAndData old_positions_data = save_shared_attribute(
curves.attributes().lookup("position", CD_PROP_FLOAT3));
Span<float3> old_positions = {static_cast<const float3 *>(old_positions_data.data),
curves.points_num()};
std::optional<MutableSpan<float3x3>> deform_mats;
if (edit_hints) {
if (!edit_hints->deform_mats.has_value()) {
edit_hints->deform_mats.emplace(drawing.strokes().points_num(), float3x3::identity());
}
deform_mats = edit_hints->deform_mats->as_mutable_span();
}
curves_mask.foreach_index(blender::GrainSize(128), [&](const int curve_i) {
const IndexRange points = points_by_curve[curve_i];
std::optional<Span<float3>> old_positions_for_curve;
std::optional<MutableSpan<float3x3>> deform_mats_for_curve;
if (deform_mats) {
old_positions_for_curve = old_positions.slice(points);
deform_mats_for_curve = deform_mats->slice(points);
}
BKE_armature_deform_coords_with_curves(*amd.object,
*ctx.object,
&curves.vertex_group_names,
positions.slice(points),
std::nullopt,
std::nullopt,
old_positions_for_curve,
deform_mats_for_curve,
dverts.slice(points),
deformflag,
amd.influence.vertex_group_name);
@@ -157,15 +193,37 @@ static void modify_geometry_set(ModifierData *md,
return;
}
GreasePencil &grease_pencil = *geometry_set->get_grease_pencil_for_write();
const GreasePencil &grease_pencil_orig = *reinterpret_cast<GreasePencil *>(
DEG_get_original_id(&grease_pencil.id));
const int frame = grease_pencil.runtime->eval_frame;
MutableSpan<bke::GreasePencilDrawingEditHints> edit_hints = {};
if (geometry_set->has_component<bke::GeometryComponentEditData>()) {
bke::GeometryComponentEditData &edit_component =
geometry_set->get_component_for_write<bke::GeometryComponentEditData>();
if (edit_component.grease_pencil_edit_hints_) {
if (!edit_component.grease_pencil_edit_hints_->drawing_hints) {
edit_component.grease_pencil_edit_hints_->drawing_hints.emplace(
grease_pencil_orig.layers().size());
}
edit_hints = *edit_component.grease_pencil_edit_hints_->drawing_hints;
}
}
IndexMaskMemory mask_memory;
const IndexMask layer_mask = modifier::greasepencil::get_filtered_layer_mask(
grease_pencil, amd->influence, mask_memory);
const Vector<Drawing *> drawings = modifier::greasepencil::get_drawings_for_write(
grease_pencil, layer_mask, frame);
threading::parallel_for_each(drawings,
[&](Drawing *drawing) { modify_curves(*md, *ctx, *drawing); });
threading::parallel_for_each(drawings.index_range(), [&](const int index) {
Drawing *drawing = drawings[index];
if (edit_hints.is_empty()) {
modify_curves(*md, *ctx, *drawing, nullptr);
}
else {
modify_curves(*md, *ctx, *drawing, &edit_hints[index]);
}
});
}
static void panel_draw(const bContext *C, Panel *panel)