Index: ps/trunk/source/graphics/tests/test_Terrain.h =================================================================== --- ps/trunk/source/graphics/tests/test_Terrain.h (revision 23456) +++ ps/trunk/source/graphics/tests/test_Terrain.h (revision 23457) @@ -1,195 +1,341 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 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 "lib/self_test.h" #include "graphics/Terrain.h" #include "graphics/Patch.h" #include "graphics/RenderableObject.h" -#include "maths/FixedVector3D.h" #include "maths/Fixed.h" +#include "maths/FixedVector3D.h" + +#include +#include class TestTerrain : public CxxTest::TestSuite { void SetVertex(CTerrain& terrain, ssize_t i, ssize_t j, u16 height) { terrain.GetHeightMap()[j*terrain.GetVerticesPerSide() + i] = height; terrain.MakeDirty(RENDERDATA_UPDATE_VERTICES); } u16 GetVertex(CTerrain& terrain, ssize_t i, ssize_t j) { return terrain.GetHeightMap()[j*terrain.GetVerticesPerSide() + i]; } void Set45Slope(CTerrain& terrain) { SetVertex(terrain, 0, 0, 100); SetVertex(terrain, 0, 1, 100); SetVertex(terrain, 0, 2, 100); SetVertex(terrain, 1, 0, 100 + TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 1, 1, 100 + TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 1, 2, 100 + TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 2, 0, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 2, 1, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 2, 2, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 3, 0, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 3, 1, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 3, 2, 100 + 2*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); } void SetHighPlateau(CTerrain& terrain, int height) { SetVertex(terrain, 4, 0, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 4, 1, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 4, 2, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 5, 0, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 5, 1, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); SetVertex(terrain, 5, 2, 100 + height*TERRAIN_TILE_SIZE*HEIGHT_UNITS_PER_METRE); } public: void test_GetExactGroundLevel() { CTerrain terrain; terrain.Initialize(4, NULL); Set45Slope(terrain); SetHighPlateau(terrain, 20); float ground; ground = terrain.GetExactGroundLevel(0.f, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE, 0.01f); ground = terrain.GetExactGroundLevel(0.5f*TERRAIN_TILE_SIZE, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE+0.5f*TERRAIN_TILE_SIZE, 0.01f); ground = terrain.GetExactGroundLevel(1.5f*TERRAIN_TILE_SIZE, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE+1.5f*TERRAIN_TILE_SIZE, 0.01f); ground = terrain.GetExactGroundLevel(2.5f*TERRAIN_TILE_SIZE, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE+2.f*TERRAIN_TILE_SIZE, 0.01f); ground = terrain.GetExactGroundLevel(3.5f*TERRAIN_TILE_SIZE, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE+11.f*TERRAIN_TILE_SIZE, 0.01f); ground = terrain.GetExactGroundLevel(4.5f*TERRAIN_TILE_SIZE, 1.5f*TERRAIN_TILE_SIZE); TS_ASSERT_DELTA(ground, 100.f/HEIGHT_UNITS_PER_METRE+20.f*TERRAIN_TILE_SIZE, 0.01f); } void test_GetExactGroundLevelFixed() { CTerrain terrain; terrain.Initialize(4, NULL); Set45Slope(terrain); SetHighPlateau(terrain, 20); const double maxDelta = 0.0001; fixed ground; ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(0.f), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE, maxDelta); ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(0.5f*TERRAIN_TILE_SIZE), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE+0.5*TERRAIN_TILE_SIZE, maxDelta); ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE+1.5*TERRAIN_TILE_SIZE, maxDelta); ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(2.5f*TERRAIN_TILE_SIZE), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE+2.0*TERRAIN_TILE_SIZE, maxDelta); ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(3.5f*TERRAIN_TILE_SIZE), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE+11.0*TERRAIN_TILE_SIZE, maxDelta); ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(4.5f*TERRAIN_TILE_SIZE), fixed::FromFloat(1.5f*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 100.0/HEIGHT_UNITS_PER_METRE+20.0*TERRAIN_TILE_SIZE, maxDelta); } void test_GetExactGroundLevelFixed_max() { CTerrain terrain; terrain.Initialize(4, NULL); SetVertex(terrain, 0, 0, 65535); SetVertex(terrain, 0, 1, 65535); SetVertex(terrain, 1, 0, 65535); SetVertex(terrain, 1, 1, 65535); const double maxDelta = 0.024; int p = 255; for (int zi = 0; zi < p; ++zi) { for (int xi = 0; xi < p; ++xi) { fixed ground = terrain.GetExactGroundLevelFixed(fixed::FromFloat(xi/(float)p*TERRAIN_TILE_SIZE), fixed::FromFloat(zi/(float)p*TERRAIN_TILE_SIZE)); TS_ASSERT_DELTA(ground.ToDouble(), 65535.0/HEIGHT_UNITS_PER_METRE, maxDelta); } } } void test_CalcNormal() { CTerrain terrain; terrain.Initialize(4, NULL); Set45Slope(terrain); CVector3D vec; terrain.CalcNormal(1, 1, vec); TS_ASSERT_DELTA(vec.X, -1.f/sqrt(2.f), 0.01f); TS_ASSERT_DELTA(vec.Y, 1.f/sqrt(2.f), 0.01f); TS_ASSERT_EQUALS(vec.Z, 0.f); terrain.CalcNormal(2, 1, vec); TS_ASSERT_DELTA(vec.X, (-1.f/sqrt(2.f)) / sqrt(2.f+sqrt(2.f)), 0.01f); TS_ASSERT_DELTA(vec.Y, (1.f+1.f/sqrt(2.f)) / sqrt(2.f+sqrt(2.f)), 0.01f); TS_ASSERT_EQUALS(vec.Z, 0); terrain.CalcNormal(5, 1, vec); TS_ASSERT_EQUALS(vec.X, 0.f); TS_ASSERT_EQUALS(vec.Y, 1.f); TS_ASSERT_EQUALS(vec.Z, 0.f); } void test_CalcNormalFixed() { CTerrain terrain; terrain.Initialize(4, NULL); Set45Slope(terrain); CFixedVector3D vec; terrain.CalcNormalFixed(1, 1, vec); TS_ASSERT_DELTA(vec.X.ToFloat(), -1.f/sqrt(2.f), 0.01f); TS_ASSERT_DELTA(vec.Y.ToFloat(), 1.f/sqrt(2.f), 0.01f); TS_ASSERT_EQUALS(vec.Z.ToFloat(), 0.f); terrain.CalcNormalFixed(2, 1, vec); TS_ASSERT_DELTA(vec.X.ToFloat(), (-1.f/sqrt(2.f)) / sqrt(2.f+sqrt(2.f)), 0.01f); TS_ASSERT_DELTA(vec.Y.ToFloat(), (1.f+1.f/sqrt(2.f)) / sqrt(2.f+sqrt(2.f)), 0.01f); TS_ASSERT_EQUALS(vec.Z.ToFloat(), 0); terrain.CalcNormalFixed(5, 1, vec); TS_ASSERT_EQUALS(vec.X.ToFloat(), 0.f); TS_ASSERT_EQUALS(vec.Y.ToFloat(), 1.f); TS_ASSERT_EQUALS(vec.Z.ToFloat(), 0.f); } + + void test_Resize() + { + // We do resize by size in patches, so it doesn't make sense to + // fill each vertex with a different value. Instead we use a single + // value per a patch. + struct ResizeTestCase + { + std::vector> sourcePatches; + std::vector> expectedPatches; + }; + const ResizeTestCase testCases[] = { + { + { + {42} + }, + { + {42} + } + }, + { + { + {1, 2}, + {3, 4} + }, + { + {1}, + } + }, + { + { + {1, 2}, + {3, 4} + }, + { + {1, 2, 0}, + {3, 4, 0}, + {0, 0, 0} + } + } + }; + + for (const ResizeTestCase& testCase : testCases) + { + const ssize_t sourceSize = testCase.sourcePatches.size(); + const ssize_t expectedSize = testCase.expectedPatches.size(); + + TS_ASSERT_LESS_THAN(0, sourceSize); + TS_ASSERT_LESS_THAN(0, expectedSize); + + const ssize_t sourceMapSize = sourceSize * PATCH_SIZE + 1; + const ssize_t expectedMapSize = expectedSize * PATCH_SIZE + 1; + + CTerrain terrain; + { + std::vector heightmap(sourceMapSize * sourceMapSize); + for (ssize_t iTile = 0; iTile < sourceSize; ++iTile) + { + TS_ASSERT_EQUALS(sourceSize, testCase.sourcePatches[iTile].size()); + for (ssize_t jTile = 0; jTile < sourceSize; ++jTile) + { + for (ssize_t i = 0; i < PATCH_SIZE; ++i) + for (ssize_t j = 0; j < PATCH_SIZE; ++j) + { + const ssize_t idx = + (jTile * PATCH_SIZE + j) * sourceMapSize + + iTile * PATCH_SIZE + i; + heightmap[idx] = testCase.sourcePatches[iTile][jTile]; + } + } + } + terrain.Initialize(sourceSize, heightmap.data()); + } + + terrain.Resize(expectedSize); + + TS_ASSERT_EQUALS(expectedMapSize, terrain.GetVerticesPerSide()); + TS_ASSERT_EQUALS(expectedSize, terrain.GetPatchesPerSide()); + + for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) + { + TS_ASSERT_EQUALS( + expectedSize, testCase.expectedPatches[iTile].size()); + for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) + { + for (ssize_t i = 0; i < PATCH_SIZE; ++i) + for (ssize_t j = 0; j < PATCH_SIZE; ++j) + { + // The whole patch should have the same height, + // since we resize by patches. + if (GetVertex(terrain, iTile * PATCH_SIZE, + jTile * PATCH_SIZE) == + GetVertex(terrain, iTile * PATCH_SIZE + i, + jTile * PATCH_SIZE + j)) + continue; + TS_FAIL("The whole patch should have the same height"); + std::stringstream ss; + ss << "iTile=" << iTile << " jTile=" << jTile + << " i=" << i << " j=" << j; + TS_WARN(ss.str()); + ss.str(std::string()); + ss << "found=" << GetVertex(terrain, iTile * PATCH_SIZE + i, + jTile * PATCH_SIZE + j) + << " expected=" << GetVertex(terrain, iTile * PATCH_SIZE, + jTile * PATCH_SIZE); + TS_WARN(ss.str()); + return; + } + + if (testCase.expectedPatches[iTile][jTile] == + GetVertex(terrain, iTile * PATCH_SIZE, + jTile * PATCH_SIZE)) + continue; + std::stringstream ss; + ss << "The patch has wrong height" + << " (i=" << iTile << " j=" << jTile << "):" + << " found=" << GetVertex(terrain, iTile * PATCH_SIZE, + jTile * PATCH_SIZE) + << " expected=" << testCase.expectedPatches[iTile][jTile]; + TS_FAIL(ss.str()); + ss.str(std::string()); + ss << "Terrain (" << terrain.GetPatchesPerSide() + << "x" << terrain.GetPatchesPerSide() << "):"; + TS_WARN(ss.str()); + for (ssize_t iTile = 0; iTile < expectedSize; ++iTile) + { + ss.str(std::string()); + ss << "["; + for (ssize_t jTile = 0; jTile < expectedSize; ++jTile) + { + if (jTile) + ss << ", "; + ss << GetVertex(terrain, iTile * PATCH_SIZE, + jTile * PATCH_SIZE); + } + ss << "]"; + TS_WARN(ss.str()); + } + return; + } + } + } + } };