/* Copyright (C) 2024 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/ColladaManager.h" #include "graphics/Decal.h" #include "graphics/Material.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ObjectEntry.h" #include "graphics/ObjectManager.h" #include "graphics/ShaderDefines.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/file/io/io.h" #include "ps/ConfigDB.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/ProfileViewer.h" #include "ps/VideoMode.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/dummy/Device.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include #include namespace { constexpr std::string_view TEST_SKELETON_XML{R"()"}; constexpr std::string_view TEST_MESH_XML{R"( Z_UP 1 1 0 1 -1 0 -1 -0.9999998 0 -0.9999997 1 0 0 0 1 0 0 1 0 1 1 0 1 4

0 0 0 3 0 1 2 0 2 1 0 3

)"}; constexpr std::string_view TEST_ACTOR_WITH_SHADOWS_NAME{"test_with_shadows.xml"}; constexpr std::string_view TEST_ACTOR_WITH_SHADOWS_XML{R"( test.dae )"}; } class TestModel : public CxxTest::TestSuite { OsPath m_ModPath; OsPath m_CachePath; std::unique_ptr m_Viewer; std::unique_ptr m_Renderer; public: void setUp() { g_VFS = CreateVfs(); CConfigDB::Initialise(); CConfigDB::Instance()->SetValueString(CFG_SYSTEM, "rendererbackend", "dummy"); CXeromyces::Startup(); TestLogger logger; g_VideoMode.InitNonSDL(); g_VideoMode.CreateBackendDevice(false); m_Viewer = std::make_unique(); m_Renderer = std::make_unique(g_VideoMode.GetBackendDevice()); m_ModPath = DataDir() / "mods" / "_test.model" / ""; m_CachePath = DataDir() / "_testcache" / ""; const OsPath skeletonsPath = m_ModPath / "art" / "skeletons"; TS_ASSERT_EQUALS(INFO::OK, CreateDirectories(skeletonsPath, 0700, false)); const OsPath meshesPath = m_ModPath / "art" / "meshes"; TS_ASSERT_EQUALS(INFO::OK, CreateDirectories(meshesPath, 0700, false)); const OsPath testSkeletonPath = skeletonsPath / "test.xml"; TS_ASSERT_EQUALS(INFO::OK, io::Store(testSkeletonPath, TEST_SKELETON_XML.data(), TEST_SKELETON_XML.size())); const OsPath testMeshPath = meshesPath / "test.dae"; TS_ASSERT_EQUALS(INFO::OK, io::Store(testMeshPath, TEST_MESH_XML.data(), TEST_MESH_XML.size())); const OsPath actorsPath = m_ModPath / "art" / "actors"; TS_ASSERT_EQUALS(INFO::OK, CreateDirectories(actorsPath, 0700, false)); const OsPath testActorPath = actorsPath / TEST_ACTOR_WITH_SHADOWS_NAME.data(); TS_ASSERT_EQUALS(INFO::OK, io::Store(testActorPath, TEST_ACTOR_WITH_SHADOWS_XML.data(), TEST_ACTOR_WITH_SHADOWS_XML.size())); TS_ASSERT_OK(g_VFS->Mount(L"", m_ModPath)); TS_ASSERT_OK(g_VFS->Mount(L"cache/", m_CachePath, 0, VFS_MAX_PRIORITY)); } void tearDown() { m_Renderer.reset(); m_Viewer.reset(); g_VideoMode.Shutdown(); CXeromyces::Terminate(); CConfigDB::Shutdown(); g_VFS.reset(); DeleteDirectory(m_ModPath); DeleteDirectory(m_CachePath); } bool HasShaderDefine(const CShaderDefines& defines, CStrIntern define) { const auto& map = defines.GetMap(); const auto it = map.find(define); return it != map.end() && it->second == str_1; } bool HasMaterialDefine(CModel* model, CStrIntern define) { return HasShaderDefine(model->GetMaterial().GetShaderDefines(), define); } bool HasMaterialDefine(CModelDecal* model, CStrIntern define) { return HasShaderDefine(model->m_Decal.m_Material.GetShaderDefines(), define); } void test_model_with_flags() { TestLogger logger; CMaterial material{}; CSimulation2 simulation{nullptr, *g_ScriptContext, nullptr}; CTerrain terrain; terrain.Initialize(4, nullptr); // TODO: load a proper mock for modeldef. CModelDefPtr modeldef = std::make_shared(); std::unique_ptr model = std::make_unique(simulation, material, modeldef); SPropPoint propPoint{}; model->AddProp(&propPoint, std::make_unique(simulation, material, modeldef), nullptr); SDecal decal{CMaterial{}, 4.0f, 4.0f, 0.0f, 4.0f, 4.0f, false}; model->AddProp(&propPoint, std::make_unique(&terrain, decal), nullptr); model->AddFlagsRec(ModelFlag::IGNORE_LOS); model->RemoveShadowsRec(); TS_ASSERT(HasMaterialDefine(model.get(), str_DISABLE_RECEIVE_SHADOWS)); TS_ASSERT(HasMaterialDefine(model.get(), str_IGNORE_LOS)); for (const CModel::Prop& prop : model->GetProps()) { TS_ASSERT(prop.m_Model->ToCModel() || prop.m_Model->ToCModelDecal()); if (prop.m_Model->ToCModel()) { TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModel(), str_DISABLE_RECEIVE_SHADOWS)); TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModel(), str_IGNORE_LOS)); } else if (prop.m_Model->ToCModelDecal()) { TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModelDecal(), str_DISABLE_RECEIVE_SHADOWS)); } } std::unique_ptr clonedModel = model->Clone(); TS_ASSERT(clonedModel->ToCModel()); TS_ASSERT(HasMaterialDefine(clonedModel->ToCModel(), str_DISABLE_RECEIVE_SHADOWS)); TS_ASSERT(HasMaterialDefine(clonedModel->ToCModel(), str_IGNORE_LOS)); TS_ASSERT_EQUALS(model->GetProps().size(), clonedModel->ToCModel()->GetProps().size()); for (const CModel::Prop& prop : clonedModel->ToCModel()->GetProps()) { TS_ASSERT(prop.m_Model->ToCModel() || prop.m_Model->ToCModelDecal()); if (prop.m_Model->ToCModel()) { TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModel(), str_DISABLE_RECEIVE_SHADOWS)); TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModel(), str_IGNORE_LOS)); } else if (prop.m_Model->ToCModelDecal()) { TS_ASSERT(HasMaterialDefine(prop.m_Model->ToCModelDecal(), str_DISABLE_RECEIVE_SHADOWS)); } } } void test_unit_reload() { TestLogger logger; CColladaManager colladaManager{g_VFS}; CMeshManager meshManager{colladaManager}; CSkeletonAnimManager skeletonAnimationManager{colladaManager}; CUnitManager unitManager; CSimulation2 simulation{&unitManager, *g_ScriptContext, nullptr}; CObjectManager objectManager{ meshManager, skeletonAnimationManager, simulation}; unitManager.SetObjectManager(objectManager); const CStrW actorName = CStr{TEST_ACTOR_WITH_SHADOWS_NAME}.FromUTF8(); const entity_id_t id = 1; const uint32_t seed = 1; CUnit* unit = unitManager.CreateUnit(actorName, id, seed); TS_ASSERT(unit); CModel* model = unit->GetModel().ToCModel(); TS_ASSERT(model); TS_ASSERT((model->GetFlags() & ModelFlag::CAST_SHADOWS) == ModelFlag::CAST_SHADOWS); auto [success, actor] = objectManager.FindActorDef(actorName); TS_ASSERT(success); const uint32_t newSeed = 2; // Trigger the unit reload. unit->SetActorSelections(actor.PickSelectionsAtRandom(newSeed)); model = unit->GetModel().ToCModel(); TS_ASSERT(model); TS_ASSERT((model->GetFlags() & ModelFlag::CAST_SHADOWS) == ModelFlag::CAST_SHADOWS); } };