/** * @file test_node.cpp * @brief Node 和 Transform 系统单元测试 * * 测试节点的变换矩阵计算、父子关系、组件系统等功能。 */ #include "test_framework.h" #include #include #include #include #include #include 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(); 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->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->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->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->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(); Mat3 local = node->local(); Mat3 identity(1.0f); TEST_ASSERT_TRUE(mat3Equal(local, identity)); } TEST(Node, LocalMatrixTranslation) { auto node = ptr::make(); 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->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->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->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(); auto child = ptr::make(); 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(); auto child = ptr::make(); 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(); auto child = ptr::make(); parent->addChild(child); child->removeFromParent(); TEST_ASSERT_NULL(child->parent()); TEST_ASSERT_EQ(0u, parent->children().size()); } TEST(Node, NestedHierarchy) { auto root = ptr::make(); auto child1 = ptr::make(); auto child2 = ptr::make(); auto grandchild = ptr::make(); 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(); parent->setPos(100, 100); auto child = ptr::make(); 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(); parent->setScale(2.0f, 2.0f); auto child = ptr::make(); 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(); parent->setRot(45.0f); auto child = ptr::make(); 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(); parent->setPos(100, 100); auto child = ptr::make(); 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(); TestComp* comp = node->add(); TEST_ASSERT_NOT_NULL(comp); TEST_ASSERT_EQ(node.get(), comp->owner()); } TEST(Node, GetComponent) { auto node = ptr::make(); node->add(); TestComp* comp = node->get(); TEST_ASSERT_NOT_NULL(comp); } TEST(Node, RemoveComponent) { auto node = ptr::make(); node->add(); node->remove(); TestComp* comp = node->get(); TEST_ASSERT_NULL(comp); } TEST(Node, UpdateComponents) { auto node = ptr::make(); TestComp* comp = node->add(); 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(); 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(); TEST_ASSERT_EQ(0, node->tag()); node->setTag(42); TEST_ASSERT_EQ(42, node->tag()); } TEST(Node, Name) { auto node = ptr::make(); 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))); }