Readded map fog
This commit is contained in:
parent
a81eed1a48
commit
a6768727fe
@ -5,6 +5,7 @@ import MapControls from "./MapControls";
|
|||||||
import MapInteraction from "./MapInteraction";
|
import MapInteraction from "./MapInteraction";
|
||||||
import MapToken from "./MapToken";
|
import MapToken from "./MapToken";
|
||||||
import MapDrawing from "./MapDrawing";
|
import MapDrawing from "./MapDrawing";
|
||||||
|
import MapFog from "./MapFog";
|
||||||
|
|
||||||
import TokenDataContext from "../../contexts/TokenDataContext";
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
import TokenMenu from "../token/TokenMenu";
|
import TokenMenu from "../token/TokenMenu";
|
||||||
@ -85,8 +86,29 @@ function Map({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [mapShapes, setMapShapes] = useState([]);
|
const [mapShapes, setMapShapes] = useState([]);
|
||||||
|
|
||||||
|
function handleMapShapeAdd(shape) {
|
||||||
|
onMapDraw({ type: "add", shapes: [shape] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMapShapeRemove(shapeId) {
|
||||||
|
onMapDraw({ type: "remove", shapeIds: [shapeId] });
|
||||||
|
}
|
||||||
|
|
||||||
const [fogShapes, setFogShapes] = useState([]);
|
const [fogShapes, setFogShapes] = useState([]);
|
||||||
|
|
||||||
|
function handleFogShapeAdd(shape) {
|
||||||
|
onFogDraw({ type: "add", shapes: [shape] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFogShapeRemove(shapeId) {
|
||||||
|
onFogDraw({ type: "remove", shapeIds: [shapeId] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFogShapeEdit(shape) {
|
||||||
|
onFogDraw({ type: "edit", shapes: [shape] });
|
||||||
|
}
|
||||||
|
|
||||||
// Replay the draw actions and convert them to shapes for the map drawing
|
// Replay the draw actions and convert them to shapes for the map drawing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!mapState) {
|
if (!mapState) {
|
||||||
@ -116,14 +138,6 @@ function Map({
|
|||||||
);
|
);
|
||||||
}, [mapState]);
|
}, [mapState]);
|
||||||
|
|
||||||
function handleMapShapeAdd(shape) {
|
|
||||||
onMapDraw({ type: "add", shapes: [shape] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMapShapeRemove(shapeId) {
|
|
||||||
onMapDraw({ type: "remove", shapeIds: [shapeId] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const disabledControls = [];
|
const disabledControls = [];
|
||||||
if (!allowMapDrawing) {
|
if (!allowMapDrawing) {
|
||||||
disabledControls.push("brush");
|
disabledControls.push("brush");
|
||||||
@ -237,6 +251,18 @@ function Map({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mapFog = (
|
||||||
|
<MapFog
|
||||||
|
shapes={fogShapes}
|
||||||
|
onShapeAdd={handleFogShapeAdd}
|
||||||
|
onShapeRemove={handleFogShapeRemove}
|
||||||
|
onShapeEdit={handleFogShapeEdit}
|
||||||
|
selectedToolId={selectedToolId}
|
||||||
|
selectedToolSettings={toolSettings[selectedToolId]}
|
||||||
|
gridSize={gridSizeNormalized}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapInteraction
|
<MapInteraction
|
||||||
map={map}
|
map={map}
|
||||||
@ -251,6 +277,7 @@ function Map({
|
|||||||
>
|
>
|
||||||
{mapDrawing}
|
{mapDrawing}
|
||||||
{mapTokens}
|
{mapTokens}
|
||||||
|
{mapFog}
|
||||||
</MapInteraction>
|
</MapInteraction>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,104 +1,82 @@
|
|||||||
import React, { useRef, useEffect, useState, useContext } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import shortid from "shortid";
|
import shortid from "shortid";
|
||||||
|
import { Group, Line } from "react-konva";
|
||||||
|
import useImage from "use-image";
|
||||||
|
|
||||||
|
import diagonalPattern from "../../images/DiagonalPattern.png";
|
||||||
|
|
||||||
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
||||||
|
|
||||||
import { compare as comparePoints } from "../../helpers/vector2";
|
import { compare as comparePoints } from "../../helpers/vector2";
|
||||||
import {
|
import {
|
||||||
getBrushPositionForTool,
|
getBrushPositionForTool,
|
||||||
isShapeHovered,
|
|
||||||
drawShape,
|
|
||||||
simplifyPoints,
|
simplifyPoints,
|
||||||
getRelativePointerPosition,
|
getStrokeWidth,
|
||||||
} from "../../helpers/drawing";
|
} from "../../helpers/drawing";
|
||||||
|
|
||||||
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
import colors from "../../helpers/colors";
|
||||||
|
|
||||||
import diagonalPattern from "../../images/DiagonalPattern.png";
|
|
||||||
|
|
||||||
function MapFog({
|
function MapFog({
|
||||||
width,
|
|
||||||
height,
|
|
||||||
isEditing,
|
|
||||||
toolSettings,
|
|
||||||
shapes,
|
shapes,
|
||||||
onShapeAdd,
|
onShapeAdd,
|
||||||
onShapeRemove,
|
onShapeRemove,
|
||||||
onShapeEdit,
|
onShapeEdit,
|
||||||
|
selectedToolId,
|
||||||
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
}) {
|
}) {
|
||||||
const canvasRef = useRef();
|
const {
|
||||||
const containerRef = useRef();
|
stageDragState,
|
||||||
|
mapDragPosition,
|
||||||
const [isPointerDown, setIsPointerDown] = useState(false);
|
stageScale,
|
||||||
|
mapWidth,
|
||||||
|
mapHeight,
|
||||||
|
} = useContext(MapInteractionContext);
|
||||||
const [drawingShape, setDrawingShape] = useState(null);
|
const [drawingShape, setDrawingShape] = useState(null);
|
||||||
const [pointerPosition, setPointerPosition] = useState({ x: -1, y: -1 });
|
|
||||||
|
|
||||||
|
const isEditing = selectedToolId === "fog";
|
||||||
const shouldHover =
|
const shouldHover =
|
||||||
isEditing &&
|
isEditing &&
|
||||||
(toolSettings.type === "toggle" || toolSettings.type === "remove");
|
(selectedToolSettings.type === "toggle" ||
|
||||||
|
selectedToolSettings.type === "remove");
|
||||||
|
|
||||||
const { scaleRef } = useContext(MapInteractionContext);
|
const [patternImage] = useImage(diagonalPattern);
|
||||||
|
|
||||||
// Reset pointer position when tool changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPointerPosition({ x: -1, y: -1 });
|
|
||||||
}, [isEditing, toolSettings]);
|
|
||||||
|
|
||||||
function handleStart(event) {
|
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.touches && event.touches.length !== 1) {
|
|
||||||
setIsPointerDown(false);
|
function startShape() {
|
||||||
setDrawingShape(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pointer = event.touches ? event.touches[0] : event;
|
|
||||||
const position = getRelativePointerPosition(pointer, containerRef.current);
|
|
||||||
setPointerPosition(position);
|
|
||||||
setIsPointerDown(true);
|
|
||||||
const brushPosition = getBrushPositionForTool(
|
const brushPosition = getBrushPositionForTool(
|
||||||
position,
|
mapDragPosition,
|
||||||
"fog",
|
selectedToolId,
|
||||||
toolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
shapes
|
shapes
|
||||||
);
|
);
|
||||||
if (isEditing && toolSettings.type === "add") {
|
if (selectedToolSettings.type === "add") {
|
||||||
setDrawingShape({
|
setDrawingShape({
|
||||||
type: "fog",
|
type: "fog",
|
||||||
data: { points: [brushPosition] },
|
data: { points: [brushPosition] },
|
||||||
strokeWidth: 0.5,
|
strokeWidth: 0.5,
|
||||||
color: "black",
|
color: "black",
|
||||||
blend: true, // Blend while drawing
|
blend: false,
|
||||||
id: shortid.generate(),
|
id: shortid.generate(),
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMove(event) {
|
function continueShape() {
|
||||||
if (!isEditing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.touches && event.touches.length !== 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pointer = event.touches ? event.touches[0] : event;
|
|
||||||
const position = getRelativePointerPosition(pointer, containerRef.current);
|
|
||||||
// Set pointer position every frame for erase tool and fog
|
|
||||||
if (shouldHover) {
|
|
||||||
setPointerPosition(position);
|
|
||||||
}
|
|
||||||
if (isPointerDown) {
|
|
||||||
setPointerPosition(position);
|
|
||||||
const brushPosition = getBrushPositionForTool(
|
const brushPosition = getBrushPositionForTool(
|
||||||
position,
|
mapDragPosition,
|
||||||
"fog",
|
selectedToolId,
|
||||||
toolSettings,
|
selectedToolSettings,
|
||||||
gridSize,
|
gridSize,
|
||||||
shapes
|
shapes
|
||||||
);
|
);
|
||||||
if (isEditing && toolSettings.type === "add" && drawingShape) {
|
if (selectedToolSettings.type === "add") {
|
||||||
setDrawingShape((prevShape) => {
|
setDrawingShape((prevShape) => {
|
||||||
const prevPoints = prevShape.data.points;
|
const prevPoints = prevShape.data.points;
|
||||||
if (
|
if (
|
||||||
@ -117,16 +95,9 @@ function MapFog({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function handleStop(event) {
|
function endShape() {
|
||||||
if (!isEditing) {
|
if (selectedToolSettings.type === "add" && drawingShape) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.touches && event.touches.length !== 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isEditing && toolSettings.type === "add" && drawingShape) {
|
|
||||||
if (drawingShape.data.points.length > 1) {
|
if (drawingShape.data.points.length > 1) {
|
||||||
const shape = {
|
const shape = {
|
||||||
...drawingShape,
|
...drawingShape,
|
||||||
@ -135,141 +106,114 @@ function MapFog({
|
|||||||
drawingShape.data.points,
|
drawingShape.data.points,
|
||||||
gridSize,
|
gridSize,
|
||||||
// Downscale fog as smoothing doesn't currently work with edge snapping
|
// Downscale fog as smoothing doesn't currently work with edge snapping
|
||||||
scaleRef.current / 2
|
stageScale / 2
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
blend: false,
|
|
||||||
};
|
};
|
||||||
onShapeAdd(shape);
|
onShapeAdd(shape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hoveredShapeRef.current && isPointerDown) {
|
|
||||||
if (toolSettings.type === "remove") {
|
|
||||||
onShapeRemove(hoveredShapeRef.current.id);
|
|
||||||
} else if (toolSettings.type === "toggle") {
|
|
||||||
onShapeEdit({
|
|
||||||
...hoveredShapeRef.current,
|
|
||||||
visible: !hoveredShapeRef.current.visible,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setDrawingShape(null);
|
setDrawingShape(null);
|
||||||
setIsPointerDown(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add listeners for draw events on map to allow drawing past the bounds
|
switch (stageDragState) {
|
||||||
// of the container
|
case "first":
|
||||||
useEffect(() => {
|
startShape();
|
||||||
const map = document.querySelector(".map");
|
return;
|
||||||
map.addEventListener("mousedown", handleStart);
|
case "dragging":
|
||||||
map.addEventListener("mousemove", handleMove);
|
continueShape();
|
||||||
map.addEventListener("mouseup", handleStop);
|
return;
|
||||||
map.addEventListener("touchstart", handleStart);
|
case "last":
|
||||||
map.addEventListener("touchmove", handleMove);
|
endShape();
|
||||||
map.addEventListener("touchend", handleStop);
|
return;
|
||||||
|
default:
|
||||||
return () => {
|
return;
|
||||||
map.removeEventListener("mousedown", handleStart);
|
|
||||||
map.removeEventListener("mousemove", handleMove);
|
|
||||||
map.removeEventListener("mouseup", handleStop);
|
|
||||||
map.removeEventListener("touchstart", handleStart);
|
|
||||||
map.removeEventListener("touchmove", handleMove);
|
|
||||||
map.removeEventListener("touchend", handleStop);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rendering
|
|
||||||
*/
|
|
||||||
const hoveredShapeRef = useRef(null);
|
|
||||||
const diagonalPatternRef = useRef();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let image = new Image();
|
|
||||||
image.src = diagonalPattern;
|
|
||||||
diagonalPatternRef.current = image;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (canvas) {
|
|
||||||
const context = canvas.getContext("2d");
|
|
||||||
|
|
||||||
context.clearRect(0, 0, width, height);
|
|
||||||
let hoveredShape = null;
|
|
||||||
if (isEditing) {
|
|
||||||
const editPattern = context.createPattern(
|
|
||||||
diagonalPatternRef.current,
|
|
||||||
"repeat"
|
|
||||||
);
|
|
||||||
for (let shape of shapes) {
|
|
||||||
if (shouldHover) {
|
|
||||||
if (
|
|
||||||
isShapeHovered(shape, context, pointerPosition, width, height)
|
|
||||||
) {
|
|
||||||
hoveredShape = shape;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drawShape(
|
|
||||||
{
|
|
||||||
...shape,
|
|
||||||
blend: true,
|
|
||||||
color: shape.visible ? "black" : editPattern,
|
|
||||||
},
|
|
||||||
context,
|
|
||||||
gridSize,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (drawingShape) {
|
|
||||||
drawShape(drawingShape, context, gridSize, width, height);
|
|
||||||
}
|
|
||||||
if (hoveredShape) {
|
|
||||||
const shape = { ...hoveredShape, color: "#BB99FF", blend: true };
|
|
||||||
drawShape(shape, context, gridSize, width, height);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Not editing
|
|
||||||
for (let shape of shapes) {
|
|
||||||
if (shape.visible) {
|
|
||||||
drawShape(shape, context, gridSize, width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hoveredShapeRef.current = hoveredShape;
|
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
shapes,
|
stageDragState,
|
||||||
width,
|
mapDragPosition,
|
||||||
height,
|
selectedToolId,
|
||||||
pointerPosition,
|
selectedToolSettings,
|
||||||
isEditing,
|
isEditing,
|
||||||
drawingShape,
|
|
||||||
gridSize,
|
gridSize,
|
||||||
shouldHover,
|
stageScale,
|
||||||
|
onShapeAdd,
|
||||||
|
shapes,
|
||||||
|
drawingShape,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
function handleShapeClick(_, shape) {
|
||||||
|
if (!isEditing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedToolSettings.type === "remove") {
|
||||||
|
onShapeRemove(shape.id);
|
||||||
|
} else if (selectedToolSettings.type === "toggle") {
|
||||||
|
onShapeEdit({ ...shape, visible: !shape.visible });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShapeMouseOver(event, shape) {
|
||||||
|
if (shouldHover) {
|
||||||
|
const path = event.target;
|
||||||
|
if (shape.visible) {
|
||||||
|
const hoverColor = "#BB99FF";
|
||||||
|
path.fill(hoverColor);
|
||||||
|
} else {
|
||||||
|
path.opacity(1);
|
||||||
|
}
|
||||||
|
path.getLayer().draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShapeMouseOut(event, shape) {
|
||||||
|
if (shouldHover) {
|
||||||
|
const path = event.target;
|
||||||
|
if (shape.visible) {
|
||||||
|
const color = colors[shape.color] || shape.color;
|
||||||
|
path.fill(color);
|
||||||
|
} else {
|
||||||
|
path.opacity(0.5);
|
||||||
|
}
|
||||||
|
path.getLayer().draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderShape(shape) {
|
||||||
return (
|
return (
|
||||||
<div
|
<Line
|
||||||
style={{
|
key={shape.id}
|
||||||
position: "absolute",
|
onMouseOver={(e) => handleShapeMouseOver(e, shape)}
|
||||||
top: 0,
|
onMouseOut={(e) => handleShapeMouseOut(e, shape)}
|
||||||
left: 0,
|
onClick={(e) => handleShapeClick(e, shape)}
|
||||||
right: 0,
|
points={shape.data.points.reduce(
|
||||||
bottom: 0,
|
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
|
||||||
pointerEvents: "none",
|
[]
|
||||||
}}
|
)}
|
||||||
ref={containerRef}
|
stroke={colors[shape.color] || shape.color}
|
||||||
>
|
fill={colors[shape.color] || shape.color}
|
||||||
<canvas
|
closed
|
||||||
ref={canvasRef}
|
lineCap="round"
|
||||||
width={width}
|
strokeWidth={getStrokeWidth(
|
||||||
height={height}
|
shape.strokeWidth,
|
||||||
style={{ width: "100%", height: "100%" }}
|
gridSize,
|
||||||
|
mapWidth,
|
||||||
|
mapHeight
|
||||||
|
)}
|
||||||
|
visible={isEditing || shape.visible}
|
||||||
|
opacity={isEditing ? 0.5 : 1}
|
||||||
|
fillPatternImage={patternImage}
|
||||||
|
fillPriority={isEditing && !shape.visible ? "pattern" : "color"}
|
||||||
/>
|
/>
|
||||||
</div>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
{shapes.map(renderShape)}
|
||||||
|
{drawingShape && renderShape(drawingShape)}
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user