oh snap
This commit is contained in:
parent
6774e83774
commit
86fc39a2f4
|
|
@ -43,9 +43,9 @@
|
||||||
<folk-rope id="rope4" source="#shape3" target="#shape4"></folk-rope>
|
<folk-rope id="rope4" source="#shape3" target="#shape4"></folk-rope>
|
||||||
<folk-rope id="rope5" source="#shape4" target="#shape8"></folk-rope>
|
<folk-rope id="rope5" source="#shape4" target="#shape8"></folk-rope>
|
||||||
<folk-rope id="rope6" source="#shape5" target="#shape8"></folk-rope>
|
<folk-rope id="rope6" source="#shape5" target="#shape8"></folk-rope>
|
||||||
<!-- <folk-graph
|
<folk-graph
|
||||||
sources="#shape1, #shape2, #shape3, #shape4, #shape5, #shape6, #shape7, #shape8, #rope1, #rope2, #rope3, #rope4, #rope5, #rope6"
|
sources="#shape1, #shape2, #shape3, #shape4, #shape5, #shape6, #shape7, #shape8, #rope1, #rope2, #rope3, #rope4, #rope5, #rope6"
|
||||||
></folk-graph> -->
|
></folk-graph>
|
||||||
<folk-physics
|
<folk-physics
|
||||||
sources="#shape1, #shape2, #shape3, #shape4, #shape5, #shape6, #shape7, #shape8, #rope1, #rope2, #rope3, #rope4, #rope5, #rope6"
|
sources="#shape1, #shape2, #shape3, #shape4, #shape5, #shape6, #shape7, #shape8, #rope1, #rope2, #rope3, #rope4, #rope5, #rope6"
|
||||||
></folk-physics>
|
></folk-physics>
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,17 @@ import type { FolkShape } from '../folk-shape';
|
||||||
* Coordinates effects between multiple systems, integrating their proposals into a single result.
|
* Coordinates effects between multiple systems, integrating their proposals into a single result.
|
||||||
* Systems register, yield effects, and await integration when all systems are ready.
|
* Systems register, yield effects, and await integration when all systems are ready.
|
||||||
*/
|
*/
|
||||||
export class EffectIntegrator<T> {
|
export class EffectIntegrator<E extends Element, T> {
|
||||||
private pending = new Map<Element, T[] | T>();
|
private pending = new Map<E, T[] | T>();
|
||||||
private systems = new Set<string>();
|
private systems = new Set<string>();
|
||||||
private waiting = new Set<string>();
|
private waiting = new Set<string>();
|
||||||
private resolvers: ((value: Map<Element, T>) => void)[] = [];
|
private resolvers: ((value: Map<E, T>) => void)[] = [];
|
||||||
|
|
||||||
/** Register a system to participate in effect integration */
|
/** Register a system to participate in effect integration */
|
||||||
register(id: string) {
|
register(id: string) {
|
||||||
this.systems.add(id);
|
this.systems.add(id);
|
||||||
return {
|
return {
|
||||||
yield: (element: Element, effect: T) => {
|
yield: (element: E, effect: T) => {
|
||||||
if (!this.pending.has(element)) {
|
if (!this.pending.has(element)) {
|
||||||
this.pending.set(element, []);
|
this.pending.set(element, []);
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export class EffectIntegrator<T> {
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Wait for all systems to submit effects, then receive integrated results */
|
/** Wait for all systems to submit effects, then receive integrated results */
|
||||||
integrate: async (): Promise<Map<Element, T>> => {
|
integrate: async (): Promise<Map<E, T>> => {
|
||||||
this.waiting.add(id);
|
this.waiting.add(id);
|
||||||
|
|
||||||
if (this.waiting.size === this.systems.size) {
|
if (this.waiting.size === this.systems.size) {
|
||||||
|
|
@ -30,7 +30,7 @@ export class EffectIntegrator<T> {
|
||||||
for (const [element, effects] of this.pending) {
|
for (const [element, effects] of this.pending) {
|
||||||
this.pending.set(element, (this.constructor as typeof EffectIntegrator).integrate(element, effects as T[]));
|
this.pending.set(element, (this.constructor as typeof EffectIntegrator).integrate(element, effects as T[]));
|
||||||
}
|
}
|
||||||
const results = this.pending as Map<Element, T>;
|
const results = this.pending as Map<E, T>;
|
||||||
|
|
||||||
// Reset for next frame
|
// Reset for next frame
|
||||||
this.pending = new Map();
|
this.pending = new Map();
|
||||||
|
|
@ -66,7 +66,7 @@ interface TransformEffect {
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TransformIntegrator extends EffectIntegrator<TransformEffect> {
|
export class TransformIntegrator extends EffectIntegrator<FolkShape, TransformEffect> {
|
||||||
private static instance: TransformIntegrator;
|
private static instance: TransformIntegrator;
|
||||||
|
|
||||||
static register(id: string) {
|
static register(id: string) {
|
||||||
|
|
|
||||||
|
|
@ -63,17 +63,12 @@ export class FolkBaseSet extends FolkElement {
|
||||||
#onSlotchange = () => this.#observeSources();
|
#onSlotchange = () => this.#observeSources();
|
||||||
|
|
||||||
#observeSources() {
|
#observeSources() {
|
||||||
console.log('observeSources');
|
|
||||||
const childElements = new Set(this.children);
|
const childElements = new Set(this.children);
|
||||||
const elements = this.sources ? document.querySelectorAll(this.sources) : [];
|
const elements = this.sources ? document.querySelectorAll(this.sources) : [];
|
||||||
const sourceElements = new Set(elements).union(childElements);
|
const sourceElements = new Set(elements).union(childElements);
|
||||||
const elementsToObserve = sourceElements.difference(this.sourceElements);
|
const elementsToObserve = sourceElements.difference(this.sourceElements);
|
||||||
const elementsToUnobserve = this.sourceElements.difference(sourceElements);
|
const elementsToUnobserve = this.sourceElements.difference(sourceElements);
|
||||||
|
|
||||||
console.log('sourceElements', sourceElements);
|
|
||||||
console.log('elementsToObserve', elementsToObserve);
|
|
||||||
console.log('elementsToUnobserve', elementsToUnobserve);
|
|
||||||
|
|
||||||
this.unobserveSources(elementsToUnobserve);
|
this.unobserveSources(elementsToUnobserve);
|
||||||
|
|
||||||
for (const el of elementsToObserve) {
|
for (const el of elementsToObserve) {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Layout } from 'webcola';
|
||||||
import { FolkShape } from './folk-shape.ts';
|
import { FolkShape } from './folk-shape.ts';
|
||||||
import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts';
|
import { AnimationFrameController, AnimationFrameControllerHost } from './common/animation-frame-controller.ts';
|
||||||
import { FolkBaseConnection } from './folk-base-connection';
|
import { FolkBaseConnection } from './folk-base-connection';
|
||||||
|
import { TransformIntegrator } from './common/EffectIntegrator.ts';
|
||||||
|
|
||||||
export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHost {
|
export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHost {
|
||||||
static override tagName = 'folk-graph';
|
static override tagName = 'folk-graph';
|
||||||
|
|
@ -11,6 +12,7 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo
|
||||||
private graphSim = new Layout();
|
private graphSim = new Layout();
|
||||||
private nodes = new Map<FolkShape, number>();
|
private nodes = new Map<FolkShape, number>();
|
||||||
private arrows = new Set<FolkBaseConnection>();
|
private arrows = new Set<FolkBaseConnection>();
|
||||||
|
private integrator = TransformIntegrator.register('graph');
|
||||||
#rAF = new AnimationFrameController(this);
|
#rAF = new AnimationFrameController(this);
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
|
@ -34,21 +36,31 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
async tick() {
|
||||||
// TODO: figure out how to let cola continue running. I was never able to do that in the past...
|
|
||||||
this.graphSim.start(1, 0, 0, 0, true, false);
|
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;
|
const shape = node.id;
|
||||||
if (shape === document.activeElement) {
|
this.integrator.yield(shape, {
|
||||||
const rect = shape.getTransformDOMRect();
|
x: node.x - shape.width / 2,
|
||||||
node.x = rect.center.x;
|
y: node.y - shape.height / 2,
|
||||||
node.y = rect.center.y;
|
rotation: shape.rotation,
|
||||||
} else {
|
width: shape.width,
|
||||||
shape.x = node.x - shape.width / 2;
|
height: shape.height,
|
||||||
shape.y = node.y - shape.height / 2;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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() {
|
private createGraph() {
|
||||||
|
|
@ -60,7 +72,7 @@ export class FolkGraph extends FolkBaseSet implements AnimationFrameControllerHo
|
||||||
|
|
||||||
console.log(colaNodes, colaLinks);
|
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() {
|
private createNodes() {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { FolkBaseSet } from './folk-base-set.ts';
|
||||||
import { PropertyValues } from '@lit/reactive-element';
|
import { PropertyValues } from '@lit/reactive-element';
|
||||||
import { FolkShape } from './folk-shape';
|
import { FolkShape } from './folk-shape';
|
||||||
import RAPIER, { init } from '@dimforge/rapier2d-compat';
|
import RAPIER, { init } from '@dimforge/rapier2d-compat';
|
||||||
|
import { TransformIntegrator } from './common/EffectIntegrator.ts';
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
export class FolkPhysics extends FolkBaseSet {
|
export class FolkPhysics extends FolkBaseSet {
|
||||||
|
|
@ -15,6 +16,7 @@ export class FolkPhysics extends FolkBaseSet {
|
||||||
private elementToRect: Map<FolkShape, DOMRectTransform> = new Map();
|
private elementToRect: Map<FolkShape, DOMRectTransform> = new Map();
|
||||||
private animationFrameId?: number;
|
private animationFrameId?: number;
|
||||||
private lastTimestamp?: number;
|
private lastTimestamp?: number;
|
||||||
|
private integrator = TransformIntegrator.register('physics');
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
@ -119,7 +121,7 @@ export class FolkPhysics extends FolkBaseSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private startSimulation() {
|
private startSimulation() {
|
||||||
const step = (timestamp: number) => {
|
const step = async (timestamp: number) => {
|
||||||
if (!this.lastTimestamp) {
|
if (!this.lastTimestamp) {
|
||||||
this.lastTimestamp = timestamp;
|
this.lastTimestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
@ -127,16 +129,33 @@ export class FolkPhysics extends FolkBaseSet {
|
||||||
if (this.world) {
|
if (this.world) {
|
||||||
this.world.step();
|
this.world.step();
|
||||||
|
|
||||||
// Update visual elements based on physics
|
// Yield physics effects
|
||||||
this.bodies.forEach((body, shape) => {
|
for (const [shape, body] of this.bodies) {
|
||||||
if (shape !== document.activeElement) {
|
const position = body.translation();
|
||||||
const position = body.translation();
|
this.integrator.yield(shape, {
|
||||||
// Scale up the position when applying to visual elements
|
x: position.x / FolkPhysics.PHYSICS_SCALE - shape.width / 2,
|
||||||
shape.x = position.x / FolkPhysics.PHYSICS_SCALE - shape.width / 2;
|
y: position.y / FolkPhysics.PHYSICS_SCALE - shape.height / 2,
|
||||||
shape.y = position.y / FolkPhysics.PHYSICS_SCALE - shape.height / 2;
|
rotation: body.rotation(),
|
||||||
shape.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);
|
this.animationFrameId = requestAnimationFrame(step);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue