mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Previously, opening a map that referenced a deleted or missing terrain caused a severe crash with multiple error dialogs, forcing users to kill the process. This change restores the previous behavior where missing terrains are gracefully handled: a warning is logged and the terrain is substituted with a fallback placeholder (ErrorTexture from TextureManager). This significantly improves UX for developers and modders working with outdated or broken maps, aligning behavior with expectations from earlier versions of the editor.
210 lines
5.8 KiB
C++
210 lines
5.8 KiB
C++
/* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "TerrainTextureEntry.h"
|
|
|
|
#include "graphics/MaterialManager.h"
|
|
#include "graphics/Terrain.h"
|
|
#include "graphics/TerrainProperties.h"
|
|
#include "graphics/TerrainTextureManager.h"
|
|
#include "graphics/TextureManager.h"
|
|
#include "lib/utf8.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/CStrInternStatic.h"
|
|
#include "ps/Filesystem.h"
|
|
#include "ps/XML/Xeromyces.h"
|
|
#include "renderer/Renderer.h"
|
|
#include "renderer/SceneRenderer.h"
|
|
|
|
#include <map>
|
|
|
|
CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, const VfsPath& path):
|
|
m_pProperties(properties),
|
|
m_BaseColor(0),
|
|
m_BaseColorValid(false)
|
|
{
|
|
ENSURE(properties);
|
|
|
|
CXeromyces XeroFile;
|
|
if (XeroFile.Load(g_VFS, path, "terrain_texture") != PSRETURN_OK)
|
|
{
|
|
LOGERROR("Terrain xml not found (%s)", path.string8());
|
|
return;
|
|
}
|
|
|
|
#define EL(x) int el_##x = XeroFile.GetElementID(#x)
|
|
#define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
|
|
EL(tag);
|
|
EL(terrain);
|
|
EL(texture);
|
|
EL(textures);
|
|
EL(material);
|
|
EL(props);
|
|
EL(alphamap);
|
|
AT(file);
|
|
AT(name);
|
|
#undef AT
|
|
#undef EL
|
|
|
|
XMBElement root = XeroFile.GetRoot();
|
|
|
|
if (root.GetNodeName() != el_terrain)
|
|
{
|
|
LOGERROR("Invalid terrain format (unrecognized root element '%s')", XeroFile.GetElementString(root.GetNodeName()));
|
|
return;
|
|
}
|
|
|
|
std::vector<std::pair<CStr, VfsPath> > samplers;
|
|
VfsPath alphamap("standard");
|
|
m_Tag = utf8_from_wstring(path.Basename().string());
|
|
|
|
XERO_ITER_EL(root, child)
|
|
{
|
|
int child_name = child.GetNodeName();
|
|
|
|
if (child_name == el_textures)
|
|
{
|
|
XERO_ITER_EL(child, textures_element)
|
|
{
|
|
ENSURE(textures_element.GetNodeName() == el_texture);
|
|
|
|
CStr name;
|
|
VfsPath terrainTexturePath;
|
|
XERO_ITER_ATTR(textures_element, relativePath)
|
|
{
|
|
if (relativePath.Name == at_file)
|
|
terrainTexturePath = VfsPath("art/textures/terrain") / relativePath.Value.FromUTF8();
|
|
else if (relativePath.Name == at_name)
|
|
name = relativePath.Value;
|
|
}
|
|
samplers.emplace_back(name, terrainTexturePath);
|
|
if (name == str_baseTex.string())
|
|
m_DiffuseTexturePath = terrainTexturePath;
|
|
}
|
|
|
|
}
|
|
else if (child_name == el_material)
|
|
{
|
|
VfsPath mat = VfsPath("art/materials") / child.GetText().FromUTF8();
|
|
if (CRenderer::IsInitialised())
|
|
m_Material = g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(mat);
|
|
}
|
|
else if (child_name == el_alphamap)
|
|
{
|
|
alphamap = child.GetText().FromUTF8();
|
|
}
|
|
else if (child_name == el_props)
|
|
{
|
|
CTerrainPropertiesPtr ret (new CTerrainProperties(properties));
|
|
ret->LoadXml(child, &XeroFile, path);
|
|
m_pProperties = ret;
|
|
}
|
|
else if (child_name == el_tag)
|
|
{
|
|
m_Tag = child.GetText();
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < samplers.size(); ++i)
|
|
{
|
|
CTextureProperties texture(samplers[i].second);
|
|
texture.SetAddressMode(Renderer::Backend::Sampler::AddressMode::REPEAT);
|
|
texture.SetAnisotropicFilter(true);
|
|
|
|
if (CRenderer::IsInitialised())
|
|
{
|
|
CTexturePtr texptr = g_Renderer.GetTextureManager().CreateTexture(texture);
|
|
m_Material.AddSampler(CMaterial::TextureSampler(samplers[i].first, texptr));
|
|
}
|
|
}
|
|
|
|
if (CRenderer::IsInitialised())
|
|
m_TerrainAlpha = g_TexMan.LoadAlphaMap(alphamap);
|
|
|
|
float texAngle{0.f};
|
|
float texSize{1.f};
|
|
|
|
if (m_pProperties)
|
|
{
|
|
m_Groups = m_pProperties->GetGroups();
|
|
texAngle = m_pProperties->GetTextureAngle();
|
|
texSize = m_pProperties->GetTextureSize();
|
|
}
|
|
GenerateTextureMatrix(texAngle, texSize);
|
|
|
|
GroupVector::iterator it=m_Groups.begin();
|
|
for (;it!=m_Groups.end();++it)
|
|
(*it)->AddTerrain(this);
|
|
}
|
|
|
|
CTerrainTextureEntry::CTerrainTextureEntry(const CStr tag):
|
|
m_pProperties(nullptr),
|
|
m_BaseColor(0),
|
|
m_BaseColorValid(false),
|
|
m_DiffuseTexturePath(""),
|
|
m_Tag(tag)
|
|
{
|
|
if (!CRenderer::IsInitialised())
|
|
return;
|
|
|
|
const VfsPath alphamap{"standard"};
|
|
const VfsPath mat{VfsPath{"art/materials"} / "terrain_norm_spec.xml"};
|
|
m_Material = g_Renderer.GetSceneRenderer().GetMaterialManager().LoadMaterial(mat);
|
|
const CTexturePtr texptr{g_Renderer.GetTextureManager().GetErrorTexture()};
|
|
m_Material.AddSampler(CMaterial::TextureSampler{str_baseTex, texptr});
|
|
m_TerrainAlpha = g_TexMan.LoadAlphaMap(alphamap);
|
|
|
|
GenerateTextureMatrix(0.0f, 1.f);
|
|
}
|
|
|
|
CTerrainTextureEntry::~CTerrainTextureEntry()
|
|
{
|
|
for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it)
|
|
(*it)->RemoveTerrain(this);
|
|
}
|
|
|
|
// BuildBaseColor: calculate the root color of the texture, used for coloring minimap, and store
|
|
// in m_BaseColor member
|
|
void CTerrainTextureEntry::BuildBaseColor()
|
|
{
|
|
// Use the explicit properties value if possible
|
|
if (m_pProperties && m_pProperties->HasBaseColor())
|
|
{
|
|
m_BaseColor=m_pProperties->GetBaseColor();
|
|
m_BaseColorValid = true;
|
|
return;
|
|
}
|
|
|
|
// Use the texture color if available
|
|
if (GetTexture()->TryLoad())
|
|
{
|
|
m_BaseColor = GetTexture()->GetBaseColor();
|
|
m_BaseColorValid = true;
|
|
}
|
|
}
|
|
|
|
void CTerrainTextureEntry::GenerateTextureMatrix(const float texAngle, const float texSize)
|
|
{
|
|
m_TextureMatrix.SetZero();
|
|
m_TextureMatrix._11 = cosf(texAngle) / texSize;
|
|
m_TextureMatrix._13 = -sinf(texAngle) / texSize;
|
|
m_TextureMatrix._21 = -sinf(texAngle) / texSize;
|
|
m_TextureMatrix._23 = -cosf(texAngle) / texSize;
|
|
m_TextureMatrix._44 = 1.f;
|
|
}
|