Shared measure functions (#254)

* Add findPointOnCurve function

* Add measure functions: contains centre/rect

* Improve measure interfaces (ReadOnlyPoint/Rect)

* nit - Doc

* Add TS strict types

* Add TS strict types

* Add TS strict types

* Add TS type
This commit is contained in:
filtered
2024-11-03 01:35:36 +11:00
committed by GitHub
parent c0217dbb7e
commit 1dfe824239
5 changed files with 133 additions and 67 deletions

View File

@@ -49,7 +49,7 @@ export class LGraph {
* ```
*/
links: Map<LinkId, LLink> & Record<LinkId, LLink>
list_of_graphcanvas?: LGraphCanvas[]
list_of_graphcanvas: LGraphCanvas[] | null
status: number
last_node_id: number
last_link_id: number
@@ -58,7 +58,7 @@ export class LGraph {
_nodes: LGraphNode[]
_nodes_by_id: Record<NodeId, LGraphNode>
_nodes_in_order: LGraphNode[]
_nodes_executable: LGraphNode[]
_nodes_executable: LGraphNode[] | null
_groups: LGraphGroup[]
iteration: number
globaltime: number
@@ -69,7 +69,7 @@ export class LGraph {
last_update_time: number
starttime: number
catch_errors: boolean
execution_timer_id: number
execution_timer_id: number | null
errors_in_execution: boolean
execution_time: number
_last_trigger_time?: number
@@ -101,8 +101,8 @@ export class LGraph {
onOutputRenamed?(old_name: string, name: string): void
onOutputTypeChanged?(name: string, type: string): void
onOutputRemoved?(name: string): void
onBeforeChange?(graph: LGraph, info: LGraphNode): void
onAfterChange?(graph: LGraph, info: LGraphNode): void
onBeforeChange?(graph: LGraph, info?: LGraphNode): void
onAfterChange?(graph: LGraph, info?: LGraphNode): void
onConnectionChange?(node: LGraphNode): void
on_change?(graph: LGraph): void
onSerialize?(data: ISerialisedGraph): void
@@ -378,11 +378,11 @@ export class LGraph {
}
//This is more internal, it computes the executable nodes in order and returns it
computeExecutionOrder(only_onExecute: boolean, set_level?: boolean): LGraphNode[] {
let L: LGraphNode[] = []
const L: LGraphNode[] = []
const S: LGraphNode[] = []
const M: Dictionary<LGraphNode> = {}
const visited_links: Record<number, boolean> = {} //to avoid repeating links
const remaining_links: Record<number, number> = {} //to a
const visited_links: Record<NodeId, boolean> = {} //to avoid repeating links
const remaining_links: Record<NodeId, number> = {} //to a
//search for the nodes without inputs (starting nodes)
for (let i = 0, l = this._nodes.length; i < l; ++i) {
@@ -414,10 +414,10 @@ export class LGraph {
}
while (true) {
if (S.length == 0) break
//get an starting node
const node = S.shift()
if (node === undefined) break
L.push(node) //add to ordered list
delete M[node.id] //remove from the pending nodes
@@ -471,13 +471,22 @@ export class LGraph {
const l = L.length
//save order number in the node
for (let i = 0; i < l; ++i) {
L[i].order = i
/** Ensure type is set */
type OrderedLGraphNode = LGraphNode & { order: number }
/** Sets the order property of each provided node to its index in {@link nodes}. */
function setOrder(nodes: LGraphNode[]): asserts nodes is OrderedLGraphNode[] {
const l = nodes.length
for (let i = 0; i < l; ++i) {
nodes[i].order = i
}
}
//save order number in the node
setOrder(L)
//sort now by priority
L = L.sort(function (A, B) {
L.sort(function (A, B) {
// @ts-expect-error ctor props
const Ap = A.constructor.priority || A.priority || 0
// @ts-expect-error ctor props
@@ -490,9 +499,7 @@ export class LGraph {
})
//save order number in the node, again...
for (let i = 0; i < l; ++i) {
L[i].order = i
}
setOrder(L)
return L
}
@@ -508,7 +515,7 @@ export class LGraph {
while (pending.length) {
const current = pending.shift()
if (!current.inputs) continue
if (!current?.inputs) continue
if (!visited[current.id] && current != node) {
visited[current.id] = true
@@ -535,7 +542,7 @@ export class LGraph {
margin = margin || 100
const nodes = this.computeExecutionOrder(false, true)
const columns = []
const columns: LGraphNode[][] = []
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i]
const col = node._level || 1
@@ -634,7 +641,7 @@ export class LGraph {
* Adds a new node instance to this graph
* @param {LGraphNode} node the instance of the node
*/
add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null {
add(node: LGraphNode | LGraphGroup, skip_compute_order?: boolean): LGraphNode | null | undefined {
if (!node) return
// LEGACY: This was changed from constructor === LGraphGroup
@@ -787,7 +794,7 @@ export class LGraph {
* @return {Array} a list with all the nodes of this type
*/
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
findNodesByClass(classObject: Function, result: LGraphNode[]): LGraphNode[] {
findNodesByClass(classObject: Function, result?: LGraphNode[]): LGraphNode[] {
result = result || []
result.length = 0
for (let i = 0, l = this._nodes.length; i < l; ++i) {
@@ -806,7 +813,7 @@ export class LGraph {
result = result || []
result.length = 0
for (let i = 0, l = this._nodes.length; i < l; ++i) {
if (this._nodes[i].type.toLowerCase() == matchType)
if (this._nodes[i].type?.toLowerCase() == matchType)
result.push(this._nodes[i])
}
return result
@@ -816,7 +823,7 @@ export class LGraph {
* @param {String} name the name of the node to search
* @return {Node} the node or null
*/
findNodeByTitle(title: string): LGraphNode {
findNodeByTitle(title: string): LGraphNode | null {
for (let i = 0, l = this._nodes.length; i < l; ++i) {
if (this._nodes[i].title == title)
return this._nodes[i]
@@ -843,20 +850,14 @@ export class LGraph {
* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
* @return {LGraphNode} the node at this position or null
*/
getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode {
getNodeOnPos(x: number, y: number, nodes_list?: LGraphNode[], margin?: number): LGraphNode | null {
nodes_list = nodes_list || this._nodes
const nRet = null
for (let i = nodes_list.length - 1; i >= 0; i--) {
const n = nodes_list[i]
const skip_title = n.constructor.title_mode == TitleMode.NO_TITLE
if (n.isPointInside(x, y, margin, skip_title)) {
// check for lesser interest nodes (TODO check for overlapping, use the top)
/*if (typeof n == "LGraphGroup"){
nRet = n;
}else{*/
if (n.isPointInside(x, y, margin, skip_title))
return n
/*}*/
}
}
return nRet
}
@@ -956,7 +957,7 @@ export class LGraph {
* @param {String} old_name
* @param {String} new_name
*/
renameInput(old_name: string, name: string): boolean {
renameInput(old_name: string, name: string): boolean | undefined {
if (name == old_name) return
if (!this.inputs[old_name]) return false
@@ -978,7 +979,7 @@ export class LGraph {
* @param {String} name
* @param {String} type
*/
changeInputType(name: string, type: string): boolean {
changeInputType(name: string, type: string): boolean | undefined {
if (!this.inputs[name]) return false
if (this.inputs[name].type &&
@@ -1045,7 +1046,7 @@ export class LGraph {
* @param {String} old_name
* @param {String} new_name
*/
renameOutput(old_name: string, name: string): boolean {
renameOutput(old_name: string, name: string): boolean | undefined {
if (!this.outputs[old_name]) return false
if (this.outputs[name]) {
@@ -1066,7 +1067,7 @@ export class LGraph {
* @param {String} name
* @param {String} type
*/
changeOutputType(name: string, type: string): boolean {
changeOutputType(name: string, type: string): boolean | undefined {
if (!this.outputs[name]) return false
if (this.outputs[name].type &&
@@ -1215,7 +1216,7 @@ export class LGraph {
* @param {String} str configure a graph from a JSON string
* @param {Boolean} returns if there was any error parsing
*/
configure(data: ISerialisedGraph, keep_old?: boolean): boolean {
configure(data: ISerialisedGraph, keep_old?: boolean): boolean | undefined {
// TODO: Finish typing configure()
if (!data) return

View File

@@ -123,7 +123,7 @@ export class LGraphNode {
title: string
graph: LGraph | null = null
id?: NodeId
id: NodeId
type: string | null = null
inputs: INodeInputSlot[] = []
outputs: INodeOutputSlot[] = []

View File

@@ -26,29 +26,38 @@ export interface IFoundSlot extends IInputOrOutput {
link_pos: Point
}
/** A point on the canvas: x, y */
/** A point represented as `[x, y]` co-ordinates */
export type Point = [x: number, y: number] | Float32Array | Float64Array
/** A size: width, height */
/** A size represented as `[width, height]` */
export type Size = [width: number, height: number] | Float32Array | Float64Array
/** A very firm array */
type ArRect = [x: number, y: number, width: number, height: number]
/** A rectangle starting at top-left coordinates: x, y, width, height */
/** A rectangle starting at top-left coordinates `[x, y, width, height]` */
export type Rect = ArRect | Float32Array | Float64Array
// TODO: Need regular arrays also?
/** A rectangle starting at top-left coordinates `[x, y, width, height]`. Requires functions exclusive to `TypedArray`. */
export type Rect32 = Float32Array
/** A point represented as `[x, y]` co-ordinates that will not be modified */
export type ReadOnlyPoint = readonly [x: number, y: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
/** A rectangle starting at top-left coordinates `[x, y, width, height]` that will not be modified */
export type ReadOnlyRect = readonly [x: number, y: number, width: number, height: number] | ReadOnlyTypedArray<Float32Array> | ReadOnlyTypedArray<Float64Array>
type TypedArrays = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array
type TypedBigIntArrays = BigInt64Array | BigUint64Array
type ReadOnlyTypedArray<T extends TypedArrays | TypedBigIntArrays> = Omit<T, "fill" | "copyWithin" | "reverse" | "set" | "sort" | "subarray">
/** Union of property names that are of type Match */
export type KeysOfType<T, Match> = { [P in keyof T]: T[P] extends Match ? P : never }[keyof T]
/** A new type that contains only the properties of T that are of type Match */
export type PickByType<T, Match> = { [P in keyof T]: Extract<T[P], Match> }
/** The names of all methods and functions in T */
export type MethodNames<T> = KeysOfType<T, (...args: any) => any>
/** The names of all (optional) methods and functions in T */
export type MethodNames<T> = KeysOfType<T, ((...args: any) => any) | undefined>
export interface IBoundaryNodes {
top: LGraphNode
@@ -113,7 +122,7 @@ export interface ConnectingLink extends IInputOrOutput {
interface IContextMenuBase {
title?: string
className?: string
callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void
callback?(value?: unknown, options?: unknown, event?: MouseEvent, previous_menu?: ContextMenu, node?: LGraphNode): void | boolean
}
/** ContextMenu */

View File

@@ -1,15 +1,13 @@
import type { Point, Rect } from "./interfaces"
import type { Point, ReadOnlyPoint, ReadOnlyRect } from "./interfaces"
import { LinkDirection } from "./types/globalEnums"
type PointReadOnly = readonly [x: number, y: number] | Float32Array | Float64Array
/**
* 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 a & b
* @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: PointReadOnly, b: PointReadOnly): number {
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])
)
@@ -18,21 +16,21 @@ export function distance(a: PointReadOnly, b: PointReadOnly): number {
/**
* Calculates the distance2 (squared) between two points (2D vector).
* Much faster when only comparing distances (closest/furthest point).
* @param a Point a as x, y
* @param b Point b as x, y
* @returns Distance2 (squared) between point a & b
* @param a Point a as `x, y`
* @param b Point b as `x, y`
* @returns Distance2 (squared) between point {@link a} & {@link b}
*/
export function dist2(a: PointReadOnly, b: PointReadOnly): number {
export function dist2(a: ReadOnlyPoint, b: ReadOnlyPoint): number {
return ((b[0] - a[0]) * (b[0] - a[0])) + ((b[1] - a[1]) * (b[1] - a[1]))
}
/**
* Determines whether a point is inside a rectangle.
* @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
* @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 isPointInRectangle(point: PointReadOnly, rect: Rect): boolean {
export function isPointInRectangle(point: ReadOnlyPoint, rect: ReadOnlyRect): boolean {
return rect[0] < point[0]
&& rect[0] + rect[2] > point[0]
&& rect[1] < point[1]
@@ -47,7 +45,7 @@ export function isPointInRectangle(point: PointReadOnly, rect: Rect): boolean {
* @param top Rect y
* @param width Rect width
* @param height Rect height
* @returns true if the point is inside the rect, otherwise false
* @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
@@ -61,7 +59,7 @@ export function isInsideRectangle(x: number, y: number, left: number, top: numbe
* @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
* @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))
@@ -70,11 +68,11 @@ export function isSortaInsideOctagon(x: number, y: number, radius: number): bool
/**
* 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
* @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: Rect, b: Rect): boolean {
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]
@@ -88,6 +86,36 @@ export function overlapBounding(a: Rect, b: Rect): boolean {
: 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 isInsideRectangle(centreX, centreY, a[0], a[1], a[2], a[3])
}
/**
* 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]
return 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
@@ -185,6 +213,34 @@ export function rotateLink(offset: Point, from: LinkDirection, to: LinkDirection
* @param point 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: PointReadOnly, lineEnd: PointReadOnly, x: number, y: number): number {
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])
}

View File

@@ -22,7 +22,7 @@ export interface Serialisable<SerialisableObject> {
/** Serialised LGraphNode */
export interface ISerialisedNode {
title?: string
id?: NodeId
id: NodeId
type?: string
pos?: Point
size?: Size