Skip to content

Commit

Permalink
fix(TileEngine): account for flipped and rotated tiles (#402)
Browse files Browse the repository at this point in the history
* Added logic to retrieve tile id when it is turn clockwise or anticlockwise

* Working version for rotated and flipped tiles.

* Cleaning up the code

* Solved the cases ID number 9 and 11

* all cases solved. working solution. I will refactor to reduce code size

* Reduced code size by refactoring conditions

* updated my comment to be right

* Added two tests to cover new features of flipped and rotated tiles
  • Loading branch information
sdeseille authored Jun 21, 2024
1 parent 1eb1da3 commit 388afb5
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 4 deletions.
60 changes: 56 additions & 4 deletions src/tileEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { removeFromArray } from './utils.js';
// @see https://doc.mapeditor.org/en/stable/reference/global-tile-ids/
let FLIPPED_HORIZONTALLY = 0x80000000;
let FLIPPED_VERTICALLY = 0x40000000;
// tile can be rotated also and use the bit 30 in conjunction
// with bit 32 or/and 31 to denote that
let FLIPPED_DIAGONALLY = 0x20000000;
// @endif

/**
Expand Down Expand Up @@ -638,14 +641,41 @@ class TileEngine {
if (!tile) return;

let flipped = 0;
let rotated = 0;

// @ifdef TILEENGINE_TILED
// read flags
let flippedHorizontal = tile & FLIPPED_HORIZONTALLY;
let flippedVertical = tile & FLIPPED_VERTICALLY;
let turnedClockwise = 0;
let turnedAntiClockwise = 0;
let flippedAndturnedClockwise = 0;
let flippedAndturnedAntiClockwise = 0;
let flippedDiagonally = 0;
flipped = flippedHorizontal || flippedVertical;

tile &= ~(FLIPPED_HORIZONTALLY | FLIPPED_VERTICALLY);

flippedDiagonally = tile & FLIPPED_DIAGONALLY;
// Identify tile rotation
if (flippedDiagonally) {
if (flippedHorizontal && flippedVertical) {
flippedAndturnedClockwise = 1;
} else if (flippedHorizontal) {
turnedClockwise = 1;
} else if (flippedVertical) {
turnedAntiClockwise = 1;
} else {
flippedAndturnedAntiClockwise = 1;
}
rotated =
turnedClockwise ||
turnedAntiClockwise ||
flippedAndturnedClockwise ||
flippedAndturnedAntiClockwise;

tile &= ~FLIPPED_DIAGONALLY;
}
// @endif

// find the tileset the tile belongs to
Expand All @@ -669,7 +699,27 @@ class TileEngine {
let sy = ((offset / cols) | 0) * (tileheight + margin);

// @ifdef TILEENGINE_TILED
if (flipped) {
if (rotated) {
context.save();
// Translate to the center of the tile
context.translate(x + tilewidth / 2, y + tileheight / 2);
if (turnedAntiClockwise || flippedAndturnedAntiClockwise) {
// Rotate 90° anticlockwise
context.rotate(-Math.PI / 2); // 90° in radians
} else if (turnedClockwise || flippedAndturnedClockwise) {
// Rotate 90° clockwise
context.rotate(Math.PI / 2); // 90° in radians
}
if (
flippedAndturnedClockwise ||
flippedAndturnedAntiClockwise
) {
// Then flip horizontally
context.scale(-1, 1);
}
x = -tilewidth / 2;
y = -tileheight / 2;
} else if (flipped) {
context.save();
context.translate(
x + (flippedHorizontal ? tilewidth : 0),
Expand All @@ -679,6 +729,8 @@ class TileEngine {
flippedHorizontal ? -1 : 1,
flippedVertical ? -1 : 1
);
x = flipped ? 0 : x;
y = flipped ? 0 : y;
}
// @endif

Expand All @@ -688,14 +740,14 @@ class TileEngine {
sy,
tilewidth,
tileheight,
flipped ? 0 : x,
flipped ? 0 : y,
x,
y,
tilewidth,
tileheight
);

// @ifdef TILEENGINE_TILED
if (flipped) {
if (flipped || rotated) {
context.restore();
}
// @endif
Expand Down
104 changes: 104 additions & 0 deletions test/unit/tileEngine.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,110 @@ describe(
)
).to.be.true;
});

it('a tile flipped and turned clockwise', () => {
let tileEngine = TileEngine({
tilewidth: 10,
tileheight: 10,
width: 1,
height: 1,
tilesets: [
{
firstgid: 1,
image: new Image(),
columns: 10
}
],
layers: [
{
name: 'test',
data: [3 + 0x80000000 + 0x40000000 + 0x20000000]
}
]
});

let r = tileEngine._r.bind(tileEngine);
let ctx;
tileEngine._r = function overrideR(layer, context) {
ctx = context;
sinon.stub(context, 'drawImage').callsFake(noop);
sinon.stub(context, 'translate').callsFake(noop);
sinon.stub(context, 'scale').callsFake(noop);
sinon.stub(context, 'rotate').callsFake(noop);
r(layer, context);
};

tileEngine.renderLayer('test');

expect(ctx.translate.calledWith(5, 5)).to.be.true;
expect(ctx.rotate.calledWith(Math.PI / 2)).to.be.true;
expect(ctx.scale.calledWith(-1, 1)).to.be.true;
expect(
ctx.drawImage.calledWith(
tileEngine.tilesets[0].image,
20,
0,
10,
10,
-5,
-5,
10,
10
)
).to.be.true;
});

it('a tile flipped and turned anticlockwise', () => {
let tileEngine = TileEngine({
tilewidth: 10,
tileheight: 10,
width: 1,
height: 1,
tilesets: [
{
firstgid: 1,
image: new Image(),
columns: 10
}
],
layers: [
{
name: 'test',
data: [3 + 0x20000000]
}
]
});

let r = tileEngine._r.bind(tileEngine);
let ctx;
tileEngine._r = function overrideR(layer, context) {
ctx = context;
sinon.stub(context, 'drawImage').callsFake(noop);
sinon.stub(context, 'translate').callsFake(noop);
sinon.stub(context, 'scale').callsFake(noop);
sinon.stub(context, 'rotate').callsFake(noop);
r(layer, context);
};

tileEngine.renderLayer('test');

expect(ctx.translate.calledWith(5, 5)).to.be.true;
expect(ctx.rotate.calledWith(-Math.PI / 2)).to.be.true;
expect(ctx.scale.calledWith(-1, 1)).to.be.true;
expect(
ctx.drawImage.calledWith(
tileEngine.tilesets[0].image,
20,
0,
10,
10,
-5,
-5,
10,
10
)
).to.be.true;
});
} else {
it('does not rotate tile', () => {
let tileEngine = TileEngine({
Expand Down

0 comments on commit 388afb5

Please sign in to comment.