import { Frame, defineFrame } from "./framework.js"; import tilemapRegistry from "./tilemap-registry.js"; export function canConnect(tile) { return tile == 1; } export function shouldConnect(a, b) { return a == b; } const dirs47 = { E: 0b0000_0001, SE: 0b0000_0010, S: 0b0000_0100, SW: 0b0000_1000, W: 0b0001_0000, NW: 0b0010_0000, N: 0b0100_0000, NE: 0b1000_0000, }; function isSet(integer, bit) { return (integer & bit) == bit; } function removeRedundancies(t) { if (isSet(t, dirs47.SE) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.E))) { t &= ~dirs47.SE; } if (isSet(t, dirs47.SW) && (!isSet(t, dirs47.S) || !isSet(t, dirs47.W))) { t &= ~dirs47.SW; } if (isSet(t, dirs47.NW) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.W))) { t &= ~dirs47.NW; } if (isSet(t, dirs47.NE) && (!isSet(t, dirs47.N) || !isSet(t, dirs47.E))) { t &= ~dirs47.NE; } return t; } function ordinalDirections() { let unique = new Set(); for (let i = 0; i <= 0b1111_1111; ++i) { unique.add(removeRedundancies(i)); } return Array.from(unique).sort((a, b) => a - b); } let xToConnectionBitSet = ordinalDirections(); let connectionBitSetToX = new Uint8Array(256); for (let i = 0; i < xToConnectionBitSet.length; ++i) { connectionBitSetToX[xToConnectionBitSet[i]] = i; } console.log(connectionBitSetToX); export class TileEditor extends Frame { constructor() { super(); this.tileCursor = { x: 0, y: 0 }; this.colorScheme = { background: "#F7F7F7", grid: "#00000011", tileCursor: "#222222", tiles: [ "transparent", "#eb134a", ], }; this.tileColorPalette = [ "transparent", "#eb134a", ]; } connectedCallback() { super.connectedCallback(); this.tileSize = parseInt(this.getAttribute("data-tile-size")); let tilemapId = this.getAttribute("data-tilemap-id"); if (tilemapId != null) { this.tilemap = tilemapRegistry[this.getAttribute("data-tilemap-id")]; } else { throw new ReferenceError(`tilemap '${tilemapId}' does not exist`); } // 0st element is explicitly null because it represents the empty tile. this.tilesets = [null]; this.tilesets47 = [null]; let attachedImages = this.getElementsByTagName("img"); for (let image of attachedImages) { if (image.hasAttribute("data-tairu-tileset")) { let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset")); this.tilesets[tilesetIndex] = image; } else if (image.hasAttribute("data-tairu-tileset-47")) { let tilesetIndex = parseInt(image.getAttribute("data-tairu-tileset-47")); this.tilesets47[tilesetIndex] = image; } } this.width = this.tilemap.width * this.tileSize; this.height = this.tilemap.height * this.tileSize; this.hasFocus = false; this.paintingTile = null; this.addEventListener("mousemove", event => this.mouseMoved(event)); this.addEventListener("mousedown", event => this.mousePressed(event)); this.addEventListener("mouseup", event => this.mouseReleased(event)); this.addEventListener("mouseenter", _ => this.hasFocus = true); this.addEventListener("mouseleave", _ => this.hasFocus = false); this.addEventListener("contextmenu", event => event.preventDefault()); // TODO: This should also work on mobile. } draw() { this.ctx.fillStyle = this.colorScheme.background; this.ctx.fillRect(0, 0, this.width, this.height); this.drawTiles(); this.drawGrid(); if (this.hasFocus) { this.drawTileCursor(); } } drawGrid() { this.ctx.beginPath(); for (let x = 0; x < this.tilemap.width; ++x) { this.ctx.moveTo(x * this.tileSize, 0); this.ctx.lineTo(x * this.tileSize, this.height); } for (let y = 0; y < this.tilemap.width; ++y) { this.ctx.moveTo(0, y * this.tileSize); this.ctx.lineTo(this.width, y * this.tileSize); } this.ctx.strokeStyle = this.colorScheme.grid; this.ctx.lineWidth = 1; this.ctx.stroke(); } drawTileCursor() { this.ctx.strokeStyle = this.colorScheme.tileCursor; this.ctx.lineWidth = 5; this.ctx.strokeRect(this.tileCursor.x * this.tileSize, this.tileCursor.y * this.tileSize, this.tileSize, this.tileSize); } get hasTilesets() { // Remember that tile 0 represents emptiness. return this.tilesets.length > 1 || this.tilesets47.length > 1; } drawTiles() { if (this.hasTilesets) { this.drawTexturedTiles(); } else { this.drawColoredTiles(); } } drawColoredTiles() { for (let y = 0; y < this.tilemap.height; ++y) { for (let x = 0; x < this.tilemap.width; ++x) { let tile = this.tilemap.at(x, y); if (tile != 0) { this.ctx.fillStyle = this.colorScheme.tiles[tile]; this.ctx.fillRect(x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize); } } } } drawTexturedTiles() { this.ctx.imageSmoothingEnabled = false; for (let y = 0; y < this.tilemap.height; ++y) { for (let x = 0; x < this.tilemap.width; ++x) { let tile = this.tilemap.at(x, y); if (tile != 0) { let tileset16 = this.tilesets[tile]; let tileset47 = this.tilesets47[tile]; let tileset = tileset47 != null ? tileset47 : tileset16; let tileIndex = 0; if (tileset47 != null) { let rawTileIndex = 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? dirs47.E : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y + 1)) ? dirs47.SE : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? dirs47.S : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y + 1)) ? dirs47.SW : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? dirs47.W : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y - 1)) ? dirs47.NW : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? dirs47.N : 0; rawTileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y - 1)) ? dirs47.NE : 0; tileIndex = connectionBitSetToX[removeRedundancies(rawTileIndex)]; } else { tileIndex |= shouldConnect(tile, this.tilemap.at(x + 1, y)) ? 0b0001 : 0; tileIndex |= shouldConnect(tile, this.tilemap.at(x, y + 1)) ? 0b0010 : 0; tileIndex |= shouldConnect(tile, this.tilemap.at(x - 1, y)) ? 0b0100 : 0; tileIndex |= shouldConnect(tile, this.tilemap.at(x, y - 1)) ? 0b1000 : 0; } let tilesetTileSize = tileset.height; let tilesetX = tileIndex * tilesetTileSize; let tilesetY = 0; this.ctx.drawImage( tileset, tilesetX, tilesetY, tilesetTileSize, tilesetTileSize, x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize, ); } } } } mouseMoved(event) { let mouse = this.getMousePositionFromEvent(event); this.tileCursor.x = Math.floor(mouse.x / this.tileSize); this.tileCursor.y = Math.floor(mouse.y / this.tileSize); this.paintTileUnderCursor(); } mousePressed(event) { event.preventDefault(); if (event.button == 0) { this.paintingTile = 1; } else if (event.button == 2) { this.paintingTile = 0; } this.paintTileUnderCursor(); } mouseReleased() { this.paintingTile = null; } paintTileUnderCursor() { if (this.paintingTile != null) { this.tilemap.setAt(this.tileCursor.x, this.tileCursor.y, this.paintingTile); } } } defineFrame("tairu-editor", TileEditor);