Added measure tool
This commit is contained in:
parent
dfce8dee05
commit
57754e0ac8
@ -7,6 +7,7 @@ import MapDrawing from "./MapDrawing";
|
|||||||
import MapFog from "./MapFog";
|
import MapFog from "./MapFog";
|
||||||
import MapDice from "./MapDice";
|
import MapDice from "./MapDice";
|
||||||
import MapGrid from "./MapGrid";
|
import MapGrid from "./MapGrid";
|
||||||
|
import MapMeasure from "./MapMeasure";
|
||||||
|
|
||||||
import TokenDataContext from "../../contexts/TokenDataContext";
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
import MapLoadingContext from "../../contexts/MapLoadingContext";
|
import MapLoadingContext from "../../contexts/MapLoadingContext";
|
||||||
@ -53,6 +54,9 @@ function Map({
|
|||||||
type: "brush",
|
type: "brush",
|
||||||
useBlending: true,
|
useBlending: true,
|
||||||
},
|
},
|
||||||
|
measure: {
|
||||||
|
type: "chebyshev",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleToolSettingChange(tool, change) {
|
function handleToolSettingChange(tool, change) {
|
||||||
@ -134,6 +138,7 @@ function Map({
|
|||||||
}
|
}
|
||||||
if (!map) {
|
if (!map) {
|
||||||
disabledControls.push("pan");
|
disabledControls.push("pan");
|
||||||
|
disabledControls.push("measure");
|
||||||
}
|
}
|
||||||
if (!allowFogDrawing) {
|
if (!allowFogDrawing) {
|
||||||
disabledControls.push("fog");
|
disabledControls.push("fog");
|
||||||
@ -277,6 +282,14 @@ function Map({
|
|||||||
<MapGrid map={map} gridSize={gridSizeNormalized} />
|
<MapGrid map={map} gridSize={gridSizeNormalized} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mapMeasure = (
|
||||||
|
<MapMeasure
|
||||||
|
active={selectedToolId === "measure"}
|
||||||
|
gridSize={gridSizeNormalized}
|
||||||
|
selectedToolSettings={toolSettings[selectedToolId]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapInteraction
|
<MapInteraction
|
||||||
map={map}
|
map={map}
|
||||||
@ -296,6 +309,7 @@ function Map({
|
|||||||
{mapDrawing}
|
{mapDrawing}
|
||||||
{mapTokens}
|
{mapTokens}
|
||||||
{mapFog}
|
{mapFog}
|
||||||
|
{mapMeasure}
|
||||||
</MapInteraction>
|
</MapInteraction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@ import SelectMapButton from "./SelectMapButton";
|
|||||||
|
|
||||||
import FogToolSettings from "./controls/FogToolSettings";
|
import FogToolSettings from "./controls/FogToolSettings";
|
||||||
import DrawingToolSettings from "./controls/DrawingToolSettings";
|
import DrawingToolSettings from "./controls/DrawingToolSettings";
|
||||||
|
import MeasureToolSettings from "./controls/MeasureToolSettings";
|
||||||
|
|
||||||
import PanToolIcon from "../../icons/PanToolIcon";
|
import PanToolIcon from "../../icons/PanToolIcon";
|
||||||
import FogToolIcon from "../../icons/FogToolIcon";
|
import FogToolIcon from "../../icons/FogToolIcon";
|
||||||
import BrushToolIcon from "../../icons/BrushToolIcon";
|
import BrushToolIcon from "../../icons/BrushToolIcon";
|
||||||
|
import MeasureToolIcon from "../../icons/MeasureToolIcon";
|
||||||
import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
|
import ExpandMoreIcon from "../../icons/ExpandMoreIcon";
|
||||||
|
|
||||||
function MapContols({
|
function MapContols({
|
||||||
@ -47,8 +49,14 @@ function MapContols({
|
|||||||
title: "Drawing Tool",
|
title: "Drawing Tool",
|
||||||
SettingsComponent: DrawingToolSettings,
|
SettingsComponent: DrawingToolSettings,
|
||||||
},
|
},
|
||||||
|
measure: {
|
||||||
|
id: "measure",
|
||||||
|
icon: <MeasureToolIcon />,
|
||||||
|
title: "Measure Tool",
|
||||||
|
SettingsComponent: MeasureToolSettings,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const tools = ["pan", "fog", "drawing"];
|
const tools = ["pan", "fog", "drawing", "measure"];
|
||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{
|
{
|
||||||
@ -63,7 +71,7 @@ function MapContols({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "drawing",
|
id: "tools",
|
||||||
component: tools.map((tool) => (
|
component: tools.map((tool) => (
|
||||||
<RadioIconButton
|
<RadioIconButton
|
||||||
key={tool}
|
key={tool}
|
||||||
|
142
src/components/map/MapMeasure.js
Normal file
142
src/components/map/MapMeasure.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import React, { useContext, useState, useEffect } from "react";
|
||||||
|
import { Group, Line, Text, Label, Tag } from "react-konva";
|
||||||
|
|
||||||
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||||
|
import MapStageContext from "../../contexts/MapStageContext";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getBrushPositionForTool,
|
||||||
|
getDefaultShapeData,
|
||||||
|
getUpdatedShapeData,
|
||||||
|
getStrokeWidth,
|
||||||
|
} from "../../helpers/drawing";
|
||||||
|
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
|
||||||
|
import * as Vector2 from "../../helpers/vector2";
|
||||||
|
|
||||||
|
function MapMeasure({ selectedToolSettings, active, gridSize }) {
|
||||||
|
const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext(
|
||||||
|
MapInteractionContext
|
||||||
|
);
|
||||||
|
const mapStageRef = useContext(MapStageContext);
|
||||||
|
const [drawingShapeData, setDrawingShapeData] = useState(null);
|
||||||
|
const [isBrushDown, setIsBrushDown] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
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 (
|
||||||
|
<Group>
|
||||||
|
<Line
|
||||||
|
points={linePoints}
|
||||||
|
strokeWidth={getStrokeWidth(1.5, gridSize, mapWidth, mapHeight)}
|
||||||
|
stroke="hsla(230, 25%, 18%, 0.8)"
|
||||||
|
lineCap="round"
|
||||||
|
/>
|
||||||
|
<Line
|
||||||
|
points={linePoints}
|
||||||
|
strokeWidth={getStrokeWidth(0.25, gridSize, mapWidth, mapHeight)}
|
||||||
|
stroke="white"
|
||||||
|
lineCap="round"
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
x={lineCenter.x}
|
||||||
|
y={lineCenter.y}
|
||||||
|
offsetX={26}
|
||||||
|
offsetY={26}
|
||||||
|
scaleX={1 / stageScale}
|
||||||
|
scaleY={1 / stageScale}
|
||||||
|
>
|
||||||
|
<Tag fill="hsla(230, 25%, 18%, 0.8)" cornerRadius={4} />
|
||||||
|
<Text
|
||||||
|
text={shapeData.length.toFixed(2)}
|
||||||
|
fill="white"
|
||||||
|
fontSize={24}
|
||||||
|
padding={4}
|
||||||
|
/>
|
||||||
|
</Label>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Group>{drawingShapeData && renderShape(drawingShapeData)}</Group>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapMeasure;
|
41
src/components/map/controls/MeasureToolSettings.js
Normal file
41
src/components/map/controls/MeasureToolSettings.js
Normal file
@ -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: <MeasureChebyshevIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "euclidean",
|
||||||
|
title: "Line Distance",
|
||||||
|
isSelected: settings.type === "euclidean",
|
||||||
|
icon: <MeasureEuclideanIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "manhattan",
|
||||||
|
title: "City Block Distance",
|
||||||
|
isSelected: settings.type === "manhattan",
|
||||||
|
icon: <MeasureManhattanIcon />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex sx={{ alignItems: "center" }}>
|
||||||
|
<ToolSection
|
||||||
|
tools={tools}
|
||||||
|
onToolClick={(tool) => onSettingChange({ type: tool.id })}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureToolSettings;
|
@ -96,4 +96,8 @@ function ToolSection({ collapse, tools, onToolClick }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolSection.defaultProps = {
|
||||||
|
collapse: false,
|
||||||
|
};
|
||||||
|
|
||||||
export default ToolSection;
|
export default ToolSection;
|
||||||
|
@ -219,3 +219,22 @@ export function pointInPolygon(p, points) {
|
|||||||
export function compare(a, b, threshold) {
|
export function compare(a, b, threshold) {
|
||||||
return lengthSquared(subtract(a, b)) < threshold * 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
18
src/icons/MeasureChebyshevIcon.js
Normal file
18
src/icons/MeasureChebyshevIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function MeasureChebyshevIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureChebyshevIcon;
|
20
src/icons/MeasureEuclideanIcon.js
Normal file
20
src/icons/MeasureEuclideanIcon.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function MeasureEuclideanIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M6.36,18.78L6.61,21l1.62-1.54l2.77-7.6c-0.68-0.17-1.28-0.51-1.77-0.98L6.36,18.78z" />
|
||||||
|
<path d="M14.77,10.88c-0.49,0.47-1.1,0.81-1.77,0.98l2.77,7.6L17.39,21l0.26-2.22L14.77,10.88z" />
|
||||||
|
<path d="M14.94,8.6c0.3-1.56-0.6-2.94-1.94-3.42V4c0-0.55-0.45-1-1-1h0c-0.55,0-1,0.45-1,1v1.18C9.84,5.6,9,6.7,9,8 c0,1.84,1.66,3.3,3.56,2.95C13.74,10.73,14.71,9.78,14.94,8.6z M12,9c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1s1,0.45,1,1 C13,8.55,12.55,9,12,9z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureEuclideanIcon;
|
18
src/icons/MeasureManhattanIcon.js
Normal file
18
src/icons/MeasureManhattanIcon.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function MeasureManhattanIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M17,11V5c0-1.1-0.9-2-2-2H9C7.9,3,7,3.9,7,5v2H5C3.9,7,3,7.9,3,9v10c0,1.1,0.9,2,2,2h5c0.55,0,1-0.45,1-1v-3h2v3 c0,0.55,0.45,1,1,1h5c1.1,0,2-0.9,2-2v-6c0-1.1-0.9-2-2-2H17z M7,19H5v-2h2V19z M7,15H5v-2h2V15z M7,11H5V9h2V11z M11,15H9v-2h2V15 z M11,11H9V9h2V11z M11,7H9V5h2V7z M15,15h-2v-2h2V15z M15,11h-2V9h2V11z M15,7h-2V5h2V7z M19,19h-2v-2h2V19z M19,15h-2v-2h2V15z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureManhattanIcon;
|
19
src/icons/MeasureToolIcon.js
Normal file
19
src/icons/MeasureToolIcon.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
function MeasureToolIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="24"
|
||||||
|
fill="currentcolor"
|
||||||
|
transform="scale(-1 1)"
|
||||||
|
>
|
||||||
|
<path d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M17.66,17.66l-0.71,0.71c-0.2,0.2-0.51,0.2-0.71,0l0,0c-0.2-0.2-0.2-0.51,0-0.71l0.71-0.71l-1.94-1.94l-0.71,0.71 c-0.2,0.2-0.51,0.2-0.71,0l0,0c-0.2-0.2-0.2-0.51,0-0.71l0.71-0.71l-1.94-1.94l-0.71,0.71c-0.2,0.2-0.51,0.2-0.71,0l0,0 c-0.2-0.2-0.2-0.51,0-0.71l0.71-0.71L9.7,9.7l-0.71,0.71c-0.2,0.2-0.51,0.2-0.71,0l0,0c-0.2-0.2-0.2-0.51,0-0.71l0.71-0.71 L7.05,7.05L6.34,7.76c-0.2,0.2-0.51,0.2-0.71,0l0,0c-0.2-0.2-0.2-0.51,0-0.71l0.71-0.71L4.85,4.85C4.54,4.54,4,4.76,4,5.21V18 c0,1.1,0.9,2,2,2h12.79c0.45,0,0.67-0.54,0.35-0.85L17.66,17.66z M7,16v-4.76L12.76,17H8C7.45,17,7,16.55,7,16z" />{" "}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MeasureToolIcon;
|
Loading…
Reference in New Issue
Block a user