2013-03-07 04:54:41 +01:00
|
|
|
/* Copyright (C) 2013 Wildfire Games.
|
2009-04-18 19:00:33 +02:00
|
|
|
* 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/>.
|
|
|
|
*/
|
|
|
|
|
2004-06-08 14:10:51 +02:00
|
|
|
#include "precompiled.h"
|
2005-06-20 17:14:36 +02:00
|
|
|
|
2012-01-22 19:20:33 +01:00
|
|
|
#include "TerrainTextureEntry.h"
|
2005-06-20 17:14:36 +02:00
|
|
|
|
2011-03-23 14:36:20 +01:00
|
|
|
#include "lib/utf8.h"
|
2006-06-02 04:10:27 +02:00
|
|
|
#include "lib/ogl.h"
|
2012-08-07 20:21:16 +02:00
|
|
|
#include "lib/allocators/shared_ptr.h"
|
2005-08-12 19:06:53 +02:00
|
|
|
#include "lib/res/graphics/ogl_tex.h"
|
2005-06-20 17:14:36 +02:00
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
#include "ps/CLogger.h"
|
|
|
|
#include "ps/Filesystem.h"
|
|
|
|
#include "ps/XML/Xeromyces.h"
|
|
|
|
|
|
|
|
#include "graphics/MaterialManager.h"
|
2012-01-22 19:20:33 +01:00
|
|
|
#include "graphics/Terrain.h"
|
|
|
|
#include "graphics/TerrainTextureManager.h"
|
|
|
|
#include "graphics/TerrainProperties.h"
|
|
|
|
#include "graphics/Texture.h"
|
2006-06-02 04:10:27 +02:00
|
|
|
#include "renderer/Renderer.h"
|
2004-06-08 14:10:51 +02:00
|
|
|
|
2012-01-22 19:20:33 +01:00
|
|
|
#include <map>
|
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, const VfsPath& path):
|
|
|
|
m_pProperties(properties),
|
2005-06-20 17:14:36 +02:00
|
|
|
m_BaseColor(0),
|
2010-09-10 23:02:10 +02:00
|
|
|
m_BaseColorValid(false)
|
2004-12-16 13:01:47 +01:00
|
|
|
{
|
2012-08-07 20:21:16 +02:00
|
|
|
ENSURE(properties);
|
|
|
|
|
|
|
|
CXeromyces XeroFile;
|
|
|
|
if (XeroFile.Load(g_VFS, path) != PSRETURN_OK)
|
|
|
|
{
|
|
|
|
LOGERROR(L"Terrain xml not found (%hs)", path.string().c_str());
|
|
|
|
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(L"Invalid terrain format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str());
|
|
|
|
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();
|
2011-02-05 14:13:57 +01:00
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
if (child_name == el_textures)
|
|
|
|
{
|
|
|
|
XERO_ITER_EL(child, textures_element)
|
|
|
|
{
|
|
|
|
ENSURE(textures_element.GetNodeName() == el_texture);
|
|
|
|
|
|
|
|
CStr name;
|
|
|
|
VfsPath path;
|
|
|
|
XERO_ITER_ATTR(textures_element, se)
|
|
|
|
{
|
|
|
|
if (se.Name == at_file)
|
|
|
|
path = VfsPath("art/textures/terrain") / se.Value.FromUTF8();
|
|
|
|
else if (se.Name == at_name)
|
|
|
|
name = se.Value;
|
|
|
|
}
|
|
|
|
samplers.push_back(make_pair(name, path));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (child_name == el_material)
|
|
|
|
{
|
|
|
|
VfsPath mat = VfsPath("art/materials") / child.GetText().FromUTF8();
|
2012-12-06 20:23:31 +01:00
|
|
|
if (CRenderer::IsInitialised())
|
|
|
|
m_Material = g_Renderer.GetMaterialManager().LoadMaterial(mat);
|
2012-08-07 20:21:16 +02:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
if (ret) 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.SetWrap(GL_REPEAT);
|
2010-09-10 23:02:10 +02:00
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
// TODO: anisotropy should probably be user-configurable, but we want it to be
|
|
|
|
// at least 2 for terrain else the ground looks very blurry when you tilt the
|
|
|
|
// camera upwards
|
|
|
|
texture.SetMaxAnisotropy(2.0f);
|
2010-09-10 23:02:10 +02:00
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
if (CRenderer::IsInitialised())
|
|
|
|
{
|
|
|
|
CTexturePtr texptr = g_Renderer.GetTextureManager().CreateTexture(texture);
|
|
|
|
m_Material.AddSampler(CMaterial::TextureSampler(samplers[i].first, texptr));
|
|
|
|
}
|
|
|
|
}
|
2012-12-06 20:23:31 +01:00
|
|
|
|
|
|
|
if (CRenderer::IsInitialised())
|
|
|
|
LoadAlphaMaps(alphamap);
|
2010-09-10 23:02:10 +02:00
|
|
|
|
2012-01-22 19:20:33 +01:00
|
|
|
float texAngle = 0.f;
|
|
|
|
float texSize = 1.f;
|
|
|
|
|
2005-08-07 23:58:36 +02:00
|
|
|
if (m_pProperties)
|
2012-01-22 19:20:33 +01:00
|
|
|
{
|
2005-08-07 23:58:36 +02:00
|
|
|
m_Groups = m_pProperties->GetGroups();
|
2012-01-22 19:20:33 +01:00
|
|
|
texAngle = m_pProperties->GetTextureAngle();
|
|
|
|
texSize = m_pProperties->GetTextureSize();
|
|
|
|
}
|
2005-06-20 17:14:36 +02:00
|
|
|
|
2012-01-22 19:20:33 +01:00
|
|
|
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;
|
|
|
|
|
2005-06-20 17:14:36 +02:00
|
|
|
GroupVector::iterator it=m_Groups.begin();
|
|
|
|
for (;it!=m_Groups.end();++it)
|
2005-08-07 23:58:36 +02:00
|
|
|
(*it)->AddTerrain(this);
|
2005-06-20 17:14:36 +02:00
|
|
|
}
|
|
|
|
|
2010-09-04 23:26:52 +02:00
|
|
|
CTerrainTextureEntry::~CTerrainTextureEntry()
|
2005-06-20 17:14:36 +02:00
|
|
|
{
|
2005-08-07 23:58:36 +02:00
|
|
|
for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it)
|
|
|
|
(*it)->RemoveTerrain(this);
|
2004-12-16 13:01:47 +01:00
|
|
|
}
|
|
|
|
|
2005-08-10 03:12:03 +02:00
|
|
|
// BuildBaseColor: calculate the root colour of the texture, used for coloring minimap, and store
|
2004-06-08 14:10:51 +02:00
|
|
|
// in m_BaseColor member
|
2010-09-04 23:26:52 +02:00
|
|
|
void CTerrainTextureEntry::BuildBaseColor()
|
2004-06-08 14:10:51 +02:00
|
|
|
{
|
2010-09-10 23:02:10 +02:00
|
|
|
// Use the explicit properties value if possible
|
2005-08-07 23:58:36 +02:00
|
|
|
if (m_pProperties && m_pProperties->HasBaseColor())
|
2005-06-20 17:14:36 +02:00
|
|
|
{
|
2005-08-07 23:58:36 +02:00
|
|
|
m_BaseColor=m_pProperties->GetBaseColor();
|
2010-09-10 23:02:10 +02:00
|
|
|
m_BaseColorValid = true;
|
2005-06-20 17:14:36 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-09-10 23:02:10 +02:00
|
|
|
// Use the texture colour if available
|
2012-08-07 20:21:16 +02:00
|
|
|
if (GetTexture()->TryLoad())
|
2010-09-10 23:02:10 +02:00
|
|
|
{
|
2012-08-07 20:21:16 +02:00
|
|
|
m_BaseColor = GetTexture()->GetBaseColour();
|
2010-09-10 23:02:10 +02:00
|
|
|
m_BaseColorValid = true;
|
|
|
|
}
|
2004-06-11 04:14:18 +02:00
|
|
|
}
|
2012-01-22 19:20:33 +01:00
|
|
|
|
|
|
|
const float* CTerrainTextureEntry::GetTextureMatrix()
|
|
|
|
{
|
|
|
|
return &m_TextureMatrix._11;
|
2012-01-31 01:06:56 +01:00
|
|
|
}
|
2012-08-07 20:21:16 +02:00
|
|
|
|
|
|
|
// LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
|
|
|
|
// calculate the coordinate of each alphamap within this packed texture
|
|
|
|
void CTerrainTextureEntry::LoadAlphaMaps(VfsPath &amtype)
|
|
|
|
{
|
2013-03-07 04:54:41 +01:00
|
|
|
std::wstring key = L"(alpha map composite" + amtype.string() + L")";
|
|
|
|
|
2012-08-07 20:21:16 +02:00
|
|
|
CTerrainTextureManager::TerrainAlphaMap::iterator it = g_TexMan.m_TerrainAlphas.find(amtype);
|
|
|
|
|
|
|
|
if (it != g_TexMan.m_TerrainAlphas.end())
|
|
|
|
{
|
|
|
|
m_TerrainAlpha = it;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_TexMan.m_TerrainAlphas[amtype] = TerrainAlpha();
|
|
|
|
it = g_TexMan.m_TerrainAlphas.find(amtype);
|
|
|
|
|
|
|
|
TerrainAlpha &result = it->second;
|
|
|
|
|
|
|
|
//
|
|
|
|
// load all textures and store Handle in array
|
|
|
|
//
|
|
|
|
Handle textures[NUM_ALPHA_MAPS] = {0};
|
|
|
|
VfsPath path(L"art/textures/terrain/alphamaps");
|
|
|
|
path = path / amtype;
|
|
|
|
|
|
|
|
const wchar_t* fnames[NUM_ALPHA_MAPS] = {
|
|
|
|
L"blendcircle.png",
|
|
|
|
L"blendlshape.png",
|
|
|
|
L"blendedge.png",
|
|
|
|
L"blendedgecorner.png",
|
|
|
|
L"blendedgetwocorners.png",
|
|
|
|
L"blendfourcorners.png",
|
|
|
|
L"blendtwooppositecorners.png",
|
|
|
|
L"blendlshapecorner.png",
|
|
|
|
L"blendtwocorners.png",
|
|
|
|
L"blendcorner.png",
|
|
|
|
L"blendtwoedges.png",
|
|
|
|
L"blendthreecorners.png",
|
|
|
|
L"blendushape.png",
|
|
|
|
L"blendbad.png"
|
|
|
|
};
|
|
|
|
size_t base = 0; // texture width/height (see below)
|
|
|
|
// for convenience, we require all alpha maps to be of the same BPP
|
|
|
|
// (avoids another ogl_tex_get_size call, and doesn't hurt)
|
|
|
|
size_t bpp = 0;
|
|
|
|
for(size_t i=0;i<NUM_ALPHA_MAPS;i++)
|
|
|
|
{
|
|
|
|
// note: these individual textures can be discarded afterwards;
|
|
|
|
// we cache the composite.
|
|
|
|
textures[i] = ogl_tex_load(g_VFS, path / fnames[i]);
|
|
|
|
if (textures[i] < 0)
|
|
|
|
{
|
|
|
|
g_TexMan.m_TerrainAlphas.erase(it);
|
|
|
|
LOGERROR((L"Failed to load alphamap: " + amtype.string()).c_str());
|
|
|
|
|
|
|
|
VfsPath standard("standard");
|
|
|
|
if (path != standard)
|
|
|
|
{
|
|
|
|
LoadAlphaMaps(standard);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get its size and make sure they are all equal.
|
|
|
|
// (the packing algo assumes this)
|
|
|
|
size_t this_width = 0, this_height = 0, this_bpp = 0; // fail-safe
|
|
|
|
(void)ogl_tex_get_size(textures[i], &this_width, &this_height, &this_bpp);
|
|
|
|
if(this_width != this_height)
|
|
|
|
DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
|
|
|
|
// .. first iteration: establish size
|
|
|
|
if(i == 0)
|
|
|
|
{
|
|
|
|
base = this_width;
|
|
|
|
bpp = this_bpp;
|
|
|
|
}
|
|
|
|
// .. not first: make sure texture size matches
|
|
|
|
else if(base != this_width || bpp != this_bpp)
|
|
|
|
DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// copy each alpha map (tile) into one buffer, arrayed horizontally.
|
|
|
|
//
|
|
|
|
size_t tile_w = 2+base+2; // 2 pixel border (avoids bilinear filtering artifacts)
|
|
|
|
size_t total_w = round_up_to_pow2(tile_w * NUM_ALPHA_MAPS);
|
|
|
|
size_t total_h = base; ENSURE(is_pow2(total_h));
|
|
|
|
shared_ptr<u8> data;
|
|
|
|
AllocateAligned(data, total_w*total_h, maxSectorSize);
|
|
|
|
// for each tile on row
|
|
|
|
for (size_t i = 0; i < NUM_ALPHA_MAPS; i++)
|
|
|
|
{
|
|
|
|
// get src of copy
|
|
|
|
u8* src = 0;
|
|
|
|
(void)ogl_tex_get_data(textures[i], &src);
|
|
|
|
|
|
|
|
size_t srcstep = bpp/8;
|
|
|
|
|
|
|
|
// get destination of copy
|
|
|
|
u8* dst = data.get() + (i*tile_w);
|
|
|
|
|
|
|
|
// for each row of image
|
|
|
|
for (size_t j = 0; j < base; j++)
|
|
|
|
{
|
|
|
|
// duplicate first pixel
|
|
|
|
*dst++ = *src;
|
|
|
|
*dst++ = *src;
|
|
|
|
|
|
|
|
// copy a row
|
|
|
|
for (size_t k = 0; k < base; k++)
|
|
|
|
{
|
|
|
|
*dst++ = *src;
|
|
|
|
src += srcstep;
|
|
|
|
}
|
|
|
|
|
|
|
|
// duplicate last pixel
|
|
|
|
*dst++ = *(src-srcstep);
|
|
|
|
*dst++ = *(src-srcstep);
|
|
|
|
|
|
|
|
// advance write pointer for next row
|
|
|
|
dst += total_w-tile_w;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w);
|
|
|
|
result.m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w);
|
|
|
|
result.m_AlphaMapCoords[i].v0 = 0.0f;
|
|
|
|
result.m_AlphaMapCoords[i].v1 = 1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < NUM_ALPHA_MAPS; i++)
|
|
|
|
(void)ogl_tex_free(textures[i]);
|
|
|
|
|
|
|
|
// upload the composite texture
|
|
|
|
Tex t;
|
2014-03-13 03:37:05 +01:00
|
|
|
(void)t.wrap(total_w, total_h, 8, TEX_GREY, data, 0);
|
2012-08-07 20:21:16 +02:00
|
|
|
|
|
|
|
// uncomment the following to save a png of the generated texture
|
|
|
|
// in the public/ directory, for debugging
|
|
|
|
/*VfsPath filename("blendtex.png");
|
|
|
|
|
|
|
|
DynArray da;
|
|
|
|
RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));
|
|
|
|
|
|
|
|
// write to disk
|
|
|
|
//Status ret = INFO::OK;
|
|
|
|
{
|
|
|
|
shared_ptr<u8> file = DummySharedPtr(da.base);
|
|
|
|
const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
|
|
|
|
if(bytes_written > 0)
|
|
|
|
ENSURE(bytes_written == (ssize_t)da.pos);
|
|
|
|
//else
|
|
|
|
// ret = (Status)bytes_written;
|
|
|
|
}
|
|
|
|
|
|
|
|
(void)da_free(&da);*/
|
|
|
|
|
|
|
|
Handle hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key);
|
|
|
|
(void)ogl_tex_set_filter(hCompositeAlphaMap, GL_LINEAR);
|
|
|
|
(void)ogl_tex_set_wrap (hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
|
|
|
|
ogl_tex_upload(hCompositeAlphaMap, GL_ALPHA, 0, 0);
|
|
|
|
result.m_hCompositeAlphaMap = hCompositeAlphaMap;
|
|
|
|
|
|
|
|
m_TerrainAlpha = it;
|
|
|
|
}
|