/
TileMap.cpp
228 lines (206 loc) · 7.44 KB
/
TileMap.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/**
* Represents the spread of all the tiles that will be outputted to the final bitmap file
*
* Tiles are stored in a vector of vectors, to represent the grid layout of the Tiles.
*
* @author Sasha Ouellet - spaouellet@me.com
* @version 1.0 - 02/05/17
* @version 1.1 - 02/19/17 - Allowing construction via pre-developed vector array of Tiles
*/
#include <cstdlib>
#include <random>
#include <chrono>
#include "TileMap.h"
/**
* Default constructor for the TileMap
*
* @param width The width, in number of tiles
* @param height The height, in number of tiles
*/
TileMap::TileMap(vector<Tile>& tileSet, unsigned int width, unsigned int height)
{
m_tileSet = tileSet;
m_width = width;
m_height = height;
m_generator = std::default_random_engine(std::chrono::system_clock::now().time_since_epoch().count());
m_distribution = std::uniform_int_distribution<int>(0, m_tileSet.size() - 1);
}
/**
* Constructs the tile map from the already generated tile set, in its grid format
* @param tiles
*/
TileMap::TileMap(vector<vector<Tile>> &tiles, unsigned int width, unsigned int height)
{
m_tiles = tiles;
m_tileSet = tiles[0];
m_width = width;
m_height = height;
}
/**
* Get the pixel width of the entire TileMap
* @return The pixel width of the map
*/
int TileMap::getPixelWidth()
{
return m_tileSet[0].getImage().getWidth() * m_width;
}
/**
* Get the pixel height of the entire TileMap
* @return The pixel height of the map
*/
int TileMap::getPixelHeight()
{
return m_tileSet[0].getImage().getHeight() * m_height;
}
/**
* Runs through the generation process of the entire tile map, populating the vectors for each row of the grid
* according to the specifications for Wang Tile tiling process
*
* Specifications: Begin with any first tile. Next, select a tile to be placed to the right. This tile's West side must
* have a matching code to the East side of the tile to its left. Repeat for the remainder of the first row.
*
* The next row must follow the same specifications, in addition to the North side codes matching the South side codes
* of their neighbor directly above.
*/
void TileMap::generate()
{
// As of now, starting with index 2 is the only known tile that will successfully tile the whole thing
for (int i = 0 ; i < m_height ; i++)
{
cout << "i: " << i << endl;
vector<Tile> row;
Tile* last = nullptr;
for (int j = 0 ; j < m_width ; j++)
{
if (j != 0)
{
last = &row[j - 1];
}
cout << "\tj: " << j << endl;
// Special case for first tile
if (i == 0 && j == 0)
{
row.push_back(getRandom());
}
// Special case for first row, don't need to check for row above
else if (i == 0)
{
Tile t = getRandom();
// Pick while the codes for the E and W sides don't match
while (!t.hasCodeAtSide(row[j - 1].getCodeAtSide(Tile::EAST), Tile::WEST) || t.isSame(last))
{
t = getRandom();
cout << "[EAST / WEST] Need: " << row[j - 1].getCodeAtSide(Tile::EAST) << ", Have: " << t.getCodeAtSide(Tile::WEST) << endl;
}
row.push_back(t);
}
// Normal case, need to check row above for code matching as well
else
{
Tile t = getRandom();
// Pick while the codes for the E and W sides don't match, AND while the codes for N and S don't match
// Special case for first of row, don't need to check E/W
if (j == 0)
{
while (!t.hasCodeAtSide(m_tiles[i - 1][j].getCodeAtSide(Tile::SOUTH), Tile::NORTH) || t.isSame(last))
{
t = getRandom();
cout << "[NORTH / SOUTH] Need: " << m_tiles[i - 1][j].getCodeAtSide(Tile::SOUTH) << ", Have: " << t.getCodeAtSide(Tile::NORTH) << endl;
}
}
// Normal case
else
{
int tries = 0;
while ((!t.hasCodeAtSide(row[j - 1].getCodeAtSide(Tile::EAST), Tile::WEST) || !t.hasCodeAtSide(m_tiles[i - 1][j].getCodeAtSide(Tile::SOUTH), Tile::NORTH) || t.isSame(last)) && tries < 10)
{
t = getRandom();
cout << "[NORTH / SOUTH] Need: " << m_tiles[i - 1][j].getCodeAtSide(Tile::SOUTH) << ", Have: " << t.getCodeAtSide(Tile::NORTH) << endl;
cout << "[EAST / WEST] Need: " << row[j - 1].getCodeAtSide(Tile::EAST) << ", Have: " << t.getCodeAtSide(Tile::WEST) << endl;
// tries++; // Fail safe to avoid endless cycle if location not tile-able
}
}
row.push_back(t);
}
}
m_tiles.push_back(row);
}
}
/**
* Gets a random tile from the tile set
* @return The randomly selected tile
*/
Tile TileMap::getRandom()
{
return m_tileSet[m_distribution(m_generator)];
}
/**
* Prints the tile map configuration
*/
void TileMap::print()
{
for (int i = 0 ; i < m_height ; i++)
{
cout << "ROW " << i << ", size: " << m_tiles[i].size() << endl << "-----------------" << endl;
for (int j = 0 ; j < m_width ; j++)
{
cout << "\t";
m_tiles[i][j].print();
cout << endl;
}
}
}
/**
* Aggregates all the tiles in the 2-dimensional vector structure to put all the Tile pixel data into the main
* pixel data array that will be written as the output file.
*
* @return The final output pixel data array of all the tile's pixel data combined
*/
unsigned char* TileMap::makeArray()
{
int size = 3 * getPixelWidth() * getPixelHeight();
unsigned char *data = new unsigned char[size];
for (int i = 0 ; i < m_height ; i++)
{
for (int j = 0 ; j < m_width ; j++)
{
placeTile(m_tiles[i][j], j, i, data);
}
}
return data;
}
/**
* Given a specific tile and its location in the 2-dimensional vector, populates the given main data array with the
* Tile's pixel data. The function determines the initial "offset" where writing of the data will begin, and from there
* the Tile's location in x-space determines how far after the offset the pixel data starts.
*
* @param tile The tile to populate the main data array with
* @param x The x location of the tile in the 2-dimensional vector
* @param y The y location of the tile in the 2-dimensional vector
* @param data The main data pixel array of all the tiles
*/
void TileMap::placeTile(Tile &tile, int x, int y, unsigned char *data)
{
y = m_height - 1 - y;
BMPFile& image = tile.getImage();
const unsigned char *imagePixels = image.getPlane()->getRawData();
int tileWidth = image.getWidth();
int size = tileWidth * image.getHeight() * 3;
int offset = y * m_width * size;
for (int i = 0; i < size; i++)
{
int row = i / (tileWidth * 3);
int col = i % (tileWidth * 3);
data[offset + (tileWidth * 3 * (x + (m_width * row))) + col] = imagePixels[i];
}
}
/**
* Gets the tile at the specified x and y coordinates in the TileMap plane
* @param x The x value of the tile
* @param y The y value of the tile
* @return The tile at these coordinates
*/
Tile TileMap::getTileAt(int x, int y)
{
return m_tiles[y][x];
}