/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "simulation2/system/ComponentTest.h" #include "maths/Matrix3D.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpWaterManager.h" class MockWater : public ICmpWaterManager { public: DEFAULT_MOCK_COMPONENT() entity_pos_t GetWaterLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const override { return entity_pos_t::FromInt(100); } float GetExactWaterLevel(float UNUSED(x), float UNUSED(z)) const override { return 100.f; } void RecomputeWaterData() override { } void SetWaterLevel(entity_pos_t UNUSED(h)) override { } }; class TestCmpPosition : public CxxTest::TestSuite { public: void setUp() { CXeromyces::Startup(); } void tearDown() { CXeromyces::Terminate(); } static CFixedVector3D fixedvec(int x, int y, int z) { return CFixedVector3D(fixed::FromInt(x), fixed::FromInt(y), fixed::FromInt(z)); } void test_basic() { ComponentTestHelper test(*g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); ICmpPosition* cmp = test.Add(CID_Position, "upright23false"); // Defaults TS_ASSERT(!cmp->IsInWorld()); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetRotation(), fixedvec(0, 0, 0)); // Change height offset cmp->SetHeightOffset(entity_pos_t::FromInt(10)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(10)); // Move out of world, while currently out of world cmp->MoveOutOfWorld(); TS_ASSERT(!cmp->IsInWorld()); // Jump into world cmp->JumpTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); TS_ASSERT(cmp->IsInWorld()); // Move out of world, while currently in world cmp->MoveOutOfWorld(); TS_ASSERT(!cmp->IsInWorld()); // Move into world cmp->MoveTo(entity_pos_t::FromInt(100), entity_pos_t::FromInt(200)); TS_ASSERT(cmp->IsInWorld()); // Position computed from move plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 60, 200)); // Interpolated position should be constant TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(100, 60, 200)); // No TurnStart message, so this move doesn't affect the interpolation cmp->MoveTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); // Move smoothly to new position cmp->MoveTo(entity_pos_t::FromInt(200), entity_pos_t::FromInt(0)); // Position computed from move plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(200, 60, 0)); // Interpolated position should vary, from original move into world to new move TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(150, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(200, 60, 0)); // Latch new position for interpolation CMessageTurnStart msg; test.HandleMessage(cmp, msg, false); // Move smoothly to new position cmp->MoveTo(entity_pos_t::FromInt(400), entity_pos_t::FromInt(300)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(200, 60, 0)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 150)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(400, 60, 300)); // Jump to new position cmp->JumpTo(entity_pos_t::FromInt(300), entity_pos_t::FromInt(100)); // Position computed from jump plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(300, 60, 100)); // Interpolated position should be constant after jump TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(300, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(300, 60, 100)); // TODO: Test the rotation methods } void test_water() { ComponentTestHelper test(*g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); MockWater water; test.AddMock(SYSTEM_ENTITY, IID_WaterManager, water); ICmpPosition* cmp = test.Add(CID_Position, "upright23true1"); // Move into the world, the fixed height uses the water level minus the float depth as a base cmp->JumpTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); TS_ASSERT(cmp->IsInWorld()); TS_ASSERT(cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); // Change height offset, the fixed height changes too cmp->SetHeightOffset(entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(110)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(0, 110, 0)); // Move cmp->MoveTo(entity_pos_t::FromInt(100), entity_pos_t::FromInt(200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(0, 122, 0)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(50, 116, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(100, 110, 200)); // Change fixed height, the height offset changes too cmp->SetHeightFixed(entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // The entity can't float anymore, the fixed height is computed from the terrain base cmp->SetFloating(false); TS_ASSERT(!cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(73)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 73, 200)); // The entity can float again cmp->SetFloating(true); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // Use non relative height, entity will not follow terrain/water height. TS_ASSERT(cmp->IsHeightRelative()); cmp->SetHeightRelative(false); TS_ASSERT(!cmp->IsHeightRelative()); cmp->SetHeightOffset(entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(110)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 110, 200)); cmp->SetHeightFixed(entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // The entity can't float anymore and height is not relative, fixed height doesn't change cmp->SetFloating(false); TS_ASSERT(!cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(72)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); } void test_serialize() { ComponentTestHelper test(*g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); ICmpPosition* cmp = test.Add(CID_Position, "upright5false"); test.Roundtrip(); cmp->SetHeightOffset(entity_pos_t::FromInt(20)); cmp->SetXZRotation(entity_angle_t::FromInt(1), entity_angle_t::FromInt(2)); cmp->SetYRotation(entity_angle_t::FromInt(3)); test.Roundtrip(); cmp->JumpTo(entity_pos_t::FromInt(10), entity_pos_t::FromInt(20)); cmp->MoveTo(entity_pos_t::FromInt(123), entity_pos_t::FromInt(456)); test.Roundtrip(); } };