Added distance calculation support for hex
This commit is contained in:
parent
d59f95464e
commit
547a214149
@ -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)
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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),
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user