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 &parameters)
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