1 /* Copyright (C) 2012 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 "MessageHandler.h"
21 
22 #include "../CommandProc.h"
23 
24 #include "graphics/Patch.h"
25 #include "graphics/TerrainTextureManager.h"
26 #include "graphics/TerrainTextureEntry.h"
27 #include "graphics/Terrain.h"
28 #include "ps/Game.h"
29 #include "ps/World.h"
30 #include "lib/ogl.h"
31 #include "lib/res/graphics/ogl_tex.h"
32 #include "simulation2/Simulation2.h"
33 #include "simulation2/components/ICmpPathfinder.h"
34 #include "simulation2/components/ICmpTerrain.h"
35 #include "simulation2/helpers/Grid.h"
36 
37 #include "../Brushes.h"
38 #include "../DeltaArray.h"
39 #include "../View.h"
40 
41 #include <queue>
42 
43 namespace AtlasMessage {
44 
QUERYHANDLER(GetTerrainGroups)45 QUERYHANDLER(GetTerrainGroups)
46 {
47 	const CTerrainTextureManager::TerrainGroupMap &groups = g_TexMan.GetGroups();
48 	std::vector<std::wstring> groupNames;
49 	for (CTerrainTextureManager::TerrainGroupMap::const_iterator it = groups.begin(); it != groups.end(); ++it)
50 		groupNames.push_back(it->first.FromUTF8());
51 	msg->groupNames = groupNames;
52 }
53 
CompareTerrain(const sTerrainTexturePreview & a,const sTerrainTexturePreview & b)54 static bool CompareTerrain(const sTerrainTexturePreview& a, const sTerrainTexturePreview& b)
55 {
56 	return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
57 }
58 
GetPreview(CTerrainTextureEntry * tex,int width,int height)59 static sTerrainTexturePreview GetPreview(CTerrainTextureEntry* tex, int width, int height)
60 {
61 	sTerrainTexturePreview preview;
62 	preview.name = tex->GetTag().FromUTF8();
63 
64 	std::vector<unsigned char> buf (width*height*3);
65 
66 #if !CONFIG2_GLES
67 	// It's not good to shrink the entire texture to fit the small preview
68 	// window, since it's the fine details in the texture that are
69 	// interesting; so just go down one mipmap level, then crop a chunk
70 	// out of the middle.
71 
72 	// Read the size of the texture. (Usually loads the texture from
73 	// disk, which is slow.)
74 	tex->GetTexture()->Bind();
75 	int level = 1; // level 0 is the original size
76 	int w = std::max(1, (int)tex->GetTexture()->GetWidth() >> level);
77 	int h = std::max(1, (int)tex->GetTexture()->GetHeight() >> level);
78 
79 	if (w >= width && h >= height)
80 	{
81 		// Read the whole texture into a new buffer
82 		unsigned char* texdata = new unsigned char[w*h*3];
83 		glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
84 
85 		// Extract the middle section (as a representative preview),
86 		// and copy into buf
87 		unsigned char* texdata_ptr = texdata + (w*(h - height)/2 + (w - width)/2) * 3;
88 		unsigned char* buf_ptr = &buf[0];
89 		for (ssize_t y = 0; y < height; ++y)
90 		{
91 			memcpy(buf_ptr, texdata_ptr, width*3);
92 			buf_ptr += width*3;
93 			texdata_ptr += w*3;
94 		}
95 
96 		delete[] texdata;
97 	}
98 	else
99 #endif
100 	{
101 		// Too small to preview, or glGetTexImage not supported (on GLES)
102 		// Just use a flat color instead
103 		u32 c = tex->GetBaseColor();
104 		for (ssize_t i = 0; i < width*height; ++i)
105 		{
106 			buf[i*3+0] = (c>>16) & 0xff;
107 			buf[i*3+1] = (c>>8) & 0xff;
108 			buf[i*3+2] = (c>>0) & 0xff;
109 		}
110 	}
111 
112 	preview.loaded = tex->GetTexture()->IsLoaded();
113 	preview.imageWidth = width;
114 	preview.imageHeight = height;
115 	preview.imageData = buf;
116 
117 	return preview;
118 }
119 
QUERYHANDLER(GetTerrainGroupPreviews)120 QUERYHANDLER(GetTerrainGroupPreviews)
121 {
122 	std::vector<sTerrainTexturePreview> previews;
123 
124 	CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
125 	for (std::vector<CTerrainTextureEntry*>::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
126 	{
127 		previews.push_back(GetPreview(*it, msg->imageWidth, msg->imageHeight));
128  	}
129 
130 	// Sort the list alphabetically by name
131 	std::sort(previews.begin(), previews.end(), CompareTerrain);
132 	msg->previews = previews;
133 }
134 
QUERYHANDLER(GetTerrainPassabilityClasses)135 QUERYHANDLER(GetTerrainPassabilityClasses)
136 {
137 	CmpPtr<ICmpPathfinder> cmpPathfinder(*AtlasView::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
138 	if (cmpPathfinder)
139 	{
140 		std::map<std::string, pass_class_t> nonPathfindingClasses, pathfindingClasses;
141 		cmpPathfinder->GetPassabilityClasses(nonPathfindingClasses, pathfindingClasses);
142 
143 		std::vector<std::wstring> classNames;
144 		for (std::map<std::string, pass_class_t>::iterator it = nonPathfindingClasses.begin(); it != nonPathfindingClasses.end(); ++it)
145 			classNames.push_back(CStr(it->first).FromUTF8());
146 		msg->classNames = classNames;
147 	}
148 }
149 
QUERYHANDLER(GetTerrainTexture)150 QUERYHANDLER(GetTerrainTexture)
151 {
152 	ssize_t x, y;
153 	g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
154 	g_CurrentBrush.GetCentre(x, y);
155 
156 	CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
157 	CMiniPatch* tile = terrain->GetTile(x, y);
158 	if (tile)
159 	{
160 		CTerrainTextureEntry* tex = tile->GetTextureEntry();
161 		msg->texture = tex->GetTag().FromUTF8();
162 	}
163 	else
164 	{
165 		msg->texture = std::wstring();
166 	}
167 }
168 
QUERYHANDLER(GetTerrainTexturePreview)169 QUERYHANDLER(GetTerrainTexturePreview)
170 {
171 	CTerrainTextureEntry* tex = g_TexMan.FindTexture(CStrW(*msg->name).ToUTF8());
172 	if (tex)
173 	{
174 		msg->preview = GetPreview(tex, msg->imageWidth, msg->imageHeight);
175 	}
176 	else
177 	{
178 		sTerrainTexturePreview noPreview;
179 		noPreview.name = std::wstring();
180 		noPreview.imageHeight = 0;
181 		noPreview.imageWidth = 0;
182 		msg->preview = noPreview;
183 	}
184 }
185 
186 //////////////////////////////////////////////////////////////////////////
187 
188 namespace {
189 
190 struct TerrainTile
191 {
TerrainTileAtlasMessage::__anon0b6003660111::TerrainTile192 	TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {}
193 	CTerrainTextureEntry* tex;
194 	ssize_t priority;
195 };
196 
197 class TerrainArray : public DeltaArray2D<TerrainTile>
198 {
199 public:
Init()200 	void Init()
201 	{
202 		m_Terrain = g_Game->GetWorld()->GetTerrain();
203 		m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
204 	}
205 
UpdatePriority(ssize_t x,ssize_t y,CTerrainTextureEntry * tex,ssize_t priorityScale,ssize_t & priority)206 	void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority)
207 	{
208 		CMiniPatch* tile = m_Terrain->GetTile(x, y);
209 		if (!tile)
210 			return; // tile was out-of-bounds
211 
212 		// If this tile matches the current texture, we just want to match its
213 		// priority; otherwise we want to exceed its priority
214 		if (tile->GetTextureEntry() == tex)
215 			priority = std::max(priority, tile->GetPriority()*priorityScale);
216 		else
217 			priority = std::max(priority, tile->GetPriority()*priorityScale + 1);
218 	}
219 
GetTexEntry(ssize_t x,ssize_t y)220 	CTerrainTextureEntry* GetTexEntry(ssize_t x, ssize_t y)
221 	{
222 		if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
223 			return NULL;
224 
225 		return get(x, y).tex;
226 	}
227 
GetPriority(ssize_t x,ssize_t y)228 	ssize_t GetPriority(ssize_t x, ssize_t y)
229 	{
230 		if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
231 			return 0;
232 
233 		return get(x, y).priority;
234 	}
235 
PaintTile(ssize_t x,ssize_t y,CTerrainTextureEntry * tex,ssize_t priority)236 	void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority)
237 	{
238 		// Ignore out-of-bounds tiles
239 		if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
240 			return;
241 
242 		set(x,y, TerrainTile(tex, priority));
243 	}
244 
GetTilesPerSide()245 	ssize_t GetTilesPerSide()
246 	{
247 		return m_VertsPerSide-1;
248 	}
249 
250 protected:
getOld(ssize_t x,ssize_t y)251 	TerrainTile getOld(ssize_t x, ssize_t y)
252 	{
253 		CMiniPatch* mp = m_Terrain->GetTile(x, y);
254 		ENSURE(mp);
255 		return TerrainTile(mp->Tex, mp->Priority);
256 	}
setNew(ssize_t x,ssize_t y,const TerrainTile & val)257 	void setNew(ssize_t x, ssize_t y, const TerrainTile& val)
258 	{
259 		CMiniPatch* mp = m_Terrain->GetTile(x, y);
260 		ENSURE(mp);
261 		mp->Tex = val.tex;
262 		mp->Priority = val.priority;
263 	}
264 
265 	CTerrain* m_Terrain;
266 	ssize_t m_VertsPerSide;
267 };
268 
269 }
270 
BEGIN_COMMAND(PaintTerrain)271 BEGIN_COMMAND(PaintTerrain)
272 {
273 	TerrainArray m_TerrainDelta;
274 	ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
275 
276 	cPaintTerrain()
277 	{
278 		m_TerrainDelta.Init();
279 	}
280 
281 	void MakeDirty()
282 	{
283 		g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
284 	}
285 
286 	void Do()
287 	{
288 		g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
289 
290 		ssize_t x0, y0;
291 		g_CurrentBrush.GetBottomLeft(x0, y0);
292 
293 		CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
294 		if (! texentry)
295 		{
296 			debug_warn(L"Can't find texentry"); // TODO: nicer error handling
297 			return;
298 		}
299 
300 		// Priority system: If the new tile should have a high priority,
301 		// set it to one plus the maximum priority of all surrounding tiles
302 		// that aren't included in the brush (so that it's definitely the highest).
303 		// Similar for low priority.
304 		ssize_t priorityScale = (msg->priority == ePaintTerrainPriority::HIGH ? +1 : -1);
305 		ssize_t priority = 0;
306 
307 		for (ssize_t dy = -1; dy < g_CurrentBrush.m_H+1; ++dy)
308 		{
309 			for (ssize_t dx = -1; dx < g_CurrentBrush.m_W+1; ++dx)
310 			{
311 				if (!(g_CurrentBrush.Get(dx, dy) > 0.5f)) // ignore tiles that will be painted over
312 					m_TerrainDelta.UpdatePriority(x0+dx, y0+dy, texentry, priorityScale, priority);
313 			}
314 		}
315 
316 		for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
317 		{
318 			for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
319 			{
320 				if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
321 					m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, priority*priorityScale);
322 			}
323 		}
324 
325 		m_i0 = x0 - 1;
326 		m_j0 = y0 - 1;
327 		m_i1 = x0 + g_CurrentBrush.m_W + 1;
328 		m_j1 = y0 + g_CurrentBrush.m_H + 1;
329 		MakeDirty();
330 	}
331 
332 	void Undo()
333 	{
334 		m_TerrainDelta.Undo();
335 		MakeDirty();
336 	}
337 
338 	void Redo()
339 	{
340 		m_TerrainDelta.Redo();
341 		MakeDirty();
342 	}
343 
344 	void MergeIntoPrevious(cPaintTerrain* prev)
345 	{
346 		prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
347 		prev->m_i0 = std::min(prev->m_i0, m_i0);
348 		prev->m_j0 = std::min(prev->m_j0, m_j0);
349 		prev->m_i1 = std::max(prev->m_i1, m_i1);
350 		prev->m_j1 = std::max(prev->m_j1, m_j1);
351 	}
352 };
353 END_COMMAND(PaintTerrain)
354 
355 //////////////////////////////////////////////////////////////////////////
356 
BEGIN_COMMAND(ReplaceTerrain)357 BEGIN_COMMAND(ReplaceTerrain)
358 {
359 	TerrainArray m_TerrainDelta;
360 	ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
361 
362 	cReplaceTerrain()
363 	{
364 		m_TerrainDelta.Init();
365 	}
366 
367 	void MakeDirty()
368 	{
369 		g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
370 		CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
371 		if (cmpTerrain)
372 			cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
373 	}
374 
375 	void Do()
376 	{
377 		g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
378 
379 		ssize_t x0, y0;
380 		g_CurrentBrush.GetBottomLeft(x0, y0);
381 
382 		m_i0 = m_i1 = x0;
383 		m_j0 = m_j1 = y0;
384 
385 		CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
386 		if (! texentry)
387 		{
388 			debug_warn(L"Can't find texentry"); // TODO: nicer error handling
389 			return;
390 		}
391 
392 		CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
393 
394 		// Don't bother if we're not making a change
395 		if (texentry == replacedTex)
396 		{
397 			return;
398 		}
399 
400 		ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
401 
402 		for (ssize_t j = 0; j < tiles; ++j)
403 		{
404 			for (ssize_t i = 0; i < tiles; ++i)
405 			{
406 				if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
407 				{
408 					m_i0 = std::min(m_i0, i-1);
409 					m_j0 = std::min(m_j0, j-1);
410 					m_i1 = std::max(m_i1, i+2);
411 					m_j1 = std::max(m_j1, j+2);
412 					m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
413 				}
414 			}
415 		}
416 
417 		MakeDirty();
418 	}
419 
420 	void Undo()
421 	{
422 		m_TerrainDelta.Undo();
423 		MakeDirty();
424 	}
425 
426 	void Redo()
427 	{
428 		m_TerrainDelta.Redo();
429 		MakeDirty();
430 	}
431 };
432 END_COMMAND(ReplaceTerrain)
433 
434 //////////////////////////////////////////////////////////////////////////
435 
BEGIN_COMMAND(FillTerrain)436 BEGIN_COMMAND(FillTerrain)
437 {
438 	TerrainArray m_TerrainDelta;
439 	ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
440 
441 	cFillTerrain()
442 	{
443 		m_TerrainDelta.Init();
444 	}
445 
446 	void MakeDirty()
447 	{
448 		g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
449 		CmpPtr<ICmpTerrain> cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
450 		if (cmpTerrain)
451 			cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
452 	}
453 
454 	void Do()
455 	{
456 		g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
457 
458 		ssize_t x0, y0;
459 		g_CurrentBrush.GetBottomLeft(x0, y0);
460 
461 		m_i0 = m_i1 = x0;
462 		m_j0 = m_j1 = y0;
463 
464 		CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
465 		if (! texentry)
466 		{
467 			debug_warn(L"Can't find texentry"); // TODO: nicer error handling
468 			return;
469 		}
470 
471 		CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
472 
473 		// Don't bother if we're not making a change
474 		if (texentry == replacedTex)
475 		{
476 			return;
477 		}
478 
479 		ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
480 
481 		// Simple 4-way flood fill algorithm using queue and a grid to keep track of visited tiles,
482 		//	almost as fast as loop for filling whole map, much faster for small patches
483 		SparseGrid<bool> visited(tiles, tiles);
484 		std::queue<std::pair<u16, u16> > queue;
485 
486 		// Initial tile
487 		queue.push(std::make_pair((u16)x0, (u16)y0));
488 		visited.set(x0, y0, true);
489 
490 		while(!queue.empty())
491 		{
492 			// Check front of queue
493 			std::pair<u16, u16> t = queue.front();
494 			queue.pop();
495 			u16 i = t.first;
496 			u16 j = t.second;
497 
498 			if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
499 			{
500 				// Found a tile to replace: adjust bounds and paint it
501 				m_i0 = std::min(m_i0, (ssize_t)i-1);
502 				m_j0 = std::min(m_j0, (ssize_t)j-1);
503 				m_i1 = std::max(m_i1, (ssize_t)i+2);
504 				m_j1 = std::max(m_j1, (ssize_t)j+2);
505 				m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
506 
507 				// Visit 4 adjacent tiles (could visit 8 if we want to count diagonal adjacency)
508 				if (i > 0 && !visited.get(i-1, j))
509 				{
510 					visited.set(i-1, j, true);
511 					queue.push(std::make_pair(i-1, j));
512 				}
513 				if (i < (tiles-1) && !visited.get(i+1, j))
514 				{
515 					visited.set(i+1, j, true);
516 					queue.push(std::make_pair(i+1, j));
517 				}
518 				if (j > 0 && !visited.get(i, j-1))
519 				{
520 					visited.set(i, j-1, true);
521 					queue.push(std::make_pair(i, j-1));
522 				}
523 				if (j < (tiles-1) && !visited.get(i, j+1))
524 				{
525 					visited.set(i, j+1, true);
526 					queue.push(std::make_pair(i, j+1));
527 				}
528 			}
529 		}
530 
531 		MakeDirty();
532 	}
533 
534 	void Undo()
535 	{
536 		m_TerrainDelta.Undo();
537 		MakeDirty();
538 	}
539 
540 	void Redo()
541 	{
542 		m_TerrainDelta.Redo();
543 		MakeDirty();
544 	}
545 };
546 END_COMMAND(FillTerrain)
547 
548 }
549