diff --git a/src/cartographer.js b/src/cartographer.js index 2ae2419..57b7e33 100644 --- a/src/cartographer.js +++ b/src/cartographer.js @@ -1,4 +1,11 @@ -import {extend} from './utils.js'; +import {has, toFixed} from './utils.js'; + +const SETTINGS_DEFAULTS = { + // in pixels + scale: 50, + scaleMin: 10, + scaleMax: 250, +}; export default class Cartographer { constructor(settings) { @@ -9,31 +16,22 @@ export default class Cartographer { 'getScale', 'move', - 'zoom' + '_checkMove', + + 'zoom', ].map(method => this[method] = this[method].bind(this)); - this.pointyTop = settings.pointyTop; + Object.assign(this, SETTINGS_DEFAULTS, settings); - // in pixels - this.originX = 0; - this.originY = 0; + this.originX = has(this, 'originX') ? this.originX : + this.canvasWidth ? parseInt(this.canvasWidth / 2) : + 0; - // in pixels - this.scale = 25; - this.scaleMin = 10; - this.scaleMax = 250; + this.originY = has(this, 'originY') ? this.originY : + this.canvasHeight ? parseInt(this.canvasHeight / 2) : + 0; - // in cells - this.width = 0; - this.height = 0; - - extend(this, settings); - - this.originX = parseInt(this.originX); - this.originY = parseInt(this.originY); - - this.width = parseInt(this.width); - this.height = parseInt(this.height); + this._checkScale(this.canvasHeight, this.canvasWidth); } getOriginX() {return this.originX;} @@ -44,13 +42,45 @@ export default class Cartographer { move(event) { if (event.deltaX) { - this.originX = Math.floor((this.originX*1000) + (event.deltaX*1000)) / 1000; + this.originX = toFixed(this.originX + event.deltaX); } if (event.deltaY) { - this.originY = Math.floor((this.originY*1000) + (event.deltaY*1000)) / 1000; + this.originY = toFixed(this.originY + event.deltaY); } + + this._checkMove(event); + } + + _checkMove(event) { + const colWidth = this.horizontalDistance(); + const rowHeight = this.verticalDistance(); + + if (this.width) { + const canvasWidth = event.target.offsetWidth; + const halfBoardWidth = (this.width * colWidth + colWidth) / 2; + + this.originX = this.originX > halfBoardWidth ? halfBoardWidth : + (canvasWidth - this.originX) > halfBoardWidth ? canvasWidth - halfBoardWidth : + this.originX; + } + + if (this.height) { + const canvasHeight = event.target.offsetHeight; + const halfBoardHeight = (this.height * rowHeight + rowHeight) / 2; + + this.originY = this.originY > halfBoardHeight ? halfBoardHeight : + (canvasHeight - this.originY) > halfBoardHeight ? canvasHeight - halfBoardHeight : + this.originY; + } + } + + _checkScale(canvasHeight, canvasWidth) { + const heightMin = this.height ? this.calculateVerticalScale(canvasHeight, this.height + 1) : 0; + const widthMin = this.width ? this.calculateHorizontalScale(canvasWidth, this.width + 1) : 0; + this.scaleMin = Math.max(this.scaleMin, heightMin, widthMin); + this.scale = this.scaleMin > this.scale ? this.scaleMin : this.scale; } zoom(event) { @@ -71,7 +101,11 @@ export default class Cartographer { // zoom to the current mouse location this.move({ deltaX: (((event.offsetX - this.originX) / scaleOrig) * (scaleOrig - scaleTemp)), - deltaY: (((event.offsetY - this.originY) / scaleOrig) * (scaleOrig - scaleTemp)) + deltaY: (((event.offsetY - this.originY) / scaleOrig) * (scaleOrig - scaleTemp)), + target: { + offsetWidth: event.target.offsetWidth, + offsetHeight: event.target.offsetHeight, + }, }); } } diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js new file mode 100644 index 0000000..27c4341 --- /dev/null +++ b/src/cartographerFlatXY.js @@ -0,0 +1,79 @@ +import Cartographer from './cartographer.js'; + +import {rangeInclusive, sqrt2} from './utils.js'; + +import Square from './square.js'; +import Point from './point.js'; + +export default class CartographerFlatXY extends Cartographer { + constructor(settings) { + super(settings); + + [ + 'maxWidth', + 'minWidth', + + 'horizontalDistance', + 'verticalDistance', + + 'calculateHorizontalScale', + 'calculateVerticalScale', + + 'tileToPixel', + 'pixelToTile', + 'boundingBox', + ].map(method => this[method] = this[method].bind(this)); + } + + maxWidth() { + return this.minWidth() * sqrt2; + } + + minWidth() { + return this.scale * 2; + } + + horizontalDistance() { + return this.minWidth(); + } + + verticalDistance() { + return this.minWidth(); + } + + calculateHorizontalScale(pixels, tiles) { + return pixels / tiles / 2; + } + + calculateVerticalScale(pixels, tiles) { + return pixels / tiles / 2; + } + + tileToPixel(square) { + const x = square.getX() * this.minWidth(); + const y = square.getY() * this.minWidth(); + + return new Point(x + this.originX, this.originY - y); + } + + pixelToTile(point) { + const pixelX = point.getX() - this.originX; + const pixelY = this.originY - point.getY(); + + const x = pixelX / this.minWidth(); + const y = pixelY / this.minWidth(); + + return new Square(x, y); + } + + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { + 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))); + } +} diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js new file mode 100644 index 0000000..2769864 --- /dev/null +++ b/src/cartographerFlatXYZ.js @@ -0,0 +1,86 @@ +import Cartographer from './cartographer.js'; + +import {rangeInclusive, sqrt3} from './utils.js'; + +import Hex from './hex.js'; +import Point from './point.js'; + +export default class CartographerFlatXYZ extends Cartographer { + constructor(settings) { + super(settings); + + [ + 'maxWidth', + 'minWidth', + + 'horizontalDistance', + 'verticalDistance', + + 'calculateHorizontalScale', + 'calculateVerticalScale', + + 'tileToPixel', + 'pixelToTile', + 'boundingBox', + ].map(method => this[method] = this[method].bind(this)); + } + + maxWidth() { + return this.scale * 2; + } + + minWidth() { + return this.scale * sqrt3; + } + + horizontalDistance() { + return this.maxWidth() * (3/4); + } + + verticalDistance() { + return this.minWidth(); + } + + calculateHorizontalScale(pixels, tiles) { + return pixels / (tiles * (3/4)) / 2; + } + + calculateVerticalScale(pixels, tiles) { + return pixels / tiles / sqrt3; + } + + tileToPixel(hex) { + const pixelX = this.scale * 3/2 * hex.getQ(); + const pixelY = this.scale * sqrt3 * (hex.getR() + (hex.getQ() / 2)); + + return new Point(pixelX + this.originX, pixelY + this.originY); + } + + pixelToTile(point) { + 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); + } + + 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 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)); + }); + } +} diff --git a/src/cartographerPointyXY.js b/src/cartographerPointyXY.js new file mode 100644 index 0000000..d1c29c9 --- /dev/null +++ b/src/cartographerPointyXY.js @@ -0,0 +1,105 @@ +import Cartographer from './cartographer.js'; + +import {rangeInclusive, sqrt2} from './utils.js'; + +import Square from './square.js'; +import Point from './point.js'; + +export default class CartographerPointyXY extends Cartographer { + constructor(settings) { + super(settings); + + [ + 'maxWidth', + 'minWidth', + + 'horizontalDistance', + 'verticalDistance', + + 'calculateHorizontalScale', + 'calculateVerticalScale', + + 'tileToPixel', + 'pixelToTile', + 'boundingBox', + ].map(method => this[method] = this[method].bind(this)); + } + + maxWidth() { + return this.minWidth() * sqrt2; + } + + minWidth() { + return this.scale * 2; + } + + horizontalDistance() { + return this.maxWidth() / 2; + } + + verticalDistance() { + return this.maxWidth() / 2; + } + + calculateHorizontalScale(pixels, tiles) { + return pixels / sqrt2 / tiles; + } + + calculateVerticalScale(pixels, tiles) { + return pixels / sqrt2 / tiles; + } + + tileToPixel(square) { + const x = square.getX(); + const y = square.getY(); + + // (above/below axis) * (distance from axis) / (size) + let pixelX = (x < y ? -1 : 1) * (Math.abs(y - x) / sqrt2) * this.minWidth(); + let pixelY = (-x < y ? 1 : -1) * (Math.abs(x + y) / sqrt2) * this.minWidth(); + + return new Point(pixelX + this.originX, this.originY - pixelY); + } + + pixelToTile(point) { + const pixelX = point.getX() - this.originX; + const pixelY = this.originY - point.getY(); + + // (above/below axis) * (distance from axis) / (size) + const x = (-pixelX < pixelY ? 1 : -1) * (Math.abs(pixelX + pixelY) / sqrt2) / this.minWidth(); + const y = (pixelX < pixelY ? 1 : -1) * (Math.abs(pixelY - pixelX) / sqrt2) / this.minWidth(); + + return new Square(x, y); + } + + 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 columns = rangeInclusive(lowerLeftTile.getX(), upperRightTile.getX()); + + const upperLeftIntercept = upperLeftTile.getY() - upperLeftTile.getX(); + const upperRightIntercept = upperLeftTile.getY() + upperLeftTile.getX(); + + const lowerLeftIntercept = lowerRightTile.getY() - lowerRightTile.getX(); + const lowerRightIntercept = lowerRightTile.getY() + lowerRightTile.getX(); + + const aboutHalf = Math.floor(columns.length / 2); + const midway = columns.length % 2 ? columns[aboutHalf] : + (columns[aboutHalf - 1] + columns[aboutHalf]) / 2; + + return columns.map(x => { + let top = x < midway ? upperLeftIntercept + x : upperRightIntercept - x; + let bottom = x < midway ? lowerRightIntercept - x : lowerLeftIntercept + x; + + bottom = Math.min(bottom, top); + top = Math.max(bottom, top); + + // push out by 1 on either end to account for interlocking tiles + const rows = rangeInclusive(bottom - 1, top + 1); + + return rows.map(y => new Square(x, y)); + }); + } +} diff --git a/src/cartographerPointyXYZ.js b/src/cartographerPointyXYZ.js new file mode 100644 index 0000000..076f31b --- /dev/null +++ b/src/cartographerPointyXYZ.js @@ -0,0 +1,86 @@ +import Cartographer from './cartographer.js'; + +import {rangeInclusive, sqrt3} from './utils.js'; + +import Hex from './hex.js'; +import Point from './point.js'; + +export default class CartographerPointyXYZ extends Cartographer { + constructor(settings) { + super(settings); + + [ + 'maxWidth', + 'minWidth', + + 'horizontalDistance', + 'verticalDistance', + + 'calculateHorizontalScale', + 'calculateVerticalScale', + + 'tileToPixel', + 'pixelToTile', + 'boundingBox', + ].map(method => this[method] = this[method].bind(this)); + } + + maxWidth() { + return this.scale * 2; + } + + minWidth() { + return this.scale * sqrt3; + } + + horizontalDistance() { + return this.minWidth(); + } + + verticalDistance() { + return this.maxWidth() * (3/4); + } + + calculateHorizontalScale(pixels, tiles) { + return pixels / tiles / sqrt3; + } + + calculateVerticalScale(pixels, tiles) { + return pixels / (tiles * (3/4)) / 2; + } + + tileToPixel(hex) { + 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) { + 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); + } + + 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 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)); + }); + } +} diff --git a/src/cartographerXY.js b/src/cartographerXY.js deleted file mode 100644 index 38539bb..0000000 --- a/src/cartographerXY.js +++ /dev/null @@ -1,132 +0,0 @@ -import Cartographer from './cartographer.js'; - -import {rangeInclusive, sqrt2} from './utils.js'; - -import Square from './square.js'; -import Point from './point.js'; - -export default class CartographerXY extends Cartographer { - constructor(settings) { - super(settings); - - [ - 'maxWidth', - 'minWidth', - - 'maxDistance', - 'minDistance', - - 'getCellWidth', - 'getCellHeight', - - 'getHorzDistance', - 'getVertDistance', - - 'tileToPixel', - 'pixelToTile', - 'boundingBox', - ].map(method => this[method] = this[method].bind(this)); - } - - maxWidth() { - return this.minWidth() * sqrt2; - } - - minWidth() { - return this.scale * 2; - } - - maxDistance() { - return this.maxWidth(); - } - - minDistance() { - return this.minWidth(); - } - - getCellWidth() {return this.pointyTop ? this.maxWidth() : this.minWidth();} - getCellHeight() {return this.pointyTop ? this.maxWidth() : this.minWidth();} - - getHorzDistance() {return this.pointyTop ? this.maxDistance() : this.minDistance();} - getVertDistance() {return this.pointyTop ? this.maxDistance() : this.minDistance();} - - tileToPixel(square) { - const scale = this.scale; - - // (above/below axis) * (distance from axis) / (size) - const pointyDistanceX = (x, y) => (x < y ? -1 : 1) * (Math.abs(y - x) / sqrt2) * this.minWidth(); - const pointyDistanceY = (x, y) => (-x < y ? 1 : -1) * (Math.abs(x + y) / sqrt2) * this.minWidth(); - - const flatDistance = a => this.minWidth() * a; - - const x = square.getX(); - const y = square.getY(); - - let pixelX = this.pointyTop ? pointyDistanceX(x, y) : flatDistance(x); - let pixelY = this.pointyTop ? pointyDistanceY(x, y) : flatDistance(y); - - pixelX += this.originX; - pixelY = this.originY - pixelY;; - - return new Point(pixelX, pixelY); - } - - pixelToTile(point) { - let scale = this.scale; - - // (above/below axis) * (distance from axis) / (size) - const pointyDistanceX = (x, y) => (-x < y ? 1 : -1) * (Math.abs(x + y) / sqrt2) / this.minWidth(); - const pointyDistanceY = (x, y) => (x < y ? 1 : -1) * (Math.abs(y - x) / sqrt2) / this.minWidth(); - - const flatDistance = a => a / this.minWidth(); - - let pixelX = point.getX() - this.originX; - let pixelY = this.originY - point.getY(); - - let x = this.pointyTop ? pointyDistanceX(pixelX, pixelY) : flatDistance(pixelX); - let y = this.pointyTop ? pointyDistanceY(pixelX, pixelY) : flatDistance(pixelY); - - return new Square(x, y); - } - - boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { - const upperLeftTile = this.pixelToTile(upperLeftPoint); - const lowerRightTile = this.pixelToTile(lowerRightPoint); - const upperRightTile = this.pixelToTile(upperRightPoint); - - if (this.pointyTop) { - const lowerLeftTile = this.pixelToTile(lowerLeftPoint); - - const columns = rangeInclusive(lowerLeftTile.getX(), upperRightTile.getX()); - - const upperLeftIntercept = upperLeftTile.getY() - upperLeftTile.getX(); - const upperRightIntercept = upperLeftTile.getY() + upperLeftTile.getX(); - - const lowerLeftIntercept = lowerRightTile.getY() - lowerRightTile.getX(); - const lowerRightIntercept = lowerRightTile.getY() + lowerRightTile.getX(); - - const aboutHalf = Math.floor(columns.length / 2); - const midway = columns.length % 2 ? columns[aboutHalf] : - (columns[aboutHalf - 1] + columns[aboutHalf]) / 2; - - return columns.map(x => { - let top = x < midway ? upperLeftIntercept + x : upperRightIntercept - x; - let bottom = x < midway ? lowerRightIntercept - x : lowerLeftIntercept + x; - - bottom = Math.min(bottom, top); - top = Math.max(bottom, top); - - // push out by 1 on either end to account for interlocking tiles - const rows = rangeInclusive(bottom - 1, top + 1); - - return rows.map(y => new Square(x, y)); - }); - } - else { - 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))); - } - } -} diff --git a/src/cartographerXYZ.js b/src/cartographerXYZ.js deleted file mode 100644 index fd24513..0000000 --- a/src/cartographerXYZ.js +++ /dev/null @@ -1,124 +0,0 @@ -import Cartographer from './cartographer.js'; - -import {extend, rangeInclusive, sqrt3} from './utils.js'; - -import Hex from './hex.js'; -import Point from './point.js'; - -export default class CartographerXYZ extends Cartographer { - constructor(settings) { - super(settings); - - [ - 'maxWidth', - 'minWidth', - - 'maxDistance', - 'minDistance', - - 'getCellWidth', - 'getCellHeight', - - 'getHorzDistance', - 'getVertDistance', - - 'tileToPixel', - 'pixelToTile', - 'boundingBox', - ].map(method => this[method] = this[method].bind(this)); - } - - maxWidth() { - return this.scale * 2; - } - - minWidth() { - return this.maxWidth() * sqrt3; - } - - maxDistance() { - return this.maxWidth() * (3/4); - } - - minDistance() { - return this.minWidth(); - } - - getCellWidth() {return this.pointyTop ? this.minWidth() : this.maxWidth();} - getCellHeight() {return this.pointyTop ? this.maxWidth() : this.minWidth();} - - getHorzDistance() {return this.pointyTop ? this.minDistance() : this.maxDistance();} - getVertDistance() {return this.pointyTop ? this.maxDistance() : this.minDistance();} - - tileToPixel(hex) { - let scale = this.scale; - - function minWidth(a, b) { - return scale * sqrt3 * (a + (b / 2)) - }; - - function maxWidth(a) { - return scale * 3/2 * a - }; - - let pixelX = this.pointyTop ? minWidth(hex.getQ(), hex.getR()) : maxWidth(hex.getQ()); - let pixelY = this.pointyTop ? maxWidth(hex.getR()) : minWidth(hex.getR(), hex.getQ()); - - pixelX += this.originX; - pixelY += this.originY; - - return new Point(pixelX, pixelY); - } - - pixelToTile(point) { - let scale = this.scale; - - function radiusLong(a, b) { - return ((a * (sqrt3 / 3)) - (b / 3)) / scale; - }; - - function radiusShort(a) { - return (a * (2 / 3)) / scale; - }; - - let pixelX = point.getX() - this.originX; - let pixelY = point.getY() - this.originY; - - let q = this.pointyTop ? radiusLong(pixelX, pixelY) : radiusShort(pixelX); - let r = this.pointyTop ? radiusShort(pixelY) : radiusLong(pixelY, pixelX); - - 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); - - if (this.pointyTop) { - const rows = rangeInclusive(upperLeftTile.getR() -1 , lowerLeftTile.getR() + 1); - - const width = upperRightTile.getQ() - upperLeftTile.getQ(); - return rows.map((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)); - }); - } - else { - const columns = rangeInclusive(upperLeftTile.getQ() - 1, upperRightTile.getQ() + 1); - - const height = lowerRightTile.getR() - upperRightTile.getR(); - return columns.map((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)); - }); - } - } -} diff --git a/src/cell.js b/src/cell.js index 4116673..1ee53ca 100644 --- a/src/cell.js +++ b/src/cell.js @@ -1,5 +1,3 @@ - -import {extend} from './utils.js'; import {random} from './utils.js'; export default class Cell { @@ -19,7 +17,7 @@ export default class Cell { this.alpha = 0.5; this.created = Date.now(); - extend(this, settings); + Object.assign(this, settings); } getColor() { diff --git a/src/consts.js b/src/consts.js new file mode 100644 index 0000000..5658a30 --- /dev/null +++ b/src/consts.js @@ -0,0 +1,19 @@ +export const HEX = 'hex'; +export const CIRCLE = 'circle'; +export const SQUARE = 'square'; + +export const TILE_STYLES = {HEX, CIRCLE, SQUARE}; + +export const BOARD_STYLES = { + HEX, + SQUARE, +}; + +export const FLAT = 'flat'; +export const POINTY = 'pointy'; + +export const TILE_ORIENTATIONS = { + FLAT, + POINTY, +}; + diff --git a/src/drawHexagon.js b/src/drawHexagon.js index f1277c1..e4bcdb3 100644 --- a/src/drawHexagon.js +++ b/src/drawHexagon.js @@ -1,3 +1,4 @@ +import {POINTY} from './consts.js'; export default class DrawHexagon { constructor(settings) { @@ -33,8 +34,8 @@ export default class DrawHexagon { outline(context, scale, x, y, cell) { scale = scale * cell.scale; - let hexCornerX = cell.pointyTop ? this.pointyTopCornerX : this.flatTopCornerX; - let hexCornerY = cell.pointyTop ? this.pointyTopCornerY : this.flatTopCornerY; + let hexCornerX = cell.orientation === POINTY ? this.pointyTopCornerX : this.flatTopCornerX; + let hexCornerY = cell.orientation === POINTY ? this.pointyTopCornerY : this.flatTopCornerY; context.beginPath(); context.moveTo(x + scale * hexCornerX[0], y + scale * hexCornerY[0]); @@ -52,8 +53,8 @@ export default class DrawHexagon { filled(context, scale, x, y, cell) { scale = scale * cell.scale; - let hexCornerX = cell.pointyTop ? this.pointyTopCornerX : this.flatTopCornerX; - let hexCornerY = cell.pointyTop ? this.pointyTopCornerY : this.flatTopCornerY; + let hexCornerX = cell.orientation === POINTY ? this.pointyTopCornerX : this.flatTopCornerX; + let hexCornerY = cell.orientation === POINTY ? this.pointyTopCornerY : this.flatTopCornerY; context.beginPath(); context.moveTo(x + scale * hexCornerX[0], y + scale * hexCornerY[0]); diff --git a/src/drawSquare.js b/src/drawSquare.js index 6977ddd..f2d8c66 100644 --- a/src/drawSquare.js +++ b/src/drawSquare.js @@ -1,5 +1,5 @@ - import {sqrt2} from './utils.js'; +import {POINTY} from './consts.js'; export default class DrawSquare { constructor(settings) { @@ -11,8 +11,8 @@ export default class DrawSquare { filled(context, scale, x, y, cell) { scale = scale * cell.scale; - let squareCornerX = cell.pointyTop ? this.diamondX : this.squareX; - let squareCornerY = cell.pointyTop ? this.diamondY : this.squareY; + let squareCornerX = cell.orientation === POINTY ? this.diamondX : this.squareX; + let squareCornerY = cell.orientation === POINTY ? this.diamondY : this.squareY; context.beginPath(); context.moveTo(x + scale * squareCornerX[0], y + scale * squareCornerY[0]); @@ -26,8 +26,8 @@ export default class DrawSquare { outline(context, scale, x, y, cell) { scale = scale * cell.scale; - let squareCornerX = cell.pointyTop ? this.diamondX : this.squareX; - let squareCornerY = cell.pointyTop ? this.diamondY : this.squareY; + let squareCornerX = cell.orientation === POINTY ? this.diamondX : this.squareX; + let squareCornerY = cell.orientation === POINTY ? this.diamondY : this.squareY; context.beginPath(); context.moveTo(x + scale * squareCornerX[0], y + scale * squareCornerY[0]); diff --git a/src/main.js b/src/main.js index c7ac06b..d7ff746 100644 --- a/src/main.js +++ b/src/main.js @@ -18,7 +18,7 @@ class Demo { tile: Tessellate.TILE_STYLES.HEX, tap: this.onTap, draw: this.draw, - pointyTop: false, //utils.random(1) ? true : false, + orientation: Tessellate.TILE_ORIENTATIONS.FLAT, }, queryStringObj)); this.circle = new DrawCircle(); @@ -40,7 +40,7 @@ class Demo { x: tap.point.x, y: tap.point.y, scale, - pointyTop: utils.random(1) ? true : false, + orientation: utils.random(1) ? Tessellate.TILE_ORIENTATIONS.FLAT : Tessellate.TILE_ORIENTATIONS.POINTY, red: utils.random(255), green: utils.random(255), blue: utils.random(255), diff --git a/src/tessellate.js b/src/tessellate.js index 14798e0..cc4444b 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -14,17 +14,21 @@ export {DrawCircle, DrawHexagon, DrawSquare}; import Cell from './cell.js'; export {Cell}; -import CartographerXY from './cartographerXY.js'; -import CartographerXYZ from './cartographerXYZ.js'; +import CartographerFlatXY from './cartographerFlatXY.js'; +import CartographerPointyXY from './cartographerPointyXY.js'; +import CartographerFlatXYZ from './cartographerFlatXYZ.js'; +import CartographerPointyXYZ from './cartographerPointyXYZ.js'; -const HEX = 'hex'; -const CIRCLE = 'circle'; -const SQUARE = 'square'; - -const TILE_STYLES = {HEX, CIRCLE, SQUARE}; +import { + HEX, CIRCLE, SQUARE, + TILE_STYLES, + BOARD_STYLES, + FLAT, POINTY, + TILE_ORIENTATIONS, +} from './consts.js'; const TILES = { - [HEX]: new DrawHexagon(), + [HEX]: new DrawHexagon(), [CIRCLE]: new DrawCircle(), [SQUARE]: new DrawSquare(), }; @@ -34,18 +38,34 @@ const DEFAULTS = { board: HEX, tap: utils.noop, draw: utils.noop, - pointyTop: false, + orientation: FLAT, }; +function selectCartographer(board, orientation) { + switch (board) { + case HEX: + switch (orientation) { + case FLAT: return CartographerFlatXYZ; + case POINTY: return CartographerPointyXYZ; + } + case SQUARE: + switch (orientation) { + case FLAT: return CartographerFlatXY; + case POINTY: return CartographerPointyXY; + } + } +} + export default class Tessellate { - static get TILES () {return TILES} - static get TILE_STYLES () {return TILE_STYLES} - static get BOARD_STYLES () {return {HEX, SQUARE}} + static get TILES() {return TILES} + static get TILE_STYLES() {return TILE_STYLES} + static get BOARD_STYLES() {return BOARD_STYLES} + static get TILE_ORIENTATIONS() {return TILE_ORIENTATIONS} constructor(settings) { ['seedTiles', 'tap', 'draw', 'drawMap', 'move', 'zoom'].map(method => {this[method] = this[method].bind(this)}); - this.settings = utils.extend(DEFAULTS, settings); + this.settings = Object.assign(DEFAULTS, settings); this.settings.element = this.settings.element instanceof HTMLElement ? this.settings.element : document.querySelector(this.settings.element); @@ -64,61 +84,33 @@ export default class Tessellate { this.map = []; this.seedTiles(); - const boardSettings = { - pointyTop: this.settings.pointyTop, - originX: this.sketch.getContext().canvas.width / 2, - originY: this.sketch.getContext().canvas.height / 2 - }; + const cartographer = selectCartographer(this.settings.board, this.settings.orientation); + this.cartographer = new cartographer({ + height: this.settings.height, + width: this.settings.width, + scale: this.settings.scale, - this.cartographer = this.settings.board === HEX ? new CartographerXYZ(boardSettings) : new CartographerXY(boardSettings); + canvasWidth: this.sketch.getContext().canvas.width, + canvasHeight: this.sketch.getContext().canvas.height, + + originX: this.settings.originX || this.sketch.getContext().canvas.width / 2, + originY: this.settings.originY || this.sketch.getContext().canvas.height / 2, + }); } seedTiles() { this.map[0] = []; -// this.map[1] = []; -// this.map[-1] = []; -// this.map[-5] = []; this.map[0][0] = new Cell({ x: 0, y: 0, - pointyTop: this.settings.pointyTop, + orientation: this.settings.orientation, red: 0, green: 0, blue: 0, alpha: 75/100, scale: 9/10, }); -// this.map[1][1] = new Cell({ -// x: 1, -// y: 1, -// pointyTop: this.settings.pointyTop, -// red: 0, -// green: 0, -// blue: 255, -// alpha: 90/100, -// scale: 9/10, -// }); -// this.map[-1][-1] = new Cell({ -// x: -1, -// y: -1, -// pointyTop: this.settings.pointyTop, -// red: 255, -// green: 0, -// blue: 0, -// alpha: 90/100, -// scale: 9/10, -// }); -// this.map[-5][5] = new Cell({ -// x: -5, -// y: 5, -// pointyTop: this.settings.pointyTop, -// red: 0, -// green: 255, -// blue: 0, -// alpha: 90/100, -// scale: 9/10, -// }); } tap(event) { @@ -161,7 +153,7 @@ export default class Tessellate { this.map[tile.getX()][tile.getY()] = new Cell({ x: tile.getX(), y: tile.getY(), - pointyTop: this.settings.pointyTop, + orientation: this.settings.orientation, red: utils.random(64, 192), green: utils.random(64, 192), blue: utils.random(64, 192), diff --git a/src/utils.js b/src/utils.js index 78aa460..3fec8cf 100644 --- a/src/utils.js +++ b/src/utils.js @@ -33,14 +33,8 @@ export function clone(obj) { return JSON.parse(JSON.stringify(obj)); } -export function extend(obj, ...sources) { - sources.forEach(src => { - for (let key in src) { - if (src.hasOwnProperty(key)) obj[key] = src[key]; - } - }); - - return obj; +export function has(obj, prop) { + return obj && obj.hasOwnProperty(prop); } export function hypotenuse(a, b) { @@ -115,4 +109,7 @@ export function parseString (str) { str; } +export function toFixed(number, precision = 3, fallback = NaN) { + return typeof number === 'number' && !isNaN(number) ? Number(number.toFixed(precision)) : fallback; +}