From 86fc39a2f43704f938c8742f974da247c139c5e0 Mon Sep 17 00:00:00 2001 From: Orion Reed Date: Mon, 16 Dec 2024 03:58:02 -0500 Subject: [PATCH] oh snap --- demo/effect-integrator.html | 4 ++-- src/common/EffectIntegrator.ts | 14 ++++++------ src/folk-base-set.ts | 5 ----- src/folk-graph.ts | 36 ++++++++++++++++++++----------- src/folk-physics.ts | 39 +++++++++++++++++++++++++--------- 5 files changed, 62 insertions(+), 36 deletions(-) diff --git a/demo/effect-integrator.html b/demo/effect-integrator.html index 0927722..5e454ab 100644 --- a/demo/effect-integrator.html +++ b/demo/effect-integrator.html @@ -43,9 +43,9 @@ - + > diff --git a/src/common/EffectIntegrator.ts b/src/common/EffectIntegrator.ts index f50e849..3adf9a1 100644 --- a/src/common/EffectIntegrator.ts +++ b/src/common/EffectIntegrator.ts @@ -4,17 +4,17 @@ import type { FolkShape } from '../folk-shape'; * Coordinates effects between multiple systems, integrating their proposals into a single result. * Systems register, yield effects, and await integration when all systems are ready. */ -export class EffectIntegrator { - private pending = new Map(); +export class EffectIntegrator { + private pending = new Map(); private systems = new Set(); private waiting = new Set(); - private resolvers: ((value: Map) => void)[] = []; + private resolvers: ((value: Map) => void)[] = []; /** Register a system to participate in effect integration */ register(id: string) { this.systems.add(id); return { - yield: (element: Element, effect: T) => { + yield: (element: E, effect: T) => { if (!this.pending.has(element)) { this.pending.set(element, []); } @@ -22,7 +22,7 @@ export class EffectIntegrator { }, /** Wait for all systems to submit effects, then receive integrated results */ - integrate: async (): Promise> => { + integrate: async (): Promise> => { this.waiting.add(id); if (this.waiting.size === this.systems.size) { @@ -30,7 +30,7 @@ export class EffectIntegrator { for (const [element, effects] of this.pending) { this.pending.set(element, (this.constructor as typeof EffectIntegrator).integrate(element, effects as T[])); } - const results = this.pending as Map; + const results = this.pending as Map; // Reset for next frame this.pending = new Map(); @@ -66,7 +66,7 @@ interface TransformEffect { height: number; } -export class TransformIntegrator extends EffectIntegrator { +export class TransformIntegrator extends EffectIntegrator { private static instance: TransformIntegrator; static register(id: string) { diff --git a/src/folk-base-set.ts b/src/folk-base-set.ts index 6be36a0..794cddf 100644 --- a/src/folk-base-set.ts +++ b/src/folk-base-set.ts @@ -63,17 +63,12 @@ export class FolkBaseSet extends FolkElement { #onSlotchange = () => this.#observeSources(); #observeSources() { - console.log('observeSources'); const childElements = new Set(this.children); const elements = this.sources ? document.querySelectorAll(this.sources) : []; const sourceElements = new Set(elements).union(childElements); const elementsToObserve = sourceElements.difference(this.sourceElements); const elementsToUnobserve = this.sourceElements.difference(sourceElements); - console.log('sourceElements', sourceElements); - console.log('elementsToObserve', elementsToObserve); - console.log('elementsToUnobserve', elementsToUnobserve); - this.unobserveSources(elementsToUnobserve); for (const el of elementsToObserve) { diff --git a/src/folk-graph.ts b/src/folk-graph.ts index 2e0a968..5f0fe5c 100644 --- a/src/folk-graph.ts +++ b/src/folk-graph.ts @@ -4,6 +4,7 @@ import { Layout } from 'webcola'; import { FolkShape } from './folk-shape.ts'; import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts'; import { FolkBaseConnection } from './folk-base-connection'; +import { TransformIntegrator } from './common/EffectIntegrator.ts'; export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHost { static override tagName = 'folk-graph'; @@ -11,6 +12,7 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo private graphSim = new Layout(); private nodes = new Map(); private arrows = new Set(); + private integrator = TransformIntegrator.register('graph'); #rAF = new AnimationFrameController(this); connectedCallback() { @@ -34,21 +36,31 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo } } - tick() { - // TODO: figure out how to let cola continue running. I was never able to do that in the past... + async tick() { this.graphSim.start(1, 0, 0, 0, true, false); - this.graphSim.nodes().forEach((node: any) => { + // Yield graph layout effects + for (const node of this.graphSim.nodes() as any[]) { const shape = node.id; - if (shape === document.activeElement) { - const rect = shape.getTransformDOMRect(); - node.x = rect.center.x; - node.y = rect.center.y; - } else { - shape.x = node.x - shape.width / 2; - shape.y = node.y - shape.height / 2; + this.integrator.yield(shape, { + x: node.x - shape.width / 2, + y: node.y - shape.height / 2, + rotation: shape.rotation, + width: shape.width, + height: shape.height, + }); + } + + // Get integrated results and update graph state + const results = await this.integrator.integrate(); + for (const [shape, result] of results) { + // TODO: this is a hack to get the node from the graph + const node = this.graphSim.nodes().find((n: any) => n.id === shape); + if (node) { + node.x = result.x + shape.width / 2; + node.y = result.y + shape.height / 2; } - }); + } } private createGraph() { @@ -60,7 +72,7 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo console.log(colaNodes, colaLinks); - this.graphSim.nodes(colaNodes).links(colaLinks).linkDistance(250).avoidOverlaps(true).handleDisconnected(true); + this.graphSim.nodes(colaNodes).links(colaLinks).linkDistance(150).avoidOverlaps(true).handleDisconnected(true); } private createNodes() { diff --git a/src/folk-physics.ts b/src/folk-physics.ts index 3fd0709..701d1ba 100644 --- a/src/folk-physics.ts +++ b/src/folk-physics.ts @@ -3,6 +3,7 @@ import { FolkBaseSet } from './folk-base-set.ts'; import { PropertyValues } from '@lit/reactive-element'; import { FolkShape } from './folk-shape'; import RAPIER, { init } from '@dimforge/rapier2d-compat'; +import { TransformIntegrator } from './common/EffectIntegrator.ts'; await init(); export class FolkPhysics extends FolkBaseSet { @@ -15,6 +16,7 @@ export class FolkPhysics extends FolkBaseSet { private elementToRect: Map = new Map(); private animationFrameId?: number; private lastTimestamp?: number; + private integrator = TransformIntegrator.register('physics'); connectedCallback() { super.connectedCallback(); @@ -119,7 +121,7 @@ export class FolkPhysics extends FolkBaseSet { } private startSimulation() { - const step = (timestamp: number) => { + const step = async (timestamp: number) => { if (!this.lastTimestamp) { this.lastTimestamp = timestamp; } @@ -127,16 +129,33 @@ export class FolkPhysics extends FolkBaseSet { if (this.world) { this.world.step(); - // Update visual elements based on physics - this.bodies.forEach((body, shape) => { - if (shape !== document.activeElement) { - const position = body.translation(); - // Scale up the position when applying to visual elements - shape.x = position.x / FolkPhysics.PHYSICS_SCALE - shape.width / 2; - shape.y = position.y / FolkPhysics.PHYSICS_SCALE - shape.height / 2; - shape.rotation = body.rotation(); + // Yield physics effects + for (const [shape, body] of this.bodies) { + const position = body.translation(); + this.integrator.yield(shape, { + x: position.x / FolkPhysics.PHYSICS_SCALE - shape.width / 2, + y: position.y / FolkPhysics.PHYSICS_SCALE - shape.height / 2, + rotation: body.rotation(), + width: shape.width, + height: shape.height, + }); + } + + // Get integrated results and update physics state + const results = await this.integrator.integrate(); + for (const [shape, result] of results) { + const body = this.bodies.get(shape); + if (body) { + body.setTranslation( + { + x: (result.x + result.width / 2) * FolkPhysics.PHYSICS_SCALE, + y: (result.y + result.height / 2) * FolkPhysics.PHYSICS_SCALE, + }, + true + ); + body.setRotation(result.rotation, true); } - }); + } } this.animationFrameId = requestAnimationFrame(step);