Skip to content

Commit 1b2f6ef

Browse files
committed
Add support for Tileset Grid Atlas
Useful for y-sorting, multi tile objects, base tile images changing, etc. - Add new "atlas" property to tileset that determines if tileset is atlas tileset or not - Atlas property is set on tileset creation through type - When atlas tileset is loaded, if it doesnt have any image rects image rects for every tile on tileset will be generated - Atlas tileset always saves all image rects For controls, go to rearrange tiles mode: - Left click dragging on existing tile starts moving it, releasing saves - Left click dragging on existing tile corner starts resizing it, releasing saves - Left click dragging on empty space starts selection for creating new tile - Right click dragging anywhere starts selection for deleting all tiles overlapping the selection - Holding shift before pressing right or left click will disable snapping to grid which allows any sized drawing Signed-off-by: Tomas Slusny <[email protected]>
1 parent f7c4e4d commit 1b2f6ef

16 files changed

+688
-110
lines changed

src/libtiled/map.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,21 @@ void Map::recomputeDrawMargins() const
143143
QMargins offsetMargins;
144144

145145
for (const SharedTileset &tileset : mTilesets) {
146-
const bool useGridSize = tileset->tileRenderSize() == Tileset::GridSize;
147-
const QSize tileSize = useGridSize ? this->tileSize()
148-
: tileset->tileSize();
146+
if (tileset->isAtlas()) {
147+
// For atlas tilesets, check all tile image rects
148+
for (const Tile *tile : tileset->tiles()) {
149+
const QRect rect = tile->imageRect();
150+
maxTileSize = std::max(maxTileSize,
151+
std::max(rect.width(), rect.height()));
152+
}
153+
} else {
154+
const bool useGridSize = tileset->tileRenderSize() == Tileset::GridSize;
155+
const QSize tileSize = useGridSize ? this->tileSize()
156+
: tileset->tileSize();
149157

150-
maxTileSize = std::max(maxTileSize, std::max(tileSize.width(),
151-
tileSize.height()));
158+
maxTileSize = std::max(maxTileSize, std::max(tileSize.width(),
159+
tileSize.height()));
160+
}
152161

153162
const QPoint offset = tileset->tileOffset();
154163
offsetMargins = maxMargins(QMargins(-offset.x(),

src/libtiled/mapreader.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,15 @@ SharedTileset MapReaderPrivate::readTileset()
390390
const QString className = atts.value(QLatin1String("class")).toString();
391391
const int tileSpacing = atts.value(QLatin1String("spacing")).toInt();
392392
const int margin = atts.value(QLatin1String("margin")).toInt();
393+
const bool atlas = atts.value(QLatin1String("atlas")).toInt();
393394
const int columns = atts.value(QLatin1String("columns")).toInt();
394395
const QString backgroundColor = atts.value(QLatin1String("backgroundcolor")).toString();
395396
const QString alignment = atts.value(QLatin1String("objectalignment")).toString();
396397
const QString tileRenderSize = atts.value(QLatin1String("tilerendersize")).toString();
397398
const QString fillMode = atts.value(QLatin1String("fillmode")).toString();
398399

399400
tileset = Tileset::create(name, tileWidth, tileHeight,
400-
tileSpacing, margin);
401+
tileSpacing, margin, atlas);
401402

402403
tileset->setClassName(className);
403404
tileset->setColumnCount(columns);

src/libtiled/maptovariantconverter.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
185185
tilesetVariant[QStringLiteral("name")] = tileset.name();
186186
if (!tileset.className().isEmpty())
187187
tilesetVariant[QStringLiteral("class")] = tileset.className();
188+
tilesetVariant[QStringLiteral("atlas")] = tileset.isAtlas();
188189
tilesetVariant[QStringLiteral("tilewidth")] = tileset.tileWidth();
189190
tilesetVariant[QStringLiteral("tileheight")] = tileset.tileHeight();
190191
tilesetVariant[QStringLiteral("spacing")] = tileset.tileSpacing();
@@ -302,14 +303,13 @@ QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
302303
tileVariant[QStringLiteral("imagewidth")] = imageSize.width();
303304
tileVariant[QStringLiteral("imageheight")] = imageSize.height();
304305
}
305-
306-
const QRect &imageRect = tile->imageRect();
307-
if (!imageRect.isNull() && imageRect != tile->image().rect() && tileset.isCollection()) {
308-
tileVariant[QStringLiteral("x")] = imageRect.x();
309-
tileVariant[QStringLiteral("y")] = imageRect.y();
310-
tileVariant[QStringLiteral("width")] = imageRect.width();
311-
tileVariant[QStringLiteral("height")] = imageRect.height();
312-
}
306+
}
307+
const QRect &imageRect = tile->imageRect();
308+
if (!imageRect.isNull() && imageRect != tile->image().rect() && (tileset.isCollection() || tileset.isAtlas())) {
309+
tileVariant[QStringLiteral("x")] = imageRect.x();
310+
tileVariant[QStringLiteral("y")] = imageRect.y();
311+
tileVariant[QStringLiteral("width")] = imageRect.width();
312+
tileVariant[QStringLiteral("height")] = imageRect.height();
313313
}
314314
if (tile->objectGroup())
315315
tileVariant[QStringLiteral("objectgroup")] = toVariant(*tile->objectGroup());

src/libtiled/mapwriter.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,9 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
337337
if (margin != 0)
338338
w.writeAttribute(QStringLiteral("margin"), QString::number(margin));
339339

340+
if (tileset.isAtlas())
341+
w.writeAttribute(QStringLiteral("atlas"), QString::number(tileset.isAtlas()));
342+
340343
w.writeAttribute(QStringLiteral("tilecount"),
341344
QString::number(tileset.tileCount()));
342345
w.writeAttribute(QStringLiteral("columns"),
@@ -413,15 +416,16 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
413416
QSize(tileset.imageWidth(), tileset.imageHeight()));
414417

415418
const bool isCollection = tileset.isCollection();
416-
const bool includeAllTiles = isCollection || tileset.anyTileOutOfOrder();
419+
const bool isAtlas = tileset.isAtlas();
420+
const bool includeAllTiles = isCollection || isAtlas || tileset.anyTileOutOfOrder();
417421

418422
for (const Tile *tile : tileset.tiles()) {
419423
if (includeAllTiles || includeTile(tile)) {
420424
w.writeStartElement(QStringLiteral("tile"));
421425
w.writeAttribute(QStringLiteral("id"), QString::number(tile->id()));
422426

423427
const QRect &imageRect = tile->imageRect();
424-
if (!imageRect.isNull() && imageRect != tile->image().rect() && isCollection) {
428+
if (!imageRect.isNull() && imageRect != tile->image().rect() && (isCollection || isAtlas)) {
425429
w.writeAttribute(QStringLiteral("x"),
426430
QString::number(imageRect.x()));
427431
w.writeAttribute(QStringLiteral("y"),

src/libtiled/tileset.cpp

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@
3838
namespace Tiled {
3939

4040
Tileset::Tileset(QString name, int tileWidth, int tileHeight,
41-
int tileSpacing, int margin)
41+
int tileSpacing, int margin, bool isAtlas)
4242
: Object(TilesetType)
4343
, mName(std::move(name))
4444
, mTileWidth(tileWidth)
4545
, mTileHeight(tileHeight)
4646
, mTileSpacing(tileSpacing)
4747
, mMargin(margin)
4848
, mGridSize(tileWidth, tileHeight)
49+
, mAtlas(isAtlas)
4950
{
5051
Q_ASSERT(tileSpacing >= 0);
5152
Q_ASSERT(margin >= 0);
@@ -245,48 +246,58 @@ bool Tileset::loadImage()
245246
return initializeTilesetTiles();
246247
}
247248

248-
bool Tileset::initializeTilesetTiles()
249+
bool Tileset::initializeTilesetTiles(bool forceGeneration)
249250
{
250251
if (mImage.isNull() || mTileWidth <= 0 || mTileHeight <= 0)
251252
return false;
252253

253254
if (mImageReference.transparentColor.isValid())
254255
mImage.setMask(mImage.createMaskFromColor(mImageReference.transparentColor));
255256

256-
QVector<QRect> tileRects;
257-
258-
for (int y = mMargin; y <= mImage.height() - mTileHeight; y += mTileHeight + mTileSpacing)
259-
for (int x = mMargin; x <= mImage.width() - mTileWidth; x += mTileWidth + mTileSpacing)
260-
tileRects.append(QRect(x, y, mTileWidth, mTileHeight));
261-
262-
for (int tileNum = 0; tileNum < tileRects.size(); ++tileNum) {
263-
auto it = mTilesById.find(tileNum);
264-
if (it != mTilesById.end()) {
265-
it.value()->setImage(QPixmap()); // make sure it uses the tileset's image
266-
it.value()->setImageRect(tileRects.at(tileNum));
267-
} else {
268-
auto tile = new Tile(tileNum, this);
269-
tile->setImageRect(tileRects.at(tileNum));
270-
mTilesById.insert(tileNum, tile);
271-
mTiles.insert(tileNum, tile);
272-
}
257+
bool needsRectGeneration = true;
258+
if (isAtlas()) {
259+
needsRectGeneration = forceGeneration;
273260
}
274261

275-
QPixmap blank;
262+
if (needsRectGeneration) {
263+
QVector<QRect> tileRects;
264+
265+
for (int y = mMargin; y <= mImage.height() - mTileHeight; y += mTileHeight + mTileSpacing)
266+
for (int x = mMargin; x <= mImage.width() - mTileWidth; x += mTileWidth + mTileSpacing)
267+
tileRects.append(QRect(x, y, mTileWidth, mTileHeight));
268+
269+
for (int tileNum = 0; tileNum < tileRects.size(); ++tileNum) {
270+
auto it = mTilesById.find(tileNum);
271+
if (it != mTilesById.end()) {
272+
it.value()->setImage(QPixmap()); // make sure it uses the tileset's image
273+
it.value()->setImageRect(tileRects.at(tileNum));
274+
} else {
275+
auto tile = new Tile(tileNum, this);
276+
tile->setImageRect(tileRects.at(tileNum));
277+
mTilesById.insert(tileNum, tile);
278+
mTiles.insert(tileNum, tile);
279+
}
280+
}
276281

277-
// Blank out any remaining tiles to avoid confusion (todo: could be more clear)
278-
for (Tile *tile : std::as_const(mTiles)) {
279-
if (tile->id() >= tileRects.size()) {
280-
if (blank.isNull()) {
281-
blank = QPixmap(mTileWidth, mTileHeight);
282-
blank.fill();
282+
QPixmap blank;
283+
284+
// Blank out any remaining tiles to avoid confusion (todo: could be more clear)
285+
if (!isAtlas()) {
286+
for (Tile *tile : std::as_const(mTiles)) {
287+
if (tile->id() >= tileRects.size()) {
288+
if (blank.isNull()) {
289+
blank = QPixmap(mTileWidth, mTileHeight);
290+
blank.fill();
291+
}
292+
tile->setImage(blank);
293+
tile->setImageRect(QRect(0, 0, mTileWidth, mTileHeight));
294+
}
283295
}
284-
tile->setImage(blank);
285-
tile->setImageRect(QRect(0, 0, mTileWidth, mTileHeight));
286296
}
287297
}
288298

289-
mNextTileId = std::max<int>(mNextTileId, tileRects.size());
299+
for (Tile *tile : std::as_const(mTiles))
300+
mNextTileId = std::max(mNextTileId, tile->id() + 1);
290301

291302
mImageReference.size = mImage.size();
292303
mColumnCount = columnCountForWidth(mImageReference.size.width());
@@ -554,6 +565,9 @@ void Tileset::setTileImageRect(Tile *tile, const QRect &imageRect)
554565

555566
void Tileset::maybeUpdateTileSize(QSize previousTileSize, QSize newTileSize)
556567
{
568+
if (isAtlas())
569+
return;
570+
557571
if (previousTileSize == newTileSize)
558572
return;
559573

@@ -681,6 +695,9 @@ SharedTileset Tileset::clone() const
681695
*/
682696
void Tileset::updateTileSize()
683697
{
698+
if (isAtlas())
699+
return;
700+
684701
int maxWidth = 0;
685702
int maxHeight = 0;
686703
for (Tile *tile : std::as_const(mTiles)) {

src/libtiled/tileset.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
120120
* pointer is initialized, which enables the sharedPointer() function.
121121
*/
122122
Tileset(QString name, int tileWidth, int tileHeight,
123-
int tileSpacing = 0, int margin = 0);
123+
int tileSpacing = 0, int margin = 0, bool isAtlas = false);
124124

125125
public:
126126
QString exportFileName;
@@ -138,6 +138,9 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
138138
void setFormat(const QString &format);
139139
QString format() const;
140140

141+
bool isAtlas() const { return mAtlas; }
142+
void setAtlas(bool atlas) { mAtlas = atlas; }
143+
141144
int tileWidth() const;
142145
int tileHeight() const;
143146

@@ -198,7 +201,7 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
198201
bool loadFromImage(const QImage &image, const QString &source);
199202
bool loadFromImage(const QString &fileName);
200203
bool loadImage();
201-
bool initializeTilesetTiles();
204+
bool initializeTilesetTiles(bool forceGeneration=false);
202205

203206
SharedTileset findSimilarTileset(const QVector<SharedTileset> &tilesets) const;
204207

@@ -311,6 +314,7 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
311314
TileRenderSize mTileRenderSize = TileSize;
312315
FillMode mFillMode = Stretch;
313316
QSize mGridSize;
317+
bool mAtlas;
314318
int mColumnCount = 0;
315319
int mExpectedColumnCount = 0;
316320
int mExpectedRowCount = 0;

src/libtiled/varianttomapconverter.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ SharedTileset VariantToMapConverter::toTileset(const QVariant &variant)
220220
}
221221

222222
const QString name = variantMap[QStringLiteral("name")].toString();
223+
const bool atlas = variantMap[QStringLiteral("atlas")].toBool();
223224
const QString className = variantMap[QStringLiteral("class")].toString();
224225
const int tileWidth = variantMap[QStringLiteral("tilewidth")].toInt();
225226
const int tileHeight = variantMap[QStringLiteral("tileheight")].toInt();
@@ -244,7 +245,7 @@ SharedTileset VariantToMapConverter::toTileset(const QVariant &variant)
244245

245246
SharedTileset tileset(Tileset::create(name,
246247
tileWidth, tileHeight,
247-
spacing, margin));
248+
spacing, margin, atlas));
248249

249250
tileset->setClassName(className);
250251
tileset->setObjectAlignment(alignmentFromString(objectAlignment));

src/tiled/documentmanager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1372,7 +1372,7 @@ void DocumentManager::onWorldUnloaded(WorldDocument *worldDocument)
13721372

13731373
static bool mayNeedColumnCountAdjustment(const Tileset &tileset)
13741374
{
1375-
if (tileset.isCollection())
1375+
if (tileset.isCollection() || tileset.isAtlas())
13761376
return false;
13771377
if (tileset.imageStatus() != LoadingReady)
13781378
return false;

src/tiled/newtilesetdialog.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ static SessionOption<int> tilesetType { "tileset.type" };
4040
static SessionOption<bool> embedInMap { "tileset.embedInMap" };
4141
static SessionOption<bool> useTransparentColor { "tileset.useTransparentColor" };
4242
static SessionOption<QColor> transparentColor { "tileset.transparentColor", Qt::magenta };
43+
static SessionOption<bool> generateGrid { "tileset.generateGrid", true };
4344
static SessionOption<QSize> tileSize { "tileset.tileSize", QSize(32, 32) };
4445
static SessionOption<int> tilesetSpacing { "tileset.spacing" };
4546
static SessionOption<int> tilesetMargin { "tileset.margin" };
4647
} // namespace session
4748

4849
enum TilesetType {
4950
TilesetImage,
51+
TilesetAtlas,
5052
ImageCollection
5153
};
5254

@@ -57,6 +59,8 @@ static TilesetType tilesetType(Ui::NewTilesetDialog *ui)
5759
case 0:
5860
return TilesetImage;
5961
case 1:
62+
return TilesetAtlas;
63+
case 2:
6064
return ImageCollection;
6165
}
6266
}
@@ -74,6 +78,7 @@ NewTilesetDialog::NewTilesetDialog(QWidget *parent) :
7478
mUi->embedded->setChecked(session::embedInMap);
7579
mUi->useTransparentColor->setChecked(session::useTransparentColor);
7680
mUi->colorButton->setColor(session::transparentColor);
81+
mUi->generateGrid->setChecked(session::generateGrid);
7782
mUi->tileWidth->setValue(tileSize.width());
7883
mUi->tileHeight->setValue(tileSize.height());
7984
mUi->spacing->setValue(session::tilesetSpacing);
@@ -93,7 +98,8 @@ NewTilesetDialog::NewTilesetDialog(QWidget *parent) :
9398
connect(mUi->buttonBox, &QDialogButtonBox::accepted, this, &NewTilesetDialog::tryAccept);
9499
connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &NewTilesetDialog::reject);
95100

96-
mUi->imageGroupBox->setVisible(session::tilesetType == 0);
101+
mUi->imageGroupBox->setVisible(session::tilesetType < 2);
102+
mUi->generateGrid->setVisible(session::tilesetType == 1);
97103
updateOkButton();
98104
}
99105

@@ -187,21 +193,24 @@ bool NewTilesetDialog::editTilesetParameters(TilesetParameters &parameters)
187193
void NewTilesetDialog::tryAccept()
188194
{
189195
const QString name = mUi->name->text();
196+
const TilesetType type = tilesetType(mUi);
190197

191198
SharedTileset tileset;
192199

193-
if (tilesetType(mUi) == TilesetImage) {
200+
if (type == TilesetImage || type == TilesetAtlas) {
194201
const QString image = mUi->image->text();
195202
const bool useTransparentColor = mUi->useTransparentColor->isChecked();
196203
const QColor transparentColor = mUi->colorButton->color();
197204
const int tileWidth = mUi->tileWidth->value();
198205
const int tileHeight = mUi->tileHeight->value();
199206
const int spacing = mUi->spacing->value();
200207
const int margin = mUi->margin->value();
208+
const bool generateGrid = mUi->generateGrid->isChecked();
201209

202210
tileset = Tileset::create(name,
203211
tileWidth, tileHeight,
204-
spacing, margin);
212+
spacing, margin,
213+
type == TilesetAtlas);
205214

206215
if (useTransparentColor)
207216
tileset->setTransparentColor(transparentColor);
@@ -214,7 +223,11 @@ void NewTilesetDialog::tryAccept()
214223
return;
215224
}
216225

217-
if (tileset->tileCount() == 0) {
226+
if (tileset->isAtlas() && generateGrid) {
227+
tileset->initializeTilesetTiles(true);
228+
}
229+
230+
if (tileset->tileCount() == 0 && (!tileset->isAtlas() || generateGrid)) {
218231
QMessageBox::critical(this, tr("Error"),
219232
tr("No tiles found in the tileset image "
220233
"when using the given tile size, "
@@ -281,7 +294,8 @@ void NewTilesetDialog::nameEdited(const QString &name)
281294

282295
void NewTilesetDialog::tilesetTypeChanged(int index)
283296
{
284-
mUi->imageGroupBox->setVisible(index == 0);
297+
mUi->imageGroupBox->setVisible(index < 2);
298+
mUi->generateGrid->setVisible(index == 1);
285299
updateOkButton();
286300
}
287301

0 commit comments

Comments
 (0)