Moved all grid snapping to useGridSnapping hook

This commit is contained in:
Mitchell McCaffrey 2021-02-09 14:13:08 +11:00
parent cdb91eed60
commit 846afe5495
9 changed files with 103 additions and 227 deletions

View File

@ -8,13 +8,14 @@ import { useGrid } from "../../contexts/GridContext";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { import {
getBrushPosition,
getDefaultShapeData, getDefaultShapeData,
getUpdatedShapeData, getUpdatedShapeData,
simplifyPoints, simplifyPoints,
} from "../../helpers/drawing"; } from "../../helpers/drawing";
import colors from "../../helpers/colors"; import colors from "../../helpers/colors";
import { getRelativePointerPosition } from "../../helpers/konva";
import useGridSnapping from "../../hooks/useGridSnapping";
function MapDrawing({ function MapDrawing({
map, map,
@ -45,19 +46,28 @@ function MapDrawing({
toolSettings.type === "circle" || toolSettings.type === "circle" ||
toolSettings.type === "triangle"; toolSettings.type === "triangle";
const snapPositionToGrid = useGridSnapping();
useEffect(() => { useEffect(() => {
if (!active) { if (!active) {
return; return;
} }
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
let position = getRelativePointerPosition(mapImage);
if (map.snapToGrid && isShape) {
position = snapPositionToGrid(position);
}
return Vector2.divide(position, {
x: mapImage.width(),
y: mapImage.height(),
});
}
function handleBrushDown() { function handleBrushDown() {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid && isShape,
gridCellNormalizedSize
);
const commonShapeData = { const commonShapeData = {
color: toolSettings.color, color: toolSettings.color,
blend: toolSettings.useBlending, blend: toolSettings.useBlending,
@ -84,12 +94,7 @@ function MapDrawing({
} }
function handleBrushMove() { function handleBrushMove() {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid && isShape,
gridCellNormalizedSize
);
if (isBrushDown && drawingShape) { if (isBrushDown && drawingShape) {
if (isBrush) { if (isBrush) {
setDrawingShape((prevShape) => { setDrawingShape((prevShape) => {

View File

@ -11,15 +11,16 @@ import { useGrid } from "../../contexts/GridContext";
import { useKeyboard } from "../../contexts/KeyboardContext"; import { useKeyboard } from "../../contexts/KeyboardContext";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { import { simplifyPoints, mergeShapes } from "../../helpers/drawing";
getFogBrushPosition,
simplifyPoints,
mergeShapes,
} from "../../helpers/drawing";
import colors from "../../helpers/colors"; import colors from "../../helpers/colors";
import { HoleyLine, Tick } from "../../helpers/konva"; import {
HoleyLine,
Tick,
getRelativePointerPosition,
} from "../../helpers/konva";
import useDebounce from "../../hooks/useDebounce"; import useDebounce from "../../hooks/useDebounce";
import useGridSnapping from "../../hooks/useGridSnapping";
function MapFog({ function MapFog({
map, map,
@ -52,6 +53,8 @@ function MapFog({
const [patternImage] = useImage(diagonalPattern); const [patternImage] = useImage(diagonalPattern);
const snapPositionToGrid = useGridSnapping();
useEffect(() => { useEffect(() => {
if (!active || !editable) { if (!active || !editable) {
return; return;
@ -59,19 +62,23 @@ function MapFog({
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
const useGridSnapping = function getBrushPosition() {
map.snapToGrid && const mapImage = mapStage.findOne("#mapImage");
(toolSettings.type === "polygon" || toolSettings.type === "rectangle"); let position = getRelativePointerPosition(mapImage);
if (
map.snapToGrid &&
(toolSettings.type === "polygon" || toolSettings.type === "rectangle")
) {
position = snapPositionToGrid(position);
}
return Vector2.divide(position, {
x: mapImage.width(),
y: mapImage.height(),
});
}
function handleBrushDown() { function handleBrushDown() {
const brushPosition = getFogBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
useGridSnapping,
gridCellNormalizedSize,
toolSettings.useEdgeSnapping,
shapes
);
if (toolSettings.type === "brush") { if (toolSettings.type === "brush") {
setDrawingShape({ setDrawingShape({
type: "fog", type: "fog",
@ -108,14 +115,7 @@ function MapFog({
function handleBrushMove() { function handleBrushMove() {
if (toolSettings.type === "brush" && isBrushDown && drawingShape) { if (toolSettings.type === "brush" && isBrushDown && drawingShape) {
const brushPosition = getFogBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
useGridSnapping,
gridCellNormalizedSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevShape) => { setDrawingShape((prevShape) => {
const prevPoints = prevShape.data.points; const prevPoints = prevShape.data.points;
if ( if (
@ -138,15 +138,7 @@ function MapFog({
} }
if (toolSettings.type === "rectangle" && isBrushDown && drawingShape) { if (toolSettings.type === "rectangle" && isBrushDown && drawingShape) {
const prevPoints = drawingShape.data.points; const prevPoints = drawingShape.data.points;
const brushPosition = getFogBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
useGridSnapping,
gridCellNormalizedSize,
toolSettings.useEdgeSnapping,
shapes,
prevPoints
);
setDrawingShape((prevShape) => { setDrawingShape((prevShape) => {
return { return {
...prevShape, ...prevShape,
@ -205,14 +197,7 @@ function MapFog({
function handlePolygonClick() { function handlePolygonClick() {
if (toolSettings.type === "polygon") { if (toolSettings.type === "polygon") {
const brushPosition = getFogBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
useGridSnapping,
gridCellNormalizedSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevDrawingShape) => { setDrawingShape((prevDrawingShape) => {
if (prevDrawingShape) { if (prevDrawingShape) {
return { return {
@ -241,14 +226,7 @@ function MapFog({
function handlePolygonMove() { function handlePolygonMove() {
if (toolSettings.type === "polygon" && drawingShape) { if (toolSettings.type === "polygon" && drawingShape) {
const brushPosition = getFogBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
useGridSnapping,
gridCellNormalizedSize,
toolSettings.useEdgeSnapping,
shapes
);
setDrawingShape((prevShape) => { setDrawingShape((prevShape) => {
if (!prevShape) { if (!prevShape) {
return; return;

View File

@ -6,11 +6,13 @@ import { useMapStage } from "../../contexts/MapStageContext";
import { useGrid } from "../../contexts/GridContext"; import { useGrid } from "../../contexts/GridContext";
import { import {
getBrushPosition,
getDefaultShapeData, getDefaultShapeData,
getUpdatedShapeData, getUpdatedShapeData,
} from "../../helpers/drawing"; } from "../../helpers/drawing";
import Vector2 from "../../helpers/Vector2"; import Vector2 from "../../helpers/Vector2";
import { getRelativePointerPosition } from "../../helpers/konva";
import useGridSnapping from "../../hooks/useGridSnapping";
function MapMeasure({ map, selectedToolSettings, active }) { function MapMeasure({ map, selectedToolSettings, active }) {
const { const {
@ -45,19 +47,28 @@ function MapMeasure({ map, selectedToolSettings, active }) {
const measureScale = parseToolScale(active && selectedToolSettings.scale); const measureScale = parseToolScale(active && selectedToolSettings.scale);
const snapPositionToGrid = useGridSnapping();
useEffect(() => { useEffect(() => {
if (!active) { if (!active) {
return; return;
} }
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
let position = getRelativePointerPosition(mapImage);
if (map.snapToGrid) {
position = snapPositionToGrid(position);
}
return Vector2.divide(position, {
x: mapImage.width(),
y: mapImage.height(),
});
}
function handleBrushDown() { function handleBrushDown() {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid,
gridCellNormalizedSize
);
const { points } = getDefaultShapeData("line", brushPosition); const { points } = getDefaultShapeData("line", brushPosition);
const length = 0; const length = 0;
setDrawingShapeData({ length, points }); setDrawingShapeData({ length, points });
@ -65,12 +76,7 @@ function MapMeasure({ map, selectedToolSettings, active }) {
} }
function handleBrushMove() { function handleBrushMove() {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid,
gridCellNormalizedSize
);
if (isBrushDown && drawingShapeData) { if (isBrushDown && drawingShapeData) {
const { points } = getUpdatedShapeData( const { points } = getUpdatedShapeData(
"line", "line",

View File

@ -5,9 +5,11 @@ import { Group } from "react-konva";
import { useMapInteraction } from "../../contexts/MapInteractionContext"; import { useMapInteraction } from "../../contexts/MapInteractionContext";
import { useMapStage } from "../../contexts/MapStageContext"; import { useMapStage } from "../../contexts/MapStageContext";
import { useAuth } from "../../contexts/AuthContext"; import { useAuth } from "../../contexts/AuthContext";
import { useGrid } from "../../contexts/GridContext";
import { getBrushPosition } from "../../helpers/drawing"; import Vector2 from "../../helpers/Vector2";
import { getRelativePointerPosition } from "../../helpers/konva";
import useGridSnapping from "../../hooks/useGridSnapping";
import Note from "../note/Note"; import Note from "../note/Note";
@ -27,26 +29,34 @@ function MapNotes({
}) { }) {
const { interactionEmitter } = useMapInteraction(); const { interactionEmitter } = useMapInteraction();
const { userId } = useAuth(); const { userId } = useAuth();
const { gridCellNormalizedSize } = useGrid();
const mapStageRef = useMapStage(); const mapStageRef = useMapStage();
const [isBrushDown, setIsBrushDown] = useState(false); const [isBrushDown, setIsBrushDown] = useState(false);
const [noteData, setNoteData] = useState(null); const [noteData, setNoteData] = useState(null);
const creatingNoteRef = useRef(); const creatingNoteRef = useRef();
const snapPositionToGrid = useGridSnapping();
useEffect(() => { useEffect(() => {
if (!active) { if (!active) {
return; return;
} }
const mapStage = mapStageRef.current; const mapStage = mapStageRef.current;
function getBrushPosition() {
const mapImage = mapStage.findOne("#mapImage");
let position = getRelativePointerPosition(mapImage);
if (map.snapToGrid) {
position = snapPositionToGrid(position);
}
return Vector2.divide(position, {
x: mapImage.width(),
y: mapImage.height(),
});
}
function handleBrushDown() { function handleBrushDown() {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid,
gridCellNormalizedSize
);
setNoteData({ setNoteData({
x: brushPosition.x, x: brushPosition.x,
y: brushPosition.y, y: brushPosition.y,
@ -65,12 +75,7 @@ function MapNotes({
function handleBrushMove() { function handleBrushMove() {
if (noteData) { if (noteData) {
const brushPosition = getBrushPosition( const brushPosition = getBrushPosition();
map,
mapStage,
map.snapToGrid,
gridCellNormalizedSize
);
setNoteData((prev) => ({ setNoteData((prev) => ({
...prev, ...prev,
x: brushPosition.x, x: brushPosition.x,

View File

@ -49,7 +49,7 @@ function MapToken({
} }
}, [tokenSourceImage]); }, [tokenSourceImage]);
const snapNodeToGrid = useGridSnapping(); const snapPositionToGrid = useGridSnapping();
function handleDragStart(event) { function handleDragStart(event) {
const tokenGroup = event.target; const tokenGroup = event.target;
@ -87,7 +87,7 @@ function MapToken({
const tokenGroup = event.target; const tokenGroup = event.target;
// Snap to corners of grid // Snap to corners of grid
if (map.snapToGrid) { if (map.snapToGrid) {
snapNodeToGrid(tokenGroup); tokenGroup.position(snapPositionToGrid(tokenGroup.position()));
} }
} }

View File

@ -33,7 +33,7 @@ function Note({
const noteHeight = noteWidth; const noteHeight = noteWidth;
const notePadding = noteWidth / 10; const notePadding = noteWidth / 10;
const snapNodeToGrid = useGridSnapping(); const snapPositionToGrid = useGridSnapping();
function handleDragStart(event) { function handleDragStart(event) {
onNoteDragStart && onNoteDragStart(event, note.id); onNoteDragStart && onNoteDragStart(event, note.id);
@ -43,7 +43,7 @@ function Note({
const noteGroup = event.target; const noteGroup = event.target;
// Snap to corners of grid // Snap to corners of grid
if (map.snapToGrid) { if (map.snapToGrid) {
snapNodeToGrid(noteGroup); noteGroup.position(snapPositionToGrid(noteGroup.position()));
} }
} }

View File

@ -3,127 +3,8 @@ import polygonClipping from "polygon-clipping";
import Vector2 from "./Vector2"; import Vector2 from "./Vector2";
import { toDegrees } from "./shared"; import { toDegrees } from "./shared";
import { getRelativePointerPositionNormalized } from "./konva";
import { logError } from "./logging"; import { logError } from "./logging";
const snappingThreshold = 1 / 5;
export function getBrushPosition(
map,
mapStage,
useGridSnappning,
gridCellNormalizedSize
) {
const mapImage = mapStage.findOne("#mapImage");
let position = getRelativePointerPositionNormalized(mapImage);
if (useGridSnappning) {
// Snap to corners of grid
// Subtract offset to transform into offset space then add it back transform back
const offset = map.grid.inset.topLeft;
const gridSnap = Vector2.add(
Vector2.roundTo(
Vector2.subtract(position, offset),
gridCellNormalizedSize
),
offset
);
const gridDistance = Vector2.length(Vector2.subtract(gridSnap, position));
// Snap to center of grid
// Subtract offset and half size to transform it into offset half space then transform it back
const halfSize = Vector2.multiply(gridCellNormalizedSize, 0.5);
const centerSnap = Vector2.add(
Vector2.add(
Vector2.roundTo(
Vector2.subtract(Vector2.subtract(position, offset), halfSize),
gridCellNormalizedSize
),
halfSize
),
offset
);
const centerDistance = Vector2.length(
Vector2.subtract(centerSnap, position)
);
const minGrid = Vector2.min(gridCellNormalizedSize);
if (gridDistance < minGrid * snappingThreshold) {
position = gridSnap;
} else if (centerDistance < minGrid * snappingThreshold) {
position = centerSnap;
}
}
return position;
}
export function getFogBrushPosition(
map,
mapStage,
useGridSnappning,
gridCellNormalizedSize,
useEdgeSnapping,
fogShapes,
rectPoints
) {
let position = getBrushPosition(
map,
mapStage,
useGridSnappning,
gridCellNormalizedSize
);
if (useEdgeSnapping) {
const minGrid = Vector2.min(gridCellNormalizedSize);
let closestDistance = Number.MAX_VALUE;
let closestPosition = position;
// Find the closest point on all fog shapes
for (let shape of fogShapes) {
// Include shape points and holes
let pointArray = [shape.data.points, ...shape.data.holes];
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;
}
}
const isCloseToShape = distanceToLine < minGrid * snappingThreshold;
if (isCloseToShape && distanceToLine < closestDistance) {
closestPosition = pointOnLine;
closestDistance = distanceToLine;
}
}
}
}
position = closestPosition;
}
return position;
}
export function getDefaultShapeData(type, brushPosition) { export function getDefaultShapeData(type, brushPosition) {
if (type === "line") { if (type === "line") {
return { return {

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import { Line, Group, Path, Circle } from "react-konva"; import { Line, Group, Path, Circle } from "react-konva";
import Konva from "konva";
import Color from "color"; import Color from "color";
import Vector2 from "./Vector2"; import Vector2 from "./Vector2";
@ -291,11 +292,15 @@ Trail.defaultProps = {
segments: 20, segments: 20,
}; };
/**
* @param {Konva.Node} node
* @returns {Vector2}
*/
export function getRelativePointerPosition(node) { export function getRelativePointerPosition(node) {
let transform = node.getAbsoluteTransform().copy(); let transform = node.getAbsoluteTransform().copy();
transform.invert(); transform.invert();
let posision = node.getStage().getPointerPosition(); let position = node.getStage().getPointerPosition();
return transform.point(posision); return transform.point(position);
} }
export function getRelativePointerPositionNormalized(node) { export function getRelativePointerPositionNormalized(node) {

View File

@ -1,7 +1,3 @@
// Load Konva for auto complete
// eslint-disable-next-line no-unused-vars
import Konva from "konva";
import Vector2 from "../helpers/Vector2"; import Vector2 from "../helpers/Vector2";
import { import {
getCellLocation, getCellLocation,
@ -26,10 +22,9 @@ function useGridSnapping(snappingSensitivity) {
const { grid, gridOffset, gridCellPixelSize } = useGrid(); const { grid, gridOffset, gridCellPixelSize } = useGrid();
/** /**
* @param {Konva.Node} node The node to snap * @param {Vector2} node The node to snap
*/ */
function snapNodeToGrid(node) { function snapPositionToGrid(position) {
const position = node.position();
// Account for grid offset // Account for grid offset
let offsetPosition = Vector2.subtract(position, gridOffset); let offsetPosition = Vector2.subtract(position, gridOffset);
// Move hex tiles to top left // Move hex tiles to top left
@ -75,13 +70,14 @@ function useGridSnapping(snappingSensitivity) {
Vector2.multiply(gridCellPixelSize, 0.5) Vector2.multiply(gridCellPixelSize, 0.5)
); );
} }
node.position(offsetSnapPoint); return offsetSnapPoint;
return;
} }
} }
return position;
} }
return snapNodeToGrid; return snapPositionToGrid;
} }
export default useGridSnapping; export default useGridSnapping;