From f4d71cd6bfeb3d0b5c0aff71446fd511ade44074 Mon Sep 17 00:00:00 2001 From: Mitchell McCaffrey Date: Sun, 14 Jun 2020 12:27:05 +1000 Subject: [PATCH] Add support for holes in fog subtraction --- src/components/map/Map.js | 31 ++++++---- src/components/map/MapFog.js | 29 +++++++--- src/helpers/konva.js | 107 +++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 src/helpers/konva.js diff --git a/src/components/map/Map.js b/src/components/map/Map.js index a22a170..35b11f7 100644 --- a/src/components/map/Map.js +++ b/src/components/map/Map.js @@ -144,22 +144,33 @@ function Map({ ]); let subtractedShapes = {}; for (let shape of Object.values(shapesById)) { - let shapeGeom = [[shape.data.points.map(({ x, y }) => [x, y])]]; + 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++) { - for (let j = 0; j < difference[i].length; j++) { - let newId = `${shape.id}-${i}_${j}`; - subtractedShapes[newId] = { - ...shape, - id: newId, - data: { - points: difference[i][j].map(([x, y]) => ({ x, y })), - }, - }; + 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; diff --git a/src/components/map/MapFog.js b/src/components/map/MapFog.js index 66569e2..30c24a5 100644 --- a/src/components/map/MapFog.js +++ b/src/components/map/MapFog.js @@ -1,6 +1,6 @@ import React, { useContext, useState, useCallback } from "react"; import shortid from "shortid"; -import { Group, Line } from "react-konva"; +import { Group } from "react-konva"; import useImage from "use-image"; import diagonalPattern from "../../images/DiagonalPattern.png"; @@ -13,9 +13,9 @@ import { simplifyPoints, getStrokeWidth, } from "../../helpers/drawing"; - import colors from "../../helpers/colors"; import useMapBrush from "../../helpers/useMapBrush"; +import { HoleyLine } from "../../helpers/konva"; function MapFog({ shapes, @@ -70,7 +70,7 @@ function MapFog({ ) { setDrawingShape({ type: "fog", - data: { points: [brushPosition] }, + data: { points: [brushPosition], holes: [] }, strokeWidth: 0.5, color: selectedToolSettings.type === "add" ? "black" : "red", blend: false, @@ -106,7 +106,10 @@ function MapFog({ } return { ...prevShape, - data: { points: [...prevPoints, brushPosition] }, + data: { + ...prevShape.data, + points: [...prevPoints, brushPosition], + }, }; }); } @@ -118,6 +121,7 @@ function MapFog({ const shape = { ...drawingShape, data: { + ...drawingShape.data, points: simplifyPoints( drawingShape.data.points, gridSize, @@ -133,6 +137,7 @@ function MapFog({ if (drawingShape.data.points.length > 1) { const shape = { data: { + ...drawingShape.data, points: simplifyPoints( drawingShape.data.points, gridSize, @@ -187,18 +192,23 @@ function MapFog({ } } + function reducePoints(acc, point) { + return [...acc, point.x * mapWidth, point.y * mapHeight]; + } + function renderShape(shape) { + const points = shape.data.points.reduce(reducePoints, []); + const holes = + shape.data.holes && + shape.data.holes.map((hole) => hole.reduce(reducePoints, [])); return ( - handleShapeOver(shape, isBrushDown)} onTouchOver={() => handleShapeOver(shape, isBrushDown)} onMouseDown={() => handleShapeOver(shape, true)} onTouchStart={() => handleShapeOver(shape, true)} - points={shape.data.points.reduce( - (acc, point) => [...acc, point.x * mapWidth, point.y * mapHeight], - [] - )} + points={points} stroke={colors[shape.color] || shape.color} fill={colors[shape.color] || shape.color} closed @@ -213,6 +223,7 @@ function MapFog({ opacity={isEditing ? 0.5 : 1} fillPatternImage={patternImage} fillPriority={isEditing && !shape.visible ? "pattern" : "color"} + holes={holes} /> ); } diff --git a/src/helpers/konva.js b/src/helpers/konva.js new file mode 100644 index 0000000..d424eb1 --- /dev/null +++ b/src/helpers/konva.js @@ -0,0 +1,107 @@ +import React from "react"; +import { Line } from "react-konva"; + +// Holes should be wound in the opposite direction as the containing points array +export function HoleyLine({ holes, ...props }) { + // Converted from https://github.com/rfestag/konva/blob/master/src/shapes/Line.ts + function drawLine(points, context, shape) { + const length = points.length; + const tension = shape.tension(); + const closed = shape.closed(); + const bezier = shape.bezier(); + + if (!length) { + return; + } + + context.moveTo(points[0], points[1]); + + if (tension !== 0 && length > 4) { + const tensionPoints = shape.getTensionPoints(); + const tensionLength = tensionPoints.length; + let n = closed ? 0 : 4; + + if (!closed) { + context.quadraticCurveTo( + tensionPoints[0], + tensionPoints[1], + tensionPoints[2], + tensionPoints[3] + ); + } + + while (n < tensionLength - 2) { + context.bezierCurveTo( + tensionPoints[n++], + tensionPoints[n++], + tensionPoints[n++], + tensionPoints[n++], + tensionPoints[n++], + tensionPoints[n++] + ); + } + + if (!closed) { + context.quadraticCurveTo( + tensionPoints[tensionLength - 2], + tensionPoints[tensionLength - 1], + points[length - 2], + points[length - 1] + ); + } + } else if (bezier) { + // no tension but bezier + let n = 2; + + while (n < length) { + context.bezierCurveTo( + points[n++], + points[n++], + points[n++], + points[n++], + points[n++], + points[n++] + ); + } + } else { + // no tension + for (let n = 2; n < length; n += 2) { + context.lineTo(points[n], points[n + 1]); + } + } + } + + // Draw points and holes + function sceneFunc(context, shape) { + const points = shape.points(); + const closed = shape.closed(); + + if (!points.length) { + return; + } + + context.beginPath(); + console.log(); + drawLine(points, context, shape); + + context.beginPath(); + drawLine(points, context, shape); + + // closed e.g. polygons and blobs + if (closed) { + context.closePath(); + if (holes && holes.length) { + for (let hole of holes) { + drawLine(hole, context, shape); + context.closePath(); + } + } + context.fillStrokeShape(shape); + } else { + // open e.g. lines and splines + context.strokeShape(shape); + } + } + + return ; +}