mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-09 01:20:09 +00:00
370 lines
10 KiB
TypeScript
370 lines
10 KiB
TypeScript
import type {
|
|
Point,
|
|
ReadOnlyPoint,
|
|
ReadOnlyRect,
|
|
Rect,
|
|
} from "./interfaces"
|
|
|
|
import { LinkDirection } from "./types/globalEnums"
|
|
|
|
/**
|
|
* Calculates the distance between two points (2D vector)
|
|
* @param a Point a as `x, y`
|
|
* @param b Point b as `x, y`
|
|
* @returns Distance between point {@link a} & {@link b}
|
|
*/
|
|
export function distance(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
|
|
return Math.sqrt(
|
|
(b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Calculates the distance2 (squared) between two points (2D vector).
|
|
* Much faster when only comparing distances (closest/furthest point).
|
|
* @param x1 Origin point X
|
|
* @param y1 Origin point Y
|
|
* @param x2 Destination point X
|
|
* @param y2 Destination point Y
|
|
* @returns Distance2 (squared) between point [{@link x1}, {@link y1}] & [{@link x2}, {@link y2}]
|
|
*/
|
|
export function dist2(x1: number, y1: number, x2: number, y2: number): number {
|
|
return ((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))
|
|
}
|
|
|
|
/**
|
|
* Determines whether a point is inside a rectangle.
|
|
*
|
|
* Otherwise identical to {@link isInsideRectangle}, it also returns `true` if `x` equals `left` or `y` equals `top`.
|
|
* @param x Point x
|
|
* @param y Point y
|
|
* @param left Rect x
|
|
* @param top Rect y
|
|
* @param width Rect width
|
|
* @param height Rect height
|
|
* @returns `true` if the point is inside the rect, otherwise `false`
|
|
*/
|
|
export function isInRectangle(
|
|
x: number,
|
|
y: number,
|
|
left: number,
|
|
top: number,
|
|
width: number,
|
|
height: number,
|
|
): boolean {
|
|
return x >= left &&
|
|
x < left + width &&
|
|
y >= top &&
|
|
y < top + height
|
|
}
|
|
|
|
/**
|
|
* Determines whether a {@link Point} is inside a {@link Rect}.
|
|
* @param point The point to check, as `x, y`
|
|
* @param rect The rectangle, as `x, y, width, height`
|
|
* @returns `true` if the point is inside the rect, otherwise `false`
|
|
*/
|
|
export function isPointInRect(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
|
|
return point[0] >= rect[0] &&
|
|
point[0] < rect[0] + rect[2] &&
|
|
point[1] >= rect[1] &&
|
|
point[1] < rect[1] + rect[3]
|
|
}
|
|
|
|
/**
|
|
* Determines whether the point represented by {@link x}, {@link y} is inside a {@link Rect}.
|
|
* @param x X co-ordinate of the point to check
|
|
* @param y Y co-ordinate of the point to check
|
|
* @param rect The rectangle, as `x, y, width, height`
|
|
* @returns `true` if the point is inside the rect, otherwise `false`
|
|
*/
|
|
export function isInRect(x: number, y: number, rect: ReadOnlyRect): boolean {
|
|
return x >= rect[0] &&
|
|
x < rect[0] + rect[2] &&
|
|
y >= rect[1] &&
|
|
y < rect[1] + rect[3]
|
|
}
|
|
|
|
/**
|
|
* Determines whether a point (`x, y`) is inside a rectangle.
|
|
*
|
|
* This is the original litegraph implementation. It returns `false` if `x` is equal to `left`, or `y` is equal to `top`.
|
|
* @deprecated
|
|
* Use {@link isInRectangle} to match inclusive of top left.
|
|
* This function returns a false negative when an integer point (e.g. pixel) is on the leftmost or uppermost edge of a rectangle.
|
|
* @param x Point x
|
|
* @param y Point y
|
|
* @param left Rect x
|
|
* @param top Rect y
|
|
* @param width Rect width
|
|
* @param height Rect height
|
|
* @returns `true` if the point is inside the rect, otherwise `false`
|
|
*/
|
|
export function isInsideRectangle(
|
|
x: number,
|
|
y: number,
|
|
left: number,
|
|
top: number,
|
|
width: number,
|
|
height: number,
|
|
): boolean {
|
|
return left < x &&
|
|
left + width > x &&
|
|
top < y &&
|
|
top + height > y
|
|
}
|
|
|
|
/**
|
|
* Cheap, low accuracy check to determine if a point is roughly inside a sort-of octagon
|
|
* @param x Point x
|
|
* @param y Point y
|
|
* @param radius Radius to use as rough guide for octagon
|
|
* @returns `true` if the point is roughly inside the octagon centred on 0,0 with specified radius
|
|
*/
|
|
export function isSortaInsideOctagon(x: number, y: number, radius: number): boolean {
|
|
const sum = Math.min(radius, Math.abs(x)) + Math.min(radius, Math.abs(y))
|
|
return sum < radius * 0.75
|
|
}
|
|
|
|
/**
|
|
* Determines if two rectangles have any overlap
|
|
* @param a Rectangle A as `x, y, width, height`
|
|
* @param b Rectangle B as `x, y, width, height`
|
|
* @returns `true` if rectangles overlap, otherwise `false`
|
|
*/
|
|
export function overlapBounding(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
|
const aRight = a[0] + a[2]
|
|
const aBottom = a[1] + a[3]
|
|
const bRight = b[0] + b[2]
|
|
const bBottom = b[1] + b[3]
|
|
|
|
return a[0] > bRight ||
|
|
a[1] > bBottom ||
|
|
aRight < b[0] ||
|
|
aBottom < b[1]
|
|
? false
|
|
: true
|
|
}
|
|
|
|
/**
|
|
* Determines if rectangle {@link a} contains the centre point of rectangle {@link b}.
|
|
* @param a Container rectangle A as `x, y, width, height`
|
|
* @param b Sub-rectangle B as `x, y, width, height`
|
|
* @returns `true` if {@link a} contains most of {@link b}, otherwise `false`
|
|
*/
|
|
export function containsCentre(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
|
const centreX = b[0] + (b[2] * 0.5)
|
|
const centreY = b[1] + (b[3] * 0.5)
|
|
return isInRect(centreX, centreY, a)
|
|
}
|
|
|
|
/**
|
|
* Determines if rectangle {@link a} wholly contains rectangle {@link b}.
|
|
* @param a Container rectangle A as `x, y, width, height`
|
|
* @param b Sub-rectangle B as `x, y, width, height`
|
|
* @returns `true` if {@link a} wholly contains {@link b}, otherwise `false`
|
|
*/
|
|
export function containsRect(a: ReadOnlyRect, b: ReadOnlyRect): boolean {
|
|
const aRight = a[0] + a[2]
|
|
const aBottom = a[1] + a[3]
|
|
const bRight = b[0] + b[2]
|
|
const bBottom = b[1] + b[3]
|
|
|
|
const identical = a[0] === b[0] &&
|
|
a[1] === b[1] &&
|
|
aRight === bRight &&
|
|
aBottom === bBottom
|
|
|
|
return !identical &&
|
|
a[0] <= b[0] &&
|
|
a[1] <= b[1] &&
|
|
aRight >= bRight &&
|
|
aBottom >= bBottom
|
|
}
|
|
|
|
/**
|
|
* Adds an offset in the specified direction to {@link out}
|
|
* @param amount Amount of offset to add
|
|
* @param direction Direction to add the offset to
|
|
* @param out The {@link Point} to add the offset to
|
|
*/
|
|
export function addDirectionalOffset(
|
|
amount: number,
|
|
direction: LinkDirection,
|
|
out: Point,
|
|
): void {
|
|
switch (direction) {
|
|
case LinkDirection.LEFT:
|
|
out[0] -= amount
|
|
return
|
|
case LinkDirection.RIGHT:
|
|
out[0] += amount
|
|
return
|
|
case LinkDirection.UP:
|
|
out[1] -= amount
|
|
return
|
|
case LinkDirection.DOWN:
|
|
out[1] += amount
|
|
return
|
|
// LinkDirection.CENTER: Nothing to do.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rotates an offset in 90° increments.
|
|
*
|
|
* Swaps/flips axis values of a 2D vector offset - effectively rotating
|
|
* {@link offset} by 90°
|
|
* @param offset The zero-based offset to rotate
|
|
* @param from Direction to rotate from
|
|
* @param to Direction to rotate to
|
|
*/
|
|
export function rotateLink(
|
|
offset: Point,
|
|
from: LinkDirection,
|
|
to: LinkDirection,
|
|
): void {
|
|
let x: number
|
|
let y: number
|
|
|
|
// Normalise to left
|
|
switch (from) {
|
|
case to:
|
|
case LinkDirection.CENTER:
|
|
case LinkDirection.NONE:
|
|
default:
|
|
// Nothing to do
|
|
return
|
|
|
|
case LinkDirection.LEFT:
|
|
x = offset[0]
|
|
y = offset[1]
|
|
break
|
|
case LinkDirection.RIGHT:
|
|
x = -offset[0]
|
|
y = -offset[1]
|
|
break
|
|
case LinkDirection.UP:
|
|
x = -offset[1]
|
|
y = offset[0]
|
|
break
|
|
case LinkDirection.DOWN:
|
|
x = offset[1]
|
|
y = -offset[0]
|
|
break
|
|
}
|
|
|
|
// Apply new direction
|
|
switch (to) {
|
|
case LinkDirection.CENTER:
|
|
case LinkDirection.NONE:
|
|
// Nothing to do
|
|
return
|
|
|
|
case LinkDirection.LEFT:
|
|
offset[0] = x
|
|
offset[1] = y
|
|
break
|
|
case LinkDirection.RIGHT:
|
|
offset[0] = -x
|
|
offset[1] = -y
|
|
break
|
|
case LinkDirection.UP:
|
|
offset[0] = y
|
|
offset[1] = -x
|
|
break
|
|
case LinkDirection.DOWN:
|
|
offset[0] = -y
|
|
offset[1] = x
|
|
break
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a point is to to the left or right of a line.
|
|
* Project a line from lineStart -> lineEnd. Determine if point is to the left
|
|
* or right of that projection.
|
|
* {@link https://www.geeksforgeeks.org/orientation-3-ordered-points/}
|
|
* @param lineStart The start point of the line
|
|
* @param lineEnd The end point of the line
|
|
* @param x X co-ordinate of the point to check
|
|
* @param y Y co-ordinate of the point to check
|
|
* @returns 0 if all three points are in a straight line, a negative value if
|
|
* point is to the left of the projected line, or positive if the point is to
|
|
* the right
|
|
*/
|
|
export function getOrientation(
|
|
lineStart: ReadOnlyPoint,
|
|
lineEnd: ReadOnlyPoint,
|
|
x: number,
|
|
y: number,
|
|
): number {
|
|
return ((lineEnd[1] - lineStart[1]) * (x - lineEnd[0])) -
|
|
((lineEnd[0] - lineStart[0]) * (y - lineEnd[1]))
|
|
}
|
|
|
|
/**
|
|
* @param out The array to store the point in
|
|
* @param a Start point
|
|
* @param b End point
|
|
* @param controlA Start curve control point
|
|
* @param controlB End curve control point
|
|
* @param t Time: factor of distance to travel along the curve (e.g 0.25 is 25% along the curve)
|
|
*/
|
|
export function findPointOnCurve(
|
|
out: Point,
|
|
a: ReadOnlyPoint,
|
|
b: ReadOnlyPoint,
|
|
controlA: ReadOnlyPoint,
|
|
controlB: ReadOnlyPoint,
|
|
t: number = 0.5,
|
|
): void {
|
|
const iT = 1 - t
|
|
|
|
const c1 = iT * iT * iT
|
|
const c2 = 3 * (iT * iT) * t
|
|
const c3 = 3 * iT * (t * t)
|
|
const c4 = t * t * t
|
|
|
|
out[0] = (c1 * a[0]) + (c2 * controlA[0]) + (c3 * controlB[0]) + (c4 * b[0])
|
|
out[1] = (c1 * a[1]) + (c2 * controlA[1]) + (c3 * controlB[1]) + (c4 * b[1])
|
|
}
|
|
|
|
export function createBounds(
|
|
objects: Iterable<{ boundingRect: ReadOnlyRect }>,
|
|
padding: number = 10,
|
|
): ReadOnlyRect | null {
|
|
const bounds = new Float32Array([Infinity, Infinity, -Infinity, -Infinity])
|
|
|
|
for (const obj of objects) {
|
|
const rect = obj.boundingRect
|
|
bounds[0] = Math.min(bounds[0], rect[0])
|
|
bounds[1] = Math.min(bounds[1], rect[1])
|
|
bounds[2] = Math.max(bounds[2], rect[0] + rect[2])
|
|
bounds[3] = Math.max(bounds[3], rect[1] + rect[3])
|
|
}
|
|
if (!bounds.every(x => isFinite(x))) return null
|
|
|
|
return [
|
|
bounds[0] - padding,
|
|
bounds[1] - padding,
|
|
bounds[2] - bounds[0] + (2 * padding),
|
|
bounds[3] - bounds[1] + (2 * padding),
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Snaps the provided {@link Point} or {@link Rect} ({@link pos}) to a grid of size {@link snapTo}.
|
|
* @param pos The point that will be snapped
|
|
* @param snapTo The value to round up/down by (multiples thereof)
|
|
* @returns `true` if snapTo is truthy, otherwise `false`
|
|
* @remarks `NaN` propagates through this function and does not affect return value.
|
|
*/
|
|
export function snapPoint(pos: Point | Rect, snapTo: number): boolean {
|
|
if (!snapTo) return false
|
|
|
|
pos[0] = snapTo * Math.round(pos[0] / snapTo)
|
|
pos[1] = snapTo * Math.round(pos[1] / snapTo)
|
|
return true
|
|
}
|