1 /*
2 * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3 *
4 * Solarus 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 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Solarus 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 along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "solarus/core/Debug.h"
18 #include "solarus/core/Map.h"
19 #include "solarus/entities/Camera.h"
20 #include "solarus/entities/Entities.h"
21 #include "solarus/entities/NonAnimatedRegions.h"
22 #include "solarus/entities/Tileset.h"
23 #include "solarus/graphics/Surface.h"
24
25 namespace Solarus {
26
27 /**
28 * \brief Constructor.
29 * \param map The map. Its size must be known.
30 * \param layer The layer to represent.
31 */
NonAnimatedRegions(Map & map,int layer)32 NonAnimatedRegions::NonAnimatedRegions(Map& map, int layer):
33 map(map),
34 layer(layer),
35 non_animated_tiles(map.get_size(), Size(512, 256)) {
36
37 }
38
39 /**
40 * \brief Adds a tile to the list of tiles.
41 */
add_tile(const TileInfo & tile)42 void NonAnimatedRegions::add_tile(const TileInfo& tile) {
43
44 Debug::check_assertion(are_squares_animated.empty(),
45 "Tile regions are already built");
46 Debug::check_assertion(tile.layer == layer, "Wrong layer for add tile");
47
48 tiles.push_back(tile);
49 }
50
51 /**
52 * \brief Determines which rectangles are animated to allow drawing all non-animated
53 * rectangles of tiles only once.
54 * \param[out] rejected_tiles The list of tiles that are in animated regions.
55 * They include all animated tiles plus the static tiles overlapping them.
56 * You will have to redraw these tiles at each frame.
57 */
build(std::vector<TileInfo> & rejected_tiles)58 void NonAnimatedRegions::build(std::vector<TileInfo>& rejected_tiles) {
59
60 Debug::check_assertion(are_squares_animated.empty(),
61 "Tile regions are already built");
62
63 const int map_width8 = map.get_width8();
64 const int map_height8 = map.get_height8();
65
66 // Initialize the are_squares_animated booleans to false.
67 for (int i = 0; i < map_width8 * map_height8; ++i) {
68 are_squares_animated.push_back(false);
69 }
70
71 // Mark animated 8x8 squares of the map.
72 for (size_t i = 0; i < tiles.size(); ++i) {
73 const TileInfo& tile = tiles[i];
74 if (tile.pattern->is_animated()) {
75 // Animated tile: mark its region as non-optimizable
76 // (otherwise, a non-animated tile above an animated one would screw us).
77
78 int tile_x8 = tile.box.get_x() / 8;
79 int tile_y8 = tile.box.get_y() / 8;
80 int tile_width8 = tile.box.get_width() / 8;
81 int tile_height8 = tile.box.get_height() / 8;
82
83 for (int i = 0; i < tile_height8; i++) {
84 for (int j = 0; j < tile_width8; j++) {
85
86 const int x8 = tile_x8 + j;
87 const int y8 = tile_y8 + i;
88 if (x8 >= 0 && x8 < map_width8 && y8 >= 0 && y8 < map_height8) {
89 int index = y8 * map_width8 + x8;
90 are_squares_animated[index] = true;
91 }
92 }
93 }
94 }
95 }
96
97 // Build the list of animated tiles and tiles overlapping them.
98 for (const TileInfo& tile: tiles) {
99 if (!tile.pattern->is_animated()) {
100 non_animated_tiles.add(tile, tile.box);
101 if (overlaps_animated_tile(tile)) {
102 rejected_tiles.push_back(tile);
103 }
104 }
105 else {
106 rejected_tiles.push_back(tile);
107 }
108 }
109
110 // No need to keep all tiles at this point.
111 // Just keep the non-animated ones to draw them lazily.
112 tiles.clear();
113 }
114
115 /**
116 * \brief Clears previous drawings because the tileset has changed.
117 */
notify_tileset_changed()118 void NonAnimatedRegions::notify_tileset_changed() {
119
120 optimized_tiles_surfaces.clear();
121 // Everything will be redrawn when necessary.
122 }
123
124 /**
125 * \brief Returns whether a tile is overlapping an animated other tile.
126 * \param tile The tile to check.
127 * \return \c true if this tile is overlapping an animated tile.
128 */
overlaps_animated_tile(const TileInfo & tile) const129 bool NonAnimatedRegions::overlaps_animated_tile(const TileInfo& tile) const {
130
131 if (tile.layer != layer) {
132 return false;
133 }
134
135 int tile_x8 = tile.box.get_x() / 8;
136 int tile_y8 = tile.box.get_y() / 8;
137 int tile_width8 = tile.box.get_width() / 8;
138 int tile_height8 = tile.box.get_height() / 8;
139
140 for (int i = 0; i < tile_height8; i++) {
141 for (int j = 0; j < tile_width8; j++) {
142
143 int x8 = tile_x8 + j;
144 int y8 = tile_y8 + i;
145 if (x8 >= 0 && x8 < map.get_width8() && y8 >= 0 && y8 < map.get_height8()) {
146
147 int index = y8 * map.get_width8() + x8;
148 if (are_squares_animated[index]) {
149 return true;
150 }
151 }
152 }
153 }
154 return false;
155 }
156
157 /**
158 * \brief Called at each frame of the main loop.
159 */
update()160 void NonAnimatedRegions::update() {
161
162 // Limit the size of the cache to avoid growing the memory usage.
163 if (optimized_tiles_surfaces.size() < 25) {
164 return;
165 }
166
167 std::vector<int> indexes_to_clear;
168 const CameraPtr& camera = map.get_camera();
169 if (camera == nullptr) {
170 return;
171 }
172
173 const Size& cell_size = non_animated_tiles.get_cell_size();
174 const Rectangle& camera_position = camera->get_bounding_box();
175 const int row1 = camera_position.get_y() / cell_size.height;
176 const int row2 = (camera_position.get_y() + camera_position.get_height()) / cell_size.height;
177 const int column1 = camera_position.get_x() / cell_size.width;
178 const int column2 = (camera_position.get_x() + camera_position.get_width()) / cell_size.width;
179
180 for (const auto& kvp : optimized_tiles_surfaces) {
181 const int cell_index = kvp.first;
182 const int row = cell_index / non_animated_tiles.get_num_columns();
183 const int column = cell_index % non_animated_tiles.get_num_columns();
184 if (column < column1 || column > column2 || row < row1 || row > row2) {
185 indexes_to_clear.push_back(cell_index);
186 }
187 }
188
189 for (int cell_index : indexes_to_clear) {
190 optimized_tiles_surfaces.erase(cell_index);
191 }
192 }
193
194 /**
195 * \brief Draws a layer of non-animated regions of tiles on the current map.
196 */
draw_on_map()197 void NonAnimatedRegions::draw_on_map() {
198
199 const CameraPtr& camera = map.get_camera();
200 if (camera == nullptr) {
201 return;
202 }
203
204 // Check all grid cells that overlap the camera.
205 const int num_rows = non_animated_tiles.get_num_rows();
206 const int num_columns = non_animated_tiles.get_num_columns();
207 const Size& cell_size = non_animated_tiles.get_cell_size();
208 const Rectangle& camera_position = camera->get_bounding_box();
209
210 const int row1 = camera_position.get_y() / cell_size.height;
211 const int row2 = (camera_position.get_y() + camera_position.get_height()) / cell_size.height;
212 const int column1 = camera_position.get_x() / cell_size.width;
213 const int column2 = (camera_position.get_x() + camera_position.get_width()) / cell_size.width;
214
215 if (row1 > row2 || column1 > column2) {
216 // No cell.
217 return;
218 }
219
220 for (int i = row1; i <= row2; ++i) {
221 if (i < 0 || i >= num_rows) {
222 continue;
223 }
224
225 for (int j = column1; j <= column2; ++j) {
226 if (j < 0 || j >= num_columns) {
227 continue;
228 }
229
230 // Make sure this cell is built.
231 int cell_index = i * num_columns + j;
232 if (optimized_tiles_surfaces.find(cell_index) == optimized_tiles_surfaces.end()) {
233 // Lazily build the cell.
234 build_cell(cell_index);
235 }
236
237 const Point cell_xy = {
238 j * cell_size.width,
239 i * cell_size.height
240 };
241
242 const Point dst_position = cell_xy - camera_position.get_xy();
243 optimized_tiles_surfaces.at(cell_index)->draw(
244 map.get_camera_surface(), dst_position
245 );
246 }
247 }
248 }
249
250 /**
251 * \brief Draws all non-animated tiles of a cell on its surface.
252 * \param cell_index Index of the cell to draw.
253 */
build_cell(int cell_index)254 void NonAnimatedRegions::build_cell(int cell_index) {
255
256 Debug::check_assertion(
257 cell_index >= 0 && (size_t) cell_index < non_animated_tiles.get_num_cells(),
258 "Wrong cell index"
259 );
260 Debug::check_assertion(optimized_tiles_surfaces.find(cell_index) == optimized_tiles_surfaces.end(),
261 "This cell is already built"
262 );
263
264 const int row = cell_index / non_animated_tiles.get_num_columns();
265 const int column = cell_index % non_animated_tiles.get_num_columns();
266
267 // Position of this cell on the map.
268 const Size cell_size = non_animated_tiles.get_cell_size();
269 const Point cell_xy = {
270 column * cell_size.width,
271 row * cell_size.height
272 };
273
274 SurfacePtr cell_surface = Surface::create(cell_size,true);
275 optimized_tiles_surfaces[cell_index] = cell_surface;
276
277 const std::vector<TileInfo>& tiles_in_cell =
278 non_animated_tiles.get_elements(cell_index);
279 for (const TileInfo& tile: tiles_in_cell) {
280
281 Rectangle dst_position(
282 tile.box.get_x() - cell_xy.x,
283 tile.box.get_y() - cell_xy.y,
284 tile.box.get_width(),
285 tile.box.get_height()
286 );
287
288 const Tileset* tileset = tile.tileset != nullptr ? tile.tileset : &map.get_tileset();
289 Debug::check_assertion(tileset != nullptr, "Missing tileset");
290 tile.pattern->fill_surface(
291 cell_surface,
292 dst_position,
293 *tileset,
294 cell_xy
295 );
296 }
297
298 // Remember that non-animated tiles are drawn after animated ones.
299 // We may have drawn too much.
300 // We have to make sure we don't exceed the non-animated regions.
301 // Erase 8x8 squares that contain animated tiles.
302 for (int y = cell_xy.y; y < cell_xy.y + cell_size.height; y += 8) {
303 if (y >= map.get_height()) { // The last cell might exceed the map border.
304 continue;
305 }
306 for (int x = cell_xy.x; x < cell_xy.x + cell_size.width; x += 8) {
307 if (x >= map.get_width()) {
308 continue;
309 }
310
311 int square_index = (y / 8) * map.get_width8() + (x / 8);
312
313 if (are_squares_animated[square_index]) {
314 Rectangle animated_square(
315 x - cell_xy.x,
316 y - cell_xy.y,
317 8,
318 8
319 );
320 cell_surface->clear(animated_square);
321 }
322 }
323 }
324 }
325
326 }
327