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 ®ion) 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 ®ion)
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