413 lines
11 KiB
C++
413 lines
11 KiB
C++
|
|
/**
|
||
|
|
* @file test_node.cpp
|
||
|
|
* @brief Node 和 Transform 系统单元测试
|
||
|
|
*
|
||
|
|
* 测试节点的变换矩阵计算、父子关系、组件系统等功能。
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "test_framework.h"
|
||
|
|
#include <extra2d/node/node.h>
|
||
|
|
#include <extra2d/node/scene_graph.h>
|
||
|
|
#include <extra2d/core/math_extended.h>
|
||
|
|
#include <glm/glm.hpp>
|
||
|
|
#include <glm/gtc/matrix_transform.hpp>
|
||
|
|
#include <cmath>
|
||
|
|
|
||
|
|
using namespace extra2d;
|
||
|
|
using namespace extra2d::test;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
constexpr f32 EPSILON = 0.0001f;
|
||
|
|
|
||
|
|
bool mat3Equal(const Mat3& a, const Mat3& b, f32 eps = EPSILON) {
|
||
|
|
for (int i = 0; i < 3; ++i) {
|
||
|
|
for (int j = 0; j < 3; ++j) {
|
||
|
|
if (std::abs(a[i][j] - b[i][j]) > eps) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool vec2Equal(const Vec2& a, const Vec2& b, f32 eps = EPSILON) {
|
||
|
|
return std::abs(a.x - b.x) < eps && std::abs(a.y - b.y) < eps;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Node 基本测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(Node, Create) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
TEST_ASSERT_NOT_NULL(node.get());
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(0, 0)));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1, 1)));
|
||
|
|
TEST_ASSERT_TRUE(std::abs(node->rot) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0.5f, 0.5f)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, SetPosition) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
node->setPos(100, 200);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(100, 200)));
|
||
|
|
|
||
|
|
node->setPos(Vec2(50, 75));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->pos, Vec2(50, 75)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, SetScale) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
node->setScale(2.0f, 3.0f);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 3)));
|
||
|
|
|
||
|
|
node->setScale(Vec2(1.5f, 2.5f));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(1.5f, 2.5f)));
|
||
|
|
|
||
|
|
node->setScale(2.0f);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->scale, Vec2(2, 2)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, SetRotation) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
node->setRot(45.0f);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(node->rot - 45.0f) < EPSILON);
|
||
|
|
|
||
|
|
node->setRot(-30.0f);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(node->rot - (-30.0f)) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, SetAnchor) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
node->setAnchor(0.0f, 0.0f);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0, 0)));
|
||
|
|
|
||
|
|
node->setAnchor(Anchor::TopRight);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(1, 1)));
|
||
|
|
|
||
|
|
node->setAnchor(Anchor::Center);
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(node->anchor, Vec2(0.5f, 0.5f)));
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Transform 矩阵测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(Node, LocalMatrixIdentity) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
Mat3 local = node->local();
|
||
|
|
Mat3 identity(1.0f);
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(mat3Equal(local, identity));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, LocalMatrixTranslation) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->setPos(100, 50);
|
||
|
|
|
||
|
|
Mat3 local = node->local();
|
||
|
|
|
||
|
|
glm::vec3 origin(0, 0, 1.0f);
|
||
|
|
glm::vec3 transformed = local * origin;
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.x - 100) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.y - 50) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, LocalMatrixScale) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->setScale(2.0f, 3.0f);
|
||
|
|
|
||
|
|
Mat3 local = node->local();
|
||
|
|
|
||
|
|
glm::vec3 point(10, 10, 1.0f);
|
||
|
|
glm::vec3 transformed = local * point;
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.x - 20) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.y - 30) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, LocalMatrixRotation) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->setRot(90.0f);
|
||
|
|
|
||
|
|
Mat3 local = node->local();
|
||
|
|
|
||
|
|
glm::vec3 point(1, 0, 1.0f);
|
||
|
|
glm::vec3 transformed = local * point;
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.x - 0.0f) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.y - 1.0f) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, LocalMatrixCombined) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->setPos(100, 100);
|
||
|
|
node->setScale(2.0f, 2.0f);
|
||
|
|
node->setRot(0.0f);
|
||
|
|
|
||
|
|
Mat3 local = node->local();
|
||
|
|
|
||
|
|
glm::vec3 point(50, 50, 1.0f);
|
||
|
|
glm::vec3 transformed = local * point;
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.x - 200) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.y - 200) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// 父子关系测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(Node, AddChild) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
|
||
|
|
TEST_ASSERT_EQ(parent.get(), child->parent());
|
||
|
|
TEST_ASSERT_EQ(1u, parent->children().size());
|
||
|
|
TEST_ASSERT_EQ(child.get(), parent->children()[0].get());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, RemoveChild) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
parent->removeChild(child);
|
||
|
|
|
||
|
|
TEST_ASSERT_NULL(child->parent());
|
||
|
|
TEST_ASSERT_EQ(0u, parent->children().size());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, RemoveFromParent) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
child->removeFromParent();
|
||
|
|
|
||
|
|
TEST_ASSERT_NULL(child->parent());
|
||
|
|
TEST_ASSERT_EQ(0u, parent->children().size());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, NestedHierarchy) {
|
||
|
|
auto root = ptr::make<Node>();
|
||
|
|
auto child1 = ptr::make<Node>();
|
||
|
|
auto child2 = ptr::make<Node>();
|
||
|
|
auto grandchild = ptr::make<Node>();
|
||
|
|
|
||
|
|
root->addChild(child1);
|
||
|
|
root->addChild(child2);
|
||
|
|
child1->addChild(grandchild);
|
||
|
|
|
||
|
|
TEST_ASSERT_EQ(root.get(), child1->parent());
|
||
|
|
TEST_ASSERT_EQ(root.get(), child2->parent());
|
||
|
|
TEST_ASSERT_EQ(child1.get(), grandchild->parent());
|
||
|
|
TEST_ASSERT_EQ(2u, root->children().size());
|
||
|
|
TEST_ASSERT_EQ(1u, child1->children().size());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// 世界变换测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(Node, WorldPosition) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
parent->setPos(100, 100);
|
||
|
|
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
child->setPos(50, 50);
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
|
||
|
|
Vec2 worldPos = child->worldPos();
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(worldPos, Vec2(150, 150)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, WorldScale) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
parent->setScale(2.0f, 2.0f);
|
||
|
|
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
child->setScale(1.5f, 1.5f);
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
|
||
|
|
Vec2 worldScale = child->worldScale();
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(worldScale, Vec2(3.0f, 3.0f)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, WorldRotation) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
parent->setRot(45.0f);
|
||
|
|
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
child->setRot(30.0f);
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
|
||
|
|
f32 worldRot = child->worldRot();
|
||
|
|
TEST_ASSERT_TRUE(std::abs(worldRot - 75.0f) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, WorldMatrix4x4) {
|
||
|
|
auto parent = ptr::make<Node>();
|
||
|
|
parent->setPos(100, 100);
|
||
|
|
|
||
|
|
auto child = ptr::make<Node>();
|
||
|
|
child->setPos(50, 50);
|
||
|
|
|
||
|
|
parent->addChild(child);
|
||
|
|
|
||
|
|
Mat4 world4x4 = child->world4x4();
|
||
|
|
|
||
|
|
Vec4 origin(0, 0, 0, 1);
|
||
|
|
Vec4 transformed = world4x4 * origin;
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.x - 150.0f) < EPSILON);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(transformed.y - 150.0f) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// 组件系统测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
class TestComp : public Comp {
|
||
|
|
public:
|
||
|
|
const char* type() const override { return "TestComp"; }
|
||
|
|
|
||
|
|
void update(f32 dt) override {
|
||
|
|
updateCount++;
|
||
|
|
lastDt = dt;
|
||
|
|
}
|
||
|
|
|
||
|
|
int updateCount = 0;
|
||
|
|
f32 lastDt = 0.0f;
|
||
|
|
};
|
||
|
|
|
||
|
|
TEST(Node, AddComponent) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
TestComp* comp = node->add<TestComp>();
|
||
|
|
|
||
|
|
TEST_ASSERT_NOT_NULL(comp);
|
||
|
|
TEST_ASSERT_EQ(node.get(), comp->owner());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, GetComponent) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->add<TestComp>();
|
||
|
|
|
||
|
|
TestComp* comp = node->get<TestComp>();
|
||
|
|
|
||
|
|
TEST_ASSERT_NOT_NULL(comp);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, RemoveComponent) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
node->add<TestComp>();
|
||
|
|
|
||
|
|
node->remove<TestComp>();
|
||
|
|
|
||
|
|
TestComp* comp = node->get<TestComp>();
|
||
|
|
TEST_ASSERT_NULL(comp);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, UpdateComponents) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
TestComp* comp = node->add<TestComp>();
|
||
|
|
|
||
|
|
node->updateComps(0.016f);
|
||
|
|
|
||
|
|
TEST_ASSERT_EQ(1, comp->updateCount);
|
||
|
|
TEST_ASSERT_TRUE(std::abs(comp->lastDt - 0.016f) < EPSILON);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// 可见性和标签测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(Node, Visibility) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(node->visible());
|
||
|
|
|
||
|
|
node->setVisible(false);
|
||
|
|
TEST_ASSERT_FALSE(node->visible());
|
||
|
|
|
||
|
|
node->setVisible(true);
|
||
|
|
TEST_ASSERT_TRUE(node->visible());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, Tag) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
TEST_ASSERT_EQ(0, node->tag());
|
||
|
|
|
||
|
|
node->setTag(42);
|
||
|
|
TEST_ASSERT_EQ(42, node->tag());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(Node, Name) {
|
||
|
|
auto node = ptr::make<Node>();
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(node->name().empty());
|
||
|
|
|
||
|
|
node->setName("TestNode");
|
||
|
|
TEST_ASSERT_EQ("TestNode", node->name());
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// 边界框测试
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
TEST(BoundingBox, Create) {
|
||
|
|
BoundingBox box(Vec2(0, 0), Vec2(100, 100));
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(box.min, Vec2(0, 0)));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(box.max, Vec2(100, 100)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(BoundingBox, FromCenter) {
|
||
|
|
BoundingBox box = BoundingBox::fromCenter(Vec2(50, 50), Vec2(25, 25));
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(box.min, Vec2(25, 25)));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(box.max, Vec2(75, 75)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(BoundingBox, Contains) {
|
||
|
|
BoundingBox box(Vec2(0, 0), Vec2(100, 100));
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(box.contains(Vec2(50, 50)));
|
||
|
|
TEST_ASSERT_TRUE(box.contains(Vec2(0, 0)));
|
||
|
|
TEST_ASSERT_TRUE(box.contains(Vec2(100, 100)));
|
||
|
|
TEST_ASSERT_FALSE(box.contains(Vec2(150, 50)));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(BoundingBox, Intersects) {
|
||
|
|
BoundingBox box1(Vec2(0, 0), Vec2(100, 100));
|
||
|
|
BoundingBox box2(Vec2(50, 50), Vec2(150, 150));
|
||
|
|
BoundingBox box3(Vec2(200, 200), Vec2(300, 300));
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(box1.intersects(box2));
|
||
|
|
TEST_ASSERT_FALSE(box1.intersects(box3));
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST(BoundingBox, Merged) {
|
||
|
|
BoundingBox box1(Vec2(0, 0), Vec2(100, 100));
|
||
|
|
BoundingBox box2(Vec2(50, 50), Vec2(150, 150));
|
||
|
|
|
||
|
|
BoundingBox merged = box1.merged(box2);
|
||
|
|
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(merged.min, Vec2(0, 0)));
|
||
|
|
TEST_ASSERT_TRUE(vec2Equal(merged.max, Vec2(150, 150)));
|
||
|
|
}
|