Files
test/source/blender/geometry/intern/transform.cc
Lukas Tönne 0d8f040c8b 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
2024-12-09 12:33:44 +01:00

323 lines
11 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#ifdef WITH_OPENVDB
# include <openvdb/openvdb.h>
#endif
#include "GEO_transform.hh"
#include "BLI_math_base.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "DNA_grease_pencil_types.h"
#include "DNA_mesh_types.h"
#include "DNA_pointcloud_types.h"
#include "BKE_attribute.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_nodes_gizmos_transforms.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_instances.hh"
#include "BKE_mesh.hh"
#include "BKE_pointcloud.hh"
#include "BKE_volume.hh"
namespace blender::geometry {
static void translate_positions(MutableSpan<float3> positions, const float3 &translation)
{
threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
for (float3 &position : positions.slice(range)) {
position += translation;
}
});
}
static void transform_positions(MutableSpan<float3> positions, const float4x4 &matrix)
{
threading::parallel_for(positions.index_range(), 1024, [&](const IndexRange range) {
for (float3 &position : positions.slice(range)) {
position = math::transform_point(matrix, position);
}
});
}
static void transform_mesh(Mesh &mesh, const float4x4 &transform)
{
transform_positions(mesh.vert_positions_for_write(), transform);
mesh.tag_positions_changed();
}
static void translate_pointcloud(PointCloud &pointcloud, const float3 translation)
{
if (math::is_zero(translation)) {
return;
}
std::optional<Bounds<float3>> bounds;
if (pointcloud.runtime->bounds_cache.is_cached()) {
bounds = pointcloud.runtime->bounds_cache.data();
}
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
bke::SpanAttributeWriter position = attributes.lookup_or_add_for_write_span<float3>(
"position", bke::AttrDomain::Point);
translate_positions(position.span, translation);
position.finish();
if (bounds) {
bounds->min += translation;
bounds->max += translation;
pointcloud.runtime->bounds_cache.ensure([&](Bounds<float3> &r_data) { r_data = *bounds; });
}
}
static void transform_pointcloud(PointCloud &pointcloud, const float4x4 &transform)
{
bke::MutableAttributeAccessor attributes = pointcloud.attributes_for_write();
bke::SpanAttributeWriter position = attributes.lookup_or_add_for_write_span<float3>(
"position", bke::AttrDomain::Point);
transform_positions(position.span, transform);
position.finish();
}
static void translate_greasepencil(GreasePencil &grease_pencil, const float3 translation)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Layer &layer = grease_pencil.layer(layer_index);
float4x4 local_transform = layer.local_transform();
local_transform.location() += translation;
layer.set_local_transform(local_transform);
}
}
static void transform_greasepencil(GreasePencil &grease_pencil, const float4x4 &transform)
{
using namespace blender::bke::greasepencil;
for (const int layer_index : grease_pencil.layers().index_range()) {
Layer &layer = grease_pencil.layer(layer_index);
float4x4 local_transform = layer.local_transform();
local_transform = transform * local_transform;
layer.set_local_transform(local_transform);
}
}
static void translate_instances(bke::Instances &instances, const float3 translation)
{
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
add_v3_v3(instance_transform.ptr()[3], translation);
}
});
}
static void transform_instances(bke::Instances &instances, const float4x4 &transform)
{
MutableSpan<float4x4> transforms = instances.transforms_for_write();
threading::parallel_for(transforms.index_range(), 1024, [&](const IndexRange range) {
for (float4x4 &instance_transform : transforms.slice(range)) {
instance_transform = transform * instance_transform;
}
});
}
static bool transform_volume(Volume &volume, const float4x4 &transform)
{
bool found_too_small_scale = false;
#ifdef WITH_OPENVDB
openvdb::Mat4s vdb_matrix;
memcpy(vdb_matrix.asPointer(), &transform, sizeof(float[4][4]));
openvdb::Mat4d vdb_matrix_d{vdb_matrix};
const int grids_num = BKE_volume_num_grids(&volume);
for (const int i : IndexRange(grids_num)) {
bke::VolumeGridData *volume_grid = BKE_volume_grid_get_for_write(&volume, i);
float4x4 grid_matrix = bke::volume_grid::get_transform_matrix(*volume_grid);
grid_matrix = transform * grid_matrix;
const float determinant = math::determinant(grid_matrix);
if (!BKE_volume_grid_determinant_valid(determinant)) {
found_too_small_scale = true;
/* Clear the tree because it is too small. */
bke::volume_grid::clear_tree(*volume_grid);
if (determinant == 0) {
/* Reset rotation and scale. */
grid_matrix.x_axis() = float3(1, 0, 0);
grid_matrix.y_axis() = float3(0, 1, 0);
grid_matrix.z_axis() = float3(0, 0, 1);
}
else {
/* Keep rotation but reset scale. */
grid_matrix.x_axis() = math::normalize(grid_matrix.x_axis());
grid_matrix.y_axis() = math::normalize(grid_matrix.y_axis());
grid_matrix.z_axis() = math::normalize(grid_matrix.z_axis());
}
}
bke::volume_grid::set_transform_matrix(*volume_grid, grid_matrix);
}
#else
UNUSED_VARS(volume, transform);
#endif
return found_too_small_scale;
}
static void translate_volume(Volume &volume, const float3 translation)
{
transform_volume(volume, math::from_location<float4x4>(translation));
}
static void transform_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float4x4 &transform)
{
if (const std::optional<MutableSpan<float3>> positions = edit_hints.positions_for_write()) {
transform_positions(*positions, transform);
}
float3x3 deform_mat;
copy_m3_m4(deform_mat.ptr(), transform.ptr());
if (edit_hints.deform_mats.has_value()) {
MutableSpan<float3x3> deform_mats = *edit_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 {
edit_hints.deform_mats.emplace(edit_hints.curves_id_orig.geometry.point_num, deform_mat);
}
}
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()) {
m = transform * m;
}
}
static void translate_curve_edit_hints(bke::CurvesEditHints &edit_hints, const float3 &translation)
{
if (const std::optional<MutableSpan<float3>> positions = edit_hints.positions_for_write()) {
translate_positions(*positions, translation);
}
}
static void translate_gizmos_edit_hints(bke::GizmoEditHints &edit_hints, const float3 &translation)
{
for (float4x4 &m : edit_hints.gizmo_transforms.values()) {
m.location() += translation;
}
}
void translate_geometry(bke::GeometrySet &geometry, const float3 translation)
{
if (Curves *curves = geometry.get_curves_for_write()) {
curves->geometry.wrap().translate(translation);
}
if (Mesh *mesh = geometry.get_mesh_for_write()) {
BKE_mesh_translate(mesh, translation, false);
}
if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) {
translate_pointcloud(*pointcloud, translation);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
translate_greasepencil(*grease_pencil, translation);
}
if (Volume *volume = geometry.get_volume_for_write()) {
translate_volume(*volume, translation);
}
if (bke::Instances *instances = geometry.get_instances_for_write()) {
translate_instances(*instances, translation);
}
if (bke::CurvesEditHints *curve_edit_hints = geometry.get_curve_edit_hints_for_write()) {
translate_curve_edit_hints(*curve_edit_hints, translation);
}
if (bke::GizmoEditHints *gizmo_edit_hints = geometry.get_gizmo_edit_hints_for_write()) {
translate_gizmos_edit_hints(*gizmo_edit_hints, translation);
}
}
std::optional<TransformGeometryErrors> transform_geometry(bke::GeometrySet &geometry,
const float4x4 &transform)
{
TransformGeometryErrors errors;
if (Curves *curves = geometry.get_curves_for_write()) {
curves->geometry.wrap().transform(transform);
}
if (Mesh *mesh = geometry.get_mesh_for_write()) {
transform_mesh(*mesh, transform);
}
if (PointCloud *pointcloud = geometry.get_pointcloud_for_write()) {
transform_pointcloud(*pointcloud, transform);
}
if (GreasePencil *grease_pencil = geometry.get_grease_pencil_for_write()) {
transform_greasepencil(*grease_pencil, transform);
}
if (Volume *volume = geometry.get_volume_for_write()) {
errors.volume_too_small = transform_volume(*volume, transform);
}
if (bke::Instances *instances = geometry.get_instances_for_write()) {
transform_instances(*instances, transform);
}
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);
}
if (errors.volume_too_small) {
return errors;
}
return std::nullopt;
}
void transform_mesh(Mesh &mesh,
const float3 translation,
const math::Quaternion rotation,
const float3 scale)
{
const float4x4 matrix = math::from_loc_rot_scale<float4x4>(translation, rotation, scale);
transform_mesh(mesh, matrix);
}
} // namespace blender::geometry