diff --git a/src/components/map/Map.js b/src/components/map/Map.js
index c3a69b7..1429bc7 100644
--- a/src/components/map/Map.js
+++ b/src/components/map/Map.js
@@ -7,6 +7,7 @@ import MapDrawing from "./MapDrawing";
import MapFog from "./MapFog";
import MapDice from "./MapDice";
import MapGrid from "./MapGrid";
+import MapMeasure from "./MapMeasure";
import TokenDataContext from "../../contexts/TokenDataContext";
import MapLoadingContext from "../../contexts/MapLoadingContext";
@@ -53,6 +54,9 @@ function Map({
type: "brush",
useBlending: true,
},
+ measure: {
+ type: "chebyshev",
+ },
});
function handleToolSettingChange(tool, change) {
@@ -134,6 +138,7 @@ function Map({
}
if (!map) {
disabledControls.push("pan");
+ disabledControls.push("measure");
}
if (!allowFogDrawing) {
disabledControls.push("fog");
@@ -277,6 +282,14 @@ function Map({
);
+ const mapMeasure = (
+
+ );
+
return (
);
}
diff --git a/src/components/map/MapControls.js b/src/components/map/MapControls.js
index 8cf5f51..983bd0e 100644
--- a/src/components/map/MapControls.js
+++ b/src/components/map/MapControls.js
@@ -8,10 +8,12 @@ import SelectMapButton from "./SelectMapButton";
import FogToolSettings from "./controls/FogToolSettings";
import DrawingToolSettings from "./controls/DrawingToolSettings";
+import MeasureToolSettings from "./controls/MeasureToolSettings";
import PanToolIcon from "../../icons/PanToolIcon";
import FogToolIcon from "../../icons/FogToolIcon";
import BrushToolIcon from "../../icons/BrushToolIcon";
+import MeasureToolIcon from "../../icons/MeasureToolIcon";
import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
function MapContols({
@@ -47,8 +49,14 @@ function MapContols({
title: "Drawing Tool",
SettingsComponent: DrawingToolSettings,
},
+ measure: {
+ id: "measure",
+ icon: ,
+ title: "Measure Tool",
+ SettingsComponent: MeasureToolSettings,
+ },
};
- const tools = ["pan", "fog", "drawing"];
+ const tools = ["pan", "fog", "drawing", "measure"];
const sections = [
{
@@ -63,7 +71,7 @@ function MapContols({
),
},
{
- id: "drawing",
+ id: "tools",
component: tools.map((tool) => (
{
+ if (!active) {
+ return;
+ }
+ const mapStage = mapStageRef.current;
+
+ function getBrushPosition() {
+ const mapImage = mapStage.findOne("#mapImage");
+ return getBrushPositionForTool(
+ getRelativePointerPositionNormalized(mapImage),
+ "drawing",
+ { type: "line" },
+ gridSize,
+ []
+ );
+ }
+
+ function handleBrushDown() {
+ const brushPosition = getBrushPosition();
+ const { points } = getDefaultShapeData("line", brushPosition);
+ const length = 0;
+ setDrawingShapeData({ length, points });
+ setIsBrushDown(true);
+ }
+
+ function handleBrushMove() {
+ const brushPosition = getBrushPosition();
+ if (isBrushDown && drawingShapeData) {
+ const { points } = getUpdatedShapeData(
+ "line",
+ drawingShapeData,
+ brushPosition,
+ gridSize
+ );
+ const length = Vector2.distance(
+ Vector2.divide(points[0], gridSize),
+ Vector2.divide(points[1], gridSize),
+ selectedToolSettings.type
+ );
+ setDrawingShapeData({
+ length,
+ points,
+ });
+ }
+ }
+
+ function handleBrushUp() {
+ setDrawingShapeData(null);
+ setIsBrushDown(false);
+ }
+
+ interactionEmitter.on("dragStart", handleBrushDown);
+ interactionEmitter.on("drag", handleBrushMove);
+ interactionEmitter.on("dragEnd", handleBrushUp);
+
+ return () => {
+ interactionEmitter.off("dragStart", handleBrushDown);
+ interactionEmitter.off("drag", handleBrushMove);
+ interactionEmitter.off("dragEnd", handleBrushUp);
+ };
+ }, [
+ drawingShapeData,
+ gridSize,
+ isBrushDown,
+ mapStageRef,
+ interactionEmitter,
+ active,
+ selectedToolSettings,
+ ]);
+
+ function renderShape(shapeData) {
+ const linePoints = shapeData.points.reduce(
+ (acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
+ []
+ );
+
+ const lineCenter = Vector2.multiply(
+ Vector2.divide(Vector2.add(shapeData.points[0], shapeData.points[1]), 2),
+ { x: mapWidth, y: mapHeight }
+ );
+
+ return (
+
+
+
+
+
+ );
+ }
+
+ return {drawingShapeData && renderShape(drawingShapeData)};
+}
+
+export default MapMeasure;
diff --git a/src/components/map/controls/MeasureToolSettings.js b/src/components/map/controls/MeasureToolSettings.js
new file mode 100644
index 0000000..22d0f6d
--- /dev/null
+++ b/src/components/map/controls/MeasureToolSettings.js
@@ -0,0 +1,41 @@
+import React from "react";
+import { Flex } from "theme-ui";
+
+import ToolSection from "./ToolSection";
+import MeasureChebyshevIcon from "../../../icons/MeasureChebyshevIcon";
+import MeasureEuclideanIcon from "../../../icons/MeasureEuclideanIcon";
+import MeasureManhattanIcon from "../../../icons/MeasureManhattanIcon";
+
+function MeasureToolSettings({ settings, onSettingChange }) {
+ const tools = [
+ {
+ id: "chebyshev",
+ title: "Grid Distance",
+ isSelected: settings.type === "chebyshev",
+ icon: ,
+ },
+ {
+ id: "euclidean",
+ title: "Line Distance",
+ isSelected: settings.type === "euclidean",
+ icon: ,
+ },
+ {
+ id: "manhattan",
+ title: "City Block Distance",
+ isSelected: settings.type === "manhattan",
+ icon: ,
+ },
+ ];
+
+ return (
+
+ onSettingChange({ type: tool.id })}
+ />
+
+ );
+}
+
+export default MeasureToolSettings;
diff --git a/src/components/map/controls/ToolSection.js b/src/components/map/controls/ToolSection.js
index d99f48a..30c24cb 100644
--- a/src/components/map/controls/ToolSection.js
+++ b/src/components/map/controls/ToolSection.js
@@ -96,4 +96,8 @@ function ToolSection({ collapse, tools, onToolClick }) {
}
}
+ToolSection.defaultProps = {
+ collapse: false,
+};
+
export default ToolSection;
diff --git a/src/helpers/vector2.js b/src/helpers/vector2.js
index 63f3030..de79f9b 100644
--- a/src/helpers/vector2.js
+++ b/src/helpers/vector2.js
@@ -219,3 +219,22 @@ export function pointInPolygon(p, points) {
export function compare(a, b, threshold) {
return lengthSquared(subtract(a, b)) < threshold * threshold;
}
+
+/**
+ * Returns the distance between two vectors
+ * @param {Vector2} a
+ * @param {Vector2} b
+ * @param {string} type - "chebyshev" | "euclidean" | "manhattan"
+ */
+export function distance(a, b, type) {
+ switch (type) {
+ case "chebyshev":
+ return Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y));
+ case "euclidean":
+ return length(subtract(a, b));
+ case "manhattan":
+ return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
+ default:
+ return length(subtract(a, b));
+ }
+}
diff --git a/src/icons/MeasureChebyshevIcon.js b/src/icons/MeasureChebyshevIcon.js
new file mode 100644
index 0000000..5073dd2
--- /dev/null
+++ b/src/icons/MeasureChebyshevIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function MeasureChebyshevIcon() {
+ return (
+
+ );
+}
+
+export default MeasureChebyshevIcon;
diff --git a/src/icons/MeasureEuclideanIcon.js b/src/icons/MeasureEuclideanIcon.js
new file mode 100644
index 0000000..6b29bf5
--- /dev/null
+++ b/src/icons/MeasureEuclideanIcon.js
@@ -0,0 +1,20 @@
+import React from "react";
+
+function MeasureEuclideanIcon() {
+ return (
+
+ );
+}
+
+export default MeasureEuclideanIcon;
diff --git a/src/icons/MeasureManhattanIcon.js b/src/icons/MeasureManhattanIcon.js
new file mode 100644
index 0000000..5371f99
--- /dev/null
+++ b/src/icons/MeasureManhattanIcon.js
@@ -0,0 +1,18 @@
+import React from "react";
+
+function MeasureManhattanIcon() {
+ return (
+
+ );
+}
+
+export default MeasureManhattanIcon;
diff --git a/src/icons/MeasureToolIcon.js b/src/icons/MeasureToolIcon.js
new file mode 100644
index 0000000..d62c53b
--- /dev/null
+++ b/src/icons/MeasureToolIcon.js
@@ -0,0 +1,19 @@
+import React from "react";
+
+function MeasureToolIcon() {
+ return (
+
+ );
+}
+
+export default MeasureToolIcon;