Finite boards (#1)
This commit is contained in:
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
79
src/cartographerFlatXY.js
Normal file
79
src/cartographerFlatXY.js
Normal file
@@ -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)));
|
||||
}
|
||||
}
|
||||
86
src/cartographerFlatXYZ.js
Normal file
86
src/cartographerFlatXYZ.js
Normal file
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
105
src/cartographerPointyXY.js
Normal file
105
src/cartographerPointyXY.js
Normal file
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
86
src/cartographerPointyXYZ.js
Normal file
86
src/cartographerPointyXYZ.js
Normal file
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
19
src/consts.js
Normal file
19
src/consts.js
Normal file
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
13
src/utils.js
13
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user