Changed grid overlay to use repeating images instead of shapes

This commit is contained in:
Mitchell McCaffrey 2021-02-09 16:58:14 +11:00
parent 663e1af4d9
commit 2d5589a02e
7 changed files with 56 additions and 123 deletions

View File

@ -1,135 +1,74 @@
import React, { useEffect, useRef } from "react"; import React from "react";
import { Line, Group, RegularPolygon } from "react-konva"; import { Group, Rect } from "react-konva";
import useImage from "use-image";
import { getCellLocation } from "../helpers/grid";
import Vector2 from "../helpers/Vector2"; import Vector2 from "../helpers/Vector2";
import { useGrid } from "../contexts/GridContext"; import { useGrid } from "../contexts/GridContext";
import { useMapInteraction } from "../contexts/MapInteractionContext";
import useDebounce from "../hooks/useDebounce"; import squarePatternDark from "../images/SquarePatternDark.png";
import squarePatternLight from "../images/SquarePatternLight.png";
import hexPatternDark from "../images/HexPatternDark.png";
import hexPatternLight from "../images/HexPatternLight.png";
function Grid({ strokeWidth, stroke }) { function Grid({ stroke }) {
const { const { grid, gridPixelSize, gridOffset, gridCellPixelSize } = useGrid();
grid, let imageSource;
gridStrokeWidth, if (grid.type === "square") {
gridPixelSize, if (stroke === "black") {
gridOffset, imageSource = squarePatternDark;
gridCellPixelSize, } else {
} = useGrid(); imageSource = squarePatternLight;
const gridGroupRef = useRef();
const { stageScale, mapWidth } = useMapInteraction();
const debouncedStageScale = useDebounce(stageScale, 50);
useEffect(() => {
const gridGroup = gridGroupRef.current;
if (gridGroup && grid?.size.x && grid?.size.y && debouncedStageScale) {
const gridRect = gridGroup.getClientRect();
if (gridRect.width > 0 && gridRect.height > 0) {
// 150 pixels per grid cell
const maxMapSize = Math.min(
Math.max(grid.size.x, grid.size.y) * 150,
7680 // Max 8K
);
const maxGridSize =
Math.max(gridRect.width, gridRect.height) / debouncedStageScale;
const maxPixelRatio = maxMapSize / maxGridSize;
gridGroup.cache({
pixelRatio: Math.min(
Math.max(debouncedStageScale * 2, 1),
maxPixelRatio
),
});
}
} }
}, [grid, debouncedStageScale, mapWidth]); } else {
if (stroke === "black") {
imageSource = hexPatternDark;
} else {
imageSource = hexPatternLight;
}
}
const [patternImage] = useImage(imageSource);
if (!grid?.size.x || !grid?.size.y) { if (!grid?.size.x || !grid?.size.y) {
return null; return null;
} }
const negativeGridOffset = Vector2.multiply(gridOffset, -1); const negativeGridOffset = Vector2.multiply(gridOffset, -1);
const finalStrokeWidth = gridStrokeWidth * strokeWidth;
const shapes = []; let patternProps = {};
if (grid.type === "square") { if (grid.type === "square") {
for (let x = 1; x < grid.size.x; x++) { // Square grid pattern is 150 DPI
shapes.push( const scale = gridCellPixelSize.width / 300;
<Line patternProps.fillPatternScaleX = scale;
key={`grid_x_${x}`} patternProps.fillPatternScaleY = scale;
points={[ patternProps.fillPatternOffsetX = gridCellPixelSize.width / 2;
x * gridCellPixelSize.width, patternProps.fillPatternOffsetY = gridCellPixelSize.height / 2;
0, } else if (grid.type === "hexVertical") {
x * gridCellPixelSize.width, // Hex tile pattern is 153 DPI to better fit hex tiles
gridPixelSize.height, const scale = gridCellPixelSize.width / 153;
]} patternProps.fillPatternScaleX = scale;
stroke={stroke} patternProps.fillPatternScaleY = scale;
strokeWidth={finalStrokeWidth} patternProps.fillPatternOffsetY = gridCellPixelSize.radius / 2;
opacity={0.5} } else if (grid.type === "hexHorizontal") {
offset={negativeGridOffset} const scale = gridCellPixelSize.height / 153;
/> patternProps.fillPatternScaleX = scale;
); patternProps.fillPatternScaleY = scale;
} patternProps.fillPatternOffsetY = -gridCellPixelSize.radius / 2;
for (let y = 1; y < grid.size.y; y++) { patternProps.fillPatternRotation = 90;
shapes.push(
<Line
key={`grid_y_${y}`}
points={[
0,
y * gridCellPixelSize.height,
gridPixelSize.width,
y * gridCellPixelSize.height,
]}
stroke={stroke}
strokeWidth={finalStrokeWidth}
opacity={0.5}
offset={negativeGridOffset}
/>
);
}
} else if (grid.type === "hexVertical" || grid.type === "hexHorizontal") {
// End at grid size + 1 to overshoot the bounds of the grid to ensure all lines are drawn
for (let x = 0; x < grid.size.x + 1; x++) {
for (let y = 0; y < grid.size.y + 1; y++) {
const cellLocation = getCellLocation(grid, x, y, gridCellPixelSize);
shapes.push(
<Group
key={`grid_${x}_${y}`}
x={cellLocation.x}
y={cellLocation.y}
offset={negativeGridOffset}
>
{/* Offset the hex tile to align to top left of grid */}
<Group offset={Vector2.multiply(gridCellPixelSize, -0.5)}>
<RegularPolygon
sides={6}
radius={gridCellPixelSize.radius}
stroke={stroke}
strokeWidth={finalStrokeWidth}
opacity={0.5}
rotation={grid.type === "hexVertical" ? 0 : 90}
/>
</Group>
</Group>
);
}
}
} }
return ( return (
<Group <Group>
// Clip grid to bounds to cover hex overshoot <Rect
clipFunc={(context) => { width={gridPixelSize.width}
context.rect( height={gridPixelSize.height}
gridOffset.x - finalStrokeWidth / 2, offset={negativeGridOffset}
gridOffset.y - finalStrokeWidth / 2, fillPatternImage={patternImage}
gridPixelSize.width + finalStrokeWidth, fillPatternRepeat="repeat"
gridPixelSize.height + finalStrokeWidth opacity={stroke === "black" ? 0.8 : 0.4}
); {...patternProps}
}} />
ref={gridGroupRef}
>
{shapes}
</Group> </Group>
); );
} }

View File

@ -135,7 +135,7 @@ function MapEditor({ map, onSettingsChange }) {
width={mapWidth} width={mapWidth}
height={mapHeight} height={mapHeight}
> >
<MapGrid map={map} strokeWidth={0.5} /> <MapGrid map={map} />
<MapGridEditor map={map} onGridChange={handleGridChange} /> <MapGridEditor map={map} onGridChange={handleGridChange} />
</GridProvider> </GridProvider>
)} )}

View File

@ -8,7 +8,7 @@ import { getImageLightness } from "../../helpers/image";
import Grid from "../Grid"; import Grid from "../Grid";
function MapGrid({ map, strokeWidth }) { function MapGrid({ map }) {
let mapSourceMap = map; let mapSourceMap = map;
// Use lowest resolution for grid lightness // Use lowest resolution for grid lightness
if (map && map.type === "file" && map.resolutions) { if (map && map.type === "file" && map.resolutions) {
@ -29,13 +29,7 @@ function MapGrid({ map, strokeWidth }) {
} }
}, [mapImage, mapLoadingStatus]); }, [mapImage, mapLoadingStatus]);
return ( return <Grid stroke={isImageLight ? "black" : "white"} />;
<Grid strokeWidth={strokeWidth} stroke={isImageLight ? "black" : "white"} />
);
} }
MapGrid.defaultProps = {
strokeWidth: 0.1,
};
export default MapGrid; export default MapGrid;

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB