diff --git a/dev/GraphBrowser.tsx b/dev/GraphBrowser.tsx index c1e4b82..534db9b 100644 --- a/dev/GraphBrowser.tsx +++ b/dev/GraphBrowser.tsx @@ -8,7 +8,13 @@ import { type Node, type PositionPartial, } from "#/lib/Graph.ts"; -import { createVector, scale, type Vector } from "#/lib/Vector.ts"; +import { + createVector, + getDistance, + scale, + setDistanceNormal, + type Vector, +} from "#/lib/Vector.ts"; function drawGraph( graph: Graph, @@ -22,13 +28,15 @@ function drawGraph( } } -function applyRepulsion( - nodes: Array<{ position: Vector; velocity: Vector }>, -): void { - const strength = 16; +type PhysicsPartial = PositionPartial & { + velocity: Vector; + acceleration: Vector; +}; + +function applyRepulsion(nodes: Array): void { + const strength = 1.8; nodes.forEach((node, index) => { - node.velocity = createVector(0, 0); for (let i = 0; i < nodes.length; i++) { if (index === i) { continue; @@ -43,24 +51,33 @@ function applyRepulsion( const distanceSquare = dx * dx + dy * dy; - const magnitude = strength / distanceSquare; + const magnitude = strength / (distanceSquare + Number.EPSILON); scale(direction, magnitude); - node.velocity.x += direction.x; - node.velocity.y += direction.y; + node.acceleration.x += direction.x; + node.acceleration.y += direction.y; } }); } -function doTheHookeThing([ - a, - b, -]: Edge): void { - const edgeLength = 32; - const stiffness = 0.8; - const displacement = 0; - const f = stiffness * displacement; +function doTheHookeThing([a, b]: Edge): void { + const restLength = 48; + const stiffness = 0.3; + + const direction = createVector(0, 0); + setDistanceNormal(direction, a.position, b.position); + + const distance = getDistance(a.position, b.position); + const magnitude = (distance - restLength) * stiffness; + + scale(direction, magnitude / 2 + Number.EPSILON); + + a.acceleration.x -= direction.x; + a.acceleration.y -= direction.y; + + b.acceleration.x += direction.x; + b.acceleration.y += direction.y; } function placeNodes(graph: Graph): void { @@ -92,21 +109,70 @@ function placeNodes(graph: Graph): void { } export const GraphBrowser: Component<{ - graph: Graph<{ position: Vector; label: string }>; + graph: Graph; }> = (props) => { let canvasRef!: HTMLCanvasElement; const [width, setWidth] = createSignal(300); const [height, setHeight] = createSignal(300); + // placeNodes(props.graph); + + const nodes: Array> = [...props.graph.keys()]; + + const edges: Array> = props.graph.entries().reduce( + (acc, [node, neighbours]) => { + for (const neighbour of neighbours) { + acc.push([node, neighbour]); + } + return acc; + }, + [] as Array>, + ); + onMount(() => { const ctx: CanvasRenderingContext2D = canvasRef.getContext("2d", { alpha: false, })!; - ctx.translate(width() / 2, height() / 2); - placeNodes(props.graph); - drawGraph(props.graph, ctx); + const damping = 0.9; + + let then = self.performance.now(); + let delta = 0; + const animate = (timestamp: number): void => { + delta = timestamp - then; + then = timestamp; + + applyRepulsion(nodes); + for (const edge of edges) { + doTheHookeThing(edge); + } + + for (const node of nodes) { + node.velocity.x += node.acceleration.x; + node.velocity.y += node.acceleration.y; + + node.velocity.x *= damping; + node.velocity.y *= damping; + + node.position.x += node.velocity.x; + node.position.y += node.velocity.y; + + node.acceleration.x = 0; + node.acceleration.y = 0; + } + + ctx.restore(); + ctx.clearRect(0, 0, canvasRef.width, canvasRef.height); + ctx.save(); + ctx.translate(width() / 2, height() / 2); + + drawGraph(props.graph, ctx); + + requestAnimationFrame(animate); + }; + + animate(then); }); return ( diff --git a/dev/main.tsx b/dev/main.tsx index 8309f22..5a29e8d 100644 --- a/dev/main.tsx +++ b/dev/main.tsx @@ -33,29 +33,42 @@ function adjustGameContainer(gameContainer: HTMLElement, width: number): void { const scenesRaw = await fetchScenes(); -type TestNode = Node<{ position: Vector; label: string }>; +type TestNode = Node<{ + position: Vector; + velocity: Vector; + acceleration: Vector; + label: string; +}>; + +function createTestNode(label: string): TestNode { + return { + label, + position: createVector(Math.random(), Math.random()), + velocity: createVector(), + acceleration: createVector(), + }; +} const nodes: Array = [ - { position: createVector(), label: "foo" }, - { position: createVector(), label: "bar" }, - { position: createVector(), label: "lol" }, - { position: createVector(), label: "hai" }, - { position: createVector(), label: "sea" }, - { position: createVector(), label: "vot" }, - { position: createVector(), label: "zoe" }, - { position: createVector(), label: "asm" }, + createTestNode("foo"), + createTestNode("bar"), + createTestNode("lol"), + createTestNode("hai"), + createTestNode("sea"), + createTestNode("vot"), + createTestNode("zoe"), + createTestNode("asm"), ]; const edges: Array> = [ [nodes[0], nodes[1]], [nodes[0], nodes[2]], - [nodes[4], nodes[5]], - [nodes[5], nodes[3]], [nodes[1], nodes[2]], - [nodes[0], nodes[3]], - [nodes[2], nodes[6]], - [nodes[5], nodes[7]], + [nodes[2], nodes[3]], + [nodes[4], nodes[5]], + [nodes[4], nodes[3]], [nodes[5], nodes[6]], + [nodes[3], nodes[6]], ]; const graph = createGraph(nodes, edges);