You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

108 lines
2.7 KiB

import { Node } from "@xyflow/react";
import { defaultNodeDimensions } from "@/store/utils/nodeDefaults";
import { NodeType } from "@/types";
const COLLISION_GAP = 20; // Same as STACK_GAP in MultiSelectToolbar
interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}
/**
* Check if two rectangles intersect (AABB collision detection)
*/
export function rectanglesIntersect(a: Rectangle, b: Rectangle): boolean {
return !(
a.x + a.width < b.x ||
b.x + b.width < a.x ||
a.y + a.height < b.y ||
b.y + b.height < a.y
);
}
/**
* Find the nearest free position that doesn't overlap existing nodes.
* Tries offsets in order: right, down, left, up (spiral pattern).
*/
export function findNearestFreePosition(
targetPos: { x: number; y: number },
nodeType: NodeType,
existingNodes: Node[]
): { x: number; y: number } {
const { width, height } = defaultNodeDimensions[nodeType];
// Try original position first
if (!hasCollision(targetPos, width, height, existingNodes)) {
return targetPos;
}
// Try offsets in spiral pattern (right, down, left, up, right-down, etc.)
const offsets = [
{ x: width + COLLISION_GAP, y: 0 }, // right
{ x: 0, y: height + COLLISION_GAP }, // down
{ x: -(width + COLLISION_GAP), y: 0 }, // left
{ x: 0, y: -(height + COLLISION_GAP) }, // up
{ x: width + COLLISION_GAP, y: height + COLLISION_GAP }, // right-down
{ x: -(width + COLLISION_GAP), y: height + COLLISION_GAP }, // left-down
{ x: width + COLLISION_GAP, y: -(height + COLLISION_GAP) }, // right-up
{ x: -(width + COLLISION_GAP), y: -(height + COLLISION_GAP) }, // left-up
];
for (const offset of offsets) {
const testPos = {
x: targetPos.x + offset.x,
y: targetPos.y + offset.y,
};
if (!hasCollision(testPos, width, height, existingNodes)) {
return testPos;
}
}
// Fallback: if all offsets fail, use original position (rare in practice)
return targetPos;
}
/**
* Check if placing a node at position would collide with existing nodes
*/
function hasCollision(
pos: { x: number; y: number },
width: number,
height: number,
existingNodes: Node[]
): boolean {
const newRect: Rectangle = {
x: pos.x,
y: pos.y,
width,
height,
};
for (const node of existingNodes) {
const nodeWidth =
(node.measured?.width as number) ||
(node.style?.width as number) ||
300;
const nodeHeight =
(node.measured?.height as number) ||
(node.style?.height as number) ||
300;
const existingRect: Rectangle = {
x: node.position.x,
y: node.position.y,
width: nodeWidth,
height: nodeHeight,
};
if (rectanglesIntersect(newRect, existingRect)) {
return true;
}
}
return false;
}