diff --git a/src/components/map/MapToken.js b/src/components/map/MapToken.js index d9dff3f..f6fd65d 100644 --- a/src/components/map/MapToken.js +++ b/src/components/map/MapToken.js @@ -6,7 +6,7 @@ import useImage from "use-image"; import useDataSource from "../../helpers/useDataSource"; import useDebounce from "../../helpers/useDebounce"; import usePrevious from "../../helpers/usePrevious"; -import * as Vector2 from "../../helpers/vector2"; +import { snapNodeToMap } from "../../helpers/map"; import AuthContext from "../../contexts/AuthContext"; import MapInteractionContext from "../../contexts/MapInteractionContext"; @@ -82,35 +82,7 @@ function MapToken({ const tokenGroup = event.target; // Snap to corners of grid if (map.snapToGrid) { - const offset = Vector2.multiply(map.grid.inset.topLeft, { - x: mapWidth, - y: mapHeight, - }); - const position = { - x: tokenGroup.x() + tokenGroup.width() / 2, - y: tokenGroup.y() + tokenGroup.height() / 2, - }; - const gridSize = { - x: - (mapWidth * - (map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) / - map.grid.size.x, - y: - (mapHeight * - (map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) / - map.grid.size.y, - }; - // Transform into offset space, round, then transform back - const gridSnap = Vector2.add( - Vector2.roundTo(Vector2.subtract(position, offset), gridSize), - offset - ); - const gridDistance = Vector2.length(Vector2.subtract(gridSnap, position)); - const minGrid = Vector2.min(gridSize); - if (gridDistance < minGrid * snappingThreshold) { - tokenGroup.x(gridSnap.x - tokenGroup.width() / 2); - tokenGroup.y(gridSnap.y - tokenGroup.height() / 2); - } + snapNodeToMap(map, mapWidth, mapHeight, tokenGroup, snappingThreshold); } } diff --git a/src/components/note/Note.js b/src/components/note/Note.js index 51f9c1c..d11da63 100644 --- a/src/components/note/Note.js +++ b/src/components/note/Note.js @@ -5,7 +5,7 @@ import { useSpring, animated } from "react-spring/konva"; import AuthContext from "../../contexts/AuthContext"; import MapInteractionContext from "../../contexts/MapInteractionContext"; -import * as Vector2 from "../../helpers/vector2"; +import { snapNodeToMap } from "../../helpers/map"; import colors from "../../helpers/colors"; import usePrevious from "../../helpers/usePrevious"; @@ -37,35 +37,7 @@ function Note({ const noteGroup = event.target; // Snap to corners of grid if (map.snapToGrid) { - const offset = Vector2.multiply(map.grid.inset.topLeft, { - x: mapWidth, - y: mapHeight, - }); - const position = { - x: noteGroup.x() + noteGroup.width() / 2, - y: noteGroup.y() + noteGroup.height() / 2, - }; - const gridSize = { - x: - (mapWidth * - (map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) / - map.grid.size.x, - y: - (mapHeight * - (map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) / - map.grid.size.y, - }; - // Transform into offset space, round, then transform back - const gridSnap = Vector2.add( - Vector2.roundTo(Vector2.subtract(position, offset), gridSize), - offset - ); - const gridDistance = Vector2.length(Vector2.subtract(gridSnap, position)); - const minGrid = Vector2.min(gridSize); - if (gridDistance < minGrid * snappingThreshold) { - noteGroup.x(gridSnap.x - noteGroup.width() / 2); - noteGroup.y(gridSnap.y - noteGroup.height() / 2); - } + snapNodeToMap(map, mapWidth, mapHeight, noteGroup, snappingThreshold); } } diff --git a/src/components/note/NoteMenu.js b/src/components/note/NoteMenu.js index 0fd0732..6ea59cb 100644 --- a/src/components/note/NoteMenu.js +++ b/src/components/note/NoteMenu.js @@ -63,7 +63,7 @@ function NoteMenu({ } function handleSizeChange(event) { - const newSize = parseInt(event.target.value); + const newSize = parseFloat(event.target.value); note && onNoteChange({ ...note, size: newSize }); } @@ -177,8 +177,8 @@ function NoteMenu({ diff --git a/src/components/token/TokenMenu.js b/src/components/token/TokenMenu.js index 2009a17..645cbe2 100644 --- a/src/components/token/TokenMenu.js +++ b/src/components/token/TokenMenu.js @@ -72,7 +72,7 @@ function TokenMenu({ } function handleSizeChange(event) { - const newSize = parseInt(event.target.value); + const newSize = parseFloat(event.target.value); tokenState && onTokenStateChange({ [tokenState.id]: { ...tokenState, size: newSize } }); } @@ -211,8 +211,8 @@ function TokenMenu({ diff --git a/src/helpers/map.js b/src/helpers/map.js index 3ded592..a2101dc 100644 --- a/src/helpers/map.js +++ b/src/helpers/map.js @@ -1,4 +1,5 @@ import GridSizeModel from "../ml/gridSize/GridSizeModel"; +import * as Vector2 from "./vector2"; import { logError } from "./logging"; @@ -162,3 +163,64 @@ export function getMapMaxZoom(map) { // Return max grid size / 2 return Math.max(Math.min(map.grid.size.x, map.grid.size.y) / 2, 5); } + +export function snapNodeToMap( + map, + mapWidth, + mapHeight, + node, + snappingThreshold +) { + const offset = Vector2.multiply(map.grid.inset.topLeft, { + x: mapWidth, + y: mapHeight, + }); + const gridSize = { + x: + (mapWidth * (map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) / + map.grid.size.x, + y: + (mapHeight * (map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) / + map.grid.size.y, + }; + + const position = node.position(); + const halfSize = Vector2.divide({ x: node.width(), y: node.height() }, 2); + + // Offsets to tranform the centered position into the four corners + const cornerOffsets = [ + halfSize, + { x: -halfSize.x, y: -halfSize.y }, + { x: halfSize.x, y: -halfSize.y }, + { x: -halfSize.x, y: halfSize.y }, + ]; + + // Minimum distance from a corner to the grid + let minCornerGridDistance = Number.MAX_VALUE; + // Minimum component of the difference between the min corner and the grid + let minCornerMinComponent; + // Closest grid value + let minGridSnap; + + // Find the closest corner to the grid + for (let cornerOffset of cornerOffsets) { + const corner = Vector2.add(position, cornerOffset); + // Transform into offset space, round, then transform back + const gridSnap = Vector2.add( + Vector2.roundTo(Vector2.subtract(corner, offset), gridSize), + offset + ); + const gridDistance = Vector2.length(Vector2.subtract(gridSnap, corner)); + const minComponent = Vector2.min(gridSize); + if (gridDistance < minCornerGridDistance) { + minCornerGridDistance = gridDistance; + minCornerMinComponent = minComponent; + // Move the grid value back to the center + minGridSnap = Vector2.subtract(gridSnap, cornerOffset); + } + } + + if (minCornerGridDistance < minCornerMinComponent * snappingThreshold) { + node.position(minGridSnap); + } +}