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.
This commit is contained in:
@@ -13,6 +13,7 @@ export const routes: Routes = [
|
|||||||
{ path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT},
|
{ path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT},
|
||||||
{ path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT},
|
{ path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT},
|
||||||
{ path: RouterConstants.FRACTAL.PATH, component: RouterConstants.FRACTAL.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}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.comp
|
|||||||
import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component';
|
import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component';
|
||||||
import {FractalComponent} from '../pages/algorithms/fractal/fractal.component';
|
import {FractalComponent} from '../pages/algorithms/fractal/fractal.component';
|
||||||
import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component';
|
import {Fractal3dComponent} from '../pages/algorithms/fractal3d/fractal3d.component';
|
||||||
|
import {PendulumComponent} from '../pages/algorithms/pendle/pendulum.component';
|
||||||
|
|
||||||
export class RouterConstants {
|
export class RouterConstants {
|
||||||
|
|
||||||
@@ -65,6 +66,12 @@ export class RouterConstants {
|
|||||||
COMPONENT: Fractal3dComponent
|
COMPONENT: Fractal3dComponent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static readonly PENDULUM = {
|
||||||
|
PATH: 'algorithms/pendulum',
|
||||||
|
LINK: '/algorithms/pendulum',
|
||||||
|
COMPONENT: PendulumComponent
|
||||||
|
};
|
||||||
|
|
||||||
static readonly IMPRINT = {
|
static readonly IMPRINT = {
|
||||||
PATH: 'imprint',
|
PATH: 'imprint',
|
||||||
LINK: '/imprint',
|
LINK: '/imprint',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {MatSelect} from '@angular/material/select';
|
|||||||
import {AlgorithmInformation} from '../information/information.models';
|
import {AlgorithmInformation} from '../information/information.models';
|
||||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||||
import {FormsModule} from '@angular/forms';
|
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 {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader';
|
||||||
import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core';
|
import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core';
|
||||||
import {MatButton} from '@angular/material/button';
|
import {MatButton} from '@angular/material/button';
|
||||||
@@ -155,8 +155,8 @@ export class FractalComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSceneReady(scene: Scene): void {
|
onSceneReady(event: SceneReadyEvent): void {
|
||||||
scene.onPointerObservable.add((pointerInfo) => {
|
event.scene.onPointerObservable.add((pointerInfo) => {
|
||||||
switch (pointerInfo.type) {
|
switch (pointerInfo.type) {
|
||||||
|
|
||||||
case PointerEventTypes.POINTERDOWN:
|
case PointerEventTypes.POINTERDOWN:
|
||||||
|
|||||||
11
src/app/pages/algorithms/pendle/pendulum.component.html
Normal file
11
src/app/pages/algorithms/pendle/pendulum.component.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<mat-card class="container">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Ich bin ein Pendel - blub</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<app-babylon-canvas
|
||||||
|
[config]="renderConfig"
|
||||||
|
(sceneReady)="onSceneReady($event)"
|
||||||
|
/>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
164
src/app/pages/algorithms/pendle/pendulum.component.ts
Normal file
164
src/app/pages/algorithms/pendle/pendulum.component.ts
Normal file
@@ -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<f32>;
|
||||||
|
attribute uv : vec2<f32>;
|
||||||
|
varying vUV : vec2<f32>;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn main(input : VertexInputs) -> FragmentInputs {
|
||||||
|
var output : FragmentInputs;
|
||||||
|
output.position = vec4<f32>(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<f32>;
|
||||||
|
|
||||||
|
var<storage, read> pixelBuffer : array<f32>;
|
||||||
|
var<uniform> params : Params;
|
||||||
|
|
||||||
|
struct Params {
|
||||||
|
resolution: vec2<f32>,
|
||||||
|
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<f32>(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
if (index < total) {
|
||||||
|
let val = pixelBuffer[index];
|
||||||
|
color = vec4<f32>(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<storage, read_write> pixelBuffer : array<f32>;
|
||||||
|
@group(0) @binding(1) var<uniform> params : Params;
|
||||||
|
|
||||||
|
struct Params {
|
||||||
|
resolution: vec2<f32>,
|
||||||
|
time: f32
|
||||||
|
};
|
||||||
|
|
||||||
|
@compute @workgroup_size(64)
|
||||||
|
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,12 @@ export class AlgorithmsService {
|
|||||||
title: 'ALGORITHM.FRACTAL3D.TITLE',
|
title: 'ALGORITHM.FRACTAL3D.TITLE',
|
||||||
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
|
description: 'ALGORITHM.FRACTAL3D.DESCRIPTION',
|
||||||
routerLink: RouterConstants.FRACTAL3d.LINK
|
routerLink: RouterConstants.FRACTAL3d.LINK
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pendulum',
|
||||||
|
title: 'ALGORITHM.PENDULUM.TITLE',
|
||||||
|
description: 'ALGORITHM.PENDULUM.DESCRIPTION',
|
||||||
|
routerLink: RouterConstants.PENDULUM.LINK
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core';
|
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 {
|
export interface RenderConfig {
|
||||||
mode: '2D' | '3D';
|
mode: '2D' | '3D';
|
||||||
computeShaderSupport?: boolean;
|
shaderLanguage?: number; //0 GLSL, 1 WGSL
|
||||||
initialViewSize: number;
|
initialViewSize: number;
|
||||||
vertexShader: string;
|
vertexShader: string;
|
||||||
fragmentShader: string;
|
fragmentShader: string;
|
||||||
@@ -12,6 +12,11 @@ export interface RenderConfig {
|
|||||||
|
|
||||||
export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void;
|
export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void;
|
||||||
|
|
||||||
|
export interface SceneReadyEvent {
|
||||||
|
scene: Scene;
|
||||||
|
engine: WebGPUEngine;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-babylon-canvas',
|
selector: 'app-babylon-canvas',
|
||||||
imports: [],
|
imports: [],
|
||||||
@@ -26,7 +31,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
@Input({ required: true }) config!: RenderConfig;
|
@Input({ required: true }) config!: RenderConfig;
|
||||||
@Input() renderCallback?: RenderCallback;
|
@Input() renderCallback?: RenderCallback;
|
||||||
|
|
||||||
@Output() sceneReady = new EventEmitter<Scene>();
|
@Output() sceneReady = new EventEmitter<SceneReadyEvent>();
|
||||||
|
|
||||||
private engine!: WebGPUEngine;
|
private engine!: WebGPUEngine;
|
||||||
private scene!: Scene;
|
private scene!: Scene;
|
||||||
@@ -63,7 +68,10 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
this.addListener(canvas);
|
this.addListener(canvas);
|
||||||
this.createShaderMaterial();
|
this.createShaderMaterial();
|
||||||
this.createFullScreenRect();
|
this.createFullScreenRect();
|
||||||
this.sceneReady.emit(this.scene);
|
this.sceneReady.emit({
|
||||||
|
scene: this.scene,
|
||||||
|
engine: this.engine
|
||||||
|
});
|
||||||
this.addRenderLoop(canvas);
|
this.addRenderLoop(canvas);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -133,7 +141,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributes: ["position", "uv"],
|
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;
|
this.shaderMaterial.disableDepthWrite = true;
|
||||||
|
|||||||
@@ -441,6 +441,10 @@
|
|||||||
"TITLE": "Fraktale 3D",
|
"TITLE": "Fraktale 3D",
|
||||||
"DESCRIPTION": "3D-Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)."
|
"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",
|
"NOTE": "HINWEIS",
|
||||||
"GRID_HEIGHT": "Höhe",
|
"GRID_HEIGHT": "Höhe",
|
||||||
"GRID_WIDTH": "Beite"
|
"GRID_WIDTH": "Beite"
|
||||||
|
|||||||
@@ -440,6 +440,10 @@
|
|||||||
"TITLE": "Fractals 3D",
|
"TITLE": "Fractals 3D",
|
||||||
"DESCRIPTION": "3D Visualisation of complex geometric patterns that resemble each other on increasingly smaller scales (self-similarity)."
|
"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",
|
"NOTE": "Note",
|
||||||
"GRID_HEIGHT": "Height",
|
"GRID_HEIGHT": "Height",
|
||||||
"GRID_WIDTH": "Width"
|
"GRID_WIDTH": "Width"
|
||||||
|
|||||||
Reference in New Issue
Block a user