From c096c787a6288464be88c3db582ca72a3c671340 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Mon, 26 Aug 2024 15:51:42 -0700 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=A4=96=20chore|libGeometry|Update?= =?UTF-8?q?=20to=200.3.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves grid measure classes to libGeometry --- scripts/geometry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/geometry b/scripts/geometry index ddd03fe..1476199 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit ddd03fe6742d5fe6fc24e2390eb8f1b6f020c235 +Subproject commit 1476199d9a2814cb983dda170a0d338a5fd8a80b From 476393a957e074b7f9fb2a231b326844cc688371 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 10:25:11 -0700 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=92=A1=20refactor|Measure|Drop=20gr?= =?UTF-8?q?id=5Fcoordinates=20in=20favor=20of=20libGeometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 2 +- scripts/Token.js | 2 +- scripts/geometry | 2 +- scripts/measurement/Grid.js | 2 +- scripts/measurement/grid_coordinates.js | 667 ------------------------ scripts/module.js | 11 +- scripts/pathfinding/BorderTriangle.js | 9 +- scripts/pathfinding/pathfinding.js | 2 +- scripts/token_speed.js | 2 +- 9 files changed, 13 insertions(+), 686 deletions(-) delete mode 100644 scripts/measurement/grid_coordinates.js diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 1022ad4..c68556a 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -39,7 +39,7 @@ import { import { tokenSpeedSegmentSplitter } from "./token_speed.js"; import { log, roundMultiple } from "./util.js"; import { MovePenalty } from "./measurement/MovePenalty.js"; -import { GridCoordinates3d } from "./measurement/grid_coordinates.js"; +import { GridCoordinates3d } from "./geometry/3d/GridCoordinates3d.js"; /** * Modified Ruler diff --git a/scripts/Token.js b/scripts/Token.js index 549936e..accd369 100644 --- a/scripts/Token.js +++ b/scripts/Token.js @@ -10,7 +10,7 @@ Ruler import { MODULE_ID, FLAGS } from "./const.js"; import { Settings } from "./settings.js"; import { log } from "./util.js"; -import { GridCoordinates3d } from "./measurement/grid_coordinates.js"; +import { GridCoordinates3d } from "./geometry/3d/GridCoordinates3d.js"; // Patches for the Token class export const PATCHES = {}; diff --git a/scripts/geometry b/scripts/geometry index 1476199..3b9097a 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit 1476199d9a2814cb983dda170a0d338a5fd8a80b +Subproject commit 3b9097a227f6aa17e4acf0fca27cdaecb756e1e7 diff --git a/scripts/measurement/Grid.js b/scripts/measurement/Grid.js index b6e86ed..dd06860 100644 --- a/scripts/measurement/Grid.js +++ b/scripts/measurement/Grid.js @@ -5,7 +5,7 @@ CONST /* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ "use strict"; -import { GridCoordinates3d } from "./grid_coordinates.js"; +import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; import { Point3d } from "../geometry/3d/Point3d.js"; /** diff --git a/scripts/measurement/grid_coordinates.js b/scripts/measurement/grid_coordinates.js deleted file mode 100644 index 699a613..0000000 --- a/scripts/measurement/grid_coordinates.js +++ /dev/null @@ -1,667 +0,0 @@ -/* globals -canvas, -CONFIG, -CONST, -foundry, -game, -PIXI -*/ -/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */ -"use strict"; - -import { Point3d } from "../geometry/3d/Point3d.js"; -import { isOdd } from "../util.js"; -import { Settings } from "../settings.js"; - - -// ----- NOTE: Foundry typedefs ----- // - -/** - * A pair of row and column coordinates of a grid space. - * @typedef {object} GridOffset - * @property {number} i The row coordinate - * @property {number} j The column coordinate - */ - -/** - * An offset of a grid space or a point with pixel coordinates. - * @typedef {GridOffset|Point} GridCoordinates - */ - - - -/** - * A 2d point that can function as Point|GridOffset. For just a point, use PIXI.Point. - */ -export class GridCoordinates extends PIXI.Point { - /** - * Factory function that converts a GridOffset to GridCoordinates. - * The {x, y} coordinates are centered. - * @param {GridOffset} offset - * @returns {GridCoordinates} - */ - static fromOffset(offset) { - const pt = new this(); - pt.setOffset(offset); - return pt; - } - - /** - * Factory function that converts a Foundry GridCoordinates. - * If the object has x,y properties, those are favored over i,j. - * @param {object} - * @returns {GridCoordinates} - */ - static fromObject(obj) { - const newObj = super.fromObject(obj); - if ( Object.hasOwn(obj, "i") && !Object.hasOwn(obj, "x") ) newObj.i = obj.i; - if ( Object.hasOwn(obj, "j") && !Object.hasOwn(obj, "y") ) newObj.j = obj.j; - return newObj; - } - - /** @type {number} */ - get i() { return canvas.grid.getOffset({ x: this.x, y: this.y }).i } - - /** @type {number} */ - get j() { return canvas.grid.getOffset({ x: this.x, y: this.y }).j } - - /** @type {number} */ - set i(value) { this.y = canvas.grid.getCenterPoint({ i: value, j: this.j }).y; } - - /** @type {number} */ - set j(value) { this.x = canvas.grid.getCenterPoint({ i: this.i, j: value }).x; } - - /** - * Faster than getting i and j separately. - * @type {object} - */ - get offset() { return canvas.grid.getOffset({ x: this.x, y: this.y }); } - - /** - * Convert this point to a new one based on its offset. - * Sets x,y,z to equal the top left for i,j,k - * @returns {PIXI.Point} New object - */ - get topLeft() { - return this.constructor.fromObject(canvas.grid.getTopLeftPoint({ x: this.x, y: this.y })); - } - - /** - * Convert this point to a new one based on its offset. - * Sets x,y,z to equal the center for i,j,k - * @returns {GridCoordinates3d} New object - */ - get center() { - return this.constructor.fromObject(canvas.grid.getCenterPoint({ x: this.x, y: this.y })); - } - - toPoint() { return PIXI.Point.fromObject(this); } - - /** - * Change this point to a specific offset value. The point will be centered. - * @param {GridOffset} offset - */ - setOffset(offset) { - const { x, y } = canvas.grid.getCenterPoint(offset); - this.x = x; - this.y = y; - return this; - } - - /** - * Center this point based on its current offset value. - */ - centerToOffset() { return this.setOffset(this); } - - /** - * For compatibility with PIXI.Point. - * @returns {this} - */ - to2d() { return this; } - - /** - * Convert to 3d. - * @returns {GridCoordinates3d} - */ - to3d() { return GridCoordinates3d.fromObject(this); } - - /** - * Determine the number of diagonals based on two 2d offsets for a square grid. - * If hexagonal, no diagonals. - * @param {GridOffset} aOffset - * @param {GridOffset} bOffset - * @returns {number} - */ - static numDiagonal(aOffset, bOffset) { - if ( canvas.grid.isHexagonal ) return 0; - let di = Math.abs(aOffset.i - bOffset.i); - let dj = Math.abs(aOffset.j - bOffset.j); - return Math.min(di, dj); - } - - /** - * Measure the distance between two points accounting for the current grid rules. - * For square, this accounts for the diagonal rules. For hex, measures in number of hexes. - * @param {Point} a - * @param {Point} b - * @returns {number} Distance, in grid units - */ - static gridDistanceBetween(a, b, altGridDistFn) { - if ( canvas.grid.isGridless ) return CONFIG.GeometryLib.utils.pixelsToGridUnits(this.distanceBetween(a, b)); - const distFn = canvas.grid.isHexagonal ? hexGridDistanceBetween : squareGridDistanceBetween; - const dist = distFn(a, b, altGridDistFn); - - // Round to the nearest grid distance if close. - const gridD = canvas.grid.distance; - if ( (dist % gridD).almostEqual(0) ) return Math.round(dist / gridD) * gridD; - return dist; - } - - /** - * Measure the distance between two offsets accounting for the current grid rules. - * Uses `gridDistanceBetween`. - * @param {GridOffset} aOffset - * @param {GridOffset} bOffset - * @returns {number} Distance, in grid units - */ - static gridDistanceBetweenOffsets(aOffset, bOffset, altGridDistFn) { - return this.gridDistanceBetween(this.fromOffset(aOffset), this.fromOffset(bOffset), altGridDistFn); - } - - /** - * Return a function that can repeatedly measure segments, tracking the alternating diagonals. - */ - static alternatingGridDistanceFn = alternatingGridDistance; - - /** - * Measure distance, offset, and cost for a given 2d segment a|b. - * Uses `gridDistanceBetween`. - * @param {Point} a Start of the segment - * @param {Point} b End of the segment - * @param {number} [numPrevDiagonal=0] Number of diagonals thus far - * @param {function} [costFn] Optional cost function; defaults to canvas.controls.ruler._getCostFunction - * @returns {object} - * - @prop {number} distance gridDistanceBetween for a|b - * - @prop {number} offsetDistance gridDistanceBetweenOffsets for a|b - * - @prop {number} cost Measured cost using the cost function - * - @prop {number} numDiagonal Number of diagonals between the offsets if square or hex elevation - */ - static gridMeasurementForSegment(a, b, numPrevDiagonal = 0, costFn) { - costFn ??= canvas.controls.ruler._getCostFunction(); - const lPrevStart = canvas.grid.diagonals === CONST.GRID_DIAGONALS.ALTERNATING_2 ? 1 : 0; - const lPrev = isOdd(numPrevDiagonal) ? lPrevStart : Number(!lPrevStart); - const aOffset = this.fromObject(a); - const bOffset = this.fromObject(b); - const distance = this.gridDistanceBetween(a, b, this.alternatingGridDistanceFn({ lPrev })); - const offsetDistance = this.gridDistanceBetweenOffsets(a, b, this.alternatingGridDistanceFn({ lPrev })); - const cost = costFn ? costFn(a, b, offsetDistance) : offsetDistance; - const numDiagonal = this.numDiagonal(aOffset, bOffset); - return { distance, offsetDistance, cost, numDiagonal }; - } - -} - -/** - * Measure the 3d segment distance for a hex grid. - * @param {Point|Point3d} a - * @param {Point|Point3d} b - * @returns {number} Number of hexes accounting for grid size. - */ -function hexGridDistanceBetween(p0, p1, altGridDistFn) { - const D = CONST.GRID_DIAGONALS; - if ( !(p0 instanceof Point3d) ) p0 = Point3d.fromObject(p0); - if ( !(p1 instanceof Point3d) ) p1 = Point3d.fromObject(p1); - - - // Translate the 2d movement to cube units. Elevation is in grid size units. - const d0 = canvas.grid.pointToCube(p0); - const d1 = canvas.grid.pointToCube(p1); - d0.k = (p0.z / canvas.grid.size) || 0; // Normalize so that elevation movement = 1 when traversing 1 grid space vertically. - d1.k = (p1.z / canvas.grid.size) || 0; - const dist2d = foundry.grid.HexagonalGrid.cubeDistance(d0, d1); - const distElev = Math.abs(d0.k - d1.k); - - // Like with squareGridDistanceBetween, use the maximum axis to avoid Math.max(), Max.min() throughout. - const [maxAxis, minAxis] = dist2d > distElev ? [dist2d, distElev] : [distElev, dist2d]; - let l; - const diagonals = game.settings.get("core", "gridDiagonals"); - switch ( diagonals ) { - case D.EQUIDISTANT: l = maxAxis; break; // Max dx, dy, dz - case D.EXACT: { - const fn = Settings.get(Settings.KEYS.MEASURING.EUCLIDEAN_GRID_DISTANCE) ? Math.hypot : exactGridDistance; - l = fn(maxAxis, minAxis); - break; - } - case D.APPROXIMATE: l = approxGridDistance(maxAxis, minAxis); break; - case D.ALTERNATING_1: - case D.ALTERNATING_2: { - altGridDistFn ??= alternatingGridDistance(); - l = altGridDistFn(maxAxis, minAxis); - break; - } - case D.RECTILINEAR: - case D.ILLEGAL: l = maxAxis + minAxis; break; - } - return l * canvas.grid.distance; -} - -function approxGridDistance(maxAxis = 0, midAxis = 0, minAxis = 0) { - return maxAxis + (0.5 * midAxis) + (0.25 * minAxis); - // Equivalent to: - // return maxAxis + ((0.5 * (midAxis - minAxis)) + (0.75 * minAxis)) -} - -function exactGridDistance(maxAxis = 0, midAxis = 0, minAxis = 0) { - const A = Math.SQRT2 - 1; - const B = Math.SQRT3 - 1; - return maxAxis + (A * midAxis) + ((B - A) * minAxis); - // Equivalent to: - // maxAxis + (A * (midAxis - minAxis)) + (B * minAxis); -} - - -/** - * Track the diagonals required for measuring alternating grid distance. - * Returns a function that calls _alternatingGridDistance with the cached previous diagonals. - * Handles hex or square grids. - * @param {object} [opts] - * - @param {number} [opts.lPrev] - * - @param {number} [opts.prevMaxAxis] - * - @param {number} [opts.prevMidAxis] - * - @param {number} [opts.prevMinAxis] - * @returns {function} - * - @param {Point|Point3d} p0 - * - @param {Point|Point3d} p1 - * - @param {object} [opts] Same opts as the original function. - * - @returns {number} The distance in number of squares or hexes - */ -function alternatingGridDistance(opts = {}) { - let lPrev = opts.lPrev ?? canvas.grid.diagonals === CONST.GRID_DIAGONALS.ALTERNATING_2 ? 1 : 0; - let prevMaxAxis = opts.prevMaxAxis ?? lPrev; - let prevMidAxis = opts.prevMidAxis ?? lPrev; - let prevMinAxis = opts.prevMinAxis ?? lPrev; - return (maxAxis = 0, midAxis = 0, minAxis = 0) => { - prevMaxAxis += maxAxis; - prevMidAxis += midAxis; - prevMinAxis += minAxis; - const lCurr = _alternatingGridDistance(prevMaxAxis, prevMidAxis, prevMinAxis); - const l = lCurr - lPrev; // If 2:1:2, this will cause the flip along with dxPrev and dyPrev. - lPrev = lCurr; - return l; - } -} - -function _alternatingGridDistance(maxAxis = 0, midAxis = 0, minAxis = 0) { - // How many full spaces have been traversed? - const spacesX = Math.floor(maxAxis); - const spacesY = Math.floor(midAxis); - const spacesZ = Math.floor(minAxis); - - // Shift in x,y since last move. - const deltaX = maxAxis - spacesX; - const deltaY = midAxis - spacesY; - const deltaZ = minAxis - spacesZ; - - // Determine the movement assuming diagonals === 2, so - const a = approxGridDistance(spacesX, spacesY, spacesZ); - const A = Math.floor(a); // If no prior move, this is the total move. - - // Add in the previous move deltas. Essentially do an approximate move for the deltas. - const B = Math.floor(a + 1); - const C = Math.floor(a + 1.5); - const D = Math.floor(a + 1.75); - return A + ((B - A) * deltaX) + ((C - B) * deltaY) + ((D - C) * deltaZ); - // Same as - // (A * (1 - deltaX)) + (B * (deltaX - deltaY)) + (C * (deltaY - deltaZ)) + (D * deltaZ); -} - - - - -/** - * Measure the 3d segment distance for a square grid, accounting for diagonal movement. - * @param {Point|Point3d} a - * @param {Point|Point3d} b - * @returns {number} Distance accounting for grid size. - */ -function squareGridDistanceBetween(p0, p1, altGridDistFn) { - const D = CONST.GRID_DIAGONALS; - if ( !(p0 instanceof Point3d) ) p0 = Point3d.fromObject(p0); - if ( !(p1 instanceof Point3d) ) p1 = Point3d.fromObject(p1); - - // Normalize so that dx === 1 when traversing 1 grid space. - const dx = Math.abs(p0.x - p1.x) / canvas.grid.size; - const dy = Math.abs(p0.y - p1.y) / canvas.grid.size; - const dz = Math.abs(p0.z - p1.z) / canvas.grid.size; - - // Make dx the maximum, dy, the middle, and dz the minimum change across the axes. - // If two-dimensional, dz will be zero. (Slightly faster than an array sort.) - const minMax = Math.minMax(dx, dy, dz); - const maxAxis = minMax.max; - const minAxis = minMax.min; - const midAxis = dx.between(dy, dz) ? dx - : dy.between(dx, dz) ? dy : dz; - - // TODO: Make setting to use Euclidean distance. - // exactDistanceFn = setting ? Math.hypot : exactGridDistance; - let l; - switch ( canvas.grid.diagonals ) { - case D.EQUIDISTANT: l = maxAxis; break; // Max dx, dy, dz - case D.EXACT: { - const fn = Settings.get(Settings.KEYS.MEASURING.EUCLIDEAN_GRID_DISTANCE) ? Math.hypot : exactGridDistance; - l = fn(maxAxis, midAxis, minAxis); - break; - } - case D.APPROXIMATE: l = approxGridDistance(maxAxis, midAxis, minAxis); break; - case D.ALTERNATING_1: - case D.ALTERNATING_2: { - altGridDistFn ??= alternatingGridDistance(); - l = altGridDistFn(maxAxis, midAxis, minAxis); - break; - } - case D.RECTILINEAR: - case D.ILLEGAL: l = maxAxis + midAxis + minAxis; break; - } - return l * canvas.grid.distance; -} - -// ----- NOTE: 3d versions of Foundry typedefs ----- // - -/** - * @typedef {object} RegionMovementWaypoint - * @property {number} x The x-coordinates in pixels (integer). - * @property {number} y The y-coordinates in pixels (integer). - * @property {number} elevation The elevation in grid units. - */ - -/** - * Row, column, elevation coordinates of a grid space. Follows from GridOffset - * The vertical assumes the grid cubes are stacked upon one another. - * @typedef {object} GridOffset3d - * @property {number} i The row coordinate - * @property {number} j The column coordinate - * @property {number} k The elevation, where 0 is at the scene elevation, negative is below the scene. - * k * canvas.scene.dimensions.distance === elevation in grid units. - */ - -/** - * An offset of a grid space or a point with pixel coordinates. - * @typedef {GridOffset3d|Point3d} GridCoordinates3d - */ - -/** - * A 3d point that can function as a Point3d|RegionMovementWaypoint. - * Does not handle GridOffset3d so that it can be passed to 2d Foundry functions that - * treat objects with {i,j} parameters differently. - */ -export class RegionMovementWaypoint3d extends Point3d { - /** @type {number} */ - get elevation() { return CONFIG.GeometryLib.utils.pixelsToGridUnits(this.z); } - - /** @type {number} */ - set elevation(value) { this.z = CONFIG.GeometryLib.utils.gridUnitsToPixels(value); } - - /** - * Factory function to convert a generic point object to a RegionMovementWaypoint3d. - * @param {Point|PIXI.Point|GridOffset|RegionMovementWaypoint|GridOffset3d|GridCoordinates3d} pt - * i, j, k assumed to refer to the center of the grid - * @returns {GridCoordinates3d} - */ - static fromPoint(pt) { - // Priority: x,y,z | elevation | i, j, k - let x; - let y; - if ( Object.hasOwn(pt, "x") ) { - x = pt.x; - y = pt.y; - } else if ( Object.hasOwn(pt, "i") ) { - const res = canvas.grid.getCenterPoint(pt); - x = res.x; - y = res.y; - } - - // Process elevation. - const newPt = new this(x, y); - if ( Object.hasOwn(pt, "z") ) newPt.z = pt.z; - else if ( Object.hasOwn(pt, "elevation") ) newPt.elevation = pt.elevation; - else if ( Object.hasOwn(pt, "k") ) newPt.elevation = GridCoordinates3d.elevationForUnit(pt.k); - return newPt; - } -} - -/** - * A 3d point that can function as Point3d|GridOffset3d|RegionMovementWaypoint. - * Links z to the elevation property. - */ -export class GridCoordinates3d extends RegionMovementWaypoint3d { - /** - * Factory function that converts a GridOffset to GridCoordinates. - * @param {GridOffset} offset - * @param {number} [elevation] Override the elevation in offset, if any. In grid units - * @returns {GridCoordinates3d} - */ - static fromOffset(offset, elevation) { - const pt = new this(); - pt.setOffset(offset); - if ( typeof elevation !== "undefined" ) pt.elevation = elevation; - return pt; - } - - /** - * Factory function to determine the grid square/hex center for the point. - * @param {Point3d} - * @returns {GridCoordinate3d} - */ - static gridCenterForPoint(pt) { - pt = new this(pt.x, pt.y, pt.z); - return pt.centerToOffset(); - } - - /** - * Factory function that converts a Foundry GridCoordinates. - * If the object has x,y,z,elevation properties, those are favored over i,j,k. - * @param {object} - * @returns {GridCoordinates3d} - */ - static fromObject(obj) { - const newObj = super.fromObject(obj); - if ( Object.hasOwn(obj, "i") && !Object.hasOwn(obj, "x") ) newObj.i = obj.i; - if ( Object.hasOwn(obj, "j") && !Object.hasOwn(obj, "y") ) newObj.j = obj.j; - if ( Object.hasOwn(obj, "k") - && !(Object.hasOwn(obj, "z") - || Object.hasOwn(obj, "elevationZ") - || Object.hasOwn(obj, "elevation")) ) newObj.k = obj.k; - return newObj; - } - - /** @type {number} */ - get i() { return canvas.grid.getOffset({ x: this.x, y: this.y }).i } - - /** @type {number} */ - get j() { return canvas.grid.getOffset({ x: this.x, y: this.y }).j } - - /** @type {number} */ - get k() { return this.constructor.unitElevation(CONFIG.GeometryLib.utils.pixelsToGridUnits(this.z)); } - - /** @type {number} */ - set i(value) { this.y = canvas.grid.getCenterPoint({ i: value, j: this.j }).y; } - - /** @type {number} */ - set j(value) { this.x = canvas.grid.getCenterPoint({ i: this.i, j: value }).x; } - - /** @type {number} */ - set k(value) { this.elevation = this.constructor.elevationForUnit(value); } - - /** - * Faster than getting i and j separately. - * @type {object} - */ - get offset() { - const o = canvas.grid.getOffset({ x: this.x, y: this.y }); - o.k = this.k; - return o; - } - - /** - * Convert this point to a new one based on its offset. - * Sets x,y,z to equal the top left for i,j,k - * @returns {GridCoordinates3d} New object - */ - get topLeft() { - const tl = this.constructor.fromObject(canvas.grid.getTopLeftPoint({ x: this.x, y: this.y })); - tl.z = CONFIG.GeometryLib.utils.gridUnitsToPixels(this.constructor.elevationForUnit(this.k)); - return tl; - } - - /** - * Convert this point to a new one based on its offset. - * Sets x,y,z to equal the center for i,j,k - * @returns {GridCoordinates3d} New object - */ - get center() { - const center = this.constructor.fromObject(canvas.grid.getCenterPoint({ x: this.x, y: this.y })); - center.z = CONFIG.GeometryLib.utils.gridUnitsToPixels(this.constructor.elevationForUnit(this.k)); - return center; - } - - - - /** - * Convert this point to a RegionMovementWaypoint. - * @returns {RegionMovementWaypoint3d} - */ - toWaypoint() { return RegionMovementWaypoint3d.fromObject(this); } - - /** - * Change this point to a specific offset value. - * Faster than setting each {i, j, k} separately. - * @param {GridOffset} offset - */ - setOffset(offset) { - const { x, y } = canvas.grid.getCenterPoint(offset); - this.x = x; - this.y = y; - this.elevation = this.constructor.elevationForUnit(offset.k || 0); - return this; - } - - /** - * Change this point to a specific offset value in the 2d axes. Do not modify elevation. - * Faster than setting each {i, j} separately. - * @param {GridOffset} offset - */ - setOffset2d(offset) { - const { x, y } = canvas.grid.getCenterPoint(offset); - this.x = x; - this.y = y; - return this; - } - - /** - * Center this point based on its current offset value. - */ - centerToOffset() { return this.setOffset(this); } - - /** - * Conversion to 2d. - * @returns {GridCoordinates} - */ - to2d() { return GridCoordinates.fromObject(this); } - - /** - * @returns {this} - */ - to3d() { return this; } - - /** - * Determine the number of diagonals based on two offsets. - * If hexagonal, only elevation diagonals count. - * @param {GridOffset} aOffset - * @param {GridOffset} bOffset - * @returns {number} - */ - static numDiagonal(aOffset, bOffset) { - if ( canvas.grid.isHexagonal ) return Math.abs(aOffset.k - bOffset.k); - let di = Math.abs(aOffset.i - bOffset.i); - let dj = Math.abs(aOffset.j - bOffset.j); - let dk = Math.abs(aOffset.k - bOffset.k); - const midAxis = di.between(dj, dk) ? di - : dj.between(di, dk) ? dj : dk; - return midAxis; - } - - /** - * Calculate the unit elevation for a given set of coordinates. - * @param {number} elevation Elevation in grid units - * @returns {number} Elevation in number of grid steps. - */ - static unitElevation(elevation) { return Math.round(elevation / canvas.scene.dimensions.distance); } - - /** - * Calculate the grid unit elevation from unit elevation. - * Inverse of `unitElevation`. - * @param {number} k Unit elevation - * @returns {number} Elevation in grid units - */ - static elevationForUnit(k) { return k * canvas.scene.dimensions.distance; } - - /** - * Measure the distance between two points accounting for the current grid rules. - * For square, this accounts for the diagonal rules. For hex, measures in number of hexes. - * @param {Point3d} a - * @param {Point3d} b - * @returns {number} Distance, in grid units - */ - static gridDistanceBetween(a, b, altGridDistFn) { - if ( canvas.grid.isGridless ) return CONFIG.GeometryLib.utils.pixelsToGridUnits(this.distanceBetween(a, b)); - const distFn = canvas.grid.isHexagonal ? hexGridDistanceBetween : squareGridDistanceBetween; - const dist = distFn(a, b, altGridDistFn); - - // Round to the nearest grid distance if close. - const gridD = canvas.grid.distance; - if ( (dist % gridD).almostEqual(0) ) return Math.round(dist / gridD) * gridD; - return dist; - } - - /** - * Measure the distance between two offsets accounting for the current grid rules. - * Uses `gridDistanceBetween`. - * @param {GridOffset3d} aOffset - * @param {GridOffset3d} bOffset - * @returns {number} Distance, in grid units - */ - static gridDistanceBetweenOffsets(aOffset, bOffset, altGridDistFn) { - return this.gridDistanceBetween(this.fromOffset(aOffset), this.fromOffset(bOffset), altGridDistFn); - } - - /** - * Measure distance, offset, and cost for a given segment a|b. - * Uses `gridDistanceBetween`. - * @param {Point3d} a Start of the segment - * @param {Point3d} b End of the segment - * @param {number} [numPrevDiagonal=0] Number of diagonals thus far - * @param {function} [costFn] Optional cost function; defaults to canvas.controls.ruler._getCostFunction - * @returns {object} - * - @prop {number} distance gridDistanceBetween for a|b - * - @prop {number} offsetDistance gridDistanceBetweenOffsets for a|b - * - @prop {number} cost Measured cost using the cost function - * - @prop {number} numDiagonal Number of diagonals between the offsets if square or hex elevation - */ - static gridMeasurementForSegment(a, b, numPrevDiagonal = 0, costFn) { - costFn ??= canvas.controls.ruler._getCostFunction(); - const lPrevStart = canvas.grid.diagonals === CONST.GRID_DIAGONALS.ALTERNATING_2 ? 1 : 0; - const lPrev = isOdd(numPrevDiagonal) ? lPrevStart : Number(!lPrevStart); - const aOffset = this.fromObject(a); - const bOffset = this.fromObject(b); - const distance = this.gridDistanceBetween(a, b, this.alternatingGridDistanceFn({ lPrev })); - const offsetDistance = this.gridDistanceBetweenOffsets(a, b, this.alternatingGridDistanceFn({ lPrev })); - const cost = costFn ? costFn(a, b, offsetDistance) : offsetDistance; - const numDiagonal = this.numDiagonal(aOffset, bOffset); - return { distance, offsetDistance, cost, numDiagonal }; - } - - /** - * Return a function that can repeatedly measure segments, tracking the alternating diagonals. - */ - static alternatingGridDistanceFn = alternatingGridDistance; -} diff --git a/scripts/module.js b/scripts/module.js index 37bdf37..2cf4a24 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -16,9 +16,6 @@ import { log, gridShape } from "./util.js"; import { defaultHPAttribute } from "./system_attributes.js"; import { registerGeometry } from "./geometry/registration.js"; -// Grid coordinates -import { GridCoordinates, RegionMovementWaypoint3d, GridCoordinates3d } from "./measurement/grid_coordinates.js"; - // Move Penalty import { MovePenalty } from "./measurement/MovePenalty.js"; @@ -152,13 +149,7 @@ Hooks.once("init", function() { game.modules.get(MODULE_ID).api = { gridShape, PATCHER, - - measure: { - GridCoordinates, - RegionMovementWaypoint3d, - GridCoordinates3d, - MovePenalty - }, + MovePenalty, pathfinding: { BorderTriangle, diff --git a/scripts/pathfinding/BorderTriangle.js b/scripts/pathfinding/BorderTriangle.js index 1d5dc45..0d30544 100644 --- a/scripts/pathfinding/BorderTriangle.js +++ b/scripts/pathfinding/BorderTriangle.js @@ -12,7 +12,7 @@ Wall import { MODULE_ID } from "../const.js"; import { Draw } from "../geometry/Draw.js"; import { WallTracerEdge } from "./WallTracer.js"; -import { GridCoordinates3d } from "../measurement/grid_coordinates.js"; +import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; const OTHER_DIRECTION = { ccw: "cw", @@ -494,11 +494,14 @@ export class BorderTriangle { _calculateMovementCost(fromPoint, toPoint, token) { // TODO: Handle 3d distance. Probably Ray3d with measureDistance or measureDistances. // TODO: Handle terrain distance. + const diagonals = Settings.get(Settings.KEYS.MEASURING.EUCLIDEAN_GRID_DISTANCE) + ? GridCoordinates3d.GRID_DIAGONALS.EUCLIDEAN : canvas.grid.diagonals; if ( CONFIG[MODULE_ID].pathfindingCheckTerrains ) { - const res = gridMeasurementForSegment(fromPoint, toPoint); + + const res = GridCoordinates3d.gridMeasurementForSegment(fromPoint, toPoint, 0, undefined, diagonals); return CONFIG.GeometryLib.utils.gridUnitsToPixels(res.cost); } - const distance = GridCoordinates3d.gridDistanceBetween(fromPoint, toPoint); + const distance = GridCoordinates3d.gridDistanceBetween(fromPoint, toPoint, undefined, diagonals); return CONFIG.GeometryLib.utils.gridUnitsToPixels(distance); } diff --git a/scripts/pathfinding/pathfinding.js b/scripts/pathfinding/pathfinding.js index 715d662..f3aad5e 100644 --- a/scripts/pathfinding/pathfinding.js +++ b/scripts/pathfinding/pathfinding.js @@ -18,7 +18,7 @@ import { cdt2dConstrainedGraph, cdt2dToBorderTriangles } from "../delaunator/cdt import { Settings } from "../settings.js"; import { MODULE_ID } from "../const.js"; import { MovePenalty } from "../measurement/MovePenalty.js"; -import { GridCoordinates3d } from "../measurement/grid_coordinates.js"; +import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; /* Testing diff --git a/scripts/token_speed.js b/scripts/token_speed.js index c05fd58..424b127 100644 --- a/scripts/token_speed.js +++ b/scripts/token_speed.js @@ -12,7 +12,7 @@ import { Point3d } from "./geometry/3d/Point3d.js"; import { Ray3d } from "./geometry/3d/Ray3d.js"; import { gridShape } from "./util.js"; import { MovePenalty } from "./measurement/MovePenalty.js"; -import { GridCoordinates3d } from "./measurement/grid_coordinates.js"; +import { GridCoordinates3d } from "./geometry/3d/GridCoordinates3d.js"; // Functions used to determine token speed colors. From e69f56f8547b07ccfb23baafc311e074d5b68d18 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 10:40:30 -0700 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=92=A1=20refactor|Ruler|Switch=20to?= =?UTF-8?q?=20mixed=20wrap=20of=20=5FanimateMovement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 42 ++++++++++++------------------------------ 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index c68556a..7fe9992 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -560,11 +560,11 @@ function _highlightMeasurementSegment(wrapped, segment) { // ----- NOTE: Token movement ----- // /** - * Mixed wrap Ruler.prototype._animateMovement + * Wrap Ruler.prototype._animateMovement * Add additional controlled tokens to the move, if permitted. */ async function _animateMovement(wrapped, token) { - if ( !this.segments || !this.segments.length ) return; // Ruler._animateMovement expects at least one segment. + if ( !this.segments || !this.segments.length ) return wrapped(token); // Ruler._animateMovement expects at least one segment. if ( CONFIG[MODULE_ID].debug ) { console.groupCollapsed(`${MODULE_ID}|_animateMovement`); @@ -651,45 +651,23 @@ function _canMove(wrapper, token) { } /** - * Override Ruler.prototype._animateSegment + * Wrap Ruler.prototype._animateSegment * When moving the token along the segments, update the token elevation to the destination + increment * for the given segment. * Mark the token update if pathfinding for this segment. */ async function _animateSegment(token, segment, destination) { log(`Updating ${token.name} destination from ({${token.document.x},${token.document.y}) to (${destination.x},${destination.y}) for segment (${segment.ray.A.x},${segment.ray.A.y})|(${segment.ray.B.x},${segment.ray.B.y})`); - - // If the segment is teleporting and the segment destination is not a waypoint or ruler destination, skip. - // Doesn't work because _animateMovement stops the movement if the token does not make it to the - // next waypoint. -// if ( segment.teleport -// && !(segment.B.x === this.destination.x && segment.B.y === this.destination.y ) -// && !this.waypoints.some(w => segment.B.x === w.x && segment.B.y === w.y) ) return; - - let name; - if ( segment.animation?.name === undefined ) name = token.animationName; - else name ||= Symbol(token.animationName); + destination.elevation = roundMultiple(CONFIG.GeometryLib.utils.pixelsToGridUnits(segment.ray.B.z)); const updateOptions = { - // terrainmapper: { usePath: false }, rulerSegment: this.segments.length > 1, firstRulerSegment: segment.first, - lastRulerSegment: segment.last, - rulerSegmentOrigin: segment.ray.A, - rulerSegmentDestination: segment.ray.B, - teleport: segment.teleport, - animation: {...segment.animation, name} + lastRulerSegment: segment.last } - const {x, y} = token.document._source; - await token.animate({x, y}, {name, duration: 0}); - await token.document.update(destination, updateOptions); - await CanvasAnimation.getAnimation(name)?.promise; - const newElevation = roundMultiple(CONFIG.GeometryLib.utils.pixelsToGridUnits(segment.ray.B.z)); - - if ( isFinite(newElevation) && token.elevationE !== newElevation ) await token.document.update({ elevation: newElevation }) + return wrapped(token, segment, destination); } - // ----- NOTE: Event handling ----- // /** @@ -797,6 +775,10 @@ PATCHES.BASIC.WRAPS = { _getMeasurementHistory, _createMeasurementHistory, + // Movement + _animateMovement, + _animateSegment, + // Wraps related to segments _getCostFunction, _getSegmentLabel, @@ -807,9 +789,9 @@ PATCHES.BASIC.WRAPS = { _onMoveKeyDown }; -PATCHES.BASIC.MIXES = { _animateMovement, _getMeasurementSegments, _broadcastMeasurement }; +PATCHES.BASIC.MIXES = { _getMeasurementSegments, _broadcastMeasurement }; -PATCHES.BASIC.OVERRIDES = { _animateSegment, _addWaypoint, _computeDistance }; +PATCHES.BASIC.OVERRIDES = { _addWaypoint, _computeDistance }; PATCHES.SPEED_HIGHLIGHTING.WRAPS = { _highlightMeasurementSegment }; From ad1c75e6b3cdd952b8b4cc494c7ba60a8782bff5 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 10:52:52 -0700 Subject: [PATCH 04/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Pathfinding|Undefined?= =?UTF-8?q?=20Settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pathfinding/BorderTriangle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/pathfinding/BorderTriangle.js b/scripts/pathfinding/BorderTriangle.js index 0d30544..1fcce5b 100644 --- a/scripts/pathfinding/BorderTriangle.js +++ b/scripts/pathfinding/BorderTriangle.js @@ -13,6 +13,7 @@ import { MODULE_ID } from "../const.js"; import { Draw } from "../geometry/Draw.js"; import { WallTracerEdge } from "./WallTracer.js"; import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; +import { Settings } from "../Settings.js"; const OTHER_DIRECTION = { ccw: "cw", From f9bcc6662a634cb1ac3f1517a8015813a338d303 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 10:53:34 -0700 Subject: [PATCH 05/15] =?UTF-8?q?=F0=9F=90=9B=20fix=20|Pathfinding|Use=20l?= =?UTF-8?q?owercase=20Settings.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/pathfinding/BorderTriangle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pathfinding/BorderTriangle.js b/scripts/pathfinding/BorderTriangle.js index 1fcce5b..7313aa8 100644 --- a/scripts/pathfinding/BorderTriangle.js +++ b/scripts/pathfinding/BorderTriangle.js @@ -13,7 +13,7 @@ import { MODULE_ID } from "../const.js"; import { Draw } from "../geometry/Draw.js"; import { WallTracerEdge } from "./WallTracer.js"; import { GridCoordinates3d } from "../geometry/3d/GridCoordinates3d.js"; -import { Settings } from "../Settings.js"; +import { Settings } from "../settings.js"; const OTHER_DIRECTION = { ccw: "cw", From bfb88b2a231786aaff46761309d038df531e2143 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 12:54:01 -0700 Subject: [PATCH 06/15] WIP|ElevationHandler|Cleanup for switch to libGeometry --- scripts/Ruler.js | 102 +++++++++++++++++++++++++++++------------------ scripts/geometry | 2 +- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 7fe9992..b7524fa 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -1,6 +1,5 @@ /* globals canvas, -CanvasAnimation, CONFIG, CONST, game, @@ -32,7 +31,6 @@ import { constructPathfindingSegments } from "./segments.js"; import { movementTypeForTokenAt } from "./token_hud.js"; import { - getPriorDistance, highlightLineRectangle, basicTextLabel, customizedTextLabel } from "./segment_labels_highlighting.js"; @@ -40,6 +38,7 @@ import { tokenSpeedSegmentSplitter } from "./token_speed.js"; import { log, roundMultiple } from "./util.js"; import { MovePenalty } from "./measurement/MovePenalty.js"; import { GridCoordinates3d } from "./geometry/3d/GridCoordinates3d.js"; +import { RegionMovementWaypoint3d } from "./geometry/3d/RegionMovementWaypoint3d.js"; /** * Modified Ruler @@ -133,7 +132,9 @@ function update(wrapper, data) { if ( !myData ) return wrapper(data); // Just in case. // Hide GM token ruler - if ( data.token && this.user.isGM && !game.user.isGM && Settings.get(Settings.KEYS.TOKEN_RULER.HIDE_GM)) return wrapper(data); + if ( data.token + && this.user.isGM + && !game.user.isGM && Settings.get(Settings.KEYS.TOKEN_RULER.HIDE_GM)) return wrapper(data); // Fix for displaying user elevation increments as they happen. const triggerMeasure = this._userElevationIncrements !== myData._userElevationIncrements; @@ -175,7 +176,7 @@ function _broadcastMeasurement(wrapped) { const elevationChanged = previewToken.document.elevation !== destElevation; if ( elevationChanged && isFinite(destElevation) ) { previewToken.document.elevation = destElevation; - previewToken.renderFlags.set({ "refreshTooltip": true }) + previewToken.renderFlags.set({ refreshTooltip: true }); } } } @@ -252,8 +253,8 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { const { width, height } = token.getSize(); const tl = token.document; return { - x: tl.x + width * 0.5, - y: tl.y + height * 0.5 + x: tl.x + (width * 0.5), + y: tl.y + (height * 0.5) }; } @@ -277,8 +278,8 @@ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { const { width, height } = token.getSize(); const tl = snap ? token._preview.getSnappedPosition(token._preview.document) : token._preview.document; return { - x: tl.x + width * 0.5, - y: tl.y + height * 0.5 + x: tl.x + (width * 0.5), + y: tl.y + (height * 0.5) }; } @@ -317,7 +318,7 @@ function _getMeasurementSegments(wrapped) { // Make sure the style is cloned for each segment b/c we are adjusting the size. if ( Settings.get(Settings.KEYS.LABELING.SCALE_TEXT) ) { - segments.forEach(segment => segment.label.style = segment.label.style.clone()) + segments.forEach(segment => segment.label.style = segment.label.style.clone()); } const segmentMap = this._pathfindingSegmentMap ??= new Map(); @@ -337,39 +338,61 @@ function _getMeasurementSegments(wrapped) { let pathPoints = []; const t0 = performance.now(); const lastSegment = segments.at(-1); + if ( CONFIG[MODULE_ID].debug ) console.groupCollapsed(`${MODULE_ID}|_getMeasurementSegments`); if ( usePathfinding ) { // If currently pathfinding, set path for the last segment, overriding any prior path. // Pathfinding when: the pathfinding icon is enabled or the temporary toggle key is held. // TODO: Pathfinding should account for region elevation changes and handle flying/burrowing. pathPoints = calculatePathPointsForSegment(lastSegment, token); + } + + if ( MODULES_ACTIVE.TERRAIN_MAPPER ) { + const t0 = performance.now(); + // For now, determine movement type for each of the path points. PathPoints are {x, y} objects. + // If no path points, use the segments. + const initialPath = pathPoints.map(pt => RegionMovementWaypoint3d.fromObject(pt)); + + // For now, set the initial path to the elevation of the last segment. + if ( pathPoints.length ) { + initialPath.forEach(pt => pt.z = lastSement.ray.A.z); + initialPath.at(-1).z = lastSegment.ray.B.z; + } else initialPath.push( + RegionMovementWaypoint3d.fromObject(lastSegment.ray.A), + RegionMovementWaypoint3d.fromObject(lastSegment.ray.B) + ); + - } else if ( MODULES_ACTIVE.TERRAIN_MAPPER ){ // Determine the region path. + pathPoints.length = 0; const ElevationHandler = MODULES_ACTIVE.API.TERRAIN_MAPPER.ElevationHandler; - const { gridUnitsToPixels, pixelsToGridUnits } = CONFIG.GeometryLib.utils; - const { A, B } = lastSegment.ray; - const start = { ...A, elevation: pixelsToGridUnits(A.z) }; - const end = { ...B, elevation: pixelsToGridUnits(B.z) }; - const movementTypeStart = movementTypeForTokenAt(token, A); - const endGround = terrainElevationAtLocation(end, end.elevation); - const movementTypeEnd = MOVEMENT_TYPES.forCurrentElevation(end.elevation, endGround); - const flying = movementTypeStart === MOVEMENT_TYPES.FLY || movementTypeEnd === MOVEMENT_TYPES.FLY; - const burrowing = movementTypeStart === MOVEMENT_TYPES.BURROW || movementTypeEnd === MOVEMENT_TYPES.BURROW; - const pathPoints = ElevationHandler.constructPath(start, end, { flying, burrowing, token }); - pathPoints.forEach(pt => pt.z = gridUnitsToPixels(pt.elevation)); + let prevPt = initialPath[0]; + pathPoints.push(prevPt); + for ( let i = 1, n = initialPath.length; i < n; i += 1 ) { + const nextPt = initialPath[1]; + const movementTypeStart = movementTypeForTokenAt(token, prevPt); + const endGround = terrainElevationAtLocation(nextPt, nextPt.elevation); + const movementTypeEnd = MOVEMENT_TYPES.forCurrentElevation(nextPt.elevation, endGround); + const flying = movementTypeStart === MOVEMENT_TYPES.FLY || movementTypeEnd === MOVEMENT_TYPES.FLY; + const burrowing = movementTypeStart === MOVEMENT_TYPES.BURROW || movementTypeEnd === MOVEMENT_TYPES.BURROW; + const subPath = ElevationHandler.constructPath(prevPt, nextPt, { flying, burrowing, token }); + pathPoints.push(...subPath); // PathPoints is a non-dupes array, so duplicate start/ends will be dropped. + } + const t1 = performance.now(); + log(`Found terrain path with ${pathPoints.length} points in ${t1-t0} ms.`); } const t1 = performance.now(); const key = `${lastSegment.ray.A.key}|${lastSegment.ray.B.key}`; if ( pathPoints.length > 2 ) { segmentMap.set(key, pathPoints); - log(`_getMeasurementSegments|Found path with ${pathPoints.length} points in ${t1-t0} ms.`, pathPoints); + log(`Found path with ${pathPoints.length} points in ${t1-t0} ms.`, pathPoints); } else segmentMap.delete(key); // For each segment, replace with path sub-segment if pathfinding or region paths were used for that segment. const t2 = performance.now(); const newSegments = constructPathfindingSegments(segments, segmentMap); const t3 = performance.now(); - log(`_getMeasurementSegments|${newSegments.length} segments processed in ${t3-t2} ms.`); + log(`${newSegments.length} segments processed in ${t3-t2} ms.`); + if ( CONFIG[MODULE_ID].debug ) console.groupEnd(`${MODULE_ID}|_getMeasurementSegments`); return newSegments; } @@ -456,8 +479,9 @@ function _computeDistance() { segment.waypoint.distance = waypointDistance += segment.distance; segment.waypoint.cost = waypointCost += segment.cost; segment.waypoint.offsetDistance = waypointOffsetDistance += segment.offsetDistance; - if ( ~currWaypointIdx ) segment.waypoint.elevationIncrement = userElevationChangeAtWaypoint(this.waypoints[currWaypointIdx]); - + if ( ~currWaypointIdx ) { + segment.waypoint.elevationIncrement = userElevationChangeAtWaypoint(this.waypoints[currWaypointIdx]); + } } } @@ -478,7 +502,7 @@ function _getCostFunction() { if ( !(currOffset instanceof GridCoordinates3d) ) currOffset = GridCoordinates3d.fromOffset(currOffset); const penalty = movePenaltyInstance.movementPenaltyForSegment(prevOffset, currOffset); return offsetDistance * penalty; - } + }; } @@ -504,7 +528,9 @@ function _getSegmentLabel(wrapped, segment) { this.totalDistance = origTotalDistance; if ( Settings.get(Settings.KEYS.LABELING.SCALE_TEXT) ) { - segment.label.style.fontSize = Math.round(CONFIG.canvasTextStyle.fontSize * (canvas.dimensions.size / 100) * CONFIG[MODULE_ID].labeling.textScale); + segment.label.style.fontSize = Math.round(CONFIG.canvasTextStyle.fontSize + * (canvas.dimensions.size / 100) + * CONFIG[MODULE_ID].labeling.textScale); } if ( !segment.label.style.fontFamily.includes("fontAwesome") ) segment.label.style.fontFamily += ",fontAwesome"; @@ -513,9 +539,6 @@ function _getSegmentLabel(wrapped, segment) { return basicTextLabel(this, segment, origLabel); } - - - /** * Wrap Ruler.prototype._highlightMeasurementSegment * @param {RulerMeasurementSegment} segment @@ -531,7 +554,9 @@ function _highlightMeasurementSegment(wrapped, segment) { // Adjust the color if this user has selected speed highlighting. // Highlight each split in turn, changing highlight color each time. if ( Settings.useSpeedHighlighting(this.token) ) { - if ( segment === this.segments[0] ) TOKEN_SPEED_SPLITTER.set(this.token, tokenSpeedSegmentSplitter(this, this.token)) + if ( segment === this.segments[0] ) { + TOKEN_SPEED_SPLITTER.set(this.token, tokenSpeedSegmentSplitter(this, this.token)); + } const splitterFn = TOKEN_SPEED_SPLITTER.get(this.token); if ( splitterFn ) { const priorColor = this.color; @@ -583,7 +608,7 @@ async function _animateMovement(wrapped, token) { this.segments.forEach((s, idx) => s.idx = idx); - //_recalculateOffset.call(this, token); + // _recalculateOffset.call(this, token); const promises = [wrapped(token)]; for ( const controlledToken of canvas.tokens.controlled ) { if ( controlledToken === token ) continue; @@ -656,15 +681,16 @@ function _canMove(wrapper, token) { * for the given segment. * Mark the token update if pathfinding for this segment. */ -async function _animateSegment(token, segment, destination) { +async function _animateSegment(wrapped, token, segment, destination, updateOptions = {}) { log(`Updating ${token.name} destination from ({${token.document.x},${token.document.y}) to (${destination.x},${destination.y}) for segment (${segment.ray.A.x},${segment.ray.A.y})|(${segment.ray.B.x},${segment.ray.B.y})`); destination.elevation = roundMultiple(CONFIG.GeometryLib.utils.pixelsToGridUnits(segment.ray.B.z)); - const updateOptions = { + foundry.utils.mergeObject(updateOptions, { rulerSegment: this.segments.length > 1, firstRulerSegment: segment.first, lastRulerSegment: segment.last - } - return wrapped(token, segment, destination); + }); + + return wrapped(token, segment, destination, updateOptions); } @@ -703,7 +729,8 @@ function _onDragStart(wrapped, event, { isTokenDrag = false } = {}) { * @param {KeyboardEventContext} context */ function _onMoveKeyDown(wrapped, context) { - const teleportKeys = new Set(game.keybindings.get(MODULE_ID, Settings.KEYBINDINGS.TELEPORT).map(binding => binding.key)); + const teleportKeys = new Set(game.keybindings.get(MODULE_ID, Settings.KEYBINDINGS.TELEPORT).map(binding => + binding.key)); if ( teleportKeys.intersects(game.keyboard.downKeys) ) this.segments.forEach(s => s.teleport = true); wrapped(context); } @@ -748,7 +775,6 @@ function decrementElevation() { // Broadcast the activity (see ControlsLayer.prototype._onMouseMove) this._broadcastMeasurement(); - // game.user.broadcastActivity({ ruler: ruler.toJSON() }); } /** diff --git a/scripts/geometry b/scripts/geometry index 3b9097a..349b2fc 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit 3b9097a227f6aa17e4acf0fca27cdaecb756e1e7 +Subproject commit 349b2fcf3a9a5a62c2c4173e1e3c2d78cccbd16b From deef03a392d943f950b411f49e8d4e3353f792de Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 14:17:15 -0700 Subject: [PATCH 07/15] =?UTF-8?q?=F0=9F=90=9B=20fix|libGeometry|Point3d=20?= =?UTF-8?q?not=20defined?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/geometry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/geometry b/scripts/geometry index 349b2fc..da99916 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit 349b2fcf3a9a5a62c2c4173e1e3c2d78cccbd16b +Subproject commit da99916d45bda02523a87722fc87632027e73971 From 8c168801cd25f857742e8837c845013b0fa942b2 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 16:29:36 -0700 Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=90=9B=20fix|libGeometry|Fixes=20fo?= =?UTF-8?q?r=20cutaway=20with=20libGeometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 3 ++- scripts/geometry | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index b7524fa..ab2221a 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -375,7 +375,8 @@ function _getMeasurementSegments(wrapped) { const flying = movementTypeStart === MOVEMENT_TYPES.FLY || movementTypeEnd === MOVEMENT_TYPES.FLY; const burrowing = movementTypeStart === MOVEMENT_TYPES.BURROW || movementTypeEnd === MOVEMENT_TYPES.BURROW; const subPath = ElevationHandler.constructPath(prevPt, nextPt, { flying, burrowing, token }); - pathPoints.push(...subPath); // PathPoints is a non-dupes array, so duplicate start/ends will be dropped. + subPath.shift(); // Remove prevPt from the array. + pathPoints.push(...subPath); } const t1 = performance.now(); log(`Found terrain path with ${pathPoints.length} points in ${t1-t0} ms.`); diff --git a/scripts/geometry b/scripts/geometry index da99916..5ebbe7d 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit da99916d45bda02523a87722fc87632027e73971 +Subproject commit 5ebbe7de4a6edaed2cb63387b4d85d6c034f68eb From 181dd671ada0cce40a87de365342d66ae038da21 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 27 Aug 2024 17:18:02 -0700 Subject: [PATCH 09/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Pathfinding|Path=20not?= =?UTF-8?q?=20displaying=20after=20the=20first=20segment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index ab2221a..15089e3 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -353,8 +353,8 @@ function _getMeasurementSegments(wrapped) { const initialPath = pathPoints.map(pt => RegionMovementWaypoint3d.fromObject(pt)); // For now, set the initial path to the elevation of the last segment. - if ( pathPoints.length ) { - initialPath.forEach(pt => pt.z = lastSement.ray.A.z); + if ( initialPath.length ) { + initialPath.forEach(pt => pt.z = lastSegment.ray.A.z); initialPath.at(-1).z = lastSegment.ray.B.z; } else initialPath.push( RegionMovementWaypoint3d.fromObject(lastSegment.ray.A), @@ -368,7 +368,7 @@ function _getMeasurementSegments(wrapped) { let prevPt = initialPath[0]; pathPoints.push(prevPt); for ( let i = 1, n = initialPath.length; i < n; i += 1 ) { - const nextPt = initialPath[1]; + const nextPt = initialPath[i]; const movementTypeStart = movementTypeForTokenAt(token, prevPt); const endGround = terrainElevationAtLocation(nextPt, nextPt.elevation); const movementTypeEnd = MOVEMENT_TYPES.forCurrentElevation(nextPt.elevation, endGround); @@ -377,6 +377,7 @@ function _getMeasurementSegments(wrapped) { const subPath = ElevationHandler.constructPath(prevPt, nextPt, { flying, burrowing, token }); subPath.shift(); // Remove prevPt from the array. pathPoints.push(...subPath); + prevPt = nextPt; } const t1 = performance.now(); log(`Found terrain path with ${pathPoints.length} points in ${t1-t0} ms.`); From e1e1477a137f886b15bc2a30c92f5778f0fb414f Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 08:56:29 -0700 Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=90=9B=20fix=20|TokenRuler|Fix=20fo?= =?UTF-8?q?r=20position=20not=20updating=20for=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For user, the cloned token stops moving when it hits an obstacle. Use the original point position to determine the position. --- scripts/Ruler.js | 27 +++++++++++++++++++++------ scripts/Token.js | 3 +++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 15089e3..ee32bbe 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -249,6 +249,8 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { const token = this.token; if ( !this._isTokenRuler || !token ) return point; + console.log(`_getMeasurementOrigin|difference: ${token.document.x - point.x},${token.document.y - point.y}`); + // Shift to token center const { width, height } = token.getSize(); const tl = token.document; @@ -269,18 +271,31 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { * @protected */ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { + console.log(`_getMeasurementDestination|point ${point.x},${point.y}`); + const origPoint = PIXI.Point.fromObject(point); + point = wrapped(point, { snap }); const token = this.token; if ( !this._isTokenRuler || !token ) return point; if ( !token._preview ) return point; // Shift to token center or snapped center - const { width, height } = token.getSize(); - const tl = snap ? token._preview.getSnappedPosition(token._preview.document) : token._preview.document; - return { - x: tl.x + (width * 0.5), - y: tl.y + (height * 0.5) - }; + console.log(`_getMeasurementDestination|document: ${token._preview.document.x},${token._preview.document.y} vs point ${point.x},${point.y}`); + console.log(`_getMeasurementDestination|difference: ${token._preview.document.x - point.x},${token._preview.document.y - point.y}`); + if ( !snap ) return point; + + // See Token#_onDragLeftMove. + const origin = token.getCenterPoint(); + const delta = origPoint.subtract(origin, PIXI.Point._tmp); + let position = PIXI.Point._tmp2.copyFrom(token.document).add(delta, PIXI.Point._tmp2); + const tlSnapped = token._preview.getSnappedPosition(position); + return token.getCenterPoint(tlSnapped); + + +// const delta = PIXI.Point._tmp.copyFrom(token.center).subtract(token.document) +// const tl = PIXI.Point._tmp2.copyFrom(point).subtract(delta); +// const tlSnapped = token._preview.getSnappedPosition(tl); // Snaps to nearest left corner. +// return token.getCenterPoint(tlSnapped); } // ----- NOTE: Segments ----- // diff --git a/scripts/Token.js b/scripts/Token.js index accd369..0912554 100644 --- a/scripts/Token.js +++ b/scripts/Token.js @@ -153,6 +153,9 @@ function _onDragLeftMove(wrapped, event) { log("Token#_onDragLeftMove"); wrapped(event); + const c = event.interactionData.clones[0]; + console.log(`_onDragLeftMove|Destination ${event.interactionData.destination.x},${event.interactionData.destination.y}, clone ${c.document.x},${c.document.y}`); + // Continue a Ruler measurement. if ( !Settings.get(Settings.KEYS.TOKEN_RULER.ENABLED) ) return; const ruler = canvas.controls.ruler; From fb705c028332c2ede450ffec1cb4bd8b9fc2fdf6 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 08:59:40 -0700 Subject: [PATCH 11/15] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20perf|TokenRuler|Use?= =?UTF-8?q?=20token.getCenterPoint=20for=20origin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index ee32bbe..7baeb9f 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -248,16 +248,9 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { point = wrapped(point, { snap }); const token = this.token; if ( !this._isTokenRuler || !token ) return point; - + return token.getCenterPoint(); console.log(`_getMeasurementOrigin|difference: ${token.document.x - point.x},${token.document.y - point.y}`); - // Shift to token center - const { width, height } = token.getSize(); - const tl = token.document; - return { - x: tl.x + (width * 0.5), - y: tl.y + (height * 0.5) - }; } /** From 73a069c8391c55f054f07d979f673539eeec2758 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 08:59:56 -0700 Subject: [PATCH 12/15] =?UTF-8?q?=F0=9F=92=8D=20test|TokenRuler|Remove=20d?= =?UTF-8?q?ebugging=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/Ruler.js | 11 ----------- scripts/Token.js | 3 --- 2 files changed, 14 deletions(-) diff --git a/scripts/Ruler.js b/scripts/Ruler.js index 7baeb9f..606725f 100644 --- a/scripts/Ruler.js +++ b/scripts/Ruler.js @@ -249,8 +249,6 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { const token = this.token; if ( !this._isTokenRuler || !token ) return point; return token.getCenterPoint(); - console.log(`_getMeasurementOrigin|difference: ${token.document.x - point.x},${token.document.y - point.y}`); - } /** @@ -264,7 +262,6 @@ function _getMeasurementOrigin(wrapped, point, {snap=true}={}) { * @protected */ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { - console.log(`_getMeasurementDestination|point ${point.x},${point.y}`); const origPoint = PIXI.Point.fromObject(point); point = wrapped(point, { snap }); @@ -273,8 +270,6 @@ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { if ( !token._preview ) return point; // Shift to token center or snapped center - console.log(`_getMeasurementDestination|document: ${token._preview.document.x},${token._preview.document.y} vs point ${point.x},${point.y}`); - console.log(`_getMeasurementDestination|difference: ${token._preview.document.x - point.x},${token._preview.document.y - point.y}`); if ( !snap ) return point; // See Token#_onDragLeftMove. @@ -283,12 +278,6 @@ function _getMeasurementDestination(wrapped, point, {snap=true}={}) { let position = PIXI.Point._tmp2.copyFrom(token.document).add(delta, PIXI.Point._tmp2); const tlSnapped = token._preview.getSnappedPosition(position); return token.getCenterPoint(tlSnapped); - - -// const delta = PIXI.Point._tmp.copyFrom(token.center).subtract(token.document) -// const tl = PIXI.Point._tmp2.copyFrom(point).subtract(delta); -// const tlSnapped = token._preview.getSnappedPosition(tl); // Snaps to nearest left corner. -// return token.getCenterPoint(tlSnapped); } // ----- NOTE: Segments ----- // diff --git a/scripts/Token.js b/scripts/Token.js index 0912554..accd369 100644 --- a/scripts/Token.js +++ b/scripts/Token.js @@ -153,9 +153,6 @@ function _onDragLeftMove(wrapped, event) { log("Token#_onDragLeftMove"); wrapped(event); - const c = event.interactionData.clones[0]; - console.log(`_onDragLeftMove|Destination ${event.interactionData.destination.x},${event.interactionData.destination.y}, clone ${c.document.x},${c.document.y}`); - // Continue a Ruler measurement. if ( !Settings.get(Settings.KEYS.TOKEN_RULER.ENABLED) ) return; const ruler = canvas.controls.ruler; From e904b6503f5a547ace29830037e01af81d99eb4f Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 09:31:01 -0700 Subject: [PATCH 13/15] =?UTF-8?q?=F0=9F=90=9B=20fix|Measurement|Gridless?= =?UTF-8?q?=20offset=20using=20pixel,=20not=20grid=20coordinates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/measurement/Grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/measurement/Grid.js b/scripts/measurement/Grid.js index dd06860..9c13cdb 100644 --- a/scripts/measurement/Grid.js +++ b/scripts/measurement/Grid.js @@ -327,7 +327,7 @@ function _measurePath(wrapped, waypoints, { cost }, result) { cost ??= (prevOffset, currOffset, offsetDistance) => offsetDistance; switch ( canvas.grid.type ) { case CONST.GRID_TYPES.GRIDLESS: - offsetDistanceFn = Point3d.distanceBetween; + offsetDistanceFn = (a, b) => CONFIG.GeometryLib.utils.pixelsToGridUnits(Point3d.distanceBetween(a, b)); break; case CONST.GRID_TYPES.SQUARE: offsetDistanceFn = singleOffsetSquareDistanceFn(diagonals); From 90b7d33205e63ae2e77a0034e23188935796ce5b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 09:31:36 -0700 Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=A4=96=20chore|libGeometry|Update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/geometry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/geometry b/scripts/geometry index 5ebbe7d..ca4d31f 160000 --- a/scripts/geometry +++ b/scripts/geometry @@ -1 +1 @@ -Subproject commit 5ebbe7de4a6edaed2cb63387b4d85d6c034f68eb +Subproject commit ca4d31f0266d32f52927fd7e752b2fd4477eb156 From 23470743dddfcd6c063c5196ce5bcc4b7aff08e9 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 28 Aug 2024 10:09:51 -0700 Subject: [PATCH 15/15] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20docs|Changelog|Add?= =?UTF-8?q?=20v0.10.4=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index 32edbaf..83ba9dc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,9 @@ +# 0.10.4 +Compatibility with Terrain Mapper 0.4.1. +Update Polish translation. Thanks @Lioheart! Closes #184. Update Brazilian Portugese translation. Thanks @Kharmans! +Fix for terrain cost measuring in pixel units instead of grid units for gridless maps. +Fix pathfinding failing for users once the preview token stops moving. Closes #138. + # 0.10.3 Fix for the ruler label blowing up the font size on gridless and hex scenes. Fix for gridless measuring in pixel units instead of grid units.