Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
TerritoryBoundary.cpp
Go to the documentation of this file.
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 #include "TerritoryBoundary.h"
20 
21 #include <algorithm> // for reverse
22 
23 #include "graphics/Terrain.h"
25 
26 std::vector<STerritoryBoundary> CTerritoryBoundaryCalculator::ComputeBoundaries(const Grid<u8>* territory)
27 {
28  std::vector<STerritoryBoundary> boundaries;
29 
30  // Copy the territories grid so we can mess with it
31  Grid<u8> grid(*territory);
32 
33  // Some constants for the border walk
34  CVector2D edgeOffsets[] = {
35  CVector2D(0.5f, 0.0f),
36  CVector2D(1.0f, 0.5f),
37  CVector2D(0.5f, 1.0f),
38  CVector2D(0.0f, 0.5f)
39  };
40 
41  // syntactic sugar
42  const u8 TILE_BOTTOM = 0;
43  const u8 TILE_RIGHT = 1;
44  const u8 TILE_TOP = 2;
45  const u8 TILE_LEFT = 3;
46 
47  const int CURVE_CW = -1;
48  const int CURVE_CCW = 1;
49 
50  // === Find territory boundaries ===
51  //
52  // The territory boundaries delineate areas of tiles that belong to the same player, and that all have the same
53  // connected-to-a-root-influence-entity status (see also STerritoryBoundary for a more wordy definition). Note that the grid
54  // values contain bit-packed information (i.e. not just the owning player ID), so we must be careful to only compare grid
55  // values using the player ID and connected flag bits. The joint mask to select these is referred to as the discriminator mask.
56  //
57  // The idea is to scan the (i,j)-grid going up row by row and look for tiles that have a different territory assignment from
58  // the one right underneath it (or, if it's a tile on the first row, they need only have a territory assignment). These tiles
59  // are necessarily edge tiles of a territory, and hence a territory boundary must pass through their bottom edge. Therefore,
60  // we start tracing the outline of the territory starting from said bottom edge, and go CCW around the territory boundary.
61  // Tracing continues until the starting point is reached, at which point the boundary is complete.
62  //
63  // While tracing a boundary, every tile in which the boundary passes through the bottom edge are marked as 'processed', so that
64  // we know not to start a new run from these tiles when scanning continues (when the boundary is complete). This information
65  // is maintained in the grid values themselves by means of the 'processed' bit mask (stressing the importance of using the
66  // discriminator mask to compare only player ID and connected flag).
67  //
68  // Thus, we can identify the following conditions for starting a trace from a tile (i,j). Let g(i,j) indicate the
69  // discriminator grid value at position (i,j); then the conditions are:
70  // - g(i,j) != 0; the tile must not be neutral
71  // - j=0 or g(i,j) != g(i,j-1); the tile directly underneath it must have a different owner and/or connected flag
72  // - the tile must not already be marked as 'processed'
73  //
74  // Additionally, there is one more point to be made; the algorithm initially assumes it's tracing CCW around the territory.
75  // If it's tracing an inner edge, however, this will actually cause it to trace in the CW direction (because inner edges curve
76  // 'backwards' compared to the outer edges when starting the trace in the same direction). This turns out to actually be
77  // exactly what the renderer needs to render two territory boundaries on the same edge back-to-back (instead of overlapping
78  // each other).
79  //
80  // In either case, we keep track of the way the outline curves while we're tracing to determine whether we're going CW or CCW.
81  // If at some point we ever need to revert the winding order or external code needs to know about it explicitly, then we can
82  // do this by looking at a curvature value which we define to start at 0, and which is incremented by 1 for every CCW turn and
83  // decremented by 1 for every CW turn. Hence, a negative multiple of 4 means a CW winding order, and a positive one means CCW.
84 
86 
87  // Try to find an assigned tile
88  for (u16 j = 0; j < grid.m_H; ++j)
89  {
90  for (u16 i = 0; i < grid.m_W; ++i)
91  {
92  // saved tile state; from MSB to LSB:
93  // processed bit, connected bit, player ID
94  u8 tileState = grid.get(i, j);
95  u8 tileDiscr = (tileState & TERRITORY_DISCR_MASK);
96 
97  // ignore neutral tiles (note that tiles without an owner should never have the connected bit set)
98  if (!tileDiscr)
99  continue;
100 
101  bool tileProcessed = ((tileState & ICmpTerritoryManager::TERRITORY_PROCESSED_MASK) != 0);
102  bool tileEligible = (j == 0 || tileDiscr != (grid.get(i, j-1) & TERRITORY_DISCR_MASK));
103 
104  if (tileProcessed || !tileEligible)
105  continue;
106 
107  // Found the first tile (which must be the lowest j value of any non-zero tile);
108  // start at the bottom edge of it and chase anticlockwise around the border until
109  // we reach the starting point again
110 
111  int curvature = 0; // +1 for every CCW 90 degree turn, -1 for every CW 90 degree turn; must be multiple of 4 at the end
112 
113  boundaries.push_back(STerritoryBoundary());
114  boundaries.back().owner = (tileState & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
115  boundaries.back().connected = (tileState & ICmpTerritoryManager::TERRITORY_CONNECTED_MASK) != 0;
116  std::vector<CVector2D>& points = boundaries.back().points;
117 
118  u8 dir = TILE_BOTTOM;
119 
120  u8 cdir = dir;
121  u16 ci = i, cj = j;
122 
123  u16 maxi = (u16)(grid.m_W-1);
124  u16 maxj = (u16)(grid.m_H-1);
125 
126  while (true)
127  {
128  points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * TERRAIN_TILE_SIZE);
129 
130  // Given that we're on an edge on a continuous boundary and aiming anticlockwise,
131  // we can either carry on straight or turn left or turn right, so examine each
132  // of the three possible cases (depending on initial direction):
133  switch (cdir)
134  {
135  case TILE_BOTTOM:
136 
137  // mark tile as processed so we don't start a new run from it after this one is complete
139  grid.set(ci, cj, grid.get(ci, cj) | ICmpTerritoryManager::TERRITORY_PROCESSED_MASK);
140 
141  if (ci < maxi && cj > 0 && (grid.get(ci+1, cj-1) & TERRITORY_DISCR_MASK) == tileDiscr)
142  {
143  ++ci;
144  --cj;
145  cdir = TILE_LEFT;
146  curvature += CURVE_CW;
147  }
148  else if (ci < maxi && (grid.get(ci+1, cj) & TERRITORY_DISCR_MASK) == tileDiscr)
149  ++ci;
150  else
151  {
152  cdir = TILE_RIGHT;
153  curvature += CURVE_CCW;
154  }
155  break;
156 
157  case TILE_RIGHT:
158  if (ci < maxi && cj < maxj && (grid.get(ci+1, cj+1) & TERRITORY_DISCR_MASK) == tileDiscr)
159  {
160  ++ci;
161  ++cj;
162  cdir = TILE_BOTTOM;
163  curvature += CURVE_CW;
164  }
165  else if (cj < maxj && (grid.get(ci, cj+1) & TERRITORY_DISCR_MASK) == tileDiscr)
166  ++cj;
167  else
168  {
169  cdir = TILE_TOP;
170  curvature += CURVE_CCW;
171  }
172  break;
173 
174  case TILE_TOP:
175  if (ci > 0 && cj < maxj && (grid.get(ci-1, cj+1) & TERRITORY_DISCR_MASK) == tileDiscr)
176  {
177  --ci;
178  ++cj;
179  cdir = TILE_RIGHT;
180  curvature += CURVE_CW;
181  }
182  else if (ci > 0 && (grid.get(ci-1, cj) & TERRITORY_DISCR_MASK) == tileDiscr)
183  --ci;
184  else
185  {
186  cdir = TILE_LEFT;
187  curvature += CURVE_CCW;
188  }
189  break;
190 
191  case TILE_LEFT:
192  if (ci > 0 && cj > 0 && (grid.get(ci-1, cj-1) & TERRITORY_DISCR_MASK) == tileDiscr)
193  {
194  --ci;
195  --cj;
196  cdir = TILE_TOP;
197  curvature += CURVE_CW;
198  }
199  else if (cj > 0 && (grid.get(ci, cj-1) & TERRITORY_DISCR_MASK) == tileDiscr)
200  --cj;
201  else
202  {
203  cdir = TILE_BOTTOM;
204  curvature += CURVE_CCW;
205  }
206  break;
207  }
208 
209  // Stop when we've reached the starting point again
210  if (ci == i && cj == j && cdir == dir)
211  break;
212  }
213 
214  ENSURE(curvature != 0 && abs(curvature) % 4 == 0);
215  }
216  }
217 
218  return boundaries;
219 }
#define u8
Definition: types.h:39
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
u16 m_H
Definition: Grid.h:101
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
static const int TERRITORY_CONNECTED_MASK
#define u16
Definition: types.h:40
static const int TERRITORY_PLAYER_MASK
Describes an outline of a territory, where the latter are understood to mean the largest sets of mutu...
T & get(int i, int j) const
Definition: Grid.h:93
static const int TERRITORY_PROCESSED_MASK
static std::vector< STerritoryBoundary > ComputeBoundaries(const Grid< u8 > *territories)
Computes and returns all territory boundaries on the provided territory map (see STerritoryBoundary f...
u16 m_W
Definition: Grid.h:101
void set(int i, int j, const T &value)
Definition: Grid.h:85