From 69cae5eb1a316517177de4f250167a74ecf8b5e8 Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sun, 2 Dec 2018 21:24:34 -0500 Subject: [PATCH 01/11] [*] limit tiles in bounds based on width/height/radius --- src/cartographer.js | 66 ++++++++++++++++++++++-------------- src/cartographerFlatXY.js | 18 +++++++++- src/cartographerFlatXYZ.js | 28 ++++++++++++++- src/cartographerPointyXY.js | 18 +++++++++- src/cartographerPointyXYZ.js | 28 ++++++++++++++- src/main.js | 2 ++ src/tessellate.js | 6 ++-- 7 files changed, 134 insertions(+), 32 deletions(-) diff --git a/src/cartographer.js b/src/cartographer.js index 975f91b..0af70b9 100644 --- a/src/cartographer.js +++ b/src/cartographer.js @@ -28,7 +28,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); @@ -102,21 +108,25 @@ 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.width) { - const boardWidth = this.width * colWidth + this.horizontalOverhang(); - const minX = maxX - (boardWidth - canvasWidth); - - this.originX = this.originX < minX ? minX : this.originX; + if (this.wrap) { } + else { + this.originX = this.originX > maxX ? maxX : this.originX; + this.originY = this.originY < minY ? minY : this.originY; - if (this.height) { - const boardHeight = this.height * rowHeight + this.verticalOverhang(); - const maxY = boardHeight - (this.tileHeight() / 2); + if (this.width) { + const boardWidth = this.width * colWidth + this.horizontalOverhang(); + const minX = maxX - (boardWidth - canvasWidth); - this.originY = this.originY > maxY ? maxY : this.originY; + this.originX = this.originX < minX ? minX : this.originX; + } + + if (this.height) { + const boardHeight = this.height * rowHeight + this.verticalOverhang(); + const maxY = boardHeight - (this.tileHeight() / 2); + + this.originY = this.originY > maxY ? maxY : this.originY; + } } } @@ -124,22 +134,26 @@ 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; - - this.originX = this.originX > halfBoardWidth ? halfBoardWidth : - (canvasWidth - this.originX) > halfBoardWidth ? canvasWidth - halfBoardWidth : - this.originX; + if (this.wrap) { } + else { + if (this.width) { + const canvasWidth = event.width; + const halfBoardWidth = (this.width * colWidth + this.horizontalOverhang()) / 2; - if (this.height) { - const canvasHeight = event.height; - const halfBoardHeight = (this.height * rowHeight + this.verticalOverhang()) / 2; + this.originX = this.originX > halfBoardWidth ? halfBoardWidth : + (canvasWidth - this.originX) > halfBoardWidth ? canvasWidth - halfBoardWidth : + this.originX; + } - 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; + } } } diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js index 13b194e..3ed4eb3 100644 --- a/src/cartographerFlatXY.js +++ b/src/cartographerFlatXY.js @@ -27,6 +27,8 @@ export default class CartographerFlatXY extends Cartographer { 'tileToPixel', 'pixelToTile', + + 'inBounds', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -92,6 +94,17 @@ export default class CartographerFlatXY extends Cartographer { return new Square(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)); + } + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this.pixelToTile(upperLeftPoint); const lowerRightTile = this.pixelToTile(lowerRightPoint); @@ -100,6 +113,9 @@ export default class CartographerFlatXY extends Cartographer { 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))); + return columns.map(x => rows.map(y => ({x, y}))) + .reduce((flat, list) => flat.concat(list), []) + .filter(({x, y}) => this.inBounds(x, y)) + .map(({x, y}) => new Square(x, y)); } } diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 9ec6194..4623b38 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -27,6 +27,8 @@ export default class CartographerFlatXYZ extends Cartographer { 'tileToPixel', 'pixelToTile', + + 'inBounds', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -92,6 +94,27 @@ export default class CartographerFlatXYZ extends Cartographer { return new Hex(q, r); } + inBounds (q, r, s = -q - r) { + if (this.radius) { + if (this.negativeTiles) { + return Math.max(Math.abs(q), Math.abs(r), Math.abs(s)) <= Math.floor(this.radius); + } + else { + return Math.max(Math.abs(q - this.radius), Math.abs(r + this.radius), Math.abs(s)) <= this.radius; + } + } + else if (this.width || this.height) { + if (this.negativeTiles) { + return (!this.width || (Math.abs(q) < this.width / 2)) + && (!this.height || (Math.abs(-r - Math.floor(q / 2)) < (this.height / 2))); + } + else { + return (!this.width || (q >= 0 && q < this.width)) + && (!this.height || (r <= (Math.floor(q / 2) * -1) && (-r - Math.floor(q / 2)) < this.height)); + } + } + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this.pixelToTile(upperLeftPoint); const lowerLeftTile = this.pixelToTile(lowerLeftPoint); @@ -106,7 +129,10 @@ export default class CartographerFlatXYZ extends Cartographer { const bottom = top + height; const rows = rangeInclusive(top, bottom + 1); - return rows.map(r => new Hex(q, r)); + return rows.map(r => ({q, r})) + .reduce((flat, list) => flat.concat(list), []) + .filter(({q, r}) => this.inBounds(q, r)) + .map(({q, r}) => new Hex(q, r)); }); } } diff --git a/src/cartographerPointyXY.js b/src/cartographerPointyXY.js index a5e1140..763d5f1 100644 --- a/src/cartographerPointyXY.js +++ b/src/cartographerPointyXY.js @@ -27,6 +27,8 @@ export default class CartographerPointyXY extends Cartographer { 'tileToPixel', 'pixelToTile', + + 'inBounds', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -97,6 +99,17 @@ export default class CartographerPointyXY extends Cartographer { return new Square(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)); + } + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this.pixelToTile(upperLeftPoint); const lowerRightTile = this.pixelToTile(lowerRightPoint); @@ -125,7 +138,10 @@ export default class CartographerPointyXY extends Cartographer { // 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)); + return rows.map(y => ({x, y})) + .reduce((flat, list) => flat.concat(list), []) + .filter(({x, y}) => this.inBounds(x, y)) + .map(({x, y}) => new Square(x, y)); }); } } diff --git a/src/cartographerPointyXYZ.js b/src/cartographerPointyXYZ.js index bd03d6b..7bd11de 100644 --- a/src/cartographerPointyXYZ.js +++ b/src/cartographerPointyXYZ.js @@ -27,6 +27,8 @@ export default class CartographerPointyXYZ extends Cartographer { 'tileToPixel', 'pixelToTile', + + 'inBounds', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -92,6 +94,27 @@ export default class CartographerPointyXYZ extends Cartographer { return new Hex(q, r); } + inBounds (q, r, s = -q - r) { + if (this.radius) { + if (this.negativeTiles) { + return Math.max(Math.abs(q), Math.abs(r), Math.abs(s)) <= Math.floor(this.radius); + } + else { + return Math.max(Math.abs(q - this.radius), Math.abs(r + this.radius), Math.abs(s)) <= this.radius; + } + } + else if (this.width || this.height) { + if (this.negativeTiles) { + return (!this.height || (Math.abs(r) < this.height / 2)) + && (!this.width || (Math.abs(-q - Math.floor(r / 2)) < (this.width / 2))); + } + else { + return (!this.height || (r >= 0 && r < this.height)) + && (!this.width || (q <= (Math.floor(r / 2)) && (q - Math.floor(r / 2)) < this.width)); + } + } + } + boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this.pixelToTile(upperLeftPoint); const lowerLeftTile = this.pixelToTile(lowerLeftPoint); @@ -106,7 +129,10 @@ export default class CartographerPointyXYZ extends Cartographer { const right = left + width; const columns = rangeInclusive(left, right + 1); - return columns.map(q => new Hex(q, r)); + return columns.map(q => ({q, r})) + .reduce((flat, list) => flat.concat(list), []) + .filter(({q, r}) => this.inBounds(q, r)) + .map(({q, r}) => new Hex(q, r)); }); } } diff --git a/src/main.js b/src/main.js index 4778a87..dde6638 100644 --- a/src/main.js +++ b/src/main.js @@ -131,6 +131,8 @@ class Demo { const pipMax = this.settings.tile === Tessellate.TILE_STYLES.HEX ? 7 : 9; this.map[key].pips = Tessellate.utils.random(1, pipMax); + + console.log({x, y, z}); } pressStart(tap) { diff --git a/src/tessellate.js b/src/tessellate.js index c77aff4..ae458aa 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -116,9 +116,11 @@ export class Tessellate { 'centerX', 'centerY', 'height', - 'width', - 'scale', 'negativeTiles', + 'radius', + 'scale', + 'width', + 'wrap', ]))); } From ad4a16b796b738fda280f0bac237d28d304bbadb Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Tue, 4 Dec 2018 21:45:27 -0500 Subject: [PATCH 02/11] [*] providing tilePoint/pixelPoint pairs to client draw function --- src/main.js | 17 ++++++++++------- src/tessellate.js | 17 +++++++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main.js b/src/main.js index dde6638..e840376 100644 --- a/src/main.js +++ b/src/main.js @@ -220,7 +220,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 }`; @@ -233,16 +237,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) { @@ -257,12 +260,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 @@ -275,7 +278,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/tessellate.js b/src/tessellate.js index ae458aa..063666a 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -224,6 +224,16 @@ 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) + .map(tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)})); + this.settings.draw({ context, @@ -233,12 +243,7 @@ export class Tessellate { lastNow: context.lastUTC, now: context.utc, - tilePoints: this.getTilePoints({ - upperLeftX: 0, - upperLeftY: 0, - lowerRightX: width, - lowerRightY: height - }), + pointGroups, }); } From 9b4f2e92ad20076c93873c0649cc71e9c3c66fcb Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sat, 15 Dec 2018 11:35:17 -0500 Subject: [PATCH 03/11] [*] wrapping for flatXY --- src/cartographerFlatXY.js | 46 ++++++++++++++++++++++++++++++++++----- src/funky.js | 11 ++++++---- src/main.js | 10 +++++++-- src/point.js | 5 +++++ src/tessellate.js | 3 +-- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js index 3ed4eb3..bbe40b7 100644 --- a/src/cartographerFlatXY.js +++ b/src/cartographerFlatXY.js @@ -1,9 +1,13 @@ 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 makeASquare = ({x, y}) => new Square(x, y); +const tilePointToSquare = ({tilePoint, pixelPoint}) => ({tilePoint: makeASquare(tilePoint), pixelPoint}); export default class CartographerFlatXY extends Cartographer { constructor(settings) { @@ -28,7 +32,9 @@ export default class CartographerFlatXY extends Cartographer { 'tileToPixel', 'pixelToTile', + 'teleport', 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -94,7 +100,22 @@ export default class CartographerFlatXY extends Cartographer { return new Square(x, y); } - inBounds (x, y) { + teleport ({x, y}) { + if (this.negativeTiles) { + x = x % Math.ceil(this.width / 2); + y = y % Math.ceil(this.height / 2); + } + else { + 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)); @@ -105,6 +126,12 @@ export default class CartographerFlatXY extends Cartographer { } } + 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); @@ -113,9 +140,16 @@ export default class CartographerFlatXY extends Cartographer { const columns = rangeInclusive(upperLeftTile.getX(), upperRightTile.getX()); const rows = rangeInclusive(lowerRightTile.getY(), upperLeftTile.getY()); - return columns.map(x => rows.map(y => ({x, y}))) - .reduce((flat, list) => flat.concat(list), []) - .filter(({x, y}) => this.inBounds(x, y)) - .map(({x, 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/funky.js b/src/funky.js index 0f6c337..5f6a683 100644 --- a/src/funky.js +++ b/src/funky.js @@ -16,7 +16,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 +64,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/main.js b/src/main.js index e840376..16b960b 100644 --- a/src/main.js +++ b/src/main.js @@ -130,9 +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); - console.log({x, y, z}); + if (this.map[key]) { + this.map[key].pips = Tessellate.utils.random(1, pipMax); + + console.log(key); + } + else { + console.log('ERROR - no tile', key); + } } pressStart(tap) { 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 063666a..6f4893f 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -231,8 +231,7 @@ export class Tessellate { lowerRightY: height }; - const pointGroups = this.getTilePoints(corners) - .map(tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)})); + const pointGroups = this.getTilePoints(corners); this.settings.draw({ context, From e631691289bad647218bcb1e1f9f70365548395e Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sun, 16 Dec 2018 14:11:56 -0500 Subject: [PATCH 04/11] wrapping for pointyXY --- src/cartographer.js | 11 ++++++- src/cartographerFlatXY.js | 28 +++++++----------- src/cartographerPointyXY.js | 57 ++++++++++++++++++++++++++++--------- src/tessellate.js | 3 +- 4 files changed, 67 insertions(+), 32 deletions(-) diff --git a/src/cartographer.js b/src/cartographer.js index 0af70b9..9f5c6e7 100644 --- a/src/cartographer.js +++ b/src/cartographer.js @@ -21,6 +21,8 @@ export default class Cartographer { 'setOriginX', 'setOriginY', + 'pixelToTile', + 'zoom', 'remap', @@ -52,7 +54,9 @@ 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 = Math.max(this.settings.scaleMin, heightMin, widthMin); +// this.scaleMin = this.wrap ? this.settings.scaleMin : Math.max(this.settings.scaleMin, heightMin, widthMin); + this.scaleMin = this.settings.scaleMin; this.scale = this.scaleMin > this.settings.scale ? this.scaleMin : this.settings.scale; } @@ -157,6 +161,11 @@ export default class Cartographer { } } + pixelToTile (point) { + const tile = this._pixelToTile(point); + return this.wrap ? this.teleport(tile) : tile; + } + zoom (event) { const scaleOrig = this.scale; diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js index bbe40b7..359baf0 100644 --- a/src/cartographerFlatXY.js +++ b/src/cartographerFlatXY.js @@ -6,8 +6,7 @@ import {rangeInclusive, invSqrt2} from './utils.js'; import Point from './point.js'; import Square from './square.js'; -const makeASquare = ({x, y}) => new Square(x, y); -const tilePointToSquare = ({tilePoint, pixelPoint}) => ({tilePoint: makeASquare(tilePoint), pixelPoint}); +const tilePointToSquare = ({tilePoint, pixelPoint}) => ({tilePoint: new Square(tilePoint), pixelPoint}); export default class CartographerFlatXY extends Cartographer { constructor(settings) { @@ -30,7 +29,7 @@ export default class CartographerFlatXY extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', 'teleport', 'inBounds', @@ -88,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; @@ -101,16 +100,11 @@ export default class CartographerFlatXY extends Cartographer { } teleport ({x, y}) { - if (this.negativeTiles) { - x = x % Math.ceil(this.width / 2); - y = y % Math.ceil(this.height / 2); - } - else { - x = x % this.width; - y = y % this.height; - x = x < 0 ? this.width + x : x; - y = y < 0 ? this.height + y : 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); } @@ -133,9 +127,9 @@ export default class CartographerFlatXY extends Cartographer { } 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()); diff --git a/src/cartographerPointyXY.js b/src/cartographerPointyXY.js index 763d5f1..08216a8 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,9 +29,11 @@ export default class CartographerPointyXY extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', + 'teleport', 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -86,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; @@ -99,7 +104,17 @@ export default class CartographerPointyXY extends Cartographer { return new Square(x, y); } - inBounds (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)); @@ -110,11 +125,17 @@ export default class CartographerPointyXY extends Cartographer { } } + 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()); @@ -128,7 +149,10 @@ 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)}); + + return funky.chain(columns) + .map(x => { let top = x < midway ? upperLeftIntercept + x : upperRightIntercept - x; let bottom = x < midway ? lowerRightIntercept - x : lowerLeftIntercept + x; @@ -138,10 +162,17 @@ export default class CartographerPointyXY extends Cartographer { // push out by 1 on either end to account for interlocking tiles const rows = rangeInclusive(bottom - 1, top + 1); - return rows.map(y => ({x, y})) - .reduce((flat, list) => flat.concat(list), []) - .filter(({x, y}) => this.inBounds(x, y)) - .map(({x, y}) => new Square(x, y)); - }); + const makeAPoint = y => ({x, y}); + + return funky.chain(rows) + .map(makeAPoint) + .map(makeAPointPair) + .map(this.enforceBoundries) + .compact() + .map(tilePointToSquare) + .value(); + }) + .flatten() + .value(); } } diff --git a/src/tessellate.js b/src/tessellate.js index 6f4893f..dd456d2 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -143,6 +143,7 @@ export class Tessellate { tap (event) { let point = new Point(event.offsetX, event.offsetY); + let tile = this.cartographer.pixelToTile(point); let tap = { @@ -216,7 +217,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) { From c77f179ad3096c3efa1e61a4649ba21eb42eb52a Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Fri, 28 Dec 2018 16:31:55 -0500 Subject: [PATCH 05/11] wrapping for flat hex tiles almost working --- src/cartographerFlatXY.js | 2 +- src/cartographerFlatXYZ.js | 92 +++++++++++++++++++++++++++++-------- src/cartographerPointyXY.js | 14 +++--- src/funky.js | 5 ++ src/hex.js | 64 ++++++++++++++++++-------- 5 files changed, 132 insertions(+), 45 deletions(-) diff --git a/src/cartographerFlatXY.js b/src/cartographerFlatXY.js index 359baf0..9ee15f8 100644 --- a/src/cartographerFlatXY.js +++ b/src/cartographerFlatXY.js @@ -30,8 +30,8 @@ export default class CartographerFlatXY extends Cartographer { 'tileToPixel', '_pixelToTile', - 'teleport', + 'inBounds', 'enforceBoundries', 'boundingBox', diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 4623b38..c1b8e42 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -1,10 +1,13 @@ 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}); + export default class CartographerFlatXYZ extends Cartographer { constructor(settings) { super(settings); @@ -26,9 +29,11 @@ export default class CartographerFlatXYZ extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', + 'teleport', 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } @@ -82,7 +87,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; @@ -94,45 +99,94 @@ export default class CartographerFlatXYZ extends Cartographer { return new Hex(q, r); } - inBounds (q, r, s = -q - r) { + teleport (hex) { + hex = hex instanceof Hex ? hex : new Hex(hex); + let {col, row} = hex.getOffsetHex(); + + if (this.radius) { + } + else { + 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.offsetToCube(col, row); + } + } + + inBounds ({x, y, z = -x - y}) { if (this.radius) { if (this.negativeTiles) { - return Math.max(Math.abs(q), Math.abs(r), Math.abs(s)) <= Math.floor(this.radius); + return Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) <= Math.floor(this.radius); } else { - return Math.max(Math.abs(q - this.radius), Math.abs(r + this.radius), Math.abs(s)) <= this.radius; + 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(q) < this.width / 2)) - && (!this.height || (Math.abs(-r - Math.floor(q / 2)) < (this.height / 2))); + 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 || (q >= 0 && q < this.width)) - && (!this.height || (r <= (Math.floor(q / 2) * -1) && (-r - Math.floor(q / 2)) < this.height)); + 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 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 => ({q, r})) - .reduce((flat, list) => flat.concat(list), []) - .filter(({q, r}) => this.inBounds(q, r)) - .map(({q, r}) => new Hex(q, r)); - }); + const makeAPoint = r => ({x: q, y: r, z: -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 08216a8..d32f813 100644 --- a/src/cartographerPointyXY.js +++ b/src/cartographerPointyXY.js @@ -30,8 +30,8 @@ export default class CartographerPointyXY extends Cartographer { 'tileToPixel', '_pixelToTile', - 'teleport', + 'inBounds', 'enforceBoundries', 'boundingBox', @@ -151,8 +151,7 @@ export default class CartographerPointyXY extends Cartographer { const makeAPointPair = tilePoint => ({tilePoint, pixelPoint: this.tileToPixel(tilePoint)}); - return funky.chain(columns) - .map(x => { + const processRow = x => { let top = x < midway ? upperLeftIntercept + x : upperRightIntercept - x; let bottom = x < midway ? lowerRightIntercept - x : lowerLeftIntercept + x; @@ -160,18 +159,21 @@ 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); const makeAPoint = y => ({x, y}); - return funky.chain(rows) + 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/funky.js b/src/funky.js index 5f6a683..ddfb9ad 100644 --- a/src/funky.js +++ b/src/funky.js @@ -1,5 +1,10 @@ export function chain (obj) { let chainInstance = { + log: function () { + console.log(obj); + return chainInstance; + }, + value: function () { return obj; } diff --git a/src/hex.js b/src/hex.js index 7f130ca..c46c4b9 100644 --- a/src/hex.js +++ b/src/hex.js @@ -35,11 +35,26 @@ function roundOff(hex) { } export default class Hex extends Point { + static offsetToCube (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, y, z = -x - y} = 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 +72,76 @@ 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) { + 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; } + + getOffsetHex () { + const col = this.x; + const row = this.z + (this.x + (this.x & 1)) / 2; + + return {col, row}; + } } From d1b76727c89c602dd20322be5399392eb90d5df3 Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Fri, 4 Jan 2019 22:03:05 -0500 Subject: [PATCH 06/11] looks like maps are finally wrapping properly --- src/cartographerFlatXYZ.js | 187 ++++++++++++++++++++++++++++++++++++- src/funky.js | 4 +- src/main.js | 2 +- 3 files changed, 189 insertions(+), 4 deletions(-) diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index c1b8e42..6a8c9b7 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -8,6 +8,160 @@ import Point from './point.js'; const tilePointToHex = ({tilePoint, pixelPoint}) => ({tilePoint: new Hex(tilePoint), pixelPoint}); +window.debugged = -1; + +const positive3x3 = [{ + x: 0, + y: 0, + z: 0, +}, { + x: 1, + y: 0, + z: -1, +}, { + x: 2, + y: -1, + z: -1, +}, { + x: 0, + y: -1, + z: 1, +}, { + x: 1, + y: -1, + z: 0, +}, { + x: 2, + y: -2, + z: 0, +}, { + x: 0, + y: -2, + z: 2, +}, { + x: 1, + y: -2, + z: 1, +}, { + x: 2, + y: -3, + z: 1, +}]; + +const positiveEast3x3 = [{ + x: 3, + y: -2, + z: -1, +}, { + x: 4, + y: -2, + z: -2, +}, { + x: 5, + y: -3, + z: -2, +}, { + x: 3, + y: -3, + z: 0, +}, { + x: 4, + y: -3, + z: -1, +}, { + x: 5, + y: -4, + z: -1, +}, { + x: 3, + y: -4, + z: 1, +}, { + x: 4, + y: -4, + z: 0, +}, { + x: 5, + y: -5, + z: 0, +}]; + +const positiveMiddleEast3x3 = [{ + x: 6, + y: -4, + z: -2, +}, { + x: 7, + y: -4, + z: -3, +}, { + x: 8, + y: -5, + z: -3, +}, { + x: 6, + y: -5, + z: -1, +}, { + x: 7, + y: -5, + z: -2, +}, { + x: 8, + y: -6, + z: -2, +}, { + x: 6, + y: -6, + z: 0, +}, { + x: 7, + y: -6, + z: -1, +}, { + x: 8, + y: -7, + z: -1, +}]; + +const positiveFarEast3x3 = [{ + x: 9, + y: -6, + z: -3, +}, { + x: 10, + y: -6, + z: -4, +}, { + x: 11, + y: -7, + z: -4, +}, { + x: 9, + y: -7, + z: -2, +}, { + x: 10, + y: -7, + z: -3, +}, { + x: 11, + y: -8, + z: -3, +}, { + x: 9, + y: -8, + z: -1, +}, { + x: 10, + y: -8, + z: -2, +}, { + x: 11, + y: -9, + z: -2, +}]; + export default class CartographerFlatXYZ extends Cartographer { constructor(settings) { super(settings); @@ -106,8 +260,20 @@ export default class CartographerFlatXYZ extends Cartographer { if (this.radius) { } else { + // 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); + const halfHeight = Math.floor(this.height / 2); if (this.negativeTiles) { col += halfWidth; @@ -157,6 +323,7 @@ export default class CartographerFlatXYZ extends Cartographer { } boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { + window.debugged++; const upperLeftTile = this._pixelToTile(upperLeftPoint); const lowerLeftTile = this._pixelToTile(lowerLeftPoint); const lowerRightTile = this._pixelToTile(lowerRightPoint); @@ -184,9 +351,27 @@ export default class CartographerFlatXYZ extends Cartographer { .value(); }; +// if (debugged < 1) { +// +// [positive3x3, positiveEast3x3, positiveMiddleEast3x3, positiveFarEast3x3].forEach(set => { +// set.forEach((input, index) => { +// const result = this.teleport(input, true); +// +// if (result.x === positive3x3[index].x && result.y === positive3x3[index].y && result.z === positive3x3[index].z) console.log(input, '->', positive3x3[index]); +// else console.log(input, '><', positive3x3[index], result); +// console.log('---------------------------------------------------'); +// }); +// +// console.log('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); +// }); +// } + return funky.chain(columns) + .log(() => window.debugged < 1) .map(processRow) + .log(() => window.debugged < 1) .flatten() + .log(() => window.debugged < 1) .value(); } } diff --git a/src/funky.js b/src/funky.js index ddfb9ad..d38dcd6 100644 --- a/src/funky.js +++ b/src/funky.js @@ -1,7 +1,7 @@ export function chain (obj) { let chainInstance = { - log: function () { - console.log(obj); + log: function (predicate = () => true) { + if (predicate(obj)) console.log(obj); return chainInstance; }, diff --git a/src/main.js b/src/main.js index 16b960b..29f09f7 100644 --- a/src/main.js +++ b/src/main.js @@ -134,7 +134,7 @@ class Demo { if (this.map[key]) { this.map[key].pips = Tessellate.utils.random(1, pipMax); - console.log(key); + console.log(`{${ x }, ${ y }${ z != null ? `, ${ z }` : ''}}`); } else { console.log('ERROR - no tile', key); From 87c2fd98855132aeb54822168882a1783e9b6955 Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Fri, 4 Jan 2019 23:47:28 -0500 Subject: [PATCH 07/11] fix {q,r} -> {x,y,z} conversion --- src/cartographerFlatXYZ.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 6a8c9b7..9deade0 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -8,8 +8,6 @@ import Point from './point.js'; const tilePointToHex = ({tilePoint, pixelPoint}) => ({tilePoint: new Hex(tilePoint), pixelPoint}); -window.debugged = -1; - const positive3x3 = [{ x: 0, y: 0, @@ -323,7 +321,6 @@ export default class CartographerFlatXYZ extends Cartographer { } boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { - window.debugged++; const upperLeftTile = this._pixelToTile(upperLeftPoint); const lowerLeftTile = this._pixelToTile(lowerLeftPoint); const lowerRightTile = this._pixelToTile(lowerRightPoint); @@ -340,7 +337,7 @@ export default class CartographerFlatXYZ extends Cartographer { const bottom = top + height; const rows = rangeInclusive(top, bottom + 1); - const makeAPoint = r => ({x: q, y: r, z: -q - r}); + const makeAPoint = r => ({x: q, z: r, y: -q - r}); return funky.chain(rows) .map(makeAPoint) @@ -367,11 +364,8 @@ export default class CartographerFlatXYZ extends Cartographer { // } return funky.chain(columns) - .log(() => window.debugged < 1) .map(processRow) - .log(() => window.debugged < 1) .flatten() - .log(() => window.debugged < 1) .value(); } } From 28f9647c3a1fe9a307cd793bb6bcd8c96da042cd Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sat, 5 Jan 2019 16:51:52 -0500 Subject: [PATCH 08/11] wrapping for pointy hexagons --- src/cartographerFlatXYZ.js | 51 +++++--------- src/cartographerPointyXYZ.js | 132 ++++++++++++++++++++++++++--------- src/hex.js | 39 ++++++++--- 3 files changed, 149 insertions(+), 73 deletions(-) diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 9deade0..805a6eb 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -161,7 +161,7 @@ const positiveFarEast3x3 = [{ }]; export default class CartographerFlatXYZ extends Cartographer { - constructor(settings) { + constructor (settings) { super(settings); [ @@ -190,47 +190,47 @@ export default class CartographerFlatXYZ extends Cartographer { ].map(method => this[method] = this[method].bind(this)); } - 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(); @@ -239,7 +239,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; @@ -253,7 +253,7 @@ export default class CartographerFlatXYZ extends Cartographer { teleport (hex) { hex = hex instanceof Hex ? hex : new Hex(hex); - let {col, row} = hex.getOffsetHex(); + let {col, row} = Hex.cubeToEvenQ(hex); if (this.radius) { } @@ -289,11 +289,11 @@ export default class CartographerFlatXYZ extends Cartographer { row -= halfHeight; } - return Hex.offsetToCube(col, row); + return Hex.evenQToCube(col, row); } } - inBounds ({x, y, z = -x - y}) { + 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); @@ -320,7 +320,7 @@ export default class CartographerFlatXYZ extends Cartographer { null; } - boundingBox(upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { + boundingBox (upperLeftPoint, upperRightPoint, lowerLeftPoint, lowerRightPoint) { const upperLeftTile = this._pixelToTile(upperLeftPoint); const lowerLeftTile = this._pixelToTile(lowerLeftPoint); const lowerRightTile = this._pixelToTile(lowerRightPoint); @@ -337,7 +337,7 @@ export default class CartographerFlatXYZ extends Cartographer { const bottom = top + height; const rows = rangeInclusive(top, bottom + 1); - const makeAPoint = r => ({x: q, z: r, y: -q - r}); + const makeAPoint = r => Hex.qrToCube(q, r); return funky.chain(rows) .map(makeAPoint) @@ -348,21 +348,6 @@ export default class CartographerFlatXYZ extends Cartographer { .value(); }; -// if (debugged < 1) { -// -// [positive3x3, positiveEast3x3, positiveMiddleEast3x3, positiveFarEast3x3].forEach(set => { -// set.forEach((input, index) => { -// const result = this.teleport(input, true); -// -// if (result.x === positive3x3[index].x && result.y === positive3x3[index].y && result.z === positive3x3[index].z) console.log(input, '->', positive3x3[index]); -// else console.log(input, '><', positive3x3[index], result); -// console.log('---------------------------------------------------'); -// }); -// -// console.log('-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'); -// }); -// } - return funky.chain(columns) .map(processRow) .flatten() diff --git a/src/cartographerPointyXYZ.js b/src/cartographerPointyXYZ.js index 7bd11de..aab082f 100644 --- a/src/cartographerPointyXYZ.js +++ b/src/cartographerPointyXYZ.js @@ -1,12 +1,15 @@ 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}); + export default class CartographerPointyXYZ extends Cartographer { - constructor(settings) { + constructor (settings) { super(settings); [ @@ -26,54 +29,56 @@ export default class CartographerPointyXYZ extends Cartographer { 'calculateVerticalScale', 'tileToPixel', - 'pixelToTile', + '_pixelToTile', + 'teleport', 'inBounds', + 'enforceBoundries', 'boundingBox', ].map(method => this[method] = this[method].bind(this)); } - 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)); @@ -82,7 +87,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; @@ -94,45 +99,108 @@ export default class CartographerPointyXYZ extends Cartographer { return new Hex(q, r); } - inBounds (q, r, s = -q - r) { + teleport (hex) { + hex = hex instanceof Hex ? hex : new Hex(hex); + let {col, row} = Hex.cubeToEvenR(hex); + + if (this.radius) { + } + else { + // 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(q), Math.abs(r), Math.abs(s)) <= Math.floor(this.radius); + 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(q - this.radius), Math.abs(r + this.radius), Math.abs(s)) <= this.radius; + 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.height || (Math.abs(r) < this.height / 2)) - && (!this.width || (Math.abs(-q - Math.floor(r / 2)) < (this.width / 2))); + 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.height || (r >= 0 && r < this.height)) - && (!this.width || (q <= (Math.floor(r / 2)) && (q - Math.floor(r / 2)) < this.width)); + 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)); } } } - 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); + 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 => ({q, r})) - .reduce((flat, list) => flat.concat(list), []) - .filter(({q, r}) => this.inBounds(q, r)) - .map(({q, r}) => 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/hex.js b/src/hex.js index c46c4b9..b46febf 100644 --- a/src/hex.js +++ b/src/hex.js @@ -35,7 +35,37 @@ function roundOff(hex) { } export default class Hex extends Point { - static offsetToCube (col, row) { + 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; @@ -137,11 +167,4 @@ export default class Hex extends Point { this.y = computeY(this.x, this.z); return this; } - - getOffsetHex () { - const col = this.x; - const row = this.z + (this.x + (this.x & 1)) / 2; - - return {col, row}; - } } From 7c3ee5b4758a4e89a79e23b3914cd9ddfd85d92d Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sun, 6 Jan 2019 15:34:56 -0500 Subject: [PATCH 09/11] wrapping for radial, hexagonal, maps --- src/cartographerFlatXYZ.js | 196 ++++++++--------------------------- src/cartographerPointyXYZ.js | 46 +++++++- src/hex.js | 14 ++- 3 files changed, 102 insertions(+), 154 deletions(-) diff --git a/src/cartographerFlatXYZ.js b/src/cartographerFlatXYZ.js index 805a6eb..4b2f45f 100644 --- a/src/cartographerFlatXYZ.js +++ b/src/cartographerFlatXYZ.js @@ -8,157 +8,7 @@ import Point from './point.js'; const tilePointToHex = ({tilePoint, pixelPoint}) => ({tilePoint: new Hex(tilePoint), pixelPoint}); -const positive3x3 = [{ - x: 0, - y: 0, - z: 0, -}, { - x: 1, - y: 0, - z: -1, -}, { - x: 2, - y: -1, - z: -1, -}, { - x: 0, - y: -1, - z: 1, -}, { - x: 1, - y: -1, - z: 0, -}, { - x: 2, - y: -2, - z: 0, -}, { - x: 0, - y: -2, - z: 2, -}, { - x: 1, - y: -2, - z: 1, -}, { - x: 2, - y: -3, - z: 1, -}]; - -const positiveEast3x3 = [{ - x: 3, - y: -2, - z: -1, -}, { - x: 4, - y: -2, - z: -2, -}, { - x: 5, - y: -3, - z: -2, -}, { - x: 3, - y: -3, - z: 0, -}, { - x: 4, - y: -3, - z: -1, -}, { - x: 5, - y: -4, - z: -1, -}, { - x: 3, - y: -4, - z: 1, -}, { - x: 4, - y: -4, - z: 0, -}, { - x: 5, - y: -5, - z: 0, -}]; - -const positiveMiddleEast3x3 = [{ - x: 6, - y: -4, - z: -2, -}, { - x: 7, - y: -4, - z: -3, -}, { - x: 8, - y: -5, - z: -3, -}, { - x: 6, - y: -5, - z: -1, -}, { - x: 7, - y: -5, - z: -2, -}, { - x: 8, - y: -6, - z: -2, -}, { - x: 6, - y: -6, - z: 0, -}, { - x: 7, - y: -6, - z: -1, -}, { - x: 8, - y: -7, - z: -1, -}]; - -const positiveFarEast3x3 = [{ - x: 9, - y: -6, - z: -3, -}, { - x: 10, - y: -6, - z: -4, -}, { - x: 11, - y: -7, - z: -4, -}, { - x: 9, - y: -7, - z: -2, -}, { - x: 10, - y: -7, - z: -3, -}, { - x: 11, - y: -8, - z: -3, -}, { - x: 9, - y: -8, - z: -1, -}, { - x: 10, - y: -8, - z: -2, -}, { - x: 11, - y: -9, - z: -2, -}]; +const zeroZeroZero = new Hex({x: 0, y: 0, z: 0}); export default class CartographerFlatXYZ extends Cartographer { constructor (settings) { @@ -188,6 +38,41 @@ export default class CartographerFlatXYZ extends Cartographer { '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 () { @@ -253,11 +138,18 @@ export default class CartographerFlatXYZ extends Cartographer { teleport (hex) { hex = hex instanceof Hex ? hex : new Hex(hex); - let {col, row} = Hex.cubeToEvenQ(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); diff --git a/src/cartographerPointyXYZ.js b/src/cartographerPointyXYZ.js index aab082f..cd7965b 100644 --- a/src/cartographerPointyXYZ.js +++ b/src/cartographerPointyXYZ.js @@ -8,6 +8,8 @@ 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) { super(settings); @@ -36,6 +38,41 @@ export default class CartographerPointyXYZ extends Cartographer { '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 () { @@ -101,11 +138,18 @@ export default class CartographerPointyXYZ extends Cartographer { teleport (hex) { hex = hex instanceof Hex ? hex : new Hex(hex); - let {col, row} = Hex.cubeToEvenR(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); diff --git a/src/hex.js b/src/hex.js index b46febf..8965f53 100644 --- a/src/hex.js +++ b/src/hex.js @@ -78,7 +78,7 @@ export default class Hex extends Point { if (arguments.length === 1) { const {q, r, s = -q - r} = arguments[0]; - const {x, y, z = -x - y} = arguments[0]; + const {x, z, y = -x - z} = arguments[0]; this.x = !isNaN(q) ? q : x; this.y = !isNaN(s) ? s : y; @@ -152,6 +152,14 @@ export default class Hex extends Point { return this; } + 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) { @@ -167,4 +175,8 @@ export default class Hex extends Point { 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)); + } } From b1956da3e4ac11dc2b53708311a624ab7fdb64f8 Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sun, 6 Jan 2019 16:21:41 -0500 Subject: [PATCH 10/11] radius overrides height and width --- src/tessellate.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tessellate.js b/src/tessellate.js index dd456d2..fe55dc6 100644 --- a/src/tessellate.js +++ b/src/tessellate.js @@ -113,6 +113,7 @@ 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', @@ -130,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++; From 322522ff12c461df5e964c79367b3bae1728a6ef Mon Sep 17 00:00:00 2001 From: Gavin McDonald Date: Sun, 6 Jan 2019 16:22:08 -0500 Subject: [PATCH 11/11] setting to disable adjusting scaleMin --- src/cartographer.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/cartographer.js b/src/cartographer.js index 9f5c6e7..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, @@ -54,9 +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.wrap ? this.settings.scaleMin : Math.max(this.settings.scaleMin, heightMin, widthMin); - this.scaleMin = this.settings.scaleMin; + 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; } @@ -112,9 +112,7 @@ export default class Cartographer { const maxX = this.tileWidth() / 2; const minY = canvasHeight - (this.tileHeight() / 2); - if (this.wrap) { - } - else { + if (!this.wrap) { this.originX = this.originX > maxX ? maxX : this.originX; this.originY = this.originY < minY ? minY : this.originY; @@ -138,9 +136,7 @@ export default class Cartographer { const colWidth = this.horizontalDistance(); const rowHeight = this.verticalDistance(); - if (this.wrap) { - } - else { + if (!this.wrap) { if (this.width) { const canvasWidth = event.width; const halfBoardWidth = (this.width * colWidth + this.horizontalOverhang()) / 2;