Skip to content

Commit f0ded07

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 f0ded07

12 files changed

+503
-48
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: 70 additions & 27 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,40 +254,58 @@ 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));
286303
}
287304
}
288305

289-
mNextTileId = std::max<int>(mNextTileId, tileRects.size());
306+
for (Tile *tile : std::as_const(mTiles)) {
307+
mNextTileId = std::max(mNextTileId, tile->id() + 1);
308+
}
290309

291310
mImageReference.size = mImage.size();
292311
mColumnCount = columnCountForWidth(mImageReference.size.width());
@@ -453,6 +472,9 @@ void Tileset::addTiles(const QList<Tile *> &tiles)
453472
Q_ASSERT(tile->tileset() == this && !mTilesById.contains(tile->id()));
454473
mTilesById.insert(tile->id(), tile);
455474
mTiles.append(tile);
475+
if (isAtlas()) {
476+
setNextTileId(std::max(nextTileId(), tile->id() + 1));
477+
}
456478
}
457479

458480
updateTileSize();
@@ -469,6 +491,9 @@ void Tileset::removeTiles(const QList<Tile *> &tiles)
469491
Q_ASSERT(tile->tileset() == this && mTilesById.contains(tile->id()));
470492
mTilesById.remove(tile->id());
471493
mTiles.removeOne(tile);
494+
if (isAtlas()) {
495+
setNextTileId(std::max(nextTileId(), tile->id() + 1));
496+
}
472497
}
473498

474499
updateTileSize();
@@ -554,6 +579,9 @@ void Tileset::setTileImageRect(Tile *tile, const QRect &imageRect)
554579

555580
void Tileset::maybeUpdateTileSize(QSize previousTileSize, QSize newTileSize)
556581
{
582+
if (isAtlas())
583+
return;
584+
557585
if (previousTileSize == newTileSize)
558586
return;
559587

@@ -681,6 +709,9 @@ SharedTileset Tileset::clone() const
681709
*/
682710
void Tileset::updateTileSize()
683711
{
712+
if (isAtlas())
713+
return;
714+
684715
int maxWidth = 0;
685716
int maxHeight = 0;
686717
for (Tile *tile : std::as_const(mTiles)) {
@@ -694,6 +725,18 @@ void Tileset::updateTileSize()
694725
mTileHeight = maxHeight;
695726
}
696727

728+
int Tileset::generateTileId(int x, int y, bool absolute) const {
729+
if (absolute) {
730+
// Normalize x and y to tile coordinates
731+
x = (x - mMargin) / (mTileWidth + mTileSpacing);
732+
y = (y - mMargin) / (mTileHeight + mTileSpacing);
733+
}
734+
735+
// Use 12+12 bit spatial hash (4096x4096 coordinates limit)
736+
x &= 0xFFF;
737+
y &= 0xFFF;
738+
return (x << 12) | y; // 24 bits total
739+
}
697740

698741
QString Tileset::orientationToString(Tileset::Orientation orientation)
699742
{

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)