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"