diff --git a/Euclidean.js b/Euclidean.js index cf48f88..8e02d39 100644 --- a/Euclidean.js +++ b/Euclidean.js @@ -54,10 +54,11 @@ export class Euclidean * @param {number} radius * @param {number} rotation * @param {boolean} close - * TODO Projection is incorrect on larger creatures; corners being cut */ static cone(canvas, auraRing, origin, radius, rotation, close) { + // TODO Projection is incorrect on larger creatures; corners being cut + const points = Euclidean.arcPoints( origin, radius, diff --git a/GridBased.js b/GridBased.js index 3b76ed2..619ab26 100644 --- a/GridBased.js +++ b/GridBased.js @@ -1,6 +1,8 @@ import { Euclidean } from "./Euclidean.js"; import { Point } from "./Point.js"; +// TODO Need to expand circle based on irregular token + export class GridBased { /** @@ -38,7 +40,6 @@ export class GridBased canvas, simpleTokenDocument, GridBased.baseCircle(simpleTokenDocument, origins, radius, gridSize, gridOffset), - gridSize, gridOffset, ); } @@ -71,27 +72,23 @@ export class GridBased const closestOriginToStart = GridBased.getClosestPointTo(origin, arcPoints.start, origins, false); const closestOriginToEnd = GridBased.getClosestPointTo(origin, arcPoints.end, origins, true); - closestOriginToStart.invert = true; - closestOriginToStart.clockwise = false; - closestOriginToEnd.invert = true; - closestOriginToEnd.clockwise = true; - let cone = GridBased.removePointsBetween(circle, closestCircleToEnd, closestCircleToStart); GridBased.addCorners(cone); cone.unshift( - new Point(origin.x, origin.y, true, false), + new Point(origin.x, origin.y), closestOriginToStart, ...GridBased.connectEnd(closestOriginToStart, closestCircleToStart, gridSize, false), ); cone.push( ...GridBased.connectEnd(closestOriginToEnd, closestCircleToEnd, gridSize, true), closestOriginToEnd, - new Point(origin.x, origin.y, true, true), + new Point(origin.x, origin.y), ); cone = GridBased.bridgeGaps(cone, gridSize, true); + cone = GridBased.removeDuplicatePoints(cone); - GridBased.debugDrawPoints(canvas, cone, '#00ff00', '#222222'); - GridBased.drawPoints(canvas, simpleTokenDocument, cone, gridSize, gridOffset); + // GridBased.debugDrawPoints(canvas, cone, '#00ff00', '#222222'); + GridBased.drawAroundPointsClockwise(canvas, cone, origin, gridSize, gridOffset); } // Shapes @@ -166,11 +163,11 @@ export class GridBased const end = points[points.length - 1]; points.unshift( - new Point(start.x, start.y, true, false), + new Point(start.x, start.y), ); points.push( - new Point(end.x, end.y, true, true), + new Point(end.x, end.y), ); } @@ -178,10 +175,9 @@ export class GridBased * Bridge the gaps between points on straights and diagonals * @param {Point[]} points * @param {number} gridSize - * @param {boolean} invert * @returns {Point[]} */ - static bridgeGaps(points, gridSize, invert = false) + static bridgeGaps(points, gridSize) { const bridgedPoints = []; let previousPoint = points[points.length - 1]; @@ -214,7 +210,7 @@ export class GridBased break; } - const newPoint = new Point(bridgeX, bridgeY, invert, point.clockwise); + const newPoint = new Point(bridgeX, bridgeY); bridgedPoints.push(newPoint); previousPoint = newPoint; } while (differenceX > gridSize || differenceY > gridSize); @@ -247,114 +243,117 @@ export class GridBased let distance = origin.distanceTo(target); while (distance > targetDistance) { - const point = new Point(currentPoint.x, currentPoint.y, true, clockwise); + const point = new Point(currentPoint.x, currentPoint.y); angle = currentPoint.angleTo(target); - if (clockwise === true) { - switch (true) { - case angle === 0: - case angle === 360: - case angle > 0 && angle < 45: - point.x += gridSize; - break; + clockwise === true + ? point.moveClockwise(angle, gridSize) + : point.moveAnticlockwise(angle, gridSize); - case angle === 45: - case angle > 45 && angle < 90: - point.x += gridSize; - point.y += gridSize; - break; + points.push(point); - case angle === 90: - case angle > 90 && angle < 135: - point.y += gridSize; - break; + currentPoint = point; + distance = currentPoint.distanceTo(target); + } - case angle === 135: - case angle > 135 && angle < 180: - point.x -= gridSize; - point.y += gridSize; - break; + return clockwise === true + ? points.reverse() + : points; + } - case angle === 180: - case angle > 180 && angle < 225: - point.x -= gridSize; - break; - case angle === 225: - case angle > 225 && angle < 270: - point.x -= gridSize; - point.y -= gridSize; - break; + /** + * Draw points onto the canvas wrapping clockwise + * @param {PIXI.Graphics} canvas + * @param {Point[]} points + * @param {Point} origin + * @param {number} gridSize + * @param {number} gridOffset + */ + static drawAroundPointsClockwise(canvas, points, origin, gridSize, gridOffset) + { + const cursor = new Point() - case angle === 270: - case angle > 270 && angle < 315: - point.y -= gridSize; - break; + for (let index = 0; index < points.length; ++index) { + const currentPoint = points[index]; + + if (index === 0) { + cursor.moveTo(currentPoint.x, currentPoint.y); - case angle === 315: - case angle > 315 && angle < 360: - point.x += gridSize; - point.y -= gridSize; - break; + if (currentPoint.isOnGrid(gridSize) === false) { + const angle = cursor.angleTo(origin); + const modifier = angle % 90 === 0 ? 45 : 0; + cursor.moveClockwise(angle + modifier, gridOffset); } - } else { - switch (true) { - case angle === 0: - case angle === 360: - case angle < 360 && angle > 315: - point.x += gridSize; - break; - case angle === 315: - case angle < 315 && angle > 270: - point.x += gridSize; - point.y -= gridSize; - break; + canvas.moveTo(cursor.x, cursor.y); + } - case angle === 270: - case angle < 270 && angle > 225: - point.y -= gridSize; - break; + if (currentPoint.isOnGrid(gridSize) === true) { + cursor.moveTo(currentPoint.x, currentPoint.y); + canvas.lineTo(currentPoint.x, currentPoint.y); + continue; + } - case angle === 225: - case angle < 225 && angle > 180: - point.x -= gridSize; - point.y -= gridSize; - break; + const nextPoint = index === points.length - 1 + ? points[0] + : points[index + 1]; + + const outgoingAngle = currentPoint.angleTo(nextPoint); - case angle === 180: - case angle < 180 && angle > 135: - point.x -= gridSize; + let targetAngle; + + switch (outgoingAngle) { + case 0: + targetAngle = 315; + break; + + case 45: + case 135: + case 225: + case 315: + targetAngle = outgoingAngle; + break; + + default: + targetAngle = outgoingAngle - 45 + break; + } + + let cursorAngle = currentPoint.angleTo(cursor); + let safety = 4; + + while (cursorAngle !== targetAngle) { + switch (cursorAngle) { + case 45: + cursor.moveBy(-gridSize, 0); break; - case angle === 135: - case angle < 135 && angle > 90: - point.x -= gridSize; - point.y += gridSize; + case 135: + cursor.moveBy(0, -gridSize); break; - case angle === 90: - case angle < 90 && angle > 45: - point.y += gridSize; + case 225: + cursor.moveBy(gridSize, 0); break; - case angle === 45: - case angle < 45 && angle > 0: - point.x += gridSize; - point.y += gridSize; + case 315: + cursor.moveBy(0, gridSize); break; } - } - points.push(point); + canvas.lineTo(cursor.x, cursor.y); + cursorAngle = currentPoint.angleTo(cursor); - currentPoint = point; - distance = currentPoint.distanceTo(target); + safety--; + if (safety === 0) { + console.warn('Safety hit'); + break; + } + } } - return clockwise === true - ? points.reverse() - : points; + canvas.closePath(); } /** @@ -362,64 +361,25 @@ export class GridBased * @param {PIXI.Graphics} canvas * @param {SimpleTokenDocument} simpleTokenDocument * @param {Point[]} points - * @param {number} gridSize * @param {number} gridOffset */ - static drawPoints(canvas, simpleTokenDocument, points, gridSize, gridOffset) + static drawPoints(canvas, simpleTokenDocument, points, gridOffset) { - // TODO Point on corner which steps out from the main branch cuts in on itself - const originX = simpleTokenDocument.object.w / 2; const originY = simpleTokenDocument.object.h / 2; let first = true; for (const point of points) { - const offsetX = point.x % gridSize; - const offsetY = point.y % gridSize; - - if (offsetX !== 0) { + if (point.x !== originX || point.y !== originY) { point.x += point.x <= originX ? -gridOffset : gridOffset; - } - - if (offsetY !== 0) { + point.y += point.y <= originY ? -gridOffset : gridOffset; } - if (point.invert === true && offsetX !== 0 && offsetY !== 0) { - const directionX = point.x < originX; - const directionY = point.y < originY; - - switch (true) { - case directionX === false && directionY === false: - point.clockwise === true - ? point.x -= gridSize - : point.y -= gridSize; - break; - - case directionX === true && directionY === false: - point.clockwise === true - ? point.y -= gridSize - : point.x += gridSize; - break; - - case directionX === true && directionY === true: - point.clockwise === true - ? point.x += gridSize - : point.y += gridSize; - break; - - case directionX === false && directionY === true: - point.clockwise === true - ? point.y += gridSize - : point.x -= gridSize; - break; - } - } - if (first === true) { canvas.moveTo(point.x, point.y); first = false; @@ -623,17 +583,12 @@ export class GridBased // Debug - static debugDrawPoints(canvas, points, strokeColour = '#ff0000', fillColour = '#00ff00') + static debugDrawPoints(canvas, points) { let current = 5; - canvas.lineStyle(3, strokeColour, 1, 0.5); - for (let point of points) { - canvas.lineTo(point); - } - - canvas.lineStyle(3, fillColour, 1, 0.5); for (let point of points) { + canvas.lineStyle(3, '#ff0000'); canvas.drawCircle(point.x, point.y, current); current+=0.5; } diff --git a/Point.js b/Point.js index 18696f8..7955d52 100644 --- a/Point.js +++ b/Point.js @@ -1,11 +1,5 @@ export class Point { - /** @type {boolean} */ - clockwise = true; - - /** @type {boolean} */ - invert = false; - /** @type {number} */ #x = 0; @@ -16,16 +10,8 @@ export class Point * Create a new Point * @param {number|float} x * @param {number|float} y - * @param {boolean} invert - */ - constructor( - x = 0, - y = 0, - invert = false, - clockwise = true, - ) { - this.clockwise = clockwise; - this.invert = invert; + */ + constructor(x = 0, y = 0) { this.x = x; this.y = y; } @@ -120,17 +106,30 @@ export class Point // Comparisons + /** + * Whether this point is aligned with the grid + * @param {number} gridSize + * @returns {boolean} + */ + isOnGrid(gridSize) + { + return this.x % gridSize === 0 + && this.y % gridSize === 0; + } + /** * Whether this Point has the same co-ordinates as another Point * @param {Point} point * @returns {boolean} */ - matches(point) { + matches(point) + { return this.x === point.x && this.y === point.y; } // Distances + /** * Find the absolute difference between two numbers * @param {number|float} start @@ -200,4 +199,138 @@ export class Point { return Point.absoluteDifference(this.y, point.y); } + + // Movement + + /** + * Move a point at an angle anti-clockwise + * @param {number|float} angle + * @param {number} distance + */ + moveAnticlockwise(angle, distance) + { + switch (true) { + case angle === 0: + case angle === 360: + case angle < 360 && angle > 315: + this.x += distance; + break; + + case angle === 315: + case angle < 315 && angle > 270: + this.x += distance; + this.y -= distance; + break; + + case angle === 270: + case angle < 270 && angle > 225: + this.y -= distance; + break; + + case angle === 225: + case angle < 225 && angle > 180: + this.x -= distance; + this.y -= distance; + break; + + case angle === 180: + case angle < 180 && angle > 135: + this.x -= distance; + break; + + case angle === 135: + case angle < 135 && angle > 90: + this.x -= distance; + this.y += distance; + break; + + case angle === 90: + case angle < 90 && angle > 45: + this.y += distance; + break; + + case angle === 45: + case angle < 45 && angle > 0: + this.x += distance; + this.y += distance; + break; + } + } + + /** + * Move a point at an angle clockwise + * @param {number|float} angle + * @param {number} distance + */ + moveClockwise(angle, distance) + { + switch (true) { + case angle === 0: + case angle === 360: + case angle > 0 && angle < 45: + this.x += distance; + break; + + case angle === 45: + case angle > 45 && angle < 90: + this.x += distance; + this.y += distance; + break; + + case angle === 90: + case angle > 90 && angle < 135: + this.y += distance; + break; + + case angle === 135: + case angle > 135 && angle < 180: + this.x -= distance; + this.y += distance; + break; + + case angle === 180: + case angle > 180 && angle < 225: + this.x -= distance; + break; + + case angle === 225: + case angle > 225 && angle < 270: + this.x -= distance; + this.y -= distance; + break; + + case angle === 270: + case angle > 270 && angle < 315: + this.y -= distance; + break; + + case angle === 315: + case angle > 315 && angle < 360: + this.x += distance; + this.y -= distance; + break; + } + } + + /** + * Move a point by an amount + * @param {number} x + * @param {number} y + */ + moveBy(x, y) + { + this.x += x; + this.y += y; + } + + /** + * Move a point to a co-ordinate + * @param {number} x + * @param {number} y + */ + moveTo(x, y) + { + this.x = x; + this.y = y; + } } diff --git a/README.md b/README.md index 85ab7e1..fd71d25 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,15 @@ Setting the "Angle" to "360" will draw a complete circle. ### Grid based aura rings +**This feature is experimental and under development** + Checking the "Use grid-based shapes" option will turn the aura ring from euclidean to grid-based, if the current scene has a grid enabled. -Hex grids are not currently supported in this mode. +![A comparison between a grid-based and euclidean Aura Ring](images/grid-based.jpg) + +Tokens must be of a uniform size (1x1, 2x2, 3x3, etc) for this to work. + +Hex grids are not currently supported. ### Negative radius @@ -90,7 +96,7 @@ You may set the radius of an aura ring to a negative value to draw within the to ![An example of a token with a negative radius](images/negative-radius.jpg) -This will only work with tokens which feauture transparency, and is limited by the overall size of the token. +This will only work with tokens which feature transparency, and is limited by the overall size of the token. For example, a standard medium creature (1 square = 5 foot) can have a minimum radius of -2.49 feet. diff --git a/images/config.jpg b/images/config.jpg index 07eb846..7abe3b8 100644 Binary files a/images/config.jpg and b/images/config.jpg differ diff --git a/images/grid-based.jpg b/images/grid-based.jpg new file mode 100644 index 0000000..5b8ff91 Binary files /dev/null and b/images/grid-based.jpg differ