diff --git a/frontend/src/lib/components/Edge.svelte b/frontend/src/lib/components/Edge.svelte
index 6d016bc..f6a713e 100644
--- a/frontend/src/lib/components/Edge.svelte
+++ b/frontend/src/lib/components/Edge.svelte
@@ -1,7 +1,6 @@
@@ -180,15 +216,15 @@
/>
{#if $status === "idle"}
- {#each edges as edge}
+ {#each edgePositions as [x1, y1, x2, y2]}
{/each}
@@ -203,9 +239,9 @@
tabindex="0"
class="wrapper"
class:zoom-small={$cameraPosition[2] < 10}
- style={`--cz: ${$cameraPosition[2]}`}
+ style={`--cz: ${$cameraPosition[2]}; ${$mouseDown ? `--node-hovered-${$mouseDown.isInput ? "out" : "in"}-${$mouseDown.type}: red;` : ""}`}
>
- {#each graph.nodes as node}
+ {#each $nodes as node}
{/each}
diff --git a/frontend/src/lib/components/graph/graph-state.ts b/frontend/src/lib/components/graph/state.ts
similarity index 58%
rename from frontend/src/lib/components/graph/graph-state.ts
rename to frontend/src/lib/components/graph/state.ts
index 9fc6d9d..f9ebd9b 100644
--- a/frontend/src/lib/components/graph/graph-state.ts
+++ b/frontend/src/lib/components/graph/state.ts
@@ -8,6 +8,7 @@ type Socket = {
node: Node;
index: number;
isInput: boolean;
+ type: string;
position: [number, number];
}
@@ -16,7 +17,7 @@ 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);
+ mouseDown: Writable)> = writable(false);
cameraPosition: Writable<[number, number, number]> = writable([0, 1, 0]);
cameraBounds = derived([this.cameraPosition, this.dimensions], ([_cameraPosition, [width, height]]) => {
return [
@@ -56,39 +57,73 @@ export class GraphState {
]);
}
- setMouseDown(opts: { x: number, y: number, node?: Node, socketIndex?: number, isInput?: boolean } | false) {
+ getSocketPosition(node: Node, index: number | string) {
+ const isOutput = typeof index === "number";
+ if (isOutput) {
+ return [node.position.x + 5, node.position.y + 0.625 + 2.5 * index] as const;
+ } else {
+ const _index = Object.keys(node.tmp?.type?.inputs || {}).indexOf(index);
+ return [node.position.x, node.position.y + 2.5 + 2.5 * _index] as const;
+ }
+ }
+
+ setMouseDown(opts: ({ x: number, y: number } & Omit) | 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) {
+ let { x, y, node, index, isInput, type } = opts;
+
+ if (node && index !== undefined && isInput !== undefined) {
debug.clear();
- this.possibleSockets = this.graph.getPossibleSockets(node, socketIndex, isInput).map(([node, index]) => {
+ // remove existing edge
+ if (isInput) {
+ const edges = this.graph.getEdgesToNode(node);
+ const key = Object.keys(node.tmp?.type?.inputs || {})[index];
+ for (const edge of edges) {
+ if (edge[3] === key) {
+ node = edge[2];
+ index = 0;
+ const pos = this.getSocketPosition(edge[0], index);
+ x = pos[0];
+ y = pos[1];
+ isInput = false;
+ this.graph.removeEdge(edge);
+ break;
+ }
+ }
+ }
+
+ this.mouseDown.set({ x, y, node, index, isInput, type });
+
+ this.possibleSockets = this.graph.getPossibleSockets(node, index, isInput).map(([node, index]) => {
if (isInput) {
- // debug.debugPosition(new Vector3(node.position.x + 5, 0, node.position.y + 0.625 + 2.5 * index));
+ const key = Object.keys(node.tmp?.type?.inputs || {})[index];
return {
node,
index,
+ isInput,
+ type: node.tmp?.type?.inputs?.[key].type || "",
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,
+ isInput,
+ type: node.tmp?.type?.outputs?.[index] || "",
position: [node.position.x, node.position.y + 2.5 + 2.5 * index]
}
-
}
});
-
-
}
+
+ console.log("possibleSockets", this.possibleSockets);
+
}
}
diff --git a/frontend/src/lib/graph-manager.ts b/frontend/src/lib/graph-manager.ts
index e5fe03f..01f1c45 100644
--- a/frontend/src/lib/graph-manager.ts
+++ b/frontend/src/lib/graph-manager.ts
@@ -1,4 +1,4 @@
-import { writable, type Writable } from "svelte/store";
+import { get, writable, type Writable } from "svelte/store";
import type { Graph, NodeRegistry as INodeRegistry, NodeType, Node, Edge } from "./types";
const nodeTypes: NodeType[] = [
@@ -12,11 +12,19 @@ const nodeTypes: NodeType[] = [
{
id: "math",
inputs: {
+ "type": { type: "select", options: ["add", "subtract", "multiply", "divide"], internal: true },
"a": { type: "float" },
"b": { type: "float" },
},
outputs: ["float"],
},
+ {
+ id: "output",
+ inputs: {
+ "input": { type: "float" },
+ },
+ outputs: [],
+ }
]
export class NodeRegistry implements INodeRegistry {
@@ -30,10 +38,18 @@ export class GraphManager {
status: Writable<"loading" | "idle" | "error"> = writable("loading");
- nodes: Node[] = [];
- edges: Edge[] = [];
+ private _nodes: Node[] = [];
+ nodes: Writable = writable([]);
+ private _edges: Edge[] = [];
+ edges: Writable = writable([]);
private constructor(private graph: Graph, private nodeRegistry: NodeRegistry = new NodeRegistry()) {
+ this.nodes.subscribe((nodes) => {
+ this._nodes = nodes;
+ });
+ this.edges.subscribe((edges) => {
+ this._edges = edges;
+ });
}
async load() {
@@ -47,15 +63,27 @@ export class GraphManager {
this.status.set("error");
return;
}
+ node.tmp = node.tmp || {};
+ node.tmp.type = nodeType;
}
- this.nodes = this.graph.nodes;
- this.edges = this.graph.edges;
+ this.nodes.set(nodes);
+ this.edges.set(this.graph.edges.map((edge) => {
+ const from = this._nodes.find((node) => node.id === edge[0]);
+ const to = this._nodes.find((node) => node.id === edge[2]);
+ if (!from || !to) return;
+ return [from, edge[1], to, edge[3]] as const;
+ })
+ .filter(Boolean) as unknown as [Node, number, Node, string][]
+ );
+
+
this.status.set("idle");
}
+
getNode(id: number) {
- return this.nodes.find((node) => node.id === id);
+ return this._nodes.find((node) => node.id === id);
}
getPossibleSockets(node: Node, socketIndex: number, isInput: boolean): [Node, number][] {
@@ -63,13 +91,11 @@ export class GraphManager {
const nodeType = this.getNodeType(node.type);
if (!nodeType) return [];
- const nodes = this.nodes.filter(n => n.id !== node.id);
-
+ 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) {
@@ -108,17 +134,39 @@ export class GraphManager {
return this.nodeRegistry.getNode(id)!;
}
- 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, edge.fromSocket, to, edge.toSocket] as const;
- })
- .filter(Boolean) as unknown as [Node, number, Node, number][];
+ removeEdge(edge: Edge) {
+ const id0 = edge[0].id;
+ const sid0 = edge[1];
+ const id2 = edge[2].id;
+ const sid2 = edge[3];
+ this.edges.update((edges) => {
+ return edges.filter((e) => e[0].id !== id0 || e[1] !== sid0 || e[2].id !== id2 || e[3] !== sid2);
+ });
}
+ getEdgesToNode(node: Node) {
+ return this._edges
+ .filter((edge) => edge[2].id === node.id)
+ .map((edge) => {
+ const from = this._nodes.find((node) => node.id === edge[0].id);
+ const to = this._nodes.find((node) => node.id === edge[2].id);
+ if (!from || !to) return;
+ return [from, edge[1], to, edge[3]] as const;
+ })
+ .filter(Boolean) as unknown as [Node, number, Node, string][];
+ }
+
+ getEdgesFromNode(node: Node) {
+ return this._edges
+ .filter((edge) => edge[0] === node.id)
+ .map((edge) => {
+ const from = this._nodes.find((node) => node.id === edge[0]);
+ const to = this._nodes.find((node) => node.id === edge[2]);
+ if (!from || !to) return;
+ return [from, edge[1], to, edge[3]] as const;
+ })
+ .filter(Boolean) as unknown as [Node, number, Node, string][];
+ }
static createEmptyGraph({ width = 20, height = 20 } = {}): GraphManager {
@@ -146,14 +194,22 @@ export class GraphManager {
type: i == 0 ? "input/float" : "math",
});
- graph.edges.push({
- from: i,
- fromSocket: 0,
- to: (i + 1),
- toSocket: 0,
- });
+ graph.edges.push([i, 0, i + 1, i === amount - 1 ? "input" : "a",]);
}
+ graph.nodes.push({
+ id: amount,
+ tmp: {
+ visible: false,
+ },
+ position: {
+ x: width * 7.5,
+ y: (height - 1) * 10,
+ },
+ type: "output",
+ props: {},
+ });
+
return new GraphManager(graph);
}
diff --git a/frontend/src/lib/helpers.ts b/frontend/src/lib/helpers.ts
index 87c43f1..d3d34b0 100644
--- a/frontend/src/lib/helpers.ts
+++ b/frontend/src/lib/helpers.ts
@@ -1,3 +1,23 @@
export function snapToGrid(value: number, gridSize: number = 10) {
return Math.round(value / gridSize) * gridSize;
+}
+
+export function lerp(a: number, b: number, t: number) {
+ return a + (b - a) * t;
+}
+
+export function animate(duration: number, callback: (progress: number) => void | false) {
+ const start = performance.now();
+ const loop = (time: number) => {
+ const progress = (time - start) / duration;
+ if (progress < 1) {
+ const res = callback(progress);
+ if (res !== false) {
+ requestAnimationFrame(loop);
+ }
+ } else {
+ callback(1);
+ }
}
+ requestAnimationFrame(loop);
+}
diff --git a/frontend/src/lib/types/index.ts b/frontend/src/lib/types/index.ts
index 5cee08e..639ac5b 100644
--- a/frontend/src/lib/types/index.ts
+++ b/frontend/src/lib/types/index.ts
@@ -1,3 +1,4 @@
+import type { NodeInput } from "./inputs";
export type { NodeInput } from "./inputs";
export type Node = {
@@ -5,6 +6,7 @@ export type Node = {
type: string;
props?: Record,
tmp?: {
+ type?: NodeType;
downX?: number;
downY?: number;
visible?: boolean;
@@ -34,12 +36,7 @@ export interface NodeRegistry {
}
-export type Edge = {
- from: number;
- fromSocket: number;
- to: number;
- toSocket: number;
-}
+export type Edge = [Node, number, Node, string];
export type Graph = {
meta?: {
@@ -47,5 +44,5 @@ export type Graph = {
lastModified?: string;
},
nodes: Node[];
- edges: Edge[];
+ edges: [number, number, number, string][];
}
diff --git a/frontend/src/lib/types/inputs.ts b/frontend/src/lib/types/inputs.ts
index 8c9c4bc..31465ab 100644
--- a/frontend/src/lib/types/inputs.ts
+++ b/frontend/src/lib/types/inputs.ts
@@ -18,4 +18,8 @@ type NodeInputSelect = {
options: string[];
}
-export type NodeInput = NodeInputFloat | NodeInputInteger | NodeInputSelect;
+type DefaultOptions = {
+ internal?: boolean;
+}
+
+export type NodeInput = (NodeInputFloat | NodeInputInteger | NodeInputSelect) & DefaultOptions;
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte
index c68bd1c..f3a1c92 100644
--- a/frontend/src/routes/+page.svelte
+++ b/frontend/src/routes/+page.svelte
@@ -7,7 +7,7 @@
import { GraphManager } from "$lib/graph-manager";
import Graph from "$lib/components/graph/Graph.svelte";
- const graph = GraphManager.createEmptyGraph({ width: 3, height: 3 });
+ const graph = GraphManager.createEmptyGraph({ width: 12, height: 12 });
graph.load();
onMount(async () => {