Skip to content

Commit f89c64e

Browse files
committed
Add support for Tileset Grid Atlas
- 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 - Atlas tilesets use tile ids based on position on tileset so they are consistent on tile deletion/recreation Atlas is editable in **Rearrange Tiles** mode: - Tiles can be merged by holding down left click (this creates multi tile tile) - Tiles can be split by right clicking on multi tile edges - Tiles can be removed from atlas by right clicking single tiles (so needs to be split first then removed) - current limitation, ideally right click hold would delete multiple, but this is probably not super common use case and the visual seection doesnt work for right click, QT limitation maybe? - Tiles can be added back to atlas by left clicking empty tiles Signed-off-by: Tomas Slusny <[email protected]>
1 parent 47285d8 commit f89c64e

12 files changed

+491
-52
lines changed

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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,15 +413,16 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
413413
QSize(tileset.imageWidth(), tileset.imageHeight()));
414414

415415
const bool isCollection = tileset.isCollection();
416-
const bool includeAllTiles = isCollection || tileset.anyTileOutOfOrder();
416+
const bool isAtlas = tileset.isAtlas();
417+
const bool includeAllTiles = isCollection || isAtlas || tileset.anyTileOutOfOrder();
417418

418419
for (const Tile *tile : tileset.tiles()) {
419420
if (includeAllTiles || includeTile(tile)) {
420421
w.writeStartElement(QStringLiteral("tile"));
421422
w.writeAttribute(QStringLiteral("id"), QString::number(tile->id()));
422423

423424
const QRect &imageRect = tile->imageRect();
424-
if (!imageRect.isNull() && imageRect != tile->image().rect() && isCollection) {
425+
if (!imageRect.isNull() && imageRect != tile->image().rect() && (isCollection || isAtlas)) {
425426
w.writeAttribute(QStringLiteral("x"),
426427
QString::number(imageRect.x()));
427428
w.writeAttribute(QStringLiteral("y"),

src/libtiled/tileset.cpp

Lines changed: 58 additions & 31 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);
@@ -253,41 +254,57 @@ bool Tileset::initializeTilesetTiles()
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);
257+
bool needsRectGeneration = true;
258+
if (isAtlas()) {
259+
for (Tile *tile : std::as_const(mTiles)) {
260+
if (!tile->imageRect().isNull()) {
261+
needsRectGeneration = false;
262+
break;
263+
}
272264
}
273265
}
274266

275-
QPixmap blank;
267+
if (needsRectGeneration) {
268+
QVector<QRect> tileRects;
269+
270+
for (int y = mMargin; y <= mImage.height() - mTileHeight; y += mTileHeight + mTileSpacing)
271+
for (int x = mMargin; x <= mImage.width() - mTileWidth; x += mTileWidth + mTileSpacing)
272+
tileRects.append(QRect(x, y, mTileWidth, mTileHeight));
273+
274+
for (int tileNum = 0; tileNum < tileRects.size(); ++tileNum) {
275+
QRect rect = tileRects.at(tileNum);
276+
const int tileId = isAtlas() ? generateTileId(rect.x(), rect.y(), true) : tileNum;
277+
auto it = mTilesById.find(tileId);
278+
if (it != mTilesById.end()) {
279+
it.value()->setImage(QPixmap()); // make sure it uses the tileset's image
280+
it.value()->setImageRect(tileRects.at(tileNum));
281+
} else {
282+
auto tile = new Tile(tileId, this);
283+
tile->setImageRect(rect);
284+
mTilesById.insert(tileId, tile);
285+
mTiles.insert(tileNum, tile);
286+
}
287+
}
276288

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();
289+
QPixmap blank;
290+
291+
// Blank out any remaining tiles to avoid confusion (todo: could be more clear)
292+
if (!isAtlas()) {
293+
for (Tile *tile : std::as_const(mTiles)) {
294+
if (tile->id() >= tileRects.size()) {
295+
if (blank.isNull()) {
296+
blank = QPixmap(mTileWidth, mTileHeight);
297+
blank.fill();
298+
}
299+
tile->setImage(blank);
300+
tile->setImageRect(QRect(0, 0, mTileWidth, mTileHeight));
301+
}
283302
}
284-
tile->setImage(blank);
285-
tile->setImageRect(QRect(0, 0, mTileWidth, mTileHeight));
303+
304+
mNextTileId = std::max<int>(mNextTileId, tileRects.size());
286305
}
287306
}
288307

289-
mNextTileId = std::max<int>(mNextTileId, tileRects.size());
290-
291308
mImageReference.size = mImage.size();
292309
mColumnCount = columnCountForWidth(mImageReference.size.width());
293310
mImageReference.status = LoadingReady;
@@ -455,7 +472,8 @@ void Tileset::addTiles(const QList<Tile *> &tiles)
455472
mTiles.append(tile);
456473
}
457474

458-
updateTileSize();
475+
if (isCollection())
476+
updateTileSize();
459477
}
460478

461479
/**
@@ -471,7 +489,8 @@ void Tileset::removeTiles(const QList<Tile *> &tiles)
471489
mTiles.removeOne(tile);
472490
}
473491

474-
updateTileSize();
492+
if (isCollection())
493+
updateTileSize();
475494
}
476495

477496
/**
@@ -549,7 +568,8 @@ void Tileset::setTileImageRect(Tile *tile, const QRect &imageRect)
549568
const QSize previousTileSize = tile->size();
550569
tile->setImageRect(imageRect);
551570

552-
maybeUpdateTileSize(previousTileSize, tile->size());
571+
if (isCollection())
572+
maybeUpdateTileSize(previousTileSize, tile->size());
553573
}
554574

555575
void Tileset::maybeUpdateTileSize(QSize previousTileSize, QSize newTileSize)
@@ -694,6 +714,13 @@ void Tileset::updateTileSize()
694714
mTileHeight = maxHeight;
695715
}
696716

717+
int Tileset::generateTileId(int x, int y, bool absolute) const {
718+
if (absolute) {
719+
x = (x - mMargin) / (mTileWidth + mTileSpacing);
720+
y = (y - mMargin) / (mTileHeight + mTileSpacing);
721+
}
722+
return qHash(QPair<int,int>(x, y));
723+
}
697724

698725
QString Tileset::orientationToString(Tileset::Orientation orientation)
699726
{

src/libtiled/tileset.h

Lines changed: 10 additions & 1 deletion
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

@@ -235,6 +238,11 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
235238
int nextTileId() const;
236239
int takeNextTileId();
237240

241+
/**
242+
* Generates a tile ID from grid column and row coordinates or absolute coordinates
243+
*/
244+
int generateTileId(int x, int y, bool absolute = false) const;
245+
238246
void setTileImage(Tile *tile,
239247
const QPixmap &image,
240248
const QUrl &source = QUrl());
@@ -311,6 +319,7 @@ class TILEDSHARED_EXPORT Tileset : public Object, public QEnableSharedFromThis<T
311319
TileRenderSize mTileRenderSize = TileSize;
312320
FillMode mFillMode = Stretch;
313321
QSize mGridSize;
322+
bool mAtlas;
314323
int mColumnCount = 0;
315324
int mExpectedColumnCount = 0;
316325
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/newtilesetdialog.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static SessionOption<int> tilesetMargin { "tileset.margin" };
4747

4848
enum TilesetType {
4949
TilesetImage,
50+
TilesetAtlas,
5051
ImageCollection
5152
};
5253

@@ -57,6 +58,8 @@ static TilesetType tilesetType(Ui::NewTilesetDialog *ui)
5758
case 0:
5859
return TilesetImage;
5960
case 1:
61+
return TilesetAtlas;
62+
case 2:
6063
return ImageCollection;
6164
}
6265
}
@@ -93,7 +96,7 @@ NewTilesetDialog::NewTilesetDialog(QWidget *parent) :
9396
connect(mUi->buttonBox, &QDialogButtonBox::accepted, this, &NewTilesetDialog::tryAccept);
9497
connect(mUi->buttonBox, &QDialogButtonBox::rejected, this, &NewTilesetDialog::reject);
9598

96-
mUi->imageGroupBox->setVisible(session::tilesetType == 0);
99+
mUi->imageGroupBox->setVisible(session::tilesetType < 2);
97100
updateOkButton();
98101
}
99102

@@ -187,10 +190,11 @@ bool NewTilesetDialog::editTilesetParameters(TilesetParameters &parameters)
187190
void NewTilesetDialog::tryAccept()
188191
{
189192
const QString name = mUi->name->text();
193+
const TilesetType type = tilesetType(mUi);
190194

191195
SharedTileset tileset;
192196

193-
if (tilesetType(mUi) == TilesetImage) {
197+
if (type == TilesetImage || type == TilesetAtlas) {
194198
const QString image = mUi->image->text();
195199
const bool useTransparentColor = mUi->useTransparentColor->isChecked();
196200
const QColor transparentColor = mUi->colorButton->color();
@@ -201,7 +205,8 @@ void NewTilesetDialog::tryAccept()
201205

202206
tileset = Tileset::create(name,
203207
tileWidth, tileHeight,
204-
spacing, margin);
208+
spacing, margin,
209+
type == TilesetAtlas);
205210

206211
if (useTransparentColor)
207212
tileset->setTransparentColor(transparentColor);
@@ -281,7 +286,7 @@ void NewTilesetDialog::nameEdited(const QString &name)
281286

282287
void NewTilesetDialog::tilesetTypeChanged(int index)
283288
{
284-
mUi->imageGroupBox->setVisible(index == 0);
289+
mUi->imageGroupBox->setVisible(index < 2);
285290
updateOkButton();
286291
}
287292

src/tiled/newtilesetdialog.ui

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@
6969
<string>Based on Tileset Image</string>
7070
</property>
7171
</item>
72+
<item>
73+
<property name="text">
74+
<string>Tileset Grid Atlas</string>
75+
</property>
76+
</item>
7277
<item>
7378
<property name="text">
7479
<string>Collection of Images</string>

src/tiled/propertieswidget.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,13 @@ class TilesetProperties : public ObjectProperties
13191319
push(new RenameTileset(tilesetDocument(), value));
13201320
});
13211321

1322+
mAtlasProperty = new BoolProperty(
1323+
tr("Atlas"),
1324+
[this] {
1325+
return tileset()->isAtlas();
1326+
});
1327+
mAtlasProperty->setEnabled(false);
1328+
13221329
mObjectAlignmentProperty = new EnumProperty<Alignment>(
13231330
tr("Object Alignment"),
13241331
[this] {
@@ -1445,6 +1452,7 @@ class TilesetProperties : public ObjectProperties
14451452

14461453
mTilesetProperties = new GroupProperty(tr("Tileset"));
14471454
mTilesetProperties->addProperty(mNameProperty);
1455+
mTilesetProperties->addProperty(mAtlasProperty);
14481456
mTilesetProperties->addProperty(mClassProperty);
14491457
mTilesetProperties->addSeparator();
14501458
mTilesetProperties->addProperty(mObjectAlignmentProperty);
@@ -1526,6 +1534,7 @@ class TilesetProperties : public ObjectProperties
15261534

15271535
GroupProperty *mTilesetProperties;
15281536
Property *mNameProperty;
1537+
Property *mAtlasProperty;
15291538
Property *mObjectAlignmentProperty;
15301539
PointProperty *mTileOffsetProperty;
15311540
Property *mTileRenderSizeProperty;

0 commit comments

Comments
 (0)