/* 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);
}
};