Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature 2833 (rotated Wang tiles) #2912

Merged
merged 24 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5e06ef7
add a new button for rotation settings
cpetig Oct 3, 2020
fde172e
Implement loading and reading rotation information on tilesets
cpetig Oct 3, 2020
1fa33a4
Rotate WangSets on demand at load time (first working prototype)
cpetig Oct 4, 2020
54b06f2
Don't store autogenerated wang tiles.
cpetig Oct 4, 2020
da223b3
flip tiles if needed, prevent adding already added ids
cpetig Oct 31, 2020
2cc6a7a
Replaced WangSet::addRotations with iterating variations in WangFiller
bjorn Nov 11, 2020
bee8a85
Introduce Checkboxes and state for rotation properties into WangSets …
cpetig Nov 14, 2020
9e064cc
Undo and Redo work on TileSet level
cpetig Nov 15, 2020
e07fa6b
Store and read flip properties in WangSet, remove old tileset rotatio…
cpetig Nov 15, 2020
5c14214
nearly complete implementation of undo/redo for individual tile Wang …
cpetig Nov 15, 2020
11a53a6
Naming corrections
cpetig Nov 15, 2020
138162e
Simplified the way WangFiller handles the flipped variations
bjorn Nov 18, 2020
53493af
WIP: Moving back to precalculating all possible WangIds
bjorn Nov 18, 2020
681bf94
Move the flipping permission into tileset and tile again
cpetig Nov 18, 2020
7d7a30a
correct writer (attributes had wrong position)
cpetig Nov 18, 2020
1553dec
remove unused icons from the resource file
cpetig Nov 18, 2020
fb1e1e0
General cleanups and changes
bjorn Nov 24, 2020
f8f1b63
Re-enabled the code in AdjustTileMetaData
bjorn Nov 24, 2020
006e703
Implement JSON im+export, document file format changes, turn off tile…
cpetig Nov 25, 2020
f4240db
Removed all code related to tile-specific flipping override
bjorn Nov 25, 2020
fe85b02
Further work on remaining changes
bjorn Nov 25, 2020
f997d4a
Leftovers
bjorn Nov 25, 2020
4d7d5a1
Changed the "Rotate" flag to mean 90, 180 and 270 degree variations
bjorn Nov 25, 2020
2a1f93c
Group the transformation properties together
bjorn Nov 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions docs/reference/json-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ Tileset
tileoffset, :ref:`json-tileset-tileoffset`, "(optional)"
tiles, array, "Array of :ref:`Tiles <json-tile>` (optional)"
tilewidth, int, "Maximum width of tiles in this set"
transformations, :ref:`json-tileset-transformations`, "Allowed transformations (optional)"
transparentcolor, string, "Hex-formatted color (#RRGGBB) (optional)"
type, string, "``tileset`` (for tileset files, since 1.0)"
version, number, "The JSON format version"
Expand Down Expand Up @@ -483,6 +484,22 @@ See :ref:`tmx-tileoffset` in the TMX Map Format.
x, int, "Horizontal offset in pixels"
y, int, "Vertical offset in pixels (positive is down)"

.. _json-tileset-transformations:

Transformations
~~~~~~~~~~~~~~~

See :ref:`tmx-tileset-transformations` in the TMX Map Format.

.. csv-table::
:header: Field, Type, Description
:widths: 1, 1, 4

hflip, bool, "Tiles can be flipped horizontally"
vflip, bool, "Tiles can be flipped vertically"
rotate, bool, "Tiles can be rotated in 90-degree increments"
preferuntransformed, bool, "Whether untransformed tiles remain preferred, otherwise transformed tiles are used to produce more variations"

Tileset Example
~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -667,21 +684,15 @@ Wang Tile
:header: Field, Type, Description
:widths: 1, 1, 4

dflip, bool, "Tile is flipped diagonally (default: ``false``)"
hflip, bool, "Tile is flipped horizontally (default: ``false``)"
tileid, int, "Local ID of tile"
vflip, bool, "Tile is flipped vertically (default: ``false``)"
wangid, array, "Array of Wang color indexes (``uchar[8]``)"

Example:

.. code:: json

{
"dflip": false,
"hflip": false,
"tileid": 0,
"vflip": false,
"wangid": [2, 0, 1, 0, 1, 0, 2, 0]
}

Expand Down Expand Up @@ -739,6 +750,12 @@ Tiled 1.5

* :ref:`json-wangcolor` can now store ``properties``.

* Added ``transformations`` property to :ref:`json-tileset` (see
:ref:`json-tileset-transformations`).

* Removed ``dflip``, ``hflip`` and ``vflip`` properties from
:ref:`json-wangtile` (no longer supported).

Tiled 1.4
~~~~~~~~~

Expand Down
5 changes: 5 additions & 0 deletions docs/reference/tmx-changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Tiled 1.5
hex format. This is because the number of colors supported in a Wang set was
increased from 15 to 255.

- Valid transformations of tiles in a set (flipping, rotation) are specified
in a :ref:`tmx-tileset-transformations` element. The partial support for the
``vflip``, ``hflip`` and ``dflip`` attributes on the :ref:`tmx-wangtile`
element has been removed.

Tiled 1.4
---------

Expand Down
28 changes: 18 additions & 10 deletions docs/reference/tmx-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ an ``<image>`` tag.

Can contain at most one: :ref:`tmx-image`, :ref:`tmx-tileoffset`,
:ref:`tmx-grid` (since 1.0), :ref:`tmx-properties`, :ref:`tmx-terraintypes`,
:ref:`tmx-wangsets` (since 1.1),
:ref:`tmx-wangsets` (since 1.1), :ref:`tmx-tileset-transformations` (since 1.5)

Can contain any number: :ref:`tmx-tileset-tile`

Expand Down Expand Up @@ -245,6 +245,20 @@ Can contain any number: :ref:`tmx-terrain`

Can contain at most one: :ref:`tmx-properties`

.. _tmx-tileset-transformations:

<transformations>
~~~~~~~~~~~~~~~~~

This element is used to describe which transformations can be applied to the
tiles (e.g. to extend a Wang set by transforming existing tiles).

- **hflip:** Whether the tiles in this set can be flipped horizontally (default 0)
- **vflip:** Whether the tiles in this set can be flipped vertically (default 0)
- **rotate:** Whether the tiles in this set can be rotated in 90 degree increments (default 0)
- **preferuntransformed:** Whether untransformed tiles remain preferred, otherwise
transformed tiles are used to produce more variations (default 0)

.. _tmx-tileset-tile:

<tile>
Expand Down Expand Up @@ -343,15 +357,9 @@ associating it with a certain Wang ID.
Wang ID was saved as a 32-bit unsigned integer stored in the format
``0xCECECECE`` (where each C is a corner color and each E is an edge color,
in reverse order)."
- **hflip:** Whether the tile is flipped horizontally. This only affects
the tile image, it does not change the meaning of the wangid. See
:ref:`Tile flipping <tmx-tile-flipping>` for more info. (defaults to false)
- **vflip:** Whether the tile is flipped vertically. This only affects
the tile image, it does not change the meaning of the wangid. See
:ref:`Tile flipping <tmx-tile-flipping>` for more info. (defaults to false)
- **dflip:** Whether the tile is flipped on its diagonal. This only affects
the tile image, it does not change the meaning of the wangid. See
:ref:`Tile flipping <tmx-tile-flipping>` for more info. (defaults to false)
- *hflip:* Whether the tile is flipped horizontally (removed in Tiled 1.5).
- *vflip:* Whether the tile is flipped vertically (removed in Tiled 1.5).
- *dflip:* Whether the tile is flipped on its diagonal (removed in Tiled 1.5).

.. _tmx-layer:

Expand Down
125 changes: 70 additions & 55 deletions src/libtiled/mapreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class MapReaderPrivate
void readTilesetEditorSettings(Tileset &tileset);
void readTilesetTile(Tileset &tileset);
void readTilesetGrid(Tileset &tileset);
void readTilesetTransformations(Tileset &tileset);
void readTilesetImage(Tileset &tileset);
void readTilesetTerrainTypes(Tileset &tileset);
void readTilesetWangSets(Tileset &tileset);
Expand Down Expand Up @@ -376,58 +377,62 @@ SharedTileset MapReaderPrivate::readTileset()
const QString name = atts.value(QLatin1String("name")).toString();
const int tileWidth = atts.value(QLatin1String("tilewidth")).toInt();
const int tileHeight = atts.value(QLatin1String("tileheight")).toInt();

if (tileWidth < 0 || tileHeight < 0
|| (firstGid == 0 && !mReadingExternalTileset)) {
xml.raiseError(tr("Invalid tileset parameters for tileset"
" '%1'").arg(name));
return {};
}

const int tileSpacing = atts.value(QLatin1String("spacing")).toInt();
const int margin = atts.value(QLatin1String("margin")).toInt();
const int columns = atts.value(QLatin1String("columns")).toInt();
const QString backgroundColor = atts.value(QLatin1String("backgroundcolor")).toString();
const QString alignment = atts.value(QLatin1String("objectalignment")).toString();

if (tileWidth < 0 || tileHeight < 0
|| (firstGid == 0 && !mReadingExternalTileset)) {
xml.raiseError(tr("Invalid tileset parameters for tileset"
" '%1'").arg(name));
} else {
tileset = Tileset::create(name, tileWidth, tileHeight,
tileSpacing, margin);
tileset = Tileset::create(name, tileWidth, tileHeight,
tileSpacing, margin);

tileset->setColumnCount(columns);
tileset->setColumnCount(columns);

if (QColor::isValidColor(backgroundColor))
tileset->setBackgroundColor(QColor(backgroundColor));
if (QColor::isValidColor(backgroundColor))
tileset->setBackgroundColor(QColor(backgroundColor));

tileset->setObjectAlignment(alignmentFromString(alignment));
tileset->setObjectAlignment(alignmentFromString(alignment));

while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("editorsettings")) {
readTilesetEditorSettings(*tileset);
} else if (xml.name() == QLatin1String("tile")) {
readTilesetTile(*tileset);
} else if (xml.name() == QLatin1String("tileoffset")) {
const QXmlStreamAttributes oa = xml.attributes();
int x = oa.value(QLatin1String("x")).toInt();
int y = oa.value(QLatin1String("y")).toInt();
tileset->setTileOffset(QPoint(x, y));
xml.skipCurrentElement();
} else if (xml.name() == QLatin1String("grid")) {
readTilesetGrid(*tileset);
} else if (xml.name() == QLatin1String("properties")) {
tileset->mergeProperties(readProperties());
} else if (xml.name() == QLatin1String("image")) {
if (tileWidth == 0 || tileHeight == 0) {
xml.raiseError(tr("Invalid tileset parameters for tileset"
" '%1'").arg(name));
tileset.clear();
break;
} else {
readTilesetImage(*tileset);
}
} else if (xml.name() == QLatin1String("terraintypes")) {
readTilesetTerrainTypes(*tileset);
} else if (xml.name() == QLatin1String("wangsets")) {
readTilesetWangSets(*tileset);
while (xml.readNextStartElement()) {
if (xml.name() == QLatin1String("editorsettings")) {
readTilesetEditorSettings(*tileset);
} else if (xml.name() == QLatin1String("tile")) {
readTilesetTile(*tileset);
} else if (xml.name() == QLatin1String("tileoffset")) {
const QXmlStreamAttributes oa = xml.attributes();
int x = oa.value(QLatin1String("x")).toInt();
int y = oa.value(QLatin1String("y")).toInt();
tileset->setTileOffset(QPoint(x, y));
xml.skipCurrentElement();
} else if (xml.name() == QLatin1String("grid")) {
readTilesetGrid(*tileset);
} else if (xml.name() == QLatin1String("transformations")) {
readTilesetTransformations(*tileset);
} else if (xml.name() == QLatin1String("properties")) {
tileset->mergeProperties(readProperties());
} else if (xml.name() == QLatin1String("image")) {
if (tileWidth == 0 || tileHeight == 0) {
xml.raiseError(tr("Invalid tileset parameters for tileset"
" '%1'").arg(name));
tileset.clear();
break;
} else {
readUnknownElement();
readTilesetImage(*tileset);
}
} else if (xml.name() == QLatin1String("terraintypes")) {
readTilesetTerrainTypes(*tileset);
} else if (xml.name() == QLatin1String("wangsets")) {
readTilesetWangSets(*tileset);
} else {
readUnknownElement();
}
}
} else { // External tileset
Expand Down Expand Up @@ -504,7 +509,7 @@ void MapReaderPrivate::readTilesetTile(Tileset &tileset)
}

if (wangId)
tileset.wangSet(0)->addTile(tile, wangId);
tileset.wangSet(0)->setWangId(id, wangId);
}

// Read tile probability
Expand Down Expand Up @@ -589,6 +594,27 @@ void MapReaderPrivate::readTilesetGrid(Tileset &tileset)
xml.skipCurrentElement();
}

void MapReaderPrivate::readTilesetTransformations(Tileset &tileset)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("transformations"));

const QXmlStreamAttributes atts = xml.attributes();

Tileset::TransformationFlags transformations;
if (atts.value(QLatin1String("hflip")).toInt())
transformations |= Tileset::AllowFlipHorizontally;
if (atts.value(QLatin1String("vflip")).toInt())
transformations |= Tileset::AllowFlipVertically;
if (atts.value(QLatin1String("rotate")).toInt())
transformations |= Tileset::AllowRotate;
if (atts.value(QLatin1String("preferuntransformed")).toInt())
transformations |= Tileset::PreferUntransformed;

tileset.setTransformationFlags(transformations);

xml.skipCurrentElement();
}

void MapReaderPrivate::readTilesetImage(Tileset &tileset)
{
Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("image"));
Expand Down Expand Up @@ -713,9 +739,9 @@ void MapReaderPrivate::readTilesetWangSets(Tileset &tileset)
const QXmlStreamAttributes atts = xml.attributes();
const QString name = atts.value(QLatin1String("name")).toString();
const WangSet::Type type = wangSetTypeFromString(atts.value(QLatin1String("type")).toString());
const int tile = atts.value(QLatin1String("tile")).toInt();
const int tileId = atts.value(QLatin1String("tile")).toInt();

auto wangSet = std::make_unique<WangSet>(&tileset, name, type, tile);
auto wangSet = std::make_unique<WangSet>(&tileset, name, type, tileId);

// For backwards-compatibility
QVector<int> cornerColors;
Expand Down Expand Up @@ -761,18 +787,7 @@ void MapReaderPrivate::readTilesetWangSets(Tileset &tileset)
return;
}

const bool fH = tileAtts.value(QLatin1String("hflip")).toInt();
const bool fV = tileAtts.value(QLatin1String("vflip")).toInt();
const bool fA = tileAtts.value(QLatin1String("dflip")).toInt();

Tile *tile = tileset.findOrCreateTile(tileId);

WangTile wangTile(tile, wangId);
wangTile.setFlippedHorizontally(fH);
wangTile.setFlippedVertically(fV);
wangTile.setFlippedAntiDiagonally(fA);

wangSet->addWangTile(wangTile);
wangSet->setWangId(tileId, wangId);

xml.skipCurrentElement();
} else if (xml.name() == QLatin1String("wangcolor") || isCorner || isEdge) {
Expand Down
15 changes: 11 additions & 4 deletions src/libtiled/maptovariantconverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,16 @@ QVariant MapToVariantConverter::toVariant(const Tileset &tileset,
tilesetVariant[QStringLiteral("imageheight")] = tileset.imageHeight();
}

const auto transformationFlags = tileset.transformationFlags();
if (transformationFlags) {
tilesetVariant[QStringLiteral("transformations")] = QVariantMap {
{ QStringLiteral("hflip"), transformationFlags.testFlag(Tileset::AllowFlipHorizontally) },
{ QStringLiteral("vflip"), transformationFlags.testFlag(Tileset::AllowFlipVertically) },
{ QStringLiteral("rotate"), transformationFlags.testFlag(Tileset::AllowRotate) },
{ QStringLiteral("preferuntransformed"), transformationFlags.testFlag(Tileset::PreferUntransformed) },
};
}

// Write the properties, terrain, external image, object group and
// animation for those tiles that have them.

Expand Down Expand Up @@ -383,10 +393,7 @@ QVariant MapToVariantConverter::toVariant(const WangSet &wangSet) const
wangIdVariant.append(QVariant(wangTile.wangId().indexColor(i)));

wangTileVariant[QStringLiteral("wangid")] = wangIdVariant;
wangTileVariant[QStringLiteral("tileid")] = wangTile.tile()->id();
wangTileVariant[QStringLiteral("hflip")] = wangTile.flippedHorizontally();
wangTileVariant[QStringLiteral("vflip")] = wangTile.flippedVertically();
wangTileVariant[QStringLiteral("dflip")] = wangTile.flippedAntiDiagonally();
wangTileVariant[QStringLiteral("tileid")] = wangTile.tileId();

wangTileVariants.append(wangTileVariant);
}
Expand Down
26 changes: 15 additions & 11 deletions src/libtiled/mapwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,20 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
w.writeEndElement();
}

const auto transformationFlags = tileset.transformationFlags();
if (transformationFlags) {
w.writeStartElement(QStringLiteral("transformations"));
w.writeAttribute(QStringLiteral("hflip"),
QString::number(transformationFlags.testFlag(Tileset::AllowFlipHorizontally)));
w.writeAttribute(QStringLiteral("vflip"),
QString::number(transformationFlags.testFlag(Tileset::AllowFlipVertically)));
w.writeAttribute(QStringLiteral("rotate"),
QString::number(transformationFlags.testFlag(Tileset::AllowRotate)));
w.writeAttribute(QStringLiteral("preferuntransformed"),
QString::number(transformationFlags.testFlag(Tileset::PreferUntransformed)));
w.writeEndElement();
}

// Write the tileset properties
writeProperties(w, tileset.properties());

Expand Down Expand Up @@ -521,18 +535,8 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset &tileset,
const auto wangTiles = ws->sortedWangTiles();
for (const WangTile &wangTile : wangTiles) {
w.writeStartElement(QStringLiteral("wangtile"));
w.writeAttribute(QStringLiteral("tileid"), QString::number(wangTile.tile()->id()));
w.writeAttribute(QStringLiteral("tileid"), QString::number(wangTile.tileId()));
w.writeAttribute(QStringLiteral("wangid"), wangTile.wangId().toString());

if (wangTile.flippedHorizontally())
w.writeAttribute(QStringLiteral("hflip"), QString::number(1));

if (wangTile.flippedVertically())
w.writeAttribute(QStringLiteral("vflip"), QString::number(1));

if (wangTile.flippedAntiDiagonally())
w.writeAttribute(QStringLiteral("dflip"), QString::number(1));

w.writeEndElement(); // </wangtile>
}

Expand Down
Loading