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"