1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "precompiled.h"
19
20 #include "TerrainTextureEntry.h"
21
22 #include "lib/utf8.h"
23 #include "lib/ogl.h"
24 #include "lib/allocators/shared_ptr.h"
25 #include "lib/res/graphics/ogl_tex.h"
26
27 #include "ps/CLogger.h"
28 #include "ps/Filesystem.h"
29 #include "ps/XML/Xeromyces.h"
30
31 #include "graphics/MaterialManager.h"
32 #include "graphics/Terrain.h"
33 #include "graphics/TerrainTextureManager.h"
34 #include "graphics/TerrainProperties.h"
35 #include "graphics/Texture.h"
36 #include "renderer/Renderer.h"
37
38 #include <map>
39
CTerrainTextureEntry(CTerrainPropertiesPtr properties,const VfsPath & path)40 CTerrainTextureEntry::CTerrainTextureEntry(CTerrainPropertiesPtr properties, const VfsPath& path):
41 m_pProperties(properties),
42 m_BaseColor(0),
43 m_BaseColorValid(false)
44 {
45 ENSURE(properties);
46
47 CXeromyces XeroFile;
48 if (XeroFile.Load(g_VFS, path, "terrain_texture") != PSRETURN_OK)
49 {
50 LOGERROR("Terrain xml not found (%s)", path.string8());
51 return;
52 }
53
54 #define EL(x) int el_##x = XeroFile.GetElementID(#x)
55 #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
56 EL(tag);
57 EL(terrain);
58 EL(texture);
59 EL(textures);
60 EL(material);
61 EL(props);
62 EL(alphamap);
63 AT(file);
64 AT(name);
65 #undef AT
66 #undef EL
67
68
69 XMBElement root = XeroFile.GetRoot();
70
71 if (root.GetNodeName() != el_terrain)
72 {
73 LOGERROR("Invalid terrain format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()).c_str());
74 return;
75 }
76
77
78 std::vector<std::pair<CStr, VfsPath> > samplers;
79 VfsPath alphamap("standard");
80 m_Tag = utf8_from_wstring(path.Basename().string());
81
82
83 XERO_ITER_EL(root, child)
84 {
85 int child_name = child.GetNodeName();
86
87 if (child_name == el_textures)
88 {
89 XERO_ITER_EL(child, textures_element)
90 {
91 ENSURE(textures_element.GetNodeName() == el_texture);
92
93 CStr name;
94 VfsPath terrainTexturePath;
95 XERO_ITER_ATTR(textures_element, relativePath)
96 {
97 if (relativePath.Name == at_file)
98 terrainTexturePath = VfsPath("art/textures/terrain") / relativePath.Value.FromUTF8();
99 else if (relativePath.Name == at_name)
100 name = relativePath.Value;
101 }
102 samplers.emplace_back(name, terrainTexturePath);
103 }
104
105 }
106 else if (child_name == el_material)
107 {
108 VfsPath mat = VfsPath("art/materials") / child.GetText().FromUTF8();
109 if (CRenderer::IsInitialised())
110 m_Material = g_Renderer.GetMaterialManager().LoadMaterial(mat);
111 }
112 else if (child_name == el_alphamap)
113 {
114 alphamap = child.GetText().FromUTF8();
115 }
116 else if (child_name == el_props)
117 {
118 CTerrainPropertiesPtr ret (new CTerrainProperties(properties));
119 ret->LoadXml(child, &XeroFile, path);
120 if (ret) m_pProperties = ret;
121 }
122 else if (child_name == el_tag)
123 {
124 m_Tag = child.GetText();
125 }
126 }
127
128
129 for (size_t i = 0; i < samplers.size(); ++i)
130 {
131 CTextureProperties texture(samplers[i].second);
132 texture.SetWrap(GL_REPEAT);
133
134 // TODO: anisotropy should probably be user-configurable, but we want it to be
135 // at least 2 for terrain else the ground looks very blurry when you tilt the
136 // camera upwards
137 texture.SetMaxAnisotropy(2.0f);
138
139 if (CRenderer::IsInitialised())
140 {
141 CTexturePtr texptr = g_Renderer.GetTextureManager().CreateTexture(texture);
142 m_Material.AddSampler(CMaterial::TextureSampler(samplers[i].first, texptr));
143 }
144 }
145
146 if (CRenderer::IsInitialised())
147 LoadAlphaMaps(alphamap);
148
149 float texAngle = 0.f;
150 float texSize = 1.f;
151
152 if (m_pProperties)
153 {
154 m_Groups = m_pProperties->GetGroups();
155 texAngle = m_pProperties->GetTextureAngle();
156 texSize = m_pProperties->GetTextureSize();
157 }
158
159 m_TextureMatrix.SetZero();
160 m_TextureMatrix._11 = cosf(texAngle) / texSize;
161 m_TextureMatrix._13 = -sinf(texAngle) / texSize;
162 m_TextureMatrix._21 = -sinf(texAngle) / texSize;
163 m_TextureMatrix._23 = -cosf(texAngle) / texSize;
164 m_TextureMatrix._44 = 1.f;
165
166 GroupVector::iterator it=m_Groups.begin();
167 for (;it!=m_Groups.end();++it)
168 (*it)->AddTerrain(this);
169 }
170
~CTerrainTextureEntry()171 CTerrainTextureEntry::~CTerrainTextureEntry()
172 {
173 for (GroupVector::iterator it=m_Groups.begin();it!=m_Groups.end();++it)
174 (*it)->RemoveTerrain(this);
175 }
176
177 // BuildBaseColor: calculate the root color of the texture, used for coloring minimap, and store
178 // in m_BaseColor member
BuildBaseColor()179 void CTerrainTextureEntry::BuildBaseColor()
180 {
181 // Use the explicit properties value if possible
182 if (m_pProperties && m_pProperties->HasBaseColor())
183 {
184 m_BaseColor=m_pProperties->GetBaseColor();
185 m_BaseColorValid = true;
186 return;
187 }
188
189 // Use the texture color if available
190 if (GetTexture()->TryLoad())
191 {
192 m_BaseColor = GetTexture()->GetBaseColor();
193 m_BaseColorValid = true;
194 }
195 }
196
GetTextureMatrix()197 const float* CTerrainTextureEntry::GetTextureMatrix()
198 {
199 return &m_TextureMatrix._11;
200 }
201
202 // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and
203 // calculate the coordinate of each alphamap within this packed texture
LoadAlphaMaps(VfsPath & amtype)204 void CTerrainTextureEntry::LoadAlphaMaps(VfsPath &amtype)
205 {
206 std::wstring key = L"(alpha map composite" + amtype.string() + L")";
207
208 CTerrainTextureManager::TerrainAlphaMap::iterator it = g_TexMan.m_TerrainAlphas.find(amtype);
209
210 if (it != g_TexMan.m_TerrainAlphas.end())
211 {
212 m_TerrainAlpha = it;
213 return;
214 }
215
216 g_TexMan.m_TerrainAlphas[amtype] = TerrainAlpha();
217 it = g_TexMan.m_TerrainAlphas.find(amtype);
218
219 TerrainAlpha &result = it->second;
220
221 //
222 // load all textures and store Handle in array
223 //
224 Handle textures[NUM_ALPHA_MAPS] = {0};
225 VfsPath path(L"art/textures/terrain/alphamaps");
226 path = path / amtype;
227
228 const wchar_t* fnames[NUM_ALPHA_MAPS] = {
229 L"blendcircle.png",
230 L"blendlshape.png",
231 L"blendedge.png",
232 L"blendedgecorner.png",
233 L"blendedgetwocorners.png",
234 L"blendfourcorners.png",
235 L"blendtwooppositecorners.png",
236 L"blendlshapecorner.png",
237 L"blendtwocorners.png",
238 L"blendcorner.png",
239 L"blendtwoedges.png",
240 L"blendthreecorners.png",
241 L"blendushape.png",
242 L"blendbad.png"
243 };
244 size_t base = 0; // texture width/height (see below)
245 // for convenience, we require all alpha maps to be of the same BPP
246 // (avoids another ogl_tex_get_size call, and doesn't hurt)
247 size_t bpp = 0;
248 for(size_t i=0;i<NUM_ALPHA_MAPS;i++)
249 {
250 // note: these individual textures can be discarded afterwards;
251 // we cache the composite.
252 textures[i] = ogl_tex_load(g_VFS, path / fnames[i]);
253 if (textures[i] < 0)
254 {
255 g_TexMan.m_TerrainAlphas.erase(it);
256 LOGERROR("Failed to load alphamap: %s", amtype.string8());
257
258 VfsPath standard("standard");
259 if (path != standard)
260 {
261 LoadAlphaMaps(standard);
262 }
263 return;
264 }
265
266 // get its size and make sure they are all equal.
267 // (the packing algo assumes this)
268 size_t this_width = 0, this_height = 0, this_bpp = 0; // fail-safe
269 (void)ogl_tex_get_size(textures[i], &this_width, &this_height, &this_bpp);
270 if(this_width != this_height)
271 DEBUG_DISPLAY_ERROR(L"Alpha maps are not square");
272 // .. first iteration: establish size
273 if(i == 0)
274 {
275 base = this_width;
276 bpp = this_bpp;
277 }
278 // .. not first: make sure texture size matches
279 else if(base != this_width || bpp != this_bpp)
280 DEBUG_DISPLAY_ERROR(L"Alpha maps are not identically sized (including pixel depth)");
281 }
282
283 //
284 // copy each alpha map (tile) into one buffer, arrayed horizontally.
285 //
286 size_t tile_w = 2+base+2; // 2 pixel border (avoids bilinear filtering artifacts)
287 size_t total_w = round_up_to_pow2(tile_w * NUM_ALPHA_MAPS);
288 size_t total_h = base; ENSURE(is_pow2(total_h));
289 shared_ptr<u8> data;
290 AllocateAligned(data, total_w*total_h, maxSectorSize);
291 // for each tile on row
292 for (size_t i = 0; i < NUM_ALPHA_MAPS; i++)
293 {
294 // get src of copy
295 u8* src = 0;
296 (void)ogl_tex_get_data(textures[i], &src);
297
298 size_t srcstep = bpp/8;
299
300 // get destination of copy
301 u8* dst = data.get() + (i*tile_w);
302
303 // for each row of image
304 for (size_t j = 0; j < base; j++)
305 {
306 // duplicate first pixel
307 *dst++ = *src;
308 *dst++ = *src;
309
310 // copy a row
311 for (size_t k = 0; k < base; k++)
312 {
313 *dst++ = *src;
314 src += srcstep;
315 }
316
317 // duplicate last pixel
318 *dst++ = *(src-srcstep);
319 *dst++ = *(src-srcstep);
320
321 // advance write pointer for next row
322 dst += total_w-tile_w;
323 }
324
325 result.m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w);
326 result.m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w);
327 result.m_AlphaMapCoords[i].v0 = 0.0f;
328 result.m_AlphaMapCoords[i].v1 = 1.0f;
329 }
330
331 for (size_t i = 0; i < NUM_ALPHA_MAPS; i++)
332 (void)ogl_tex_free(textures[i]);
333
334 // upload the composite texture
335 Tex t;
336 (void)t.wrap(total_w, total_h, 8, TEX_GREY, data, 0);
337
338 // uncomment the following to save a png of the generated texture
339 // in the public/ directory, for debugging
340 /*VfsPath filename("blendtex.png");
341
342 DynArray da;
343 RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da));
344
345 // write to disk
346 //Status ret = INFO::OK;
347 {
348 shared_ptr<u8> file = DummySharedPtr(da.base);
349 const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
350 if(bytes_written > 0)
351 ENSURE(bytes_written == (ssize_t)da.pos);
352 //else
353 // ret = (Status)bytes_written;
354 }
355
356 (void)da_free(&da);*/
357
358 Handle hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key);
359 (void)ogl_tex_set_filter(hCompositeAlphaMap, GL_LINEAR);
360 (void)ogl_tex_set_wrap (hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
361 ogl_tex_upload(hCompositeAlphaMap, GL_ALPHA, 0, 0);
362 result.m_hCompositeAlphaMap = hCompositeAlphaMap;
363
364 m_TerrainAlpha = it;
365 }
366