diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt index 9f8061238..3b8a48aaf 100644 --- a/src/unittest/CMakeLists.txt +++ b/src/unittest/CMakeLists.txt @@ -44,6 +44,7 @@ set (UNITTEST_SRCS set (UNITTEST_CLIENT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/mesh_compare.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_clientactiveobjectmgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_content_mapblock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp diff --git a/src/unittest/test_content_mapblock.cpp b/src/unittest/test_content_mapblock.cpp new file mode 100644 index 000000000..798206a5f --- /dev/null +++ b/src/unittest/test_content_mapblock.cpp @@ -0,0 +1,268 @@ +/* +Minetest +Copyright (C) 2023 Vitaliy Lobachevskiy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include +#include + +#include "gamedef.h" +#include "dummygamedef.h" +#include "client/content_mapblock.h" +#include "client/mapblock_mesh.h" +#include "client/meshgen/collector.h" +#include "mesh_compare.h" +#include "util/directiontables.h" + +namespace { + +class MockGameDef : public DummyGameDef { +public: + IWritableItemDefManager *item_mgr() noexcept { + return static_cast(m_itemdef); + } + + NodeDefManager *node_mgr() noexcept { + return const_cast(m_nodedef); + } + + content_t registerNode(ItemDefinition itemdef, ContentFeatures nodedef) { + item_mgr()->registerItem(itemdef); + return node_mgr()->set(nodedef.name, nodedef); + } + + void finalize() { + node_mgr()->resolveCrossrefs(); + } + + MeshMakeData makeSingleNodeMMD(bool smooth_lighting = true, bool for_shaders = true) + { + MeshMakeData data{ndef(), 1, for_shaders}; + data.setSmoothLighting(smooth_lighting); + data.m_blockpos = {0, 0, 0}; + for (s16 x = -1; x <= 1; x++) + for (s16 y = -1; y <= 1; y++) + for (s16 z = -1; z <= 1; z++) + data.m_vmanip.setNode({x, y, z}, {CONTENT_AIR, 0, 0}); + return data; + } + + content_t addSimpleNode(std::string name, u32 texture) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_NORMAL; + f.solidness = 2; + f.alpha = ALPHAMODE_OPAQUE; + for (TileDef &tiledef : f.tiledef) + tiledef.name = name + ".png"; + for (TileSpec &tile : f.tiles) + tile.layers[0].texture_id = texture; + + return registerNode(itemdef, f); + } + + content_t addLiquidSource(std::string name, u32 texture) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name + "_source"; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_LIQUID; + f.solidness = 1; + f.alpha = ALPHAMODE_BLEND; + f.light_propagates = true; + f.param_type = CPT_LIGHT; + f.liquid_type = LIQUID_SOURCE; + f.liquid_viscosity = 4; + f.groups["liquids"] = 3; + f.liquid_alternative_source = "test:" + name + "_source"; + f.liquid_alternative_flowing = "test:" + name + "_flowing"; + for (TileDef &tiledef : f.tiledef) + tiledef.name = name + ".png"; + for (TileSpec &tile : f.tiles) + tile.layers[0].texture_id = texture; + + return registerNode(itemdef, f); + } + + content_t addLiquidFlowing(std::string name, u32 texture_top, u32 texture_side) + { + ItemDefinition itemdef; + itemdef.type = ITEM_NODE; + itemdef.name = "test:" + name + "_flowing"; + itemdef.description = name; + + ContentFeatures f; + f.name = itemdef.name; + f.drawtype = NDT_FLOWINGLIQUID; + f.solidness = 0; + f.alpha = ALPHAMODE_BLEND; + f.light_propagates = true; + f.param_type = CPT_LIGHT; + f.liquid_type = LIQUID_FLOWING; + f.liquid_viscosity = 4; + f.groups["liquids"] = 3; + f.liquid_alternative_source = "test:" + name + "_source"; + f.liquid_alternative_flowing = "test:" + name + "_flowing"; + f.tiledef_special[0].name = name + "_top.png"; + f.tiledef_special[1].name = name + "_side.png"; + f.special_tiles[0].layers[0].texture_id = texture_top; + f.special_tiles[1].layers[0].texture_id = texture_side; + + return registerNode(itemdef, f); + } +}; + +void set_light_decode_table() +{ + u8 table[LIGHT_SUN + 1] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, + }; + memcpy(const_cast(light_decode_table), table, sizeof(table)); +} + +class TestMapblockMeshGenerator : public TestBase { +public: + TestMapblockMeshGenerator() { TestManager::registerTestModule(this); } + const char *getName() override { return "TestMapblockMeshGenerator"; } + + void runTests(IGameDef *gamedef) override; + void testSimpleNode(); + void testSurroundedNode(); + void testInterliquidSame(); + void testInterliquidDifferent(); +}; + +static TestMapblockMeshGenerator g_test_instance; + +void TestMapblockMeshGenerator::runTests(IGameDef *gamedef) +{ + set_light_decode_table(); + TEST(testSimpleNode); + TEST(testSurroundedNode); + TEST(testInterliquidSame); + TEST(testInterliquidDifferent); +} + +namespace quad { + constexpr float h = BS / 2.0f; + const Quad zp{{{{-h, -h, h}, {0, 0, 1}, 0, {1, 1}}, {{h, -h, h}, {0, 0, 1}, 0, {0, 1}}, {{h, h, h}, {0, 0, 1}, 0, {0, 0}}, {{-h, h, h}, {0, 0, 1}, 0, {1, 0}}}}; + const Quad yp{{{{-h, h, -h}, {0, 1, 0}, 0, {0, 1}}, {{-h, h, h}, {0, 1, 0}, 0, {0, 0}}, {{h, h, h}, {0, 1, 0}, 0, {1, 0}}, {{h, h, -h}, {0, 1, 0}, 0, {1, 1}}}}; + const Quad xp{{{{h, -h, -h}, {1, 0, 0}, 0, {0, 1}}, {{h, h, -h}, {1, 0, 0}, 0, {0, 0}}, {{h, h, h}, {1, 0, 0}, 0, {1, 0}}, {{h, -h, h}, {1, 0, 0}, 0, {1, 1}}}}; + const Quad zn{{{{-h, -h, -h}, {0, 0, -1}, 0, {0, 1}}, {{-h, h, -h}, {0, 0, -1}, 0, {0, 0}}, {{h, h, -h}, {0, 0, -1}, 0, {1, 0}}, {{h, -h, -h}, {0, 0, -1}, 0, {1, 1}}}}; + const Quad yn{{{{-h, -h, -h}, {0, -1, 0}, 0, {0, 0}}, {{h, -h, -h}, {0, -1, 0}, 0, {1, 0}}, {{h, -h, h}, {0, -1, 0}, 0, {1, 1}}, {{-h, -h, h}, {0, -1, 0}, 0, {0, 1}}}}; + const Quad xn{{{{-h, -h, -h}, {-1, 0, 0}, 0, {1, 1}}, {{-h, -h, h}, {-1, 0, 0}, 0, {0, 1}}, {{-h, h, h}, {-1, 0, 0}, 0, {0, 0}}, {{-h, h, -h}, {-1, 0, 0}, 0, {1, 0}}}}; +} + +void TestMapblockMeshGenerator::testSimpleNode() +{ + MockGameDef gamedef; + content_t stone = gamedef.addSimpleNode("stone", 42); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testSurroundedNode() +{ + MockGameDef gamedef; + content_t stone = gamedef.addSimpleNode("stone", 42); + content_t wood = gamedef.addSimpleNode("wood", 13); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0}); + data.m_vmanip.setNode({1, 0, 0}, {wood, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testInterliquidSame() +{ + MockGameDef gamedef; + auto water = gamedef.addLiquidSource("water", 42); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0}); + data.m_vmanip.setNode({1, 0, 0}, {water, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +void TestMapblockMeshGenerator::testInterliquidDifferent() +{ + MockGameDef gamedef; + auto water = gamedef.addLiquidSource("water", 42); + auto lava = gamedef.addLiquidSource("lava", 13); + gamedef.finalize(); + + MeshMakeData data = gamedef.makeSingleNodeMMD(); + data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0}); + data.m_vmanip.setNode({0, 0, 1}, {lava, 0, 0}); + + MeshCollector col{{}}; + MapblockMeshGenerator mg{&data, &col, nullptr}; + mg.generate(); + UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1); + UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0); + + auto &&buf = col.prebuffers[0][0]; + UASSERTEQ(u32, buf.layer.texture_id, 42); + UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp})); +} + +}