1 /*
2 * map.cpp
3 * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4 * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
5 * Copyright 2010, Andrew G. Crowell <overkill9999@gmail.com>
6 *
7 * This file is part of libtiled.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
22 * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
28 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "map.h"
32
33 #include "layer.h"
34 #include "objectgroup.h"
35 #include "objecttemplate.h"
36 #include "tile.h"
37 #include "tilelayer.h"
38 #include "mapobject.h"
39
40 #include <QtMath>
41
42 using namespace Tiled;
43
Map()44 Map::Map()
45 : Map(Parameters())
46 {
47 }
48
Map(const Parameters & parameters)49 Map::Map(const Parameters ¶meters)
50 : Object(MapType)
51 , mParameters(parameters)
52 {
53 }
54
Map(Orientation orientation,int width,int height,int tileWidth,int tileHeight)55 Map::Map(Orientation orientation,
56 int width, int height,
57 int tileWidth, int tileHeight)
58 : Map()
59 {
60 mParameters.orientation = orientation;
61 mParameters.width = width;
62 mParameters.height = height;
63 mParameters.tileWidth = tileWidth;
64 mParameters.tileHeight = tileHeight;
65 }
66
~Map()67 Map::~Map()
68 {
69 qDeleteAll(mLayers);
70 }
71
72 /**
73 * Returns the margins that have to be taken into account when figuring
74 * out which part of the map to repaint after changing some tiles.
75 */
drawMargins() const76 QMargins Map::drawMargins() const
77 {
78 if (mDrawMarginsDirty)
79 recomputeDrawMargins();
80
81 return mDrawMargins;
82 }
83
84 /**
85 * Computes the extra margins due to layer offsets. These need to be taken into
86 * account when determining the bounding rect of the map for example.
87 */
computeLayerOffsetMargins() const88 QMargins Map::computeLayerOffsetMargins() const
89 {
90 QMargins offsetMargins;
91
92 for (const Layer *layer : allLayers()) {
93 if (layer->isGroupLayer())
94 continue;
95
96 const QPointF offset = layer->totalOffset();
97 offsetMargins = maxMargins(QMargins(qCeil(-offset.x()),
98 qCeil(-offset.y()),
99 qCeil(offset.x()),
100 qCeil(offset.y())),
101 offsetMargins);
102 }
103
104 return offsetMargins;
105 }
106
107 /**
108 * Recomputes the draw margins for this map and each of its tilesets. Needed
109 * after the tile offset of a tileset has changed for example.
110 */
recomputeDrawMargins() const111 void Map::recomputeDrawMargins() const
112 {
113 int maxTileSize = 0;
114 QMargins offsetMargins;
115
116 for (const SharedTileset &tileset : mTilesets) {
117 const QPoint offset = tileset->tileOffset();
118 const QSize tileSize = tileset->tileSize();
119
120 maxTileSize = std::max(maxTileSize, std::max(tileSize.width(),
121 tileSize.height()));
122
123 offsetMargins = maxMargins(QMargins(-offset.x(),
124 -offset.y(),
125 offset.x(),
126 offset.y()),
127 offsetMargins);
128 }
129
130 // We subtract the tile size of the map, since that part does not
131 // contribute to additional margin.
132 mDrawMargins = QMargins(offsetMargins.left(),
133 offsetMargins.top() + maxTileSize - tileHeight(),
134 offsetMargins.right() + maxTileSize - tileWidth(),
135 offsetMargins.bottom());
136
137 mDrawMarginsDirty = false;
138 }
139
140 /**
141 * Convenience function that returns the number of layers of this map that
142 * match the given \a type.
143 */
layerCount(Layer::TypeFlag type) const144 int Map::layerCount(Layer::TypeFlag type) const
145 {
146 int count = 0;
147 LayerIterator iterator(this, type);
148 while (iterator.next())
149 count++;
150 return count;
151 }
152
addLayer(Layer * layer)153 void Map::addLayer(Layer *layer)
154 {
155 adoptLayer(*layer);
156 mLayers.append(layer);
157 }
158
159 /**
160 * Returns the index of the layer given by \a layerName, or -1 if no
161 * layer with that name is found.
162 *
163 * The second optional parameter specifies the layer types which are
164 * searched.
165 *
166 * @deprecated Does not support group layers. Use findLayer() instead.
167 */
indexOfLayer(const QString & layerName,int layerTypes) const168 int Map::indexOfLayer(const QString &layerName, int layerTypes) const
169 {
170 for (int index = 0; index < mLayers.size(); index++)
171 if (layerAt(index)->name() == layerName
172 && (layerTypes & layerAt(index)->layerType()))
173 return index;
174
175 return -1;
176 }
177
178 /**
179 * Returns the first layer with the given \a name, or nullptr if no
180 * layer with that name is found.
181 *
182 * The second optional parameter specifies the layer types which are
183 * searched.
184 */
findLayer(const QString & name,int layerTypes) const185 Layer *Map::findLayer(const QString &name, int layerTypes) const
186 {
187 LayerIterator it(this, layerTypes);
188 while (Layer *layer = it.next())
189 if (layer->name() == name)
190 return layer;
191 return nullptr;
192 }
193
194 /**
195 * Adds a layer to this map, inserting it at the given index.
196 */
insertLayer(int index,Layer * layer)197 void Map::insertLayer(int index, Layer *layer)
198 {
199 adoptLayer(*layer);
200 mLayers.insert(index, layer);
201 }
202
adoptLayer(Layer & layer)203 void Map::adoptLayer(Layer &layer)
204 {
205 if (layer.id() == 0)
206 layer.setId(takeNextLayerId());
207
208 layer.setMap(this);
209
210 if (ObjectGroup *group = layer.asObjectGroup())
211 initializeObjectIds(*group);
212 }
213
214 /**
215 * Removes the layer at the given index from this map and returns it.
216 * The caller becomes responsible for the lifetime of this layer.
217 */
takeLayerAt(int index)218 Layer *Map::takeLayerAt(int index)
219 {
220 Layer *layer = mLayers.takeAt(index);
221 layer->setMap(nullptr);
222 return layer;
223 }
224
225 /**
226 * Adds a tileset to this map. The map does not take ownership over its
227 * tilesets, this is merely for keeping track of which tilesets are used by
228 * the map, and their saving order.
229 *
230 * @param tileset the tileset to add
231 * @return whether the tileset wasn't already part of the map
232 */
addTileset(const SharedTileset & tileset)233 bool Map::addTileset(const SharedTileset &tileset)
234 {
235 if (mTilesets.contains(tileset))
236 return false;
237
238 mTilesets.append(tileset);
239 invalidateDrawMargins();
240 return true;
241 }
242
243 /**
244 * Convenience function to be used together with Layer::usedTilesets()
245 */
addTilesets(const QSet<SharedTileset> & tilesets)246 void Map::addTilesets(const QSet<SharedTileset> &tilesets)
247 {
248 for (const SharedTileset &tileset : tilesets)
249 addTileset(tileset);
250 }
251
252 /**
253 * Inserts \a tileset at \a index in the list of tilesets used by this map.
254 */
insertTileset(int index,const SharedTileset & tileset)255 void Map::insertTileset(int index, const SharedTileset &tileset)
256 {
257 Q_ASSERT(!mTilesets.contains(tileset));
258 mTilesets.insert(index, tileset);
259 invalidateDrawMargins();
260 }
261
262 /**
263 * Returns the index of the given \a tileset, or -1 if it is not used in
264 * this map.
265 */
indexOfTileset(const SharedTileset & tileset) const266 int Map::indexOfTileset(const SharedTileset &tileset) const
267 {
268 return mTilesets.indexOf(tileset);
269 }
270
271 /**
272 * Removes the tileset at \a index from this map.
273 *
274 * \warning Does not make sure that this map no longer refers to tiles from
275 * the removed tileset!
276 *
277 * \sa addTileset
278 */
removeTilesetAt(int index)279 void Map::removeTilesetAt(int index)
280 {
281 mTilesets.remove(index);
282 invalidateDrawMargins();
283 }
284
285 /**
286 * Replaces all tiles from \a oldTileset with tiles from \a newTileset.
287 * Also replaces the old tileset with the new tileset in the list of
288 * tilesets.
289 *
290 * @return whether the new tileset was added to the map
291 */
replaceTileset(const SharedTileset & oldTileset,const SharedTileset & newTileset)292 bool Map::replaceTileset(const SharedTileset &oldTileset,
293 const SharedTileset &newTileset)
294 {
295 Q_ASSERT(oldTileset != newTileset);
296
297 const int index = mTilesets.indexOf(oldTileset);
298 Q_ASSERT(index != -1);
299
300 const auto &layers = mLayers;
301 for (Layer *layer : layers) {
302 layer->replaceReferencesToTileset(oldTileset.data(),
303 newTileset.data());
304 }
305
306 invalidateDrawMargins();
307
308 if (mTilesets.contains(newTileset)) {
309 mTilesets.remove(index);
310 return false;
311 } else {
312 mTilesets.replace(index, newTileset);
313 return true;
314 }
315 }
316
317 /**
318 * Computes the tilesets that are used by this map.
319 */
usedTilesets() const320 QSet<SharedTileset> Map::usedTilesets() const
321 {
322 QSet<SharedTileset> tilesets;
323
324 // Only top-level layers need to be considered, since GroupLayer goes over
325 // its children
326 for (const Layer *layer : mLayers)
327 tilesets |= layer->usedTilesets();
328
329 return tilesets;
330 }
331
332 /**
333 * Returns whether the given \a tileset is used by any tile layer of this
334 * map.
335 */
isTilesetUsed(const Tileset * tileset) const336 bool Map::isTilesetUsed(const Tileset *tileset) const
337 {
338 for (const Layer *layer : mLayers)
339 if (layer->referencesTileset(tileset))
340 return true;
341
342 return false;
343 }
344
clone() const345 std::unique_ptr<Map> Map::clone() const
346 {
347 auto o = std::make_unique<Map>(mParameters);
348 o->fileName = fileName;
349 o->exportFileName = exportFileName;
350 o->exportFormat = exportFormat;
351 o->mEditorSettings = mEditorSettings;
352 o->mDrawMargins = mDrawMargins;
353 o->mDrawMarginsDirty = mDrawMarginsDirty;
354 for (const Layer *layer : mLayers) {
355 Layer *clone = layer->clone();
356 clone->setMap(o.get());
357 o->mLayers.append(clone);
358 }
359 o->mTilesets = mTilesets;
360 o->mNextLayerId = mNextLayerId;
361 o->mNextObjectId = mNextObjectId;
362 o->setProperties(properties());
363 return o;
364 }
365
366 /**
367 * Copies the given \a tileRegion of the \a layers to \a targetMap.
368 *
369 * When there is no intersection with between the tile region and a tile
370 * layer's bounds, the layer is not added to the target map.
371 *
372 * Currently only copies tile layers.
373 */
copyLayers(const QList<Layer * > & layers,const QRegion & tileRegion,Map & targetMap) const374 void Map::copyLayers(const QList<Layer *> &layers,
375 const QRegion &tileRegion,
376 Map &targetMap) const
377 {
378 LayerIterator layerIterator(this);
379 while (Layer *layer = layerIterator.next()) {
380 switch (layer->layerType()) {
381 case Layer::TileLayerType: {
382 if (!layers.contains(layer)) // ignore unselected tile layers
383 continue;
384
385 const TileLayer *tileLayer = static_cast<const TileLayer*>(layer);
386 const QRegion area = tileRegion.intersected(tileLayer->bounds());
387 if (area.isEmpty()) // nothing to copy
388 continue;
389
390 // Copy the selected part of the layer
391 auto copyLayer = tileLayer->copy(area.translated(-tileLayer->position()));
392 copyLayer->setName(tileLayer->name());
393 copyLayer->setPosition(area.boundingRect().topLeft());
394
395 targetMap.addLayer(std::move(copyLayer));
396 break;
397 }
398 case Layer::ObjectGroupType: // todo: maybe it makes sense to group selected objects by layer
399 case Layer::ImageLayerType:
400 case Layer::GroupLayerType:
401 break; // nothing to do
402 }
403 }
404 }
405
406 /**
407 * Determines the unified content area of all tile layers and then repositions
408 * those layers to eliminate unnecessary offset. Also sets the size of the map
409 * to encompass the final tile layer contents exactly.
410 */
normalizeTileLayerPositionsAndMapSize()411 void Map::normalizeTileLayerPositionsAndMapSize()
412 {
413 LayerIterator it(this, Layer::TileLayerType);
414
415 QRect contentRect;
416 while (auto tileLayer = static_cast<TileLayer*>(it.next()))
417 contentRect |= tileLayer->region().boundingRect();
418
419 if (!contentRect.topLeft().isNull()) {
420 it.toFront();
421 while (auto tileLayer = static_cast<TileLayer*>(it.next()))
422 tileLayer->setPosition(tileLayer->position() - contentRect.topLeft());
423
424 // Adjust the stagger index when layers are moved by odd amounts
425 const int staggerOffSet = (staggerAxis() == Map::StaggerX ? contentRect.x()
426 : contentRect.y()) % 2;
427
428 setStaggerIndex(static_cast<Map::StaggerIndex>((staggerIndex() + staggerOffSet) % 2));
429 }
430
431 setWidth(contentRect.width());
432 setHeight(contentRect.height());
433 }
434
435 /**
436 * Returns a list of MapObjects to be updated in the map scene
437 */
replaceObjectTemplate(const ObjectTemplate * oldObjectTemplate,const ObjectTemplate * newObjectTemplate)438 QList<MapObject*> Map::replaceObjectTemplate(const ObjectTemplate *oldObjectTemplate,
439 const ObjectTemplate *newObjectTemplate)
440 {
441 Q_ASSERT(oldObjectTemplate != newObjectTemplate);
442
443 QList<MapObject*> changedObjects;
444
445 for (auto layer : objectGroups()) {
446 for (auto o : static_cast<ObjectGroup*>(layer)->objects()) {
447 if (o->objectTemplate() == oldObjectTemplate) {
448 o->setObjectTemplate(newObjectTemplate);
449 o->syncWithTemplate();
450 changedObjects.append(o);
451 }
452 }
453 }
454
455 return changedObjects;
456 }
457
initializeObjectIds(ObjectGroup & objectGroup)458 void Map::initializeObjectIds(ObjectGroup &objectGroup)
459 {
460 for (MapObject *o : objectGroup) {
461 if (o->id() == 0)
462 o->setId(takeNextObjectId());
463 }
464 }
465
findLayerById(int layerId) const466 Layer *Map::findLayerById(int layerId) const
467 {
468 for (Layer *layer : allLayers()) {
469 if (layer->id() == layerId)
470 return layer;
471 }
472 return nullptr;
473 }
474
findObjectById(int objectId) const475 MapObject *Map::findObjectById(int objectId) const
476 {
477 for (Layer *layer : objectGroups()) {
478 for (MapObject *mapObject : static_cast<ObjectGroup*>(layer)->objects()) {
479 if (mapObject->id() == objectId)
480 return mapObject;
481 }
482 }
483 return nullptr;
484 }
485
tileRegion() const486 QRegion Map::tileRegion() const
487 {
488 QRegion region;
489 LayerIterator it(this, Layer::TileLayerType);
490 while (auto tileLayer = static_cast<TileLayer*>(it.next()))
491 region |= tileLayer->region();
492 return region;
493 }
494
staggerAxisToString(Map::StaggerAxis staggerAxis)495 QString Tiled::staggerAxisToString(Map::StaggerAxis staggerAxis)
496 {
497 switch (staggerAxis) {
498 case Map::StaggerY:
499 return QStringLiteral("y");
500 case Map::StaggerX:
501 return QStringLiteral("x");
502 }
503 return QString();
504 }
505
staggerAxisFromString(const QString & string)506 Map::StaggerAxis Tiled::staggerAxisFromString(const QString &string)
507 {
508 Map::StaggerAxis staggerAxis = Map::StaggerY;
509 if (string == QLatin1String("x"))
510 staggerAxis = Map::StaggerX;
511 return staggerAxis;
512 }
513
staggerIndexToString(Map::StaggerIndex staggerIndex)514 QString Tiled::staggerIndexToString(Map::StaggerIndex staggerIndex)
515 {
516 switch (staggerIndex) {
517 case Map::StaggerOdd:
518 return QStringLiteral("odd");
519 case Map::StaggerEven:
520 return QStringLiteral("even");
521 }
522 return QString();
523 }
524
staggerIndexFromString(const QString & string)525 Map::StaggerIndex Tiled::staggerIndexFromString(const QString &string)
526 {
527 Map::StaggerIndex staggerIndex = Map::StaggerOdd;
528 if (string == QLatin1String("even"))
529 staggerIndex = Map::StaggerEven;
530 return staggerIndex;
531 }
532
orientationToString(Map::Orientation orientation)533 QString Tiled::orientationToString(Map::Orientation orientation)
534 {
535 switch (orientation) {
536 case Map::Unknown:
537 return QStringLiteral("unknown");
538 case Map::Orthogonal:
539 return QStringLiteral("orthogonal");
540 case Map::Isometric:
541 return QStringLiteral("isometric");
542 case Map::Staggered:
543 return QStringLiteral("staggered");
544 case Map::Hexagonal:
545 return QStringLiteral("hexagonal");
546 }
547 return QString();
548 }
549
orientationFromString(const QString & string)550 Map::Orientation Tiled::orientationFromString(const QString &string)
551 {
552 Map::Orientation orientation = Map::Unknown;
553 if (string == QLatin1String("orthogonal")) {
554 orientation = Map::Orthogonal;
555 } else if (string == QLatin1String("isometric")) {
556 orientation = Map::Isometric;
557 } else if (string == QLatin1String("staggered")) {
558 orientation = Map::Staggered;
559 } else if (string == QLatin1String("hexagonal")) {
560 orientation = Map::Hexagonal;
561 }
562 return orientation;
563 }
564
compressionToString(Map::LayerDataFormat layerDataFormat)565 QString Tiled::compressionToString(Map::LayerDataFormat layerDataFormat)
566 {
567 switch (layerDataFormat) {
568 case Map::XML:
569 case Map::Base64:
570 case Map::CSV:
571 return QString();
572 case Map::Base64Gzip:
573 return QStringLiteral("gzip");
574 case Map::Base64Zlib:
575 return QStringLiteral("zlib");
576 case Map::Base64Zstandard:
577 return QStringLiteral("zstd");
578 }
579 return QString();
580 }
581
renderOrderToString(Map::RenderOrder renderOrder)582 QString Tiled::renderOrderToString(Map::RenderOrder renderOrder)
583 {
584 switch (renderOrder) {
585 case Map::RightDown:
586 return QStringLiteral("right-down");
587 case Map::RightUp:
588 return QStringLiteral("right-up");
589 case Map::LeftDown:
590 return QStringLiteral("left-down");
591 case Map::LeftUp:
592 return QStringLiteral("left-up");
593 }
594 return QString();
595 }
596
renderOrderFromString(const QString & string)597 Map::RenderOrder Tiled::renderOrderFromString(const QString &string)
598 {
599 Map::RenderOrder renderOrder = Map::RightDown;
600 if (string == QLatin1String("right-up"))
601 renderOrder = Map::RightUp;
602 else if (string == QLatin1String("left-down"))
603 renderOrder = Map::LeftDown;
604 else if (string == QLatin1String("left-up"))
605 renderOrder = Map::LeftUp;
606 return renderOrder;
607 }
608