Separated fog snapping and drawing snapping, added square edge snapping

This commit is contained in:
Mitchell McCaffrey 2021-01-22 15:00:31 +11:00
parent 3abf9c62fa
commit 5219815a1f
5 changed files with 160 additions and 126 deletions

View File

@ -7,13 +7,12 @@ import MapStageContext from "../../contexts/MapStageContext";
import { compare as comparePoints } from "../../helpers/vector2";
import {
getBrushPositionForTool,
getBrushPosition,
getDefaultShapeData,
getUpdatedShapeData,
simplifyPoints,
getStrokeWidth,
} from "../../helpers/drawing";
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
import colors from "../../helpers/colors";
@ -49,20 +48,13 @@ function MapDrawing({
}
const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
map.snapToGrid && isShape,
false,
gridSize,
shapes
);
}
function handleBrushDown() {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid && isShape,
gridSize
);
const commonShapeData = {
color: toolSettings.color,
blend: toolSettings.useBlending,
@ -89,7 +81,12 @@ function MapDrawing({
}
function handleBrushMove() {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid && isShape,
gridSize
);
if (isBrushDown && drawingShape) {
if (isBrush) {
setDrawingShape((prevShape) => {

View File

@ -16,17 +16,13 @@ import MapStageContext from "../../contexts/MapStageContext";
import { compare as comparePoints } from "../../helpers/vector2";
import {
getBrushPositionForTool,
getFogBrushPosition,
simplifyPoints,
getStrokeWidth,
mergeShapes,
} from "../../helpers/drawing";
import colors from "../../helpers/colors";
import {
HoleyLine,
getRelativePointerPositionNormalized,
Tick,
} from "../../helpers/konva";
import { HoleyLine, Tick } from "../../helpers/konva";
import useKeyboard from "../../helpers/useKeyboard";
import useDebounce from "../../helpers/useDebounce";
@ -64,22 +60,19 @@ function MapFog({
const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
map.snapToGrid &&
(toolSettings.type === "polygon" ||
toolSettings.type === "rectangle"),
toolSettings.useEdgeSnapping,
gridSize,
shapes
);
}
const useGridSnapping =
map.snapToGrid &&
(toolSettings.type === "polygon" || toolSettings.type === "rectangle");
function handleBrushDown() {
const brushPosition = getBrushPosition();
const brushPosition = getFogBrushPosition(
map,
mapStage,
useGridSnapping,
gridSize,
toolSettings.useEdgeSnapping,
shapes
);
if (toolSettings.type === "brush") {
setDrawingShape({
type: "fog",
@ -116,7 +109,14 @@ function MapFog({
function handleBrushMove() {
if (toolSettings.type === "brush" && isBrushDown && drawingShape) {
const brushPosition = getBrushPosition();
const brushPosition = getFogBrushPosition(
map,
mapStage,
useGridSnapping,
gridSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
if (
@ -138,9 +138,17 @@ function MapFog({
});
}
if (toolSettings.type === "rectangle" && isBrushDown && drawingShape) {
const brushPosition = getBrushPosition();
const prevPoints = drawingShape.data.points;
const brushPosition = getFogBrushPosition(
map,
mapStage,
useGridSnapping,
gridSize,
toolSettings.useEdgeSnapping,
shapes,
prevPoints
);
setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points;
return {
...prevShape,
data: {
@ -198,7 +206,14 @@ function MapFog({
function handlePolygonClick() {
if (toolSettings.type === "polygon") {
const brushPosition = getBrushPosition();
const brushPosition = getFogBrushPosition(
map,
mapStage,
useGridSnapping,
gridSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevDrawingShape) => {
if (prevDrawingShape) {
return {
@ -227,7 +242,14 @@ function MapFog({
function handlePolygonMove() {
if (toolSettings.type === "polygon" && drawingShape) {
const brushPosition = getBrushPosition();
const brushPosition = getFogBrushPosition(
map,
mapStage,
useGridSnapping,
gridSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevShape) => {
if (!prevShape) {
return;
@ -350,7 +372,7 @@ function MapFog({
onMouseUp={eraseHoveredShapes}
onTouchEnd={eraseHoveredShapes}
points={points}
stroke={colors[shape.color] || shape.color}
stroke={editable ? colors.white : colors[shape.color] || shape.color}
fill={colors[shape.color] || shape.color}
closed
lineCap="round"
@ -425,22 +447,27 @@ function MapFog({
useEffect(() => {
const fogGroup = fogGroupRef.current;
const canvas = fogGroup.getChildren()[0].getCanvas();
const pixelRatio = canvas.pixelRatio || 1;
if (!editable) {
const canvas = fogGroup.getChildren()[0].getCanvas();
const pixelRatio = canvas.pixelRatio || 1;
// Constrain fog buffer to the map resolution
const fogRect = fogGroup.getClientRect();
const maxMapSize = map ? Math.max(map.width, map.height) : 4096; // Default to 4096
const maxFogSize =
Math.max(fogRect.width, fogRect.height) / debouncedStageScale;
const maxPixelRatio = maxMapSize / maxFogSize;
// Constrain fog buffer to the map resolution
const fogRect = fogGroup.getClientRect();
const maxMapSize = map ? Math.max(map.width, map.height) : 4096; // Default to 4096
const maxFogSize =
Math.max(fogRect.width, fogRect.height) / debouncedStageScale;
const maxPixelRatio = maxMapSize / maxFogSize;
fogGroup.cache({
pixelRatio: Math.min(
Math.max(debouncedStageScale * pixelRatio, 1),
maxPixelRatio
),
});
} else {
fogGroup.clearCache();
}
fogGroup.cache({
pixelRatio: Math.min(
Math.max(debouncedStageScale * pixelRatio, 1),
maxPixelRatio
),
});
fogGroup.getLayer().draw();
}, [fogShapes, editable, active, debouncedStageScale, mapWidth, map]);

View File

@ -5,7 +5,7 @@ import MapInteractionContext from "../../contexts/MapInteractionContext";
import MapStageContext from "../../contexts/MapStageContext";
import {
getBrushPositionForTool,
getBrushPosition,
getDefaultShapeData,
getUpdatedShapeData,
getStrokeWidth,
@ -48,20 +48,13 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
}
const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
map.snapToGrid,
false,
gridSize,
[]
);
}
function handleBrushDown() {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid,
gridSize
);
const { points } = getDefaultShapeData("line", brushPosition);
const length = 0;
setDrawingShapeData({ length, points });
@ -69,7 +62,12 @@ function MapMeasure({ map, selectedToolSettings, active, gridSize }) {
}
function handleBrushMove() {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid,
gridSize
);
if (isBrushDown && drawingShapeData) {
const { points } = getUpdatedShapeData(
"line",

View File

@ -6,7 +6,7 @@ import MapInteractionContext from "../../contexts/MapInteractionContext";
import MapStageContext from "../../contexts/MapStageContext";
import AuthContext from "../../contexts/AuthContext";
import { getBrushPositionForTool } from "../../helpers/drawing";
import { getBrushPosition } from "../../helpers/drawing";
import { getRelativePointerPositionNormalized } from "../../helpers/konva";
import Note from "../note/Note";
@ -39,20 +39,13 @@ function MapNotes({
}
const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
return getBrushPositionForTool(
map,
getRelativePointerPositionNormalized(mapImage),
map.snapToGrid,
false,
gridSize,
[]
);
}
function handleBrushDown() {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid,
gridSize
);
setNoteData({
x: brushPosition.x,
y: brushPosition.y,
@ -70,7 +63,12 @@ function MapNotes({
function handleBrushMove() {
if (noteData) {
const brushPosition = getBrushPosition();
const brushPosition = getBrushPosition(
map,
mapStage,
map.snapToGrid,
gridSize
);
setNoteData((prev) => ({
...prev,
x: brushPosition.x,

View File

@ -3,17 +3,12 @@ import polygonClipping from "polygon-clipping";
import * as Vector2 from "./vector2";
import { toDegrees, omit } from "./shared";
import { getRelativePointerPositionNormalized } from "./konva";
const snappingThreshold = 1 / 5;
export function getBrushPositionForTool(
map,
brushPosition,
useGridSnappning,
useEdgeSnapping,
gridSize,
shapes
) {
let position = brushPosition;
export function getBrushPosition(map, mapStage, useGridSnappning, gridSize) {
const mapImage = mapStage.findOne("#mapImage");
let position = getRelativePointerPositionNormalized(mapImage);
if (useGridSnappning) {
// Snap to corners of grid
@ -48,53 +43,72 @@ export function getBrushPositionForTool(
position = centerSnap;
}
}
return position;
}
export function getFogBrushPosition(
map,
mapStage,
useGridSnappning,
gridSize,
useEdgeSnapping,
fogShapes,
rectPoints
) {
let position = getBrushPosition(map, mapStage, useGridSnappning, gridSize);
if (useEdgeSnapping) {
const minGrid = Vector2.min(gridSize);
let closestDistance = Number.MAX_VALUE;
let closestPosition = position;
// Find the closest point on all fog shapes
for (let shape of shapes) {
if (shape.type === "fog") {
// Include shape points and holes
let pointArray = [shape.data.points, ...shape.data.holes];
for (let shape of fogShapes) {
// Include shape points and holes
let pointArray = [shape.data.points, ...shape.data.holes];
// Check whether the position is in the shape but not any holes
let isInShape = Vector2.pointInPolygon(position, shape.data.points);
if (shape.data.holes.length > 0) {
for (let hole of shape.data.holes) {
if (Vector2.pointInPolygon(position, hole)) {
isInShape = false;
for (let points of pointArray) {
// Find the closest point to each line of the shape
for (let i = 0; i < points.length; i++) {
const a = points[i];
// Wrap around points to the start to account for closed shape
const b = points[(i + 1) % points.length];
let {
distance: distanceToLine,
point: pointOnLine,
} = Vector2.distanceToLine(position, a, b);
if (rectPoints) {
const { distance: d1, point: p1 } = Vector2.distanceToLine(
{ x: position.x, y: rectPoints[1].y },
a,
b
);
const { distance: d3, point: p3 } = Vector2.distanceToLine(
{ x: rectPoints[3].x, y: position.y },
a,
b
);
if (d1 < minGrid * snappingThreshold) {
distanceToLine = d1;
pointOnLine.x = p1.x;
}
if (d3 < minGrid * snappingThreshold) {
distanceToLine = d3;
pointOnLine.y = p3.y;
}
}
}
for (let points of pointArray) {
// Find the closest point to each line of the shape
for (let i = 0; i < points.length; i++) {
const a = points[i];
// Wrap around points to the start to account for closed shape
const b = points[(i + 1) % points.length];
const {
distance: distanceToLine,
point: pointOnLine,
} = Vector2.distanceToLine(position, a, b);
const isCloseToShape = distanceToLine < minGrid * snappingThreshold;
if (
(isInShape || isCloseToShape) &&
distanceToLine < closestDistance
) {
closestPosition = pointOnLine;
closestDistance = distanceToLine;
}
const isCloseToShape = distanceToLine < minGrid * snappingThreshold;
if (isCloseToShape && distanceToLine < closestDistance) {
closestPosition = pointOnLine;
closestDistance = distanceToLine;
}
}
}
}
position = closestPosition;
}
return position;
}