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