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/PixelBits.h"
19 #include "solarus/core/Rectangle.h"
20 #include "solarus/core/System.h"
21 #include "solarus/graphics/Surface.h"
22 #include <SDL.h>
23 #include <algorithm>
24 #include <iostream> // print functions
25
26 namespace Solarus {
27
28 /**
29 * \brief Creates a pixel bits object.
30 * \param surface The surface where the image is.
31 * \param image_position Position of the image on this surface.
32 */
PixelBits(const Surface & surface,const Rectangle & image_position)33 PixelBits::PixelBits(const Surface& surface, const Rectangle& image_position):
34 width(0),
35 height(0),
36 nb_integers_per_row(0),
37 bits() {
38
39 // Create a list of boolean values representing the transparency of each pixel.
40 // This list is implemented as bit fields.
41
42 // Clip the rectangle passed as parameter.
43 const Rectangle clipped_image_position(
44 image_position.get_intersection(Rectangle(surface.get_size()))
45 );
46
47 if (clipped_image_position.is_flat()) {
48 return;
49 }
50
51 width = clipped_image_position.get_width();
52 height = clipped_image_position.get_height();
53
54 nb_integers_per_row = width >> 5; // width / 32
55 if ((width & 31) != 0) { // width % 32 != 0
56 nb_integers_per_row++;
57 }
58
59 int pixel_index = clipped_image_position.get_y() * surface.get_width() + clipped_image_position.get_x();
60
61 bits.resize(height);
62 for (int i = 0; i < height; ++i) {
63 bits[i].resize(nb_integers_per_row);
64
65 // Fill the bits for this row, using nb_integers_per_row sequences of 32 bits.
66 int k = -1;
67 uint32_t mask = 0x00000000; // Current bit in the sequence of 32 bits.
68 for (int j = 0; j < width; ++j) {
69 if (mask == 0x00000000) {
70 // Time for a new sequence of 32 bits.
71 k++;
72 mask = 0x80000000;
73 bits[i][k] = 0x00000000; // Initialize the sequence to transparent.
74 }
75
76 // If the pixel is opaque.
77 if (!surface.is_pixel_transparent(pixel_index)) {
78 bits[i][k] |= mask;
79 }
80
81 mask >>= 1;
82 ++pixel_index;
83 }
84 pixel_index += surface.get_width() - width;
85 }
86 }
87
88 /**
89 * \brief Detects whether the image represented by these pixel bits is
90 * overlapping another image.
91 * \param other The other image.
92 * \param location1 Position of the upper-left corner of this image on the map.
93 * \param location2 Position of the upper-left corner of the other image.
94 * \return \c true if there is a collision.
95 */
test_aligned_collision(const PixelBits & other,const Point & location1,const Point & location2) const96 bool PixelBits::test_aligned_collision(const PixelBits& other,
97 const Point &location1,
98 const Point &location2
99 ) const {
100 const bool debug_pixel_collisions = false;
101
102 if (bits.empty()) {
103 // No image.
104 return false;
105 }
106
107 // Compute both bounding boxes.
108 const Rectangle bounding_box1(location1.x, location1.y, width, height);
109 const Rectangle bounding_box2(location2.x, location2.y, other.width, other.height);
110
111 // Check collision between the two bounding boxes.
112 if (!bounding_box1.overlaps(bounding_box2)) {
113 return false;
114 }
115
116 if (debug_pixel_collisions) {
117 std::cout << System::now() << "\n bounding box collision\n";
118 std::cout << "rect1 = " << bounding_box1 << "\n";
119 std::cout << "rect2 = " << bounding_box2 << "\n";
120 print();
121 other.print();
122 }
123
124 // Compute the intersection between both rectangles.
125 const int intersection_x = std::max(bounding_box1.get_x(), bounding_box2.get_x());
126 const int intersection_y = std::max(bounding_box1.get_y(), bounding_box2.get_y());
127 const Rectangle intersection(
128 intersection_x,
129 intersection_y,
130 std::min(bounding_box1.get_x() + bounding_box1.get_width(),
131 bounding_box2.get_x() + bounding_box2.get_width()) - intersection_x,
132 std::min(bounding_box1.get_y() + bounding_box1.get_height(),
133 bounding_box2.get_y() + bounding_box2.get_height()) - intersection_y);
134
135 if (debug_pixel_collisions) {
136 std::cout << "intersection: " << intersection << "\n";
137 }
138
139 // compute the relative position of the intersection rectangle for each bounding_box
140 Point offset1 = intersection.get_xy() - bounding_box1.get_xy();
141 Point offset2 = intersection.get_xy() - bounding_box2.get_xy();
142
143 if (debug_pixel_collisions) {
144 std::cout << "offset1.x = " << offset1.x << ", offset1.y = " << offset1.y;
145 std::cout << ", offset2.x = " << offset2.x << ", offset2.y = " << offset2.y << std::endl;
146 }
147
148 // For each row of the intersection, we will call row 'a' the row coming from the right bounding box
149 // and row 'b' the one coming from the left bounding box.
150
151 std::vector<std::vector<uint32_t>>::const_iterator rows_a; // for each row: array of masks of the right bounding box
152 std::vector<std::vector<uint32_t>>::const_iterator rows_b; // for each row: array of masks of the left bounding box
153
154 int nb_masks_per_row_a; // number of masks on row a that are in the intersection (row b may have one more mask)
155 int nb_masks_per_row_b; // number of masks on row b that are in the intersection
156 int nb_unused_masks_row_b; // number of unused masks on row b (i.e. before the intersection)
157 int nb_unused_bits_row_b; // number of unused bits on the first used mask of row b
158 int nb_used_bits_row_b; // number of bits used on the first used mask of row b
159
160 // make sure row a starts after row b
161 if (bounding_box1.get_x() > bounding_box2.get_x()) {
162 rows_a = this->bits.begin() + offset1.y;
163 rows_b = other.bits.begin() + offset2.y;
164 nb_unused_masks_row_b = offset2.x >> 5;
165 nb_unused_bits_row_b = offset2.x & 31;
166 }
167 else {
168 rows_a = other.bits.begin() + offset2.y;
169 rows_b = this->bits.begin() + offset1.y;
170 nb_unused_masks_row_b = offset1.x >> 5;
171 nb_unused_bits_row_b = offset1.x & 31;
172 }
173 nb_used_bits_row_b = 32 - nb_unused_bits_row_b;
174
175 // compute the number of masks in the intersection on row a
176 nb_masks_per_row_a = intersection.get_width() >> 5;
177 if ((intersection.get_width() & 31) != 0) {
178 nb_masks_per_row_a++;
179 }
180
181 // compute the number of masks in the intersection on row b
182 nb_masks_per_row_b = (intersection.get_width() + nb_unused_bits_row_b) >> 5;
183 if (((intersection.get_width() + nb_unused_bits_row_b) & 31) != 0) {
184 nb_masks_per_row_b++;
185 }
186
187 // check the collisions each row of the intersection rectangle
188 for (int i = 0; i < intersection.get_height(); ++i) {
189
190 // current row
191 const std::vector<uint32_t>& bits_a = *rows_a;
192 const std::vector<uint32_t>& bits_b = *rows_b;
193
194 ++rows_a;
195 ++rows_b;
196
197 if (debug_pixel_collisions) {
198 std::cout << "*** checking row " << i << " of the intersection rectangle\n";
199 }
200
201 // Check each mask.
202 for (int j = 0; j < nb_masks_per_row_a; ++j) {
203
204 // We compare the left of a with the right part of b,
205 // and the right part of a with the right part of the next b if any.
206
207 uint32_t mask_a = bits_a[j];
208 uint32_t mask_b = bits_b[j + nb_unused_masks_row_b];
209 uint32_t mask_a_left = mask_a >> nb_unused_bits_row_b;
210 uint32_t next_mask_b_left = 0x00000000;
211 if (j + 1 < nb_masks_per_row_a || // Not the last a-mask.
212 nb_masks_per_row_b > nb_masks_per_row_a // Last a-mask but there exists one more b-mask.
213 ) {
214 // We are still inside the intersection: next_mask_b_left exists.
215 next_mask_b_left = bits_b[j + nb_unused_masks_row_b + 1] >> nb_used_bits_row_b;
216 }
217
218 if (debug_pixel_collisions) {
219 std::cout << "mask_a = ";
220 print_mask(mask_a);
221 std::cout << ", mask b = ";
222 print_mask(mask_b);
223 std::cout << "\n";
224 }
225
226 if (((mask_a_left & mask_b) | (mask_a & next_mask_b_left)) != 0x00000000) {
227 return true;
228 }
229 }
230 }
231
232 return false;
233 }
234
235 /**
236 * @brief test collision of this pixmap with another one
237 * @param other other PixelBits
238 * @param transform1 transform of this pixel bits
239 * @param transform2 transform of the other pixel bits
240 * @return true if pixel bits intersects
241 *
242 * Both pixelbits are potentially transformed, if one of them is not trivially translated
243 * a slower algorithm is used where one of the pixelbits is projected on the other
244 */
test_collision(const PixelBits & other,const Transform & transform1,const Transform & transform2) const245 bool PixelBits::test_collision(const PixelBits &other,
246 const Transform &transform1, const Transform &transform2) const {
247 if(transform1.aligned() && transform2.aligned()) {
248 return test_aligned_collision(other,transform1.position,transform2.position);
249 } else {
250 return test_oriented_collision(other,transform1,transform2);
251 }
252 }
253
254 /**
255 * @brief slower collision test taking rotation and scale into account
256 * @param other other pixelbits
257 * @param transform1 transform applied to this pixelbits
258 * @param transform2 transform applied to the other pixel bits
259 * @return
260 */
test_oriented_collision(const PixelBits & other,const Transform & transform1,const Transform & transform2) const261 bool PixelBits::test_oriented_collision(const PixelBits &other,
262 const Transform& transform1,
263 const Transform& transform2) const {
264 if(!transform1.obb_intersect(Size(width,height),transform2,Size(other.width,other.height))) {
265 return false;
266 }
267 glm::vec2 origin,vx,vy;
268 transform1.compute_collision_data(transform2,origin,vx,vy);
269 for(int y = 0; y < height; y++) {
270 for(int x = 0; x < width; x++) {
271 if(at(x,y)){
272 glm::vec2 p = origin+vx*(float)x+vy*(float)y;
273 int ox = p.x;
274 int oy = p.y;
275
276 if(other.at(ox,oy)) {
277 return true;
278 }
279 }
280 }
281 }
282 return false;
283 }
284
285 /**
286 * @brief access the pixel at given coords
287 * @param x x coord in the pixel map
288 * @param y y coord in the pixel map
289 * @return
290 */
at(int x,int y) const291 bool PixelBits::at(int x, int y) const {
292 if(x < 0 || y < 0 || x >= width || y >= height) {
293 return false;
294 }
295 const auto& row = bits.at(y);
296 int index = x / 32;
297 int offset = x % 32;
298 return (row[index] << offset) & 0x80000000;
299 }
300
301 /**
302 * \brief Prints an ASCII representation of the pixels (for debugging purposes only).
303 */
print() const304 void PixelBits::print() const {
305
306 std::cout << "frame size is " << width << " x " << height << std::endl;
307 for (int i = 0; i < height; i++) {
308 for (int j = 0; j < width; j++) {
309 if (at(j,i)) {
310 std::cout << "X ";
311 }
312 else {
313 std::cout << ". ";
314 }
315 }
316 std::cout << std::endl;
317 }
318 }
319
320 /**
321 * \brief Prints an ASCII representation of a 32-bit mask (for debugging purposes only).
322 */
print_mask(uint32_t mask) const323 void PixelBits::print_mask(uint32_t mask) const {
324
325 for (int i = 0; i < 32; i++) {
326 std::cout << (((mask & 0x80000000) != 0x00000000) ? "X" : ".");
327 mask <<= 1;
328 }
329 }
330
331 }
332
333