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

Support rotate hexagonal tiles 60 degrees #1447

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ automappingconverter.exe
*.lib
*.exp
*.intermediate.manifest
default/

# Build artifacts on Mac
translations/phony_target.app
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ it to tiled.pro, for example:

You can now simply run Tiled using bin/tiled.

For windows and msvc something like this:

qbs setup-toolchains --detect
qbs setup-qt <path_to_qmake.exe> qt5
qbs config defaultProfile qt5
qbs config profiles.qt5.baseProfile profile:MSVC2015-amd64_x86
qbs profile:qt5

read more https://doc.qt.io/qbs/configuring.html and https://doc.qt.io/qbs/qt-versions.html

Installing
-------------------------------------------------------------------------------

Expand All @@ -77,3 +87,6 @@ By default, Tiled and its plugins are compiled with an Rpath so that they can
find the shared libtiled library when running it straight after compile. When
packaging for a distribution, this Rpath should generally be disabled by
appending `RPATH=no` to the qmake command.

Use windeployqt for deployment process for Windows (https://doc.qt.io/qt-5/windows-deployment.html)

Binary file added examples/test_hexagonal_tile_60x60x30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions examples/test_hexagonal_tile_60x60x30.tmx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="hexagonal" renderorder="right-down" width="20" height="20" tilewidth="60" tileheight="60" hexsidelength="30" staggeraxis="x" staggerindex="odd" nextobjectid="1">
<tileset firstgid="1" name="test_hexagonal_tile_60x60x30" tilewidth="60" tileheight="60" tilecount="1" columns="1">
<image source="test_hexagonal_tile_60x60x30.png" width="60" height="60"/>
</tileset>
<layer name="Tile Layer 1" width="20" height="20">
<data encoding="csv">
1,536870913,268435457,3221225473,3758096385,3489660929,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2147483649,2684354561,2415919105,1073741825,1610612737,1342177281,2147483649,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
</map>
14 changes: 13 additions & 1 deletion src/libtiled/gidmapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const int FlippedHorizontallyFlag = 0x80000000;
const int FlippedVerticallyFlag = 0x40000000;
const int FlippedAntiDiagonallyFlag = 0x20000000;

const int RotatedHexagonal60Flag = 0x20000000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had noted earlier that this variable didn't actually need to exist in this file, since it is just an alias for FlippedAntiDiagonallyFlag. As such it's also only causing duplicated processing below. Only RotatedHexagonal120Flag needs to be added here.

const int RotatedHexagonal120Flag = 0x10000000;

/**
* Default constructor. Use \l insert to initialize the gid mapper
* incrementally.
Expand Down Expand Up @@ -74,10 +77,15 @@ Cell GidMapper::gidToCell(unsigned gid, bool &ok) const
result.setFlippedVertically(gid & FlippedVerticallyFlag);
result.setFlippedAntiDiagonally(gid & FlippedAntiDiagonallyFlag);

result.setRotatedHexagonal60(gid & RotatedHexagonal60Flag);
result.setRotatedHexagonal120(gid & RotatedHexagonal120Flag);

// Clear the flags
gid &= ~(FlippedHorizontallyFlag |
FlippedVerticallyFlag |
FlippedAntiDiagonallyFlag);
FlippedAntiDiagonallyFlag |
RotatedHexagonal60Flag |
RotatedHexagonal120Flag);

if (gid == 0) {
ok = true;
Expand Down Expand Up @@ -130,6 +138,10 @@ unsigned GidMapper::cellToGid(const Cell &cell) const
gid |= FlippedVerticallyFlag;
if (cell.flippedAntiDiagonally())
gid |= FlippedAntiDiagonallyFlag;
if (cell.rotatedHexagonal60())
gid |= RotatedHexagonal60Flag;
if (cell.rotatedHexagonal120())
gid |= RotatedHexagonal120Flag;

return gid;
}
Expand Down
2 changes: 1 addition & 1 deletion src/libtiled/hexagonalrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ void HexagonalRenderer::drawTileLayer(QPainter *painter,
if (inLeftHalf)
startTile.rx()--;

CellRenderer renderer(painter);
CellRenderer renderer(painter, CellRenderer::HexagonalCells);

if (p.staggerX) {
startTile.setX(qMax(-1, startTile.x()));
Expand Down
14 changes: 12 additions & 2 deletions src/libtiled/maprenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,11 @@ static bool hasOpenGLEngine(const QPainter *painter)
type == QPaintEngine::OpenGL2);
}

CellRenderer::CellRenderer(QPainter *painter)
CellRenderer::CellRenderer(QPainter *painter, const CellType cellType)
: mPainter(painter)
, mTile(nullptr)
, mIsOpenGL(hasOpenGLEngine(painter))
, mCellType(cellType)
{
}

Expand Down Expand Up @@ -164,7 +165,16 @@ void CellRenderer::render(const Cell &cell, const QPointF &pos, const QSizeF &si
if (origin == BottomCenter)
fragment.x -= sizeHalf.x();

if (cell.flippedAntiDiagonally()) {
qreal rotation = 0;
if (mCellType == HexagonalCells) {
if (cell.rotatedHexagonal60()) {
fragment.rotation += 60;
}
if (cell.rotatedHexagonal120()) {
fragment.rotation += 120;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coding style: please leave out the superfluous { and } for one-line bodies.

} else if (cell.flippedAntiDiagonally()) {
Q_ASSERT(mCellType == OrthogonalCells);
fragment.rotation = 90;

flippedHorizontally = cell.flippedVertically();
Expand Down
8 changes: 7 additions & 1 deletion src/libtiled/maprenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,12 @@ class CellRenderer
BottomCenter
};

explicit CellRenderer(QPainter *painter);
enum CellType {
OrthogonalCells,
HexagonalCells
};

explicit CellRenderer(QPainter *painter, const CellType cellType = OrthogonalCells);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave out the const here, since that has no meaning for arguments passed by value.


~CellRenderer() { flush(); }

Expand All @@ -270,6 +275,7 @@ class CellRenderer
const Tile *mTile;
QVector<QPainter::PixmapFragment> mFragments;
const bool mIsOpenGL;
const CellType mCellType;
};

} // namespace Tiled
Expand Down
96 changes: 96 additions & 0 deletions src/libtiled/tilelayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,42 @@ void TileLayer::flip(FlipDirection direction)
mGrid = newGrid;
}

void TileLayer::flipHexagonal(FlipDirection direction)
{
QVector<Cell> newGrid(mWidth * mHeight);

Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically);

// for more info see impl "void TileLayer::rotateHexagonal(RotateDirection direction)"
static const char flipMaskH[16] = { 8, 6, 5, 4, 12, 2, 1, 0, 0, 14, 13, 12, 4, 10, 9, 8 }; // [0,15]<=>[8,7]; 2<=>5; 1<=>6; [12,3]<=>[4,11]; 14<=>9; 13<=>10;
static const char flipMaskV[16] = { 4, 10, 9, 8, 0, 14, 13, 12, 12, 2, 1, 0, 8, 6, 5, 4 }; // [0,15]<=>[4,11]; 2<=>9; 1<=>10; [12,3]<=>[8,7]; 14<=>5; 13<=>6;

const char (&flipMask)[16] = (direction == FlipHorizontally ? flipMaskH : flipMaskV);

for (int y = 0; y < mHeight; ++y) {
for (int x = 0; x < mWidth; ++x) {
const Cell &source = (direction == FlipHorizontally ? cellAt(mWidth - x - 1, y) : cellAt(x, mHeight - y - 1));
Cell &dest = newGrid[x + y * mWidth];
dest = source;

unsigned char mask =
(static_cast<unsigned char>(dest.flippedHorizontally()) << 3) |
(static_cast<unsigned char>(dest.flippedVertically()) << 2) |
(static_cast<unsigned char>(dest.rotatedHexagonal60()) << 1) |
(static_cast<unsigned char>(dest.rotatedHexagonal120()) << 0);

mask = flipMask[mask];

dest.setFlippedHorizontally((mask & 8) != 0);
dest.setFlippedVertically((mask & 4) != 0);
dest.setRotatedHexagonal60((mask & 2) != 0);
dest.setRotatedHexagonal120((mask & 1) != 0);
}
}

mGrid = newGrid;
}

void TileLayer::rotate(RotateDirection direction)
{
static const char rotateRightMask[8] = { 5, 4, 1, 0, 7, 6, 3, 2 };
Expand Down Expand Up @@ -276,6 +312,66 @@ void TileLayer::rotate(RotateDirection direction)
mGrid = newGrid;
}

void TileLayer::rotateHexagonal(RotateDirection direction)
{
int newWidth = mHeight;
int newHeight = mWidth;
QVector<Cell> newGrid(newWidth * newHeight);

/* https://github.com/bjorn/tiled/pull/1447

0 or 15 0: None or (Rotated60 | Rotated120 | FlippedVertically | FlippedHorizontally)
2 60: Rotated60
1 120: Rotated120
12 or 3 180: (FlippedHorizontally | FlippedVertically) or (Rotated60 | Rotated120)
14 240: Rotated60 | FlippedHorizontally | FlippedVertically
13 300: Rotated120 | FlippedHorizontally | FlippedVertically

8 or 7 0: FlippedHorizontally or (Rotated60 | Rotated120 | FlippedVertically)
10 60: Rotated60 | FlippedHorizontally
9 120: Rotated120 | FlippedHorizontally
4 or 11 180: (FlippedVertically) or (Rotated60 | Rotated120 | FlippedHorizontally)
6 240: Rotated60 | FlippedVertically
5 300: Rotated120 | FlippedVertically

*/

static const char rotateRightMask[16] = { 2, 12, 1, 14, 6, 8, 5, 10, 10, 4, 9, 0, 14, 0, 13, 2 }; // [0,15]->2->1->[12,3]->14->13; [8,7]->10->9->[4,11]->6->5;
static const char rotateLeftMask[16] = { 13, 2, 0, 1, 9, 6, 4, 5, 5, 10, 8, 9, 1, 14, 12, 13 }; // [0,15]->13->14->[12,3]->1->2; [8,7]->5->6->[4,11]->9->10;

const char (&rotateMask)[16] =
(direction == RotateRight) ? rotateRightMask : rotateLeftMask;

for (int y = 0; y < mHeight; ++y) {
for (int x = 0; x < mWidth; ++x) {
const Cell &source = cellAt(x, y);
Cell dest = source;

unsigned char mask =
(static_cast<unsigned char>(dest.flippedHorizontally()) << 3) |
(static_cast<unsigned char>(dest.flippedVertically()) << 2) |
(static_cast<unsigned char>(dest.rotatedHexagonal60()) << 1) |
(static_cast<unsigned char>(dest.rotatedHexagonal120()) << 0);

mask = rotateMask[mask];

dest.setFlippedHorizontally((mask & 8) != 0);
dest.setFlippedVertically((mask & 4) != 0);
dest.setRotatedHexagonal60((mask & 2) != 0);
dest.setRotatedHexagonal120((mask & 1) != 0);

if (direction == RotateRight)
newGrid[x * newWidth + (mHeight - y - 1)] = dest;
else
newGrid[(mWidth - x - 1) * newWidth + y] = dest;
}
}

mWidth = newWidth;
mHeight = newHeight;
mGrid = newGrid;
}


QSet<SharedTileset> TileLayer::usedTilesets() const
{
Expand Down
39 changes: 35 additions & 4 deletions src/libtiled/tilelayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,19 @@ class Cell
_tileId(-1),
_flippedHorizontally(false),
_flippedVertically(false),
_flippedAntiDiagonally(false)
_flippedAntiDiagonally(false),
_rotatedHexagonal60(false),
_rotatedHexagonal120(false)
{}

explicit Cell(Tile *tile) :
_tileset(tile ? tile->tileset() : nullptr),
_tileId(tile ? tile->id() : -1),
_flippedHorizontally(false),
_flippedVertically(false),
_flippedAntiDiagonally(false)
_flippedAntiDiagonally(false),
_rotatedHexagonal60(false),
_rotatedHexagonal120(false)
{}

bool isEmpty() const { return _tileset == nullptr; }
Expand All @@ -77,7 +81,9 @@ class Cell
&& _tileId == other._tileId
&& _flippedHorizontally == other._flippedHorizontally
&& _flippedVertically == other._flippedVertically
&& _flippedAntiDiagonally == other._flippedAntiDiagonally;
&& _flippedAntiDiagonally == other._flippedAntiDiagonally
&& _rotatedHexagonal60 == other._rotatedHexagonal60
&& _rotatedHexagonal120 == other._rotatedHexagonal120;
}

bool operator != (const Cell &other) const
Expand All @@ -86,7 +92,9 @@ class Cell
|| _tileId != other._tileId
|| _flippedHorizontally != other._flippedHorizontally
|| _flippedVertically != other._flippedVertically
|| _flippedAntiDiagonally != other._flippedAntiDiagonally;
|| _flippedAntiDiagonally != other._flippedAntiDiagonally
|| _rotatedHexagonal60 != other._rotatedHexagonal60
|| _rotatedHexagonal120 != other._rotatedHexagonal120;
}

Tileset *tileset() const { return _tileset; }
Expand All @@ -96,10 +104,16 @@ class Cell
bool flippedVertically() const { return _flippedVertically; }
bool flippedAntiDiagonally() const { return _flippedAntiDiagonally; }

bool rotatedHexagonal60() const { return _rotatedHexagonal60; }
bool rotatedHexagonal120() const { return _rotatedHexagonal120; }

void setFlippedHorizontally(bool f) { _flippedHorizontally = f; }
void setFlippedVertically(bool f) { _flippedVertically = f; }
void setFlippedAntiDiagonally(bool f) { _flippedAntiDiagonally = f; }

void setRotatedHexagonal60(bool f) { _rotatedHexagonal60 = f; }
void setRotatedHexagonal120(bool f) { _rotatedHexagonal120 = f; }

Tile *tile() const;
void setTile(Tile *tile);
void setTile(Tileset *tileset, int tileId);
Expand All @@ -111,6 +125,9 @@ class Cell
bool _flippedHorizontally;
bool _flippedVertically;
bool _flippedAntiDiagonally;

bool _rotatedHexagonal60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is just an alias for _flippedAntiDiagonally, please don't add it as a member. Instead, you can use _flippedAntiDiagonally in rotatedHexagonal60 and setRotatedHexagonal60. It may be a little confusing, but on the other hand it will make it more clear that the same flag is used here.

bool _rotatedHexagonal120;
};

inline Tile *Cell::tile() const
Expand Down Expand Up @@ -237,13 +254,27 @@ class TILEDSHARED_EXPORT TileLayer : public Layer
*/
void flip(FlipDirection direction);

/**
* Hexagonal flip this tile layer in the given \a direction. Direction must be
* horizontal or vertical. This doesn't change the dimensions of the
* tile layer.
*/
void flipHexagonal(FlipDirection direction);

/**
* Rotate this tile layer by 90 degrees left or right. The tile positions
* are rotated within the layer, and the tiles themselves are rotated. The
* dimensions of the tile layer are swapped.
*/
void rotate(RotateDirection direction);

/**
* Hexagonal rotate this tile layer by 90 degrees left or right. The tile positions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation should mention 60 degrees here.

* are rotated within the layer, and the tiles themselves are rotated. The
* dimensions of the tile layer are swapped.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that swapping the dimensions of the layer really makes no sense at all, and indeed the rotation of multiple tiles by 60 degrees doesn't really do anything useful. Essentially, the layer is rotated by 90 degrees while the tile graphics are getting rotated by 60 degrees. Is this something you have considered, and do you see a good solution to this? Of course, it doesn't help that trying to paint with a bigger stamp on a hex map doesn't work properly half the time due to the staggering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be this documentation helps http://www.redblobgames.com/grids/hexagons/#rotation

A bird in the hand is worth two in the bush )

Rotate group tile is good feature, but now it rather rotate a single tile for purposes in my application.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so we can finish the basic feature first for single-tiles, and I can keep an issue open for making the rotation of groups work properly. That documentation definitely helps.

*/
void rotateHexagonal(RotateDirection direction);

/**
* Computes and returns the set of tilesets used by this tile layer.
*/
Expand Down
12 changes: 10 additions & 2 deletions src/tiled/tilestamp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ TileStamp TileStamp::flipped(FlipDirection direction) const

for (const TileStampVariation &variation : flipped.variations()) {
TileLayer *layer = variation.tileLayer();
layer->flip(direction);
if (variation.map->orientation() != Map::Hexagonal) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer if == was used here, and the bodies swapped of course.

layer->flip(direction);
} else {
layer->flipHexagonal(direction);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coding style: please leave out the superfluous { and } for one-line bodies.

}

return flipped;
Expand All @@ -249,7 +253,11 @@ TileStamp TileStamp::rotated(RotateDirection direction) const

for (const TileStampVariation &variation : rotated.variations()) {
TileLayer *layer = variation.tileLayer();
layer->rotate(direction);
if (variation.map->orientation() != Map::Hexagonal) {
layer->rotate(direction);
} else {
layer->rotateHexagonal(direction);
}

variation.map->setWidth(layer->width());
variation.map->setHeight(layer->height());
Expand Down