diff --git a/src/cartographer.js b/src/cartographer.js new file mode 100644 index 0000000..a15f064 --- /dev/null +++ b/src/cartographer.js @@ -0,0 +1,189 @@ + +import {extend} from './utils.js'; + +//import Point from './point.js'; + +export default class Cartographer { + constructor(settings) { + [ +// 'maxWidth', +// 'minWidth', +// 'maxDistance', +// 'minDistance', + 'getOriginX', + 'getOriginY', + 'getScale', +// 'getCellWidth', +// 'getCellHeight', +// 'getHorzDistance', +// 'getVertDistance', +// 'tileToPixel', +// 'pixelToTile', +// 'boundingBox', + 'move', + 'zoom' + ].map(method => this[method] = this[method].bind(this)); + + this.pointyTop = settings.pointyTop; + + // in pixels + this.originX = 0; + this.originY = 0; + + // in pixels + this.scale = 25; + this.scaleMin = 10; + this.scaleMax = 250; + + // 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); + } + +// maxWidth() { +// return this.scale * 2; +// } +// +// minWidth() { +// return this.maxWidth() * sqrt3; +// } +// +// maxDistance() { +// return this.maxWidth() * (3/4); +// } +// +// minDistance() { +// return this.minWidth(); +// } + + getOriginX() {return this.originX;} + getOriginY() {return this.originY;} + + getScale() {return this.scale;} +// 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(tile) { +// 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(tile.getQ(), tile.getR()) : maxWidth(tile.getQ()); +// let pixelY = this.pointyTop ? maxWidth(tile.getR()) : minWidth(tile.getR(), tile.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 Tile(q, r); +// } + +// boundingBox(upperLeftPoint, lowerRightPoint) { +// let hexagons = []; +// let upperRightPoint = new Point(lowerRightPoint.getX(), upperLeftPoint.getY()); +// +// // push out by a 1 axially to account for interlocking hexagons +// // possibly return hexagons not within bounds +// let upperLeftTile = this.pixelToTile(upperLeftPoint).moveAxial({q: 0, r: -1}); +// let lowerRightTile = this.pixelToTile(lowerRightPoint).moveAxial({q: 0, r: 1}); +// let upperRightTile = this.pixelToTile(upperRightPoint).moveAxial({q: 1, r: -1}); +// +// let height = lowerRightTile.getR() - upperRightTile.getR(); +// let width = upperRightTile.getQ() - upperLeftTile.getQ(); +// +// if (this.pointyTop) { +// for (let row = 0; row <= height; row++) { +// hexagons[row] = []; +// let r = upperLeftTile.getR() + row; +// let qOffset = upperLeftTile.getQ() - Math.floor(row / 2); +// +// for (let q = qOffset; q <= qOffset + width; q++) { +// hexagons[row].push(new Tile(q, r)); +// } +// } +// } +// else { +// for (let col = 0; col <= width; col++) { +// hexagons[col] = []; +// let q = upperLeftTile.getQ() + col; +// let rOffset = upperLeftTile.getR() - Math.floor(col / 2); +// +// for (let r = rOffset; r <= rOffset + height; r++) { +// hexagons[col].push(new Tile(q, r)); +// } +// } +// } +// +// return hexagons; +// } + + move(event) { + if (event.deltaX) { + + this.originX = Math.floor((this.originX*1000) + (event.deltaX*1000)) / 1000; + } + + if (event.deltaY) { + + this.originY = Math.floor((this.originY*1000) + (event.deltaY*1000)) / 1000; + } + } + + zoom(event) { + let scaleOrig = this.scale; + + let scaleTemp = parseInt((scaleOrig + (event.deltaY / -10)) * 1000) / 1000; + + // make sure 'scale' doesn't get too small nor too big + if (scaleTemp < this.scaleMin) + scaleTemp = this.scaleMin; + else if (scaleTemp > this.scaleMax) + scaleTemp = this.scaleMax; + + if (scaleOrig != scaleTemp) { + + this.scale = scaleTemp; + + // zoom to the current mouse location + this.move({ + deltaX: (((event.offsetX - this.originX) / scaleOrig) * (scaleOrig - scaleTemp)), + deltaY: (((event.offsetY - this.originY) / scaleOrig) * (scaleOrig - scaleTemp)) + }); + } + } +} diff --git a/src/cartographerXY.js b/src/cartographerXY.js new file mode 100644 index 0000000..baf7f5e --- /dev/null +++ b/src/cartographerXY.js @@ -0,0 +1,120 @@ +import Cartographer from './cartographer.js'; + +import {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) { + let scale = this.scale; + + const minWidth = (a) => this.minWidth() * a; + + const maxWidth = (a) => this.maxWidth() * a; + + let pixelX = this.pointyTop ? maxWidth(square.getX()) : minWidth(square.getX()); + let pixelY = this.pointyTop ? maxWidth(square.getY()) : minWidth(square.getY()); + + pixelX += this.originX; + pixelY += this.originY; + + return new Point(pixelX, pixelY); + } + + pixelToTile(point) { + let scale = this.scale; + + const radiusLong = a => a / this.maxWidth(); + const radiusShort = a => a / this.minWidth(); + + let pixelX = point.getX() - this.originX; + let pixelY = point.getY() - this.originY; + + let x = this.pointyTop ? radiusLong(pixelX, pixelY) : radiusShort(pixelX); + let y = this.pointyTop ? radiusLong(pixelY, pixelX) : radiusShort(pixelY); + + return new Square(x, y); + } + + boundingBox(upperLeftPoint, lowerRightPoint) { + let tiles = []; + let upperRightPoint = new Point(lowerRightPoint.getX(), upperLeftPoint.getY()); + + let upperLeftTile = this.pixelToTile(upperLeftPoint); + let lowerRightTile = this.pixelToTile(lowerRightPoint); + let upperRightTile = this.pixelToTile(upperRightPoint); + + let height = lowerRightTile.getY() - upperRightTile.getY(); + let width = upperRightTile.getX() - upperLeftTile.getX(); + + if (this.pointyTop) { + for (let row = 0; row <= height; row++) { + tiles[row] = []; + let y = upperLeftTile.getY() + row; + let xOffset = upperLeftTile.getX(); + + for (let x = xOffset; x <= xOffset + width; x++) { + tiles[row].push(new Square(x, y)); + } + } + } + else { + for (let col = 0; col <= width; col++) { + tiles[col] = []; + let x = upperLeftTile.getX() + col; + let yOffset = upperLeftTile.getY(); + + for (let y = yOffset; y <= yOffset + height; y++) { + tiles[col].push(new Square(x, y)); + } + } + } + + return tiles; + } +} diff --git a/src/exWhyZee.js b/src/exWhyZee.js index c759744..8e4f4bc 100644 --- a/src/exWhyZee.js +++ b/src/exWhyZee.js @@ -19,13 +19,13 @@ export default class ExWhyZee { 'getHorzDistance', 'getVertDistance', 'hexToPixel', - 'pixelToHex', + 'pixelToTile', 'boundingBox', 'move', 'zoom' ].map(method => this[method] = this[method].bind(this)); - this.pointyTop = false; + this.pointyTop = settings.pointyTop; // in pixels this.originX = 0; @@ -94,7 +94,7 @@ export default class ExWhyZee { return new Point(pixelX, pixelY); } - pixelToHex(point) { + pixelToTile(point) { let scale = this.scale; function radiusLong(a, b) { @@ -120,18 +120,18 @@ export default class ExWhyZee { // push out by a 1 axially to account for interlocking hexagons // possibly return hexagons not within bounds - let upperLeftHex = this.pixelToHex(upperLeftPoint).moveAxial({q: 0, r: -1}); - let lowerRightHex = this.pixelToHex(lowerRightPoint).moveAxial({q: 0, r: 1}); - let upperRightHex = this.pixelToHex(upperRightPoint).moveAxial({q: 1, r: -1}); + let upperLeftTile = this.pixelToTile(upperLeftPoint).moveAxial({q: 0, r: -1}); + let lowerRightTile = this.pixelToTile(lowerRightPoint).moveAxial({q: 0, r: 1}); + let upperRightTile = this.pixelToTile(upperRightPoint).moveAxial({q: 1, r: -1}); - let height = lowerRightHex.getR() - upperRightHex.getR(); - let width = upperRightHex.getQ() - upperLeftHex.getQ(); + let height = lowerRightTile.getR() - upperRightTile.getR(); + let width = upperRightTile.getQ() - upperLeftTile.getQ(); if (this.pointyTop) { for (let row = 0; row <= height; row++) { hexagons[row] = []; - let r = upperLeftHex.getR() + row; - let qOffset = upperLeftHex.getQ() - Math.floor(row / 2); + let r = upperLeftTile.getR() + row; + let qOffset = upperLeftTile.getQ() - Math.floor(row / 2); for (let q = qOffset; q <= qOffset + width; q++) { hexagons[row].push(new Hex(q, r)); @@ -141,8 +141,8 @@ export default class ExWhyZee { else { for (let col = 0; col <= width; col++) { hexagons[col] = []; - let q = upperLeftHex.getQ() + col; - let rOffset = upperLeftHex.getR() - Math.floor(col / 2); + let q = upperLeftTile.getQ() + col; + let rOffset = upperLeftTile.getR() - Math.floor(col / 2); for (let r = rOffset; r <= rOffset + height; r++) { hexagons[col].push(new Hex(q, r)); diff --git a/src/hex.js b/src/hex.js index 26c971b..7f130ca 100644 --- a/src/hex.js +++ b/src/hex.js @@ -1,4 +1,3 @@ - import Point from './point.js'; function computeY(x, z) { @@ -91,7 +90,8 @@ export default class Hex extends Point { return this; } - getHex() { return {x: this.x, y: this.y, z: this.z}; } + getPoint() { return {x: this.x, y: this.y, z: this.z}; } + setHex(newHex) { this.x = newHex.x; this.y = newHex.y; diff --git a/src/main.js b/src/main.js index 9eb3ae2..a6c91ed 100644 --- a/src/main.js +++ b/src/main.js @@ -14,7 +14,8 @@ class Demo { let tessellate = new Tessellate({ element: '#container', tap: this.onTap, - draw: this.draw + draw: this.draw, + pointyTop: false, //utils.random(1) ? true : false, }); this.circle = new DrawCircle(); @@ -28,7 +29,7 @@ class Demo { onTap(tap) { let scale = utils.random(10, 50); - console.log(tap.hex.getHex()); + console.log(tap.tile.getPoint()); this.map.push(new Cell({ tile: this.tiles[utils.random(this.tiles.length-1)], diff --git a/src/point.js b/src/point.js index f13de93..117589b 100644 --- a/src/point.js +++ b/src/point.js @@ -1,8 +1,8 @@ export default class Point { - constructor(newX, newY) { - this.x = newX; - this.y = newY; + constructor(x, y) { + this.x = Math.round(x); + this.y = Math.round(y); } getX() { return this.x; } diff --git a/src/sketch.js b/src/sketch.js index 6d1086d..d99b64d 100644 --- a/src/sketch.js +++ b/src/sketch.js @@ -38,7 +38,8 @@ export default class Sketch { this.lastNow = now; - setTimeout(() => {requestAnimationFrame(this.render)}, this.drawDelay); + if (this.drawDelay) setTimeout(() => requestAnimationFrame(this.render), this.drawDelay); + else requestAnimationFrame(this.render); } } diff --git a/src/square.js b/src/square.js new file mode 100644 index 0000000..f936deb --- /dev/null +++ b/src/square.js @@ -0,0 +1,7 @@ +import Point from './point.js'; + +export default class Square extends Point { + constructor(x, y) { + super(x, y); + } +} diff --git a/src/tessellate.js b/src/tessellate.js index b19a0f0..aa86e87 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -14,81 +14,90 @@ export {DrawCircle, DrawHexagon, DrawSquare}; import Cell from './cell.js'; export {Cell}; -import ExWhyZee from './exWhyZee.js'; +//import ExWhy from './exWhy.js'; +//import ExWhyZee from './exWhyZee.js'; +import CartographerXY from './cartographerXY.js'; + +const DEFAULTS = { + tap: utils.noop, + draw: utils.noop, + pointyTop: false, +}; export default class Tessellate { constructor(settings) { ['tap', 'draw', 'drawMap', 'move', 'zoom'].map(method => {this[method] = this[method].bind(this)}); - this.tapCB = settings.tap || utils.noop; - this.drawCB = settings.draw || utils.noop; - this.element = document.querySelector(settings.element); + this.settings = utils.extend(DEFAULTS, settings); + this.settings.element = this.settings.element instanceof HTMLElement ? this.settings.element : + document.querySelector(this.settings.element); this.sketch = new Sketch({ - element: this.element, + element: this.settings.element, draw: this.draw }); this.onTap = new OnTap({ - element: this.element, + element: this.settings.element, tap: this.tap, move: this.move, zoom: this.zoom }); this.map = []; - this.xyz = new ExWhyZee({ - pointyTop: true, + this.cartographer = new CartographerXY({ + pointyTop: this.settings.pointyTop, originX: this.sketch.getContext().canvas.width / 2, originY: this.sketch.getContext().canvas.height / 2 }); this.circle = new DrawCircle(); this.hexagon = new DrawHexagon(); + this.square = new DrawSquare(); } tap(event) { let point = new Point(event.offsetX, event.offsetY); - let hex = this.xyz.pixelToHex(point); + let tile = this.cartographer.pixelToTile(point); let tap = { event, point, - hex + tile }; - this.tapCB(tap); + this.settings.tap(tap); } move(event) { - this.xyz.move(event); + this.cartographer.move(event); } zoom(event) { - this.xyz.zoom(event); + this.cartographer.zoom(event); } drawMap(context) { - let scale = this.xyz.getScale(); + let scale = this.cartographer.getScale(); let upperLeft = new Point(0, 0); let lowerRight = new Point(context.canvas.width, context.canvas.height); - let hexagons = this.xyz.boundingBox(upperLeft, lowerRight); + let tiles = this.cartographer.boundingBox(upperLeft, lowerRight); - let height = hexagons.length; + let height = tiles.length; for (let r=0; r