diff --git a/src/libtiled/map.cpp b/src/libtiled/map.cpp index b37e6d3ce6..a4dd9a7ab6 100644 --- a/src/libtiled/map.cpp +++ b/src/libtiled/map.cpp @@ -59,6 +59,7 @@ Map::Map(Orientation orientation, mHexSideLength(0), mStaggerAxis(StaggerY), mStaggerIndex(StaggerOdd), + mChunkSize(CHUNK_SIZE, CHUNK_SIZE), mDrawMarginsDirty(true), mLayerDataFormat(Base64Zlib), mNextLayerId(1), @@ -337,6 +338,7 @@ Map *Map::clone() const o->mStaggerAxis = mStaggerAxis; o->mStaggerIndex = mStaggerIndex; o->mBackgroundColor = mBackgroundColor; + o->mChunkSize = mChunkSize; o->mDrawMargins = mDrawMargins; o->mDrawMarginsDirty = mDrawMarginsDirty; for (const Layer *layer : mLayers) { diff --git a/src/libtiled/map.h b/src/libtiled/map.h index 7165140f1d..620bc05b39 100644 --- a/src/libtiled/map.h +++ b/src/libtiled/map.h @@ -415,6 +415,16 @@ class TILEDSHARED_EXPORT Map : public Object */ void setBackgroundColor(QColor color) { mBackgroundColor = color; } + /** + * Returns the chunk size used when saving tile layers of this map. + */ + QSize chunkSize() const { return mChunkSize; } + + /** + * Sets the chunk size used when saving tile layers of this map. + */ + void setChunkSize(QSize size) { mChunkSize = size; } + /** * Returns whether the given \a tileset is used by any tile layer of this * map. @@ -470,6 +480,7 @@ class TILEDSHARED_EXPORT Map : public Object StaggerAxis mStaggerAxis; StaggerIndex mStaggerIndex; QColor mBackgroundColor; + QSize mChunkSize; mutable QMargins mDrawMargins; mutable bool mDrawMarginsDirty; QList mLayers; diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 35b68a0f0f..f21e616d30 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -791,6 +791,14 @@ void MapReaderPrivate::readTileLayerData(TileLayer &tileLayer) mMap->setLayerDataFormat(layerDataFormat); + int chunkWidth = atts.value(QLatin1String("outputchunkwidth")).toInt(); + int chunkHeight = atts.value(QLatin1String("outputchunkheight")).toInt(); + + chunkWidth = chunkWidth == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkWidth); + chunkHeight = chunkHeight == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkHeight); + + mMap->setChunkSize(QSize(chunkWidth, chunkHeight)); + readTileLayerRect(tileLayer, layerDataFormat, encoding, diff --git a/src/libtiled/maptovariantconverter.cpp b/src/libtiled/maptovariantconverter.cpp index be07de0e6f..7587d518e6 100644 --- a/src/libtiled/maptovariantconverter.cpp +++ b/src/libtiled/maptovariantconverter.cpp @@ -91,7 +91,8 @@ QVariant MapToVariantConverter::toVariant(const Map &map, const QDir &mapDir) mapVariant[QLatin1String("tilesets")] = tilesetVariants; mapVariant[QLatin1String("layers")] = toVariant(map.layers(), - map.layerDataFormat()); + map.layerDataFormat(), + map.chunkSize()); return mapVariant; } @@ -385,14 +386,15 @@ QVariant MapToVariantConverter::toVariant(const WangColor &wangColor) const } QVariant MapToVariantConverter::toVariant(const QList &layers, - Map::LayerDataFormat format) const + Map::LayerDataFormat format, + QSize chunkSize) const { QVariantList layerVariants; for (const Layer *layer : layers) { switch (layer->layerType()) { case Layer::TileLayerType: - layerVariants << toVariant(*static_cast(layer), format); + layerVariants << toVariant(*static_cast(layer), format, chunkSize); break; case Layer::ObjectGroupType: layerVariants << toVariant(*static_cast(layer)); @@ -401,7 +403,7 @@ QVariant MapToVariantConverter::toVariant(const QList &layers, layerVariants << toVariant(*static_cast(layer)); break; case Layer::GroupLayerType: - layerVariants << toVariant(*static_cast(layer), format); + layerVariants << toVariant(*static_cast(layer), format, chunkSize); } } @@ -409,7 +411,8 @@ QVariant MapToVariantConverter::toVariant(const QList &layers, } QVariant MapToVariantConverter::toVariant(const TileLayer &tileLayer, - Map::LayerDataFormat format) const + Map::LayerDataFormat format, + QSize chunkSize) const { QVariantMap tileLayerVariant; tileLayerVariant[QLatin1String("type")] = QLatin1String("tilelayer"); @@ -446,9 +449,14 @@ QVariant MapToVariantConverter::toVariant(const TileLayer &tileLayer, } if (tileLayer.map()->infinite()) { + if (chunkSize.width() != CHUNK_SIZE || chunkSize.height() != CHUNK_SIZE) { + tileLayerVariant[QLatin1String("outputchunkwidth")] = chunkSize.width(); + tileLayerVariant[QLatin1String("outputchunkheight")] = chunkSize.height(); + } + QVariantList chunkVariants; - const auto chunks = tileLayer.sortedChunksToWrite(); + const auto chunks = tileLayer.sortedChunksToWrite(chunkSize); for (const QRect &rect : chunks) { QVariantMap chunkVariant; @@ -649,7 +657,8 @@ QVariant MapToVariantConverter::toVariant(const ImageLayer &imageLayer) const } QVariant MapToVariantConverter::toVariant(const GroupLayer &groupLayer, - Map::LayerDataFormat format) const + Map::LayerDataFormat format, + QSize chunkSize) const { QVariantMap groupLayerVariant; groupLayerVariant[QLatin1String("type")] = QLatin1String("group"); @@ -657,7 +666,8 @@ QVariant MapToVariantConverter::toVariant(const GroupLayer &groupLayer, addLayerAttributes(groupLayerVariant, groupLayer); groupLayerVariant[QLatin1String("layers")] = toVariant(groupLayer.layers(), - format); + format, + chunkSize); return groupLayerVariant; } diff --git a/src/libtiled/maptovariantconverter.h b/src/libtiled/maptovariantconverter.h index 4843d7bd97..efac069d0c 100644 --- a/src/libtiled/maptovariantconverter.h +++ b/src/libtiled/maptovariantconverter.h @@ -68,13 +68,13 @@ class TILEDSHARED_EXPORT MapToVariantConverter QVariant propertyTypesToVariant(const Properties &properties) const; QVariant toVariant(const WangSet &wangSet) const; QVariant toVariant(const WangColor &wangColor) const; - QVariant toVariant(const QList &layers, Map::LayerDataFormat format) const; - QVariant toVariant(const TileLayer &tileLayer, Map::LayerDataFormat format) const; + QVariant toVariant(const QList &layers, Map::LayerDataFormat format, QSize chunkSize) const; + QVariant toVariant(const TileLayer &tileLayer, Map::LayerDataFormat format, QSize chunkSize) const; QVariant toVariant(const ObjectGroup &objectGroup) const; QVariant toVariant(const MapObject &object) const; QVariant toVariant(const TextData &textData) const; QVariant toVariant(const ImageLayer &imageLayer) const; - QVariant toVariant(const GroupLayer &groupLayer, Map::LayerDataFormat format) const; + QVariant toVariant(const GroupLayer &groupLayer, Map::LayerDataFormat format, QSize chunkSize) const; void addTileLayerData(QVariantMap &variant, const TileLayer &tileLayer, diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index 00a504461b..159c65610d 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -85,6 +85,7 @@ class MapWriterPrivate QString mError; Map::LayerDataFormat mLayerDataFormat; bool mDtdEnabled; + QSize mChunkSize; private: void writeMap(QXmlStreamWriter &w, const Map &map); @@ -114,6 +115,7 @@ class MapWriterPrivate MapWriterPrivate::MapWriterPrivate() : mLayerDataFormat(Map::Base64Zlib) , mDtdEnabled(false) + , mChunkSize(CHUNK_SIZE, CHUNK_SIZE) , mUseAbsolutePaths(false) { } @@ -149,6 +151,7 @@ void MapWriterPrivate::writeMap(const Map *map, QIODevice *device, mMapDir = QDir(path); mUseAbsolutePaths = path.isEmpty(); mLayerDataFormat = map->layerDataFormat(); + mChunkSize = map->chunkSize(); AutoFormattingWriter writer(device); writer.writeStartDocument(); @@ -588,7 +591,12 @@ void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w, w.writeAttribute(QLatin1String("compression"), compression); if (tileLayer.map()->infinite()) { - const auto chunks = tileLayer.sortedChunksToWrite(); + if (mChunkSize.width() != CHUNK_SIZE || mChunkSize.height() != CHUNK_SIZE) { + w.writeAttribute(QLatin1String("outputchunkwidth"), QString::number(mChunkSize.width())); + w.writeAttribute(QLatin1String("outputchunkheight"), QString::number(mChunkSize.height())); + } + + const auto chunks = tileLayer.sortedChunksToWrite(mChunkSize); for (const QRect &rect : chunks) { w.writeStartElement(QLatin1String("chunk")); w.writeAttribute(QLatin1String("x"), QString::number(rect.x())); diff --git a/src/libtiled/tiled.h b/src/libtiled/tiled.h index 90cd97624f..aa1c9b9650 100644 --- a/src/libtiled/tiled.h +++ b/src/libtiled/tiled.h @@ -68,6 +68,7 @@ enum LoadingStatus { }; const int CHUNK_SIZE = 16; +const int CHUNK_SIZE_MIN = 4; const int CHUNK_MASK = CHUNK_SIZE - 1; static const char TILES_MIMETYPE[] = "application/vnd.tile.list"; diff --git a/src/libtiled/tilelayer.cpp b/src/libtiled/tilelayer.cpp index eb6d88c09c..55753eda7a 100644 --- a/src/libtiled/tilelayer.cpp +++ b/src/libtiled/tilelayer.cpp @@ -35,6 +35,8 @@ #include #include +#include + using namespace Tiled; Cell Cell::empty; @@ -760,19 +762,72 @@ static bool compareRectPos(const QRect &a, const QRect &b) * This function is used to determine the chunks to write when saving a tile * layer. */ -QVector TileLayer::sortedChunksToWrite() const +QVector TileLayer::sortedChunksToWrite(QSize chunkSize) const { QVector chunksToWrite; - chunksToWrite.reserve(mChunks.size()); + QSet existingChunks; + + bool isNativeChunkSize = (chunkSize.width() == CHUNK_SIZE && + chunkSize.height() == CHUNK_SIZE); + + if (isNativeChunkSize) + chunksToWrite.reserve(mChunks.size()); QHashIterator it(mChunks); while (it.hasNext()) { - it.next(); - if (!it.value().isEmpty()) { - const QPoint p = it.key(); + const Chunk &chunk = it.next().value(); + if (chunk.isEmpty()) + continue; + + const QPoint &p = it.key(); + + if (isNativeChunkSize) { + // If the desired chunk size is equal to our native chunk size, + // then we just we just have to iterate our chunk list and return + // the bounds of each chunk. chunksToWrite.append(QRect(p.x() * CHUNK_SIZE, p.y() * CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE)); + } else { + // If the desired chunk size is not the native size, we have to do + // a bit of extra work and "rearrange" chunks as we iterate our + // list. We do this by iterating every cell in a chunk. If it's not + // empty, we check what chunk it should go into with the new chunk + // size. If that chunk doesn't exist yet, we create it. + // + // NOTE: Rather than checking every cell in every chunk, we could + // also just test which "new" chunks our "old" chunk would + // intersect with and return all of those, this would be faster. + // However, that way we could end up with completely empty chunks, + // so we'll take the slower route and iterate all cells instead to + // avoid that. + int oldChunkStartX = p.x() * CHUNK_SIZE; + int oldChunkStartY = p.y() * CHUNK_SIZE; + + for (int y = 0; y < CHUNK_SIZE; ++y) { + for (int x = 0; x < CHUNK_SIZE; ++x) { + const Cell &cell = chunk.cellAt(x, y); + + if (!cell.isEmpty()) { + int tileX = oldChunkStartX + x; + int tileY = oldChunkStartY + y; + + // Nasty conditionals because of potentially negative + // chunk start position. Modulo with negative numbers + // is weird and unintuitive in C++... + int moduloX = tileX % chunkSize.width(); + int newChunkStartX = tileX - (moduloX < 0 ? moduloX + chunkSize.width() : moduloX); + int moduloY = tileY % chunkSize.height(); + int newChunkStartY = tileY - (moduloY < 0 ? moduloY + chunkSize.height() : moduloY); + QPoint startPoint(newChunkStartX, newChunkStartY); + + if (!existingChunks.contains(startPoint)) { + existingChunks.insert(startPoint); + chunksToWrite.append(QRect(newChunkStartX, newChunkStartY, chunkSize.width(), chunkSize.height())); + } + } + } + } } } diff --git a/src/libtiled/tilelayer.h b/src/libtiled/tilelayer.h index 8602ef510f..9aa6a0984c 100644 --- a/src/libtiled/tilelayer.h +++ b/src/libtiled/tilelayer.h @@ -520,7 +520,7 @@ class TILEDSHARED_EXPORT TileLayer : public Layer const_iterator begin() const { return const_iterator(mChunks.begin(), mChunks.end()); } const_iterator end() const { return const_iterator(mChunks.end(), mChunks.end()); } - QVector sortedChunksToWrite() const; + QVector sortedChunksToWrite(QSize chunkSize) const; protected: TileLayer *initializeClone(TileLayer *clone) const; diff --git a/src/libtiled/varianttomapconverter.cpp b/src/libtiled/varianttomapconverter.cpp index 9b7ee344c3..0ff6d2d4b4 100644 --- a/src/libtiled/varianttomapconverter.cpp +++ b/src/libtiled/varianttomapconverter.cpp @@ -541,6 +541,14 @@ std::unique_ptr VariantToMapConverter::toTileLayer(const QVariantMap } mMap->setLayerDataFormat(layerDataFormat); + int chunkWidth = variantMap[QLatin1String("outputchunkwidth")].toInt(); + int chunkHeight = variantMap[QLatin1String("outputchunkheight")].toInt(); + + chunkWidth = chunkWidth == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkWidth); + chunkHeight = chunkHeight == 0 ? CHUNK_SIZE : qMax(CHUNK_SIZE_MIN, chunkHeight); + + mMap->setChunkSize(QSize(chunkWidth, chunkHeight)); + if (dataVariant.isValid() && !dataVariant.isNull()) { if (!readTileLayerData(*tileLayer, dataVariant, layerDataFormat, QRect(startX, startY, tileLayer->width(), tileLayer->height()))) { diff --git a/src/plugins/lua/luaplugin.cpp b/src/plugins/lua/luaplugin.cpp index bf0ade8bbd..fb92d5c8b0 100644 --- a/src/plugins/lua/luaplugin.cpp +++ b/src/plugins/lua/luaplugin.cpp @@ -67,9 +67,11 @@ class LuaWriter bool embedded = true); void writeLayers(LuaTableWriter &, const QList &layers, - Tiled::Map::LayerDataFormat format); + Tiled::Map::LayerDataFormat format, + QSize chunkSize); void writeTileLayer(LuaTableWriter &, const Tiled::TileLayer *, - Tiled::Map::LayerDataFormat); + Tiled::Map::LayerDataFormat, + QSize chunkSize); void writeTileLayerData(LuaTableWriter &, const Tiled::TileLayer *, Tiled::Map::LayerDataFormat format, QRect bounds); @@ -77,7 +79,8 @@ class LuaWriter const QByteArray &key = QByteArray()); void writeImageLayer(LuaTableWriter &, const Tiled::ImageLayer *); void writeGroupLayer(LuaTableWriter &, const Tiled::GroupLayer *, - Tiled::Map::LayerDataFormat); + Tiled::Map::LayerDataFormat, + QSize chunkSize); void writeMapObject(LuaTableWriter &, const Tiled::MapObject *); static void writePolygon(LuaTableWriter &, const Tiled::MapObject *); @@ -234,7 +237,7 @@ void LuaWriter::writeMap(LuaTableWriter &writer, const Map *map) } writer.writeEndTable(); - writeLayers(writer, map->layers(), map->layerDataFormat()); + writeLayers(writer, map->layers(), map->layerDataFormat(), map->chunkSize()); writer.writeEndTable(); } @@ -415,13 +418,14 @@ void LuaWriter::writeTileset(LuaTableWriter &writer, const Tileset &tileset, void LuaWriter::writeLayers(LuaTableWriter &writer, const QList &layers, - Map::LayerDataFormat format) + Map::LayerDataFormat format, + QSize chunkSize) { writer.writeStartTable("layers"); for (const Layer *layer : layers) { switch (layer->layerType()) { case Layer::TileLayerType: - writeTileLayer(writer, static_cast(layer), format); + writeTileLayer(writer, static_cast(layer), format, chunkSize); break; case Layer::ObjectGroupType: writeObjectGroup(writer, static_cast(layer)); @@ -430,7 +434,7 @@ void LuaWriter::writeLayers(LuaTableWriter &writer, writeImageLayer(writer, static_cast(layer)); break; case Layer::GroupLayerType: - writeGroupLayer(writer, static_cast(layer), format); + writeGroupLayer(writer, static_cast(layer), format, chunkSize); break; } } @@ -439,7 +443,8 @@ void LuaWriter::writeLayers(LuaTableWriter &writer, void LuaWriter::writeTileLayer(LuaTableWriter &writer, const TileLayer *tileLayer, - Map::LayerDataFormat format) + Map::LayerDataFormat format, + QSize chunkSize) { writer.writeStartTable(); @@ -480,8 +485,13 @@ void LuaWriter::writeTileLayer(LuaTableWriter &writer, } if (tileLayer->map()->infinite()) { + if (chunkSize.width() != CHUNK_SIZE || chunkSize.height() != CHUNK_SIZE) { + writer.writeKeyAndValue("outputchunkwidth", chunkSize.width()); + writer.writeKeyAndValue("outputchunkheight", chunkSize.height()); + } + writer.writeStartTable("chunks"); - const auto chunks = tileLayer->sortedChunksToWrite(); + const auto chunks = tileLayer->sortedChunksToWrite(chunkSize); for (const QRect &rect : chunks) { writer.writeStartTable(); @@ -596,7 +606,8 @@ void LuaWriter::writeImageLayer(LuaTableWriter &writer, void LuaWriter::writeGroupLayer(LuaTableWriter &writer, const GroupLayer *groupLayer, - Map::LayerDataFormat format) + Map::LayerDataFormat format, + QSize chunkSize) { writer.writeStartTable(); @@ -612,7 +623,7 @@ void LuaWriter::writeGroupLayer(LuaTableWriter &writer, writeProperties(writer, groupLayer->properties()); - writeLayers(writer, groupLayer->layers(), format); + writeLayers(writer, groupLayer->layers(), format, chunkSize); writer.writeEndTable(); } diff --git a/src/tiled/changemapproperty.cpp b/src/tiled/changemapproperty.cpp index 85a06ce195..e76a34a9e6 100644 --- a/src/tiled/changemapproperty.cpp +++ b/src/tiled/changemapproperty.cpp @@ -68,6 +68,16 @@ ChangeMapProperty::ChangeMapProperty(MapDocument *mapDocument, { } +ChangeMapProperty::ChangeMapProperty(MapDocument *mapDocument, + QSize chunkSize) + : QUndoCommand(QCoreApplication::translate("Undo Commands", + "Change Chunk Size")) + , mMapDocument(mapDocument) + , mProperty(ChunkSize) + , mChunkSize(chunkSize) +{ +} + ChangeMapProperty::ChangeMapProperty(MapDocument *mapDocument, Map::StaggerAxis staggerAxis) : QUndoCommand(QCoreApplication::translate("Undo Commands", @@ -194,6 +204,12 @@ void ChangeMapProperty::swap() mLayerDataFormat = layerDataFormat; break; } + case ChunkSize: { + const QSize chunkSize = map->chunkSize(); + map->setChunkSize(mChunkSize); + mChunkSize = chunkSize; + break; + } } emit mMapDocument->mapChanged(); diff --git a/src/tiled/changemapproperty.h b/src/tiled/changemapproperty.h index 84e3bcbd6b..6129c9f218 100644 --- a/src/tiled/changemapproperty.h +++ b/src/tiled/changemapproperty.h @@ -42,7 +42,8 @@ class ChangeMapProperty : public QUndoCommand Orientation, RenderOrder, BackgroundColor, - LayerDataFormat + LayerDataFormat, + ChunkSize }; /** @@ -62,6 +63,14 @@ class ChangeMapProperty : public QUndoCommand * @param backgroundColor the new color to apply for the background */ ChangeMapProperty(MapDocument *mapDocument, const QColor &backgroundColor); + + /** + * Constructs a command that changes the chunk size. + * + * @param mapDocument the map document of the map + * @param chunkSize the new chunk size to use for tile layers + */ + ChangeMapProperty(MapDocument *mapDocument, QSize chunkSize); /** * Constructs a command that changes the map stagger axis. @@ -112,6 +121,7 @@ class ChangeMapProperty : public QUndoCommand MapDocument *mMapDocument; Property mProperty; QColor mBackgroundColor; + QSize mChunkSize; union { int mIntValue; Map::StaggerAxis mStaggerAxis; diff --git a/src/tiled/propertybrowser.cpp b/src/tiled/propertybrowser.cpp index 0eb2ca4015..d21fed6e78 100644 --- a/src/tiled/propertybrowser.cpp +++ b/src/tiled/propertybrowser.cpp @@ -599,6 +599,12 @@ void PropertyBrowser::addMapProperties() layerFormatProperty->setAttribute(QLatin1String("enumNames"), mLayerFormatNames); + QtVariantProperty *chunkWidthProperty = addProperty(ChunkWidthProperty, QVariant::Int, tr("Output Chunk Width"), groupProperty); + QtVariantProperty *chunkHeightProperty = addProperty(ChunkHeightProperty, QVariant::Int, tr("Output Chunk Height"), groupProperty); + + chunkWidthProperty->setAttribute(QLatin1String("minimum"), CHUNK_SIZE_MIN); + chunkHeightProperty->setAttribute(QLatin1String("minimum"), CHUNK_SIZE_MIN); + QtVariantProperty *renderOrderProperty = addProperty(RenderOrderProperty, QtVariantPropertyManager::enumTypeId(), @@ -978,6 +984,18 @@ void PropertyBrowser::applyMapValue(PropertyId id, const QVariant &val) case BackgroundColorProperty: command = new ChangeMapProperty(mMapDocument, val.value()); break; + case ChunkWidthProperty: { + QSize chunkSize = mMapDocument->map()->chunkSize(); + chunkSize.setWidth(val.toInt()); + command = new ChangeMapProperty(mMapDocument, chunkSize); + break; + } + case ChunkHeightProperty: { + QSize chunkSize = mMapDocument->map()->chunkSize(); + chunkSize.setHeight(val.toInt()); + command = new ChangeMapProperty(mMapDocument, chunkSize); + break; + } default: break; } @@ -1578,6 +1596,8 @@ void PropertyBrowser::updateProperties() mIdToProperty[LayerFormatProperty]->setValue(map->layerDataFormat()); mIdToProperty[RenderOrderProperty]->setValue(map->renderOrder()); mIdToProperty[BackgroundColorProperty]->setValue(map->backgroundColor()); + mIdToProperty[ChunkWidthProperty]->setValue(map->chunkSize().width()); + mIdToProperty[ChunkHeightProperty]->setValue(map->chunkSize().height()); break; } case Object::MapObjectType: { diff --git a/src/tiled/propertybrowser.h b/src/tiled/propertybrowser.h index e20b3467aa..478987ab8c 100644 --- a/src/tiled/propertybrowser.h +++ b/src/tiled/propertybrowser.h @@ -160,7 +160,9 @@ private slots: WangColorProbabilityProperty, CustomProperty, InfiniteProperty, - TemplateProperty + TemplateProperty, + ChunkWidthProperty, + ChunkHeightProperty }; void addMapProperties();