Added line tool

This commit is contained in:
Mitchell McCaffrey 2020-06-25 08:47:53 +10:00
parent 80b711296b
commit cac2af1608
6 changed files with 123 additions and 67 deletions

View File

@ -1,5 +1,4 @@
import React, { useState, useContext, useEffect } from "react";
import polygonClipping from "polygon-clipping";
import MapControls from "./MapControls";
import MapInteraction from "./MapInteraction";
@ -16,7 +15,7 @@ import TokenMenu from "../token/TokenMenu";
import TokenDragOverlay from "../token/TokenDragOverlay";
import LoadingOverlay from "../LoadingOverlay";
import { omit } from "../../helpers/shared";
import { drawActionsToShapes } from "../../helpers/drawing";
function Map({
map,
@ -121,64 +120,11 @@ function Map({
if (!mapState) {
return;
}
function actionsToShapes(actions, actionIndex) {
let shapesById = {};
for (let i = 0; i <= actionIndex; i++) {
const action = actions[i];
if (action.type === "add" || action.type === "edit") {
for (let shape of action.shapes) {
shapesById[shape.id] = shape;
}
}
if (action.type === "remove") {
shapesById = omit(shapesById, action.shapeIds);
}
if (action.type === "subtract") {
const actionGeom = action.shapes.map((actionShape) => [
actionShape.data.points.map(({ x, y }) => [x, y]),
]);
let subtractedShapes = {};
for (let shape of Object.values(shapesById)) {
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]];
const difference = polygonClipping.difference(
shapeGeom,
actionGeom
);
for (let i = 0; i < difference.length; i++) {
let newId = difference.length > 1 ? `${shape.id}-${i}` : shape.id;
// Holes detected
let holes = [];
if (difference[i].length > 1) {
for (let j = 1; j < difference[i].length; j++) {
holes.push(difference[i][j].map(([x, y]) => ({ x, y })));
}
}
subtractedShapes[newId] = {
...shape,
id: newId,
data: {
points: difference[i][0].map(([x, y]) => ({ x, y })),
holes,
},
};
}
}
shapesById = subtractedShapes;
}
}
return Object.values(shapesById);
}
setMapShapes(
actionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
drawActionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
);
setFogShapes(
actionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
drawActionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
);
}, [mapState]);

View File

@ -42,7 +42,8 @@ function MapDrawing({
selectedToolSettings.type === "paint");
const isShape =
selectedToolSettings &&
(selectedToolSettings.type === "rectangle" ||
(selectedToolSettings.type === "line" ||
selectedToolSettings.type === "rectangle" ||
selectedToolSettings.type === "circle" ||
selectedToolSettings.type === "triangle");
@ -83,7 +84,7 @@ function MapDrawing({
type: "shape",
shapeType: selectedToolSettings.type,
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
strokeWidth: 0,
strokeWidth: selectedToolSettings.type === "line" ? 1 : 0,
...commonShapeData,
});
}
@ -246,6 +247,24 @@ function MapDrawing({
{...defaultProps}
/>
);
} else if (shape.shapeType === "line") {
return (
<Line
points={shape.data.points.reduce(
(acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight],
[]
)}
strokeWidth={getStrokeWidth(
shape.strokeWidth,
gridSize,
mapWidth,
mapHeight
)}
stroke={colors[shape.color] || shape.color}
lineCap="round"
{...defaultProps}
/>
);
}
}
}

View File

@ -6,7 +6,8 @@ import AlphaBlendToggle from "./AlphaBlendToggle";
import RadioIconButton from "./RadioIconButton";
import BrushIcon from "../../../icons/BrushToolIcon";
import BrushFillIcon from "../../../icons/BrushPaintIcon";
import BrushPaintIcon from "../../../icons/BrushPaintIcon";
import BrushLineIcon from "../../../icons/BrushLineIcon";
import BrushRectangleIcon from "../../../icons/BrushRectangleIcon";
import BrushCircleIcon from "../../../icons/BrushCircleIcon";
import BrushTriangleIcon from "../../../icons/BrushTriangleIcon";
@ -35,6 +36,8 @@ function DrawingToolSettings({
onSettingChange({ type: "brush" });
} else if (key === "p") {
onSettingChange({ type: "paint" });
} else if (key === "l") {
onSettingChange({ type: "line" });
} else if (key === "r") {
onSettingChange({ type: "rectangle" });
} else if (key === "c") {
@ -94,7 +97,14 @@ function DrawingToolSettings({
onClick={() => onSettingChange({ type: "paint" })}
isSelected={settings.type === "paint"}
>
<BrushFillIcon />
<BrushPaintIcon />
</RadioIconButton>
<RadioIconButton
title="Line"
onClick={() => onSettingChange({ type: "line" })}
isSelected={settings.type === "line"}
>
<BrushLineIcon />
</RadioIconButton>
<RadioIconButton
title="Rectangle"

View File

@ -1,7 +1,8 @@
import simplify from "simplify-js";
import polygonClipping from "polygon-clipping";
import * as Vector2 from "./vector2";
import { toDegrees } from "./shared";
import { toDegrees, omit } from "./shared";
const snappingThreshold = 1 / 5;
export function getBrushPositionForTool(
@ -14,7 +15,8 @@ export function getBrushPositionForTool(
let position = brushPosition;
const useGridSnappning =
(tool === "drawing" &&
(toolSettings.type === "rectangle" ||
(toolSettings.type === "line" ||
toolSettings.type === "rectangle" ||
toolSettings.type === "circle" ||
toolSettings.type === "triangle")) ||
(tool === "fog" && toolSettings.type === "polygon");
@ -92,7 +94,14 @@ export function getBrushPositionForTool(
}
export function getDefaultShapeData(type, brushPosition) {
if (type === "circle") {
if (type === "line") {
return {
points: [
{ x: brushPosition.x, y: brushPosition.y },
{ x: brushPosition.x, y: brushPosition.y },
],
};
} else if (type === "circle") {
return { x: brushPosition.x, y: brushPosition.y, radius: 0 };
} else if (type === "rectangle") {
return {
@ -124,7 +133,11 @@ export function getGridScale(gridSize) {
export function getUpdatedShapeData(type, data, brushPosition, gridSize) {
const gridScale = getGridScale(gridSize);
if (type === "circle") {
if (type === "line") {
return {
points: [data.points[0], { x: brushPosition.x, y: brushPosition.y }],
};
} else if (type === "circle") {
const dif = Vector2.subtract(brushPosition, {
x: data.x,
y: data.y,
@ -185,3 +198,53 @@ export function simplifyPoints(points, gridSize, scale) {
(Vector2.min(gridSize) * defaultSimplifySize) / scale
);
}
export function drawActionsToShapes(actions, actionIndex) {
let shapesById = {};
for (let i = 0; i <= actionIndex; i++) {
const action = actions[i];
if (action.type === "add" || action.type === "edit") {
for (let shape of action.shapes) {
shapesById[shape.id] = shape;
}
}
if (action.type === "remove") {
shapesById = omit(shapesById, action.shapeIds);
}
if (action.type === "subtract") {
const actionGeom = action.shapes.map((actionShape) => [
actionShape.data.points.map(({ x, y }) => [x, y]),
]);
let subtractedShapes = {};
for (let shape of Object.values(shapesById)) {
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]];
const difference = polygonClipping.difference(shapeGeom, actionGeom);
for (let i = 0; i < difference.length; i++) {
let newId = difference.length > 1 ? `${shape.id}-${i}` : shape.id;
// Holes detected
let holes = [];
if (difference[i].length > 1) {
for (let j = 1; j < difference[i].length; j++) {
holes.push(difference[i][j].map(([x, y]) => ({ x, y })));
}
}
subtractedShapes[newId] = {
...shape,
id: newId,
data: {
points: difference[i][0].map(([x, y]) => ({ x, y })),
holes,
},
};
}
}
shapesById = subtractedShapes;
}
}
return Object.values(shapesById);
}

View File

@ -0,0 +1,18 @@
import React from "react";
function BrushLineIcon() {
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.222 16.95L16.95 4.222a2 2 0 112.828 2.828L7.05 19.778a2 2 0 11-2.828-2.828z" />
</svg>
);
}
export default BrushLineIcon;

View File

@ -1,6 +1,6 @@
import React from "react";
function BrushFillIcon() {
function BrushPaintIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -15,4 +15,4 @@ function BrushFillIcon() {
);
}
export default BrushFillIcon;
export default BrushPaintIcon;