Animation: New test for armature deformation

This tests code in `armature_deform.cc` under various combinations
of input options.

- Interpolation:
    - [x] Linear
    - [x] Dual-Quaternion (aka. "Preserve Volume", `ARM_DEF_QUATERNION`)
- Bone weighting:
    - [x] disabled (no `ARM_DEF_*` flags)
    - [x] envelopes (`ARM_DEF_ENVELOPE`)
    - [x] vertex groups (`ARM_DEF_VGROUP`)
    - [x] envelopes + vertex groups
    - [x] single vertex group (`defgrp_name` parameter)
- Outputs:
    - [x] Position-only
    - [x] "Full" (deform matrix, for crazyspace)
- Target object types:
    - [x] mesh
    - [x] edit-mesh (bmesh)
    - [x] curves
    - [x] unsupported ID type (should pass through)
- [ ] Inverted vertex group (`ARM_DEF_INVERT_VGROUP`)
- [ ] Multi-modifier feature

Pull Request: https://projects.blender.org/blender/blender/pulls/140541
This commit is contained in:
Lukas Tönne
2025-06-23 18:11:33 +02:00
parent a33881286a
commit 8dd3bc48cb
3 changed files with 880 additions and 0 deletions

View File

@@ -832,6 +832,7 @@ if(WITH_GTESTS)
set(TEST_SRC
intern/action_test.cc
intern/armature_test.cc
intern/armature_deform_test.cc
intern/asset_metadata_test.cc
intern/attribute_storage_test.cc
intern/bpath_test.cc

View File

@@ -0,0 +1,876 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_string.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BKE_object_deform.h"
#include "CLG_log.h"
#include "DNA_armature_types.h"
#include "DNA_curves_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "testing/testing.h"
/* TODO Parameterized tests are not registered in `blender_test`.
* Possible reason: the cmake gtest_add_tests function may not be able to detect them reliably.
* https://cmake.org/cmake/help/latest/module/GoogleTest.html
* https://www.kitware.com/dynamic-google-test-discovery-in-cmake-3-10/
*/
// #define USE_PARAMETERIZED_TESTS
namespace blender::bke::tests {
/* Type of data that is being deformed.
* This distinction is needed to handle some inconsistent behaviors. */
enum class TargetDataType {
Mesh,
EditMesh,
Curves,
GreasePencil,
};
enum class InterpolationTest {
/* Linear interpolation. */
Linear,
/* Dual-quaternion method, aka. "Preserve Volume" (ARM_DEF_QUATERNION). */
DualQuaternion,
};
enum class OutputValueTest {
/* Only position vector. */
Position,
/* Vector and deformation matrix ("full deform"). */
PositionAndDeformMatrix,
};
enum class WeightingTest {
/* Disabled (no deform). */
None,
/* Falloff from closest envelope point. */
Envelope,
/* Vertex group weight. */
VertexGroups,
/* Use both envelopes and vertex groups. */
EnvelopeAndVertexGroups,
};
enum class MaskingTest {
/* Deform all vertices. */
All,
/* Limit deformation to one vertex group. */
VertexGroup,
};
/* Defines the source of vertex groups and weights for mesh deformation. */
enum class VertexWeightSource {
/* Read vertex groups and weights from the target object's mesh data. */
TargetObject,
/* Use a separate mesh for defining vertex weights. */
SeparateMesh,
};
class ArmatureDeformTestBase {
public:
Main *bmain;
/* Translation for bones.
* Rotation is omitted here for simplicity, the goal is to test all the code paths rather than
* the details of the bone transformation. Other tests are more suitable for comparing
* deformation results. */
static float3 offset_bone1()
{
return float3(5, 0, 1);
}
static float3 offset_bone2()
{
return float3(0, -2, 0);
}
/* This happens usually in BKE_pose_bone_done. Update here to avoid creating a full depsgraph. */
static void update_pose_matrices(bPoseChannel &pchan)
{
BKE_pchan_calc_mat(&pchan);
if (!(pchan.bone->flag & BONE_NO_DEFORM)) {
mat4_to_dquat(&pchan.runtime.deform_dual_quat, pchan.bone->arm_mat, pchan.chan_mat);
}
}
Object *create_test_armature_object() const
{
Object *ob = BKE_object_add_only_object(bmain, OB_ARMATURE, "Test Armature Object");
bArmature *arm = BKE_id_new<bArmature>(bmain, "Test Armature");
ob->data = arm;
Bone *bone1 = MEM_callocN<Bone>("Bone1");
STRNCPY(bone1->name, "Bone1");
copy_v3_v3(bone1->tail, float3(0, 0, 0));
copy_v3_v3(bone1->head, float3(0, 0, 1));
BLI_addtail(&arm->bonebase, bone1);
BKE_armature_where_is_bone(bone1, nullptr, false);
bone1->weight = 1.0f;
/* Bone envelope large enough to include all vertices.
* Falloff math isn't tested here, just have to make sure vertices are included. */
bone1->rad_head = 2.0f;
bone1->rad_tail = 2.0f;
Bone *bone2 = MEM_callocN<Bone>("Bone2");
STRNCPY(bone2->name, "Bone2");
copy_v3_v3(bone2->tail, float3(0, 0, 0));
copy_v3_v3(bone2->head, float3(0, 0, 1));
BLI_addtail(&arm->bonebase, bone2);
BKE_armature_where_is_bone(bone2, nullptr, false);
bone2->weight = 1.0f;
bone2->rad_head = 2.0f;
bone2->rad_tail = 2.0f;
BKE_pose_ensure(bmain, ob, arm, false);
bPoseChannel *pchan1 = BKE_pose_channel_find_name(ob->pose, "Bone1");
bPoseChannel *pchan2 = BKE_pose_channel_find_name(ob->pose, "Bone2");
copy_v3_v3(pchan1->loc, offset_bone1());
copy_v3_v3(pchan2->loc, offset_bone2());
update_pose_matrices(*pchan1);
update_pose_matrices(*pchan2);
return ob;
}
static Span<float3> vertex_positions()
{
static Array<float3> data = {float3(-1, -1, -1),
float3(1, -1, -1),
float3(-1, 1, -1),
float3(1, 1, -1),
float3(-1, -1, 1),
float3(1, -1, 1),
float3(-1, 1, 1),
float3(1, 1, 1)};
return data;
}
static Array<float3x3> identity_deform_mats()
{
return Array<float3x3>(8, float3x3::identity());
}
static Span<float> vertex_weights_bone1()
{
static Array<float> data = {1, 1, 1, 1, 1, 1, 1, 1};
return data;
}
static Span<float> vertex_weights_bone2()
{
static Array<float> data = {0, 0, 0, 0, 1, 1, 1, 1};
return data;
}
static Span<int> curve_offsets()
{
static Array<int> data = {0, 2, 5, 8};
return data;
}
static Mesh *create_test_mesh()
{
Mesh *mesh = BKE_mesh_new_nomain(vertex_positions().size(), 0, 0, 0);
mesh->vert_positions_for_write().copy_from(vertex_positions());
MutableSpan<MDeformVert> dverts = mesh->deform_verts_for_write();
for (const int i : dverts.index_range()) {
const float weight0 = vertex_weights_bone1()[i];
const float weight1 = vertex_weights_bone2()[i];
if (weight0 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 0, weight0);
}
if (weight1 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 1, weight1);
}
}
mesh->tag_positions_changed();
bDeformGroup *defgroup1 = MEM_callocN<bDeformGroup>(__func__);
bDeformGroup *defgroup2 = MEM_callocN<bDeformGroup>(__func__);
STRNCPY(defgroup1->name, "Bone1");
STRNCPY(defgroup2->name, "Bone2");
BLI_addtail(&mesh->vertex_group_names, defgroup1);
BLI_addtail(&mesh->vertex_group_names, defgroup2);
return mesh;
}
/* Creates a cube with all vertices in "Bone1" group and the top face in "Bone2" group. */
Object *create_test_mesh_object() const
{
Object *ob = BKE_object_add_only_object(bmain, OB_MESH, "Test Mesh Object");
Mesh *mesh_in_main = BKE_mesh_add(bmain, "Test Mesh");
ob->data = mesh_in_main;
Mesh *mesh = create_test_mesh();
BKE_mesh_nomain_to_mesh(mesh, mesh_in_main, ob);
BLI_assert(!mesh_in_main->deform_verts().is_empty());
return ob;
}
/* Creates curves with a mix of vertices in "Bone1" and "Bone2" groups.
* Curves datablock does not support vertex groups at this point, these are ignored. */
Object *create_test_curves_object() const
{
Object *ob = BKE_object_add_only_object(bmain, OB_CURVES, "Test Curves Object");
Curves *curves_id = BKE_curves_add(bmain, "Test Curves");
ob->data = curves_id;
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
curves.resize(vertex_positions().size(), 3);
curves.offsets_for_write().copy_from(curve_offsets());
curves.positions_for_write().copy_from(vertex_positions());
MutableSpan<MDeformVert> dverts = curves.deform_verts_for_write();
for (const int i : dverts.index_range()) {
const float weight0 = vertex_weights_bone1()[i];
const float weight1 = vertex_weights_bone2()[i];
if (weight0 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 0, weight0);
}
if (weight1 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 1, weight1);
}
}
curves.tag_topology_changed();
curves.tag_positions_changed();
return ob;
}
/* Creates grease pencil with a mix of vertices in "Bone1" and "Bone2" groups. */
Object *create_test_grease_pencil_object() const
{
Object *ob = BKE_object_add_only_object(bmain, OB_GREASE_PENCIL, "Test Grease Pencil Object");
GreasePencil *grease_pencil = BKE_grease_pencil_add(bmain, "Test Grease Pencil");
ob->data = grease_pencil;
bke::greasepencil::Layer &layer = grease_pencil->add_layer("Test");
greasepencil::Drawing &drawing = grease_pencil->insert_frame(layer, 1)->wrap();
bke::CurvesGeometry &curves = drawing.geometry.wrap();
curves.resize(vertex_positions().size(), 3);
curves.offsets_for_write().copy_from(curve_offsets());
curves.positions_for_write().copy_from(vertex_positions());
MutableSpan<MDeformVert> dverts = curves.deform_verts_for_write();
for (const int i : dverts.index_range()) {
const float weight0 = vertex_weights_bone1()[i];
const float weight1 = vertex_weights_bone2()[i];
if (weight0 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 0, weight0);
}
if (weight1 > 0.0f) {
BKE_defvert_add_index_notest(&dverts[i], 1, weight1);
}
}
curves.tag_topology_changed();
curves.tag_positions_changed();
BKE_object_defgroup_new(ob, "Bone1");
BKE_object_defgroup_new(ob, "Bone2");
return ob;
}
/* Select the expected positions result based on data type and parameters. */
static Span<float3> expected_positions(const TargetDataType target_type,
const WeightingTest weighting,
const MaskingTest masking)
{
/* Both bones weighted equally. */
static Array<float3> data_envelope = {float3(1.5f, -2, -0.5f),
float3(3.5f, -2, -0.5f),
float3(1.5f, 0, -0.5f),
float3(3.5f, 0, -0.5f),
float3(1.5f, -2, 1.5f),
float3(3.5f, -2, 1.5f),
float3(1.5f, 0, 1.5f),
float3(3.5f, 0, 1.5f)};
static Array<float3> data_envelope_masked = {float3(-1, -1, -1),
float3(1, -1, -1),
float3(-1, 1, -1),
float3(1, 1, -1),
float3(1.5f, -2, 1.5f),
float3(3.5f, -2, 1.5f),
float3(1.5f, 0, 1.5f),
float3(3.5f, 0, 1.5f)};
/* Bottom verts deformed only by Bone1, top group deformed equally by both bones. */
static Array<float3> data_vgroups = {float3(4, -1, 0),
float3(6, -1, 0),
float3(4, 1, 0),
float3(6, 1, 0),
float3(1.5f, -2, 1.5f),
float3(3.5f, -2, 1.5f),
float3(1.5f, 0, 1.5f),
float3(3.5f, 0, 1.5f)};
static Array<float3> data_vgroups_masked = {float3(-1, -1, -1),
float3(1, -1, -1),
float3(-1, 1, -1),
float3(1, 1, -1),
float3(1.5f, -2, 1.5f),
float3(3.5f, -2, 1.5f),
float3(1.5f, 0, 1.5f),
float3(3.5f, 0, 1.5f)};
/* Curves data type does not support vertext groups. */
const bool vgroups_supported = ELEM(
target_type, TargetDataType::Mesh, TargetDataType::EditMesh, TargetDataType::GreasePencil);
/* Might be a bug: Mesh and GreasePencil allows masking even in envelope mode, but EditMesh
* does not! Curves does not have vgroups and therefore masking in the first place. */
const bool allow_masking_with_envelope = ELEM(
target_type, TargetDataType::Mesh, TargetDataType::GreasePencil);
const bool vertex_groups_enabled = ELEM(
weighting, WeightingTest::VertexGroups, WeightingTest::EnvelopeAndVertexGroups);
const bool masked = (masking == MaskingTest::VertexGroup) &&
((vgroups_supported && vertex_groups_enabled) ||
allow_masking_with_envelope);
switch (weighting) {
case WeightingTest::None:
return vertex_positions();
case WeightingTest::Envelope:
return masked ? data_envelope_masked : data_envelope;
case WeightingTest::VertexGroups:
if (vgroups_supported) {
return masked ? data_vgroups_masked : data_vgroups;
}
else {
return vertex_positions();
}
case WeightingTest::EnvelopeAndVertexGroups:
if (vgroups_supported) {
return masked ? data_vgroups_masked : data_vgroups;
}
else {
return masked ? data_envelope_masked : data_envelope;
}
}
BLI_assert_unreachable();
return {};
}
static Span<float3x3> expected_deform_mats(const WeightingTest weighting)
{
static Array<float3x3> data_zero = identity_deform_mats();
static Array<float3x3> data = {float3x3::identity(),
float3x3::identity(),
float3x3::identity(),
float3x3::identity(),
float3x3::identity(),
float3x3::identity(),
float3x3::identity(),
float3x3::identity()};
switch (weighting) {
case WeightingTest::None:
return data_zero;
case WeightingTest::Envelope:
case WeightingTest::VertexGroups:
case WeightingTest::EnvelopeAndVertexGroups:
return data;
}
return {};
}
static int get_deform_flag(const InterpolationTest interpolation, const WeightingTest weighting)
{
int deform_flag = 0;
switch (interpolation) {
case InterpolationTest::Linear:
/* Nothing to change, default mode. */
break;
case InterpolationTest::DualQuaternion:
deform_flag |= ARM_DEF_QUATERNION;
break;
}
switch (weighting) {
case WeightingTest::None:
/* Nothing to do. */
break;
case WeightingTest::Envelope:
deform_flag |= ARM_DEF_ENVELOPE;
break;
case WeightingTest::VertexGroups:
deform_flag |= ARM_DEF_VGROUP;
break;
case WeightingTest::EnvelopeAndVertexGroups:
deform_flag |= ARM_DEF_ENVELOPE | ARM_DEF_VGROUP;
break;
}
return deform_flag;
}
static const char *get_defgrp_name(const MaskingTest masking)
{
switch (masking) {
case MaskingTest::All:
return "";
case MaskingTest::VertexGroup:
return "Bone2";
}
BLI_assert_unreachable();
return "";
}
void mesh_test(const InterpolationTest interpolation,
const OutputValueTest output,
const WeightingTest weighting,
const MaskingTest masking,
const VertexWeightSource dvert_source)
{
Object *ob_arm = this->create_test_armature_object();
Object *ob_target = this->create_test_mesh_object();
Mesh *mesh = static_cast<Mesh *>(ob_target->data);
/* Mesh deform function supports a separate Mesh data block for deform_groups and dverts. */
Mesh *mesh_target = (dvert_source == VertexWeightSource::SeparateMesh) ? create_test_mesh() :
nullptr;
MutableSpan<float3> vert_positions = mesh->vert_positions_for_write();
float(*vert_positions_ptr)[3] = vert_positions.cast<float[3]>().data();
Array<float3x3> deform_mats;
float(*deform_mats_ptr)[3][3] = nullptr;
if (output == OutputValueTest::PositionAndDeformMatrix) {
deform_mats = identity_deform_mats();
deform_mats_ptr = reinterpret_cast<float(*)[3][3]>(deform_mats.data());
}
const int deform_flag = get_deform_flag(interpolation, weighting);
const char *defgrp_name = get_defgrp_name(masking);
BKE_armature_deform_coords_with_mesh(ob_arm,
ob_target,
vert_positions_ptr,
deform_mats_ptr,
vert_positions.size(),
deform_flag,
nullptr,
defgrp_name,
mesh_target);
EXPECT_EQ_SPAN(expected_positions(TargetDataType::Mesh, weighting, masking),
vert_positions.as_span());
if (output == OutputValueTest::PositionAndDeformMatrix) {
EXPECT_EQ_SPAN(expected_deform_mats(weighting), deform_mats.as_span());
}
if (mesh_target) {
/* Not in bmain. */
BKE_id_free(nullptr, mesh_target);
}
BKE_id_delete(bmain, ob_arm);
BKE_id_delete(bmain, ob_target);
}
void edit_mesh_test(const InterpolationTest interpolation,
const OutputValueTest output,
const WeightingTest weighting,
const MaskingTest masking)
{
Object *ob_arm = this->create_test_armature_object();
Object *ob_target = this->create_test_mesh_object();
Mesh *mesh = static_cast<Mesh *>(ob_target->data);
BMeshCreateParams create_params{};
create_params.use_toolflags = true;
BMesh *bm = BKE_mesh_to_bmesh(mesh, 0, false, &create_params);
BMEditMesh *edit_mesh = BKE_editmesh_create(bm);
Array<float3> bm_verts_wrapper = BM_mesh_vert_coords_alloc(edit_mesh->bm);
float(*vert_positions_ptr)[3] = bm_verts_wrapper.as_mutable_span().cast<float[3]>().data();
Array<float3x3> deform_mats;
float(*deform_mats_ptr)[3][3] = nullptr;
if (output == OutputValueTest::PositionAndDeformMatrix) {
deform_mats = identity_deform_mats();
deform_mats_ptr = reinterpret_cast<float(*)[3][3]>(deform_mats.data());
}
const int deform_flag = get_deform_flag(interpolation, weighting);
const char *defgrp_name = get_defgrp_name(masking);
BKE_armature_deform_coords_with_editmesh(ob_arm,
ob_target,
vert_positions_ptr,
deform_mats_ptr,
bm_verts_wrapper.size(),
deform_flag,
nullptr,
defgrp_name,
edit_mesh);
EXPECT_EQ_SPAN(expected_positions(TargetDataType::EditMesh, weighting, masking),
bm_verts_wrapper.as_span());
if (output == OutputValueTest::PositionAndDeformMatrix) {
EXPECT_EQ_SPAN(expected_deform_mats(weighting), deform_mats.as_span());
}
BKE_editmesh_free_data(edit_mesh);
MEM_delete(edit_mesh);
BKE_id_delete(bmain, ob_arm);
BKE_id_delete(bmain, ob_target);
}
void curves_test(const InterpolationTest interpolation,
const OutputValueTest output,
const WeightingTest weighting,
const MaskingTest masking)
{
Object *ob_arm = this->create_test_armature_object();
Object *ob_target = this->create_test_curves_object();
Curves *curves_id = static_cast<Curves *>(ob_target->data);
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
Array<float3x3> deform_mats;
std::optional<MutableSpan<float3x3>> deform_mats_opt;
if (output == OutputValueTest::PositionAndDeformMatrix) {
deform_mats = identity_deform_mats();
deform_mats_opt = deform_mats;
}
const int deform_flag = get_deform_flag(interpolation, weighting);
const char *defgrp_name = get_defgrp_name(masking);
BKE_armature_deform_coords_with_curves(*ob_arm,
*ob_target,
nullptr,
curves.positions_for_write(),
std::nullopt,
deform_mats_opt,
curves.deform_verts(),
deform_flag,
defgrp_name);
/* Note: Curves objects don't support vertex groups. */
EXPECT_EQ_SPAN(expected_positions(TargetDataType::Curves, weighting, masking),
curves.positions());
if (output == OutputValueTest::PositionAndDeformMatrix) {
EXPECT_EQ_SPAN(expected_deform_mats(weighting), deform_mats.as_span());
}
BKE_id_delete(bmain, ob_arm);
BKE_id_delete(bmain, ob_target);
}
void grease_pencil_test(const InterpolationTest interpolation,
const OutputValueTest output,
const WeightingTest weighting,
const MaskingTest masking)
{
Object *ob_arm = this->create_test_armature_object();
Object *ob_target = this->create_test_grease_pencil_object();
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob_target->data);
BLI_assert(!grease_pencil->drawings().is_empty());
GreasePencilDrawingBase *drawing_base = grease_pencil->drawings()[0];
BLI_assert(drawing_base->type == GP_DRAWING);
greasepencil::Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
bke::CurvesGeometry &curves = drawing.geometry.wrap();
Array<float3x3> deform_mats;
std::optional<MutableSpan<float3x3>> deform_mats_opt;
if (output == OutputValueTest::PositionAndDeformMatrix) {
deform_mats = identity_deform_mats();
deform_mats_opt = deform_mats;
}
const int deform_flag = get_deform_flag(interpolation, weighting);
const char *defgrp_name = get_defgrp_name(masking);
BKE_armature_deform_coords_with_curves(*ob_arm,
*ob_target,
&grease_pencil->vertex_group_names,
curves.positions_for_write(),
std::nullopt,
deform_mats_opt,
curves.deform_verts(),
deform_flag,
defgrp_name);
EXPECT_EQ_SPAN(expected_positions(TargetDataType::GreasePencil, weighting, masking),
curves.positions());
if (output == OutputValueTest::PositionAndDeformMatrix) {
EXPECT_EQ_SPAN(expected_deform_mats(weighting), deform_mats.as_span());
}
BKE_id_delete(bmain, ob_arm);
BKE_id_delete(bmain, ob_target);
}
};
#ifdef USE_PARAMETERIZED_TESTS
using ArmatureDeformTestParams =
std::tuple<InterpolationTest, OutputValueTest, WeightingTest, MaskingTest, VertexWeightSource>;
class ArmatureDeformParamTest : public ArmatureDeformTestBase,
public testing::TestWithParam<ArmatureDeformTestParams> {
public:
static void SetUpTestSuite()
{
CLG_init();
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_P(ArmatureDeformParamTest, MeshDeformParameterized)
{
const ArmatureDeformTestParams &params = this->GetParam();
InterpolationTest interpolation = std::get<0>(params);
OutputValueTest output = std::get<1>(params);
WeightingTest weighting = std::get<2>(params);
MaskingTest masking = std::get<3>(params);
VertexWeightSource dvert_source = std::get<4>(params);
mesh_test(interpolation, output, weighting, masking, dvert_source);
}
TEST_P(ArmatureDeformParamTest, EditMeshDeformParameterized)
{
const ArmatureDeformTestParams &params = this->GetParam();
InterpolationTest interpolation = std::get<0>(params);
OutputValueTest output = std::get<1>(params);
WeightingTest weighting = std::get<2>(params);
MaskingTest masking = std::get<3>(params);
edit_mesh_test(interpolation, output, weighting, masking);
}
TEST_P(ArmatureDeformParamTest, CurvesDeformParameterized)
{
const ArmatureDeformTestParams &params = this->GetParam();
InterpolationTest interpolation = std::get<0>(params);
OutputValueTest output = std::get<1>(params);
WeightingTest weighting = std::get<2>(params);
MaskingTest masking = std::get<3>(params);
curves_test(interpolation, output, weighting, masking);
}
TEST_P(ArmatureDeformParamTest, GreasePencilDeformParameterized)
{
const ArmatureDeformTestParams &params = this->GetParam();
InterpolationTest interpolation = std::get<0>(params);
OutputValueTest output = std::get<1>(params);
WeightingTest weighting = std::get<2>(params);
MaskingTest masking = std::get<3>(params);
grease_pencil_test(interpolation, output, weighting, masking);
}
INSTANTIATE_TEST_SUITE_P(
AllArmatureDeformTests,
ArmatureDeformParamTest,
testing::Combine(
testing::Values(InterpolationTest::Linear, InterpolationTest::DualQuaternion),
testing::Values(OutputValueTest::Position, OutputValueTest::PositionAndDeformMatrix),
testing::Values(WeightingTest::None,
WeightingTest::Envelope,
WeightingTest::VertexGroups,
WeightingTest::EnvelopeAndVertexGroups),
testing::Values(MaskingTest::All, MaskingTest::VertexGroup),
testing::Values(VertexWeightSource::TargetObject, VertexWeightSource::SeparateMesh)),
[](const testing::TestParamInfo<ArmatureDeformTestParams> &info) {
InterpolationTest interpolation = std::get<0>(info.param);
OutputValueTest output = std::get<1>(info.param);
WeightingTest weighting = std::get<2>(info.param);
MaskingTest masking = std::get<3>(info.param);
VertexWeightSource dvert_source = std::get<4>(info.param);
std::string name;
switch (interpolation) {
case InterpolationTest::Linear:
name += "linear";
break;
case InterpolationTest::DualQuaternion:
name += "dualquat";
break;
}
switch (output) {
case OutputValueTest::Position:
name += "_pos";
break;
case OutputValueTest::PositionAndDeformMatrix:
name += "_posdefmat";
break;
}
switch (weighting) {
case WeightingTest::None:
name += "_disabled";
break;
case WeightingTest::Envelope:
name += "_envelope";
break;
case WeightingTest::VertexGroups:
name += "_vgroups";
break;
case WeightingTest::EnvelopeAndVertexGroups:
name += "_envelopevgroups";
break;
}
switch (masking) {
case MaskingTest::All:
break;
case MaskingTest::VertexGroup:
name += "_masked";
break;
}
switch (dvert_source) {
case VertexWeightSource::TargetObject:
break;
case VertexWeightSource::SeparateMesh:
name += "_dvertsource";
break;
}
return name;
});
#else // USE_PARAMETERIZED_TESTS
class ArmatureDeformTest : public ArmatureDeformTestBase, public testing::Test {
public:
static void SetUpTestSuite()
{
CLG_init();
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(ArmatureDeformTest, MeshDeform)
{
for (InterpolationTest ipol : {InterpolationTest::Linear, InterpolationTest::DualQuaternion}) {
for (WeightingTest weight : {WeightingTest::None,
WeightingTest::Envelope,
WeightingTest::VertexGroups,
WeightingTest::EnvelopeAndVertexGroups})
{
for (OutputValueTest output :
{OutputValueTest::Position, OutputValueTest::PositionAndDeformMatrix})
{
for (MaskingTest mask : {MaskingTest::All, MaskingTest::VertexGroup}) {
for (VertexWeightSource dvert_source :
{VertexWeightSource::TargetObject, VertexWeightSource::SeparateMesh})
{
mesh_test(ipol, output, weight, mask, dvert_source);
}
}
}
}
}
}
TEST_F(ArmatureDeformTest, EditMeshDeform)
{
for (InterpolationTest ipol : {InterpolationTest::Linear, InterpolationTest::DualQuaternion}) {
for (OutputValueTest output :
{OutputValueTest::Position, OutputValueTest::PositionAndDeformMatrix})
{
for (WeightingTest weight : {WeightingTest::None,
WeightingTest::Envelope,
WeightingTest::VertexGroups,
WeightingTest::EnvelopeAndVertexGroups})
{
for (MaskingTest mask : {MaskingTest::All, MaskingTest::VertexGroup}) {
edit_mesh_test(ipol, output, weight, mask);
}
}
}
}
}
TEST_F(ArmatureDeformTest, CurvesDeform)
{
for (InterpolationTest ipol : {InterpolationTest::Linear, InterpolationTest::DualQuaternion}) {
for (OutputValueTest output :
{OutputValueTest::Position, OutputValueTest::PositionAndDeformMatrix})
{
for (WeightingTest weight : {WeightingTest::None,
WeightingTest::Envelope,
WeightingTest::VertexGroups,
WeightingTest::EnvelopeAndVertexGroups})
{
for (MaskingTest mask : {MaskingTest::All, MaskingTest::VertexGroup}) {
curves_test(ipol, output, weight, mask);
}
}
}
}
}
TEST_F(ArmatureDeformTest, GreasePencilDeform)
{
for (InterpolationTest ipol : {InterpolationTest::Linear, InterpolationTest::DualQuaternion}) {
for (OutputValueTest output :
{OutputValueTest::Position, OutputValueTest::PositionAndDeformMatrix})
{
for (WeightingTest weight : {WeightingTest::None,
WeightingTest::Envelope,
WeightingTest::VertexGroups,
WeightingTest::EnvelopeAndVertexGroups})
{
for (MaskingTest mask : {MaskingTest::All, MaskingTest::VertexGroup}) {
grease_pencil_test(ipol, output, weight, mask);
}
}
}
}
}
#endif
} // namespace blender::bke::tests

View File

@@ -944,6 +944,9 @@ static void pose_channel_flush_to_orig_if_needed(Depsgraph *depsgraph,
void BKE_pose_bone_done(Depsgraph *depsgraph, Object *object, int pchan_index)
{
/* Note: tests in `armature_deform_test.cc` update pose matrices locally to avoid creating a full
* depsgraph. Keep these in sync if this function is changed! */
const bArmature *armature = (bArmature *)object->data;
if (armature->edbo != nullptr) {
return;