Move grid snapping to hook and added math for hex snapping
This commit is contained in:
parent
f20173de35
commit
fbea8bd7a7
@ -5,7 +5,9 @@ import {
|
||||
getCellLocation,
|
||||
gridClipFunction,
|
||||
shouldClipCell,
|
||||
getNearestCellCoordinates,
|
||||
} from "../helpers/grid";
|
||||
import Vector2 from "../helpers/Vector2";
|
||||
|
||||
import { useGrid } from "../contexts/GridContext";
|
||||
|
||||
@ -22,6 +24,8 @@ function Grid({ strokeWidth, stroke }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const negativeGridOffset = Vector2.multiply(gridOffset, -1);
|
||||
|
||||
const shapes = [];
|
||||
if (grid.type === "square") {
|
||||
for (let x = 1; x < grid.size.x; x++) {
|
||||
@ -37,7 +41,7 @@ function Grid({ strokeWidth, stroke }) {
|
||||
stroke={stroke}
|
||||
strokeWidth={gridStrokeWidth * strokeWidth}
|
||||
opacity={0.5}
|
||||
offset={gridOffset}
|
||||
offset={negativeGridOffset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -54,7 +58,7 @@ function Grid({ strokeWidth, stroke }) {
|
||||
stroke={stroke}
|
||||
strokeWidth={gridStrokeWidth * strokeWidth}
|
||||
opacity={0.5}
|
||||
offset={gridOffset}
|
||||
offset={negativeGridOffset}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -73,7 +77,7 @@ function Grid({ strokeWidth, stroke }) {
|
||||
}
|
||||
x={cellLocation.x}
|
||||
y={cellLocation.y}
|
||||
offset={gridOffset}
|
||||
offset={negativeGridOffset}
|
||||
>
|
||||
<RegularPolygon
|
||||
sides={6}
|
||||
@ -82,6 +86,16 @@ function Grid({ strokeWidth, stroke }) {
|
||||
strokeWidth={gridStrokeWidth * strokeWidth}
|
||||
opacity={0.5}
|
||||
rotation={grid.type === "hexVertical" ? 0 : 90}
|
||||
onMouseDown={() => {
|
||||
console.log(
|
||||
getNearestCellCoordinates(
|
||||
grid,
|
||||
cellLocation.x,
|
||||
cellLocation.y,
|
||||
gridCellPixelSize
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
@ -7,8 +7,7 @@ import Konva from "konva";
|
||||
import useDataSource from "../../hooks/useDataSource";
|
||||
import useDebounce from "../../hooks/useDebounce";
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
|
||||
import { snapNodeToGrid } from "../../helpers/grid";
|
||||
import useGridSnapping from "../../hooks/useGridSnapping";
|
||||
|
||||
import { useAuth } from "../../contexts/AuthContext";
|
||||
import { useMapInteraction } from "../../contexts/MapInteractionContext";
|
||||
@ -52,6 +51,8 @@ function MapToken({
|
||||
}
|
||||
}, [tokenSourceImage]);
|
||||
|
||||
const snapNodeToGrid = useGridSnapping(snappingThreshold);
|
||||
|
||||
function handleDragStart(event) {
|
||||
const tokenGroup = event.target;
|
||||
const tokenImage = imageRef.current;
|
||||
@ -88,13 +89,7 @@ function MapToken({
|
||||
const tokenGroup = event.target;
|
||||
// Snap to corners of grid
|
||||
if (map.snapToGrid) {
|
||||
snapNodeToGrid(
|
||||
map.grid,
|
||||
mapWidth,
|
||||
mapHeight,
|
||||
tokenGroup,
|
||||
snappingThreshold
|
||||
);
|
||||
snapNodeToGrid(tokenGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,10 @@ import { useAuth } from "../../contexts/AuthContext";
|
||||
import { useMapInteraction } from "../../contexts/MapInteractionContext";
|
||||
import { useGrid } from "../../contexts/GridContext";
|
||||
|
||||
import { snapNodeToGrid } from "../../helpers/grid";
|
||||
import colors from "../../helpers/colors";
|
||||
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
import useGridSnapping from "../../hooks/useGridSnapping";
|
||||
|
||||
const snappingThreshold = 1 / 5;
|
||||
|
||||
@ -31,6 +31,8 @@ function Note({
|
||||
const noteHeight = noteWidth;
|
||||
const notePadding = noteWidth / 10;
|
||||
|
||||
const snapNodeToGrid = useGridSnapping(snappingThreshold);
|
||||
|
||||
function handleDragStart(event) {
|
||||
onNoteDragStart && onNoteDragStart(event, note.id);
|
||||
}
|
||||
@ -39,13 +41,7 @@ function Note({
|
||||
const noteGroup = event.target;
|
||||
// Snap to corners of grid
|
||||
if (map.snapToGrid) {
|
||||
snapNodeToGrid(
|
||||
map.grid,
|
||||
mapWidth,
|
||||
mapHeight,
|
||||
noteGroup,
|
||||
snappingThreshold
|
||||
);
|
||||
snapNodeToGrid(noteGroup);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,14 @@ import { getGridPixelSize, getCellPixelSize, Grid } from "../helpers/grid";
|
||||
*/
|
||||
const defaultValue = {
|
||||
grid: {
|
||||
size: { x: 0, y: 0 },
|
||||
inset: { topLeft: { x: 0, y: 0 }, bottomRight: { x: 1, y: 1 } },
|
||||
size: new Vector2(0, 0),
|
||||
inset: { topLeft: new Vector2(0, 0), bottomRight: new Vector2(1, 1) },
|
||||
type: "square",
|
||||
},
|
||||
gridPixelSize: new Size(0, 0),
|
||||
gridCellPixelSize: new Size(0, 0, 0),
|
||||
gridCellNormalizedSize: new Size(0, 0, 0),
|
||||
gridOffset: { x: 0, y: 0 },
|
||||
gridOffset: new Vector2(0, 0),
|
||||
gridStrokeWidth: 0,
|
||||
};
|
||||
|
||||
@ -53,10 +53,10 @@ export function GridProvider({ grid, width, height, children }) {
|
||||
gridCellPixelSize.width / gridPixelSize.width,
|
||||
gridCellPixelSize.height / gridPixelSize.height
|
||||
);
|
||||
const gridOffset = {
|
||||
x: grid.inset.topLeft.x * width * -1,
|
||||
y: grid.inset.topLeft.y * height * -1,
|
||||
};
|
||||
const gridOffset = Vector2.multiply(grid.inset.topLeft, {
|
||||
x: width,
|
||||
y: height,
|
||||
});
|
||||
const gridStrokeWidth =
|
||||
(gridCellPixelSize.width < gridCellPixelSize.height
|
||||
? gridCellPixelSize.width
|
||||
|
55
src/helpers/Vector3.js
Normal file
55
src/helpers/Vector3.js
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Vector class with x, y, z and static helper methods
|
||||
*/
|
||||
class Vector3 {
|
||||
/**
|
||||
* @type {number} x - X component of the vector
|
||||
*/
|
||||
x;
|
||||
/**
|
||||
* @type {number} y - Y component of the vector
|
||||
*/
|
||||
y;
|
||||
/**
|
||||
* @type {number} z - Z component of the vector
|
||||
*/
|
||||
z;
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
*/
|
||||
constructor(x, y, z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Round a Vector3 to the nearest integer while maintaining x + y + z = 0
|
||||
* @param {Vector3} cube
|
||||
* @returns {Vector3}
|
||||
*/
|
||||
static cubeRound(cube) {
|
||||
var rX = Math.round(cube.x);
|
||||
var rY = Math.round(cube.y);
|
||||
var rZ = Math.round(cube.z);
|
||||
|
||||
var xDiff = Math.abs(rX - cube.x);
|
||||
var yDiff = Math.abs(rY - cube.y);
|
||||
var zDiff = Math.abs(rZ - cube.z);
|
||||
|
||||
if (xDiff > yDiff && xDiff > zDiff) {
|
||||
rX = -rY - rZ;
|
||||
} else if (yDiff > zDiff) {
|
||||
rY = -rX - rZ;
|
||||
} else {
|
||||
rZ = -rX - rY;
|
||||
}
|
||||
|
||||
return new Vector3(rX, rY, rZ);
|
||||
}
|
||||
}
|
||||
|
||||
export default Vector3;
|
@ -1,4 +1,5 @@
|
||||
import GridSizeModel from "../ml/gridSize/GridSizeModel";
|
||||
import Vector3 from "./Vector3";
|
||||
import Vector2 from "./Vector2";
|
||||
import Size from "./Size";
|
||||
|
||||
@ -56,50 +57,81 @@ export function getCellPixelSize(grid, gridWidth, gridHeight) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the location of cell in the grid
|
||||
* Find the location of a cell in the grid.
|
||||
* Hex is addressed in an offset coordinate system with even numbered columns/rows offset to the right
|
||||
* @param {Grid} grid
|
||||
* @param {number} x X-axis location of the cell
|
||||
* @param {number} y Y-axis location of the cell
|
||||
* @param {number} col X-axis coordinate of the cell
|
||||
* @param {number} row Y-axis coordinate of the cell
|
||||
* @param {Size} cellSize Cell size in pixels
|
||||
* @returns {Vector2}
|
||||
*/
|
||||
export function getCellLocation(grid, x, y, cellSize) {
|
||||
export function getCellLocation(grid, col, row, cellSize) {
|
||||
switch (grid.type) {
|
||||
case "square":
|
||||
return { x: x * cellSize.width, y: y * cellSize.height };
|
||||
return { x: col * cellSize.width, y: row * cellSize.height };
|
||||
case "hexVertical":
|
||||
return {
|
||||
x: x * cellSize.width + (cellSize.width * (1 + (y % 2))) / 2,
|
||||
y: y * cellSize.height * (3 / 4) + cellSize.radius,
|
||||
x: col * cellSize.width + (cellSize.width * (1 + (row & 1))) / 2,
|
||||
y: row * cellSize.height * (3 / 4) + cellSize.radius,
|
||||
};
|
||||
case "hexHorizontal":
|
||||
return {
|
||||
x: x * cellSize.width * (3 / 4) + cellSize.radius,
|
||||
y: y * cellSize.height + (cellSize.height * (1 + (x % 2))) / 2,
|
||||
x: col * cellSize.width * (3 / 4) + cellSize.radius,
|
||||
y: row * cellSize.height + (cellSize.height * (1 + (col & 1))) / 2,
|
||||
};
|
||||
default:
|
||||
throw GRID_TYPE_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the coordinates of the nearest cell in the grid to a point in pixels
|
||||
* @param {Grid} grid
|
||||
* @param {number} x X location to look for in pixels
|
||||
* @param {number} y Y location to look for in pixels
|
||||
* @param {Size} cellSize Cell size in pixels
|
||||
* @returns {Vector2}
|
||||
*/
|
||||
export function getNearestCellCoordinates(grid, x, y, cellSize) {
|
||||
switch (grid.type) {
|
||||
case "square":
|
||||
return Vector2.roundTo({ x, y }, cellSize);
|
||||
case "hexVertical":
|
||||
// Find nearest cell in cube coordinates the convert to offset coordinates
|
||||
const cubeXVert = ((SQRT3 / 3) * x - (1 / 3) * y) / cellSize.radius;
|
||||
const cubeZVert = ((2 / 3) * y) / cellSize.radius;
|
||||
const cubeYVert = -cubeXVert - cubeZVert;
|
||||
const cubeVert = new Vector3(cubeXVert, cubeYVert, cubeZVert);
|
||||
return hexCubeToOffset(Vector3.cubeRound(cubeVert), "hexVertical");
|
||||
case "hexHorizontal":
|
||||
const cubeXHorz = ((2 / 3) * x) / cellSize.radius;
|
||||
const cubeZHorz = (-(1 / 3) * x + (SQRT3 / 3) * y) / cellSize.radius;
|
||||
const cubeYHorz = -cubeXHorz - cubeZHorz;
|
||||
const cubeHorz = new Vector3(cubeXHorz, cubeYHorz, cubeZHorz);
|
||||
return hexCubeToOffset(Vector3.cubeRound(cubeHorz), "hexHorizontal");
|
||||
default:
|
||||
throw GRID_TYPE_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the cell located at `x, y` is out of bounds of the grid
|
||||
* @param {Grid} grid
|
||||
* @param {number} x X-axis location of the cell
|
||||
* @param {number} y Y-axis location of the cell
|
||||
* @param {number} col X-axis coordinate of the cell
|
||||
* @param {number} row Y-axis coordinate of the cell
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldClipCell(grid, x, y) {
|
||||
if (x < 0 || y < 0) {
|
||||
export function shouldClipCell(grid, col, row) {
|
||||
if (col < 0 || row < 0) {
|
||||
return true;
|
||||
}
|
||||
switch (grid.type) {
|
||||
case "square":
|
||||
return false;
|
||||
case "hexVertical":
|
||||
return x === grid.size.x - 1 && y % 2 !== 0;
|
||||
return col === grid.size.x - 1 && (row & 1) !== 0;
|
||||
case "hexHorizontal":
|
||||
return y === grid.size.y - 1 && x % 2 !== 0;
|
||||
return row === grid.size.y - 1 && (col & 1) !== 0;
|
||||
default:
|
||||
throw GRID_TYPE_NOT_IMPLEMENTED;
|
||||
}
|
||||
@ -109,25 +141,33 @@ export function shouldClipCell(grid, x, y) {
|
||||
* Canvas clip function for culling hex cells that overshoot/undershoot the grid
|
||||
* @param {CanvasRenderingContext2D} context The canvas context of the clip function
|
||||
* @param {Grid} grid
|
||||
* @param {number} x X-axis location of the cell
|
||||
* @param {number} y Y-axis location of the cell
|
||||
* @param {number} col X-axis coordinate of the cell
|
||||
* @param {number} row Y-axis coordinate of the cell
|
||||
* @param {Size} cellSize Cell size in pixels
|
||||
*/
|
||||
export function gridClipFunction(context, grid, x, y, cellSize) {
|
||||
export function gridClipFunction(context, grid, col, row, cellSize) {
|
||||
// Clip the undershooting cells unless they are needed to fill out a specific grid type
|
||||
if ((x < 0 && grid.type !== "hexVertical") || (x < 0 && y % 2 === 0)) {
|
||||
if (
|
||||
(col < 0 && grid.type !== "hexVertical") ||
|
||||
(col < 0 && (row & 1) === 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if ((y < 0 && grid.type !== "hexHorizontal") || (y < 0 && x % 2 === 0)) {
|
||||
if (
|
||||
(row < 0 && grid.type !== "hexHorizontal") ||
|
||||
(row < 0 && (col & 1) === 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
context.rect(
|
||||
x < 0 ? 0 : -cellSize.radius,
|
||||
y < 0 ? 0 : -cellSize.radius,
|
||||
x > 0 && grid.type === "hexVertical"
|
||||
col < 0 ? 0 : -cellSize.radius,
|
||||
row < 0 ? 0 : -cellSize.radius,
|
||||
col > 0 && grid.type === "hexVertical"
|
||||
? cellSize.radius
|
||||
: cellSize.radius * 2,
|
||||
y > 0 && grid.type === "hexVertical" ? cellSize.radius * 2 : cellSize.radius
|
||||
row > 0 && grid.type === "hexVertical"
|
||||
? cellSize.radius * 2
|
||||
: cellSize.radius
|
||||
);
|
||||
}
|
||||
|
||||
@ -184,6 +224,56 @@ export function getGridUpdatedInset(grid, mapWidth, mapHeight) {
|
||||
return inset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max zoom for a grid
|
||||
* @param {Grid} grid
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getGridMaxZoom(grid) {
|
||||
if (!grid) {
|
||||
return 10;
|
||||
}
|
||||
// Return max grid size / 2
|
||||
return Math.max(Math.max(grid.size.x, grid.size.y) / 2, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a 3D cube hex representation to a 2D offset one
|
||||
* @param {Vector3} cube Cube representation of the hex cell
|
||||
* @param {("hexVertical"|"hexHorizontal")} type
|
||||
* @returns {Vector2}
|
||||
*/
|
||||
function hexCubeToOffset(cube, type) {
|
||||
if (type === "hexVertical") {
|
||||
const x = cube.x + (cube.z + (cube.z & 1)) / 2;
|
||||
const y = cube.z;
|
||||
return new Vector2(x, y);
|
||||
} else {
|
||||
const x = cube.x;
|
||||
const y = cube.z + (cube.x + (cube.x & 1)) / 2;
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Convert from a 2D offset hex representation to a 3D cube one
|
||||
* @param {Vector2} offset Offset representation of the hex cell
|
||||
* @param {("hexVertical"|"hexHorizontal")} type
|
||||
* @returns {Vector3}
|
||||
*/
|
||||
function hexOffsetToCube(offset, type) {
|
||||
if (type === "hexVertical") {
|
||||
const x = offset.x - (offset.y - (offset.y & 1)) / 2;
|
||||
const z = offset.y;
|
||||
const y = -x - z;
|
||||
return { x, y, z };
|
||||
} else {
|
||||
const x = offset.x;
|
||||
const z = offset.y - (offset.x - (offset.x & 1)) / 2;
|
||||
const y = -x - z;
|
||||
return { x, y, z };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all factors of a number
|
||||
* @param {number} n
|
||||
@ -364,103 +454,3 @@ export async function getGridSizeFromImage(image) {
|
||||
|
||||
return prediction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max zoom for a grid
|
||||
* @param {Grid} grid
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getGridMaxZoom(grid) {
|
||||
if (!grid) {
|
||||
return 10;
|
||||
}
|
||||
// Return max grid size / 2
|
||||
return Math.max(Math.max(grid.size.x, grid.size.y) / 2, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Snap a Konva Node to a the closest grid cell
|
||||
* @param {Grid} grid
|
||||
* @param {number} mapWidth
|
||||
* @param {number} mapHeight
|
||||
* @param {Konva.Node} node
|
||||
* @param {number} snappingThreshold 1 = Always snap, 0 = never snap
|
||||
*/
|
||||
export function snapNodeToGrid(
|
||||
grid,
|
||||
mapWidth,
|
||||
mapHeight,
|
||||
node,
|
||||
snappingThreshold
|
||||
) {
|
||||
const offset = Vector2.multiply(grid.inset.topLeft, {
|
||||
x: mapWidth,
|
||||
y: mapHeight,
|
||||
});
|
||||
const gridSize = {
|
||||
x:
|
||||
(mapWidth * (grid.inset.bottomRight.x - grid.inset.topLeft.x)) /
|
||||
grid.size.x,
|
||||
y:
|
||||
(mapHeight * (grid.inset.bottomRight.y - grid.inset.topLeft.y)) /
|
||||
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 = [
|
||||
{ x: 0, y: 0 },
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Snap to center of grid
|
||||
// Subtract offset and half grid size to transform it into offset half space then transform it back
|
||||
const halfGridSize = Vector2.multiply(gridSize, 0.5);
|
||||
const centerSnap = Vector2.add(
|
||||
Vector2.add(
|
||||
Vector2.roundTo(
|
||||
Vector2.subtract(Vector2.subtract(position, offset), halfGridSize),
|
||||
gridSize
|
||||
),
|
||||
halfGridSize
|
||||
),
|
||||
offset
|
||||
);
|
||||
const centerDistance = Vector2.length(Vector2.subtract(centerSnap, position));
|
||||
|
||||
if (minCornerGridDistance < minCornerMinComponent * snappingThreshold) {
|
||||
node.position(minGridSnap);
|
||||
} else if (centerDistance < Vector2.min(gridSize) * snappingThreshold) {
|
||||
node.position(centerSnap);
|
||||
}
|
||||
}
|
||||
|
92
src/hooks/useGridSnapping.js
Normal file
92
src/hooks/useGridSnapping.js
Normal file
@ -0,0 +1,92 @@
|
||||
import Konva from "konva";
|
||||
|
||||
import Vector2 from "../helpers/Vector2";
|
||||
import { getCellLocation } from "../helpers/grid";
|
||||
|
||||
import { useGrid } from "../contexts/GridContext";
|
||||
|
||||
/**
|
||||
* Returns a function that when called will snap a node to the current grid
|
||||
* @param {number} snappingThreshold 1 = Always snap, 0 = never snap
|
||||
*/
|
||||
function useGridSnapping(snappingThreshold) {
|
||||
const { gridOffset, gridCellPixelSize } = useGrid();
|
||||
|
||||
/**
|
||||
* @param {Konva.Node} node The node to snap
|
||||
*/
|
||||
function snapNodeToGrid(node) {
|
||||
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 = [
|
||||
{ x: 0, y: 0 },
|
||||
// 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 gridOffset space, round, then transform back
|
||||
const gridSnap = Vector2.add(
|
||||
Vector2.roundTo(
|
||||
Vector2.subtract(corner, gridOffset),
|
||||
gridCellPixelSize
|
||||
),
|
||||
gridOffset
|
||||
);
|
||||
const gridDistance = Vector2.length(Vector2.subtract(gridSnap, corner));
|
||||
const minComponent = Vector2.min(gridCellPixelSize);
|
||||
if (gridDistance < minCornerGridDistance) {
|
||||
minCornerGridDistance = gridDistance;
|
||||
minCornerMinComponent = minComponent;
|
||||
// Move the grid value back to the center
|
||||
minGridSnap = Vector2.subtract(gridSnap, cornerOffset);
|
||||
}
|
||||
}
|
||||
|
||||
// Snap to center of grid
|
||||
// Subtract gridOffset and half grid size to transform it into gridOffset half space then transform it back
|
||||
const halfGridSize = Vector2.multiply(gridCellPixelSize, 0.5);
|
||||
const centerSnap = Vector2.add(
|
||||
Vector2.add(
|
||||
Vector2.roundTo(
|
||||
Vector2.subtract(
|
||||
Vector2.subtract(position, gridOffset),
|
||||
halfGridSize
|
||||
),
|
||||
gridCellPixelSize
|
||||
),
|
||||
halfGridSize
|
||||
),
|
||||
gridOffset
|
||||
);
|
||||
const centerDistance = Vector2.length(
|
||||
Vector2.subtract(centerSnap, position)
|
||||
);
|
||||
|
||||
if (minCornerGridDistance < minCornerMinComponent * snappingThreshold) {
|
||||
node.position(minGridSnap);
|
||||
} else if (
|
||||
centerDistance <
|
||||
Vector2.min(gridCellPixelSize) * snappingThreshold
|
||||
) {
|
||||
node.position(centerSnap);
|
||||
}
|
||||
}
|
||||
|
||||
return snapNodeToGrid;
|
||||
}
|
||||
|
||||
export default useGridSnapping;
|
Loading…
Reference in New Issue
Block a user