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 CartographerPointyXYZ 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.maxWidth(); } tileWidth () { return this.minWidth(); } maxWidth () { return this.scale * 2; } minWidth () { return this.scale * sqrt3; } horizontalOverhang () { return 0; } verticalOverhang () { return this.maxWidth() * 0.25; } horizontalDistance () { return this.minWidth(); } verticalDistance () { return this.maxWidth() * (3/4); } calculateHorizontalScale (pixels, tiles) { return pixels / tiles / sqrt3; } calculateVerticalScale (pixels, tiles) { return pixels / (tiles * 0.75 + 0.25) / 2; } tileToPixel (hex) { hex = hex instanceof Hex ? hex : new Hex(...arguments); const pixelX = this.scale * sqrt3 * (hex.getQ() + (hex.getR() / 2)); const pixelY = this.scale * 3/2 * hex.getR(); return new Point(pixelX + this.originX, pixelY + this.originY); } 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 * (sqrt3 / 3)) - (pixelY / 3)) / this.scale; const r = (pixelY * (2 / 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.cubeToEvenR(hex); // ensure odd-width maps wrap properly if (this.height % 2) { const offset = Math.floor(row / this.height); let horizontalAdjust = offset / 2; horizontalAdjust = offset % 2 === 0 ? horizontalAdjust : row % 2 ? Math.ceil(horizontalAdjust) : Math.floor(horizontalAdjust); col -= horizontalAdjust; } 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.evenRToCube(col, row); } } inBounds (hex) { hex = hex instanceof Hex ? hex : new Hex(hex); if (this.radius) { if (this.negativeTiles) { return Math.max(Math.abs(hex.x), Math.abs(hex.y), Math.abs(hex.z)) <= Math.floor(this.radius); } else { return Math.max(Math.abs(hex.x - this.radius), Math.abs(hex.y + this.radius), Math.abs(hex.z)) <= this.radius; } } else if (this.width || this.height) { if (this.negativeTiles) { return (!this.width || (Math.abs(hex.x) < this.width / 2)) && (!this.height || (Math.abs(-hex.y - Math.floor(hex.x / 2)) < (this.height / 2))); } else { return (!this.width || (hex.x >= 0 && hex.x < this.width)) && (!this.height || (hex.y <= (Math.floor(hex.x / 2) * -1) && (-hex.y - Math.floor(hex.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 rows = rangeInclusive(upperLeftTile.getR() -1 , lowerLeftTile.getR() + 1); const width = upperRightTile.getQ() - upperLeftTile.getQ(); const makeAPointPair = tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)}); const processColumn = (r, index) => { const left = upperLeftTile.getQ() - Math.floor(index / 2); const right = left + width; const columns = rangeInclusive(left, right + 1); const makeAPoint = q => Hex.qrToCube(q, r); //({x: q, z: r, y: -q - r}); return funky.chain(columns) .map(makeAPoint) .map(makeAPointPair) .map(this.enforceBoundries) .map(tilePointToHex) .value(); }; return funky.chain(rows) .map(processColumn) .flatten() .value(); } }