mirror of
https://github.com/markmanx/isoflow.git
synced 2025-02-08 04:18:29 +00:00
feat: implements connector logic
This commit is contained in:
parent
d1bc30e8d8
commit
19324bed91
5 changed files with 51 additions and 85 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -84,8 +84,8 @@ export const CustomNode = () => {
|
|||
labelHeight: 40,
|
||||
iconId: 'server',
|
||||
position: {
|
||||
x: 0,
|
||||
y: -3
|
||||
x: 5,
|
||||
y: 3
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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 }
|
||||
];
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue