From 60ab78abe83537e45354179d9caa3fb49b1a4754 Mon Sep 17 00:00:00 2001 From: straker <2433219+straker@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:13:56 -0600 Subject: [PATCH 1/2] feat(tileEngine): support flipped tiles in Tiled --- src/tileEngine.js | 46 +++++++- test/unit/tileEngine.spec.js | 200 +++++++++++++++++++++++++++++++++-- 2 files changed, 236 insertions(+), 10 deletions(-) diff --git a/src/tileEngine.js b/src/tileEngine.js index 183e0c47..4a8f7d43 100644 --- a/src/tileEngine.js +++ b/src/tileEngine.js @@ -3,6 +3,15 @@ import { on } from './events.js'; import { clamp, getWorldRect } from './helpers.js'; import { removeFromArray } from './utils.js'; +// @ifdef TILEENGINE_TILED + +// Tiled uses the bits 32 and 31 to denote that a tile is +// flipped horizontally or vertically (respectively) +// @see https://doc.mapeditor.org/en/stable/reference/global-tile-ids/ +let FLIPPED_HORIZONTALLY = 0x80000000; +let FLIPPED_VERTICALLY = 0x40000000; +// @endif + /** * Get the row from the y coordinate. * @private @@ -628,6 +637,17 @@ class TileEngine { // skip empty tiles (0) if (!tile) return; + let flipped = 0; + + // read flags + // @ifdef TILEENGINE_TILED + let flippedHorizontal = tile & FLIPPED_HORIZONTALLY; + let flippedVertical = tile & FLIPPED_VERTICALLY; + flipped = flippedHorizontal || flippedVertical; + + tile &= ~(FLIPPED_HORIZONTALLY | FLIPPED_VERTICALLY); + // @endif + // find the tileset the tile belongs to // assume tilesets are ordered by firstgid let tileset; @@ -645,20 +665,40 @@ class TileEngine { let x = (index % width) * tilewidth; let y = ((index / width) | 0) * tileheight; - let sx = (offset % cols) * (tilewidth + margin); + let sx = (offset % cols | 0) * (tilewidth + margin); let sy = ((offset / cols) | 0) * (tileheight + margin); + // @ifdef TILEENGINE_TILED + if (flipped) { + context.save(); + context.translate( + x + (flippedHorizontal ? tilewidth : 0), + y + (flippedVertical ? tileheight : 0) + ); + context.scale( + flippedHorizontal ? -1 : 1, + flippedVertical ? -1 : 1 + ); + } + // @endif + context.drawImage( image, sx, sy, tilewidth, tileheight, - x, - y, + flipped ? 0 : x, + flipped ? 0 : y, tilewidth, tileheight ); + + // @ifdef TILEENGINE_TILED + if (flipped) { + context.restore(); + } + // @endif }); context.restore(); diff --git a/test/unit/tileEngine.spec.js b/test/unit/tileEngine.spec.js index 8e901275..3f70845e 100644 --- a/test/unit/tileEngine.spec.js +++ b/test/unit/tileEngine.spec.js @@ -6,7 +6,8 @@ import { noop } from '../../src/utils.js'; let testContext = { TILEENGINE_CAMERA: true, TILEENGINE_DYNAMIC: true, - TILEENGINE_QUERY: true + TILEENGINE_QUERY: true, + TILEENGINE_TILED: true }; // test-context:end @@ -229,8 +230,7 @@ describe( expect(tileEngine.sx).to.equal(0); expect(tileEngine.sy).to.equal(0); }); - } - else { + } else { it('should not have sx and sy properties', () => { expect(tileEngine.sx).to.not.exist; expect(tileEngine.sy).to.not.exist; @@ -753,9 +753,6 @@ describe( tileEngine.renderLayer('test'); - const img = new Image(); - img.src = tileEngine.layerCanvases.test.toDataURL(); - expect(context.drawImage.called).to.be.true; expect( context.drawImage.calledWith( @@ -878,7 +875,7 @@ describe( expect(tileEngine._r.called).to.be.true; }); } else { - it('doe snot call render if the layer is dirty', () => { + it('does not call render if the layer is dirty', () => { let tileEngine = TileEngine({ tilewidth: 10, tileheight: 10, @@ -939,6 +936,195 @@ describe( expect(fn).to.not.throw(); }); + + if (testContext.TILEENGINE_TILED) { + it('rotates a tile horizontally', () => { + 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] + } + ] + }); + + 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); + r(layer, context); + }; + + tileEngine.renderLayer('test'); + + expect(ctx.translate.calledWith(10, 0)).to.be.true; + expect(ctx.scale.calledWith(-1, 1)).to.be.true; + expect( + ctx.drawImage.calledWith( + tileEngine.tilesets[0].image, + 20, + 0, + 10, + 10, + 0, + 0, + 10, + 10 + ) + ).to.be.true; + }); + + it('rotates a tile vertically', () => { + let tileEngine = TileEngine({ + tilewidth: 10, + tileheight: 10, + width: 1, + height: 1, + tilesets: [ + { + firstgid: 1, + image: new Image(), + columns: 10 + } + ], + layers: [ + { + name: 'test', + data: [3 + 0x40000000] + } + ] + }); + + 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); + r(layer, context); + }; + + tileEngine.renderLayer('test'); + + expect(ctx.translate.calledWith(0, 10)).to.be.true; + expect(ctx.scale.calledWith(1, -1)).to.be.true; + expect( + ctx.drawImage.calledWith( + tileEngine.tilesets[0].image, + 20, + 0, + 10, + 10, + 0, + 0, + 10, + 10 + ) + ).to.be.true; + }); + + it('rotates a tile horizontally and vertically', () => { + 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] + } + ] + }); + + 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); + r(layer, context); + }; + + tileEngine.renderLayer('test'); + + expect(ctx.translate.calledWith(10, 10)).to.be.true; + expect(ctx.scale.calledWith(-1, -1)).to.be.true; + expect( + ctx.drawImage.calledWith( + tileEngine.tilesets[0].image, + 20, + 0, + 10, + 10, + 0, + 0, + 10, + 10 + ) + ).to.be.true; + }); + } else { + it('does not rotate tile', () => { + 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] + } + ] + }); + + 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); + r(layer, context); + }; + + tileEngine.renderLayer('test'); + + expect(ctx.translate.called).to.be.false; + expect(ctx.scale.called).to.be.false; + }); + } }); // -------------------------------------------------- From 597ad1d613da06dd5c57ace08cd4e0d22d5b9b7c Mon Sep 17 00:00:00 2001 From: straker <2433219+straker@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:22:52 -0600 Subject: [PATCH 2/2] revert small change --- src/tileEngine.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tileEngine.js b/src/tileEngine.js index 4a8f7d43..d3a5a1f8 100644 --- a/src/tileEngine.js +++ b/src/tileEngine.js @@ -639,8 +639,8 @@ class TileEngine { let flipped = 0; - // read flags // @ifdef TILEENGINE_TILED + // read flags let flippedHorizontal = tile & FLIPPED_HORIZONTALLY; let flippedVertical = tile & FLIPPED_VERTICALLY; flipped = flippedHorizontal || flippedVertical; @@ -665,7 +665,7 @@ class TileEngine { let x = (index % width) * tilewidth; let y = ((index / width) | 0) * tileheight; - let sx = (offset % cols | 0) * (tilewidth + margin); + let sx = (offset % cols) * (tilewidth + margin); let sy = ((offset / cols) | 0) * (tileheight + margin); // @ifdef TILEENGINE_TILED