From 796fdf4a798f39ea486d3f3f438d20934c6f3925 Mon Sep 17 00:00:00 2001 From: LoboTheDark Date: Tue, 17 Feb 2026 09:39:37 +0100 Subject: [PATCH 01/14] Fixed small visual problems with canvas --- .../canvas/babylon-canvas.component.scss | 16 +++++---- .../canvas/babylon-canvas.component.ts | 34 +++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.scss b/src/app/shared/rendering/canvas/babylon-canvas.component.scss index aca3f68..b482a7a 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.scss +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.scss @@ -1,16 +1,19 @@ .canvas-container { - width: 100%; display: flex; justify-content: center; align-items: center; - background-color: #1a1a1a; + width: 100%; + + max-width: 1000px; + max-height: 1000px; + margin: 0 auto; } canvas { - aspect-ratio: 1 / 1; - + display: block; width: 100%; - height: auto; + height: 100%; + aspect-ratio: 1 / 1; min-width: 200px; min-height: 200px; @@ -18,8 +21,7 @@ canvas { max-height: 1000px; touch-action: none; - display: block; - border: none; + border-radius: 20px; outline: none; } diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index 5ad06cf..a3ee058 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -1,5 +1,5 @@ import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; -import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core'; +import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; export interface RenderConfig { mode: '2D' | '3D'; @@ -27,19 +27,17 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { @Output() sceneReady = new EventEmitter(); - private engine!: Engine; + private engine!: WebGPUEngine; private scene!: Scene; private shaderMaterial!: ShaderMaterial; private camera!: Camera; //Listener - private resizeHandler = () => this.handleResize(); - private wheelHandler = (evt: WheelEvent) => evt.preventDefault(); + private readonly resizeHandler = () => this.handleResize(); + private readonly wheelHandler = (evt: WheelEvent) => evt.preventDefault(); ngAfterViewInit(): void { - this.ngZone.runOutsideAngular(() => { - this.initBabylon(); - }); + this.initBabylon().then(() => { console.log("Engine initialized"); }); } ngOnDestroy(): void { @@ -55,16 +53,18 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } } - private initBabylon(): void { + private async initBabylon(): Promise { const canvas = this.canvasRef.nativeElement; - this.engine = new Engine(canvas, true); - this.scene = new Scene(this.engine); - this.setupCamera(canvas); - this.addListener(canvas); - this.createShaderMaterial(); - this.createFullScreenRect(); - this.sceneReady.emit(this.scene); - this.addRenderLoop(canvas); + this.engine = new WebGPUEngine(canvas); + await this.engine.initAsync().then(() => { + this.scene = new Scene(this.engine); + this.setupCamera(canvas); + this.addListener(canvas); + this.createShaderMaterial(); + this.createFullScreenRect(); + this.sceneReady.emit(this.scene); + this.addRenderLoop(canvas); + }); } private addListener(canvas: HTMLCanvasElement) { @@ -109,7 +109,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } private createFullScreenRect() { - const plane = MeshBuilder.CreatePlane("plane", {size: 110}, this.scene); + const plane = MeshBuilder.CreatePlane("plane", {size: 100}, this.scene); if (this.config.mode === '3D') { plane.parent = this.camera; -- 2.49.1 From 68e21489ea58dc54f3340ad89eb650eb61a25120 Mon Sep 17 00:00:00 2001 From: LoboTheDark Date: Tue, 17 Feb 2026 10:41:51 +0100 Subject: [PATCH 02/14] Update babylon-canvas.component.ts --- src/app/shared/rendering/canvas/babylon-canvas.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index a3ee058..f5af17d 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -1,8 +1,9 @@ import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; -import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; +import {ArcRotateCamera, Camera, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; export interface RenderConfig { mode: '2D' | '3D'; + computeShaderSupport?: boolean; initialViewSize: number; vertexShader: string; fragmentShader: string; -- 2.49.1 From 55ece27e1cab635c701baa6616e573fad36998b0 Mon Sep 17 00:00:00 2001 From: LoboTheDark Date: Wed, 18 Feb 2026 11:58:25 +0100 Subject: [PATCH 03/14] Add pendulum demo and WGSL support Introduce a new Pendulum demo (component, template, stylesheet) and wire it into routing and the algorithms list. Extend Babylon canvas API to emit a SceneReadyEvent (scene + engine) and accept a shaderLanguage option in RenderConfig so materials/shaders can target WGSL; update Fractal to consume the new SceneReadyEvent signature. Also add i18n entries for the pendulum demo. --- src/app/app.routes.ts | 3 +- src/app/constants/RouterConstants.ts | 7 + .../algorithms/fractal/fractal.component.ts | 6 +- .../algorithms/pendle/pendulum.component.html | 11 ++ .../algorithms/pendle/pendulum.component.scss | 0 .../algorithms/pendle/pendulum.component.ts | 164 ++++++++++++++++++ .../algorithms/service/algorithms.service.ts | 6 + .../canvas/babylon-canvas.component.ts | 19 +- src/assets/i18n/de.json | 4 + src/assets/i18n/en.json | 4 + 10 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 src/app/pages/algorithms/pendle/pendulum.component.html create mode 100644 src/app/pages/algorithms/pendle/pendulum.component.scss create mode 100644 src/app/pages/algorithms/pendle/pendulum.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c8960c9..d079fbb 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -13,6 +13,7 @@ export const routes: Routes = [ { path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT}, { path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT}, { path: RouterConstants.FRACTAL.PATH, component: RouterConstants.FRACTAL.COMPONENT}, - { path: RouterConstants.FRACTAL3d.PATH, component: RouterConstants.FRACTAL3d.COMPONENT} + { path: RouterConstants.FRACTAL3d.PATH, component: RouterConstants.FRACTAL3d.COMPONENT}, + { path: RouterConstants.PENDULUM.PATH, component: RouterConstants.PENDULUM.COMPONENT} ]; diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 6b9b9d9..e199a65 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -8,6 +8,7 @@ import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.comp import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component'; import {FractalComponent} from '../pages/algorithms/fractal/fractal.component'; import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component'; +import {PendulumComponent} from '../pages/algorithms/pendle/pendulum.component'; export class RouterConstants { @@ -65,6 +66,12 @@ export class RouterConstants { COMPONENT: Fractal3dComponent }; + static readonly PENDULUM = { + PATH: 'algorithms/pendulum', + LINK: '/algorithms/pendulum', + COMPONENT: PendulumComponent + }; + static readonly IMPRINT = { PATH: 'imprint', LINK: '/imprint', diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index d7aed47..c621eb8 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -8,7 +8,7 @@ import {MatSelect} from '@angular/material/select'; import {AlgorithmInformation} from '../information/information.models'; import {UrlConstants} from '../../../constants/UrlConstants'; import {FormsModule} from '@angular/forms'; -import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component'; +import {BabylonCanvas, RenderCallback, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader'; import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core'; import {MatButton} from '@angular/material/button'; @@ -155,8 +155,8 @@ export class FractalComponent implements OnInit { } } - onSceneReady(scene: Scene): void { - scene.onPointerObservable.add((pointerInfo) => { + onSceneReady(event: SceneReadyEvent): void { + event.scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case PointerEventTypes.POINTERDOWN: diff --git a/src/app/pages/algorithms/pendle/pendulum.component.html b/src/app/pages/algorithms/pendle/pendulum.component.html new file mode 100644 index 0000000..5e9e0b0 --- /dev/null +++ b/src/app/pages/algorithms/pendle/pendulum.component.html @@ -0,0 +1,11 @@ + + + Ich bin ein Pendel - blub + + + + + diff --git a/src/app/pages/algorithms/pendle/pendulum.component.scss b/src/app/pages/algorithms/pendle/pendulum.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/algorithms/pendle/pendulum.component.ts b/src/app/pages/algorithms/pendle/pendulum.component.ts new file mode 100644 index 0000000..f495381 --- /dev/null +++ b/src/app/pages/algorithms/pendle/pendulum.component.ts @@ -0,0 +1,164 @@ +import {Component} from '@angular/core'; +import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; + +@Component({ + selector: 'app-pendulum', + imports: [ + BabylonCanvas, + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + ], + templateUrl: './pendulum.component.html', + styleUrl: './pendulum.component.scss', +}) +export class PendulumComponent { +// --- VERTEX SHADER --- +// Das hat funktioniert. Wir definieren Attribute, Babylon baut 'VertexInputs'. +// Wir geben 'FragmentInputs' zurück (was Babylon aus unseren varying baut). + private readonly vertexShaderWGSL = ` + attribute position : vec3; + attribute uv : vec2; + varying vUV : vec2; + + @vertex + fn main(input : VertexInputs) -> FragmentInputs { + var output : FragmentInputs; + output.position = vec4(input.position, 1.0); + output.vUV = input.uv; + return output; + } + `; + +// --- FRAGMENT SHADER (FIXED) --- +// Änderungen: +// 1. Rückgabetyp ist jetzt 'FragmentOutputs' (das Babylon Struct). +// 2. Wir schreiben in 'fragmentOutputs.color'. +// 3. Wir returnen 'fragmentOutputs'. + private readonly fragmentShaderWGSL = ` + varying vUV : vec2; + + var pixelBuffer : array; + var params : Params; + + struct Params { + resolution: vec2, + time: f32 + }; + + @fragment + fn main(input : FragmentInputs) -> FragmentOutputs { + let x = u32(input.vUV.x * params.resolution.x); + let y = u32(input.vUV.y * params.resolution.y); + let width = u32(params.resolution.x); + + let index = y * width + x; + let total = u32(params.resolution.x * params.resolution.y); + + // Default Farbe (schwarz) + var color = vec4(0.0, 0.0, 0.0, 1.0); + + if (index < total) { + let val = pixelBuffer[index]; + color = vec4(val, val * 0.5, 0.2, 1.0); + } + + // Babylon stellt die Variable 'fragmentOutputs' bereit. + // Das Feld heißt standardmäßig 'color'. + fragmentOutputs.color = color; + + return fragmentOutputs; + } + `; + +// --- COMPUTE SHADER (Unverändert) --- + private readonly computeShaderWGSL = ` + @group(0) @binding(0) var pixelBuffer : array; + @group(0) @binding(1) var params : Params; + + struct Params { + resolution: vec2, + time: f32 + }; + + @compute @workgroup_size(64) + fn main(@builtin(global_invocation_id) global_id : vec3) { + let index = global_id.x; + let totalPixels = u32(params.resolution.x * params.resolution.y); + if (index >= totalPixels) { return; } + + let width = u32(params.resolution.x); + let x = f32(index % width); + let y = f32(index / width); + + // Zeit-Variable nutzen für Animation + let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); + + pixelBuffer[index] = value * 0.5 + 0.5; + } + `; + + renderConfig: RenderConfig = { + mode: '2D', + initialViewSize: 2, + shaderLanguage: ShaderLanguage.WGSL, + vertexShader: this.vertexShaderWGSL, + fragmentShader: this.fragmentShaderWGSL, + uniformNames: ["params"] + }; + + onSceneReady(event: SceneReadyEvent) { + const engine = event.engine; + const scene = event.scene; + + const width = engine.getRenderWidth(); + const height = engine.getRenderHeight(); + const totalPixels = width * height; + + // Buffer: 1 Float pro Pixel + const bufferSize = totalPixels * 4; + const pixelBuffer = new StorageBuffer(engine, bufferSize); + + // Uniform Buffer + const ubo = new UniformBuffer(engine); + ubo.addUniform("resolution", 2); + ubo.addUniform("time", 1); + ubo.update(); + + // Compute Shader + const cs = new ComputeShader("myCompute", engine, { + computeSource: this.computeShaderWGSL + }, { + bindingsMapping: { + "pixelBuffer": { group: 0, binding: 0 }, + "params": { group: 0, binding: 1 } + } + }); + cs.setStorageBuffer("pixelBuffer", pixelBuffer); + cs.setUniformBuffer("params", ubo); + + // Material Setup + const plane = scene.getMeshByName("plane"); + if (plane && plane.material) { + const mat = plane.material as any; + mat.setStorageBuffer("pixelBuffer", pixelBuffer); + mat.setUniformBuffer("params", ubo); + } + + // Render Loop + let time = 0; + scene.onBeforeRenderObservable.add(() => { + time += engine.getDeltaTime() / 1000.0; + + ubo.updateFloat2("resolution", width, height); + ubo.updateFloat("time", time); + ubo.update(); + + const dispatchCount = Math.ceil(totalPixels / 64); + cs.dispatch(dispatchCount, 1, 1); + }); + } +} diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts index bb9e889..c56b771 100644 --- a/src/app/pages/algorithms/service/algorithms.service.ts +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -44,6 +44,12 @@ export class AlgorithmsService { title: 'ALGORITHM.FRACTAL3D.TITLE', description: 'ALGORITHM.FRACTAL3D.DESCRIPTION', routerLink: RouterConstants.FRACTAL3d.LINK + }, + { + id: 'pendulum', + title: 'ALGORITHM.PENDULUM.TITLE', + description: 'ALGORITHM.PENDULUM.DESCRIPTION', + routerLink: RouterConstants.PENDULUM.LINK } ]; diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index f5af17d..18dbd76 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -1,9 +1,9 @@ import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; -import {ArcRotateCamera, Camera, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; +import {ArcRotateCamera, Camera, MeshBuilder, Scene, ShaderLanguage, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; export interface RenderConfig { mode: '2D' | '3D'; - computeShaderSupport?: boolean; + shaderLanguage?: number; //0 GLSL, 1 WGSL initialViewSize: number; vertexShader: string; fragmentShader: string; @@ -12,6 +12,11 @@ export interface RenderConfig { export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void; +export interface SceneReadyEvent { + scene: Scene; + engine: WebGPUEngine; +} + @Component({ selector: 'app-babylon-canvas', imports: [], @@ -26,7 +31,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { @Input({ required: true }) config!: RenderConfig; @Input() renderCallback?: RenderCallback; - @Output() sceneReady = new EventEmitter(); + @Output() sceneReady = new EventEmitter(); private engine!: WebGPUEngine; private scene!: Scene; @@ -63,7 +68,10 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.addListener(canvas); this.createShaderMaterial(); this.createFullScreenRect(); - this.sceneReady.emit(this.scene); + this.sceneReady.emit({ + scene: this.scene, + engine: this.engine + }); this.addRenderLoop(canvas); }); } @@ -133,7 +141,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { }, { attributes: ["position", "uv"], - uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames] + uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames], + shaderLanguage: this.config.shaderLanguage ?? ShaderLanguage.GLSL } ); this.shaderMaterial.disableDepthWrite = true; diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 0fa1e57..a81a351 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -441,6 +441,10 @@ "TITLE": "Fraktale 3D", "DESCRIPTION": "3D-Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)." }, + "PENDULUM": { + "TITLE": "Pendel", + "DESCRIPTION": "Noch ein WebGPU test." + }, "NOTE": "HINWEIS", "GRID_HEIGHT": "Höhe", "GRID_WIDTH": "Beite" diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index f770896..4fbbc63 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -440,6 +440,10 @@ "TITLE": "Fractals 3D", "DESCRIPTION": "3D Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)." }, + "PENDULUM": { + "TITLE": "Pendulum", + "DESCRIPTION": "Just a test atm." + }, "NOTE": "Note", "GRID_HEIGHT": "Height", "GRID_WIDTH": "Width" -- 2.49.1 From 13b59d0b365e6bd416ff97f45ce7ad525493ed4a Mon Sep 17 00:00:00 2001 From: Lobo Date: Fri, 20 Feb 2026 16:50:24 +0100 Subject: [PATCH 04/14] Smaller refactoring - Put shader in own file - renamed package --- src/app/constants/RouterConstants.ts | 2 +- .../algorithms/pendle/pendulum.component.ts | 164 ------------------ .../pendulum.component.html | 0 .../pendulum.component.scss | 0 .../algorithms/pendulum/pendulum.component.ts | 80 +++++++++ .../algorithms/pendulum/pendulum.shader.ts | 74 ++++++++ 6 files changed, 155 insertions(+), 165 deletions(-) delete mode 100644 src/app/pages/algorithms/pendle/pendulum.component.ts rename src/app/pages/algorithms/{pendle => pendulum}/pendulum.component.html (100%) rename src/app/pages/algorithms/{pendle => pendulum}/pendulum.component.scss (100%) create mode 100644 src/app/pages/algorithms/pendulum/pendulum.component.ts create mode 100644 src/app/pages/algorithms/pendulum/pendulum.shader.ts diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index e199a65..1ce6a6a 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -8,7 +8,7 @@ import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.comp import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component'; import {FractalComponent} from '../pages/algorithms/fractal/fractal.component'; import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component'; -import {PendulumComponent} from '../pages/algorithms/pendle/pendulum.component'; +import {PendulumComponent} from '../pages/algorithms/pendulum/pendulum.component'; export class RouterConstants { diff --git a/src/app/pages/algorithms/pendle/pendulum.component.ts b/src/app/pages/algorithms/pendle/pendulum.component.ts deleted file mode 100644 index f495381..0000000 --- a/src/app/pages/algorithms/pendle/pendulum.component.ts +++ /dev/null @@ -1,164 +0,0 @@ -import {Component} from '@angular/core'; -import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; -import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; -import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; - -@Component({ - selector: 'app-pendulum', - imports: [ - BabylonCanvas, - MatCard, - MatCardContent, - MatCardHeader, - MatCardTitle, - ], - templateUrl: './pendulum.component.html', - styleUrl: './pendulum.component.scss', -}) -export class PendulumComponent { -// --- VERTEX SHADER --- -// Das hat funktioniert. Wir definieren Attribute, Babylon baut 'VertexInputs'. -// Wir geben 'FragmentInputs' zurück (was Babylon aus unseren varying baut). - private readonly vertexShaderWGSL = ` - attribute position : vec3; - attribute uv : vec2; - varying vUV : vec2; - - @vertex - fn main(input : VertexInputs) -> FragmentInputs { - var output : FragmentInputs; - output.position = vec4(input.position, 1.0); - output.vUV = input.uv; - return output; - } - `; - -// --- FRAGMENT SHADER (FIXED) --- -// Änderungen: -// 1. Rückgabetyp ist jetzt 'FragmentOutputs' (das Babylon Struct). -// 2. Wir schreiben in 'fragmentOutputs.color'. -// 3. Wir returnen 'fragmentOutputs'. - private readonly fragmentShaderWGSL = ` - varying vUV : vec2; - - var pixelBuffer : array; - var params : Params; - - struct Params { - resolution: vec2, - time: f32 - }; - - @fragment - fn main(input : FragmentInputs) -> FragmentOutputs { - let x = u32(input.vUV.x * params.resolution.x); - let y = u32(input.vUV.y * params.resolution.y); - let width = u32(params.resolution.x); - - let index = y * width + x; - let total = u32(params.resolution.x * params.resolution.y); - - // Default Farbe (schwarz) - var color = vec4(0.0, 0.0, 0.0, 1.0); - - if (index < total) { - let val = pixelBuffer[index]; - color = vec4(val, val * 0.5, 0.2, 1.0); - } - - // Babylon stellt die Variable 'fragmentOutputs' bereit. - // Das Feld heißt standardmäßig 'color'. - fragmentOutputs.color = color; - - return fragmentOutputs; - } - `; - -// --- COMPUTE SHADER (Unverändert) --- - private readonly computeShaderWGSL = ` - @group(0) @binding(0) var pixelBuffer : array; - @group(0) @binding(1) var params : Params; - - struct Params { - resolution: vec2, - time: f32 - }; - - @compute @workgroup_size(64) - fn main(@builtin(global_invocation_id) global_id : vec3) { - let index = global_id.x; - let totalPixels = u32(params.resolution.x * params.resolution.y); - if (index >= totalPixels) { return; } - - let width = u32(params.resolution.x); - let x = f32(index % width); - let y = f32(index / width); - - // Zeit-Variable nutzen für Animation - let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); - - pixelBuffer[index] = value * 0.5 + 0.5; - } - `; - - renderConfig: RenderConfig = { - mode: '2D', - initialViewSize: 2, - shaderLanguage: ShaderLanguage.WGSL, - vertexShader: this.vertexShaderWGSL, - fragmentShader: this.fragmentShaderWGSL, - uniformNames: ["params"] - }; - - onSceneReady(event: SceneReadyEvent) { - const engine = event.engine; - const scene = event.scene; - - const width = engine.getRenderWidth(); - const height = engine.getRenderHeight(); - const totalPixels = width * height; - - // Buffer: 1 Float pro Pixel - const bufferSize = totalPixels * 4; - const pixelBuffer = new StorageBuffer(engine, bufferSize); - - // Uniform Buffer - const ubo = new UniformBuffer(engine); - ubo.addUniform("resolution", 2); - ubo.addUniform("time", 1); - ubo.update(); - - // Compute Shader - const cs = new ComputeShader("myCompute", engine, { - computeSource: this.computeShaderWGSL - }, { - bindingsMapping: { - "pixelBuffer": { group: 0, binding: 0 }, - "params": { group: 0, binding: 1 } - } - }); - cs.setStorageBuffer("pixelBuffer", pixelBuffer); - cs.setUniformBuffer("params", ubo); - - // Material Setup - const plane = scene.getMeshByName("plane"); - if (plane && plane.material) { - const mat = plane.material as any; - mat.setStorageBuffer("pixelBuffer", pixelBuffer); - mat.setUniformBuffer("params", ubo); - } - - // Render Loop - let time = 0; - scene.onBeforeRenderObservable.add(() => { - time += engine.getDeltaTime() / 1000.0; - - ubo.updateFloat2("resolution", width, height); - ubo.updateFloat("time", time); - ubo.update(); - - const dispatchCount = Math.ceil(totalPixels / 64); - cs.dispatch(dispatchCount, 1, 1); - }); - } -} diff --git a/src/app/pages/algorithms/pendle/pendulum.component.html b/src/app/pages/algorithms/pendulum/pendulum.component.html similarity index 100% rename from src/app/pages/algorithms/pendle/pendulum.component.html rename to src/app/pages/algorithms/pendulum/pendulum.component.html diff --git a/src/app/pages/algorithms/pendle/pendulum.component.scss b/src/app/pages/algorithms/pendulum/pendulum.component.scss similarity index 100% rename from src/app/pages/algorithms/pendle/pendulum.component.scss rename to src/app/pages/algorithms/pendulum/pendulum.component.scss diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts new file mode 100644 index 0000000..7292393 --- /dev/null +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -0,0 +1,80 @@ +import {Component} from '@angular/core'; +import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; +import {PENDULUM_COMPUTE_SHADER_WGSL, PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; + +@Component({ + selector: 'app-pendulum', + imports: [ + BabylonCanvas, + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + ], + templateUrl: './pendulum.component.html', + styleUrl: './pendulum.component.scss', +}) +export class PendulumComponent { + renderConfig: RenderConfig = { + mode: '2D', + initialViewSize: 2, + shaderLanguage: ShaderLanguage.WGSL, + vertexShader: PENDULUM_VERTEX_SHADER_WGSL, + fragmentShader: PENDULUM_FRAGMENT_SHADER_WGSL, + uniformNames: ["params"] + }; + + onSceneReady(event: SceneReadyEvent) { + const engine = event.engine; + const scene = event.scene; + + const width = engine.getRenderWidth(); + const height = engine.getRenderHeight(); + const totalPixels = width * height; + + // Buffer: 1 Float pro Pixel + const bufferSize = totalPixels * 4; + const pixelBuffer = new StorageBuffer(engine, bufferSize); + + // Uniform Buffer + const ubo = new UniformBuffer(engine); + ubo.addUniform("resolution", 2); + ubo.addUniform("time", 1); + ubo.update(); + + // Compute Shader + const cs = new ComputeShader("Pendulum Compute Shader", engine, { + computeSource: PENDULUM_COMPUTE_SHADER_WGSL + }, { + bindingsMapping: { + "pixelBuffer": { group: 0, binding: 0 }, + "params": { group: 0, binding: 1 } + } + }); + cs.setStorageBuffer("pixelBuffer", pixelBuffer); + cs.setUniformBuffer("params", ubo); + + // Material Setup + const plane = scene.getMeshByName("plane"); + if (plane?.material) { + const mat = plane.material as any; + mat.setStorageBuffer("pixelBuffer", pixelBuffer); + mat.setUniformBuffer("params", ubo); + } + + // Render Loop + let time = 0; + scene.onBeforeRenderObservable.add(() => { + time += engine.getDeltaTime() / 1000.0; + + ubo.updateFloat2("resolution", width, height); + ubo.updateFloat("time", time); + ubo.update(); + + const dispatchCount = Math.ceil(totalPixels / 64); + cs.dispatch(dispatchCount, 1, 1); + }); + } +} diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts new file mode 100644 index 0000000..75237a0 --- /dev/null +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -0,0 +1,74 @@ +export const PENDULUM_VERTEX_SHADER_WGSL = ` + attribute position : vec3; + attribute uv : vec2; + varying vUV : vec2; + + @vertex + fn main(input : VertexInputs) -> FragmentInputs { + var output : FragmentInputs; + output.position = vec4(input.position, 1.0); + output.vUV = input.uv; + return output; + } + `; + +export const PENDULUM_FRAGMENT_SHADER_WGSL = ` + varying vUV : vec2; + + var pixelBuffer : array; + var params : Params; + + struct Params { + resolution: vec2, + time: f32 + }; + + @fragment + fn main(input : FragmentInputs) -> FragmentOutputs { + let x = u32(input.vUV.x * params.resolution.x); + let y = u32(input.vUV.y * params.resolution.y); + let width = u32(params.resolution.x); + + let index = y * width + x; + let total = u32(params.resolution.x * params.resolution.y); + + // Default Color (Black) + var color = vec4(0.0, 0.0, 0.0, 1.0); + + if (index < total) { + let val = pixelBuffer[index]; + color = vec4(val, val * 0.5, 0.2, 1.0); + } + + //fragmentOutput is provided by babylon + fragmentOutputs.color = color; + + return fragmentOutputs; + } + `; + +export const PENDULUM_COMPUTE_SHADER_WGSL = ` + @group(0) @binding(0) var pixelBuffer : array; + @group(0) @binding(1) var params : Params; + + struct Params { + resolution: vec2, + time: f32 + }; + + @compute @workgroup_size(64) + fn main(@builtin(global_invocation_id) global_id : vec3) { + let index = global_id.x; + let totalPixels = u32(params.resolution.x * params.resolution.y); + if (index >= totalPixels) { return; } + + let width = u32(params.resolution.x); + let x = f32(index % width); + let y = f32(index / width); + + // Zeit-Variable nutzen für Animation + let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); + + pixelBuffer[index] = value * 0.5 + 0.5; + } + `; -- 2.49.1 From 0d2e7c97ecd2d63447d673dc9b2276986ad82893 Mon Sep 17 00:00:00 2001 From: Lobo Date: Fri, 20 Feb 2026 17:14:58 +0100 Subject: [PATCH 05/14] See pendulum I can see the pendulum, but something is not correct with the resolution --- .../algorithms/pendulum/pendulum.component.ts | 89 +++++++-- .../algorithms/pendulum/pendulum.shader.ts | 176 +++++++++++++++--- 2 files changed, 220 insertions(+), 45 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 7292393..3a22779 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -2,7 +2,7 @@ import {Component} from '@angular/core'; import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; -import {PENDULUM_COMPUTE_SHADER_WGSL, PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; +import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; @Component({ selector: 'app-pendulum', @@ -30,33 +30,66 @@ export class PendulumComponent { const engine = event.engine; const scene = event.scene; + engine.resize(); + const width = engine.getRenderWidth(); const height = engine.getRenderHeight(); const totalPixels = width * height; - // Buffer: 1 Float pro Pixel - const bufferSize = totalPixels * 4; - const pixelBuffer = new StorageBuffer(engine, bufferSize); + console.log("Width and Height ", width, height); - // Uniform Buffer + // A. Buffer Setup + // 1. Pixel Buffer (wie gehabt) + const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); + + // 2. State Buffer (4 Floats: theta1, theta2, v1, v2) + const stateBuffer = new StorageBuffer(engine, 4 * 4); + stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); + + // 3. Params Uniform Buffer const ubo = new UniformBuffer(engine); ubo.addUniform("resolution", 2); ubo.addUniform("time", 1); + ubo.addUniform("dt", 1); + ubo.addUniform("g", 1); + ubo.addUniform("m1", 1); + ubo.addUniform("m2", 1); + ubo.addUniform("l1", 1); + ubo.addUniform("l2", 1); + ubo.addUniform("damping", 1); + ubo.addUniform("pad1", 1); //Alignment + ubo.addUniform("pad2", 1); //Alignment ubo.update(); - // Compute Shader - const cs = new ComputeShader("Pendulum Compute Shader", engine, { - computeSource: PENDULUM_COMPUTE_SHADER_WGSL + // B. Compute Shaders Setup + + // CS1: Physics + const csPhysics = new ComputeShader("physics", engine, { + computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL + }, { + bindingsMapping: { + "state": { group: 0, binding: 0 }, + "p": { group: 0, binding: 1 } + } + }); + csPhysics.setStorageBuffer("state", stateBuffer); + csPhysics.setUniformBuffer("p", ubo); + + // CS2: Render/Trail + const csRender = new ComputeShader("render", engine, { + computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, { bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, - "params": { group: 0, binding: 1 } + "p": { group: 0, binding: 1 }, + "state": { group: 0, binding: 2 } } }); - cs.setStorageBuffer("pixelBuffer", pixelBuffer); - cs.setUniformBuffer("params", ubo); + csRender.setStorageBuffer("pixelBuffer", pixelBuffer); + csRender.setUniformBuffer("p", ubo); + csRender.setStorageBuffer("state", stateBuffer); - // Material Setup + // C. Material Setup (Anzeige) const plane = scene.getMeshByName("plane"); if (plane?.material) { const mat = plane.material as any; @@ -64,17 +97,41 @@ export class PendulumComponent { mat.setUniformBuffer("params", ubo); } - // Render Loop + // D. Simulation Loop let time = 0; - scene.onBeforeRenderObservable.add(() => { - time += engine.getDeltaTime() / 1000.0; + // Physik Parameter + const dt = 0.015; + const g = 9.81; + const m1 = 2.0; + const m2 = 1.0; + const l1 = 1.5; + const l2 = 1.2; + const damping = 0.99; + + scene.onBeforeRenderObservable.add(() => { + time += dt; + + // Update Params ubo.updateFloat2("resolution", width, height); ubo.updateFloat("time", time); + ubo.updateFloat("dt", dt); + ubo.updateFloat("g", g); + ubo.updateFloat("m1", m1); + ubo.updateFloat("m2", m2); + ubo.updateFloat("l1", l1); + ubo.updateFloat("l2", l2); + ubo.updateFloat("damping", damping); + ubo.updateFloat("pad1", 0); + ubo.updateFloat("pad2", 0); ubo.update(); + // Do physics (1 thread) + csPhysics.dispatch(1, 1, 1); + + // Paint per pixel const dispatchCount = Math.ceil(totalPixels / 64); - cs.dispatch(dispatchCount, 1, 1); + csRender.dispatch(dispatchCount, 1, 1); }); } } diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 75237a0..c736647 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -14,61 +14,179 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` varying vUV : vec2; - var pixelBuffer : array; var params : Params; struct Params { resolution: vec2, - time: f32 + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, // <-- Alignment + pad2: f32 // <-- Alignment }; @fragment fn main(input : FragmentInputs) -> FragmentOutputs { + let width = u32(params.resolution.x); let x = u32(input.vUV.x * params.resolution.x); let y = u32(input.vUV.y * params.resolution.y); - let width = u32(params.resolution.x); - let index = y * width + x; - let total = u32(params.resolution.x * params.resolution.y); - // Default Color (Black) - var color = vec4(0.0, 0.0, 0.0, 1.0); + let val = pixelBuffer[index]; - if (index < total) { - let val = pixelBuffer[index]; - color = vec4(val, val * 0.5, 0.2, 1.0); - } - - //fragmentOutput is provided by babylon - fragmentOutputs.color = color; + // Hintergrund = Dunkelgrau, Linien = Grau, Massen = Weiß + var color = vec3(0.1, 0.1, 0.15); + if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Linie + if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Masse + fragmentOutputs.color = vec4(color, 1.0); return fragmentOutputs; } - `; +`; -export const PENDULUM_COMPUTE_SHADER_WGSL = ` - @group(0) @binding(0) var pixelBuffer : array; - @group(0) @binding(1) var params : Params; +export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` + struct State { + theta1: f32, + theta2: f32, + v1: f32, + v2: f32 + }; + + @group(0) @binding(0) var state : State; + @group(0) @binding(1) var p : Params; struct Params { resolution: vec2, - time: f32 + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, // <-- Alignment + pad2: f32 // <-- Alignment }; + @compute @workgroup_size(1) + fn main() { + let t1 = state.theta1; + let t2 = state.theta2; + let v1 = state.v1; + let v2 = state.v2; + + let m1 = p.m1; + let m2 = p.m2; + let l1 = p.l1; + let l2 = p.l2; + let g = p.g; + + let num1 = -g * (2.0 * m1 + m2) * sin(t1) + - m2 * g * sin(t1 - 2.0 * t2) + - 2.0 * sin(t1 - t2) * m2 * (v2 * v2 * l2 + v1 * v1 * l1 * cos(t1 - t2)); + let den1 = l1 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + let a1 = num1 / den1; + + let num2 = 2.0 * sin(t1 - t2) * (v1 * v1 * l1 * (m1 + m2) + g * (m1 + m2) * cos(t1) + v2 * v2 * l2 * m2 * cos(t1 - t2)); + let den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + let a2 = num2 / den2; + + let new_v1 = (v1 + a1 * p.dt) * p.damping; + let new_v2 = (v2 + a2 * p.dt) * p.damping; + + state.v1 = new_v1; + state.v2 = new_v2; + state.theta1 = t1 + new_v1 * p.dt; + state.theta2 = t2 + new_v2 * p.dt; + } +`; + +export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` + struct State { + theta1: f32, + theta2: f32, + v1: f32, + v2: f32 + }; + + @group(0) @binding(0) var pixelBuffer : array; + @group(0) @binding(1) var p : Params; + @group(0) @binding(2) var state : State; + + struct Params { + resolution: vec2, + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, + pad2: f32 + }; + + fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { + let pa = p - a; + let ba = b - a; + let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); + } + @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_id : vec3) { - let index = global_id.x; - let totalPixels = u32(params.resolution.x * params.resolution.y); - if (index >= totalPixels) { return; } + let index = global_id.x; + let width = u32(p.resolution.x); + let height = u32(p.resolution.y); - let width = u32(params.resolution.x); - let x = f32(index % width); - let y = f32(index / width); + if (index >= width * height) { return; } - // Zeit-Variable nutzen für Animation - let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); + let x = f32(index % width); + let y = f32(index / width); + let uv = vec2(x / p.resolution.x, y / p.resolution.y); - pixelBuffer[index] = value * 0.5 + 0.5; + let aspect = p.resolution.x / p.resolution.y; + let uv_corr = vec2(uv.x * aspect, uv.y); + + var newVal = 0.0; + + let origin = vec2(0.5 * aspect, 0.7); + + let s1 = sin(state.theta1); + let c1 = cos(state.theta1); + let s2 = sin(state.theta2); + let c2 = cos(state.theta2); + + let displayScale = 0.15; + + let p1 = origin + vec2(s1, -c1) * p.l1 * displayScale; + let p2 = p1 + vec2(s2, -c2) * p.l2 * displayScale; + + let dLine1 = sdSegment(uv_corr, origin, p1); + let dLine2 = sdSegment(uv_corr, p1, p2); + let dMass1 = length(uv_corr - p1); + let dMass2 = length(uv_corr - p2); + + // Geometrie zeichnen + let lineThick = 0.003; + let m1Radius = 0.02; + let m2Radius = 0.02; + + if (dLine1 < lineThick || dLine2 < lineThick) { + newVal = 0.5; + } + if (dMass1 < m1Radius || dMass2 < m2Radius) { + newVal = 1.0; + } + + pixelBuffer[index] = newVal; } - `; +`; -- 2.49.1 From f499b78fd5373989bb265992dc7a49bdfbe5fc2a Mon Sep 17 00:00:00 2001 From: Lobo Date: Fri, 20 Feb 2026 17:25:42 +0100 Subject: [PATCH 06/14] Adding uniform buffers But still resolution problem --- .../algorithms/pendulum/pendulum.component.ts | 51 +++++++------------ .../algorithms/pendulum/pendulum.shader.ts | 19 ++++--- .../canvas/babylon-canvas.component.ts | 2 + 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 3a22779..572474a 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -17,13 +17,15 @@ import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PEND styleUrl: './pendulum.component.scss', }) export class PendulumComponent { + renderConfig: RenderConfig = { mode: '2D', initialViewSize: 2, shaderLanguage: ShaderLanguage.WGSL, vertexShader: PENDULUM_VERTEX_SHADER_WGSL, fragmentShader: PENDULUM_FRAGMENT_SHADER_WGSL, - uniformNames: ["params"] + uniformNames: [], + uniformBufferNames: ["params"] }; onSceneReady(event: SceneReadyEvent) { @@ -36,17 +38,11 @@ export class PendulumComponent { const height = engine.getRenderHeight(); const totalPixels = width * height; - console.log("Width and Height ", width, height); - - // A. Buffer Setup - // 1. Pixel Buffer (wie gehabt) const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); - // 2. State Buffer (4 Floats: theta1, theta2, v1, v2) const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); - // 3. Params Uniform Buffer const ubo = new UniformBuffer(engine); ubo.addUniform("resolution", 2); ubo.addUniform("time", 1); @@ -57,13 +53,10 @@ export class PendulumComponent { ubo.addUniform("l1", 1); ubo.addUniform("l2", 1); ubo.addUniform("damping", 1); - ubo.addUniform("pad1", 1); //Alignment - ubo.addUniform("pad2", 1); //Alignment + ubo.addUniform("pad1", 1); + ubo.addUniform("pad2", 1); ubo.update(); - // B. Compute Shaders Setup - - // CS1: Physics const csPhysics = new ComputeShader("physics", engine, { computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL }, { @@ -75,7 +68,6 @@ export class PendulumComponent { csPhysics.setStorageBuffer("state", stateBuffer); csPhysics.setUniformBuffer("p", ubo); - // CS2: Render/Trail const csRender = new ComputeShader("render", engine, { computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, { @@ -89,7 +81,6 @@ export class PendulumComponent { csRender.setUniformBuffer("p", ubo); csRender.setStorageBuffer("state", stateBuffer); - // C. Material Setup (Anzeige) const plane = scene.getMeshByName("plane"); if (plane?.material) { const mat = plane.material as any; @@ -97,39 +88,33 @@ export class PendulumComponent { mat.setUniformBuffer("params", ubo); } - // D. Simulation Loop let time = 0; - - // Physik Parameter const dt = 0.015; - const g = 9.81; - const m1 = 2.0; - const m2 = 1.0; - const l1 = 1.5; - const l2 = 1.2; - const damping = 0.99; + // Du hast die Physik wieder drin gelassen, das ist super: scene.onBeforeRenderObservable.add(() => { time += dt; - // Update Params - ubo.updateFloat2("resolution", width, height); + const currentWidth = engine.getRenderWidth(); + const currentHeight = engine.getRenderHeight(); + + ubo.updateFloat2("resolution", currentWidth, currentHeight); ubo.updateFloat("time", time); ubo.updateFloat("dt", dt); - ubo.updateFloat("g", g); - ubo.updateFloat("m1", m1); - ubo.updateFloat("m2", m2); - ubo.updateFloat("l1", l1); - ubo.updateFloat("l2", l2); - ubo.updateFloat("damping", damping); + ubo.updateFloat("g", 9.81); + ubo.updateFloat("m1", 2.0); + ubo.updateFloat("m2", 1.0); + ubo.updateFloat("l1", 1.5); + ubo.updateFloat("l2", 1.2); + ubo.updateFloat("damping", 0.99); ubo.updateFloat("pad1", 0); ubo.updateFloat("pad2", 0); ubo.update(); - // Do physics (1 thread) + // Physik Dispatch an csPhysics.dispatch(1, 1, 1); - // Paint per pixel + const totalPixels = currentWidth * currentHeight; const dispatchCount = Math.ceil(totalPixels / 64); csRender.dispatch(dispatchCount, 1, 1); }); diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index c736647..a5fd52f 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -15,7 +15,7 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` varying vUV : vec2; var pixelBuffer : array; - var params : Params; + var params : Params; // Zurück zum bewährten Struct! struct Params { resolution: vec2, @@ -27,23 +27,26 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` l1: f32, l2: f32, damping: f32, - pad1: f32, // <-- Alignment - pad2: f32 // <-- Alignment + pad1: f32, + pad2: f32 }; @fragment fn main(input : FragmentInputs) -> FragmentOutputs { let width = u32(params.resolution.x); - let x = u32(input.vUV.x * params.resolution.x); - let y = u32(input.vUV.y * params.resolution.y); + let height = u32(params.resolution.y); + + // clamp schützt uns vor Abstürzen durch Rundungsfehler am Bildschirmrand + let x = clamp(u32(input.vUV.x * params.resolution.x), 0u, width - 1u); + let y = clamp(u32(input.vUV.y * params.resolution.y), 0u, height - 1u); + let index = y * width + x; let val = pixelBuffer[index]; - // Hintergrund = Dunkelgrau, Linien = Grau, Massen = Weiß var color = vec3(0.1, 0.1, 0.15); - if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Linie - if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Masse + if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } + if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } fragmentOutputs.color = vec4(color, 1.0); return fragmentOutputs; diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index 18dbd76..a0170f0 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -8,6 +8,7 @@ export interface RenderConfig { vertexShader: string; fragmentShader: string; uniformNames: string[]; + uniformBufferNames?: string[]; } export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void; @@ -142,6 +143,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { { attributes: ["position", "uv"], uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames], + uniformBuffers: this.config.uniformBufferNames ?? [], shaderLanguage: this.config.shaderLanguage ?? ShaderLanguage.GLSL } ); -- 2.49.1 From 598013a7d0a830353fef297bd1752a3204b64e6a Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 09:46:55 +0100 Subject: [PATCH 07/14] Rendeirng problems fixed Problem was broken uv coordinates in the fragment shader --- .../algorithms/pendulum/pendulum.component.ts | 56 ++++----- .../algorithms/pendulum/pendulum.shader.ts | 115 +++++++----------- 2 files changed, 70 insertions(+), 101 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 572474a..ebfb083 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -1,7 +1,7 @@ import {Component} from '@angular/core'; import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; -import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; +import {ComputeShader, ShaderLanguage, StorageBuffer} from '@babylonjs/core'; import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; @Component({ @@ -25,7 +25,7 @@ export class PendulumComponent { vertexShader: PENDULUM_VERTEX_SHADER_WGSL, fragmentShader: PENDULUM_FRAGMENT_SHADER_WGSL, uniformNames: [], - uniformBufferNames: ["params"] + uniformBufferNames: [] }; onSceneReady(event: SceneReadyEvent) { @@ -38,24 +38,17 @@ export class PendulumComponent { const height = engine.getRenderHeight(); const totalPixels = width * height; + // Buffer 1: Die Pixel (Bild) const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); + // Buffer 2: Physik-Status const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); - const ubo = new UniformBuffer(engine); - ubo.addUniform("resolution", 2); - ubo.addUniform("time", 1); - ubo.addUniform("dt", 1); - ubo.addUniform("g", 1); - ubo.addUniform("m1", 1); - ubo.addUniform("m2", 1); - ubo.addUniform("l1", 1); - ubo.addUniform("l2", 1); - ubo.addUniform("damping", 1); - ubo.addUniform("pad1", 1); - ubo.addUniform("pad2", 1); - ubo.update(); + // Buffer 3: Parameter (DER NEUE, ABSOLUT SICHERE WEG) + // Wir reservieren Platz für 10 Floats. + const paramsBuffer = new StorageBuffer(engine, 10 * 4); + const paramsData = new Float32Array(10); const csPhysics = new ComputeShader("physics", engine, { computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL @@ -66,7 +59,7 @@ export class PendulumComponent { } }); csPhysics.setStorageBuffer("state", stateBuffer); - csPhysics.setUniformBuffer("p", ubo); + csPhysics.setStorageBuffer("p", paramsBuffer); // Nutzen jetzt StorageBuffer const csRender = new ComputeShader("render", engine, { computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL @@ -78,40 +71,39 @@ export class PendulumComponent { } }); csRender.setStorageBuffer("pixelBuffer", pixelBuffer); - csRender.setUniformBuffer("p", ubo); + csRender.setStorageBuffer("p", paramsBuffer); csRender.setStorageBuffer("state", stateBuffer); + // Material Setup const plane = scene.getMeshByName("plane"); if (plane?.material) { const mat = plane.material as any; mat.setStorageBuffer("pixelBuffer", pixelBuffer); - mat.setUniformBuffer("params", ubo); + mat.setStorageBuffer("paramsBuffer", paramsBuffer); // Auch hier StorageBuffer } let time = 0; const dt = 0.015; - // Du hast die Physik wieder drin gelassen, das ist super: scene.onBeforeRenderObservable.add(() => { time += dt; const currentWidth = engine.getRenderWidth(); const currentHeight = engine.getRenderHeight(); - ubo.updateFloat2("resolution", currentWidth, currentHeight); - ubo.updateFloat("time", time); - ubo.updateFloat("dt", dt); - ubo.updateFloat("g", 9.81); - ubo.updateFloat("m1", 2.0); - ubo.updateFloat("m2", 1.0); - ubo.updateFloat("l1", 1.5); - ubo.updateFloat("l2", 1.2); - ubo.updateFloat("damping", 0.99); - ubo.updateFloat("pad1", 0); - ubo.updateFloat("pad2", 0); - ubo.update(); + paramsData[0] = currentWidth; + paramsData[1] = currentHeight; + paramsData[2] = time; + paramsData[3] = dt; + paramsData[4] = 9.81; // g + paramsData[5] = 2.0; // m1 + paramsData[6] = 1.0; // m2 + paramsData[7] = 1.5; // l1 + paramsData[8] = 1.2; // l2 + paramsData[9] = 0.99; // damping + + paramsBuffer.update(paramsData); - // Physik Dispatch an csPhysics.dispatch(1, 1, 1); const totalPixels = currentWidth * currentHeight; diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index a5fd52f..e974ded 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -13,35 +13,36 @@ `; export const PENDULUM_FRAGMENT_SHADER_WGSL = ` - varying vUV : vec2; + varying vUV : vec2; // Lassen wir stehen, damit der Vertex-Shader nicht meckert var pixelBuffer : array; - var params : Params; // Zurück zum bewährten Struct! - - struct Params { - resolution: vec2, - time: f32, - dt: f32, - g: f32, - m1: f32, - m2: f32, - l1: f32, - l2: f32, - damping: f32, - pad1: f32, - pad2: f32 - }; + var paramsBuffer : array; @fragment fn main(input : FragmentInputs) -> FragmentOutputs { - let width = u32(params.resolution.x); - let height = u32(params.resolution.y); + let width = u32(paramsBuffer[0]); + let height = u32(paramsBuffer[1]); - // clamp schützt uns vor Abstürzen durch Rundungsfehler am Bildschirmrand - let x = clamp(u32(input.vUV.x * params.resolution.x), 0u, width - 1u); - let y = clamp(u32(input.vUV.y * params.resolution.y), 0u, height - 1u); + if (width == 0u || height == 0u) { + fragmentOutputs.color = vec4(0.5, 0.0, 0.0, 1.0); + return fragmentOutputs; + } + + // ============================================================== + // DER MAGISCHE TRICK: Wir ignorieren die kaputten UV-Koordinaten! + // input.position enthält die exakten Bildschirm-Pixelkoordinaten + // (z.B. x geht von 0 bis 1000, y geht von 0 bis 1000). + // Damit lesen wir den Puffer 1:1 auf Pixel-Ebene aus! + // ============================================================== + let x = u32(input.position.x); + let y = u32(input.position.y); + + // Sicherheits-Check, damit wir nicht außerhalb des Buffers lesen + if (x >= width || y >= height) { + fragmentOutputs.color = vec4(0.0, 0.0, 0.0, 1.0); + return fragmentOutputs; + } let index = y * width + x; - let val = pixelBuffer[index]; var color = vec3(0.1, 0.1, 0.15); @@ -62,21 +63,7 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` }; @group(0) @binding(0) var state : State; - @group(0) @binding(1) var p : Params; - - struct Params { - resolution: vec2, - time: f32, - dt: f32, - g: f32, - m1: f32, - m2: f32, - l1: f32, - l2: f32, - damping: f32, - pad1: f32, // <-- Alignment - pad2: f32 // <-- Alignment - }; + @group(0) @binding(1) var p : array; @compute @workgroup_size(1) fn main() { @@ -85,11 +72,13 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` let v1 = state.v1; let v2 = state.v2; - let m1 = p.m1; - let m2 = p.m2; - let l1 = p.l1; - let l2 = p.l2; - let g = p.g; + let dt = p[3]; + let g = p[4]; + let m1 = p[5]; + let m2 = p[6]; + let l1 = p[7]; + let l2 = p[8]; + let damping = p[9]; let num1 = -g * (2.0 * m1 + m2) * sin(t1) - m2 * g * sin(t1 - 2.0 * t2) @@ -101,13 +90,13 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` let den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); let a2 = num2 / den2; - let new_v1 = (v1 + a1 * p.dt) * p.damping; - let new_v2 = (v2 + a2 * p.dt) * p.damping; + let new_v1 = (v1 + a1 * dt) * damping; + let new_v2 = (v2 + a2 * dt) * damping; state.v1 = new_v1; state.v2 = new_v2; - state.theta1 = t1 + new_v1 * p.dt; - state.theta2 = t2 + new_v2 * p.dt; + state.theta1 = t1 + new_v1 * dt; + state.theta2 = t2 + new_v2 * dt; } `; @@ -120,23 +109,9 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` }; @group(0) @binding(0) var pixelBuffer : array; - @group(0) @binding(1) var p : Params; + @group(0) @binding(1) var p : array; @group(0) @binding(2) var state : State; - struct Params { - resolution: vec2, - time: f32, - dt: f32, - g: f32, - m1: f32, - m2: f32, - l1: f32, - l2: f32, - damping: f32, - pad1: f32, - pad2: f32 - }; - fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { let pa = p - a; let ba = b - a; @@ -147,21 +122,23 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_id : vec3) { let index = global_id.x; - let width = u32(p.resolution.x); - let height = u32(p.resolution.y); + + let width = u32(p[0]); + let height = u32(p[1]); if (index >= width * height) { return; } let x = f32(index % width); let y = f32(index / width); - let uv = vec2(x / p.resolution.x, y / p.resolution.y); + let uv = vec2(x / p[0], y / p[1]); - let aspect = p.resolution.x / p.resolution.y; + let aspect = p[0] / p[1]; let uv_corr = vec2(uv.x * aspect, uv.y); var newVal = 0.0; - let origin = vec2(0.5 * aspect, 0.7); + // 1. FIX: Y = 0.3 setzt den Ursprung ins obere Drittel des Bildschirms + let origin = vec2(0.5 * aspect, 0.3); let s1 = sin(state.theta1); let c1 = cos(state.theta1); @@ -170,15 +147,15 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` let displayScale = 0.15; - let p1 = origin + vec2(s1, -c1) * p.l1 * displayScale; - let p2 = p1 + vec2(s2, -c2) * p.l2 * displayScale; + // 2. FIX: +c1 und +c2 genutzt, da Y in WebGPU nach unten wächst + let p1 = origin + vec2(s1, c1) * p[7] * displayScale; + let p2 = p1 + vec2(s2, c2) * p[8] * displayScale; let dLine1 = sdSegment(uv_corr, origin, p1); let dLine2 = sdSegment(uv_corr, p1, p2); let dMass1 = length(uv_corr - p1); let dMass2 = length(uv_corr - p2); - // Geometrie zeichnen let lineThick = 0.003; let m1Radius = 0.02; let m2Radius = 0.02; -- 2.49.1 From 66df3a7f886fa4e06c41d6f293a0aa92d6d9519d Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 09:53:14 +0100 Subject: [PATCH 08/14] Added some comments and removed unused UVs --- .../algorithms/pendulum/pendulum.component.ts | 10 +++---- .../algorithms/pendulum/pendulum.shader.ts | 28 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index ebfb083..1824ae0 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -38,15 +38,13 @@ export class PendulumComponent { const height = engine.getRenderHeight(); const totalPixels = width * height; - // Buffer 1: Die Pixel (Bild) + // Pixel buffer for image data const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); - // Buffer 2: Physik-Status + // Physics buffer for physics values const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); - // Buffer 3: Parameter (DER NEUE, ABSOLUT SICHERE WEG) - // Wir reservieren Platz für 10 Floats. const paramsBuffer = new StorageBuffer(engine, 10 * 4); const paramsData = new Float32Array(10); @@ -79,7 +77,7 @@ export class PendulumComponent { if (plane?.material) { const mat = plane.material as any; mat.setStorageBuffer("pixelBuffer", pixelBuffer); - mat.setStorageBuffer("paramsBuffer", paramsBuffer); // Auch hier StorageBuffer + mat.setStorageBuffer("paramsBuffer", paramsBuffer); } let time = 0; @@ -104,8 +102,10 @@ export class PendulumComponent { paramsBuffer.update(paramsData); + //dispatching physics csPhysics.dispatch(1, 1, 1); + //doing rendering const totalPixels = currentWidth * currentHeight; const dispatchCount = Math.ceil(totalPixels / 64); csRender.dispatch(dispatchCount, 1, 1); diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index e974ded..6c980cb 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -1,19 +1,18 @@ -export const PENDULUM_VERTEX_SHADER_WGSL = ` +//Simple Pass-Through Shader +export const PENDULUM_VERTEX_SHADER_WGSL = ` attribute position : vec3; attribute uv : vec2; - varying vUV : vec2; @vertex fn main(input : VertexInputs) -> FragmentInputs { var output : FragmentInputs; output.position = vec4(input.position, 1.0); - output.vUV = input.uv; return output; } `; +//Fragment Shader to display the pixel buffer export const PENDULUM_FRAGMENT_SHADER_WGSL = ` - varying vUV : vec2; // Lassen wir stehen, damit der Vertex-Shader nicht meckert var pixelBuffer : array; var paramsBuffer : array; @@ -36,9 +35,9 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` let x = u32(input.position.x); let y = u32(input.position.y); - // Sicherheits-Check, damit wir nicht außerhalb des Buffers lesen + // Range check, if outside it is painted red if (x >= width || y >= height) { - fragmentOutputs.color = vec4(0.0, 0.0, 0.0, 1.0); + fragmentOutputs.color = vec4(1.0, 0.0, 0.0, 1.0); return fragmentOutputs; } @@ -46,14 +45,23 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` let val = pixelBuffer[index]; var color = vec3(0.1, 0.1, 0.15); - if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } - if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } + //pendulum color + if (val > 0.1) { + color = vec3(0.5, 0.5, 0.5); + } + + //mass color + if (val > 0.8) { + color = vec3(1.0, 1.0, 1.0); + } fragmentOutputs.color = vec4(color, 1.0); return fragmentOutputs; } `; +//Math for the double pendulum +//https://en.wikipedia.org/wiki/Double_pendulum export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` struct State { theta1: f32, @@ -100,6 +108,7 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` } `; +//Pixel data to visualize the pendulum export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` struct State { theta1: f32, @@ -137,7 +146,7 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` var newVal = 0.0; - // 1. FIX: Y = 0.3 setzt den Ursprung ins obere Drittel des Bildschirms + // Pendulum origin let origin = vec2(0.5 * aspect, 0.3); let s1 = sin(state.theta1); @@ -147,7 +156,6 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` let displayScale = 0.15; - // 2. FIX: +c1 und +c2 genutzt, da Y in WebGPU nach unten wächst let p1 = origin + vec2(s1, c1) * p[7] * displayScale; let p2 = p1 + vec2(s2, c2) * p[8] * displayScale; -- 2.49.1 From 13f99ac7ae4f193fded47301d3ee4482cb9231a9 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 10:03:01 +0100 Subject: [PATCH 09/14] Refactored #2 - Refactored shader code and typescript code - Made it more clear - Added some comments --- .../algorithms/pendulum/pendulum.component.ts | 83 +++++----- .../algorithms/pendulum/pendulum.shader.ts | 154 ++++++++---------- 2 files changed, 109 insertions(+), 128 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 1824ae0..377d590 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -18,6 +18,7 @@ import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PEND }) export class PendulumComponent { + // --- CONFIGURATION --- renderConfig: RenderConfig = { mode: '2D', initialViewSize: 2, @@ -28,86 +29,84 @@ export class PendulumComponent { uniformBufferNames: [] }; - onSceneReady(event: SceneReadyEvent) { - const engine = event.engine; - const scene = event.scene; + // Central management of physics parameters + private readonly simParams = { + time: 0, + dt: 0.015, + g: 9.81, + m1: 2.0, + m2: 1.0, + l1: 1.5, + l2: 1.2, + damping: 0.999 // Less damping for longer swinging + }; + onSceneReady(event: SceneReadyEvent) { + const { engine, scene } = event; engine.resize(); const width = engine.getRenderWidth(); const height = engine.getRenderHeight(); const totalPixels = width * height; - // Pixel buffer for image data + // --- 1. BUFFERS --- const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); - // Physics buffer for physics values const stateBuffer = new StorageBuffer(engine, 4 * 4); - stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); + stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles const paramsBuffer = new StorageBuffer(engine, 10 * 4); const paramsData = new Float32Array(10); - const csPhysics = new ComputeShader("physics", engine, { - computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL - }, { - bindingsMapping: { - "state": { group: 0, binding: 0 }, - "p": { group: 0, binding: 1 } - } - }); + // --- 2. SHADERS --- + const csPhysics = new ComputeShader("physics", engine, + { computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL }, + { bindingsMapping: { "state": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 } } } + ); csPhysics.setStorageBuffer("state", stateBuffer); - csPhysics.setStorageBuffer("p", paramsBuffer); // Nutzen jetzt StorageBuffer + csPhysics.setStorageBuffer("p", paramsBuffer); - const csRender = new ComputeShader("render", engine, { - computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL - }, { - bindingsMapping: { - "pixelBuffer": { group: 0, binding: 0 }, - "p": { group: 0, binding: 1 }, - "state": { group: 0, binding: 2 } - } - }); + const csRender = new ComputeShader("render", engine, + { computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, + { bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 }, "state": { group: 0, binding: 2 } } } + ); csRender.setStorageBuffer("pixelBuffer", pixelBuffer); csRender.setStorageBuffer("p", paramsBuffer); csRender.setStorageBuffer("state", stateBuffer); - // Material Setup + // --- 3. MATERIAL --- const plane = scene.getMeshByName("plane"); if (plane?.material) { const mat = plane.material as any; mat.setStorageBuffer("pixelBuffer", pixelBuffer); - mat.setStorageBuffer("paramsBuffer", paramsBuffer); + mat.setStorageBuffer("p", paramsBuffer); } - let time = 0; - const dt = 0.015; - + // --- 4. RENDER LOOP --- scene.onBeforeRenderObservable.add(() => { - time += dt; + this.simParams.time += this.simParams.dt; const currentWidth = engine.getRenderWidth(); const currentHeight = engine.getRenderHeight(); + // Fill parameter array (must match the exact order of the WGSL struct!) paramsData[0] = currentWidth; paramsData[1] = currentHeight; - paramsData[2] = time; - paramsData[3] = dt; - paramsData[4] = 9.81; // g - paramsData[5] = 2.0; // m1 - paramsData[6] = 1.0; // m2 - paramsData[7] = 1.5; // l1 - paramsData[8] = 1.2; // l2 - paramsData[9] = 0.99; // damping + paramsData[2] = this.simParams.time; + paramsData[3] = this.simParams.dt; + paramsData[4] = this.simParams.g; + paramsData[5] = this.simParams.m1; + paramsData[6] = this.simParams.m2; + paramsData[7] = this.simParams.l1; + paramsData[8] = this.simParams.l2; + paramsData[9] = this.simParams.damping; paramsBuffer.update(paramsData); - //dispatching physics + // Trigger simulation and rendering csPhysics.dispatch(1, 1, 1); - //doing rendering - const totalPixels = currentWidth * currentHeight; - const dispatchCount = Math.ceil(totalPixels / 64); + const dispatchCount = Math.ceil((currentWidth * currentHeight) / 64); csRender.dispatch(dispatchCount, 1, 1); }); } diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 6c980cb..2ad76f5 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -1,7 +1,6 @@ //Simple Pass-Through Shader export const PENDULUM_VERTEX_SHADER_WGSL = ` attribute position : vec3; - attribute uv : vec2; @vertex fn main(input : VertexInputs) -> FragmentInputs { @@ -9,51 +8,64 @@ export const PENDULUM_VERTEX_SHADER_WGSL = ` output.position = vec4(input.position, 1.0); return output; } - `; +`; + +// --- SHARED DATA STRUCTURES --- +// These structs map exactly to the Float32Array in the TypeScript code. +const SHARED_STRUCTS = ` + struct Params { + width: f32, + height: f32, + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32 + }; + + struct State { + theta1: f32, + theta2: f32, + v1: f32, + v2: f32 + }; +`; //Fragment Shader to display the pixel buffer -export const PENDULUM_FRAGMENT_SHADER_WGSL = ` +export const PENDULUM_FRAGMENT_SHADER_WGSL = SHARED_STRUCTS + ` var pixelBuffer : array; - var paramsBuffer : array; + var p : Params; @fragment fn main(input : FragmentInputs) -> FragmentOutputs { - let width = u32(paramsBuffer[0]); - let height = u32(paramsBuffer[1]); + let width = u32(p.width); + let height = u32(p.height); + // Fallback if buffer is not loaded yet if (width == 0u || height == 0u) { fragmentOutputs.color = vec4(0.5, 0.0, 0.0, 1.0); return fragmentOutputs; } - // ============================================================== - // DER MAGISCHE TRICK: Wir ignorieren die kaputten UV-Koordinaten! - // input.position enthält die exakten Bildschirm-Pixelkoordinaten - // (z.B. x geht von 0 bis 1000, y geht von 0 bis 1000). - // Damit lesen wir den Puffer 1:1 auf Pixel-Ebene aus! - // ============================================================== + // Direct access to the pixel via screen coordinates let x = u32(input.position.x); let y = u32(input.position.y); - // Range check, if outside it is painted red + // Boundary check to prevent reading outside the buffer if (x >= width || y >= height) { - fragmentOutputs.color = vec4(1.0, 0.0, 0.0, 1.0); + fragmentOutputs.color = vec4(0.0, 0.0, 0.0, 1.0); return fragmentOutputs; } let index = y * width + x; let val = pixelBuffer[index]; - var color = vec3(0.1, 0.1, 0.15); - //pendulum color - if (val > 0.1) { - color = vec3(0.5, 0.5, 0.5); - } - - //mass color - if (val > 0.8) { - color = vec3(1.0, 1.0, 1.0); - } + var color = vec3(0.1, 0.1, 0.15); // Background + if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Line + if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Mass fragmentOutputs.color = vec4(color, 1.0); return fragmentOutputs; @@ -62,16 +74,9 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` //Math for the double pendulum //https://en.wikipedia.org/wiki/Double_pendulum -export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` - struct State { - theta1: f32, - theta2: f32, - v1: f32, - v2: f32 - }; - +export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` @group(0) @binding(0) var state : State; - @group(0) @binding(1) var p : array; + @group(0) @binding(1) var p : Params; @compute @workgroup_size(1) fn main() { @@ -80,49 +85,38 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` let v1 = state.v1; let v2 = state.v2; - let dt = p[3]; - let g = p[4]; - let m1 = p[5]; - let m2 = p[6]; - let l1 = p[7]; - let l2 = p[8]; - let damping = p[9]; + let delta_t = t1 - t2; - let num1 = -g * (2.0 * m1 + m2) * sin(t1) - - m2 * g * sin(t1 - 2.0 * t2) - - 2.0 * sin(t1 - t2) * m2 * (v2 * v2 * l2 + v1 * v1 * l1 * cos(t1 - t2)); - let den1 = l1 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + // Equations split for better readability + let num1 = -p.g * (2.0 * p.m1 + p.m2) * sin(t1) + - p.m2 * p.g * sin(t1 - 2.0 * t2) + - 2.0 * sin(delta_t) * p.m2 * (v2 * v2 * p.l2 + v1 * v1 * p.l1 * cos(delta_t)); + let den1 = p.l1 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t)); let a1 = num1 / den1; - let num2 = 2.0 * sin(t1 - t2) * (v1 * v1 * l1 * (m1 + m2) + g * (m1 + m2) * cos(t1) + v2 * v2 * l2 * m2 * cos(t1 - t2)); - let den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + let num2 = 2.0 * sin(delta_t) * (v1 * v1 * p.l1 * (p.m1 + p.m2) + p.g * (p.m1 + p.m2) * cos(t1) + v2 * v2 * p.l2 * p.m2 * cos(delta_t)); + let den2 = p.l2 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t)); let a2 = num2 / den2; - let new_v1 = (v1 + a1 * dt) * damping; - let new_v2 = (v2 + a2 * dt) * damping; + // Integration (Semi-Implicit Euler) + let new_v1 = (v1 + a1 * p.dt) * p.damping; + let new_v2 = (v2 + a2 * p.dt) * p.damping; state.v1 = new_v1; state.v2 = new_v2; - state.theta1 = t1 + new_v1 * dt; - state.theta2 = t2 + new_v2 * dt; + state.theta1 = t1 + new_v1 * p.dt; + state.theta2 = t2 + new_v2 * p.dt; } `; //Pixel data to visualize the pendulum -export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` - struct State { - theta1: f32, - theta2: f32, - v1: f32, - v2: f32 - }; - +export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` @group(0) @binding(0) var pixelBuffer : array; - @group(0) @binding(1) var p : array; + @group(0) @binding(1) var p : Params; @group(0) @binding(2) var state : State; - fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { - let pa = p - a; + fn sdSegment(point: vec2, a: vec2, b: vec2) -> f32 { + let pa = point - a; let ba = b - a; let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); return length(pa - ba * h); @@ -131,49 +125,37 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) global_id : vec3) { let index = global_id.x; - - let width = u32(p[0]); - let height = u32(p[1]); + let width = u32(p.width); + let height = u32(p.height); if (index >= width * height) { return; } let x = f32(index % width); let y = f32(index / width); - let uv = vec2(x / p[0], y / p[1]); + let uv = vec2(x / p.width, y / p.height); - let aspect = p[0] / p[1]; + let aspect = p.width / p.height; let uv_corr = vec2(uv.x * aspect, uv.y); - var newVal = 0.0; + var newVal = 0.0; // Clear background - // Pendulum origin + // Pendulum geometry let origin = vec2(0.5 * aspect, 0.3); - - let s1 = sin(state.theta1); - let c1 = cos(state.theta1); - let s2 = sin(state.theta2); - let c2 = cos(state.theta2); - let displayScale = 0.15; - let p1 = origin + vec2(s1, c1) * p[7] * displayScale; - let p2 = p1 + vec2(s2, c2) * p[8] * displayScale; + // Calculate positions + let p1 = origin + vec2(sin(state.theta1), cos(state.theta1)) * p.l1 * displayScale; + let p2 = p1 + vec2(sin(state.theta2), cos(state.theta2)) * p.l2 * displayScale; + // Distances let dLine1 = sdSegment(uv_corr, origin, p1); let dLine2 = sdSegment(uv_corr, p1, p2); let dMass1 = length(uv_corr - p1); let dMass2 = length(uv_corr - p2); - let lineThick = 0.003; - let m1Radius = 0.02; - let m2Radius = 0.02; - - if (dLine1 < lineThick || dLine2 < lineThick) { - newVal = 0.5; - } - if (dMass1 < m1Radius || dMass2 < m2Radius) { - newVal = 1.0; - } + // Draw + if (dLine1 < 0.003 || dLine2 < 0.003) { newVal = 0.5; } + if (dMass1 < 0.02 || dMass2 < 0.02) { newVal = 1.0; } pixelBuffer[index] = newVal; } -- 2.49.1 From 2bfa8ba9a1752e59eee4a621c7129e9c93ea2621 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 10:21:21 +0100 Subject: [PATCH 10/14] Added trail effect --- .../algorithms/pendulum/pendulum.component.ts | 9 ++- .../algorithms/pendulum/pendulum.shader.ts | 63 ++++++++++++++----- src/assets/i18n/de.json | 2 +- src/assets/i18n/en.json | 2 +- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 377d590..b446cf5 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -38,7 +38,8 @@ export class PendulumComponent { m2: 1.0, l1: 1.5, l2: 1.2, - damping: 0.999 // Less damping for longer swinging + damping: 0.999, + trailDecay: 0.98 }; onSceneReady(event: SceneReadyEvent) { @@ -55,8 +56,8 @@ export class PendulumComponent { const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles - const paramsBuffer = new StorageBuffer(engine, 10 * 4); - const paramsData = new Float32Array(10); + const paramsBuffer = new StorageBuffer(engine, 12 * 4); + const paramsData = new Float32Array(12); // --- 2. SHADERS --- const csPhysics = new ComputeShader("physics", engine, @@ -100,6 +101,8 @@ export class PendulumComponent { paramsData[7] = this.simParams.l1; paramsData[8] = this.simParams.l2; paramsData[9] = this.simParams.damping; + paramsData[10] = this.simParams.trailDecay; + paramsData[11] = 0; // Pad paramsBuffer.update(paramsData); diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 2ad76f5..e8f4d22 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -23,7 +23,9 @@ const SHARED_STRUCTS = ` m2: f32, l1: f32, l2: f32, - damping: f32 + damping: f32, + trailDecay: f32, + pad: f32 // <-- Padding for safe 16-byte memory alignment }; struct State { @@ -44,30 +46,45 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = SHARED_STRUCTS + ` let width = u32(p.width); let height = u32(p.height); - // Fallback if buffer is not loaded yet if (width == 0u || height == 0u) { fragmentOutputs.color = vec4(0.5, 0.0, 0.0, 1.0); return fragmentOutputs; } - // Direct access to the pixel via screen coordinates let x = u32(input.position.x); let y = u32(input.position.y); - // Boundary check to prevent reading outside the buffer if (x >= width || y >= height) { fragmentOutputs.color = vec4(0.0, 0.0, 0.0, 1.0); return fragmentOutputs; } let index = y * width + x; - let val = pixelBuffer[index]; - var color = vec3(0.1, 0.1, 0.15); // Background - if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Line - if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Mass + // --- THE MAGIC DECODING --- + let rawVal = pixelBuffer[index]; + var trailVal = rawVal; + var isLine = false; - fragmentOutputs.color = vec4(color, 1.0); + // Check if the +10.0 flag is present (meaning this pixel is currently a line) + if (trailVal >= 10.0) { + isLine = true; + trailVal = trailVal - 10.0; // Remove flag to get the true trail value underneath + } + + let bgColor = vec3(0.1, 0.1, 0.15); + let massColor = vec3(1.0, 1.0, 1.0); + let lineColor = vec3(0.5, 0.5, 0.5); + + // Calculate background blending with the trail + var finalColor = mix(bgColor, massColor, clamp(trailVal, 0.0, 1.0)); + + // Overwrite with the grey line if necessary + if (isLine) { + finalColor = lineColor; + } + + fragmentOutputs.color = vec4(finalColor, 1.0); return fragmentOutputs; } `; @@ -87,7 +104,6 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let delta_t = t1 - t2; - // Equations split for better readability let num1 = -p.g * (2.0 * p.m1 + p.m2) * sin(t1) - p.m2 * p.g * sin(t1 - 2.0 * t2) - 2.0 * sin(delta_t) * p.m2 * (v2 * v2 * p.l2 + v1 * v1 * p.l1 * cos(delta_t)); @@ -98,7 +114,6 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let den2 = p.l2 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t)); let a2 = num2 / den2; - // Integration (Semi-Implicit Euler) let new_v1 = (v1 + a1 * p.dt) * p.damping; let new_v2 = (v2 + a2 * p.dt) * p.damping; @@ -137,25 +152,39 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let aspect = p.width / p.height; let uv_corr = vec2(uv.x * aspect, uv.y); - var newVal = 0.0; // Clear background + // --- TRAIL EXTRACT & DECAY --- + let oldRaw = pixelBuffer[index]; + var oldTrail = oldRaw; + + // If the pixel was a line last frame, remove the +10 flag to get the trail memory + if (oldTrail >= 10.0) { + oldTrail = oldTrail - 10.0; + } + var newVal = oldTrail * p.trailDecay; // Pendulum geometry let origin = vec2(0.5 * aspect, 0.3); let displayScale = 0.15; - // Calculate positions let p1 = origin + vec2(sin(state.theta1), cos(state.theta1)) * p.l1 * displayScale; let p2 = p1 + vec2(sin(state.theta2), cos(state.theta2)) * p.l2 * displayScale; - // Distances let dLine1 = sdSegment(uv_corr, origin, p1); let dLine2 = sdSegment(uv_corr, p1, p2); let dMass1 = length(uv_corr - p1); let dMass2 = length(uv_corr - p2); - // Draw - if (dLine1 < 0.003 || dLine2 < 0.003) { newVal = 0.5; } - if (dMass1 < 0.02 || dMass2 < 0.02) { newVal = 1.0; } + let isLine = (dLine1 < 0.002 || dLine2 < 0.002); + let isMass = (dMass1 < 0.01 || dMass2 < 0.01); + + // --- SMART LAYERING --- + if (isMass) { + newVal = 1.0; + } else if (isLine) { + // Lines are marked with +10.0 to tell the fragment shader to paint them grey, + // WITHOUT overwriting the fading trail memory underneath! + newVal = newVal + 10.0; + } pixelBuffer[index] = newVal; } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index a81a351..91fb19a 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1,7 +1,7 @@ { "APP": { "TITLE": "Playground", - "COPYRIGHT": "Bilder urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!" + "COPYRIGHT": "Bilder und Sourcecode sind urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!" }, "TOPBAR": { "ABOUT": "Über mich", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 4fbbc63..845b786 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,7 +1,7 @@ { "APP": { "TITLE": "Playground", - "COPYRIGHT": "Images protected by copyright, no use without permission!" + "COPYRIGHT": "Images and code protected by copyright, no use without permission!" }, "TOPBAR": { "ABOUT": "About me", -- 2.49.1 From 5721b2e48eea4b7aa332ca23a85e485e8797c81a Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 10:25:20 +0100 Subject: [PATCH 11/14] Added resize callback to restart simulation new if canvas is resized --- src/app/pages/algorithms/fractal/fractal.component.ts | 4 ++-- .../pages/algorithms/pendulum/pendulum.component.html | 1 + .../pages/algorithms/pendulum/pendulum.component.ts | 5 +++-- .../rendering/canvas/babylon-canvas.component.ts | 10 ++++++++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index c621eb8..24473f0 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -8,7 +8,7 @@ import {MatSelect} from '@angular/material/select'; import {AlgorithmInformation} from '../information/information.models'; import {UrlConstants} from '../../../constants/UrlConstants'; import {FormsModule} from '@angular/forms'; -import {BabylonCanvas, RenderCallback, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; +import {BabylonCanvas, RenderCallback, RenderConfig, SceneEventData} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader'; import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core'; import {MatButton} from '@angular/material/button'; @@ -155,7 +155,7 @@ export class FractalComponent implements OnInit { } } - onSceneReady(event: SceneReadyEvent): void { + onSceneReady(event: SceneEventData): void { event.scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.html b/src/app/pages/algorithms/pendulum/pendulum.component.html index 5e9e0b0..bf751e5 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.html +++ b/src/app/pages/algorithms/pendulum/pendulum.component.html @@ -6,6 +6,7 @@ diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index b446cf5..9795aa1 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; +import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {ComputeShader, ShaderLanguage, StorageBuffer} from '@babylonjs/core'; import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; @@ -42,7 +42,7 @@ export class PendulumComponent { trailDecay: 0.98 }; - onSceneReady(event: SceneReadyEvent) { + onSceneReady(event: SceneEventData) { const { engine, scene } = event; engine.resize(); @@ -113,4 +113,5 @@ export class PendulumComponent { csRender.dispatch(dispatchCount, 1, 1); }); } + } diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index a0170f0..975a187 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -13,7 +13,7 @@ export interface RenderConfig { export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void; -export interface SceneReadyEvent { +export interface SceneEventData { scene: Scene; engine: WebGPUEngine; } @@ -32,7 +32,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { @Input({ required: true }) config!: RenderConfig; @Input() renderCallback?: RenderCallback; - @Output() sceneReady = new EventEmitter(); + @Output() sceneReady = new EventEmitter(); + @Output() sceneResized = new EventEmitter(); private engine!: WebGPUEngine; private scene!: Scene; @@ -179,5 +180,10 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { this.camera.orthoTop = viewSize / 2; this.camera.orthoBottom = -viewSize / 2; } + + this.sceneResized?.emit({ + scene: this.scene, + engine: this.engine + }); } } -- 2.49.1 From 34148aade24f55327c167640da07543114979fa4 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 11:47:57 +0100 Subject: [PATCH 12/14] Added UI and painted pendulum in different colors --- .../conway-gol/conway-gol.component.html | 8 +- .../pathfinding/pathfinding.component.html | 6 +- .../pendulum/pendulum.component.html | 32 ++++- .../algorithms/pendulum/pendulum.component.ts | 73 ++++++++++-- .../algorithms/pendulum/pendulum.model.ts | 21 ++++ .../algorithms/pendulum/pendulum.shader.ts | 109 ++++++++++++------ src/assets/i18n/de.json | 10 ++ src/assets/i18n/en.json | 10 ++ src/styles.scss | 10 +- 9 files changed, 227 insertions(+), 52 deletions(-) create mode 100644 src/app/pages/algorithms/pendulum/pendulum.model.ts diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.component.html b/src/app/pages/algorithms/conway-gol/conway-gol.component.html index 7bb078b..d0cfcc0 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.component.html +++ b/src/app/pages/algorithms/conway-gol/conway-gol.component.html @@ -35,8 +35,8 @@ }

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

-
- +
+ {{ 'ALGORITHM.GRID_HEIGHT' | translate }} - + {{ 'ALGORITHM.GRID_WIDTH' | translate }} - + {{ 'GOL.SPEED' | translate }}
-
- +
+ {{ 'ALGORITHM.GRID_HEIGHT' | translate }} - + {{ 'ALGORITHM.GRID_WIDTH' | translate }} - Ich bin ein Pendel - blub + {{ 'PENDULUM.TITLE' | translate }} +
+
+

{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}

+ +

{{ 'PENDULUM.ATTRACTION' | translate }}

+ +
+
+

{{ 'PENDULUM.L1_LENGTH' | translate }}

+ +

{{ 'PENDULUM.L2_LENGTH' | translate }}

+ +
+
+

{{ 'PENDULUM.M1_MASS' | translate }}

+ +

{{ 'PENDULUM.M2_MASS' | translate }}

+ +
+
+

{{ 'PENDULUM.DAMPING' | translate }}

+ +
+
+ L1 + L2 + M1 + M2 +
+
= 10.0) { - isLine = true; - trailVal = trailVal - 10.0; // Remove flag to get the true trail value underneath + // 1. Check for overlays (Lines) + if (val >= 20.0) { + isLine2 = true; + val = val - 20.0; + } else if (val >= 10.0) { + isLine1 = true; + val = val - 10.0; } - let bgColor = vec3(0.1, 0.1, 0.15); - let massColor = vec3(1.0, 1.0, 1.0); - let lineColor = vec3(0.5, 0.5, 0.5); + // 2. Check which trail it is + var isTrail2 = false; + if (val >= 2.0) { + isTrail2 = true; + val = val - 2.0; + } + + // 3. What remains is purely the fading intensity (0.0 to 1.0) + let trailIntensity = val; + + // --- COLORS --- + let bgColor = vec3(0.1, 0.1, 0.15); + let mass1Color = vec3(1.0, 0.0, 0.0); // Red + let mass2Color = vec3(0.0, 1.0, 0.0); // Green + let line1Color = vec3(1.0, 1.0, 0.0); // Yellow + let line2Color = vec3(1.0, 0.0, 1.0); // Magenta + + var massColor = mass1Color; + if (isTrail2) { + massColor = mass2Color; + } // Calculate background blending with the trail - var finalColor = mix(bgColor, massColor, clamp(trailVal, 0.0, 1.0)); + var finalColor = mix(bgColor, massColor, clamp(trailIntensity, 0.0, 1.0)); - // Overwrite with the grey line if necessary - if (isLine) { - finalColor = lineColor; - } + // Overwrite with the line colors if necessary + if (isLine1) { finalColor = line1Color; } + if (isLine2) { finalColor = line2Color; } fragmentOutputs.color = vec4(finalColor, 1.0); return fragmentOutputs; @@ -152,17 +172,24 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let aspect = p.width / p.height; let uv_corr = vec2(uv.x * aspect, uv.y); - // --- TRAIL EXTRACT & DECAY --- - let oldRaw = pixelBuffer[index]; - var oldTrail = oldRaw; + // --- 1. EXTRACT & DECAY OLD MEMORY --- + var memory = pixelBuffer[index]; - // If the pixel was a line last frame, remove the +10 flag to get the trail memory - if (oldTrail >= 10.0) { - oldTrail = oldTrail - 10.0; + // Strip line overlays from the previous frame + if (memory >= 20.0) { memory = memory - 20.0; } + else if (memory >= 10.0) { memory = memory - 10.0; } + + // Check if the memory belongs to Trail 2 + var isTrail2 = false; + if (memory >= 2.0) { + isTrail2 = true; + memory = memory - 2.0; } - var newVal = oldTrail * p.trailDecay; - // Pendulum geometry + // Apply decay to the pure intensity + memory = memory * p.trailDecay; + + // --- 2. CALCULATE GEOMETRY --- let origin = vec2(0.5 * aspect, 0.3); let displayScale = 0.15; @@ -174,18 +201,34 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let dMass1 = length(uv_corr - p1); let dMass2 = length(uv_corr - p2); - let isLine = (dLine1 < 0.002 || dLine2 < 0.002); - let isMass = (dMass1 < 0.01 || dMass2 < 0.01); + // --- 3. SMART LAYERING --- + var baseVal = 0.0; - // --- SMART LAYERING --- - if (isMass) { - newVal = 1.0; - } else if (isLine) { - // Lines are marked with +10.0 to tell the fragment shader to paint them grey, - // WITHOUT overwriting the fading trail memory underneath! - newVal = newVal + 10.0; + // Base Layer (Masses & Trails) + if (dMass1 < 0.02) { + baseVal = 1.0; // Mass 1 = 1.0 (Trail 1 Max) + } else if (dMass2 < 0.02) { + baseVal = 3.0; // Mass 2 = 2.0 (Flag) + 1.0 (Trail 2 Max) + } else { + // Write fading memory back + if (isTrail2) { + baseVal = memory + 2.0; + } else { + baseVal = memory; + } } - pixelBuffer[index] = newVal; + // Overlay Layer (Lines) + var overlay = 0.0; + // Don't draw lines over the masses (Clean Z-Index) + if (dMass1 < 0.02 || dMass2 < 0.02) { + overlay = 0.0; + } else if (dLine1 < 0.003) { + overlay = 10.0; + } else if (dLine2 < 0.003) { + overlay = 20.0; + } + + pixelBuffer[index] = baseVal + overlay; } `; diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 91fb19a..7e4c94e 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -415,6 +415,16 @@ "DISCLAIMER_4": "Licht & Schatten: Um die Tiefe sichtbar zu machen, werden Lichtreflexionen und Schatten (Ambient Occlusion) basierend auf der Krümmung der Formel simuliert." } }, + "PENDULUM": { + "TITLE": "Doppel-Pendel", + "TRAIL_DECAY_TIME": "Spurlänge", + "DAMPING": "Dämpfung", + "ATTRACTION": "Anziehungskraft", + "L1_LENGTH": "Länge L1", + "L2_LENGTH": "Länge L2", + "M1_MASS": "Masse M1", + "M2_MASS": "Masse M2" + }, "ALGORITHM": { "TITLE": "Algorithmen", "PATHFINDING": { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 845b786..29d8324 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -414,6 +414,16 @@ "DISCLAIMER_4": "Light & Shadow: To visualize depth, light reflections and shadows (Ambient Occlusion) are simulated based on the curvature of the formula." } }, + "PENDULUM": { + "TITLE": "Double pendulum", + "TRAIL_DECAY_TIME": "Trail length", + "DAMPING": "Damping", + "ATTRACTION": "Attraction", + "L1_LENGTH": "Length L1", + "L2_LENGTH": "Length L2", + "M1_MASS": "Mass M1", + "M2_MASS": "Mass M2" + }, "ALGORITHM": { "TITLE": "Algorithms", "PATHFINDING": { diff --git a/src/styles.scss b/src/styles.scss index fdba66c..ff47d5c 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -262,13 +262,13 @@ a { } } -.grid-size { +.input-container { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; - .grid-field { + .input-field { width: 150px; } } @@ -301,6 +301,10 @@ canvas { &.path { background-color: gold; } &.empty { background-color: lightgray; } &.alive { background-color: black; } + &.L1 { background-color: yellow; } + &.L2 { background-color: magenta; } + &.M1 { background-color: red; } + &.M2 { background-color: green; } } } @@ -339,4 +343,4 @@ canvas { background-color: #4caf50; /* Green for sorted */ } } -} \ No newline at end of file +} -- 2.49.1 From 24d6d9cdbe681191146e0bde3eeca4eaa1499ced Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 12:25:07 +0100 Subject: [PATCH 13/14] Finalized Algorithm Added final descriptions and polished the system --- src/app/constants/RouterConstants.ts | 2 +- src/app/constants/UrlConstants.ts | 1 + .../pendulum/pendulum.component.html | 22 +++-- .../algorithms/pendulum/pendulum.component.ts | 88 ++++++++++++++++--- .../algorithms/pendulum/pendulum.model.ts | 3 + .../algorithms/pendulum/pendulum.shader.ts | 6 +- src/assets/i18n/de.json | 19 +++- src/assets/i18n/en.json | 19 +++- src/styles.scss | 6 ++ 9 files changed, 141 insertions(+), 25 deletions(-) diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 1ce6a6a..ac1efab 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -8,7 +8,7 @@ import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.comp import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component'; import {FractalComponent} from '../pages/algorithms/fractal/fractal.component'; import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component'; -import {PendulumComponent} from '../pages/algorithms/pendulum/pendulum.component'; +import PendulumComponent from '../pages/algorithms/pendulum/pendulum.component'; export class RouterConstants { diff --git a/src/app/constants/UrlConstants.ts b/src/app/constants/UrlConstants.ts index 640b9d5..cc33dc8 100644 --- a/src/app/constants/UrlConstants.ts +++ b/src/app/constants/UrlConstants.ts @@ -17,4 +17,5 @@ static readonly MANDELBULB_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle' static readonly MANDELBOX_WIKI = 'https://de.wikipedia.org/wiki/Mandelbox' static readonly JULIA3D_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle' + static readonly DOUBLE_PENDULUM_WIKI = 'https://de.wikipedia.org/wiki/Doppelpendel' } diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.html b/src/app/pages/algorithms/pendulum/pendulum.component.html index dabde52..2c853d3 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.html +++ b/src/app/pages/algorithms/pendulum/pendulum.component.html @@ -3,30 +3,42 @@ {{ 'PENDULUM.TITLE' | translate }} +
-
+

{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}

{{ 'PENDULUM.ATTRACTION' | translate }}

-
+

{{ 'PENDULUM.L1_LENGTH' | translate }}

{{ 'PENDULUM.L2_LENGTH' | translate }}

-
+

{{ 'PENDULUM.M1_MASS' | translate }}

{{ 'PENDULUM.M2_MASS' | translate }}

-
+

{{ 'PENDULUM.DAMPING' | translate }}

-
+
+ + + +
+
L1 L2 M1 diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index d95e098..3c27ef4 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -5,8 +5,12 @@ import {ComputeShader, ShaderLanguage, StorageBuffer} from '@babylonjs/core'; import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; import {FormsModule} from '@angular/forms'; import {NgxSliderModule, Options} from '@angular-slider/ngx-slider'; -import {DEFAULT_DAMPING, DEFAULT_G, DEFAULT_L1_LENGTH, DEFAULT_M1_MASS, DEFAULT_L2_LENGTH, DEFAULT_M2_MASS, DEFAULT_TRAIL_DECAY, MAX_DAMPING, MAX_G, MAX_LENGTH, MAX_MASS, MAX_TRAIL_DECAY, MIN_DAMPING, MIN_G, MIN_LENGTH, MIN_MASS, MIN_TRAIL_DECAY} from './pendulum.model'; +import {DEFAULT_DAMPING, DEFAULT_G, DEFAULT_L1_LENGTH, DEFAULT_M1_MASS, DEFAULT_L2_LENGTH, DEFAULT_M2_MASS, DEFAULT_TRAIL_DECAY, MAX_DAMPING, MAX_G, MAX_LENGTH, MAX_MASS, MAX_TRAIL_DECAY, MIN_DAMPING, MIN_G, MIN_LENGTH, MIN_MASS, MIN_TRAIL_DECAY, IMPULSE_M2, IMPULSE_M1} from './pendulum.model'; import {TranslatePipe} from '@ngx-translate/core'; +import {MatButton} from '@angular/material/button'; +import {Information} from '../information/information'; +import {AlgorithmInformation} from '../information/information.models'; +import {UrlConstants} from '../../../constants/UrlConstants'; @Component({ selector: 'app-pendulum', @@ -19,13 +23,30 @@ import {TranslatePipe} from '@ngx-translate/core'; FormsModule, NgxSliderModule, TranslatePipe, + MatButton, + Information, ], templateUrl: './pendulum.component.html', styleUrl: './pendulum.component.scss', }) -export class PendulumComponent { +class PendulumComponent { // --- CONFIGURATION --- + algoInformation: AlgorithmInformation = { + title: 'PENDULUM.EXPLANATION.TITLE', + entries: [ + { + name: '', + description: 'PENDULUM.EXPLANATION.EXPLANATION', + link: UrlConstants.DOUBLE_PENDULUM_WIKI + } + ], + disclaimer: 'PENDULUM.EXPLANATION.DISCLAIMER', + disclaimerBottom: 'PENDULUM.EXPLANATION.DISCLAIMER_BOTTOM', + disclaimerListEntry: ['PENDULUM.EXPLANATION.DISCLAIMER_1', 'PENDULUM.EXPLANATION.DISCLAIMER_2', 'PENDULUM.EXPLANATION.DISCLAIMER_3', 'PENDULUM.EXPLANATION.DISCLAIMER_4'] + }; + + renderConfig: RenderConfig = { mode: '2D', initialViewSize: 2, @@ -96,11 +117,23 @@ export class PendulumComponent { l1: DEFAULT_L1_LENGTH, l2: DEFAULT_L2_LENGTH, damping: DEFAULT_DAMPING, - trailDecay: DEFAULT_TRAIL_DECAY + trailDecay: DEFAULT_TRAIL_DECAY, + impulseM1: 0.0, + impulseM2: 0.0, }; + private currentSceneData: SceneEventData | null = null; + onSceneReady(event: SceneEventData) { - const { engine, scene } = event; + this.currentSceneData = event; + this.createSimulation(); + } + + private createSimulation() { + if (!this.currentSceneData){ + return; + } + const {engine, scene} = this.currentSceneData; engine.resize(); const width = engine.getRenderWidth(); @@ -113,20 +146,20 @@ export class PendulumComponent { const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles - const paramsBuffer = new StorageBuffer(engine, 12 * 4); - const paramsData = new Float32Array(12); + const paramsBuffer = new StorageBuffer(engine, 14 * 4); + const paramsData = new Float32Array(14); // --- 2. SHADERS --- const csPhysics = new ComputeShader("physics", engine, - { computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL }, - { bindingsMapping: { "state": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 } } } + {computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL}, + {bindingsMapping: {"state": {group: 0, binding: 0}, "p": {group: 0, binding: 1}}} ); csPhysics.setStorageBuffer("state", stateBuffer); csPhysics.setStorageBuffer("p", paramsBuffer); const csRender = new ComputeShader("render", engine, - { computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, - { bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 }, "state": { group: 0, binding: 2 } } } + {computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL}, + {bindingsMapping: {"pixelBuffer": {group: 0, binding: 0}, "p": {group: 0, binding: 1}, "state": {group: 0, binding: 2}}} ); csRender.setStorageBuffer("pixelBuffer", pixelBuffer); csRender.setStorageBuffer("p", paramsBuffer); @@ -140,6 +173,8 @@ export class PendulumComponent { mat.setStorageBuffer("p", paramsBuffer); } + //remove old observables if available + scene.onBeforeRenderObservable.clear(); // --- 4. RENDER LOOP --- scene.onBeforeRenderObservable.add(() => { this.simParams.time += this.simParams.dt; @@ -159,7 +194,11 @@ export class PendulumComponent { paramsData[8] = this.simParams.l2; paramsData[9] = this.simParams.damping; paramsData[10] = this.simParams.trailDecay; - paramsData[11] = 0; // Pad + paramsData[11] = this.simParams.impulseM1; + paramsData[12] = this.simParams.impulseM2; + paramsData[13] = 0; // Pad + + this.resetImpulses(); paramsBuffer.update(paramsData); @@ -171,4 +210,31 @@ export class PendulumComponent { }); } + private resetImpulses() { + if (this.simParams.impulseM1 !== 0.0) { + this.simParams.impulseM1 = 0; + } + + if (this.simParams.impulseM2 !== 0.0) { + this.simParams.impulseM2 = 0; + } + } + + pushPendulum(m1: boolean) { + if (m1) + { + this.simParams.impulseM1 = IMPULSE_M1; + return; + } + + this.simParams.impulseM2 = IMPULSE_M2; + } + + resetPendulum() { + this.createSimulation(); + } } + + + +export default PendulumComponent diff --git a/src/app/pages/algorithms/pendulum/pendulum.model.ts b/src/app/pages/algorithms/pendulum/pendulum.model.ts index 0d3df42..63d48b1 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.model.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.model.ts @@ -19,3 +19,6 @@ export const DEFAULT_M1_MASS = 2; export const DEFAULT_M2_MASS = 1; export const MIN_MASS = 0.1; export const MAX_MASS = 5; + +export const IMPULSE_M1 = 7; +export const IMPULSE_M2 = 15; diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 3aaa60c..c095e70 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -25,6 +25,8 @@ const SHARED_STRUCTS = ` l2: f32, damping: f32, trailDecay: f32, + impulseM1: f32, + impulseM2: f32, pad: f32 // <-- Padding for safe 16-byte memory alignment }; @@ -134,8 +136,8 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let den2 = p.l2 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t)); let a2 = num2 / den2; - let new_v1 = (v1 + a1 * p.dt) * p.damping; - let new_v2 = (v2 + a2 * p.dt) * p.damping; + let new_v1 = (v1 + a1 * p.dt) * p.damping + p.impulseM1; + let new_v2 = (v2 + a2 * p.dt) * p.damping + p.impulseM2; state.v1 = new_v1; state.v2 = new_v2; diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 7e4c94e..d20c574 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -423,7 +423,20 @@ "L1_LENGTH": "Länge L1", "L2_LENGTH": "Länge L2", "M1_MASS": "Masse M1", - "M2_MASS": "Masse M2" + "M2_MASS": "Masse M2", + "POKE_M1": "Schubse M1", + "POKE_M2": "Schubse M2", + "RESET": "Neustarten", + "EXPLANATION": { + "TITLE": "Chaostheorie: Das Doppelpendel", + "EXPLANATION": "Das Doppelpendel ist eines der bekanntesten und faszinierendsten Beispiele der Physik für ein dynamisches System, das 'deterministisches Chaos' erzeugt. Es besteht schlicht aus einem einfachen Pendel, an dessen unterem Ende ein zweites Pendel befestigt ist. Obwohl die zugrundeliegenden Bewegungsgesetze der klassischen Mechanik streng mathematisch definiert sind, ist das Verhalten des Doppelpendels auf lange Sicht absolut unvorhersehbar. Es gilt in der Physik als das klassische Vorzeigeobjekt für den sogenannten Schmetterlingseffekt.", + "DISCLAIMER": "Diese WebGPU-Simulation berechnet die Bewegungs- und Beschleunigungsgleichungen des Pendels 60-mal pro Sekunde in Echtzeit. Dabei gelten folgende Besonderheiten:", + "DISCLAIMER_1": "Extreme Sensitivität: Winzigste Änderungen in den Startbedingungen (z.B. ein Tausendstel Grad Abweichung im Startwinkel oder bei der Masse) führen schon nach kurzer Zeit zu einer völlig anderen, chaotischen Flugbahn.", + "DISCLAIMER_2": "Deterministisches Chaos: Die Bewegung wirkt zwar völlig wild und zufällig, ist es aber nicht. Startest du die Simulation mit exakt denselben Werten neu, wird das Pendel zu 100 % denselben Weg fliegen.", + "DISCLAIMER_3": "Numerische Integration: Da Computer Zeit nicht stufenlos, sondern in winzigen Schritten (dt) berechnen, entstehen bei jedem Frame winzige mathematische Rundungsfehler. Diese summieren sich auf und beeinflussen das Chaos zusätzlich.", + "DISCLAIMER_4": "Energieerhaltung & Reibung: In einem perfekten physikalischen System ohne Widerstand würde das Pendel ewig weiterschwingen. Für eine natürliche Optik nutzt der Algorithmus einen künstlichen Dämpfungsfaktor, der Luftreibung simuliert und das System irgendwann beruhigt.", + "DISCLAIMER_BOTTOM": "HINWEIS: Wenn zuviele Impulse in das System gegeben werden, wird die Simulation instabil. Dann hängt das Pendel nur noch runter und es muss neu gestartet werden." + } }, "ALGORITHM": { "TITLE": "Algorithmen", @@ -452,8 +465,8 @@ "DESCRIPTION": "3D-Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)." }, "PENDULUM": { - "TITLE": "Pendel", - "DESCRIPTION": "Noch ein WebGPU test." + "TITLE": "Doppel-Pendel", + "DESCRIPTION": "Visualisierung einer chaotischen Doppel-Pendel-Simulation mit WebGPU." }, "NOTE": "HINWEIS", "GRID_HEIGHT": "Höhe", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 29d8324..4c970d5 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -422,7 +422,20 @@ "L1_LENGTH": "Length L1", "L2_LENGTH": "Length L2", "M1_MASS": "Mass M1", - "M2_MASS": "Mass M2" + "M2_MASS": "Mass M2", + "POKE_M1": "Poke M1", + "POKE_M2": "Poke M2", + "RESET": "Reset", + "EXPLANATION": { + "TITLE": "Chaos Theory: The Double Pendulum", + "EXPLANATION": "The double pendulum is one of physics' most famous and fascinating examples of a dynamic system that generates 'deterministic chaos'. It simply consists of a standard pendulum with a second pendulum attached to its lower end. Although the underlying laws of classical mechanics are strictly mathematically defined, the long-term behavior of the double pendulum is absolutely unpredictable. In physics, it is considered the classic showcase object for the so-called butterfly effect.", + "DISCLAIMER": "This WebGPU simulation calculates the motion and acceleration equations of the pendulum 60 times per second in real-time. The following characteristics apply:", + "DISCLAIMER_1": "Extreme Sensitivity: The tiniest changes in the initial conditions (e.g., a thousandth of a degree deviation in the starting angle or mass) lead to a completely different, chaotic trajectory after just a short time.", + "DISCLAIMER_2": "Deterministic Chaos: The movement may look completely wild and random, but it isn't. If you restart the simulation with the exact same values, the pendulum will follow 100% the same path.", + "DISCLAIMER_3": "Numerical Integration: Since computers do not calculate time continuously but in tiny steps (dt), minute mathematical rounding errors occur in every frame. These add up over time and further influence the chaos.", + "DISCLAIMER_4": "Energy Conservation & Friction: In a perfect physical system without resistance, the pendulum would swing forever. For a natural look, the algorithm uses an artificial damping factor that simulates air friction and eventually brings the system to a halt.", + "DISCLAIMER_BOTTOM": "NOTE: If too many impulses are fed into the system, the simulation becomes unstable. The pendulum will then just hang down and the simulation will have to be restarted." + } }, "ALGORITHM": { "TITLE": "Algorithms", @@ -451,8 +464,8 @@ "DESCRIPTION": "3D Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)." }, "PENDULUM": { - "TITLE": "Pendulum", - "DESCRIPTION": "Just a test atm." + "TITLE": "Double pendulum", + "DESCRIPTION": "Visualisation of a chaotic double pendulum simulation with WebGPU." }, "NOTE": "Note", "GRID_HEIGHT": "Height", diff --git a/src/styles.scss b/src/styles.scss index ff47d5c..ccbe57b 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -314,6 +314,12 @@ canvas { margin-bottom: 1rem; } +.slider-control-container { + display: flex; + align-items: center; + gap: 10px; +} + /* Sorting Visualization */ .sorting-visualization-area { display: flex; -- 2.49.1 From eed7e8c0fa0591c3eb2de34cf10d424fb7ecafea Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 21 Feb 2026 12:27:07 +0100 Subject: [PATCH 14/14] Update fractal.component.ts fixed linting issue --- src/app/pages/algorithms/fractal/fractal.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index 24473f0..acd18aa 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -10,7 +10,7 @@ import {UrlConstants} from '../../../constants/UrlConstants'; import {FormsModule} from '@angular/forms'; import {BabylonCanvas, RenderCallback, RenderConfig, SceneEventData} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader'; -import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core'; +import {PointerEventTypes, PointerInfo, ShaderMaterial, Vector2} from '@babylonjs/core'; import {MatButton} from '@angular/material/button'; import {MatIcon} from '@angular/material/icon'; import {NgxSliderModule, Options} from '@angular-slider/ngx-slider'; -- 2.49.1