1 /*
2  * tilelayer.cpp
3  * Copyright 2008-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2009, Jeff Bland <jksb@member.fsf.org>
5  *
6  * This file is part of libtiled.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  *    1. Redistributions of source code must retain the above copyright notice,
12  *       this list of conditions and the following disclaimer.
13  *
14  *    2. Redistributions in binary form must reproduce the above copyright
15  *       notice, this list of conditions and the following disclaimer in the
16  *       documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "tilelayer.h"
31 
32 #include "tile.h"
33 #include "hex.h"
34 
35 #include <algorithm>
36 #include <memory>
37 
38 #include <QSet>
39 
40 using namespace Tiled;
41 
42 Cell Cell::empty;
43 
rotate(RotateDirection direction)44 void Cell::rotate(RotateDirection direction)
45 {
46     static constexpr unsigned char rotateMasks[2][8] = {
47         { 3, 2, 7, 6, 1, 0, 5, 4 },
48         { 5, 4, 1, 0, 7, 6, 3, 2 },
49     };
50 
51     unsigned char mask =
52             (flippedHorizontally() << 2) |
53             (flippedVertically() << 1) |
54             (flippedAntiDiagonally() << 0);
55 
56     mask = rotateMasks[direction][mask];
57 
58     setFlippedHorizontally((mask & 4) != 0);
59     setFlippedVertically((mask & 2) != 0);
60     setFlippedAntiDiagonally((mask & 1) != 0);
61 }
62 
region(std::function<bool (const Cell &)> condition) const63 QRegion Chunk::region(std::function<bool (const Cell &)> condition) const
64 {
65     QRegion region;
66 
67     for (int y = 0; y < CHUNK_SIZE; ++y) {
68         for (int x = 0; x < CHUNK_SIZE; ++x) {
69             if (condition(cellAt(x, y))) {
70                 const int rangeStart = x;
71                 for (++x; x <= CHUNK_SIZE; ++x) {
72                     if (x == CHUNK_SIZE || !condition(cellAt(x, y))) {
73                         const int rangeEnd = x;
74                         region += QRect(rangeStart, y, rangeEnd - rangeStart, 1);
75                         break;
76                     }
77                 }
78             }
79         }
80     }
81 
82     return region;
83 }
84 
setCell(int x,int y,const Cell & cell)85 void Chunk::setCell(int x, int y, const Cell &cell)
86 {
87     int index = x + y * CHUNK_SIZE;
88 
89     mGrid[index] = cell;
90 }
91 
isEmpty() const92 bool Chunk::isEmpty() const
93 {
94     for (int y = 0; y < CHUNK_SIZE; ++y) {
95         for (int x = 0; x < CHUNK_SIZE; ++x) {
96             if (!cellAt(x, y).isEmpty())
97                 return false;
98         }
99     }
100 
101     return true;
102 }
103 
hasCell(std::function<bool (const Cell &)> condition) const104 bool Chunk::hasCell(std::function<bool (const Cell &)> condition) const
105 {
106     for (const Cell &cell : mGrid)
107         if (condition(cell))
108             return true;
109 
110     return false;
111 }
112 
removeReferencesToTileset(Tileset * tileset)113 void Chunk::removeReferencesToTileset(Tileset *tileset)
114 {
115     for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) {
116         if (mGrid.at(i).tileset() == tileset)
117             mGrid.replace(i, Cell::empty);
118     }
119 }
120 
replaceReferencesToTileset(Tileset * oldTileset,Tileset * newTileset)121 void Chunk::replaceReferencesToTileset(Tileset *oldTileset, Tileset *newTileset)
122 {
123     for (Cell &cell : mGrid) {
124         if (cell.tileset() == oldTileset)
125             cell.setTile(newTileset, cell.tileId());
126     }
127 }
128 
TileLayer(const QString & name,int x,int y,int width,int height)129 TileLayer::TileLayer(const QString &name, int x, int y, int width, int height)
130     : Layer(TileLayerType, name, x, y)
131     , mWidth(width)
132     , mHeight(height)
133     , mUsedTilesetsDirty(false)
134 {
135 }
136 
TileLayer(const QString & name,QPoint position,QSize size)137 TileLayer::TileLayer(const QString &name, QPoint position, QSize size)
138     : TileLayer(name, position.x(), position.y(), size.width(), size.height())
139 {
140 }
141 
computeDrawMargins(const QSet<SharedTileset> & tilesets)142 static QMargins computeDrawMargins(const QSet<SharedTileset> &tilesets)
143 {
144     // Not differentiating between width and height, because tiles could be rotated
145     int maxTileSize = 0;
146     QMargins offsetMargins;
147 
148     for (const SharedTileset &tileset : tilesets) {
149         const QPoint offset = tileset->tileOffset();
150         const QSize tileSize = tileset->tileSize();
151 
152         maxTileSize = std::max(maxTileSize, std::max(tileSize.width(),
153                                                      tileSize.height()));
154 
155         offsetMargins = maxMargins(QMargins(-offset.x(),
156                                             -offset.y(),
157                                             offset.x(),
158                                             offset.y()),
159                                    offsetMargins);
160     }
161 
162     // Adding maxTileSize to top-right of the margins assumes a bottom-left tile
163     // alignment within the grid cell.
164     return QMargins(offsetMargins.left(),
165                     offsetMargins.top() + maxTileSize,
166                     offsetMargins.right() + maxTileSize,
167                     offsetMargins.bottom());
168 }
169 
drawMargins() const170 QMargins TileLayer::drawMargins() const
171 {
172     return computeDrawMargins(usedTilesets());
173 }
174 
175 /**
176  * Calculates the region of cells in this tile layer for which the given
177  * \a condition returns true.
178  */
region(std::function<bool (const Cell &)> condition) const179 QRegion TileLayer::region(std::function<bool (const Cell &)> condition) const
180 {
181     QRegion region;
182 
183     QHashIterator<QPoint, Chunk> it(mChunks);
184     while (it.hasNext()) {
185         it.next();
186         region += it.value().region(condition).translated(it.key().x() * CHUNK_SIZE + mX,
187                                                           it.key().y() * CHUNK_SIZE + mY);
188     }
189 
190     return region;
191 }
192 
193 /**
194  * Sets the cell at the given coordinates.
195  */
setCell(int x,int y,const Cell & cell)196 void Tiled::TileLayer::setCell(int x, int y, const Cell &cell)
197 {
198     if (!findChunk(x, y)) {
199         if (cell == Cell::empty && !cell.checked()) {
200             return;
201         } else {
202             mBounds = mBounds.united(QRect(x - (x & CHUNK_MASK),
203                                            y - (y & CHUNK_MASK),
204                                            CHUNK_SIZE,
205                                            CHUNK_SIZE));
206         }
207     }
208 
209     Chunk &_chunk = chunk(x, y);
210 
211     if (!mUsedTilesetsDirty) {
212         Tileset *oldTileset = _chunk.cellAt(x & CHUNK_MASK, y & CHUNK_MASK).tileset();
213         Tileset *newTileset = cell.tileset();
214         if (oldTileset != newTileset) {
215             if (oldTileset)
216                 mUsedTilesetsDirty = true;
217             else if (newTileset)
218                 mUsedTilesets.insert(newTileset->sharedPointer());
219         }
220     }
221 
222     _chunk.setCell(x & CHUNK_MASK, y & CHUNK_MASK, cell);
223 }
224 
copy(const QRegion & region) const225 std::unique_ptr<TileLayer> TileLayer::copy(const QRegion &region) const
226 {
227     const QRect regionBounds = region.boundingRect();
228     const QRegion regionWithContents = region.intersected(mBounds);
229 
230     auto copied = std::make_unique<TileLayer>(QString(),
231                                               0, 0,
232                                               regionBounds.width(), regionBounds.height());
233 
234 #if QT_VERSION < 0x050800
235     const auto rects = regionWithContents.rects();
236     for (const QRect &rect : rects) {
237 #else
238     for (const QRect &rect : regionWithContents) {
239 #endif
240         for (int x = rect.left(); x <= rect.right(); ++x)
241             for (int y = rect.top(); y <= rect.bottom(); ++y)
242                 copied->setCell(x - regionBounds.x(),
243                                 y - regionBounds.y(),
244                                 cellAt(x, y));
245     }
246 
247     return copied;
248 }
249 
250 void TileLayer::merge(QPoint pos, const TileLayer *layer)
251 {
252     // Determine the overlapping area
253     QRect area = QRect(pos, QSize(layer->width(), layer->height()));
254     area &= QRect(0, 0, width(), height());
255 
256     for (int y = area.top(); y <= area.bottom(); ++y) {
257         for (int x = area.left(); x <= area.right(); ++x) {
258             const Cell &cell = layer->cellAt(x - pos.x(),
259                                              y - pos.y());
260             if (!cell.isEmpty())
261                 setCell(x, y, cell);
262         }
263     }
264 }
265 
266 void TileLayer::setCells(int x, int y, const TileLayer *layer,
267                          const QRegion &area)
268 {
269 #if QT_VERSION < 0x050800
270     const auto rects = area.rects();
271     for (const QRect &rect : rects)
272 #else
273     for (const QRect &rect : area)
274 #endif
275         for (int _x = rect.left(); _x <= rect.right(); ++_x)
276             for (int _y = rect.top(); _y <= rect.bottom(); ++_y)
277                 setCell(_x, _y, layer->cellAt(_x - x, _y - y));
278 }
279 
280 /**
281  * Sets the tiles in the given \a area to \a tile. Flipping flags are
282  * preserved.
283  */
284 void TileLayer::setTiles(const QRegion &area, Tile *tile)
285 {
286     Q_ASSERT(area.subtracted(QRegion(0, 0, mWidth, mHeight)).isEmpty());
287 
288 #if QT_VERSION < 0x050800
289     const auto rects = area.rects();
290     for (const QRect &rect : rects) {
291 #else
292     for (const QRect &rect : area) {
293 #endif
294         for (int x = rect.left(); x <= rect.right(); ++x) {
295             for (int y = rect.top(); y <= rect.bottom(); ++y) {
296                 Cell cell = cellAt(x, y);
297                 cell.setTile(tile);
298                 setCell(x, y, cell);
299             }
300         }
301     }
302 }
303 
304 void TileLayer::erase(const QRegion &region)
305 {
306     const QRegion regionWithContents = region.intersected(mBounds);
307 
308 #if QT_VERSION < 0x050800
309     const auto rects = regionWithContents.rects();
310     for (const QRect &rect : rects)
311 #else
312     for (const QRect &rect : regionWithContents)
313 #endif
314         for (int x = rect.left(); x <= rect.right(); ++x)
315             for (int y = rect.top(); y <= rect.bottom(); ++y)
316                 setCell(x, y, Cell::empty);
317 }
318 
319 /**
320  * Clears all tiles from this layer.
321  */
322 void TileLayer::clear()
323 {
324     mChunks.clear();
325     mBounds = QRect();
326     mUsedTilesets.clear();
327     mUsedTilesetsDirty = false;
328 }
329 
330 void TileLayer::flip(FlipDirection direction)
331 {
332     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, mWidth, mHeight);
333 
334     Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically);
335 
336     QHashIterator<QPoint, Chunk> it(mChunks);
337     while (it.hasNext()) {
338         it.next();
339         for (int y = 0; y < CHUNK_SIZE; ++y) {
340             for (int x = 0; x < CHUNK_SIZE; ++x) {
341                 int _x = it.key().x() * CHUNK_SIZE + x;
342                 int _y = it.key().y() * CHUNK_SIZE + y;
343 
344                 Cell dest(it.value().cellAt(x, y));
345 
346                 if (dest.isEmpty())
347                     continue;
348 
349                 if (direction == FlipHorizontally) {
350                     dest.setFlippedHorizontally(!dest.flippedHorizontally());
351                     newLayer->setCell(mWidth - _x - 1, _y, dest);
352                 } else if (direction == FlipVertically) {
353                     dest.setFlippedVertically(!dest.flippedVertically());
354                     newLayer->setCell(_x, mHeight - _y - 1, dest);
355                 }
356             }
357         }
358     }
359 
360     mChunks = newLayer->mChunks;
361     mBounds = newLayer->mBounds;
362 }
363 
364 void TileLayer::flipHexagonal(FlipDirection direction)
365 {
366     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, mWidth, mHeight);
367 
368     Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically);
369 
370     // for more info see impl "void TileLayer::rotateHexagonal(RotateDirection direction)"
371     static constexpr unsigned char flipMaskH[16] = { 8, 6, 5, 4, 12, 2, 1, 0, 0, 14, 13, 12, 4, 10, 9, 8 }; // [0,15]<=>[8,7]; 2<=>5; 1<=>6; [12,3]<=>[4,11]; 14<=>9; 13<=>10;
372     static constexpr unsigned char flipMaskV[16] = { 4, 10, 9, 8, 0, 14, 13, 12, 12, 2, 1, 0, 8, 6, 5, 4 }; // [0,15]<=>[4,11]; 2<=>9; 1<=>10; [12,3]<=>[8,7]; 14<=>5; 13<=>6;
373 
374     const unsigned char (&flipMask)[16] = (direction == FlipHorizontally ? flipMaskH : flipMaskV);
375 
376     QHashIterator<QPoint, Chunk> it(mChunks);
377     while (it.hasNext()) {
378         it.next();
379         for (int y = 0; y < CHUNK_SIZE; ++y) {
380             for (int x = 0; x < CHUNK_SIZE; ++x) {
381                 int _x = it.key().x() * CHUNK_SIZE + x;
382                 int _y = it.key().y() * CHUNK_SIZE + y;
383 
384                 Cell dest(it.value().cellAt(x, y));
385 
386                 if (dest.isEmpty())
387                     continue;
388 
389                 unsigned char mask =
390                         (static_cast<unsigned char>(dest.flippedHorizontally()) << 3) |
391                         (static_cast<unsigned char>(dest.flippedVertically()) << 2) |
392                         (static_cast<unsigned char>(dest.flippedAntiDiagonally()) << 1) |
393                         (static_cast<unsigned char>(dest.rotatedHexagonal120()) << 0);
394 
395                 mask = flipMask[mask];
396 
397                 dest.setFlippedHorizontally((mask & 8) != 0);
398                 dest.setFlippedVertically((mask & 4) != 0);
399                 dest.setFlippedAntiDiagonally((mask & 2) != 0);
400                 dest.setRotatedHexagonal120((mask & 1) != 0);
401 
402                 if (direction == FlipHorizontally)
403                     newLayer->setCell(mWidth - _x - 1, _y, dest);
404                 else
405                     newLayer->setCell(_x, mHeight - _y - 1, dest);
406             }
407         }
408     }
409 
410     mChunks = newLayer->mChunks;
411     mBounds = newLayer->mBounds;
412 }
413 
414 void TileLayer::rotate(RotateDirection direction)
415 {
416     constexpr unsigned char rotateRightMask[8] = { 5, 4, 1, 0, 7, 6, 3, 2 };
417     constexpr unsigned char rotateLeftMask[8]  = { 3, 2, 7, 6, 1, 0, 5, 4 };
418 
419     const unsigned char (&rotateMask)[8] =
420             (direction == RotateRight) ? rotateRightMask : rotateLeftMask;
421 
422     int newWidth = mHeight;
423     int newHeight = mWidth;
424     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, newWidth, newHeight);
425 
426     QHashIterator<QPoint, Chunk> it(mChunks);
427     while (it.hasNext()) {
428         it.next();
429         for (int y = 0; y < CHUNK_SIZE; ++y) {
430             for (int x = 0; x < CHUNK_SIZE; ++x) {
431                 int _x = it.key().x() * CHUNK_SIZE + x;
432                 int _y = it.key().y() * CHUNK_SIZE + y;
433 
434                 Cell dest(it.value().cellAt(x, y));
435 
436                 if (dest.isEmpty())
437                     continue;
438 
439                 unsigned char mask =
440                         (dest.flippedHorizontally() << 2) |
441                         (dest.flippedVertically() << 1) |
442                         (dest.flippedAntiDiagonally() << 0);
443 
444                 mask = rotateMask[mask];
445 
446                 dest.setFlippedHorizontally((mask & 4) != 0);
447                 dest.setFlippedVertically((mask & 2) != 0);
448                 dest.setFlippedAntiDiagonally((mask & 1) != 0);
449 
450                 if (direction == RotateRight)
451                     newLayer->setCell(mHeight - _y - 1, _x, dest);
452                 else
453                     newLayer->setCell(_y, mWidth - _x - 1, dest);
454             }
455         }
456     }
457 
458     mWidth = newWidth;
459     mHeight = newHeight;
460     mChunks = newLayer->mChunks;
461     mBounds = newLayer->mBounds;
462 }
463 
464 void TileLayer::rotateHexagonal(RotateDirection direction, Map *map)
465 {
466     Map::StaggerIndex staggerIndex = map->staggerIndex();
467     Map::StaggerAxis staggerAxis = map->staggerAxis();
468 
469     Hex bottomRight(mWidth, mHeight, staggerIndex, staggerAxis);
470     Hex topRight(mWidth, 0, staggerIndex, staggerAxis);
471     Hex center(mWidth / 2, mHeight / 2, staggerIndex, staggerAxis);
472 
473     bottomRight -= center;
474     topRight -= center;
475 
476     bottomRight.rotate(RotateRight);
477     topRight.rotate(RotateRight);
478 
479     int newWidth = topRight.toStaggered(staggerIndex, staggerAxis).x() * 2 + 2;
480     int newHeight = bottomRight.toStaggered(staggerIndex, staggerAxis).y() * 2 + 2;
481     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, newWidth, newHeight);
482 
483     Hex newCenter(newWidth / 2, newHeight / 2, staggerIndex, staggerAxis);
484 
485     /* https://github.com/bjorn/tiled/pull/1447
486 
487   0 or 15     0: None or (Rotated60 | Rotated120 | FlippedVertically | FlippedHorizontally)
488      2       60: Rotated60
489      1      120: Rotated120
490  12 or 3    180: (FlippedHorizontally | FlippedVertically) or (Rotated60 | Rotated120)
491     14      240: Rotated60 | FlippedHorizontally | FlippedVertically
492     13      300: Rotated120 | FlippedHorizontally | FlippedVertically
493 
494   8 or 7      0: FlippedHorizontally or (Rotated60 | Rotated120 | FlippedVertically)
495     10       60: Rotated60 | FlippedHorizontally
496      9      120: Rotated120 | FlippedHorizontally
497   4 or 11   180: (FlippedVertically) or (Rotated60 | Rotated120 | FlippedHorizontally)
498      6      240: Rotated60 | FlippedVertically
499      5      300: Rotated120 | FlippedVertically
500 
501     */
502 
503     static constexpr unsigned char rotateRightMask[16] = { 2, 12, 1, 14, 6, 8, 5, 10, 10,  4, 9, 0, 14,  0, 13,  2 }; // [0,15]->2->1->[12,3]->14->13; [8,7]->10->9->[4,11]->6->5;
504     static constexpr unsigned char rotateLeftMask[16]  = { 13, 2, 0,  1, 9, 6, 4,  5,  5, 10, 8, 9,  1, 14, 12, 13 }; // [0,15]->13->14->[12,3]->1->2; [8,7]->5->6->[4,11]->9->10;
505 
506     const unsigned char (&rotateMask)[16] =
507             (direction == RotateRight) ? rotateRightMask : rotateLeftMask;
508 
509     QHashIterator<QPoint, Chunk> it(mChunks);
510     while (it.hasNext()) {
511         it.next();
512         for (int y = 0; y < CHUNK_SIZE; ++y) {
513             for (int x = 0; x < CHUNK_SIZE; ++x) {
514                 int _x = it.key().x() * CHUNK_SIZE + x;
515                 int _y = it.key().y() * CHUNK_SIZE + y;
516 
517                 Cell dest(it.value().cellAt(x, y));
518 
519                 if (dest.isEmpty())
520                     continue;
521 
522                 unsigned char mask =
523                         (static_cast<unsigned char>(dest.flippedHorizontally()) << 3) |
524                         (static_cast<unsigned char>(dest.flippedVertically()) << 2) |
525                         (static_cast<unsigned char>(dest.flippedAntiDiagonally()) << 1) |
526                         (static_cast<unsigned char>(dest.rotatedHexagonal120()) << 0);
527 
528                 mask = rotateMask[mask];
529 
530                 dest.setFlippedHorizontally((mask & 8) != 0);
531                 dest.setFlippedVertically((mask & 4) != 0);
532                 dest.setFlippedAntiDiagonally((mask & 2) != 0);
533                 dest.setRotatedHexagonal120((mask & 1) != 0);
534 
535                 Hex rotatedHex(_x, _y, staggerIndex, staggerAxis);
536                 rotatedHex -= center;
537                 rotatedHex.rotate(direction);
538                 rotatedHex += newCenter;
539 
540                 QPoint rotatedPoint = rotatedHex.toStaggered(staggerIndex, staggerAxis);
541 
542                 newLayer->setCell(rotatedPoint.x(), rotatedPoint.y(), dest);
543             }
544         }
545     }
546 
547     mWidth = newWidth;
548     mHeight = newHeight;
549     mChunks = newLayer->mChunks;
550     mBounds = newLayer->mBounds;
551 
552     QRect filledRect = region().boundingRect();
553 
554     if (staggerAxis == Map::StaggerY) {
555         if (filledRect.y() & 1)
556             map->invertStaggerIndex();
557     } else {
558         if (filledRect.x() & 1)
559             map->invertStaggerIndex();
560     }
561 
562     resize(filledRect.size(), -filledRect.topLeft());
563 }
564 
565 
566 QSet<SharedTileset> TileLayer::usedTilesets() const
567 {
568     if (mUsedTilesetsDirty) {
569         QSet<SharedTileset> tilesets;
570 
571         for (const Chunk &chunk : mChunks) {
572             for (const Cell &cell : chunk)
573                 if (const Tile *tile = cell.tile())
574                     tilesets.insert(tile->sharedTileset());
575         }
576 
577         mUsedTilesets.swap(tilesets);
578         mUsedTilesetsDirty = false;
579     }
580 
581     return mUsedTilesets;
582 }
583 
584 bool TileLayer::hasCell(std::function<bool (const Cell &)> condition) const
585 {
586     for (const Chunk &chunk : mChunks) {
587         if (chunk.hasCell(condition))
588             return true;
589     }
590 
591     return false;
592 }
593 
594 bool TileLayer::referencesTileset(const Tileset *tileset) const
595 {
596     return usedTilesets().contains(tileset->sharedPointer());
597 }
598 
599 void TileLayer::removeReferencesToTileset(Tileset *tileset)
600 {
601     for (Chunk &chunk : mChunks)
602         chunk.removeReferencesToTileset(tileset);
603 
604     mUsedTilesets.remove(tileset->sharedPointer());
605 }
606 
607 void TileLayer::replaceReferencesToTileset(Tileset *oldTileset,
608                                            Tileset *newTileset)
609 {
610     for (Chunk &chunk : mChunks)
611         chunk.replaceReferencesToTileset(oldTileset, newTileset);
612 
613     if (mUsedTilesets.remove(oldTileset->sharedPointer()))
614         mUsedTilesets.insert(newTileset->sharedPointer());
615 }
616 
617 void TileLayer::resize(QSize size, QPoint offset)
618 {
619     if (this->size() == size && offset.isNull())
620         return;
621 
622     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, size.width(), size.height());
623 
624     // Copy over the preserved part
625     QRect area = mBounds.translated(offset).intersected(newLayer->rect());
626     for (int y = area.top(); y <= area.bottom(); ++y)
627         for (int x = area.left(); x <= area.right(); ++x)
628             newLayer->setCell(x, y, cellAt(x - offset.x(), y - offset.y()));
629 
630     mChunks = newLayer->mChunks;
631     mBounds = newLayer->mBounds;
632     setSize(size);
633 }
634 
635 static int clampWrap(int value, int min, int max)
636 {
637     int v = value - min;
638     int d = max - min;
639     return (v < 0 ? (v + 1) % d + d - 1 : v % d) + min;
640 }
641 
642 void TileLayer::offsetTiles(QPoint offset,
643                             QRect bounds,
644                             bool wrapX, bool wrapY)
645 {
646     if (offset.isNull())
647         return;
648 
649     const std::unique_ptr<TileLayer> newLayer(clone());
650 
651     for (int y = bounds.top(); y <= bounds.bottom(); ++y) {
652         for (int x = bounds.left(); x <= bounds.right(); ++x) {
653             // Get position to pull tile value from
654             int oldX = x - offset.x();
655             int oldY = y - offset.y();
656 
657             // Wrap x value that will be pulled from
658             if (wrapX)
659                 oldX = clampWrap(oldX, bounds.left(), bounds.right() + 1);
660 
661             // Wrap y value that will be pulled from
662             if (wrapY)
663                 oldY = clampWrap(oldY, bounds.top(), bounds.bottom() + 1);
664 
665             // Set the new tile
666             if (bounds.contains(oldX, oldY))
667                 newLayer->setCell(x, y, cellAt(oldX, oldY));
668             else
669                 newLayer->setCell(x, y, Cell::empty);
670         }
671     }
672 
673     mChunks = newLayer->mChunks;
674     mBounds = newLayer->mBounds;
675 }
676 
677 void TileLayer::offsetTiles(QPoint offset)
678 {
679     const auto newLayer = std::make_unique<TileLayer>(QString(), 0, 0, 0, 0);
680 
681     // Process only the allocated chunks
682     QHashIterator<QPoint, Chunk> it(mChunks);
683     while (it.hasNext()) {
684         it.next();
685 
686         const QPoint p = it.key();
687         const Chunk &chunk = it.value();
688         const QRect r(p.x() * CHUNK_SIZE,
689                       p.y() * CHUNK_SIZE,
690                       CHUNK_SIZE, CHUNK_SIZE);
691 
692         for (int y = r.top(); y <= r.bottom(); ++y) {
693             for (int x = r.left(); x <= r.right(); ++x) {
694                 int newX = x + offset.x();
695                 int newY = y + offset.y();
696                 newLayer->setCell(newX, newY, chunk.cellAt(x - r.left(), y - r.top()));
697             }
698         }
699     }
700 
701     mChunks = newLayer->mChunks;
702     mBounds = newLayer->mBounds;
703 }
704 
705 bool TileLayer::canMergeWith(const Layer *other) const
706 {
707     return other->isTileLayer();
708 }
709 
710 Layer *TileLayer::mergedWith(const Layer *other) const
711 {
712     Q_ASSERT(canMergeWith(other));
713 
714     const TileLayer *o = static_cast<const TileLayer*>(other);
715     const QRect unitedRect = rect().united(o->rect());
716     const QPoint offset = position() - unitedRect.topLeft();
717 
718     TileLayer *merged = clone();
719     merged->resize(unitedRect.size(), offset);
720     merged->merge(o->position() - unitedRect.topLeft(), o);
721     return merged;
722 }
723 
724 QRegion TileLayer::computeDiffRegion(const TileLayer *other) const
725 {
726     QRegion ret;
727 
728     const int dx = other->x() - mX;
729     const int dy = other->y() - mY;
730 
731     const QRect r = bounds().united(other->bounds()).translated(-position());
732 
733     for (int y = r.top(); y <= r.bottom(); ++y) {
734         for (int x = r.left(); x <= r.right(); ++x) {
735             if (cellAt(x, y) != other->cellAt(x - dx, y - dy)) {
736                 const int rangeStart = x;
737                 while (x <= r.right() &&
738                        cellAt(x, y) != other->cellAt(x - dx, y - dy)) {
739                     ++x;
740                 }
741                 const int rangeEnd = x;
742                 ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1);
743             }
744         }
745     }
746 
747     return ret;
748 }
749 
750 bool TileLayer::isEmpty() const
751 {
752     for (const Chunk &chunk : mChunks)
753         if (!chunk.isEmpty())
754             return false;
755 
756     return true;
757 }
758 
759 static bool compareRectPos(const QRect &a, const QRect &b)
760 {
761     if (a.y() != b.y())
762         return a.y() < b.y();
763     return a.x() < b.x();
764 }
765 
766 /**
767  * Returns a list of rectangles that cover all the used area of this layer.
768  * The list is sorted by the top-left of each rectangle.
769  *
770  * This function is used to determine the chunks to write when saving a tile
771  * layer.
772  */
773 QVector<QRect> TileLayer::sortedChunksToWrite(QSize chunkSize) const
774 {
775     QVector<QRect> chunksToWrite;
776     QSet<QPoint> existingChunks;
777 
778     bool isNativeChunkSize = (chunkSize.width() == CHUNK_SIZE &&
779                               chunkSize.height() == CHUNK_SIZE);
780 
781     if (isNativeChunkSize)
782         chunksToWrite.reserve(mChunks.size());
783 
784     QHashIterator<QPoint, Chunk> it(mChunks);
785     while (it.hasNext()) {
786         const Chunk &chunk = it.next().value();
787         if (chunk.isEmpty())
788             continue;
789 
790         const QPoint &p = it.key();
791 
792         if (isNativeChunkSize) {
793             // If the desired chunk size is equal to our native chunk size,
794             // then we just we just have to iterate our chunk list and return
795             // the bounds of each chunk.
796             chunksToWrite.append(QRect(p.x() * CHUNK_SIZE,
797                                        p.y() * CHUNK_SIZE,
798                                        CHUNK_SIZE, CHUNK_SIZE));
799         } else {
800             // If the desired chunk size is not the native size, we have to do
801             // a bit of extra work and "rearrange" chunks as we iterate our
802             // list. We do this by iterating every cell in a chunk. If it's not
803             // empty, we check what chunk it should go into with the new chunk
804             // size. If that chunk doesn't exist yet, we create it.
805             //
806             // NOTE: Rather than checking every cell in every chunk, we could
807             // also just test which "new" chunks our "old" chunk would
808             // intersect with and return all of those, this would be faster.
809             // However, that way we could end up with completely empty chunks,
810             // so we'll take the slower route and iterate all cells instead to
811             // avoid that.
812             int oldChunkStartX = p.x() * CHUNK_SIZE;
813             int oldChunkStartY = p.y() * CHUNK_SIZE;
814 
815             for (int y = 0; y < CHUNK_SIZE; ++y) {
816                 for (int x = 0; x < CHUNK_SIZE; ++x) {
817                     const Cell &cell = chunk.cellAt(x, y);
818 
819                     if (!cell.isEmpty()) {
820                         int tileX = oldChunkStartX + x;
821                         int tileY = oldChunkStartY + y;
822 
823                         // Nasty conditionals because of potentially negative
824                         // chunk start position. Modulo with negative numbers
825                         // is weird and unintuitive in C++...
826                         int moduloX = tileX % chunkSize.width();
827                         int newChunkStartX = tileX - (moduloX < 0 ? moduloX + chunkSize.width() : moduloX);
828                         int moduloY = tileY % chunkSize.height();
829                         int newChunkStartY = tileY - (moduloY < 0 ? moduloY + chunkSize.height() : moduloY);
830                         QPoint startPoint(newChunkStartX, newChunkStartY);
831 
832                         if (!existingChunks.contains(startPoint)) {
833                             existingChunks.insert(startPoint);
834                             chunksToWrite.append(QRect(newChunkStartX, newChunkStartY, chunkSize.width(), chunkSize.height()));
835                         }
836                     }
837                 }
838             }
839         }
840     }
841 
842     std::sort(chunksToWrite.begin(), chunksToWrite.end(), compareRectPos);
843 
844     return chunksToWrite;
845 }
846 
847 /**
848  * Returns a duplicate of this TileLayer.
849  *
850  * \sa Layer::clone()
851  */
852 TileLayer *TileLayer::clone() const
853 {
854     return initializeClone(new TileLayer(mName, mX, mY, mWidth, mHeight));
855 }
856 
857 TileLayer *TileLayer::initializeClone(TileLayer *clone) const
858 {
859     Layer::initializeClone(clone);
860     clone->mChunks = mChunks;
861     clone->mBounds = mBounds;
862     clone->mUsedTilesets = mUsedTilesets;
863     clone->mUsedTilesetsDirty = mUsedTilesetsDirty;
864     return clone;
865 }
866 
867 #include "moc_tilelayer.cpp"
868