From e169de3357f722e57206c672e0670cd9f7199de2 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 07:23:06 -0700 Subject: [PATCH 01/22] Switch to libRuler 0.1 - Change Segment class to RulerSegment - Use static RulerSegment.CalculateDistance - Remove distance function; now handled by libRuler - Handle array of physical path points, updating each with elevation. --- scripts/patching.js | 9 ++-- scripts/segments.js | 126 +++++++------------------------------------- 2 files changed, 23 insertions(+), 112 deletions(-) diff --git a/scripts/patching.js b/scripts/patching.js index 6541eb7..17dcbc4 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -2,21 +2,18 @@ import { MODULE_ID, log } from "./module.js"; import { elevationRulerClear, elevationRulerAddWaypoint, elevationRulerRemoveWaypoint, - elevationRulerAnimateToken } from "./ruler.js"; import { elevationRulerAddProperties, elevationRulerConstructPhysicalPath, - elevationRulerDistanceFunction, elevationRulerGetText } from "./segments.js"; export function registerRuler() { // segment methods (for measuring) - libWrapper.register(MODULE_ID, 'window.libRuler.Segment.prototype.addProperties', elevationRulerAddProperties, 'WRAPPER'); - libWrapper.register(MODULE_ID, 'window.libRuler.Segment.prototype.constructPhysicalPath', elevationRulerConstructPhysicalPath, 'WRAPPER'); - libWrapper.register(MODULE_ID, 'window.libRuler.Segment.prototype.distanceFunction', elevationRulerDistanceFunction, 'WRAPPER'); - libWrapper.register(MODULE_ID, 'window.libRuler.Segment.prototype.text', elevationRulerGetText, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.addProperties', elevationRulerAddProperties, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.constructPhysicalPath', elevationRulerConstructPhysicalPath, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.text', elevationRulerGetText, 'WRAPPER'); // move token methods libWrapper.register(MODULE_ID, 'Ruler.prototype.animateToken', elevationRulerAnimateToken, 'WRAPPER'); diff --git a/scripts/segments.js b/scripts/segments.js index 1707839..bc81044 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -142,55 +142,35 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { // destination // Need to apply canvas.scene.data.grid (140) and canvas.scene.data.gridDistance (5) // 7350 (x1) - 6930 (x0) = 420 (delta_x) / 140 * 5 = move in canvas units (e.g. 15') - - // will need to address later if there are multiple points in the physical path, rather - // than just origin and destination... const elevation_delta = ending_elevation_grid_units - starting_elevation_grid_units; const ruler_distance = this.ray.distance; - // destination - const simple_path_distance = CalculateDistance(default_path.origin, default_path.destination); - const ratio = simple_path_distance / ruler_distance; - default_path.destination.z = starting_elevation_grid_units + elevation_delta * ratio; - - // origin - default_path.origin.z = starting_elevation_grid_units; + // Assume there could be multiple points in the path, represented by an array. + // The starting point must have the pre-determined starting elevation. + // The ending point must have the calculated elevation. + // Any points in-between should have a ratio unless they already have a z dimension. + const last_idx = default_path.length - 1; + default_path.map((p, idx, arr) => { + if(idx === 0) { + // origin + p.z = starting_elevation_grid_units; + return p; + } else if(idx === last_idx || !("z" in p)) { + // destination or intermediate p without a z dimension. + const p_origin = arr[idx - 1]; + const simple_path_distance = RulerSegment.CalculateDistance(p_origin, p); + const ratio = simple_path_distance / ruler_distance; + p.z = starting_elevation_grid_units + elevation_delta * ratio; + } + + return p; + }); log("Default path", default_path); return default_path; } -export function elevationRulerDistanceFunction(wrapped, physical_path) { - // Project the 3-D path to 2-D canvas - log(`Projecting physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y}, ${physical_path.origin.z} - to dest ${physical_path.destination.x}, ${physical_path.destination.y}, ${physical_path.destination.z}`); - - // for each of the points, construct a 2-D path and send to the underlying function - // will need to address later if there are multiple points in the physical path, rather - // than just origin and destination... - - physical_path.destination = ProjectElevatedPoint(physical_path.origin, physical_path.destination); - delete physical_path.origin.z; - delete physical_path.destination.z; - - // if we are using grid spaces, the destination needs to be re-centered to the grid. - // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent - // depending on cardinality of the move, as rounding will increase/decrease to the nearest gridspace - if(this.measure_distance_options?.gridSpaces) { - // canvas.grid.getCenter returns an array [x, y]; - const snapped = canvas.grid.getCenter(physical_path.destination.x, physical_path.destination.y); - log(`Snapping ${physical_path.destination.x}, ${physical_path.destination.y} to ${snapped[0]}, ${snapped[1]}`); - physical_path.destination = { x: snapped[0], y: snapped[1] }; - } - - - log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} - to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); - - return wrapped(physical_path); -} - /* * @param {number} segmentDistance * @param {number} totalDistance @@ -414,69 +394,3 @@ function checkForHole(intersectionPT, zz) { } -// ----- MATH FOR MEASURING ELEVATION DISTANCE ----- // -/** - * Calculate a new point by projecting the elevated point back onto the 2-D surface - * If the movement on the plane is represented by moving from point A to point B, - * and you also move 'height' distance orthogonal to the plane, the distance is the - * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. - * Project by rotating the vertical triangle 90º, then calculate the new point C. - * - * Cx = { height * (By - Ay) / dist(A to B) } + Bx - * Cy = { height * (Bx - Ax) / dist(A to B) } + By - * @param {{x: number, y: number}} A - * @param {{x: number, y: number}} B - */ -function ProjectElevatedPoint(A, B) { - const height = B.z - A.z; - const distance = CalculateDistance(A, B); - const projected_x = B.x + ((height / distance) * (A.y - B.y)); - const projected_y = B.y - ((height / distance) * (A.x - B.x)); - - return new PIXI.Point(projected_x, projected_y); -} - -function CalculateDistance(A, B) { - const dx = B.x - A.x; - const dy = B.y - A.y; - return Math.hypot(dy, dx); -} - -// console.log(Math.hypot(3, 4)); -// // expected output: 5 -// -// console.log(Math.hypot(5, 12)); -// // expected output: 13 -// -// let m; -// let o = {x:0, y:0} -// m = ProjectElevatedPoint(o, {x:1, y:0}, 1); -// CalculateDistance(o, m) // 1.414 -// -// m = ProjectElevatedPoint(o, {x:3, y:0}, 4); -// CalculateDistance(o, m) // 5 -// -// m = ProjectElevatedPoint(o, {x:0, y:3}, 4); -// CalculateDistance(o, m) // 5 -// -// m = ProjectElevatedPoint(o, {x:0, y:3}, 4); - -// m = distance -// n = height -// A = origin () -// B = destination (1) -// C = destination with height (2) -// |Ay - By| / m = |Bx - Cx| / n -// |Ax - Bx| / m = |Cy - By| / n -// -// |Bx - Cx| / n = |Ay - By| / m -// |Cy - By| / n = |Ax - Bx| / m -// -// |Bx - Cx| = |Ay - By| * n/m -// |Cy - By| = |Ax - Bx| * n/m -// -// Bx - Cx = ± n/m * (Ay - By) -// Cy - By = ± n/m * (Ax - Bx) -// -// Cx = Bx ± n/m * (Ay - By) -// Cy = By ± n/m * (Ax - Bx) From 6fad33a0c569fbc9c0601139ecd2add593f3c6b2 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 07:23:22 -0700 Subject: [PATCH 02/22] Update changelog for 0.3.0 --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index befed3e..7b325cf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,9 @@ +# 0.3.0 +Move to libRuler 0.1 compatibility. +Breaking changes due to libRuler changes: +- `Segment` class is now `RulerSegment` +- libRuler now handles the measurement from a 3-D path; Elevation Ruler only creates the path. + # 0.2.4 Minor update with additional checks on presence of elevation increment flag. Additional logging. From 5ca1678fa6d118b200143bddf4fbafd2fb906161 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 08:50:31 -0700 Subject: [PATCH 03/22] fixes to measuring 3-d distance --- scripts/segments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/segments.js b/scripts/segments.js index bc81044..27a39dd 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -158,7 +158,7 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { } else if(idx === last_idx || !("z" in p)) { // destination or intermediate p without a z dimension. const p_origin = arr[idx - 1]; - const simple_path_distance = RulerSegment.CalculateDistance(p_origin, p); + const simple_path_distance = this.calculateDistance(p_origin, p); const ratio = simple_path_distance / ruler_distance; p.z = starting_elevation_grid_units + elevation_delta * ratio; } From 832746e94943a47dec023272543c62a7daf37fc6 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 12:33:14 -0700 Subject: [PATCH 04/22] .1-compatibility: Auto stash before merge of "feature/libruler-0.1-compatibility" and "origin/feature/libruler-0.1-compatibility" --- scripts/segments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/segments.js b/scripts/segments.js index 27a39dd..4d3c6be 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -158,7 +158,7 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { } else if(idx === last_idx || !("z" in p)) { // destination or intermediate p without a z dimension. const p_origin = arr[idx - 1]; - const simple_path_distance = this.calculateDistance(p_origin, p); + const simple_path_distance = window.libRuler.RulerUtilities.calculateDistance(p_origin, p); const ratio = simple_path_distance / ruler_distance; p.z = starting_elevation_grid_units + elevation_delta * ratio; } From 3a76776ddd5c486a9bda24acab6953b5df65c95d Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 14:42:58 -0700 Subject: [PATCH 05/22] Remove old code, log token move. --- scripts/ruler.js | 151 +---------------------------------------------- 1 file changed, 1 insertion(+), 150 deletions(-) diff --git a/scripts/ruler.js b/scripts/ruler.js index 078e4db..2b9bcfa 100644 --- a/scripts/ruler.js +++ b/scripts/ruler.js @@ -22,156 +22,6 @@ import { ElevationAtPoint, toGridDistance } from "./segments.js"; // wrapping the constructor appears not to work. // see https://github.com/ruipin/fvtt-lib-wrapper/issues/14 - -/** - * Calculate a new point by projecting the elevated point back onto the 2-D surface - * If the movement on the plane is represented by moving from point A to point B, - * and you also move 'height' distance orthogonal to the plane, the distance is the - * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. - * Project by rotating the vertical triangle 90º, then calculate the new point C. - * - * Cx = { height * (By - Ay) / dist(A to B) } + Bx - * Cy = { height * (Bx - Ax) / dist(A to B) } + By - * @param {{x: number, y: number}} A - * @param {{x: number, y: number}} B - */ - - - -function projectElevatedPoint(A, B, height) { - const distance = CalculateDistance(A, B); - const projected_x = B.x + ((height / distance) * (A.y - B.y)); - const projected_y = B.y - ((height / distance) * (A.x - B.x)); - - return new PIXI.Point(projected_x, projected_y); -} - -Object.defineProperty(Ruler.prototype, "projectElevatedPoint", { - value: projectElevatedPoint, - writable: true, - configurable: true -}); - -function CalculateDistance(A, B) { - const dx = B.x - A.x; - const dy = B.y - A.y; - return Math.hypot(dy, dx); -} - -// console.log(Math.hypot(3, 4)); -// // expected output: 5 -// -// console.log(Math.hypot(5, 12)); -// // expected output: 13 -// -// let m; -// let o = {x:0, y:0} -// m = ProjectElevatedPoint(o, {x:1, y:0}, 1); -// CalculateDistance(o, m) // 1.414 -// -// m = ProjectElevatedPoint(o, {x:3, y:0}, 4); -// CalculateDistance(o, m) // 5 -// -// m = ProjectElevatedPoint(o, {x:0, y:3}, 4); -// CalculateDistance(o, m) // 5 -// -// m = ProjectElevatedPoint(o, {x:0, y:3}, 4); - -// m = distance -// n = height -// A = origin () -// B = destination (1) -// C = destination with height (2) -// |Ay - By| / m = |Bx - Cx| / n -// |Ax - Bx| / m = |Cy - By| / n -// -// |Bx - Cx| / n = |Ay - By| / m -// |Cy - By| / n = |Ax - Bx| / m -// -// |Bx - Cx| = |Ay - By| * n/m -// |Cy - By| = |Ax - Bx| * n/m -// -// Bx - Cx = ± n/m * (Ay - By) -// Cy - By = ± n/m * (Ax - Bx) -// -// Cx = Bx ± n/m * (Ay - By) -// Cy = By ± n/m * (Ax - Bx) - - - - - -// will need to update measuring to account for elevation -export function elevationRulerMeasure(wrapped, destination, {gridSpaces=true}={}) { - log("we are measuring!"); - log(`${this.waypoints.length} waypoints. ${this.destination_elevation_increment} elevation increments for destination. ${this.elevation_increments.length} elevation waypoints.`, this.elevation_increments); - - // if no elevation present, go with original function. - if(!this.destination_elevation_increment && - (!this.elevation_increments || - this.elevation_increments.every(i => i === 0))) { - - log("Using original measure"); - return wrapped(destination, gridSpaces); - } - - // Mostly a copy from Ruler.measure, but adding in distance for elevation - // Original segments need to be retained so that the displayed path is correct. - // But the distances need to be modified to account for segment elevation. - // Project the elevated point back to the 2-D space, using a rotated right triangle. - // See, e.g. https://math.stackexchange.com/questions/927802/how-to-find-coordinates-of-3rd-vertex-of-a-right-angled-triangle-when-everything - - destination = new PIXI.Point(...canvas.grid.getCenter(destination.x, destination.y)); - const waypoints = this.waypoints.concat([destination]); - const waypoints_elevation = this.elevation_increments.concat([this.destination_elevation_increment]); - - const r = this.ruler; - this.destination = destination; - - log("Measure ruler", r); - - // Iterate over waypoints and construct segment rays - // Also create elevation segments, adjusting segments for elevation - // waypoint 0 is added as the origin (see _onDragStart) - // so elevation_waypoint 0 should also be the origin, and so 0 - // the for loop uses the next waypoint as destination. - // for loop will count from 0 to waypoints.length - 1 - - const segments = []; - const elevation_segments = []; - for ( let [i, dest] of waypoints.slice(1).entries() ) { - log(`Processing waypoint ${i}`, dest); - - const origin = waypoints[i]; - const label = this.labels.children[i]; - const ray = new Ray(origin, dest); - - // first waypoint is origin; elevation increment is 0. - // need to account for units of the grid - // canvas.scene.data.grid e.g. 140; canvas.scene.data.gridDistance e.g. 5 - const elevation = waypoints_elevation[i + 1] * canvas.scene.data.grid; - log("Origin", origin); - log("Destination", dest); - log(`Elevation ${elevation} for i = ${i}.`); - - - const elevated_dest = this.projectElevatedPoint(origin, dest, elevation); - const ray_elevated = new Ray(origin, elevated_dest); - - log("Elevated_dest", elevated_dest); - log("Ray", ray); - log("Elevated Ray", ray_elevated); - - if ( ray_elevated.distance < 10 ) { - if ( label ) label.visible = false; - continue; - } - segments.push({ray, label}); - elevation_segments.push({ray: ray_elevated, label: label}); - } -} - - // clear should reset elevation info export function elevationRulerClear(wrapped, ...args) { log("we are clearing!", this); @@ -266,6 +116,7 @@ export async function elevationRulerAnimateToken(wrapped, token, ray, dx, dy, se log(`Current token elevation is ${current_elevation}. Will be changed to ${end_elevation}.`); // move the token first. + log(`Segment {segment_num}: Moving token from {token.center.x, token.center.y} by x {dx} and y {dy}.`, token, ray); let res = wrapped(token, ray, dx, dy, segment_num); if(current_elevation !== end_elevation) { From 938da9ca6aeab507edcb793471f18faef19b2212 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Tue, 20 Jul 2021 14:56:08 -0700 Subject: [PATCH 06/22] Fix awaiting token movement. --- scripts/ruler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ruler.js b/scripts/ruler.js index 2b9bcfa..1f18ed3 100644 --- a/scripts/ruler.js +++ b/scripts/ruler.js @@ -116,8 +116,8 @@ export async function elevationRulerAnimateToken(wrapped, token, ray, dx, dy, se log(`Current token elevation is ${current_elevation}. Will be changed to ${end_elevation}.`); // move the token first. - log(`Segment {segment_num}: Moving token from {token.center.x, token.center.y} by x {dx} and y {dy}.`, token, ray); - let res = wrapped(token, ray, dx, dy, segment_num); + log(`Segment {segment_num}: Moving token from ${token.center.x}, ${token.center.y} by x ${ray.B.x} and y ${ray.B.y}.`, token, ray); + let res = await wrapped(token, ray, dx, dy, segment_num); if(current_elevation !== end_elevation) { await token.document.update({ 'elevation': end_elevation }); From edd89f8ae8431776fbe6dfe3356d3a15e5878167 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 07:30:34 -0700 Subject: [PATCH 07/22] Revert back to modifying physical path Cleaner to keep this in Elevation Ruler instead of libRuler. Patch measurePhysicalPath to check for the z dimension and project accordingly. --- scripts/patching.js | 2 ++ scripts/segments.js | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/scripts/patching.js b/scripts/patching.js index 17dcbc4..42cdc04 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -6,6 +6,7 @@ import { elevationRulerClear, import { elevationRulerAddProperties, elevationRulerConstructPhysicalPath, + elevationRulerMeasurePhysicalPath, elevationRulerGetText } from "./segments.js"; export function registerRuler() { @@ -13,6 +14,7 @@ export function registerRuler() { // segment methods (for measuring) libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.addProperties', elevationRulerAddProperties, 'WRAPPER'); libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.constructPhysicalPath', elevationRulerConstructPhysicalPath, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.measurePhysicalPath', elevationRulerMeasurePhysicalPath, 'WRAPPER'); libWrapper.register(MODULE_ID, 'window.libRuler.RulerSegment.prototype.text', elevationRulerGetText, 'WRAPPER'); // move token methods diff --git a/scripts/segments.js b/scripts/segments.js index 4d3c6be..fe5ffbe 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -171,6 +171,70 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { return default_path; } + /* + * Extend libRuler measurePhysicalPath to measure in 3 dimensions. + * Project the z dimension back to the 2-D canvas and measure using the default + * distanceFunction method. + * Projection is accomplished by imagining a right triangle with the hypotenuse between + * p0 and p1, + * where p0 is the origin in 3d + * p1 is the destination in 3d + * and the two sides of the triangle are orthogonal in 3d space. + * @param {Object} physical_path An object that contains {origin, destination}. + * Each has {x, y, z} where z is optional. + * @return {Number} Total distance for the path + */ +export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { + if("z" in physical_path.origin || "z" in physical_path.destination) { + if(!("z" in physical_path.origin)) physical_path.origin.z = 0; + if(!("z" in physical_path.destination)) physical_path.destination.z = 0; + + // Project the 3-D path to 2-D canvas + log(`Projecting physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y}, ${physical_path.origin.z} to dest ${physical_path.destination.x}, ${physical_path.destination.y}, ${physical_path.destination.z}`); + + physical_path.destination = projectElevatedPoint(physical_path.origin, physical_path.destination); + + // if we are using grid spaces, the destination needs to be re-centered to the grid. + // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent + // depending on cardinality of the move, as rounding will increase/decrease to the nearest gridspace + if(this.distance_function_options?.gridSpaces) { + // canvas.grid.getCenter returns an array [x, y]; + const snapped = canvas.grid.getCenter(physical_path.destination.x, physical_path.destination.y); + log(`Snapping ${destination.x}, ${destination.y} to ${snapped[0]}, ${snapped[1]}`); + physical_path.destination = { x: snapped[0], y: snapped[1] }; + } + delete physical_path.origin.z + delete physical_path.destination.z; + + log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); + } + + return wrapped(physical_path); +} + + /* + * Calculate a new point by projecting the elevated point back onto the 2-D surface + * If the movement on the plane is represented by moving from point A to point B, + * and you also move 'height' distance orthogonal to the plane, the distance is the + * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. + * Project by rotating the vertical triangle 90º, then calculate the new point C. + * + * Cx = { height * (By - Ay) / dist(A to B) } + Bx + * Cy = { height * (Bx - Ax) / dist(A to B) } + By + * @param {{x: number, y: number}} A + * @param {{x: number, y: number}} B + */ +function projectElevatedPoint(A, B) { + const height = B.z - A.z; + const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); + const projected_x = B.x + ((height / distance) * (A.y - B.y)); + const projected_y = B.y - ((height / distance) * (A.x - B.x)); + + return new PIXI.Point(projected_x, projected_y); +} + + + /* * @param {number} segmentDistance * @param {number} totalDistance From 3a93ba382a45673dccc1f8331958c969d1ea4619 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 07:37:43 -0700 Subject: [PATCH 08/22] Revert back to adding z to origin and destination of path. No longer dealing with an array of points. --- scripts/segments.js | 48 +++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/scripts/segments.js b/scripts/segments.js index fe5ffbe..a82cab2 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -117,7 +117,29 @@ export function toGridDistance(increment) { return Math.round(increment * canvas.scene.data.gridDistance * 100) / 100; } - + /* + * Construct a physical path for the segment that represents how the measured item + * actually would move within the segment. + * + * This patch adds the 3rd dimension as z. + * + * The constructed path is an object with an origin and destination. + * By convention, each point should have at least x and y. If 3d, it should have z. + * The physical path object may have other properties, but these may be ignored by + * other modules. + * + * If you intend to create deviations from a line, you may want to include + * additional properties in the segment or in the path to represent those deviations. + * For example, a property for a formula to represent a curve. + * In such a case, modifying measurePhysicalPath distanceFunction methods may be necessary. + * + * @param {Segment} destination_point If provided, this should be either a Segment class or an object + * with the properties ray containing a Ray object. + * @return {Object} An object that contains {origin, destination}. + * It may contain other properties related to the physical path to be handled by specific modules. + * Default origin and destination will contain {x, y}. By convention, elevation should + * be represented by a {z} property. + */ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { // elevate or lower the destination point in 3-D space // measure from the origin of the ruler movement, so that canvas = 0 and each segment @@ -145,26 +167,10 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { const elevation_delta = ending_elevation_grid_units - starting_elevation_grid_units; const ruler_distance = this.ray.distance; - // Assume there could be multiple points in the path, represented by an array. - // The starting point must have the pre-determined starting elevation. - // The ending point must have the calculated elevation. - // Any points in-between should have a ratio unless they already have a z dimension. - const last_idx = default_path.length - 1; - default_path.map((p, idx, arr) => { - if(idx === 0) { - // origin - p.z = starting_elevation_grid_units; - return p; - } else if(idx === last_idx || !("z" in p)) { - // destination or intermediate p without a z dimension. - const p_origin = arr[idx - 1]; - const simple_path_distance = window.libRuler.RulerUtilities.calculateDistance(p_origin, p); - const ratio = simple_path_distance / ruler_distance; - p.z = starting_elevation_grid_units + elevation_delta * ratio; - } - - return p; - }); + const simple_path_distance = window.libRuler.RulerUtilities.calculateDistance(default_path.origin, default_path.destination); + const ratio = simple_path_distance / ruler_distance; + default_path.origin.z = starting_elevation_grid_units; + default_path.destination.z = starting_elevation_grid_units + elevation_delta * ratio; log("Default path", default_path); From 5dd26d80b6e693d275f8e812a2dda3091e951c19 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 16:57:14 -0700 Subject: [PATCH 09/22] Additional merge correction for projectElevatedPoint --- scripts/segments.js | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/scripts/segments.js b/scripts/segments.js index f3c9363..df74d07 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -227,10 +227,10 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { * @param {{x: number, y: number}} B */ function projectElevatedPoint(A, B) { - const height = B.z - A.z; - const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); - const projected_x = B.x + ((height / distance) * (A.y - B.y)); - const projected_y = B.y - ((height / distance) * (A.x - B.x)); + const height = A.z - B.z; + const distance = CalculateDistance(A, B); + const projected_x = A.x + ((height / distance) * (B.y - A.y)); + const projected_y = A.y - ((height / distance) * (B.x - A.x)); return new PIXI.Point(projected_x, projected_y); } @@ -457,24 +457,4 @@ function checkForHole(intersectionPT, zz) { } } return undefined; -} - - -// ----- MATH FOR MEASURING ELEVATION DISTANCE ----- // -/** - * Calculate a new point by projecting the elevated point back onto the 2-D surface - * If the movement on the plane is represented by moving from point A to point B, - * and you also move 'height' distance orthogonal to the plane, the distance is the - * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. - * Project by rotating the vertical triangle 90º, then calculate the new point C. - * - * Cx = { height * (By - Ay) / dist(A to B) } + Bx - * Cy = { height * (Bx - Ax) / dist(A to B) } + By - * @param {{x: number, y: number}} A - * @param {{x: number, y: number}} B - */ -export function ProjectElevatedPoint(A, B) { - const height = A.z - B.z; - const distance = CalculateDistance(A, B); - const projected_x = A.x + ((height / distance) * (B.y - A.y)); - const projected_y = A.y - ((height / distance) * (B.x - A.x)); +} \ No newline at end of file From beb0d7d6e4f07905e6def6b9e0a1c54b77a5ffb0 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 16:57:49 -0700 Subject: [PATCH 10/22] Correct logging for physical_path.origin snap --- scripts/segments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/segments.js b/scripts/segments.js index df74d07..4d788eb 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -206,7 +206,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { if(this.distance_function_options?.gridSpaces) { // canvas.grid.getCenter returns an array [x, y]; const snapped = canvas.grid.getCenter(physical_path.origin.x, physical_path.origin.y); - log(`Snapping ${physical_path.destination.x}, ${physical_path.destination.y} to ${snapped[0]}, ${snapped[1]}`); + log(`Snapping ${physical_path.origin.x}, ${physical_path.origin.y} to ${snapped[0]}, ${snapped[1]}`); physical_path.origin = { x: snapped[0], y: snapped[1] }; log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); } From eb072f0f32fa9e39b671f021834c928a0737721b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 17:18:06 -0700 Subject: [PATCH 11/22] Only project points if necessary Add check to compare origin and destination elevations. Only project if unequal. --- scripts/segments.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/segments.js b/scripts/segments.js index 4d788eb..72954cc 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -194,21 +194,22 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { if("z" in physical_path.origin || "z" in physical_path.destination) { if(!("z" in physical_path.origin)) physical_path.origin.z = 0; if(!("z" in physical_path.destination)) physical_path.destination.z = 0; - // Project the 3-D path to 2-D canvas - log(`Projecting physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y}, ${physical_path.origin.z} to dest ${physical_path.destination.x}, ${physical_path.destination.y}, ${physical_path.destination.z}`); - - physical_path.origin = ProjectElevatedPoint(physical_path.origin, physical_path.destination); + if(!window.libRuler.RulerUtilities.almostEqual(physical_path.origin.z, physical_path.destination.z)) { + // Project the 3-D path to 2-D canvas + log(`Projecting physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y}, ${physical_path.origin.z} to dest ${physical_path.destination.x}, ${physical_path.destination.y}, ${physical_path.destination.z}`); + physical_path.origin = ProjectElevatedPoint(physical_path.origin, physical_path.destination); - // if we are using grid spaces, the destination needs to be re-centered to the grid. - // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent - // depending on cardinality of the move, as rounding will increase/decrease to the nearest gridspace - if(this.distance_function_options?.gridSpaces) { - // canvas.grid.getCenter returns an array [x, y]; - const snapped = canvas.grid.getCenter(physical_path.origin.x, physical_path.origin.y); - log(`Snapping ${physical_path.origin.x}, ${physical_path.origin.y} to ${snapped[0]}, ${snapped[1]}`); - physical_path.origin = { x: snapped[0], y: snapped[1] }; - log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); + // if we are using grid spaces, the destination needs to be re-centered to the grid. + // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent + // depending on cardinality of the move, as rounding will increase/decrease to the nearest gridspace + if(this.distance_function_options?.gridSpaces) { + // canvas.grid.getCenter returns an array [x, y]; + const snapped = canvas.grid.getCenter(physical_path.origin.x, physical_path.origin.y); + log(`Snapping ${physical_path.origin.x}, ${physical_path.origin.y} to ${snapped[0]}, ${snapped[1]}`); + physical_path.origin = { x: snapped[0], y: snapped[1] }; + log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); + } } return wrapped(physical_path); From e9cd086e44d2857ffc06ceb03c7f520569039778 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 17:18:31 -0700 Subject: [PATCH 12/22] Use libRuler's calculate distance function. --- scripts/segments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/segments.js b/scripts/segments.js index 72954cc..09cd0a5 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -229,7 +229,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { */ function projectElevatedPoint(A, B) { const height = A.z - B.z; - const distance = CalculateDistance(A, B); + const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); const projected_x = A.x + ((height / distance) * (B.y - A.y)); const projected_y = A.y - ((height / distance) * (B.x - A.x)); From a184544177fd4b024664a1100c30a4148d90bd4e Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 17:29:58 -0700 Subject: [PATCH 13/22] Minor fixes. --- scripts/module.js | 4 ++-- scripts/segments.js | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/module.js b/scripts/module.js index a750af1..1edcd0b 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,6 +1,6 @@ import { registerSettings, registerHotkeys } from "./settings.js"; import { registerRuler } from "./patching.js"; -import { ProjectElevatedPoint } from "./segments.js"; +import { projectElevatedPoint } from "./segments.js"; export const MODULE_ID = 'elevationruler'; const FORCE_DEBUG = false; // used for logging before dev mode is set up @@ -24,7 +24,7 @@ export function log(...args) { Hooks.once('init', async function() { log("Initializing Elevation Ruler Options."); - window['elevationRuler'] = { ProjectElevatedPoint: ProjectElevatedPoint }; + window['elevationRuler'] = { projectElevatedPoint: projectElevatedPoint }; }); diff --git a/scripts/segments.js b/scripts/segments.js index 09cd0a5..eddd850 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -198,7 +198,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { if(!window.libRuler.RulerUtilities.almostEqual(physical_path.origin.z, physical_path.destination.z)) { // Project the 3-D path to 2-D canvas log(`Projecting physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y}, ${physical_path.origin.z} to dest ${physical_path.destination.x}, ${physical_path.destination.y}, ${physical_path.destination.z}`); - physical_path.origin = ProjectElevatedPoint(physical_path.origin, physical_path.destination); + physical_path.origin = projectElevatedPoint(physical_path.origin, physical_path.destination); // if we are using grid spaces, the destination needs to be re-centered to the grid. // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent @@ -210,6 +210,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { physical_path.origin = { x: snapped[0], y: snapped[1] }; log(`Projected physical_path from origin ${physical_path.origin.x}, ${physical_path.origin.y} to dest ${physical_path.destination.x}, ${physical_path.destination.y}`); } + } } return wrapped(physical_path); @@ -227,7 +228,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { * @param {{x: number, y: number}} A * @param {{x: number, y: number}} B */ -function projectElevatedPoint(A, B) { +export function projectElevatedPoint(A, B) { const height = A.z - B.z; const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); const projected_x = A.x + ((height / distance) * (B.y - A.y)); @@ -458,4 +459,4 @@ function checkForHole(intersectionPT, zz) { } } return undefined; -} \ No newline at end of file +} From 5adc1590912f66a7309cb81eafda0e823f3f806b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Wed, 21 Jul 2021 17:32:50 -0700 Subject: [PATCH 14/22] Correct call to options.gridSpaces Need to correctly trigger snapping for the projected origin. --- scripts/segments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/segments.js b/scripts/segments.js index eddd850..2ca7694 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -203,7 +203,7 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { // if we are using grid spaces, the destination needs to be re-centered to the grid. // otherwise, when a token moves in 2-D diagonally, the 3-D measure will be inconsistent // depending on cardinality of the move, as rounding will increase/decrease to the nearest gridspace - if(this.distance_function_options?.gridSpaces) { + if(this.options?.gridSpaces) { // canvas.grid.getCenter returns an array [x, y]; const snapped = canvas.grid.getCenter(physical_path.origin.x, physical_path.origin.y); log(`Snapping ${physical_path.origin.x}, ${physical_path.origin.y} to ${snapped[0]}, ${snapped[1]}`); From a98435b6388d64615561ec7b8fde8af9ec2c1caf Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 10:53:50 -0700 Subject: [PATCH 15/22] Add utility.js, override libRuler utilities with 3-D versions iterateGrid: handle moving along grid and changing elevation. calculatedistance: calculate in 3 dimensions. --- scripts/module.js | 7 ++-- scripts/patching.js | 10 +++++ scripts/segments.js | 23 +----------- scripts/utility.js | 92 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 25 deletions(-) create mode 100644 scripts/utility.js diff --git a/scripts/module.js b/scripts/module.js index 1edcd0b..5778771 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,7 +1,7 @@ import { registerSettings, registerHotkeys } from "./settings.js"; import { registerRuler } from "./patching.js"; -import { projectElevatedPoint } from "./segments.js"; - +import { iterateGridUnder3dLine, projectElevatedPoint } from "./utility.js"; + export const MODULE_ID = 'elevationruler'; const FORCE_DEBUG = false; // used for logging before dev mode is set up @@ -24,7 +24,8 @@ export function log(...args) { Hooks.once('init', async function() { log("Initializing Elevation Ruler Options."); - window['elevationRuler'] = { projectElevatedPoint: projectElevatedPoint }; + window['elevationRuler'] = { projectElevatedPoint: projectElevatedPoint, + iterateGridUnder3dLine: iterateGridUnder3dLine }; }); diff --git a/scripts/patching.js b/scripts/patching.js index 42cdc04..535acb3 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -9,6 +9,9 @@ import { elevationRulerAddProperties, elevationRulerMeasurePhysicalPath, elevationRulerGetText } from "./segments.js"; +import { calculate3dDistance, + iterateGridUnder3dLine } from "./utilities.js"; + export function registerRuler() { // segment methods (for measuring) @@ -25,5 +28,12 @@ export function registerRuler() { libWrapper.register(MODULE_ID, 'Ruler.prototype._addWaypoint', elevationRulerAddWaypoint, 'WRAPPER'); libWrapper.register(MODULE_ID, 'Ruler.prototype._removeWaypoint', elevationRulerRemoveWaypoint, 'WRAPPER'); + // utilities + libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.prototype.calculateDistance', calculate3dDistance, 'MIXED'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.prototype.iterateGridUnderLine', iterateGridUnder3dLine, 'WRAPPER'); + + log("registerRuler finished!"); } + + diff --git a/scripts/segments.js b/scripts/segments.js index 2ca7694..b02eccd 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -1,4 +1,5 @@ import { MODULE_ID, log } from "./module.js"; +import { projectElevatedPoint } from "./utility.js"; /* * Add flags to the segment specific to elevation: @@ -216,28 +217,6 @@ export function elevationRulerMeasurePhysicalPath(wrapped, physical_path) { return wrapped(physical_path); } - /* - * Calculate a new point by projecting the elevated point back onto the 2-D surface - * If the movement on the plane is represented by moving from point A to point B, - * and you also move 'height' distance orthogonal to the plane, the distance is the - * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. - * Project by rotating the vertical triangle 90º, then calculate the new point C. - * - * Cx = { height * (By - Ay) / dist(A to B) } + Bx - * Cy = { height * (Bx - Ax) / dist(A to B) } + By - * @param {{x: number, y: number}} A - * @param {{x: number, y: number}} B - */ -export function projectElevatedPoint(A, B) { - const height = A.z - B.z; - const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); - const projected_x = A.x + ((height / distance) * (B.y - A.y)); - const projected_y = A.y - ((height / distance) * (B.x - A.x)); - - return new PIXI.Point(projected_x, projected_y); -} - - /* * @param {number} segmentDistance diff --git a/scripts/utility.js b/scripts/utility.js new file mode 100644 index 0000000..0d5adea --- /dev/null +++ b/scripts/utility.js @@ -0,0 +1,92 @@ +/* + * Generator to iterate grid points under a line. + * This version handles lines in 3d. + * It assumes elevation movement by the set grid distance. + * @param {x: Number, y: Number, z: Number} origin Origination point + * @param {x: Number, y: Number, z: Number} destination Destination point + * @return Iterator, which in turn + * returns [row, col, elevation] for each grid point under the line. + */ +export function * iterateGridUnder3dLine(wrapped, origin, destination) { + + const ray2d = new Ray(origin, destination); + const gridIter2d = window.libRuler.RulerUtilities.iterateGridUnderLine(ray2d); + + let prior_elevation = origin.z || 0; + const end_elevation = destination.z || 0; + const direction = prior_elevation <= end_elevation ? 1 : -1; + const elevation_increment = canvas.grid.grid.options.dimensions.distance; + + for(const [row, col] of gridIter) { + // step down in elevation if necessary + if(prior_elevation != end_elevation) { + const remainder = Math.abs(prior_elevation - end_elevation); + const step_elevation = Math.min(remainder, elevation_increment); + prior_elevation += step_elevation * direction; + + } + yield [row, col, prior_elevation]; + } + + // more elevation? increment straight down. + const MAX_ITERATIONS = 1000; // to avoid infinite loops + let iteration = 0; + while(prior_elevation != end_elevation && iteration < MAX_ITERATIONS) { + iteration += 1; + const remainder = Math.abs(prior_elevation - end_elevation); + const step_elevation = Math.min(remainder, elevation_increment); + prior_elevation += step_elevation * direction; + + yield [row, col, elevation]; + } +} + + + /* + * Calculate a new point by projecting the elevated point back onto the 2-D surface + * If the movement on the plane is represented by moving from point A to point B, + * and you also move 'height' distance orthogonal to the plane, the distance is the + * hypotenuse of the triangle formed by A, B, and C, where C is orthogonal to B. + * Project by rotating the vertical triangle 90º, then calculate the new point C. + * + * Cx = { height * (By - Ay) / dist(A to B) } + Bx + * Cy = { height * (Bx - Ax) / dist(A to B) } + By + * @param {{x: number, y: number}} A + * @param {{x: number, y: number}} B + */ +export function projectElevatedPoint(A, B) { + const height = A.z - B.z; + const distance = window.libRuler.RulerUtilities.calculateDistance(A, B); + const projected_x = A.x + ((height / distance) * (B.y - A.y)); + const projected_y = A.y - ((height / distance) * (B.x - A.x)); + + // for square grids, rotate so that the origin point A is vertical or horizontal from original A? + // for hex grids, rotate so that the origin point A is in a straight line from original A? + // this will give correct results for diagonal moves, b/c A should always be straight line to projected A, as it is a vertical move + + return new PIXI.Point(projected_x, projected_y); +} + + /* + * Calculate the distance between two points in {x,y,z} dimensions. + * @param {PIXI.Point} A Point in {x, y, z} format. + * @param {PIXI.Point} B Point in {x, y, z} format. + * @return The distance between the two points. + */ +export function calculate3dDistance(wrapped, A, B, EPSILON = 1e-6) { + if(A.z === undefined) A.z = 0; + if(B.z === undefined) B.z = 0; + + const dz = Math.abs(B.z - A.z); + if(dz < EPSILON) { return wrapped(A, B, EPSILON); } + + const dy = Math.abs(B.y - A.y); + if(dy < EPSILON) { return wrapped({x: A.x, y: A.z}, {x: B.x, y: B.z}, EPSILON); + + const dx = Math.abs(B.x - A.x); + if(dx < EPSILON) { return wrapped({x: A.z, y: A.y}, {x: B.z, y: B.y}, EPSILON) + + return Math.hypot(dz, dy, dx); +} + + From 2744e31db4d2c64a3b0dae7a964b5e5f6b1ba29d Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 11:22:27 -0700 Subject: [PATCH 16/22] Correct references to utility functions. --- scripts/patching.js | 6 +++--- scripts/utility.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/patching.js b/scripts/patching.js index 535acb3..19dc9a1 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -10,7 +10,7 @@ import { elevationRulerAddProperties, elevationRulerGetText } from "./segments.js"; import { calculate3dDistance, - iterateGridUnder3dLine } from "./utilities.js"; + iterateGridUnder3dLine } from "./utility.js"; export function registerRuler() { @@ -29,8 +29,8 @@ export function registerRuler() { libWrapper.register(MODULE_ID, 'Ruler.prototype._removeWaypoint', elevationRulerRemoveWaypoint, 'WRAPPER'); // utilities - libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.prototype.calculateDistance', calculate3dDistance, 'MIXED'); - libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.prototype.iterateGridUnderLine', iterateGridUnder3dLine, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.calculateDistance', calculate3dDistance, 'MIXED'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.iterateGridUnderLine', iterateGridUnder3dLine, 'WRAPPER'); log("registerRuler finished!"); diff --git a/scripts/utility.js b/scripts/utility.js index 0d5adea..5f7c1f6 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -81,10 +81,10 @@ export function calculate3dDistance(wrapped, A, B, EPSILON = 1e-6) { if(dz < EPSILON) { return wrapped(A, B, EPSILON); } const dy = Math.abs(B.y - A.y); - if(dy < EPSILON) { return wrapped({x: A.x, y: A.z}, {x: B.x, y: B.z}, EPSILON); + if(dy < EPSILON) { return wrapped({x: A.x, y: A.z}, {x: B.x, y: B.z}, EPSILON); } const dx = Math.abs(B.x - A.x); - if(dx < EPSILON) { return wrapped({x: A.z, y: A.y}, {x: B.z, y: B.y}, EPSILON) + if(dx < EPSILON) { return wrapped({x: A.z, y: A.y}, {x: B.z, y: B.y}, EPSILON); } return Math.hypot(dz, dy, dx); } From 3c6de56b7c88bf00564623b758610c4650fc7be6 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 11:25:34 -0700 Subject: [PATCH 17/22] Switch to wrap call fo iterateGrid Need to call wrap explicitly for libWrapper to work. --- scripts/utility.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/utility.js b/scripts/utility.js index 0d5adea..d89a590 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -8,10 +8,9 @@ * returns [row, col, elevation] for each grid point under the line. */ export function * iterateGridUnder3dLine(wrapped, origin, destination) { + const gridIter2d = wrapped(origin, destination); - const ray2d = new Ray(origin, destination); - const gridIter2d = window.libRuler.RulerUtilities.iterateGridUnderLine(ray2d); - + const ray2d = new Ray(origin, destination); let prior_elevation = origin.z || 0; const end_elevation = destination.z || 0; const direction = prior_elevation <= end_elevation ? 1 : -1; From fc37db3c5b8936535905629666c71cf226e42ff4 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 11:28:38 -0700 Subject: [PATCH 18/22] Correct reference to gridIter --- scripts/utility.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/utility.js b/scripts/utility.js index a7adc55..2ad7822 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -10,13 +10,12 @@ export function * iterateGridUnder3dLine(wrapped, origin, destination) { const gridIter2d = wrapped(origin, destination); - const ray2d = new Ray(origin, destination); let prior_elevation = origin.z || 0; const end_elevation = destination.z || 0; const direction = prior_elevation <= end_elevation ? 1 : -1; const elevation_increment = canvas.grid.grid.options.dimensions.distance; - for(const [row, col] of gridIter) { + for(const [row, col] of gridIter2d) { // step down in elevation if necessary if(prior_elevation != end_elevation) { const remainder = Math.abs(prior_elevation - end_elevation); From b2029f072fbaca12b6095027b895eb71e5dcee1b Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 12:43:23 -0700 Subject: [PATCH 19/22] iterateGrid wrapper for libWrapper See https://discord.com/channels/170995199584108546/722566083499786352/868215051918639204 libWrapper cannot wrap a generator function directly. --- scripts/patching.js | 4 ++-- scripts/utility.js | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/patching.js b/scripts/patching.js index 19dc9a1..aeb91e0 100644 --- a/scripts/patching.js +++ b/scripts/patching.js @@ -10,7 +10,7 @@ import { elevationRulerAddProperties, elevationRulerGetText } from "./segments.js"; import { calculate3dDistance, - iterateGridUnder3dLine } from "./utility.js"; + iterateGridUnder3dLine_wrapper } from "./utility.js"; export function registerRuler() { @@ -30,7 +30,7 @@ export function registerRuler() { // utilities libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.calculateDistance', calculate3dDistance, 'MIXED'); - libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.iterateGridUnderLine', iterateGridUnder3dLine, 'WRAPPER'); + libWrapper.register(MODULE_ID, 'window.libRuler.RulerUtilities.iterateGridUnderLine', iterateGridUnder3dLine_wrapper, 'WRAPPER'); log("registerRuler finished!"); diff --git a/scripts/utility.js b/scripts/utility.js index 2ad7822..7c41986 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -7,15 +7,13 @@ * @return Iterator, which in turn * returns [row, col, elevation] for each grid point under the line. */ -export function * iterateGridUnder3dLine(wrapped, origin, destination) { - const gridIter2d = wrapped(origin, destination); - +function * iterateGridUnder3dLine(generator, origin, destination) { let prior_elevation = origin.z || 0; const end_elevation = destination.z || 0; const direction = prior_elevation <= end_elevation ? 1 : -1; const elevation_increment = canvas.grid.grid.options.dimensions.distance; - for(const [row, col] of gridIter2d) { + for(const [row, col] of generator) { // step down in elevation if necessary if(prior_elevation != end_elevation) { const remainder = Math.abs(prior_elevation - end_elevation); @@ -39,6 +37,10 @@ export function * iterateGridUnder3dLine(wrapped, origin, destination) { } } +// needed for libWrapper +export function iterateGridUnder3dLine_wrapper(wrapped, origin, destination) { + return iterateGridUnder3dLine(wrapped(origin, destination), origin, destination); +} /* * Calculate a new point by projecting the elevated point back onto the 2-D surface From b3cb113eb4713f53ca9c9ce9c506d89ec838c190 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 13:23:17 -0700 Subject: [PATCH 20/22] Fix call to original generator; use yield* Need to get the values from the original generator. --- scripts/utility.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/utility.js b/scripts/utility.js index 7c41986..880ed6c 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -13,8 +13,11 @@ function * iterateGridUnder3dLine(generator, origin, destination) { const direction = prior_elevation <= end_elevation ? 1 : -1; const elevation_increment = canvas.grid.grid.options.dimensions.distance; - for(const [row, col] of generator) { + for(const res of generator) { // step down in elevation if necessary + const {value, done} = res; + const [row, col] = value; + if(prior_elevation != end_elevation) { const remainder = Math.abs(prior_elevation - end_elevation); const step_elevation = Math.min(remainder, elevation_increment); @@ -39,7 +42,9 @@ function * iterateGridUnder3dLine(generator, origin, destination) { // needed for libWrapper export function iterateGridUnder3dLine_wrapper(wrapped, origin, destination) { - return iterateGridUnder3dLine(wrapped(origin, destination), origin, destination); + yield* base_gen = wrapped(origin, destination); + + return iterateGridUnder3dLine(base_gen, origin, destination); } /* From a341cca538e13145bada4ac919395492b0399c4d Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 13:25:01 -0700 Subject: [PATCH 21/22] Add export to iterateGrid for testing. --- scripts/utility.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utility.js b/scripts/utility.js index 880ed6c..54ed8e7 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -7,7 +7,7 @@ * @return Iterator, which in turn * returns [row, col, elevation] for each grid point under the line. */ -function * iterateGridUnder3dLine(generator, origin, destination) { +export function * iterateGridUnder3dLine(generator, origin, destination) { let prior_elevation = origin.z || 0; const end_elevation = destination.z || 0; const direction = prior_elevation <= end_elevation ? 1 : -1; From 0efc6c9c18796fa6234d4c32590112ccf4f69f75 Mon Sep 17 00:00:00 2001 From: Michael Enion Date: Fri, 23 Jul 2021 14:03:31 -0700 Subject: [PATCH 22/22] Add logging to confirm generator is working. --- scripts/segments.js | 6 +++--- scripts/utility.js | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/scripts/segments.js b/scripts/segments.js index b02eccd..f7de545 100644 --- a/scripts/segments.js +++ b/scripts/segments.js @@ -148,7 +148,7 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { // --> this is done in AddProperties function log("Constructing the physical path."); const default_path = wrapped(...args); - log("Default path", default_path); + log("Default path: (${default_path.origin.x}, ${default_path.origin.y}), (${default_path.destination.x}, ${default_path.destination.y})", default_path); const starting_elevation = this.getFlag(MODULE_ID, "starting_elevation"); const ending_elevation = this.getFlag(MODULE_ID, "ending_elevation"); @@ -171,9 +171,9 @@ export function elevationRulerConstructPhysicalPath(wrapped, ...args) { const simple_path_distance = window.libRuler.RulerUtilities.calculateDistance(default_path.origin, default_path.destination); const ratio = simple_path_distance / ruler_distance; default_path.origin.z = starting_elevation_grid_units; - default_path.destination.z = starting_elevation_grid_units + elevation_delta * ratio; + default_path.destination.z = (starting_elevation_grid_units + elevation_delta) * ratio; - log("Default path", default_path); + log("Default path: (${default_path.origin.x}, ${default_path.origin.y}, ${default_path.origin.z}), (${default_path.destination.x}, ${default_path.destination.y}, ${default_path.destination.z})", default_path); return default_path; } diff --git a/scripts/utility.js b/scripts/utility.js index 54ed8e7..de374e3 100644 --- a/scripts/utility.js +++ b/scripts/utility.js @@ -1,3 +1,5 @@ +import { log } from "./module.js"; + /* * Generator to iterate grid points under a line. * This version handles lines in 3d. @@ -11,12 +13,17 @@ export function * iterateGridUnder3dLine(generator, origin, destination) { let prior_elevation = origin.z || 0; const end_elevation = destination.z || 0; const direction = prior_elevation <= end_elevation ? 1 : -1; - const elevation_increment = canvas.grid.grid.options.dimensions.distance; - + const elevation_increment = canvas.scene.data.gridDistance * canvas.scene.data.grid; + log(`elevation: ${prior_elevation}[prior], ${end_elevation}[end], ${direction}[direction], ${elevation_increment}[increment]`); + //log(generator); + let last_row, last_col; + for(const res of generator) { // step down in elevation if necessary - const {value, done} = res; - const [row, col] = value; + log(res); + //const {value, done} = res; + const [row, col] = res; + [last_row, last_col] = res; if(prior_elevation != end_elevation) { const remainder = Math.abs(prior_elevation - end_elevation); @@ -34,17 +41,17 @@ export function * iterateGridUnder3dLine(generator, origin, destination) { iteration += 1; const remainder = Math.abs(prior_elevation - end_elevation); const step_elevation = Math.min(remainder, elevation_increment); + log(`elevation: ${prior_elevation}[prior], ${end_elevation}[end], ${step_elevation}[step]`); prior_elevation += step_elevation * direction; - yield [row, col, elevation]; + yield [last_row, last_col, prior_elevation]; } } // needed for libWrapper export function iterateGridUnder3dLine_wrapper(wrapped, origin, destination) { - yield* base_gen = wrapped(origin, destination); - - return iterateGridUnder3dLine(base_gen, origin, destination); + log(`iterateGrid origin, destination`, origin, destination); + return iterateGridUnder3dLine(wrapped(origin, destination), origin, destination); } /*