diff --git a/src/cartographer.js b/src/cartographer.js index 975f91b..07a00e5 100644 --- a/src/cartographer.js +++ b/src/cartographer.js @@ -2,6 +2,8 @@ import {has} from './utils.js'; import {pick} from './funky'; const DEFAULTS = { + adjustScaleMin: true, + // in pixels scale: 50, scaleMin: 10, @@ -21,6 +23,8 @@ export default class Cartographer { 'setOriginX', 'setOriginY', + 'pixelToTile', + 'zoom', 'remap', @@ -28,7 +32,13 @@ export default class Cartographer { this.settings = Object.assign({}, DEFAULTS, settings); - Object.assign(this, pick(this.settings, ['height', 'width', 'negativeTiles'])); + Object.assign(this, pick(this.settings, [ + 'height', + 'negativeTiles', + 'radius', + 'width', + 'wrap', + ])); this.checkScale(this.settings.canvasHeight, this.settings.canvasWidth); @@ -46,7 +56,7 @@ export default class Cartographer { const widthMin = this.width ? this.calculateHorizontalScale(canvasWidth, this.width) : 0; this.scaleMax = this.settings.scaleMax; - this.scaleMin = Math.max(this.settings.scaleMin, heightMin, widthMin); + this.scaleMin = this.settings.adjustScaleMin ? Math.max(this.settings.scaleMin, heightMin, widthMin) : this.settings.scaleMin; this.scale = this.scaleMin > this.settings.scale ? this.scaleMin : this.settings.scale; } @@ -102,21 +112,23 @@ export default class Cartographer { const maxX = this.tileWidth() / 2; const minY = canvasHeight - (this.tileHeight() / 2); - this.originX = this.originX > maxX ? maxX : this.originX; - this.originY = this.originY < minY ? minY : this.originY; + if (!this.wrap) { + this.originX = this.originX > maxX ? maxX : this.originX; + this.originY = this.originY < minY ? minY : this.originY; - if (this.width) { - const boardWidth = this.width * colWidth + this.horizontalOverhang(); - const minX = maxX - (boardWidth - canvasWidth); + if (this.width) { + const boardWidth = this.width * colWidth + this.horizontalOverhang(); + const minX = maxX - (boardWidth - canvasWidth); - this.originX = this.originX < minX ? minX : this.originX; - } + this.originX = this.originX < minX ? minX : this.originX; + } - if (this.height) { - const boardHeight = this.height * rowHeight + this.verticalOverhang(); - const maxY = boardHeight - (this.tileHeight() / 2); + if (this.height) { + const boardHeight = this.height * rowHeight + this.verticalOverhang(); + const maxY = boardHeight - (this.tileHeight() / 2); - this.originY = this.originY > maxY ? maxY : this.originY; + this.originY = this.originY > maxY ? maxY : this.originY; + } } } @@ -124,23 +136,30 @@ export default class Cartographer { const colWidth = this.horizontalDistance(); const rowHeight = this.verticalDistance(); - if (this.width) { - const canvasWidth = event.width; - const halfBoardWidth = (this.width * colWidth + this.horizontalOverhang()) / 2; + if (!this.wrap) { + if (this.width) { + const canvasWidth = event.width; + const halfBoardWidth = (this.width * colWidth + this.horizontalOverhang()) / 2; - this.originX = this.originX > halfBoardWidth ? halfBoardWidth : - (canvasWidth - this.originX) > halfBoardWidth ? canvasWidth - halfBoardWidth : - this.originX; + this.originX = this.originX > halfBoardWidth ? halfBoardWidth : + (canvasWidth - this.originX) > halfBoardWidth ? canvasWidth - halfBoardWidth : + this.originX; + } + + if (this.height) { + const canvasHeight = event.height; + const halfBoardHeight = (this.height * rowHeight + this.verticalOverhang()) / 2; + + this.originY = this.originY > halfBoardHeight ? halfBoardHeight : + (canvasHeight - this.originY) > halfBoardHeight ? canvasHeight - halfBoardHeight : + this.originY; + } } + } - if (this.height) { - const canvasHeight = event.height; - const halfBoardHeight = (this.height * rowHeight + this.verticalOverhang()) / 2; - - this.originY = this.originY > halfBoardHeight ? halfBoardHeight : - (canvasHeight - this.originY) > halfBoardHeight ? canvasHeight - halfBoardHeight : - this.originY; - } + pixelToTile (point) { + const tile = this._pixelToTile(point); + return this.wrap ? this.teleport(tile) : tile; } zoom (event) { diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js index 13b194e..9ee15f8 100644 --- a/src/cartographerFlatXY.js +++ b/src/cartographerFlatXY.js @@ -1,9 +1,12 @@ import Cartographer from './cartographer.js'; +import * as funky from './funky'; import {rangeInclusive, invSqrt2} from './utils.js'; -import Square from './square.js'; import Point from './point.js'; +import Square from './square.js'; + +const tilePointToSquare = ({tilePoint, pixelPoint}) => ({tilePoint: new Square(tilePoint), pixelPoint}); export default class CartographerFlatXY extends Cartographer { constructor(settings) { @@ -26,7 +29,11 @@ export default class CartographerFlatXY extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', + 'teleport', + + 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -80,7 +87,7 @@ export default class CartographerFlatXY extends Cartographer { return new Point(x + this.originX, this.originY - y); } - pixelToTile(point) { + _pixelToTile (point) { point = point instanceof Point ? point : new Point(...arguments); const pixelX = point.getX() - this.originX; @@ -92,14 +99,51 @@ export default class CartographerFlatXY extends Cartographer { return new Square(x, y); } + teleport ({x, y}) { + x = x % this.width; + y = y % this.height; + + x = x < 0 ? this.width + x : x; + y = y < 0 ? this.height + y : y; + + return new Point(x, y); + } + + inBounds ({x, y}) { + if (this.negativeTiles) { + return (!this.width || Math.abs(x) <= Math.floor(this.width / 2)) + && (!this.height || Math.abs(y) <= Math.floor(this.height / 2)); + } + else { + return (!this.width || (x >= 0 && x < this.width)) + && (!this.height || (y >= 0 && y < this.height)); + } + } + + enforceBoundries ({tilePoint, pixelPoint}) { + return this.wrap ? ({tilePoint: this.teleport(tilePoint), pixelPoint}) : + this.inBounds(tilePoint) ? ({tilePoint, pixelPoint}) : + null; + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { - const upperLeftTile = this.pixelToTile(upperLeftPoint); - const lowerRightTile = this.pixelToTile(lowerRightPoint); - const upperRightTile = this.pixelToTile(upperRightPoint); + const upperLeftTile = this._pixelToTile(upperLeftPoint); + const lowerRightTile = this._pixelToTile(lowerRightPoint); + const upperRightTile = this._pixelToTile(upperRightPoint); const columns = rangeInclusive(upperLeftTile.getX(), upperRightTile.getX()); const rows = rangeInclusive(lowerRightTile.getY(), upperLeftTile.getY()); - return columns.map(x => rows.map(y => new Square(x, y))); + const makeAPoint = x => rows.map(y => ({x, y})); + const makeAPointPair = tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)}); + + return funky.chain(columns) + .map(makeAPoint) + .flatten() + .map(makeAPointPair) + .map(this.enforceBoundries) + .compact() + .map(tilePointToSquare) + .value(); } } diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 9ec6194..4b2f45f 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -1,12 +1,17 @@ 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, pixelPoint}) => ({tilePoint: new Hex(tilePoint), pixelPoint}); + +const zeroZeroZero = new Hex({x: 0, y: 0, z: 0}); + export default class CartographerFlatXYZ extends Cartographer { - constructor(settings) { + constructor (settings) { super(settings); [ @@ -26,52 +31,91 @@ export default class CartographerFlatXYZ extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_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() { + tileHeight () { return this.minWidth(); } - tileWidth() { + tileWidth () { return this.maxWidth(); } - maxWidth() { + maxWidth () { return this.scale * 2; } - minWidth() { + minWidth () { return this.scale * sqrt3; } - horizontalOverhang() { + horizontalOverhang () { return this.maxWidth() * 0.25; } - verticalOverhang() { + verticalOverhang () { return 0; } - horizontalDistance() { + horizontalDistance () { return this.maxWidth() * (3/4); } - verticalDistance() { + verticalDistance () { return this.minWidth(); } - calculateHorizontalScale(pixels, tiles) { + calculateHorizontalScale (pixels, tiles) { return pixels / (tiles * 0.75 + 0.25) / 2; } - calculateVerticalScale(pixels, tiles) { + calculateVerticalScale (pixels, tiles) { return pixels / tiles / sqrt3; } - tileToPixel(hex) { + tileToPixel (hex) { hex = hex instanceof Hex ? hex : new Hex(...arguments); const pixelX = this.scale * 3/2 * hex.getQ(); @@ -80,7 +124,7 @@ export default class CartographerFlatXYZ extends Cartographer { return new Point(pixelX + this.originX, pixelY + this.originY); } - pixelToTile(point) { + _pixelToTile (point) { point = point instanceof Point ? point : new Point(...arguments); const pixelX = point.getX() - this.originX; @@ -92,21 +136,113 @@ export default class CartographerFlatXYZ extends Cartographer { return new Hex(q, r); } - 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); + teleport (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)); + } + } + } + + enforceBoundries ({tilePoint, pixelPoint}) { + return this.wrap ? ({tilePoint: this.teleport(tilePoint), pixelPoint}) : + this.inBounds(tilePoint) ? ({tilePoint, pixelPoint}) : + null; + } + + 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(); - return columns.map((q, index) => { + + 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); - return rows.map(r => new Hex(q, r)); - }); + const makeAPoint = r => Hex.qrToCube(q, r); + + return funky.chain(rows) + .map(makeAPoint) + .map(makeAPointPair) + .map(this.enforceBoundries) + .compact() + .map(tilePointToHex) + .value(); + }; + + return funky.chain(columns) + .map(processRow) + .flatten() + .value(); } } diff --git a/src/cartographerPointyXY.js b/src/cartographerPointyXY.js index a5e1140..d32f813 100644 --- a/src/cartographerPointyXY.js +++ b/src/cartographerPointyXY.js @@ -1,10 +1,13 @@ import Cartographer from './cartographer.js'; +import * as funky from './funky'; import {rangeInclusive, invSqrt2, sqrt2} from './utils.js'; import Square from './square.js'; import Point from './point.js'; +const tilePointToSquare = ({tilePoint, pixelPoint}) => ({tilePoint: new Square(tilePoint), pixelPoint}); + export default class CartographerPointyXY extends Cartographer { constructor(settings) { super(settings); @@ -26,7 +29,11 @@ export default class CartographerPointyXY extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', + 'teleport', + + 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -84,7 +91,7 @@ export default class CartographerPointyXY extends Cartographer { return new Point(pixelX + this.originX, this.originY - pixelY); } - pixelToTile(point) { + _pixelToTile (point) { point = point instanceof Point ? point : new Point(...arguments); const pixelX = point.getX() - this.originX; @@ -97,11 +104,38 @@ export default class CartographerPointyXY extends Cartographer { return new Square(x, y); } + teleport ({x, y}) { + x = x % this.width; + y = y % this.height; + + x = x < 0 ? this.width + x : x; + y = y < 0 ? this.height + y : y; + + return new Point(x, y); + } + + inBounds ({x, y}) { + if (this.negativeTiles) { + return (!this.width || Math.abs(x) <= Math.floor(this.width / 2)) + && (!this.height || Math.abs(y) <= Math.floor(this.height / 2)); + } + else { + return (!this.width || (x >= 0 && x < this.width)) + && (!this.height || (y >= 0 && y < this.height)); + } + } + + enforceBoundries ({tilePoint, pixelPoint}) { + return this.wrap ? ({tilePoint: this.teleport(tilePoint), pixelPoint}) : + this.inBounds(tilePoint) ? ({tilePoint, pixelPoint}) : + null; + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { - const upperLeftTile = this.pixelToTile(upperLeftPoint); - const lowerRightTile = this.pixelToTile(lowerRightPoint); - const upperRightTile = this.pixelToTile(upperRightPoint); - const lowerLeftTile = this.pixelToTile(lowerLeftPoint); + const upperLeftTile = this._pixelToTile(upperLeftPoint); + const lowerRightTile = this._pixelToTile(lowerRightPoint); + const upperRightTile = this._pixelToTile(upperRightPoint); + const lowerLeftTile = this._pixelToTile(lowerLeftPoint); const columns = rangeInclusive(lowerLeftTile.getX(), upperRightTile.getX()); @@ -115,7 +149,9 @@ export default class CartographerPointyXY extends Cartographer { const midway = columns.length % 2 ? columns[aboutHalf] : (columns[aboutHalf - 1] + columns[aboutHalf]) / 2; - return columns.map(x => { + const makeAPointPair = tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)}); + + const processRow = x => { let top = x < midway ? upperLeftIntercept + x : upperRightIntercept - x; let bottom = x < midway ? lowerRightIntercept - x : lowerLeftIntercept + x; @@ -123,9 +159,22 @@ export default class CartographerPointyXY extends Cartographer { top = Math.max(bottom, top); // push out by 1 on either end to account for interlocking tiles - const rows = rangeInclusive(bottom - 1, top + 1); + const row = rangeInclusive(bottom - 1, top + 1); - return rows.map(y => new Square(x, y)); - }); + const makeAPoint = y => ({x, y}); + + return funky.chain(row) + .map(makeAPoint) + .map(makeAPointPair) + .map(this.enforceBoundries) + .compact() + .map(tilePointToSquare) + .value(); + }; + + return funky.chain(columns) + .map(processRow) + .flatten() + .value(); } } diff --git a/src/cartographerPointyXYZ.js b/src/cartographerPointyXYZ.js index bd03d6b..cd7965b 100644 --- a/src/cartographerPointyXYZ.js +++ b/src/cartographerPointyXYZ.js @@ -1,12 +1,17 @@ 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, pixelPoint}) => ({tilePoint: new Hex(tilePoint), pixelPoint}); + +const zeroZeroZero = new Hex({x: 0, y: 0, z: 0}); + export default class CartographerPointyXYZ extends Cartographer { - constructor(settings) { + constructor (settings) { super(settings); [ @@ -26,52 +31,91 @@ export default class CartographerPointyXYZ extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_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() { + tileHeight () { return this.maxWidth(); } - tileWidth() { + tileWidth () { return this.minWidth(); } - maxWidth() { + maxWidth () { return this.scale * 2; } - minWidth() { + minWidth () { return this.scale * sqrt3; } - horizontalOverhang() { + horizontalOverhang () { return 0; } - verticalOverhang() { + verticalOverhang () { return this.maxWidth() * 0.25; } - horizontalDistance() { + horizontalDistance () { return this.minWidth(); } - verticalDistance() { + verticalDistance () { return this.maxWidth() * (3/4); } - calculateHorizontalScale(pixels, tiles) { + calculateHorizontalScale (pixels, tiles) { return pixels / tiles / sqrt3; } - calculateVerticalScale(pixels, tiles) { + calculateVerticalScale (pixels, tiles) { return pixels / (tiles * 0.75 + 0.25) / 2; } - tileToPixel(hex) { + tileToPixel (hex) { hex = hex instanceof Hex ? hex : new Hex(...arguments); const pixelX = this.scale * sqrt3 * (hex.getQ() + (hex.getR() / 2)); @@ -80,7 +124,7 @@ export default class CartographerPointyXYZ extends Cartographer { return new Point(pixelX + this.originX, pixelY + this.originY); } - pixelToTile(point) { + _pixelToTile (point) { point = point instanceof Point ? point : new Point(...arguments); const pixelX = point.getX() - this.originX; @@ -92,21 +136,115 @@ export default class CartographerPointyXYZ extends Cartographer { return new Hex(q, r); } - 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); + teleport (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)); + } + } + } + + enforceBoundries ({tilePoint, pixelPoint}) { + return this.wrap ? ({tilePoint: this.teleport(tilePoint), pixelPoint}) : + this.inBounds(tilePoint) ? ({tilePoint, pixelPoint}) : + null; + } + + 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(); - return rows.map((r, index) => { + + 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); - return columns.map(q => new Hex(q, r)); - }); + 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) + .compact() + .map(tilePointToHex) + .value(); + }; + + return funky.chain(rows) + .map(processColumn) + .flatten() + .value(); } } diff --git a/src/funky.js b/src/funky.js index 0f6c337..d38dcd6 100644 --- a/src/funky.js +++ b/src/funky.js @@ -1,5 +1,10 @@ export function chain (obj) { let chainInstance = { + log: function (predicate = () => true) { + if (predicate(obj)) console.log(obj); + return chainInstance; + }, + value: function () { return obj; } @@ -16,7 +21,7 @@ export function chain (obj) { } export function compact (obj) { - return filter(obj, val => val ? true : false); + return filter(obj, val => val != null); } export function contains (obj, value) { @@ -64,9 +69,12 @@ export function find (obj, predicate) { } } -export function flatten (list) { - if (Array.isArray(list)) { - return list.reduce((memo, element) => memo.concat(Array.isArray(element) ? flatten(element) : element), []); +export function flatten (obj) { + if (Array.isArray(obj)) { + return obj.reduce((memo, element) => memo.concat(Array.isArray(element) ? flatten(element) : element), []); + } + else { + return reduce((flat, prop) => flat.concat(prop), []) } } diff --git a/src/hex.js b/src/hex.js index 7f130ca..8965f53 100644 --- a/src/hex.js +++ b/src/hex.js @@ -35,11 +35,56 @@ function roundOff(hex) { } export default class Hex extends Point { + static qrToCube (q, r) { + return { + x: q, + y: computeY(q, r), + z: r, + }; + } + + static cubeToEvenR ({x, y, z}) { + const col = x + (z + (z & 1)) / 2;; + const row = z; + + return {col, row}; + } + + static evenRToCube (col, row) { + const x = col - (row + (row & 1)) / 2;; + const z = row; + const y = -x - z; + + return new Hex(x, y, z); + } + + static cubeToEvenQ ({x, y, z}) { + const col = x; + const row = z + (x + (x & 1)) / 2; + + return {col, row}; + } + + static evenQToCube (col, row) { + const x = col; + const z = row - (col + (col & 1)) / 2; + const y = -x - z; + + return new Hex(x, y, z); + } constructor() { super(); - if (arguments.length === 2) { // hex = Hex(q, r); + if (arguments.length === 1) { + const {q, r, s = -q - r} = arguments[0]; + const {x, z, y = -x - z} = arguments[0]; + + this.x = !isNaN(q) ? q : x; + this.y = !isNaN(s) ? s : y; + this.z = !isNaN(r) ? r : z; + } + else if (arguments.length === 2) { // hex = Hex(q, r); this.x = arguments[0]; this.z = arguments[1]; this.y = computeY(this.x, this.z); @@ -57,65 +102,81 @@ export default class Hex extends Point { getY() {return this.y;} getZ() {return this.z;} - setX(newX) {this.x = newX; return this;} - setY(newY) {this.y = newY; return this;} - setZ(newZ) {this.z = newZ; return this;} + setX (newX) {this.x = newX; return this;} + setY (newY) {this.y = newY; return this;} + setZ (newZ) {this.z = newZ; return this;} - moveX(byX) {this.x += byX; return this;} - moveY(byY) {this.y += byY; return this;} - moveZ(byZ) {this.z += byZ; return this;} + moveX (byX) {this.x += byX; return this;} + moveY (byY) {this.y += byY; return this;} + moveZ (byZ) {this.z += byZ; return this;} - getQ() {return this.x;} - getR() {return this.z;} + getQ () {return this.x;} + getR () {return this.z;} - setQ(newQ) { + setQ (newQ) { this.x = newQ; this.y = computeY(this.x, this.z); return this; } - setR(newR) { + + setR (newR) { this.z = newR; this.y = computeY(this.x, this.z); return this; } - moveQ(byQ) { + moveQ (byQ) { this.x += byQ; this.y = computeY(this.x, this.z); return this; } - moveR(byR) { + + moveR (byR) { this.z += byR; this.y = computeY(this.x, this.z); return this; } - getPoint() { return {x: this.x, y: this.y, z: this.z}; } + getPoint () { return {x: this.x, y: this.y, z: this.z}; } - setHex(newHex) { + setHex (newHex) { this.x = newHex.x; this.y = newHex.y; this.z = newHex.z; return this; } - moveHex(byHex) { + moveHex (byHex) { this.x += byHex.x; this.y += byHex.y; this.z += byHex.z; return this; } - getAxial() {return {q: this.x, r: this.z};} - setAxial(newAxial) { + subtractHex (hex) { + this.x -= hex.x; + this.y -= hex.y; + this.z -= hex.z; + + return this; + } + + getAxial () {return {q: this.x, r: this.z};} + + setAxial (newAxial) { this.x = newAxial.q; this.z = newAxial.r; this.y = computeY(this.x, this.y); return this; } - moveAxial(byAxial) { + + moveAxial (byAxial) { this.x += byAxial.q; this.z += byAxial.r; this.y = computeY(this.x, this.z); return this; } + + distance (hex) { + return Math.max(Math.abs(this.x - hex.x), Math.abs(this.y - hex.y), Math.abs(this.z - hex.z)); + } } diff --git a/src/main.js b/src/main.js index 4778a87..29f09f7 100644 --- a/src/main.js +++ b/src/main.js @@ -130,7 +130,15 @@ class Demo { const key = `${ x },${ z != null ? z : y }`; const pipMax = this.settings.tile === Tessellate.TILE_STYLES.HEX ? 7 : 9; - this.map[key].pips = Tessellate.utils.random(1, pipMax); + + if (this.map[key]) { + this.map[key].pips = Tessellate.utils.random(1, pipMax); + + console.log(`{${ x }, ${ y }${ z != null ? `, ${ z }` : ''}}`); + } + else { + console.log('ERROR - no tile', key); + } } pressStart(tap) { @@ -218,7 +226,11 @@ class Demo { return tile; } - drawTile({x, y, z}, context, scale) { + drawTile(pointGroup, context, scale) { + const {x, y, z} = pointGroup.tilePoint; + const pixelX = pointGroup.pixelPoint.getX(); + const pixelY = pointGroup.pixelPoint.getY(); + this.counts[0] += 1; const key = `${ x },${ z != null ? z : y }`; @@ -231,16 +243,15 @@ class Demo { }); let tile = this.map[key]; - const pixelPoint = this.tessellate.tileToPixel(x, y, z); const fadeFactor = this.gray ? Math.min((Date.now() - this.gray) / 5000, 1) : null; tile = this.gray ? this.fadeToGray(tile, fadeFactor) : tile; - Tessellate.TILES[tile.tileStyle][tile.drawStyle](context, scale, pixelPoint.getX(), pixelPoint.getY(), tile); + Tessellate.TILES[tile.tileStyle][tile.drawStyle](context, scale, pixelX, pixelY, tile); if (this.mined) { this.counts[2] += 1; - Tessellate.Shapes.mine(context, scale, pixelPoint.getX(), pixelPoint.getY()); + Tessellate.Shapes.mine(context, scale, pixelX, pixelY); } else { if (!tile.pips && this.pipDefault !== null) { @@ -255,12 +266,12 @@ class Demo { if (tile.pips) { this.counts[1] += tile.pips; - Tessellate.Shapes.pips(context, scale, pixelPoint.getX(), pixelPoint.getY(), tile); + Tessellate.Shapes.pips(context, scale, pixelX, pixelY, tile); } } } - draw({context, height, width, scale, tilePoints, now, lastNow}) { + draw({context, height, width, scale, pointGroups, now, lastNow}) { this.counts = [ 0, // tiles 0, // pips @@ -273,7 +284,7 @@ class Demo { blue: 128, }); - tilePoints.forEach(tilePoint => this.drawTile(tilePoint, context, scale)); + pointGroups.forEach(pointGroup => this.drawTile(pointGroup, context, scale)); this.ripples.forEach(({timestamp, cell}) => { const pressFactor = Math.min((now - timestamp) / PRESS_RIPPLE, 1); diff --git a/src/point.js b/src/point.js index 8d96b1f..50d54b9 100644 --- a/src/point.js +++ b/src/point.js @@ -1,6 +1,11 @@ export default class Point { constructor(x, y) { + if (typeof x === 'object') { + y = x.y; + x = x.x; + } + // add zero to turn -0 into 0 this.x = Math.round(x) + 0; this.y = Math.round(y) + 0; diff --git a/src/tessellate.js b/src/tessellate.js index c77aff4..fe55dc6 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -113,12 +113,15 @@ export class Tessellate { const cartographer = selectCartographer(this.settings.board, this.settings.orientation); this.cartographer = new cartographer(Object.assign(this.sketch.getSize(), funky.pick(this.settings, [ + 'adjustScaleMin', 'centerX', 'centerY', 'height', - 'width', - 'scale', 'negativeTiles', + 'radius', + 'scale', + 'width', + 'wrap', ]))); } @@ -128,6 +131,11 @@ export class Tessellate { this.settings.element = this.settings.element instanceof HTMLElement ? this.settings.element : document.querySelector(this.settings.element); + if (this.settings.radius) { + this.settings.height = this.settings.radius * 2 + 1; + this.settings.width = this.settings.radius * 2 + 1; + } + if (this.settings.negativeTiles) { if (this.settings.height && (this.settings.height % 2 === 0)) { this.settings.height++; @@ -141,6 +149,7 @@ export class Tessellate { tap (event) { let point = new Point(event.offsetX, event.offsetY); + let tile = this.cartographer.pixelToTile(point); let tap = { @@ -214,7 +223,7 @@ export class Tessellate { const lowerLeft = new Point(0, lowerRightY); const lowerRight = new Point(lowerRightX, lowerRightY); - return funky.flatten(this.cartographer.boundingBox(upperLeft, upperRight, lowerLeft, lowerRight)); + return this.cartographer.boundingBox(upperLeft, upperRight, lowerLeft, lowerRight); } draw (context) { @@ -222,6 +231,15 @@ export class Tessellate { const height = canvas.height; const width = canvas.width; + const corners = { + upperLeftX: 0, + upperLeftY: 0, + lowerRightX: width, + lowerRightY: height + }; + + const pointGroups = this.getTilePoints(corners); + this.settings.draw({ context, @@ -231,12 +249,7 @@ export class Tessellate { lastNow: context.lastUTC, now: context.utc, - tilePoints: this.getTilePoints({ - upperLeftX: 0, - upperLeftY: 0, - lowerRightX: width, - lowerRightY: height - }), + pointGroups, }); }