2021-02-06 13:32:38 +11:00
|
|
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
2020-04-28 22:05:47 +10:00
|
|
|
import shortid from "shortid";
|
2021-02-16 08:27:39 +11:00
|
|
|
import { Group, Rect, Line } from "react-konva";
|
2020-05-22 17:22:32 +10:00
|
|
|
import useImage from "use-image";
|
|
|
|
|
|
|
|
|
|
import diagonalPattern from "../../images/DiagonalPattern.png";
|
|
|
|
|
|
2021-02-06 13:32:38 +11:00
|
|
|
import { useMapInteraction } from "../../contexts/MapInteractionContext";
|
|
|
|
|
import { useMapStage } from "../../contexts/MapStageContext";
|
|
|
|
|
import { useGrid } from "../../contexts/GridContext";
|
|
|
|
|
import { useKeyboard } from "../../contexts/KeyboardContext";
|
2020-04-28 22:05:47 +10:00
|
|
|
|
2021-02-04 15:06:34 +11:00
|
|
|
import Vector2 from "../../helpers/Vector2";
|
2021-02-11 19:57:34 +11:00
|
|
|
import {
|
|
|
|
|
simplifyPoints,
|
|
|
|
|
mergeFogShapes,
|
|
|
|
|
getFogShapesBoundingBoxes,
|
|
|
|
|
getGuidesFromBoundingBoxes,
|
|
|
|
|
getGuidesFromGridCell,
|
|
|
|
|
findBestGuides,
|
|
|
|
|
} from "../../helpers/drawing";
|
2020-05-22 17:22:32 +10:00
|
|
|
import colors from "../../helpers/colors";
|
2021-02-09 14:13:08 +11:00
|
|
|
import {
|
|
|
|
|
HoleyLine,
|
|
|
|
|
Tick,
|
|
|
|
|
getRelativePointerPosition,
|
|
|
|
|
} from "../../helpers/konva";
|
2021-02-04 15:06:34 +11:00
|
|
|
|
2021-02-16 08:27:39 +11:00
|
|
|
import SubtractShapeAction from "../../actions/SubtractShapeAction";
|
|
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
import useSetting from "../../hooks/useSetting";
|
2020-04-29 20:40:34 +10:00
|
|
|
|
2020-04-28 22:05:47 +10:00
|
|
|
function MapFog({
|
2020-08-07 12:28:50 +10:00
|
|
|
map,
|
2020-04-28 22:05:47 +10:00
|
|
|
shapes,
|
2021-02-16 08:27:39 +11:00
|
|
|
onShapesAdd,
|
|
|
|
|
onShapesCut,
|
2020-05-31 12:12:16 +10:00
|
|
|
onShapesRemove,
|
|
|
|
|
onShapesEdit,
|
2020-08-04 14:51:31 +10:00
|
|
|
active,
|
|
|
|
|
toolSettings,
|
2021-01-02 12:17:27 +11:00
|
|
|
editable,
|
2020-04-28 22:05:47 +10:00
|
|
|
}) {
|
2021-02-06 13:32:38 +11:00
|
|
|
const {
|
|
|
|
|
stageScale,
|
|
|
|
|
mapWidth,
|
|
|
|
|
mapHeight,
|
|
|
|
|
interactionEmitter,
|
|
|
|
|
} = useMapInteraction();
|
2021-02-11 19:57:34 +11:00
|
|
|
const {
|
|
|
|
|
grid,
|
|
|
|
|
gridCellNormalizedSize,
|
|
|
|
|
gridCellPixelSize,
|
|
|
|
|
gridStrokeWidth,
|
|
|
|
|
gridCellPixelOffset,
|
|
|
|
|
gridOffset,
|
|
|
|
|
} = useGrid();
|
|
|
|
|
const [gridSnappingSensitivity] = useSetting("map.gridSnappingSensitivity");
|
2021-02-06 13:32:38 +11:00
|
|
|
const mapStageRef = useMapStage();
|
|
|
|
|
|
2020-04-28 22:05:47 +10:00
|
|
|
const [drawingShape, setDrawingShape] = useState(null);
|
2020-05-31 12:12:16 +10:00
|
|
|
const [isBrushDown, setIsBrushDown] = useState(false);
|
|
|
|
|
const [editingShapes, setEditingShapes] = useState([]);
|
2020-04-28 22:05:47 +10:00
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
// Shapes that have been merged for fog
|
|
|
|
|
const [fogShapes, setFogShapes] = useState(shapes);
|
|
|
|
|
// Bounding boxes for guides
|
|
|
|
|
const [fogShapeBoundingBoxes, setFogShapeBoundingBoxes] = useState([]);
|
|
|
|
|
const [guides, setGuides] = useState([]);
|
|
|
|
|
|
2020-04-28 22:05:47 +10:00
|
|
|
const shouldHover =
|
2020-08-04 14:51:31 +10:00
|
|
|
active &&
|
2021-01-02 12:17:27 +11:00
|
|
|
editable &&
|
2020-08-04 14:51:31 +10:00
|
|
|
(toolSettings.type === "toggle" || toolSettings.type === "remove");
|
2020-04-28 22:05:47 +10:00
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
const shouldRenderGuides =
|
|
|
|
|
active &&
|
|
|
|
|
editable &&
|
2021-02-16 08:27:39 +11:00
|
|
|
(toolSettings.type === "rectangle" || toolSettings.type === "polygon");
|
2020-04-29 18:21:44 +10:00
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
const [patternImage] = useImage(diagonalPattern);
|
2021-02-09 14:13:08 +11:00
|
|
|
|
2020-06-19 18:04:58 +10:00
|
|
|
useEffect(() => {
|
2021-01-02 12:17:27 +11:00
|
|
|
if (!active || !editable) {
|
2020-06-19 18:04:58 +10:00
|
|
|
return;
|
2020-05-31 12:12:16 +10:00
|
|
|
}
|
2020-06-19 18:04:58 +10:00
|
|
|
|
|
|
|
|
const mapStage = mapStageRef.current;
|
|
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
function getBrushPosition(snapping = true) {
|
2021-02-09 14:13:08 +11:00
|
|
|
const mapImage = mapStage.findOne("#mapImage");
|
|
|
|
|
let position = getRelativePointerPosition(mapImage);
|
2021-02-16 08:27:39 +11:00
|
|
|
if (snapping && shouldRenderGuides) {
|
|
|
|
|
for (let guide of guides) {
|
|
|
|
|
if (guide.orientation === "vertical") {
|
|
|
|
|
position.x = guide.start.x * mapWidth;
|
|
|
|
|
}
|
|
|
|
|
if (guide.orientation === "horizontal") {
|
|
|
|
|
position.y = guide.start.y * mapHeight;
|
2021-02-11 19:57:34 +11:00
|
|
|
}
|
|
|
|
|
}
|
2021-02-09 14:13:08 +11:00
|
|
|
}
|
|
|
|
|
return Vector2.divide(position, {
|
|
|
|
|
x: mapImage.width(),
|
|
|
|
|
y: mapImage.height(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-01-22 15:00:31 +11:00
|
|
|
|
|
|
|
|
function handleBrushDown() {
|
2020-08-04 14:51:31 +10:00
|
|
|
if (toolSettings.type === "brush") {
|
2021-02-11 19:57:34 +11:00
|
|
|
const brushPosition = getBrushPosition();
|
2020-06-19 18:04:58 +10:00
|
|
|
setDrawingShape({
|
|
|
|
|
type: "fog",
|
|
|
|
|
data: {
|
|
|
|
|
points: [brushPosition],
|
|
|
|
|
holes: [],
|
|
|
|
|
},
|
|
|
|
|
strokeWidth: 0.5,
|
2020-12-03 16:52:24 +11:00
|
|
|
color: toolSettings.useFogCut ? "red" : "black",
|
2020-06-19 18:04:58 +10:00
|
|
|
id: shortid.generate(),
|
|
|
|
|
visible: true,
|
|
|
|
|
});
|
2020-05-22 17:22:32 +10:00
|
|
|
}
|
2020-12-03 17:32:41 +11:00
|
|
|
if (toolSettings.type === "rectangle") {
|
2021-02-11 19:57:34 +11:00
|
|
|
const brushPosition = getBrushPosition();
|
2020-12-03 17:32:41 +11:00
|
|
|
setDrawingShape({
|
|
|
|
|
type: "fog",
|
|
|
|
|
data: {
|
|
|
|
|
points: [
|
|
|
|
|
brushPosition,
|
|
|
|
|
brushPosition,
|
|
|
|
|
brushPosition,
|
|
|
|
|
brushPosition,
|
|
|
|
|
],
|
|
|
|
|
holes: [],
|
|
|
|
|
},
|
|
|
|
|
strokeWidth: 0.5,
|
|
|
|
|
color: toolSettings.useFogCut ? "red" : "black",
|
|
|
|
|
id: shortid.generate(),
|
|
|
|
|
visible: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-06-19 18:04:58 +10:00
|
|
|
setIsBrushDown(true);
|
|
|
|
|
}
|
2020-05-22 17:22:32 +10:00
|
|
|
|
2020-06-19 18:04:58 +10:00
|
|
|
function handleBrushMove() {
|
2020-08-04 14:51:31 +10:00
|
|
|
if (toolSettings.type === "brush" && isBrushDown && drawingShape) {
|
2021-02-09 14:13:08 +11:00
|
|
|
const brushPosition = getBrushPosition();
|
2020-06-19 18:04:58 +10:00
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
const prevPoints = prevShape.data.points;
|
|
|
|
|
if (
|
2021-02-04 15:06:34 +11:00
|
|
|
Vector2.compare(
|
2020-06-19 18:04:58 +10:00
|
|
|
prevPoints[prevPoints.length - 1],
|
|
|
|
|
brushPosition,
|
|
|
|
|
0.001
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return prevShape;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: {
|
|
|
|
|
...prevShape.data,
|
|
|
|
|
points: [...prevPoints, brushPosition],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
2020-04-28 22:05:47 +10:00
|
|
|
}
|
2020-12-03 17:32:41 +11:00
|
|
|
if (toolSettings.type === "rectangle" && isBrushDown && drawingShape) {
|
2021-01-22 15:00:31 +11:00
|
|
|
const prevPoints = drawingShape.data.points;
|
2021-02-09 14:13:08 +11:00
|
|
|
const brushPosition = getBrushPosition();
|
2020-12-03 17:32:41 +11:00
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: {
|
|
|
|
|
...prevShape.data,
|
|
|
|
|
points: [
|
|
|
|
|
prevPoints[0],
|
|
|
|
|
{ x: brushPosition.x, y: prevPoints[1].y },
|
|
|
|
|
brushPosition,
|
|
|
|
|
{ x: prevPoints[3].x, y: brushPosition.y },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-06-19 18:04:58 +10:00
|
|
|
}
|
2020-04-28 22:05:47 +10:00
|
|
|
|
2020-06-19 18:04:58 +10:00
|
|
|
function handleBrushUp() {
|
2020-12-03 17:32:41 +11:00
|
|
|
if (
|
2021-02-16 08:27:39 +11:00
|
|
|
(toolSettings.type === "brush" || toolSettings.type === "rectangle") &&
|
|
|
|
|
drawingShape
|
2020-12-03 17:32:41 +11:00
|
|
|
) {
|
2020-12-03 16:52:24 +11:00
|
|
|
const cut = toolSettings.useFogCut;
|
2021-02-16 08:27:39 +11:00
|
|
|
|
|
|
|
|
let drawingShapes = [drawingShape];
|
|
|
|
|
if (!toolSettings.multilayer) {
|
|
|
|
|
const shapesToSubtract = shapes.filter((shape) =>
|
|
|
|
|
cut ? !shape.visible : shape.visible
|
|
|
|
|
);
|
|
|
|
|
const subtractAction = new SubtractShapeAction(
|
|
|
|
|
mergeFogShapes(shapesToSubtract, !cut)
|
|
|
|
|
);
|
|
|
|
|
const state = subtractAction.execute({
|
|
|
|
|
[drawingShape.id]: drawingShape,
|
|
|
|
|
});
|
|
|
|
|
drawingShapes = Object.values(state)
|
|
|
|
|
.filter((shape) => shape.data.points.length > 2)
|
|
|
|
|
.map((shape) => ({ ...shape, id: shortid.generate() }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (drawingShapes.length > 0) {
|
|
|
|
|
drawingShapes = drawingShapes.map((shape) => {
|
|
|
|
|
let shapeData = {};
|
|
|
|
|
if (cut) {
|
|
|
|
|
shapeData = { id: shape.id, type: shape.type };
|
|
|
|
|
} else {
|
|
|
|
|
shapeData = { ...shape, color: "black" };
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...shapeData,
|
|
|
|
|
data: {
|
|
|
|
|
...shape.data,
|
|
|
|
|
points: simplifyPoints(
|
|
|
|
|
shape.data.points,
|
|
|
|
|
gridCellNormalizedSize,
|
|
|
|
|
// Downscale fog as smoothing doesn't currently work with edge snapping
|
|
|
|
|
Math.max(stageScale, 1) / 2
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2020-12-03 16:52:24 +11:00
|
|
|
if (cut) {
|
2021-02-16 08:27:39 +11:00
|
|
|
onShapesCut(drawingShapes);
|
2020-06-19 18:04:58 +10:00
|
|
|
} else {
|
2021-02-16 08:27:39 +11:00
|
|
|
onShapesAdd(drawingShapes);
|
2020-06-09 12:45:52 +10:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-25 15:07:12 +10:00
|
|
|
setDrawingShape(null);
|
2020-05-22 17:22:32 +10:00
|
|
|
}
|
|
|
|
|
|
2020-11-20 09:24:28 +11:00
|
|
|
eraseHoveredShapes();
|
2020-06-19 18:04:58 +10:00
|
|
|
|
|
|
|
|
setIsBrushDown(false);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 17:04:35 +11:00
|
|
|
function handlePointerClick() {
|
2020-08-04 14:51:31 +10:00
|
|
|
if (toolSettings.type === "polygon") {
|
2021-02-09 14:13:08 +11:00
|
|
|
const brushPosition = getBrushPosition();
|
2020-06-24 09:27:20 +10:00
|
|
|
setDrawingShape((prevDrawingShape) => {
|
|
|
|
|
if (prevDrawingShape) {
|
|
|
|
|
return {
|
|
|
|
|
...prevDrawingShape,
|
|
|
|
|
data: {
|
|
|
|
|
...prevDrawingShape.data,
|
|
|
|
|
points: [...prevDrawingShape.data.points, brushPosition],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
type: "fog",
|
|
|
|
|
data: {
|
|
|
|
|
points: [brushPosition, brushPosition],
|
|
|
|
|
holes: [],
|
|
|
|
|
},
|
|
|
|
|
strokeWidth: 0.5,
|
2020-12-03 16:52:24 +11:00
|
|
|
color: toolSettings.useFogCut ? "red" : "black",
|
2020-06-24 09:27:20 +10:00
|
|
|
id: shortid.generate(),
|
|
|
|
|
visible: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 17:04:35 +11:00
|
|
|
function handlePointerMove() {
|
2021-02-11 19:57:34 +11:00
|
|
|
if (
|
|
|
|
|
active &&
|
2021-02-16 08:27:39 +11:00
|
|
|
(toolSettings.type === "polygon" || toolSettings.type === "rectangle")
|
2021-02-11 19:57:34 +11:00
|
|
|
) {
|
|
|
|
|
let guides = [];
|
|
|
|
|
const brushPosition = getBrushPosition(false);
|
|
|
|
|
const absoluteBrushPosition = Vector2.multiply(brushPosition, {
|
|
|
|
|
x: mapWidth,
|
|
|
|
|
y: mapHeight,
|
2020-06-24 09:27:20 +10:00
|
|
|
});
|
2021-02-11 19:57:34 +11:00
|
|
|
if (map.snapToGrid) {
|
|
|
|
|
guides.push(
|
|
|
|
|
...getGuidesFromGridCell(
|
|
|
|
|
absoluteBrushPosition,
|
|
|
|
|
grid,
|
|
|
|
|
gridCellPixelSize,
|
|
|
|
|
gridOffset,
|
|
|
|
|
gridCellPixelOffset,
|
|
|
|
|
gridSnappingSensitivity,
|
|
|
|
|
{ x: mapWidth, y: mapHeight }
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guides.push(
|
|
|
|
|
...getGuidesFromBoundingBoxes(
|
|
|
|
|
brushPosition,
|
|
|
|
|
fogShapeBoundingBoxes,
|
|
|
|
|
gridCellNormalizedSize,
|
|
|
|
|
gridSnappingSensitivity
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setGuides(findBestGuides(brushPosition, guides));
|
|
|
|
|
}
|
|
|
|
|
if (toolSettings.type === "polygon") {
|
|
|
|
|
const brushPosition = getBrushPosition();
|
|
|
|
|
if (toolSettings.type === "polygon" && drawingShape) {
|
|
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
if (!prevShape) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
|
|
|
|
data: {
|
|
|
|
|
...prevShape.data,
|
|
|
|
|
points: [...prevShape.data.points.slice(0, -1), brushPosition],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-06-24 09:27:20 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-22 17:04:35 +11:00
|
|
|
function handelTouchEnd() {
|
|
|
|
|
setGuides([]);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-24 09:27:20 +10:00
|
|
|
interactionEmitter.on("dragStart", handleBrushDown);
|
|
|
|
|
interactionEmitter.on("drag", handleBrushMove);
|
|
|
|
|
interactionEmitter.on("dragEnd", handleBrushUp);
|
|
|
|
|
// Use mouse events for polygon and erase to allow for single clicks
|
2021-02-22 17:04:35 +11:00
|
|
|
mapStage.on("mousedown touchstart", handlePointerMove);
|
|
|
|
|
mapStage.on("mousemove touchmove", handlePointerMove);
|
|
|
|
|
mapStage.on("click tap", handlePointerClick);
|
|
|
|
|
mapStage.on("touchend", handelTouchEnd);
|
2020-06-19 18:04:58 +10:00
|
|
|
|
|
|
|
|
return () => {
|
2020-06-24 09:27:20 +10:00
|
|
|
interactionEmitter.off("dragStart", handleBrushDown);
|
|
|
|
|
interactionEmitter.off("drag", handleBrushMove);
|
|
|
|
|
interactionEmitter.off("dragEnd", handleBrushUp);
|
2021-02-22 17:04:35 +11:00
|
|
|
mapStage.off("mousedown touchstart", handlePointerMove);
|
|
|
|
|
mapStage.off("mousemove touchmove", handlePointerMove);
|
|
|
|
|
mapStage.off("click tap", handlePointerClick);
|
|
|
|
|
mapStage.off("touchend", handelTouchEnd);
|
2020-06-19 18:04:58 +10:00
|
|
|
};
|
2020-08-07 12:28:50 +10:00
|
|
|
});
|
2020-05-22 17:22:32 +10:00
|
|
|
|
2020-06-21 16:09:37 +10:00
|
|
|
const finishDrawingPolygon = useCallback(() => {
|
2020-12-03 16:52:24 +11:00
|
|
|
const cut = toolSettings.useFogCut;
|
2021-02-16 08:27:39 +11:00
|
|
|
|
|
|
|
|
let polygonShape = {
|
|
|
|
|
id: drawingShape.id,
|
|
|
|
|
type: drawingShape.type,
|
|
|
|
|
data: {
|
|
|
|
|
...drawingShape.data,
|
|
|
|
|
// Remove the last point as it hasn't been placed yet
|
|
|
|
|
points: drawingShape.data.points.slice(0, -1),
|
|
|
|
|
},
|
2020-06-21 16:09:37 +10:00
|
|
|
};
|
2021-02-16 08:27:39 +11:00
|
|
|
|
|
|
|
|
let polygonShapes = [polygonShape];
|
|
|
|
|
if (!toolSettings.multilayer) {
|
|
|
|
|
const shapesToSubtract = shapes.filter((shape) =>
|
|
|
|
|
cut ? !shape.visible : shape.visible
|
|
|
|
|
);
|
|
|
|
|
const subtractAction = new SubtractShapeAction(
|
|
|
|
|
mergeFogShapes(shapesToSubtract, !cut)
|
|
|
|
|
);
|
|
|
|
|
const state = subtractAction.execute({
|
|
|
|
|
[polygonShape.id]: polygonShape,
|
2020-06-21 16:09:37 +10:00
|
|
|
});
|
2021-02-16 08:27:39 +11:00
|
|
|
polygonShapes = Object.values(state)
|
|
|
|
|
.filter((shape) => shape.data.points.length > 2)
|
|
|
|
|
.map((shape) => ({ ...shape, id: shortid.generate() }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (polygonShapes.length > 0) {
|
|
|
|
|
if (cut) {
|
|
|
|
|
onShapesCut(polygonShapes);
|
|
|
|
|
} else {
|
|
|
|
|
onShapesAdd(
|
|
|
|
|
polygonShapes.map((shape) => ({
|
|
|
|
|
...drawingShape,
|
|
|
|
|
data: shape.data,
|
|
|
|
|
id: shape.id,
|
|
|
|
|
color: "black",
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-06-21 16:09:37 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setDrawingShape(null);
|
2021-02-16 08:27:39 +11:00
|
|
|
}, [toolSettings, drawingShape, onShapesCut, onShapesAdd, shapes]);
|
2020-06-21 16:09:37 +10:00
|
|
|
|
|
|
|
|
// Add keyboard shortcuts
|
2020-09-30 13:26:39 +10:00
|
|
|
function handleKeyDown({ key }) {
|
|
|
|
|
if (key === "Enter" && toolSettings.type === "polygon" && drawingShape) {
|
|
|
|
|
finishDrawingPolygon();
|
2020-06-24 18:05:33 +10:00
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
if (key === "Escape" && drawingShape) {
|
|
|
|
|
setDrawingShape(null);
|
2020-06-21 16:09:37 +10:00
|
|
|
}
|
2021-02-16 08:38:40 +11:00
|
|
|
// Remove last point from polygon shape if delete pressed
|
|
|
|
|
if (
|
|
|
|
|
(key === "Backspace" || key === "Delete") &&
|
|
|
|
|
drawingShape &&
|
|
|
|
|
toolSettings.type === "polygon"
|
|
|
|
|
) {
|
|
|
|
|
if (drawingShape.data.points.length > 2) {
|
|
|
|
|
setDrawingShape((drawingShape) => ({
|
|
|
|
|
...drawingShape,
|
|
|
|
|
data: {
|
|
|
|
|
...drawingShape.data,
|
|
|
|
|
points: [
|
|
|
|
|
// Shift last point to previous point
|
|
|
|
|
...drawingShape.data.points.slice(0, -2),
|
|
|
|
|
...drawingShape.data.points.slice(-1),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
setDrawingShape(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-30 13:26:39 +10:00
|
|
|
}
|
2020-06-21 16:09:37 +10:00
|
|
|
|
2020-12-03 16:52:24 +11:00
|
|
|
useKeyboard(handleKeyDown);
|
2020-06-24 18:05:33 +10:00
|
|
|
|
2020-12-03 16:52:24 +11:00
|
|
|
// Update shape color when useFogCut changes
|
|
|
|
|
useEffect(() => {
|
2020-09-30 13:26:39 +10:00
|
|
|
setDrawingShape((prevShape) => {
|
|
|
|
|
if (!prevShape) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
...prevShape,
|
2020-12-03 16:52:24 +11:00
|
|
|
color: toolSettings.useFogCut ? "red" : "black",
|
2020-09-30 13:26:39 +10:00
|
|
|
};
|
|
|
|
|
});
|
2020-12-03 16:52:24 +11:00
|
|
|
}, [toolSettings.useFogCut]);
|
2020-06-21 16:09:37 +10:00
|
|
|
|
2020-11-20 09:24:28 +11:00
|
|
|
function eraseHoveredShapes() {
|
|
|
|
|
// Erase
|
|
|
|
|
if (editingShapes.length > 0) {
|
|
|
|
|
if (toolSettings.type === "remove") {
|
|
|
|
|
onShapesRemove(editingShapes.map((shape) => shape.id));
|
|
|
|
|
} else if (toolSettings.type === "toggle") {
|
|
|
|
|
onShapesEdit(
|
|
|
|
|
editingShapes.map((shape) => ({
|
2021-01-23 15:44:25 +11:00
|
|
|
id: shape.id,
|
2020-11-20 09:24:28 +11:00
|
|
|
visible: !shape.visible,
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
setEditingShapes([]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-31 12:12:16 +10:00
|
|
|
function handleShapeOver(shape, isDown) {
|
|
|
|
|
if (shouldHover && isDown) {
|
|
|
|
|
if (editingShapes.findIndex((s) => s.id === shape.id) === -1) {
|
|
|
|
|
setEditingShapes((prevShapes) => [...prevShapes, shape]);
|
2020-04-29 20:40:34 +10:00
|
|
|
}
|
2020-04-28 22:05:47 +10:00
|
|
|
}
|
2020-05-22 17:22:32 +10:00
|
|
|
}
|
2020-04-28 22:05:47 +10:00
|
|
|
|
2020-06-14 12:27:05 +10:00
|
|
|
function reducePoints(acc, point) {
|
|
|
|
|
return [...acc, point.x * mapWidth, point.y * mapHeight];
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-22 17:22:32 +10:00
|
|
|
function renderShape(shape) {
|
2020-06-14 12:27:05 +10:00
|
|
|
const points = shape.data.points.reduce(reducePoints, []);
|
|
|
|
|
const holes =
|
|
|
|
|
shape.data.holes &&
|
|
|
|
|
shape.data.holes.map((hole) => hole.reduce(reducePoints, []));
|
2020-05-22 17:22:32 +10:00
|
|
|
return (
|
2020-06-14 12:27:05 +10:00
|
|
|
<HoleyLine
|
2020-05-22 17:22:32 +10:00
|
|
|
key={shape.id}
|
2020-05-31 12:12:16 +10:00
|
|
|
onMouseMove={() => handleShapeOver(shape, isBrushDown)}
|
|
|
|
|
onTouchOver={() => handleShapeOver(shape, isBrushDown)}
|
|
|
|
|
onMouseDown={() => handleShapeOver(shape, true)}
|
|
|
|
|
onTouchStart={() => handleShapeOver(shape, true)}
|
2020-11-20 09:24:28 +11:00
|
|
|
onMouseUp={eraseHoveredShapes}
|
|
|
|
|
onTouchEnd={eraseHoveredShapes}
|
2020-06-14 12:27:05 +10:00
|
|
|
points={points}
|
2021-02-09 08:00:24 +11:00
|
|
|
stroke={
|
|
|
|
|
editable ? colors.lightGray : colors[shape.color] || shape.color
|
|
|
|
|
}
|
2020-05-22 17:22:32 +10:00
|
|
|
fill={colors[shape.color] || shape.color}
|
|
|
|
|
closed
|
|
|
|
|
lineCap="round"
|
2020-08-07 14:05:55 +10:00
|
|
|
lineJoin="round"
|
2021-02-22 10:41:34 +11:00
|
|
|
strokeWidth={gridStrokeWidth * shape.strokeWidth}
|
2021-02-11 19:57:34 +11:00
|
|
|
opacity={editable ? (!shape.visible ? 0.2 : 0.5) : 1}
|
2020-05-22 17:22:32 +10:00
|
|
|
fillPatternImage={patternImage}
|
2020-08-04 14:51:31 +10:00
|
|
|
fillPriority={active && !shape.visible ? "pattern" : "color"}
|
2020-06-14 12:27:05 +10:00
|
|
|
holes={holes}
|
2020-08-04 14:51:31 +10:00
|
|
|
// Disable collision if the fog is transparent and we're not editing it
|
|
|
|
|
// This allows tokens to be moved under the fog
|
2021-01-02 12:17:27 +11:00
|
|
|
hitFunc={editable && !active ? () => {} : undefined}
|
2020-04-28 22:05:47 +10:00
|
|
|
/>
|
2020-05-22 17:22:32 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-31 12:12:16 +10:00
|
|
|
function renderEditingShape(shape) {
|
|
|
|
|
const editingShape = {
|
|
|
|
|
...shape,
|
|
|
|
|
color: "#BB99FF",
|
|
|
|
|
};
|
|
|
|
|
return renderShape(editingShape);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-21 16:09:37 +10:00
|
|
|
function renderPolygonAcceptTick(shape) {
|
|
|
|
|
if (shape.data.points.length === 0) {
|
2020-06-25 18:57:12 +10:00
|
|
|
return null;
|
2020-06-21 16:09:37 +10:00
|
|
|
}
|
2020-06-24 09:27:20 +10:00
|
|
|
const isCross = shape.data.points.length < 4;
|
2020-06-21 16:09:37 +10:00
|
|
|
return (
|
|
|
|
|
<Tick
|
|
|
|
|
x={shape.data.points[0].x * mapWidth}
|
|
|
|
|
y={shape.data.points[0].y * mapHeight}
|
|
|
|
|
scale={1 / stageScale}
|
2020-06-24 09:27:20 +10:00
|
|
|
cross={isCross}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.cancelBubble = true;
|
|
|
|
|
if (isCross) {
|
2020-06-21 16:09:37 +10:00
|
|
|
setDrawingShape(null);
|
|
|
|
|
} else {
|
|
|
|
|
finishDrawingPolygon();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-11 19:57:34 +11:00
|
|
|
function renderGuides() {
|
|
|
|
|
return guides.map((guide, index) => (
|
|
|
|
|
<Line
|
|
|
|
|
points={[
|
|
|
|
|
guide.start.x * mapWidth,
|
|
|
|
|
guide.start.y * mapHeight,
|
|
|
|
|
guide.end.x * mapWidth,
|
|
|
|
|
guide.end.y * mapHeight,
|
|
|
|
|
]}
|
|
|
|
|
stroke="hsl(260, 100%, 80%)"
|
|
|
|
|
key={index}
|
|
|
|
|
strokeWidth={gridStrokeWidth * 0.25}
|
|
|
|
|
lineCap="round"
|
|
|
|
|
lineJoin="round"
|
|
|
|
|
/>
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-02 12:17:27 +11:00
|
|
|
useEffect(() => {
|
|
|
|
|
function shapeVisible(shape) {
|
|
|
|
|
return (active && !toolSettings.preview) || shape.visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (editable) {
|
2021-02-11 19:57:34 +11:00
|
|
|
const visibleShapes = shapes.filter(shapeVisible);
|
|
|
|
|
setFogShapeBoundingBoxes(getFogShapesBoundingBoxes(visibleShapes));
|
|
|
|
|
setFogShapes(visibleShapes);
|
2021-01-02 12:17:27 +11:00
|
|
|
} else {
|
2021-02-11 19:57:34 +11:00
|
|
|
setFogShapes(mergeFogShapes(shapes));
|
2021-01-02 12:17:27 +11:00
|
|
|
}
|
|
|
|
|
}, [shapes, editable, active, toolSettings]);
|
|
|
|
|
|
|
|
|
|
const fogGroupRef = useRef();
|
|
|
|
|
|
2020-05-22 17:22:32 +10:00
|
|
|
return (
|
|
|
|
|
<Group>
|
2021-01-02 12:17:27 +11:00
|
|
|
<Group ref={fogGroupRef}>
|
|
|
|
|
{/* Render a blank shape so cache works with no fog shapes */}
|
|
|
|
|
<Rect width={1} height={1} />
|
|
|
|
|
{fogShapes.map(renderShape)}
|
|
|
|
|
</Group>
|
2021-02-11 19:57:34 +11:00
|
|
|
{shouldRenderGuides && renderGuides()}
|
2020-05-22 17:22:32 +10:00
|
|
|
{drawingShape && renderShape(drawingShape)}
|
2020-06-21 16:09:37 +10:00
|
|
|
{drawingShape &&
|
2020-08-04 14:51:31 +10:00
|
|
|
toolSettings &&
|
|
|
|
|
toolSettings.type === "polygon" &&
|
2020-06-21 16:09:37 +10:00
|
|
|
renderPolygonAcceptTick(drawingShape)}
|
2020-05-31 12:12:16 +10:00
|
|
|
{editingShapes.length > 0 && editingShapes.map(renderEditingShape)}
|
2020-05-22 17:22:32 +10:00
|
|
|
</Group>
|
2020-04-28 22:05:47 +10:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default MapFog;
|