diff --git a/frontend/src/lib/components/Camera.svelte b/frontend/src/lib/components/Camera.svelte index b315248..d586ff8 100644 --- a/frontend/src/lib/components/Camera.svelte +++ b/frontend/src/lib/components/Camera.svelte @@ -55,7 +55,7 @@ }); - + - import type { Node } from "$lib/types"; - import { T } from "@threlte/core"; + import { T, extend } from "@threlte/core"; import { MeshLineGeometry, MeshLineMaterial } from "@threlte/extras"; - import { CubicBezierCurve, Vector2, Vector3 } from "three"; + import { onMount } from "svelte"; + import { CubicBezierCurve, Mesh, Vector2, Vector3 } from "three"; - export let from: { position: { x: number; y: number } }; - export let to: { position: { x: number; y: number } }; + extend({ MeshLineGeometry, MeshLineMaterial }); - let samples = 12; + export let from: { x: number; y: number }; + export let to: { x: number; y: number }; const curve = new CubicBezierCurve( - new Vector2(from.position.x + 20, from.position.y), - new Vector2(from.position.x + 2, from.position.y), - new Vector2(to.position.x - 2, to.position.y), - new Vector2(to.position.x, to.position.y), + new Vector2(from.x, from.y), + new Vector2(from.x + 2, from.y), + new Vector2(to.x - 2, to.y), + new Vector2(to.x, to.y), ); let points: Vector3[] = []; @@ -21,32 +21,47 @@ let last_from_x = 0; let last_from_y = 0; + let mesh: Mesh; + function update(force = false) { if (!force) { - const new_x = from.position.x + to.position.x; - const new_y = from.position.y + to.position.y; + const new_x = from.x + to.x; + const new_y = from.y + to.y; if (last_from_x === new_x && last_from_y === new_y) { return; } last_from_x = new_x; last_from_y = new_y; } - curve.v0.set(from.position.x + 5, from.position.y + 0.65); - curve.v1.set(from.position.x + 7, from.position.y + 0.65); - curve.v2.set(to.position.x - 2, to.position.y + 2.5); - curve.v3.set(to.position.x, to.position.y + 2.5); + + const mid = new Vector2((from.x + to.x) / 2, (from.y + to.y) / 2); + + // const length = Math.sqrt( + // Math.pow(to.x - from.x, 2) + Math.pow(to.y - from.y, 2), + // ); + // + // let samples = Math.max(5, Math.floor(length)); + // console.log(samples); + const samples = 12; + + curve.v0.set(from.x, from.y); + curve.v1.set(mid.x, from.y); + curve.v2.set(mid.x, to.y); + curve.v3.set(to.x, to.y); points = curve.getPoints(samples).map((p) => new Vector3(p.x, 0, p.y)); + // mesh.setGeometry(points); + // mesh.needsUpdate = true; } update(); - $: if (from.position || to.position) { + $: if (from || to) { update(); } @@ -55,8 +70,8 @@ @@ -64,7 +79,7 @@ - + - + diff --git a/frontend/src/lib/components/Graph.svelte b/frontend/src/lib/components/Graph.svelte deleted file mode 100644 index 10561c1..0000000 --- a/frontend/src/lib/components/Graph.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - - - -{#each edges as edge} - -{/each} - -{#if $mouseDown && $mouseDown?.socket} - -{/if} - - -
- {#each graph.nodes as node} - - {/each} -
- - - diff --git a/frontend/src/lib/components/Node.svelte b/frontend/src/lib/components/Node.svelte index 777de2f..8cb24c3 100644 --- a/frontend/src/lib/components/Node.svelte +++ b/frontend/src/lib/components/Node.svelte @@ -1,17 +1,15 @@
- {node.type} + {node.type} / {node.id}
+
- + + + + + + + + import type { NodeInput } from "$lib/types"; + import type { Node } from "$lib/types"; + import { getGraphState } from "./graph/context"; + export let node: Node; export let value: unknown; export let input: NodeInput; - export let label: string; + export let id: string; + export let index: number; export let isLast = false; + const state = getGraphState(); + function createPath({ depth = 8, height = 20, y = 50 } = {}) { let corner = isLast ? 2 : 0; @@ -39,15 +45,29 @@ } Z`.replace(/\s+/g, " "); } + + function handleMouseDown(ev: MouseEvent) { + ev.preventDefault(); + ev.stopPropagation(); + state.setMouseDown({ + x: node.position.x, + y: node.position.y + 2.5 + index * 2.5, + node, + socketIndex: index, + isInput: true, + }); + }
- +
input
+
+ diff --git a/frontend/src/lib/components/Scene.svelte b/frontend/src/lib/components/Scene.svelte deleted file mode 100644 index c9e26a4..0000000 --- a/frontend/src/lib/components/Scene.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - -{#if $status === "idle"} - -{:else if $status === "loading"} - Loading... -{:else if $status === "error"} - Error -{/if} diff --git a/frontend/src/lib/components/Background.frag b/frontend/src/lib/components/background/Background.frag similarity index 100% rename from frontend/src/lib/components/Background.frag rename to frontend/src/lib/components/background/Background.frag diff --git a/frontend/src/lib/components/Background.story.svelte b/frontend/src/lib/components/background/Background.story.svelte similarity index 100% rename from frontend/src/lib/components/Background.story.svelte rename to frontend/src/lib/components/background/Background.story.svelte diff --git a/frontend/src/lib/components/Background.svelte b/frontend/src/lib/components/background/Background.svelte similarity index 100% rename from frontend/src/lib/components/Background.svelte rename to frontend/src/lib/components/background/Background.svelte diff --git a/frontend/src/lib/components/Background.vert b/frontend/src/lib/components/background/Background.vert similarity index 100% rename from frontend/src/lib/components/Background.vert rename to frontend/src/lib/components/background/Background.vert diff --git a/frontend/src/lib/components/background/index.ts b/frontend/src/lib/components/background/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/lib/components/debug/Debug.svelte b/frontend/src/lib/components/debug/Debug.svelte new file mode 100644 index 0000000..94686a6 --- /dev/null +++ b/frontend/src/lib/components/debug/Debug.svelte @@ -0,0 +1,24 @@ + + +{#each $points as point} + + + + +{/each} + +{#each $lines as line} + + + + +{/each} diff --git a/frontend/src/lib/components/debug/index.ts b/frontend/src/lib/components/debug/index.ts new file mode 100644 index 0000000..b539c9b --- /dev/null +++ b/frontend/src/lib/components/debug/index.ts @@ -0,0 +1,26 @@ +import type { Vector3 } from "three"; +import { lines, points } from "./store"; + +export function debugPosition(pos: Vector3) { + points.update((p) => { + p.push(pos); + return p; + }); + +} + +export function clear() { + points.set([]); + lines.set([]); +} + +export function debugLine(line: Vector3[]) { + lines.update((l) => { + l.push(line); + return l; + }); +} + +import Component from "./Debug.svelte"; + +export default Component diff --git a/frontend/src/lib/components/debug/store.ts b/frontend/src/lib/components/debug/store.ts new file mode 100644 index 0000000..4dbe8fd --- /dev/null +++ b/frontend/src/lib/components/debug/store.ts @@ -0,0 +1,6 @@ +import { writable } from "svelte/store"; +import type { Vector3 } from "three"; + +export const points = writable([]); + +export const lines = writable([]); diff --git a/frontend/src/lib/components/graph/Graph.svelte b/frontend/src/lib/components/graph/Graph.svelte new file mode 100644 index 0000000..eb87403 --- /dev/null +++ b/frontend/src/lib/components/graph/Graph.svelte @@ -0,0 +1,231 @@ + + + + + + + + + + +{#if $status === "idle"} + {#each edges as edge} + + {/each} + + {#if $mouseDown && $mouseDown?.node} + + {/if} + + +
+ {#each graph.nodes as node} + + {/each} +
+ +{:else if $status === "loading"} + Loading +{:else if $status === "error"} + Error +{/if} + + diff --git a/frontend/src/lib/components/graph/context.ts b/frontend/src/lib/components/graph/context.ts new file mode 100644 index 0000000..7eeff01 --- /dev/null +++ b/frontend/src/lib/components/graph/context.ts @@ -0,0 +1,11 @@ +import type { GraphManager } from "$lib/graph-manager"; +import { getContext } from "svelte"; +import type { GraphState } from "./graph-state"; + +export function getGraphManager(): GraphManager { + return getContext("graphManager"); +} + +export function getGraphState(): GraphState { + return getContext("graphState"); +} diff --git a/frontend/src/lib/components/graph/graph-state.ts b/frontend/src/lib/components/graph/graph-state.ts new file mode 100644 index 0000000..9fc6d9d --- /dev/null +++ b/frontend/src/lib/components/graph/graph-state.ts @@ -0,0 +1,94 @@ +import type { GraphManager } from "$lib/graph-manager"; +import type { Node } from "$lib/types"; +import { derived, get, writable, type Writable } from "svelte/store"; +import * as debug from "../debug"; + + +type Socket = { + node: Node; + index: number; + isInput: boolean; + position: [number, number]; +} + +export class GraphState { + + activeNodeId: Writable = writable(-1); + dimensions: Writable<[number, number]> = writable([100, 100]); + mouse: Writable<[number, number]> = writable([0, 0]); + mouseDown: Writable = writable(false); + cameraPosition: Writable<[number, number, number]> = writable([0, 1, 0]); + cameraBounds = derived([this.cameraPosition, this.dimensions], ([_cameraPosition, [width, height]]) => { + return [ + _cameraPosition[0] - width / _cameraPosition[2], + _cameraPosition[0] + width / _cameraPosition[2], + _cameraPosition[1] - height / _cameraPosition[2], + _cameraPosition[1] + height / _cameraPosition[2], + ] as const + }); + + possibleSockets: Socket[] = []; + hoveredSocket: Writable = writable(null); + + constructor(private graph: GraphManager) { + if (globalThis?.innerWidth && globalThis?.innerHeight) { + this.dimensions.set([window.innerWidth, window.innerHeight]); + globalThis.addEventListener("resize", () => { + this.dimensions.set([window.innerWidth, window.innerHeight]); + }) + } + } + + setMouse(x: number, y: number) { + this.mouse.set([x, y]); + } + + setMouseFromEvent(event: MouseEvent) { + const x = event.clientX; + const y = event.clientY; + + const cameraPosition = get(this.cameraPosition); + const dimensions = get(this.dimensions); + + this.mouse.set([ + cameraPosition[0] + (x - dimensions[0] / 2) / cameraPosition[2], + cameraPosition[1] + (y - dimensions[1] / 2) / cameraPosition[2], + ]); + } + + setMouseDown(opts: { x: number, y: number, node?: Node, socketIndex?: number, isInput?: boolean } | false) { + if (!opts) { + this.mouseDown.set(false); + return; + } + const { x, y, node, socketIndex, isInput } = opts; + this.mouseDown.set({ x, y, node, socketIndex, isInput }); + + if (node && socketIndex !== undefined) { + + debug.clear(); + + this.possibleSockets = this.graph.getPossibleSockets(node, socketIndex, isInput).map(([node, index]) => { + if (isInput) { + // debug.debugPosition(new Vector3(node.position.x + 5, 0, node.position.y + 0.625 + 2.5 * index)); + return { + node, + index, + position: [node.position.x + 5, node.position.y + 0.625 + 2.5 * index] + } + } else { + // debug.debugPosition(new Vector3(node.position.x, 0, node.position.y + 2.5 + 2.5 * index)); + return { + node, + index, + position: [node.position.x, node.position.y + 2.5 + 2.5 * index] + } + + } + }); + + + } + } + +} diff --git a/frontend/src/lib/components/graph/index.ts b/frontend/src/lib/components/graph/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/lib/graph-manager.ts b/frontend/src/lib/graph-manager.ts index 00b83ae..e5fe03f 100644 --- a/frontend/src/lib/graph-manager.ts +++ b/frontend/src/lib/graph-manager.ts @@ -1,6 +1,5 @@ import { writable, type Writable } from "svelte/store"; -import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node } from "./types"; -import { snapToGrid } from "./helpers"; +import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node, Edge } from "./types"; const nodeTypes: NodeType[] = [ { @@ -32,7 +31,7 @@ export class GraphManager { status: Writable<"loading" | "idle" | "error"> = writable("loading"); nodes: Node[] = []; - edges: { from: string, to: string }[] = []; + edges: Edge[] = []; private constructor(private graph: Graph, private nodeRegistry: NodeRegistry = new NodeRegistry()) { } @@ -55,23 +54,69 @@ export class GraphManager { this.status.set("idle"); } - getNode(id: string) { + getNode(id: number) { return this.nodes.find((node) => node.id === id); } - public getNodeType(id: string): NodeType { + getPossibleSockets(node: Node, socketIndex: number, isInput: boolean): [Node, number][] { + + const nodeType = this.getNodeType(node.type); + if (!nodeType) return []; + + const nodes = this.nodes.filter(n => n.id !== node.id); + + + const sockets: [Node, number][] = [] + if (isInput) { + + + const ownType = Object.values(nodeType?.inputs || {})[socketIndex].type; + + for (const node of nodes) { + const nodeType = this.getNodeType(node.type); + const inputs = nodeType?.outputs; + if (!inputs) continue; + for (let index = 0; index < inputs.length; index++) { + if (inputs[index] === ownType) { + sockets.push([node, index]); + } + } + } + + } else { + + const ownType = nodeType.outputs?.[socketIndex]; + + for (const node of nodes) { + const nodeType = this.getNodeType(node.type); + const inputs = nodeType?.inputs; + const entries = Object.values(inputs || {}); + entries.map((input, index) => { + if (input.type === ownType) { + sockets.push([node, index]); + } + }); + } + + } + + return sockets; + + } + + getNodeType(id: string): NodeType { return this.nodeRegistry.getNode(id)!; } - public getEdges() { + getEdges() { return this.edges .map((edge) => { const from = this.nodes.find((node) => node.id === edge.from); const to = this.nodes.find((node) => node.id === edge.to); if (!from || !to) return; - return [from, to] as const; + return [from, edge.fromSocket, to, edge.toSocket] as const; }) - .filter(Boolean) as unknown as [Node, Node][]; + .filter(Boolean) as unknown as [Node, number, Node, number][]; } @@ -89,7 +134,7 @@ export class GraphManager { const y = Math.floor(i / height); graph.nodes.push({ - id: `${i.toString()}`, + id: i, tmp: { visible: false, }, @@ -102,8 +147,10 @@ export class GraphManager { }); graph.edges.push({ - from: i.toString(), - to: (i + 1).toString(), + from: i, + fromSocket: 0, + to: (i + 1), + toSocket: 0, }); } diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types/index.ts similarity index 62% rename from frontend/src/lib/types.ts rename to frontend/src/lib/types/index.ts index 2f8a9a5..5cee08e 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types/index.ts @@ -1,6 +1,7 @@ +export type { NodeInput } from "./inputs"; export type Node = { - id: string; + id: number; type: string; props?: Record, tmp?: { @@ -19,28 +20,6 @@ export type Node = { } } -type NodeInputFloat = { - type: "float"; - value?: number; - min?: number; - max?: number; -} - -type NodeInputInteger = { - type: "integer"; - value?: number; - min?: number; - max?: number; -} - -type NodeInputSelect = { - type: "select"; - value?: string; - options: string[]; -} - -export type NodeInput = NodeInputFloat | NodeInputInteger | NodeInputSelect; - export type NodeType = { id: string; inputs?: Record; @@ -56,8 +35,10 @@ export interface NodeRegistry { export type Edge = { - from: string; - to: string; + from: number; + fromSocket: number; + to: number; + toSocket: number; } export type Graph = { diff --git a/frontend/src/lib/types/inputs.ts b/frontend/src/lib/types/inputs.ts new file mode 100644 index 0000000..8c9c4bc --- /dev/null +++ b/frontend/src/lib/types/inputs.ts @@ -0,0 +1,21 @@ +type NodeInputFloat = { + type: "float"; + value?: number; + min?: number; + max?: number; +} + +type NodeInputInteger = { + type: "integer"; + value?: number; + min?: number; + max?: number; +} + +type NodeInputSelect = { + type: "select"; + value?: string; + options: string[]; +} + +export type NodeInput = NodeInputFloat | NodeInputInteger | NodeInputSelect; diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index f621966..c68bd1c 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -4,10 +4,10 @@ import { PerfMonitor } from "@threlte/extras"; import { Canvas } from "@threlte/core"; - import Scene from "$lib/components/Scene.svelte"; import { GraphManager } from "$lib/graph-manager"; + import Graph from "$lib/components/graph/Graph.svelte"; - const graph = GraphManager.createEmptyGraph(); + const graph = GraphManager.createEmptyGraph({ width: 3, height: 3 }); graph.load(); onMount(async () => { @@ -30,7 +30,7 @@
- +