Added distance calculation support for hex

This commit is contained in:
Mitchell McCaffrey 2021-02-11 10:06:06 +11:00
parent d59f95464e
commit 547a214149
5 changed files with 107 additions and 58 deletions

View File

@ -67,7 +67,7 @@ function MapGridEditor({ map, onGridChange }) {
// Find distance and direction of dragging
const previousPosition = handlePreviousPositionRef.current;
const position = getHandleNormalizedPosition(handle);
const distance = Vector2.distance(previousPosition, position, "euclidean");
const distance = Vector2.distance(previousPosition, position);
const direction = Vector2.normalize(
Vector2.subtract(position, previousPosition)
);

View File

@ -11,6 +11,7 @@ import {
} from "../../helpers/drawing";
import Vector2 from "../../helpers/Vector2";
import { getRelativePointerPosition } from "../../helpers/konva";
import { parseGridScale, gridDistance } from "../../helpers/grid";
import useGridSnapping from "../../hooks/useGridSnapping";
@ -21,31 +22,17 @@ function MapMeasure({ map, active }) {
mapHeight,
interactionEmitter,
} = useMapInteraction();
const { grid, gridCellNormalizedSize, gridStrokeWidth } = useGrid();
const {
grid,
gridCellNormalizedSize,
gridStrokeWidth,
gridCellPixelSize,
} = useGrid();
const mapStageRef = useMapStage();
const [drawingShapeData, setDrawingShapeData] = useState(null);
const [isBrushDown, setIsBrushDown] = useState(false);
function parseToolScale(scale) {
if (typeof scale === "string") {
const match = scale.match(/(\d*)(\.\d*)?([a-zA-Z]*)/);
const integer = parseFloat(match[1]);
const fractional = parseFloat(match[2]);
const unit = match[3] || "";
if (!isNaN(integer) && !isNaN(fractional)) {
return {
multiplier: integer + fractional,
unit: unit,
digits: match[2].length - 1,
};
} else if (!isNaN(integer) && isNaN(fractional)) {
return { multiplier: integer, unit: unit, digits: 0 };
}
}
return { multiplier: 1, unit: "", digits: 0 };
}
const measureScale = parseToolScale(active && grid.measurement.scale);
const gridScale = parseGridScale(active && grid.measurement.scale);
const snapPositionToGrid = useGridSnapping();
@ -54,6 +41,7 @@ function MapMeasure({ map, active }) {
return;
}
const mapStage = mapStageRef.current;
const mapImage = mapStage?.findOne("#mapImage");
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
@ -84,19 +72,16 @@ function MapMeasure({ map, active }) {
brushPosition,
gridCellNormalizedSize
);
// Round the grid positions to the nearest 0.1 to aviod floating point issues
const precision = { x: 0.1, y: 0.1 };
const length = Vector2.distance(
Vector2.roundTo(
Vector2.divide(points[0], gridCellNormalizedSize),
precision
),
Vector2.roundTo(
Vector2.divide(points[1], gridCellNormalizedSize),
precision
),
grid.measurement.type
);
// Convert back to pixel values
const a = Vector2.multiply(points[0], {
x: mapImage.width(),
y: mapImage.height(),
});
const b = Vector2.multiply(points[1], {
x: mapImage.width(),
y: mapImage.height(),
});
const length = gridDistance(grid, a, b, gridCellPixelSize);
setDrawingShapeData({
length,
points,
@ -155,9 +140,9 @@ function MapMeasure({ map, active }) {
>
<Tag fill="hsla(230, 25%, 18%, 0.8)" cornerRadius={4} />
<Text
text={`${(shapeData.length * measureScale.multiplier).toFixed(
measureScale.digits
)}${measureScale.unit}`}
text={`${(shapeData.length * gridScale.multiplier).toFixed(
gridScale.digits
)}${gridScale.unit}`}
fill="white"
fontSize={24}
padding={4}

View File

@ -23,6 +23,10 @@ const defaultValue = {
size: new Vector2(0, 0),
inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) },
type: "square",
measurement: {
scale: "",
type: "euclidean",
},
},
gridPixelSize: new Size(0, 0),
gridCellPixelSize: new Size(0, 0, 0),

View File

@ -408,25 +408,9 @@ class Vector2 {
* Returns the distance between two vectors
* @param {Vector2} a
* @param {Vector2} b
* @param {string=} type - `chebyshev | euclidean | manhattan | alternating`
*/
static distance(a, b, type = "euclidean") {
switch (type) {
case "chebyshev":
return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
case "euclidean":
return this.length(this.subtract(a, b));
case "manhattan":
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
case "alternating":
// Alternating diagonal distance like D&D 3.5 and Pathfinder
const delta = this.abs(this.subtract(a, b));
const ma = this.max(delta);
const mi = this.min(delta);
return ma - mi + Math.floor(1.5 * mi);
default:
return this.length(this.subtract(a, b));
}
static distance(a, b) {
return this.length(this.subtract(a, b));
}
/**

View File

@ -259,18 +259,94 @@ export function hexCubeToOffset(cube, type) {
*/
export function hexOffsetToCube(offset, type) {
if (type === "hexVertical") {
const x = offset.x - (offset.y - (offset.y & 1)) / 2;
const x = offset.x - (offset.y + (offset.y & 1)) / 2;
const z = offset.y;
const y = -x - z;
return { x, y, z };
} else {
const x = offset.x;
const z = offset.y - (offset.x - (offset.x & 1)) / 2;
const z = offset.y - (offset.x + (offset.x & 1)) / 2;
const y = -x - z;
return { x, y, z };
}
}
/**
* Get the distance between a and b on the grid
* @param {Grid} grid
* @param {Vector2} a
* @param {Vector2} b
*/
export function gridDistance(grid, a, b, cellSize) {
// Get grid coordinates
const aCoord = getNearestCellCoordinates(grid, a.x, a.y, cellSize);
const bCoord = getNearestCellCoordinates(grid, b.x, b.y, cellSize);
if (grid.type === "square") {
if (grid.measurement.type === "chebyshev") {
return Math.max(
Math.abs(aCoord.x - bCoord.x),
Math.abs(aCoord.y - bCoord.y)
);
} else if (grid.measurement.type === "alternating") {
// Alternating diagonal distance like D&D 3.5 and Pathfinder
const delta = Vector2.abs(Vector2.subtract(aCoord, bCoord));
const max = Vector2.max(delta);
const min = Vector2.min(delta);
return max - min + Math.floor(1.5 * min);
} else if (grid.measurement.type === "euclidean") {
return Vector2.distance(aCoord, bCoord);
} else if (grid.measurement.type === "manhattan") {
return Math.abs(aCoord.x - bCoord.x) + Math.abs(aCoord.y - bCoord.y);
}
} else {
if (grid.measurement.type === "manhattan") {
// Convert to cube coordinates to get distance easier
const aCube = hexOffsetToCube(aCoord, grid.type);
const bCube = hexOffsetToCube(bCoord, grid.type);
return (
(Math.abs(aCube.x - bCube.x) +
Math.abs(aCube.y - bCube.y) +
Math.abs(aCube.z - bCube.z)) /
2
);
} else if (grid.measurement.type === "euclidean") {
return Vector2.distance(aCoord, bCoord);
}
}
}
/**
* @typedef GridScale
* @property {number} multiplier The number multiplier of the scale
* @property {string} unit The unit of the scale
* @property {number} digits The precision of the scale
*/
/**
* Parse a string representation of scale e.g. 5ft into a `GridScale`
* @param {string} scale
* @returns {GridScale}
*/
export function parseGridScale(scale) {
if (typeof scale === "string") {
const match = scale.match(/(\d*)(\.\d*)?([a-zA-Z]*)/);
const integer = parseFloat(match[1]);
const fractional = parseFloat(match[2]);
const unit = match[3] || "";
if (!isNaN(integer) && !isNaN(fractional)) {
return {
multiplier: integer + fractional,
unit: unit,
digits: match[2].length - 1,
};
} else if (!isNaN(integer) && isNaN(fractional)) {
return { multiplier: integer, unit: unit, digits: 0 };
}
}
return { multiplier: 1, unit: "", digits: 0 };
}
/**
* Get all factors of a number
* @param {number} n