feat: implements connector logic

This commit is contained in:
Mark Mankarious 2023-08-10 10:20:58 +01:00
parent d1bc30e8d8
commit 19324bed91
5 changed files with 51 additions and 85 deletions

View file

@ -2,18 +2,24 @@ import React, { useMemo } from 'react';
import { Box } from '@mui/material';
import { Node, Coords } from 'src/types';
import { UNPROJECTED_TILE_SIZE } from 'src/config';
import { pathfinder, getBoundingBox, getBoundingBoxSize } from 'src/utils';
import {
findPath,
getBoundingBox,
getBoundingBoxSize,
sortByPosition,
CoordsUtils
} from 'src/utils';
import { IsoTileArea } from 'src/components/IsoTileArea/IsoTileArea';
import { useGetTilePosition } from 'src/hooks/useGetTilePosition';
import { useUiStateStore } from 'src/stores/uiStateStore';
interface Props {
// connector: ConnectorI;
fromNode: Node;
toNode: Node;
}
// How far a connector can be outside the grid area that covers two nodes
// The boundaries of the search area for the pathfinder algorithm
// is the grid that encompasses the two nodes + the offset below.
const BOUNDS_OFFSET: Coords = { x: 3, y: 3 };
export const Connector = ({ fromNode, toNode }: Props) => {
@ -21,44 +27,51 @@ export const Connector = ({ fromNode, toNode }: Props) => {
return state.zoom;
});
const { getTilePosition } = useGetTilePosition();
const connectorParams = useMemo(() => {
const route = useMemo(() => {
const searchArea = getBoundingBox(
[fromNode.position, toNode.position],
BOUNDS_OFFSET
);
const searchAreaSize = getBoundingBoxSize(searchArea);
const { findPath } = pathfinder(searchAreaSize);
const sorted = sortByPosition(searchArea);
const topLeftTile = { x: sorted.highX, y: sorted.highY };
const connectorRoute = findPath([fromNode.position, toNode.position]);
const connectorAreaSize = getBoundingBoxSize(connectorRoute);
const positionsNormalisedFromSearchArea = {
from: CoordsUtils.subtract(topLeftTile, fromNode.position),
to: CoordsUtils.subtract(topLeftTile, toNode.position)
};
const connectorRoute = findPath({
from: positionsNormalisedFromSearchArea.from,
to: positionsNormalisedFromSearchArea.to,
gridSize: searchAreaSize
});
const unprojectedTileSize = UNPROJECTED_TILE_SIZE * zoom;
const path = connectorRoute.reduce((acc, tile) => {
return `${acc} ${tile.x * unprojectedTileSize},${
tile.y * unprojectedTileSize
}`;
}, '');
const position = getTilePosition({
tile: fromNode.position
tile: topLeftTile
});
return { path, connectorAreaSize, position };
}, [fromNode.position, toNode.position, getTilePosition, zoom]);
return { path, searchAreaSize, topLeftTile, position };
}, [fromNode.position, toNode.position, zoom, getTilePosition]);
return (
<Box
id="connector"
sx={{
position: 'absolute',
left: connectorParams.position.x,
top: connectorParams.position.y
left: route.position.x,
top: route.position.y
}}
>
<IsoTileArea
tileArea={connectorParams.connectorAreaSize}
zoom={zoom}
fill="none"
>
<IsoTileArea tileArea={route.searchAreaSize} zoom={zoom} fill="none">
<polyline
points={connectorParams.path}
points={route.path}
stroke="black"
strokeWidth={10 * zoom}
fill="none"

View file

@ -8,7 +8,7 @@ import { Grid } from 'src/components/Grid/Grid';
import { Cursor } from 'src/components/Cursor/Cursor';
import { Node } from 'src/components/Node/Node';
import { Group } from 'src/components/Group/Group';
// import { Connector } from 'src/components/Connector/Connector';
import { Connector } from 'src/components/Connector/Connector';
import { DebugUtils } from 'src/components/DebugUtils/DebugUtils';
import { useResizeObserver } from 'src/hooks/useResizeObserver';
@ -94,19 +94,13 @@ export const Renderer = () => {
return <Group key={group.id} group={group} nodes={nodes} />;
})}
{mode.showCursor && <Cursor tile={mouse.position.tile} />}
{/* {scene.connectors.map((connector) => {
{scene.connectors.map((connector) => {
const nodes = getNodesFromIds([connector.from, connector.to]);
return (
<Connector
connector={connector}
fromNode={nodes[0]}
toNode={nodes[1]}
scroll={scroll}
zoom={zoom}
/>
<Connector key={connector.id} fromNode={nodes[0]} toNode={nodes[1]} />
);
})} */}
})}
{scene.nodes.map((node) => {
return (
<Node

View file

@ -84,8 +84,8 @@ export const CustomNode = () => {
labelHeight: 40,
iconId: 'server',
position: {
x: 0,
y: -3
x: 5,
y: 3
}
}
]

View file

@ -5,8 +5,8 @@ import { CustomNode } from './CustomNode/CustomNode';
import { Callbacks } from './Callbacks/Callbacks';
const examples = [
{ name: 'Callbacks', component: Callbacks },
{ name: 'Live Diagrams', component: CustomNode },
{ name: 'Callbacks', component: Callbacks },
{ name: 'Basic Editor', component: BasicEditor }
];

View file

@ -1,67 +1,26 @@
import PF from 'pathfinding';
import { Size, Coords } from 'src/types';
// TODO1: This file is a mess, refactor it
// TODO: Have one single place for utils
export const pathfinder = (gridSize: Size) => {
interface Args {
gridSize: Size;
from: Coords;
to: Coords;
}
export const findPath = ({ gridSize, from, to }: Args): Coords[] => {
const grid = new PF.Grid(gridSize.width, gridSize.height);
const finder = new PF.AStarFinder({
heuristic: PF.Heuristic.manhattan,
diagonalMovement: PF.DiagonalMovement.Always
});
const path = finder.findPath(from.x, from.y, to.x, to.y, grid);
const convertToGridXY = ({ x, y }: Coords): Coords => {
const pathTiles = path.map((tile) => {
return {
x: x + Math.floor(gridSize.width * 0.5),
y: y + Math.floor(gridSize.height * 0.5)
x: tile[0],
y: tile[1]
};
};
});
const convertToSceneXY = ({ x, y }: Coords): Coords => {
return {
x: x - Math.floor(gridSize.width * 0.5),
y: y - Math.floor(gridSize.height * 0.5)
};
};
const setWalkableAt = (coords: Coords, isWalkable: boolean) => {
const { x, y } = convertToGridXY(coords);
grid.setWalkableAt(x, y, isWalkable);
};
const findPath = (tiles: Coords[]): Coords[] => {
const normalisedRoute = tiles.map((tile) => {
return convertToGridXY(tile);
});
const path = normalisedRoute.reduce((acc, stop, i) => {
if (i === 0) {
return acc;
}
const workingGrid = grid.clone();
workingGrid.setWalkableAt(stop.x, stop.y, true);
const prevStop = normalisedRoute[i - 1];
const segment = finder.findPath(
prevStop.x,
prevStop.y,
stop.x,
stop.y,
workingGrid
);
return [...acc, ...(i > 1 ? segment.slice(1) : segment)];
}, [] as number[][]);
return path.map((tile) => {
return convertToSceneXY({ x: tile[0], y: tile[1] });
});
};
return {
setWalkableAt,
findPath
};
return pathTiles;
};