import Cartographer from './cartographer.js'; import * as funky from './funky'; import {rangeInclusive, sqrt3} from './utils.js'; import Hex from './hex.js'; import Point from './point.js'; const tilePointToHex = ({tilePoint, mapPoint, pixelPoint}) => ({ tile: tilePoint instanceof Hex ? tilePoint : new Hex(tilePoint), mapTile: mapPoint instanceof Hex ? mapPoint : mapPoint ? new Hex(mapPoint) : mapPoint, pixelPoint, }); const zeroZeroZero = new Hex({x: 0, y: 0, z: 0}); export default class CartographerFlatXYZ extends Cartographer { constructor (settings) { super(settings); [ 'tileHeight', 'tileWidth', 'maxWidth', 'minWidth', 'horizontalOverhang', 'verticalOverhang', 'horizontalDistance', 'verticalDistance', 'calculateHorizontalScale', 'calculateVerticalScale', 'tileToPixel', 'pixelToTile', 'teleport', 'inBounds', 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); if (this.radius) { this.mirrors = [ new Hex({ // East x: 2 * this.radius + 1, y: -this.radius - 1, z: -this.radius, }), new Hex({ // North East x: this.radius + 1, y: this.radius, z: -2 * this.radius - 1, }), new Hex({ // North West x: -this.radius, y: 2 * this.radius + 1, z: -this.radius - 1, }), new Hex ({ // West x: -2 * this.radius - 1, y: this.radius + 1, z: this.radius, }), new Hex ({ // South West x: -this.radius - 1, y: -this.radius, z: 2 * this.radius + 1, }), new Hex ({ // South East x: this.radius, y: -2 * this.radius - 1, z: this.radius + 1, }), ]; } } tileHeight () { return this.minWidth(); } tileWidth () { return this.maxWidth(); } maxWidth () { return this.scale * 2; } minWidth () { return this.scale * sqrt3; } horizontalOverhang () { return this.maxWidth() * 0.25; } verticalOverhang () { return 0; } horizontalDistance () { return this.maxWidth() * (3/4); } verticalDistance () { return this.minWidth(); } calculateHorizontalScale (pixels, tiles) { return pixels / (tiles * 0.75 + 0.25) / 2; } calculateVerticalScale (pixels, tiles) { return pixels / tiles / sqrt3; } tileToPixel (hex) { hex = hex instanceof Hex ? hex : new Hex(...arguments); const pixelX = this.scale * 3/2 * hex.getQ() + this.originX; const pixelY = this.scale * sqrt3 * (hex.getR() + (hex.getQ() / 2)) + this.originY; return new Point(pixelX, pixelY); } pixelToTile (point) { point = point instanceof Point ? point : new Point(...arguments); const pixelX = point.getX() - this.originX; const pixelY = point.getY() - this.originY; const q = (pixelX * (2 / 3)) / this.scale; const r = ((pixelY * (sqrt3 / 3)) - (pixelX / 3)) / this.scale; return new Hex(q, r); } teleport (hex) { if (!this.wrap) return hex; hex = hex instanceof Hex ? hex : new Hex(hex); if (this.radius) { if (hex.distance(zeroZeroZero) <= this.radius) return hex; const distances = this.mirrors.map(mirror => hex.distance(mirror)); const mirror = this.mirrors[distances.indexOf(Math.min(...distances))]; return this.teleport(hex.subtractHex(mirror)); } else { let {col, row} = Hex.cubeToEvenQ(hex); // ensure odd-width maps wrap properly if (this.width % 2) { const offset = Math.floor(col / this.width); let verticalAdjust = offset / 2; verticalAdjust = offset % 2 === 0 ? verticalAdjust : col % 2 ? Math.ceil(verticalAdjust) : Math.floor(verticalAdjust); row -= verticalAdjust; } const halfWidth = Math.floor(this.width / 2); const halfHeight = Math.floor(this.height / 2); if (this.negativeTiles) { col += halfWidth; row += halfHeight; } col = col % this.width; row = row % this.height; col = col < 0 ? col + this.width : col; row = row < 0 ? row + this.height : row; if (this.negativeTiles) { col -= halfWidth; row -= halfHeight; } return Hex.evenQToCube(col, row); } } inBounds ({x, z, y = -x - z}) { if (this.radius) { if (this.negativeTiles) { return Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) <= Math.floor(this.radius); } else { return Math.max(Math.abs(x - this.radius), Math.abs(y + this.radius), Math.abs(z)) <= this.radius; } } else if (this.width || this.height) { if (this.negativeTiles) { return (!this.width || (Math.abs(x) < this.width / 2)) && (!this.height || (Math.abs(-y - Math.floor(x / 2)) < (this.height / 2))); } else { return (!this.width || (x >= 0 && x < this.width)) && (!this.height || (y <= (Math.floor(x / 2) * -1) && (-y - Math.floor(x / 2)) < this.height)); } } else { return true; } } enforceBoundries ({tilePoint, pixelPoint}) { return this.wrap ? {tilePoint, mapPoint: this.teleport(tilePoint), pixelPoint} : this.inBounds(tilePoint) ? {tilePoint, mapPoint: tilePoint, pixelPoint} : {tilePoint, mapPoint: null, pixelPoint}; } boundingBox (upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this.pixelToTile(upperLeftPoint); const lowerLeftTile = this.pixelToTile(lowerLeftPoint); const lowerRightTile = this.pixelToTile(lowerRightPoint); const upperRightTile = this.pixelToTile(upperRightPoint); const columns = rangeInclusive(upperLeftTile.getQ() - 1, upperRightTile.getQ() + 1); const height = lowerRightTile.getR() - upperRightTile.getR(); const makeAPointPair = tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)}); const processRow = (q, index) => { const top = upperLeftTile.getR() - Math.floor(index / 2); const bottom = top + height; const rows = rangeInclusive(top, bottom + 1); const makeAPoint = r => Hex.qrToCube(q, r); return funky.chain(rows) .map(makeAPoint) .map(makeAPointPair) .map(this.enforceBoundries) .map(tilePointToHex) .value(); }; return funky.chain(columns) .map(processRow) .flatten() .value(); } }