Added line tool
This commit is contained in:
parent
80b711296b
commit
cac2af1608
@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useContext, useEffect } from "react";
|
import React, { useState, useContext, useEffect } from "react";
|
||||||
import polygonClipping from "polygon-clipping";
|
|
||||||
|
|
||||||
import MapControls from "./MapControls";
|
import MapControls from "./MapControls";
|
||||||
import MapInteraction from "./MapInteraction";
|
import MapInteraction from "./MapInteraction";
|
||||||
@ -16,7 +15,7 @@ import TokenMenu from "../token/TokenMenu";
|
|||||||
import TokenDragOverlay from "../token/TokenDragOverlay";
|
import TokenDragOverlay from "../token/TokenDragOverlay";
|
||||||
import LoadingOverlay from "../LoadingOverlay";
|
import LoadingOverlay from "../LoadingOverlay";
|
||||||
|
|
||||||
import { omit } from "../../helpers/shared";
|
import { drawActionsToShapes } from "../../helpers/drawing";
|
||||||
|
|
||||||
function Map({
|
function Map({
|
||||||
map,
|
map,
|
||||||
@ -121,64 +120,11 @@ function Map({
|
|||||||
if (!mapState) {
|
if (!mapState) {
|
||||||
return;
|
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(
|
setMapShapes(
|
||||||
actionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
|
drawActionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
|
||||||
);
|
);
|
||||||
setFogShapes(
|
setFogShapes(
|
||||||
actionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
|
drawActionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
|
||||||
);
|
);
|
||||||
}, [mapState]);
|
}, [mapState]);
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ function MapDrawing({
|
|||||||
selectedToolSettings.type === "paint");
|
selectedToolSettings.type === "paint");
|
||||||
const isShape =
|
const isShape =
|
||||||
selectedToolSettings &&
|
selectedToolSettings &&
|
||||||
(selectedToolSettings.type === "rectangle" ||
|
(selectedToolSettings.type === "line" ||
|
||||||
|
selectedToolSettings.type === "rectangle" ||
|
||||||
selectedToolSettings.type === "circle" ||
|
selectedToolSettings.type === "circle" ||
|
||||||
selectedToolSettings.type === "triangle");
|
selectedToolSettings.type === "triangle");
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ function MapDrawing({
|
|||||||
type: "shape",
|
type: "shape",
|
||||||
shapeType: selectedToolSettings.type,
|
shapeType: selectedToolSettings.type,
|
||||||
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
|
data: getDefaultShapeData(selectedToolSettings.type, brushPosition),
|
||||||
strokeWidth: 0,
|
strokeWidth: selectedToolSettings.type === "line" ? 1 : 0,
|
||||||
...commonShapeData,
|
...commonShapeData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -246,6 +247,24 @@ function MapDrawing({
|
|||||||
{...defaultProps}
|
{...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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@ import AlphaBlendToggle from "./AlphaBlendToggle";
|
|||||||
import RadioIconButton from "./RadioIconButton";
|
import RadioIconButton from "./RadioIconButton";
|
||||||
|
|
||||||
import BrushIcon from "../../../icons/BrushToolIcon";
|
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 BrushRectangleIcon from "../../../icons/BrushRectangleIcon";
|
||||||
import BrushCircleIcon from "../../../icons/BrushCircleIcon";
|
import BrushCircleIcon from "../../../icons/BrushCircleIcon";
|
||||||
import BrushTriangleIcon from "../../../icons/BrushTriangleIcon";
|
import BrushTriangleIcon from "../../../icons/BrushTriangleIcon";
|
||||||
@ -35,6 +36,8 @@ function DrawingToolSettings({
|
|||||||
onSettingChange({ type: "brush" });
|
onSettingChange({ type: "brush" });
|
||||||
} else if (key === "p") {
|
} else if (key === "p") {
|
||||||
onSettingChange({ type: "paint" });
|
onSettingChange({ type: "paint" });
|
||||||
|
} else if (key === "l") {
|
||||||
|
onSettingChange({ type: "line" });
|
||||||
} else if (key === "r") {
|
} else if (key === "r") {
|
||||||
onSettingChange({ type: "rectangle" });
|
onSettingChange({ type: "rectangle" });
|
||||||
} else if (key === "c") {
|
} else if (key === "c") {
|
||||||
@ -94,7 +97,14 @@ function DrawingToolSettings({
|
|||||||
onClick={() => onSettingChange({ type: "paint" })}
|
onClick={() => onSettingChange({ type: "paint" })}
|
||||||
isSelected={settings.type === "paint"}
|
isSelected={settings.type === "paint"}
|
||||||
>
|
>
|
||||||
<BrushFillIcon />
|
<BrushPaintIcon />
|
||||||
|
</RadioIconButton>
|
||||||
|
<RadioIconButton
|
||||||
|
title="Line"
|
||||||
|
onClick={() => onSettingChange({ type: "line" })}
|
||||||
|
isSelected={settings.type === "line"}
|
||||||
|
>
|
||||||
|
<BrushLineIcon />
|
||||||
</RadioIconButton>
|
</RadioIconButton>
|
||||||
<RadioIconButton
|
<RadioIconButton
|
||||||
title="Rectangle"
|
title="Rectangle"
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import simplify from "simplify-js";
|
import simplify from "simplify-js";
|
||||||
|
import polygonClipping from "polygon-clipping";
|
||||||
|
|
||||||
import * as Vector2 from "./vector2";
|
import * as Vector2 from "./vector2";
|
||||||
import { toDegrees } from "./shared";
|
import { toDegrees, omit } from "./shared";
|
||||||
|
|
||||||
const snappingThreshold = 1 / 5;
|
const snappingThreshold = 1 / 5;
|
||||||
export function getBrushPositionForTool(
|
export function getBrushPositionForTool(
|
||||||
@ -14,7 +15,8 @@ export function getBrushPositionForTool(
|
|||||||
let position = brushPosition;
|
let position = brushPosition;
|
||||||
const useGridSnappning =
|
const useGridSnappning =
|
||||||
(tool === "drawing" &&
|
(tool === "drawing" &&
|
||||||
(toolSettings.type === "rectangle" ||
|
(toolSettings.type === "line" ||
|
||||||
|
toolSettings.type === "rectangle" ||
|
||||||
toolSettings.type === "circle" ||
|
toolSettings.type === "circle" ||
|
||||||
toolSettings.type === "triangle")) ||
|
toolSettings.type === "triangle")) ||
|
||||||
(tool === "fog" && toolSettings.type === "polygon");
|
(tool === "fog" && toolSettings.type === "polygon");
|
||||||
@ -92,7 +94,14 @@ export function getBrushPositionForTool(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultShapeData(type, brushPosition) {
|
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 };
|
return { x: brushPosition.x, y: brushPosition.y, radius: 0 };
|
||||||
} else if (type === "rectangle") {
|
} else if (type === "rectangle") {
|
||||||
return {
|
return {
|
||||||
@ -124,7 +133,11 @@ export function getGridScale(gridSize) {
|
|||||||
|
|
||||||
export function getUpdatedShapeData(type, data, brushPosition, gridSize) {
|
export function getUpdatedShapeData(type, data, brushPosition, gridSize) {
|
||||||
const gridScale = getGridScale(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, {
|
const dif = Vector2.subtract(brushPosition, {
|
||||||
x: data.x,
|
x: data.x,
|
||||||
y: data.y,
|
y: data.y,
|
||||||
@ -185,3 +198,53 @@ export function simplifyPoints(points, gridSize, scale) {
|
|||||||
(Vector2.min(gridSize) * defaultSimplifySize) / 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);
|
||||||
|
}
|
||||||
|
18
src/icons/BrushLineIcon.js
Normal file
18
src/icons/BrushLineIcon.js
Normal 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;
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function BrushFillIcon() {
|
function BrushPaintIcon() {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@ -15,4 +15,4 @@ function BrushFillIcon() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BrushFillIcon;
|
export default BrushPaintIcon;
|
||||||
|
Loading…
Reference in New Issue
Block a user