1 // Copyright 2013-2018 the openage authors. See copying.md for legal info.
2 
3 #include "terrain.h"
4 
5 #include <cmath>
6 #include <memory>
7 #include <set>
8 #include <unordered_map>
9 
10 #include "../log/log.h"
11 #include "../error/error.h"
12 #include "../engine.h"
13 #include "../game_renderer.h"
14 #include "../coord/pixel.h"
15 #include "../coord/chunk.h"
16 #include "../coord/tile.h"
17 #include "../util/misc.h"
18 #include "../util/strings.h"
19 
20 #include "terrain_chunk.h"
21 #include "terrain_object.h"
22 
23 namespace openage {
24 
TileContent()25 TileContent::TileContent() :
26 	terrain_id{0} {
27 }
28 
~TileContent()29 TileContent::~TileContent() {}
30 
Terrain(terrain_meta * meta,bool is_infinite)31 Terrain::Terrain(terrain_meta *meta, bool is_infinite)
32 	:
33 	infinite{is_infinite},
34 	meta{meta} {
35 
36 	// TODO:
37 	//this->limit_positive =
38 	//this->limit_negative =
39 
40 	// maps chunk position to chunks
41 	this->chunks = std::unordered_map<coord::chunk, TerrainChunk *, coord_chunk_hash>{};
42 
43 }
44 
~Terrain()45 Terrain::~Terrain() {
46 	log::log(MSG(dbg) << "Cleanup terrain");
47 
48 	for (auto &chunk : this->chunks) {
49 		// this chunk was autogenerated, so clean it up
50 		if (chunk.second->manually_created == false) {
51 			delete chunk.second;
52 		}
53 	}
54 }
55 
used_chunks() const56 std::vector<coord::chunk> Terrain::used_chunks() const {
57 	std::vector<coord::chunk> result;
58 	for (auto &c : chunks) {
59 		result.push_back(c.first);
60 	}
61 	return result;
62 }
63 
fill(const int * data,const coord::tile_delta & size)64 bool Terrain::fill(const int *data, const coord::tile_delta &size) {
65 	bool was_cut = false;
66 
67 	coord::tile pos = {0, 0};
68 	for (; pos.ne < size.ne; pos.ne++) {
69 		for (pos.se = 0; pos.se < size.se; pos.se++) {
70 			if (this->check_tile(pos) == tile_state::invalid) {
71 				was_cut = true;
72 				continue;
73 			}
74 			int terrain_id = data[pos.ne * size.ne + pos.se];
75 			TerrainChunk *chunk = this->get_create_chunk(pos);
76 			chunk->get_data(pos)->terrain_id = terrain_id;
77 		}
78 	}
79 	return was_cut;
80 }
81 
attach_chunk(TerrainChunk * new_chunk,const coord::chunk & position,bool manually_created)82 void Terrain::attach_chunk(TerrainChunk *new_chunk,
83                            const coord::chunk &position,
84                            bool manually_created) {
85 	new_chunk->set_terrain(this);
86 	new_chunk->manually_created = manually_created;
87 	log::log(MSG(dbg) << "Inserting new chunk at (" << position.ne << "," << position.se << ")");
88 	this->chunks[position] = new_chunk;
89 
90 	struct chunk_neighbors neigh = this->get_chunk_neighbors(position);
91 	for (int i = 0; i < 8; i++) {
92 		TerrainChunk *neighbor = neigh.neighbor[i];
93 		if (neighbor != nullptr) {
94 			//set the new chunks neighbor to the neighbor chunk
95 			new_chunk->neighbors.neighbor[i] = neighbor;
96 
97 			//set the neighbors neighbor on the opposite direction
98 			//to the new chunk
99 			neighbor->neighbors.neighbor[(i+4) % 8] = new_chunk;
100 
101 			log::log(MSG(dbg) << "Neighbor " << i << " gets notified of new neighbor.");
102 		}
103 		else {
104 			log::log(MSG(dbg) << "Neighbor " << i << " not found.");
105 		}
106 	}
107 }
108 
get_chunk(const coord::chunk & position)109 TerrainChunk *Terrain::get_chunk(const coord::chunk &position) {
110 	auto iter = this->chunks.find(position);
111 
112 	if (iter == this->chunks.end()) {
113 		return nullptr;
114 	}
115 	else {
116 		return iter->second;
117 	}
118 }
119 
get_chunk(const coord::tile & position)120 TerrainChunk *Terrain::get_chunk(const coord::tile &position) {
121 	return this->get_chunk(position.to_chunk());
122 }
123 
get_create_chunk(const coord::chunk & position)124 TerrainChunk *Terrain::get_create_chunk(const coord::chunk &position) {
125 	TerrainChunk *res = this->get_chunk(position);
126 	if (res == nullptr) {
127 		res = new TerrainChunk();
128 		this->attach_chunk(res, position, false);
129 	}
130 	return res;
131 }
132 
get_create_chunk(const coord::tile & position)133 TerrainChunk *Terrain::get_create_chunk(const coord::tile &position) {
134 	return this->get_create_chunk(position.to_chunk());
135 }
136 
get_data(const coord::tile & position)137 TileContent *Terrain::get_data(const coord::tile &position) {
138 	TerrainChunk *c = this->get_chunk(position.to_chunk());
139 	if (c == nullptr) {
140 		return nullptr;
141 	} else {
142 		return c->get_data(position.get_pos_on_chunk());
143 	}
144 }
145 
obj_at_point(const coord::phys3 & point)146 TerrainObject *Terrain::obj_at_point(const coord::phys3 &point) {
147 	coord::tile t = point.to_tile();
148 	TileContent *tc = this->get_data(t);
149 	if (!tc) {
150 		return nullptr;
151 	}
152 
153 	// prioritise selecting the smallest object
154 	TerrainObject *smallest = nullptr;
155 	for (auto obj_ptr : tc->obj) {
156 		if (obj_ptr->contains(point) &&
157 		    (!smallest || obj_ptr->min_axis() < smallest->min_axis())) {
158 			smallest = obj_ptr;
159 		}
160 	}
161 	return smallest;
162 }
163 
validate_terrain(terrain_t terrain_id)164 bool Terrain::validate_terrain(terrain_t terrain_id) {
165 	if (terrain_id >= (ssize_t)this->meta->terrain_id_count) {
166 		throw Error(MSG(err) << "Requested terrain_id is out of range: " << terrain_id);
167 	}
168 	else {
169 		return true;
170 	}
171 }
172 
validate_mask(ssize_t mask_id)173 bool Terrain::validate_mask(ssize_t mask_id) {
174 	if (mask_id >= (ssize_t)this->meta->blendmode_count) {
175 		throw Error(MSG(err) << "Requested mask_id is out of range: " << mask_id);
176 	}
177 	else {
178 		return true;
179 	}
180 }
181 
priority(terrain_t terrain_id)182 int Terrain::priority(terrain_t terrain_id) {
183 	this->validate_terrain(terrain_id);
184 	return this->meta->terrain_id_priority_map[terrain_id];
185 }
186 
blendmode(terrain_t terrain_id)187 int Terrain::blendmode(terrain_t terrain_id) {
188 	this->validate_terrain(terrain_id);
189 	return this->meta->terrain_id_blendmode_map[terrain_id];
190 }
191 
texture(terrain_t terrain_id)192 Texture *Terrain::texture(terrain_t terrain_id) {
193 	this->validate_terrain(terrain_id);
194 	return this->meta->textures[terrain_id];
195 }
196 
blending_mask(ssize_t mask_id)197 Texture *Terrain::blending_mask(ssize_t mask_id) {
198 	this->validate_mask(mask_id);
199 	return this->meta->blending_masks[mask_id];
200 }
201 
get_subtexture_id(const coord::tile & pos,unsigned atlas_size)202 unsigned Terrain::get_subtexture_id(const coord::tile &pos, unsigned atlas_size) {
203 	unsigned result = 0;
204 
205 	result += util::mod<coord::tile_t>(pos.se, atlas_size);
206 	result *= atlas_size;
207 	result += util::mod<coord::tile_t>(pos.ne, atlas_size);
208 
209 	return result;
210 }
211 
get_chunk_neighbors(const coord::chunk & position)212 struct chunk_neighbors Terrain::get_chunk_neighbors(const coord::chunk &position) {
213 	struct chunk_neighbors ret;
214 
215 	for (int i = 0; i < 8; i++) {
216 		coord::chunk tmp {
217 			position.ne + (coord::chunk_t) neigh_offsets[i].ne,
218 			position.se + (coord::chunk_t) neigh_offsets[i].se
219 		};
220 		ret.neighbor[i] = this->get_chunk(tmp);
221 	}
222 
223 	return ret;
224 }
225 
get_blending_mode(terrain_t base_id,terrain_t neighbor_id)226 int Terrain::get_blending_mode(terrain_t base_id, terrain_t neighbor_id) {
227 	/*
228 	 * this function may require much more code, but this simple
229 	 * magnitude comparison seems to do the job.
230 	 * feel free to confirm or fix the behavior.
231 	 *
232 	 * my guess is that the blending mode encodes another information
233 	 * not publicly noticed yet: the overlay priority.
234 	 * the higher the blendmode id, the higher the mode priority.
235 	 * this may also be the reason why there are mask duplicates
236 	 * in blendomatic.dat
237 	 *
238 	 * funny enough, just using the modes in the dat file lead
239 	 * to a totally wrong render. the convert script reassigns the
240 	 * blending modes with a simple key=>val mapping,
241 	 * and after that, it looks perfect.
242 	 */
243 
244 	int base_mode     = this->blendmode(base_id);
245 	int neighbor_mode = this->blendmode(neighbor_id);
246 
247 	if (neighbor_mode > base_mode) {
248 		return neighbor_mode;
249 	} else {
250 		return base_mode;
251 	}
252 }
253 
check_tile(const coord::tile & position)254 tile_state Terrain::check_tile(const coord::tile &position) {
255 	if (this->check_tile_position(position) == false) {
256 		return tile_state::invalid;
257 	}
258 	else {
259 		TerrainChunk *chunk = this->get_chunk(position);
260 		if (chunk == nullptr) {
261 			return tile_state::creatable;
262 		}
263 		else {
264 			return tile_state::existing;
265 		}
266 	}
267 }
268 
check_tile_position(const coord::tile &)269 bool Terrain::check_tile_position(const coord::tile &/*pos*/) {
270 	if (this->infinite == true) {
271 		return true;
272 	}
273 	else {
274 		throw Error(ERR << "non-infinite terrains are not supported yet");
275 	}
276 }
277 
draw(Engine * engine,RenderOptions * settings)278 void Terrain::draw(Engine *engine, RenderOptions *settings) {
279 	// TODO: move this draw invokation to a render manager.
280 	//       it can reorder the draw instructions and minimize texture switching.
281 
282 	// query the window coordinates from the engine first
283 	coord::viewport wbl = coord::viewport{0, 0};
284 	coord::viewport wbr = coord::viewport{engine->coord.viewport_size.x, 0};
285 	coord::viewport wtl = coord::viewport{0, engine->coord.viewport_size.y};
286 	coord::viewport wtr = coord::viewport{engine->coord.viewport_size.x, engine->coord.viewport_size.y};
287 
288 	// top left, bottom right tile coordinates
289 	// that are currently visible in the window
290 	// then convert them to tile coordinates.
291 	coord::tile tl = wtl.to_tile(engine->coord);
292 	coord::tile tr = wtr.to_tile(engine->coord);
293 	coord::tile bl = wbl.to_tile(engine->coord);
294 	coord::tile br = wbr.to_tile(engine->coord);
295 
296 	// main terrain calculation call: get the `terrain_render_data`
297 	auto draw_data = this->create_draw_advice(tl, tr, br, bl, settings->terrain_blending.value);
298 
299 	// TODO: the following loop is totally inefficient and shit.
300 	//       it reloads the drawing texture to the gpu FOR EACH TILE!
301 	//       nevertheless, currently it works.
302 
303 	// draw the terrain ground
304 	for (auto &tile : draw_data.tiles) {
305 
306 		// iterate over all layers to be drawn
307 		for (int i = 0; i < tile.count; i++) {
308 			struct tile_data *layer = &tile.data[i];
309 
310 			// position, where the tile is drawn
311 			coord::tile tile_pos = layer->pos;
312 
313 			int      mask_id       = layer->mask_id;
314 			Texture *texture       = layer->tex;
315 			int      subtexture_id = layer->subtexture_id;
316 			Texture *mask_texture  = layer->mask_tex;
317 
318 			texture->draw(engine->coord, *this, tile_pos, ALPHAMASKED, subtexture_id, mask_texture, mask_id);
319 		}
320 	}
321 
322 	// TODO: drawing buildings can't be the job of the terrain..
323 	// draw the buildings
324 	for (auto &object : draw_data.objects) {
325 		object->draw(*engine);
326 	}
327 }
328 
create_draw_advice(const coord::tile & ab,const coord::tile & cd,const coord::tile & ef,const coord::tile & gh,bool blending_enabled)329 struct terrain_render_data Terrain::create_draw_advice(const coord::tile &ab,
330                                                        const coord::tile &cd,
331                                                        const coord::tile &ef,
332                                                        const coord::tile &gh,
333                                                        bool blending_enabled) {
334 
335 	/*
336 	 * The passed parameters define the screen corners.
337 	 *
338 	 *    ne, se coordinates
339 	 *    o = screen corner, where the tile coordinates can be queried.
340 	 *    x = corner of the rhombus that will be drawn, calculated by all o.
341 	 *
342 	 *                  cb
343 	 *                   x
344 	 *                 .   .
345 	 *               .       .
346 	 *          ab o===========o cd
347 	 *           . =  visible  = .
348 	 *      gb x   =  screen   =   x cf
349 	 *           . =           = .
350 	 *          gh o===========o ef
351 	 *               .       .
352 	 *                 .   .
353 	 *                   x
354 	 *                  gf
355 	 *
356 	 * The rendering area may be optimized further in the future,
357 	 * to exactly fit the visible screen.
358 	 * For now, we are drawing the big rhombus.
359 	 */
360 
361 	// procedure: find all the tiles to be drawn
362 	// and store them to a tile drawing instruction structure
363 	struct terrain_render_data data;
364 
365 	// vector of tiles to be drawn
366 	std::vector<struct tile_draw_data> *tiles = &data.tiles;
367 
368 	// ordered set of objects on the terrain (buildings.)
369 	// it's ordered by the visibility layers.
370 	auto objects = &data.objects;
371 
372 	coord::tile gb = {gh.ne, ab.se};
373 	coord::tile cf = {cd.ne, ef.se};
374 
375 	// hint the vector about the number of tiles it will contain
376 	size_t tiles_count = std::abs(cf.ne - gb.ne) * std::abs(cf.se - gb.se);
377 	tiles->reserve(tiles_count);
378 
379 	// sweep the whole rhombus area
380 	for (coord::tile tilepos = gb; tilepos.ne <= cf.ne; tilepos.ne++) {
381 		for (tilepos.se = gb.se; tilepos.se <= cf.se; tilepos.se++) {
382 
383 			// get the terrain tile drawing data
384 			auto tile = this->create_tile_advice(tilepos, blending_enabled);
385 			tiles->push_back(tile);
386 
387 			// get the object standing on the tile
388 			// TODO: make the terrain independent of objects standing on it.
389 			TileContent *tile_content = this->get_data(tilepos);
390 			if (tile_content != nullptr) {
391 				for (auto obj_item : tile_content->obj) {
392 					objects->insert(obj_item);
393 				}
394 			}
395 		}
396 	}
397 
398 	return data;
399 }
400 
401 
create_tile_advice(coord::tile position,bool blending_enabled)402 struct tile_draw_data Terrain::create_tile_advice(coord::tile position, bool blending_enabled) {
403 	// this struct will be filled with all tiles and overlays to draw.
404 	struct tile_draw_data tile;
405 	tile.count = 0;
406 
407 	TileContent *base_tile_content = this->get_data(position);
408 
409 	// chunk of this tile does not exist
410 	if (base_tile_content == nullptr) {
411 		return tile;
412 	}
413 
414 	struct tile_data base_tile_data;
415 
416 	// the base terrain id of the tile
417 	base_tile_data.terrain_id = base_tile_content->terrain_id;
418 
419 	// the base terrain is not existant.
420 	if (base_tile_data.terrain_id < 0) {
421 		return tile;
422 	}
423 
424 	this->validate_terrain(base_tile_data.terrain_id);
425 
426 	Texture *tex = this->texture(base_tile_data.terrain_id);
427 
428 	base_tile_data.state         = tile_state::existing;
429 	base_tile_data.pos           = position;
430 	base_tile_data.priority      = this->priority(base_tile_data.terrain_id);
431 	base_tile_data.tex           = tex;
432 	base_tile_data.subtexture_id = this->get_subtexture_id(
433 		position,
434 		std::sqrt(tex->get_subtexture_count())
435 	);
436 	base_tile_data.blend_mode    = -1;
437 	base_tile_data.mask_tex      = nullptr;
438 	base_tile_data.mask_id       = -1;
439 
440 	tile.data[tile.count] = base_tile_data;
441 	tile.count += 1;
442 
443 	// blendomatic!!111
444 	//  see doc/media/blendomatic for the idea behind this.
445 	if (blending_enabled) {
446 
447 		// the neighbors of the base tile
448 		struct neighbor_tile neigh_data[8];
449 
450 		// get all neighbor tiles around position, reset the influence directions.
451 		this->get_neighbors(position, neigh_data, this->meta->influences_buf.get());
452 
453 		// create influence list (direction, priority)
454 		// strip and order influences, get the final influence data structure
455 		struct influence_group influence_group = this->calculate_influences(
456 			&base_tile_data, neigh_data,
457 			this->meta->influences_buf.get()
458 		);
459 
460 		// create the draw_masks from the calculated influences
461 		this->calculate_masks(position, &tile, &influence_group);
462 	}
463 
464 	return tile;
465 }
466 
get_neighbors(coord::tile basepos,neighbor_tile * neigh_data,influence * influences_by_terrain_id)467 void Terrain::get_neighbors(coord::tile basepos,
468                             neighbor_tile *neigh_data,
469                             influence *influences_by_terrain_id) {
470 
471 	// walk over all given neighbor tiles and store them to the influence list,
472 	// group them by terrain id.
473 
474 	for (int neigh_id = 0; neigh_id < 8; neigh_id++) {
475 
476 		// the current neighbor
477 		auto neighbor = &neigh_data[neigh_id];
478 
479 		// calculate the pos of the neighbor tile
480 		coord::tile neigh_pos = basepos + neigh_offsets[neigh_id];
481 
482 		// get the neighbor data
483 		TileContent *neigh_content = this->get_data(neigh_pos);
484 
485 		// chunk for neighbor or single tile is not existant
486 		if (neigh_content == nullptr || neigh_content->terrain_id < 0) {
487 			neighbor->state = tile_state::missing;
488 		}
489 		else {
490 			neighbor->terrain_id = neigh_content->terrain_id;
491 			neighbor->state      = tile_state::existing;
492 			neighbor->priority   = this->priority(neighbor->terrain_id);
493 
494 			// reset influence directions for this tile
495 			influences_by_terrain_id[neighbor->terrain_id].direction = 0;
496 		}
497 	}
498 }
499 
calculate_influences(struct tile_data * base_tile,struct neighbor_tile * neigh_data,struct influence * influences_by_terrain_id)500 struct influence_group Terrain::calculate_influences(struct tile_data *base_tile,
501                                                      struct neighbor_tile *neigh_data,
502                                                      struct influence *influences_by_terrain_id) {
503 	// influences to actually draw (-> maximum 8)
504 	struct influence_group influences;
505 	influences.count = 0;
506 
507 	// process adjacent neighbors first,
508 	// then add diagonal influences, if no adjacent influence was found
509 	constexpr int neigh_id_lookup[] = {1, 3, 5, 7, 0, 2, 4, 6};
510 
511 	for (int i = 0; i < 8; i++) {
512 		// diagonal neighbors: (neigh_id % 2) == 0
513 		// adjacent neighbors: (neigh_id % 2) == 1
514 
515 		int neigh_id = neigh_id_lookup[i];
516 		bool is_adjacent_neighbor = neigh_id % 2 == 1;
517 		bool is_diagonal_neighbor = not is_adjacent_neighbor;
518 
519 		// the current neighbor_tile.
520 		auto neighbor = &neigh_data[neigh_id];
521 
522 		// neighbor is nonexistant
523 		if (neighbor->state == tile_state::missing) {
524 			continue;
525 		}
526 
527 		// neighbor only interesting if it's a different terrain than the base.
528 		// if it is the same id, the priorities are equal.
529 		// neighbor draws over the base if it's priority is greater.
530 		if (neighbor->priority > base_tile->priority) {
531 
532 			// get influence storage for the neighbor terrain id
533 			// to group influences by id
534 			auto influence = &influences_by_terrain_id[neighbor->terrain_id];
535 
536 			// check if diagonal influence is valid
537 			if (is_diagonal_neighbor) {
538 				// get the adjacent neighbors to the current diagonal
539 				// influence
540 				//  (a & 0x07) == (a % 8)
541 				uint8_t adj_neigh_0 = (neigh_id - 1) & 0x07;
542 				uint8_t adj_neigh_1 = (neigh_id + 1) & 0x07;
543 
544 				uint8_t neigh_mask = (1 << adj_neigh_0) | (1 << adj_neigh_1);
545 
546 				// the adjacent neigbors are already influencing
547 				// the current tile, therefore don't apply the diagonal mask
548 				if ((influence->direction & neigh_mask) != 0) {
549 					continue;
550 				}
551 			}
552 
553 			// this terrain id hasn't had influence so far:
554 			// add it to the list of influences.
555 			if (influence->direction == 0) {
556 				influences.terrain_ids[influences.count] = neighbor->terrain_id;
557 				influences.count += 1;
558 			}
559 
560 			// as tile i has influence for this priority
561 			//  => bit i is set to 1 by 2^i
562 			influence->direction |= 1 << neigh_id;
563 			influence->priority = neighbor->priority;
564 			influence->terrain_id = neighbor->terrain_id;
565 		}
566 	}
567 
568 	// influences_by_terrain_id will be merged in the following,
569 	// unused terrain ids will be dropped now.
570 
571 	// shrink the big influence buffer that had entries for all terrains
572 	// by copying the possible (max 8) influences to a separate buffer.
573 	for (int k = 0; k < influences.count; k++) {
574 		int relevant_id    = influences.terrain_ids[k];
575 		influences.data[k] = influences_by_terrain_id[relevant_id];
576 	}
577 
578 	// order the influences by their priority
579 	for (int k = 1; k < influences.count; k++) {
580 		struct influence tmp_influence = influences.data[k];
581 
582 		int l = k - 1;
583 		while (l >= 0 && influences.data[l].priority > tmp_influence.priority) {
584 			influences.data[l + 1] = influences.data[l];
585 			l -= 1;
586 		}
587 
588 		influences.data[l + 1] = tmp_influence;
589 	}
590 
591 	return influences;
592 }
593 
594 
calculate_masks(coord::tile position,struct tile_draw_data * tile_data,struct influence_group * influences)595 void Terrain::calculate_masks(coord::tile position,
596                               struct tile_draw_data *tile_data,
597                               struct influence_group *influences) {
598 
599 	// influences are grouped by terrain id.
600 	// the direction member has each bit set to 1 that is an influence from that direction.
601 	// create a mask for this direction combination.
602 
603 	// the base tile is stored at position 0 of the draw_mask
604 	terrain_t base_terrain_id = tile_data->data[0].terrain_id;
605 
606 	// iterate over all neighbors (with different terrain_ids) that have influence
607 	for (ssize_t i = 0; i < influences->count; i++) {
608 
609 		// neighbor id of the current influence
610 		char direction_bits = influences->data[i].direction;
611 
612 		// all bits are 0 -> no influence directions stored.
613 		//  => no influence can be ignored.
614 		if (direction_bits == 0) {
615 			continue;
616 		}
617 
618 		terrain_t neighbor_terrain_id = influences->data[i].terrain_id;
619 		int adjacent_mask_id = -1;
620 
621 		/* neighbor ids:
622 		     0
623 		   7   1      => 8 neighbors that can have influence on
624 		 6   @   2         the mask id selection.
625 		   5   3
626 		     4
627 		*/
628 
629 		// filter adjacent and diagonal influences    neighbor_id: 76543210
630 		uint8_t direction_bits_adjacent = direction_bits & 0xAA; //0b10101010
631 		uint8_t direction_bits_diagonal = direction_bits & 0x55; //0b01010101
632 
633 		switch (direction_bits_adjacent) {
634 		case 0x08:  //0b00001000
635 			adjacent_mask_id = 0;  //0..3
636 			break;
637 		case 0x02:  //0b00000010
638 			adjacent_mask_id = 4;  //4..7
639 			break;
640 		case 0x20:  //0b00100000
641 			adjacent_mask_id = 8;  //8..11
642 			break;
643 		case 0x80:  //0b10000000
644 			adjacent_mask_id = 12; //12..15
645 			break;
646 		case 0x22:  //0b00100010
647 			adjacent_mask_id = 20;
648 			break;
649 		case 0x88:  //0b10001000
650 			adjacent_mask_id = 21;
651 			break;
652 		case 0xA0:  //0b10100000
653 			adjacent_mask_id = 22;
654 			break;
655 		case 0x82:  //0b10000010
656 			adjacent_mask_id = 23;
657 			break;
658 		case 0x28:  //0b00101000
659 			adjacent_mask_id = 24;
660 			break;
661 		case 0x0A:  //0b00001010
662 			adjacent_mask_id = 25;
663 			break;
664 		case 0x2A:  //0b00101010
665 			adjacent_mask_id = 26;
666 			break;
667 		case 0xA8:  //0b10101000
668 			adjacent_mask_id = 27;
669 			break;
670 		case 0xA2:  //0b10100010
671 			adjacent_mask_id = 28;
672 			break;
673 		case 0x8A:  //0b10001010
674 			adjacent_mask_id = 29;
675 			break;
676 		case 0xAA:  //0b10101010
677 			adjacent_mask_id = 30;
678 			break;
679 		}
680 
681 		// if it's the linear adjacent mask, cycle the 4 possible masks.
682 		// e.g. long shorelines don't look the same then.
683 		//  maskid == 0x08 0x02 0x80 0x20 for that.
684 		if (adjacent_mask_id <= 12 && adjacent_mask_id % 4 == 0) {
685 			//we have 4 = 2^2 anti redundancy masks, so keep the last 2 bits
686 			uint8_t anti_redundancy_offset = (position.ne + position.se) & 0x03;
687 			adjacent_mask_id += anti_redundancy_offset;
688 		}
689 
690 		// get the blending mode (the mask selection) for this transition
691 		// the mode is dependent on the two meeting terrain types
692 		int blend_mode = this->get_blending_mode(base_terrain_id, neighbor_terrain_id);
693 
694 		// append the mask for the adjacent blending
695 		if (adjacent_mask_id >= 0) {
696 			struct tile_data *overlay = &tile_data->data[tile_data->count];
697 			overlay->pos        = position;
698 			overlay->mask_id    = adjacent_mask_id;
699 			overlay->blend_mode = blend_mode;
700 			overlay->terrain_id = neighbor_terrain_id;
701 			overlay->tex        = this->texture(neighbor_terrain_id);
702 			overlay->subtexture_id = this->get_subtexture_id(
703 				position,
704 				std::sqrt(overlay->tex->get_subtexture_count())
705 			);
706 			overlay->mask_tex   = this->blending_mask(blend_mode);
707 			overlay->state      = tile_state::existing;
708 
709 			tile_data->count += 1;
710 		}
711 
712 		// append the mask for the diagonal blending
713 		if (direction_bits_diagonal > 0) {
714 			for (int l = 0; l < 4; l++) {
715 				// generate one mask for each influencing diagonal neighbor id.
716 				// even if they all have the same terrain_id,
717 				// because we don't have combined diagonal influence masks.
718 
719 				// l == 0: pos = 0b000000001, mask = 18
720 				// l == 1: pos = 0b000000100, mask = 16
721 				// l == 2: pos = 0b000010000, mask = 17
722 				// l == 3: pos = 0b001000000, mask = 19
723 
724 				int current_direction_bit = 1 << (l*2);
725 				constexpr int diag_mask_id_map[4] = {18, 16, 17, 19};
726 
727 				if (direction_bits_diagonal & current_direction_bit) {
728 					struct tile_data *overlay = &tile_data->data[tile_data->count];
729 					overlay->pos        = position;
730 					overlay->mask_id    = diag_mask_id_map[l];
731 					overlay->blend_mode = blend_mode;
732 					overlay->terrain_id = neighbor_terrain_id;
733 					overlay->tex        = this->texture(neighbor_terrain_id);
734 					overlay->subtexture_id = this->get_subtexture_id(
735 						position,
736 						std::sqrt(overlay->tex->get_subtexture_count())
737 					);
738 					overlay->mask_tex   = this->blending_mask(blend_mode);
739 					overlay->state      = tile_state::existing;
740 
741 					tile_data->count += 1;
742 				}
743 			}
744 		}
745 	}
746 }
747 
748 } // namespace openage
749