Added drop shadow to fog, and optimized it with caching

This commit is contained in:
Mitchell McCaffrey 2021-01-02 12:17:27 +11:00
parent 4adc6015f1
commit c90434b626
7 changed files with 106 additions and 14 deletions

View File

@ -333,7 +333,7 @@ function Map({
active={selectedToolId === "fog"}
toolSettings={settings.fog}
gridSize={gridSizeNormalized}
transparent={allowFogDrawing && !settings.fog.preview}
editable={allowFogDrawing && !settings.fog.preview}
/>
);

View File

@ -1,6 +1,12 @@
import React, { useContext, useState, useEffect, useCallback } from "react";
import React, {
useContext,
useState,
useEffect,
useCallback,
useRef,
} from "react";
import shortid from "shortid";
import { Group } from "react-konva";
import { Group, Rect } from "react-konva";
import useImage from "use-image";
import diagonalPattern from "../../images/DiagonalPattern.png";
@ -13,6 +19,7 @@ import {
getBrushPositionForTool,
simplifyPoints,
getStrokeWidth,
mergeShapes,
} from "../../helpers/drawing";
import colors from "../../helpers/colors";
import {
@ -21,6 +28,7 @@ import {
Tick,
} from "../../helpers/konva";
import useKeyboard from "../../helpers/useKeyboard";
import useDebounce from "../../helpers/useDebounce";
function MapFog({
map,
@ -32,7 +40,7 @@ function MapFog({
active,
toolSettings,
gridSize,
transparent,
editable,
}) {
const { stageScale, mapWidth, mapHeight, interactionEmitter } = useContext(
MapInteractionContext
@ -44,12 +52,13 @@ function MapFog({
const shouldHover =
active &&
editable &&
(toolSettings.type === "toggle" || toolSettings.type === "remove");
const [patternImage] = useImage(diagonalPattern);
useEffect(() => {
if (!active) {
if (!active || !editable) {
return;
}
@ -80,7 +89,6 @@ function MapFog({
},
strokeWidth: 0.5,
color: toolSettings.useFogCut ? "red" : "black",
blend: false,
id: shortid.generate(),
visible: true,
});
@ -99,7 +107,6 @@ function MapFog({
},
strokeWidth: 0.5,
color: toolSettings.useFogCut ? "red" : "black",
blend: false,
id: shortid.generate(),
visible: true,
});
@ -210,7 +217,6 @@ function MapFog({
},
strokeWidth: 0.5,
color: toolSettings.useFogCut ? "red" : "black",
blend: false,
id: shortid.generate(),
visible: true,
};
@ -355,14 +361,16 @@ function MapFog({
mapWidth,
mapHeight
)}
visible={(active && !toolSettings.preview) || shape.visible}
opacity={transparent ? 0.5 : 1}
opacity={editable ? 0.5 : 1}
fillPatternImage={patternImage}
fillPriority={active && !shape.visible ? "pattern" : "color"}
holes={holes}
// Disable collision if the fog is transparent and we're not editing it
// This allows tokens to be moved under the fog
hitFunc={transparent && !active ? () => {} : undefined}
hitFunc={editable && !active ? () => {} : undefined}
shadowColor={editable ? "rgba(0, 0, 0, 0)" : "rgba(0, 0, 0, 0.33)"}
shadowOffset={{ x: 0, y: 5 }}
shadowBlur={10}
/>
);
}
@ -398,9 +406,41 @@ function MapFog({
);
}
const [fogShapes, setFogShapes] = useState(shapes);
useEffect(() => {
function shapeVisible(shape) {
return (active && !toolSettings.preview) || shape.visible;
}
if (editable) {
setFogShapes(shapes.filter(shapeVisible));
} else {
setFogShapes(mergeShapes(shapes));
}
}, [shapes, editable, active, toolSettings]);
const fogGroupRef = useRef();
const debouncedStageScale = useDebounce(stageScale, 50);
useEffect(() => {
const fogGroup = fogGroupRef.current;
const canvas = fogGroup.getChildren()[0].getCanvas();
const pixelRatio = canvas.pixelRatio || 1;
fogGroup.cache({
pixelRatio: Math.min(debouncedStageScale * pixelRatio, 20),
});
fogGroup.getLayer().draw();
}, [fogShapes, editable, active, debouncedStageScale, mapWidth]);
return (
<Group>
{shapes.map(renderShape)}
<Group ref={fogGroupRef}>
{/* Render a blank shape so cache works with no fog shapes */}
<Rect width={1} height={1} />
{fogShapes.map(renderShape)}
</Group>
{drawingShape && renderShape(drawingShape)}
{drawingShape &&
toolSettings &&

View File

@ -4,7 +4,11 @@ import { IconButton } from "theme-ui";
import SnappingOnIcon from "../../../icons/SnappingOnIcon";
import SnappingOffIcon from "../../../icons/SnappingOffIcon";
function EdgeSnappingToggle({ useEdgeSnapping, onEdgeSnappingChange }) {
function EdgeSnappingToggle({
useEdgeSnapping,
onEdgeSnappingChange,
disabled,
}) {
return (
<IconButton
aria-label={
@ -18,6 +22,7 @@ function EdgeSnappingToggle({ useEdgeSnapping, onEdgeSnappingChange }) {
: "Enable Edge Snapping (S)"
}
onClick={() => onEdgeSnappingChange(!useEdgeSnapping)}
disabled={disabled}
>
{useEdgeSnapping ? <SnappingOnIcon /> : <SnappingOffIcon />}
</IconButton>

View File

@ -4,7 +4,7 @@ import { IconButton } from "theme-ui";
import CutOnIcon from "../../../icons/FogCutOnIcon";
import CutOffIcon from "../../../icons/FogCutOffIcon";
function FogCutToggle({ useFogCut, onFogCutChange }) {
function FogCutToggle({ useFogCut, onFogCutChange, disabled }) {
return (
<IconButton
aria-label={
@ -12,6 +12,7 @@ function FogCutToggle({ useFogCut, onFogCutChange }) {
}
title={useFogCut ? "Disable Fog Cutting (C)" : "Enable Fog Cutting (C)"}
onClick={() => onFogCutChange(!useFogCut)}
disabled={disabled}
>
{useFogCut ? <CutOnIcon /> : <CutOffIcon />}
</IconButton>

View File

@ -80,18 +80,21 @@ function BrushToolSettings({
title: "Fog Polygon (P)",
isSelected: settings.type === "polygon",
icon: <FogPolygonIcon />,
disabled: settings.preview,
},
{
id: "rectangle",
title: "Fog Rectangle (R)",
isSelected: settings.type === "rectangle",
icon: <FogRectangleIcon />,
disabled: settings.preview,
},
{
id: "brush",
title: "Fog Brush (B)",
isSelected: settings.type === "brush",
icon: <FogBrushIcon />,
disabled: settings.preview,
},
];
@ -107,6 +110,7 @@ function BrushToolSettings({
title="Toggle Fog (T)"
onClick={() => onSettingChange({ type: "toggle" })}
isSelected={settings.type === "toggle"}
disabled={settings.preview}
>
<FogToggleIcon />
</RadioIconButton>
@ -114,6 +118,7 @@ function BrushToolSettings({
title="Erase Fog (E)"
onClick={() => onSettingChange({ type: "remove" })}
isSelected={settings.type === "remove"}
disabled={settings.preview}
>
<FogRemoveIcon />
</RadioIconButton>
@ -121,12 +126,14 @@ function BrushToolSettings({
<FogCutToggle
useFogCut={settings.useFogCut}
onFogCutChange={(useFogCut) => onSettingChange({ useFogCut })}
disabled={settings.preview}
/>
<EdgeSnappingToggle
useEdgeSnapping={settings.useEdgeSnapping}
onEdgeSnappingChange={(useEdgeSnapping) =>
onSettingChange({ useEdgeSnapping })
}
disabled={settings.preview}
/>
<FogPreviewToggle
useFogPreview={settings.preview}

View File

@ -36,6 +36,7 @@ function ToolSection({ collapse, tools, onToolClick }) {
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
disabled={tool.disabled}
>
{tool.icon}
</RadioIconButton>
@ -90,6 +91,7 @@ function ToolSection({ collapse, tools, onToolClick }) {
onClick={() => handleToolClick(tool)}
key={tool.id}
isSelected={tool.isSelected}
disabled={tool.disabled}
>
{tool.icon}
</RadioIconButton>

View File

@ -294,3 +294,40 @@ function addPolygonIntersectionToShapes(shape, intersection, shapes) {
};
}
}
export function mergeShapes(shapes) {
if (shapes.length === 0) {
return shapes;
}
let geometries = [];
for (let shape of shapes) {
if (!shape.visible) {
continue;
}
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
const shapeHoles = shape.data.holes.map((hole) =>
hole.map(({ x, y }) => [x, y])
);
let shapeGeom = [[shapePoints, ...shapeHoles]];
geometries.push(shapeGeom);
}
let union = polygonClipping.union(...geometries);
let merged = [];
for (let i = 0; i < union.length; i++) {
let holes = [];
if (union[i].length > 1) {
for (let j = 1; j < union[i].length; j++) {
holes.push(union[i][j].map(([x, y]) => ({ x, y })));
}
}
merged.push({
...shapes[0],
id: `merged-${i}`,
data: {
points: union[i][0].map(([x, y]) => ({ x, y })),
holes,
},
});
}
return merged;
}