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