Finalized Algorithm
Some checks failed
Build, Test & Push Frontend / quality-check (pull_request) Failing after 56s
Build, Test & Push Frontend / docker (pull_request) Has been skipped

Added final descriptions and polished the system
This commit is contained in:
2026-02-21 12:25:07 +01:00
parent 34148aade2
commit 24d6d9cdbe
9 changed files with 141 additions and 25 deletions

View File

@@ -8,7 +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/pendulum/pendulum.component'; import PendulumComponent from '../pages/algorithms/pendulum/pendulum.component';
export class RouterConstants { export class RouterConstants {

View File

@@ -17,4 +17,5 @@
static readonly MANDELBULB_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle' static readonly MANDELBULB_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle'
static readonly MANDELBOX_WIKI = 'https://de.wikipedia.org/wiki/Mandelbox' static readonly MANDELBOX_WIKI = 'https://de.wikipedia.org/wiki/Mandelbox'
static readonly JULIA3D_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle' static readonly JULIA3D_WIKI = 'https://de.wikipedia.org/wiki/Mandelknolle'
static readonly DOUBLE_PENDULUM_WIKI = 'https://de.wikipedia.org/wiki/Doppelpendel'
} }

View File

@@ -3,30 +3,42 @@
<mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title> <mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
<app-information [algorithmInformation]="algoInformation"/>
<div class="controls-container"> <div class="controls-container">
<div style="display: flex; align-items: center; gap: 10px;"> <div class="slider-control-container">
<p style="white-space: nowrap">{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p>
<ngx-slider [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.ATTRACTION' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.ATTRACTION' | translate }}</p>
<ngx-slider [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider>
</div> </div>
<div style="display: flex; align-items: center; gap: 10px;"> <div class="slider-control-container">
<p style="white-space: nowrap">{{ 'PENDULUM.L1_LENGTH' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.L1_LENGTH' | translate }}</p>
<ngx-slider [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.L2_LENGTH' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.L2_LENGTH' | translate }}</p>
<ngx-slider [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider>
</div> </div>
<div style="display: flex; align-items: center; gap: 10px;"> <div class="slider-control-container">
<p style="white-space: nowrap">{{ 'PENDULUM.M1_MASS' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.M1_MASS' | translate }}</p>
<ngx-slider [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.M2_MASS' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.M2_MASS' | translate }}</p>
<ngx-slider [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider>
</div> </div>
<div style="display: flex; align-items: center; gap: 10px;"> <div class="slider-control-container">
<p style="white-space: nowrap">{{ 'PENDULUM.DAMPING' | translate }}</p> <p style="white-space: nowrap">{{ 'PENDULUM.DAMPING' | translate }}</p>
<ngx-slider [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider> <ngx-slider [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider>
</div> </div>
<div class="legend"> <div class="slider-control-container">
<button mat-raised-button color="primary" (click)="pushPendulum(true)">
{{ 'PENDULUM.POKE_M1' | translate }}
</button>
<button mat-raised-button color="primary" (click)="pushPendulum(false)">
{{ 'PENDULUM.POKE_M2' | translate }}
</button>
<button mat-raised-button color="primary" (click)="resetPendulum()">
{{ 'PENDULUM.RESET' | translate }}
</button>
</div>
<div class="legend" style="margin-top: 10px">
<span><span class="legend-color L1"></span> L1</span> <span><span class="legend-color L1"></span> L1</span>
<span><span class="legend-color L2"></span> L2</span> <span><span class="legend-color L2"></span> L2</span>
<span><span class="legend-color M1"></span> M1</span> <span><span class="legend-color M1"></span> M1</span>

View File

@@ -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 {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 {FormsModule} from '@angular/forms';
import {NgxSliderModule, Options} from '@angular-slider/ngx-slider'; 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 {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({ @Component({
selector: 'app-pendulum', selector: 'app-pendulum',
@@ -19,13 +23,30 @@ import {TranslatePipe} from '@ngx-translate/core';
FormsModule, FormsModule,
NgxSliderModule, NgxSliderModule,
TranslatePipe, TranslatePipe,
MatButton,
Information,
], ],
templateUrl: './pendulum.component.html', templateUrl: './pendulum.component.html',
styleUrl: './pendulum.component.scss', styleUrl: './pendulum.component.scss',
}) })
export class PendulumComponent { class PendulumComponent {
// --- CONFIGURATION --- // --- 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 = { renderConfig: RenderConfig = {
mode: '2D', mode: '2D',
initialViewSize: 2, initialViewSize: 2,
@@ -96,11 +117,23 @@ export class PendulumComponent {
l1: DEFAULT_L1_LENGTH, l1: DEFAULT_L1_LENGTH,
l2: DEFAULT_L2_LENGTH, l2: DEFAULT_L2_LENGTH,
damping: DEFAULT_DAMPING, 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) { 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(); engine.resize();
const width = engine.getRenderWidth(); const width = engine.getRenderWidth();
@@ -113,20 +146,20 @@ export class PendulumComponent {
const stateBuffer = new StorageBuffer(engine, 4 * 4); const stateBuffer = new StorageBuffer(engine, 4 * 4);
stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles
const paramsBuffer = new StorageBuffer(engine, 12 * 4); const paramsBuffer = new StorageBuffer(engine, 14 * 4);
const paramsData = new Float32Array(12); const paramsData = new Float32Array(14);
// --- 2. SHADERS --- // --- 2. SHADERS ---
const csPhysics = new ComputeShader("physics", engine, const csPhysics = new ComputeShader("physics", engine,
{ computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL }, {computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL},
{ bindingsMapping: { "state": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 } } } {bindingsMapping: {"state": {group: 0, binding: 0}, "p": {group: 0, binding: 1}}}
); );
csPhysics.setStorageBuffer("state", stateBuffer); csPhysics.setStorageBuffer("state", stateBuffer);
csPhysics.setStorageBuffer("p", paramsBuffer); csPhysics.setStorageBuffer("p", paramsBuffer);
const csRender = new ComputeShader("render", engine, const csRender = new ComputeShader("render", engine,
{ computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, {computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL},
{ bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, "p": { group: 0, binding: 1 }, "state": { group: 0, binding: 2 } } } {bindingsMapping: {"pixelBuffer": {group: 0, binding: 0}, "p": {group: 0, binding: 1}, "state": {group: 0, binding: 2}}}
); );
csRender.setStorageBuffer("pixelBuffer", pixelBuffer); csRender.setStorageBuffer("pixelBuffer", pixelBuffer);
csRender.setStorageBuffer("p", paramsBuffer); csRender.setStorageBuffer("p", paramsBuffer);
@@ -140,6 +173,8 @@ export class PendulumComponent {
mat.setStorageBuffer("p", paramsBuffer); mat.setStorageBuffer("p", paramsBuffer);
} }
//remove old observables if available
scene.onBeforeRenderObservable.clear();
// --- 4. RENDER LOOP --- // --- 4. RENDER LOOP ---
scene.onBeforeRenderObservable.add(() => { scene.onBeforeRenderObservable.add(() => {
this.simParams.time += this.simParams.dt; this.simParams.time += this.simParams.dt;
@@ -159,7 +194,11 @@ export class PendulumComponent {
paramsData[8] = this.simParams.l2; paramsData[8] = this.simParams.l2;
paramsData[9] = this.simParams.damping; paramsData[9] = this.simParams.damping;
paramsData[10] = this.simParams.trailDecay; 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); 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

View File

@@ -19,3 +19,6 @@ export const DEFAULT_M1_MASS = 2;
export const DEFAULT_M2_MASS = 1; export const DEFAULT_M2_MASS = 1;
export const MIN_MASS = 0.1; export const MIN_MASS = 0.1;
export const MAX_MASS = 5; export const MAX_MASS = 5;
export const IMPULSE_M1 = 7;
export const IMPULSE_M2 = 15;

View File

@@ -25,6 +25,8 @@ const SHARED_STRUCTS = `
l2: f32, l2: f32,
damping: f32, damping: f32,
trailDecay: f32, trailDecay: f32,
impulseM1: f32,
impulseM2: f32,
pad: f32 // <-- Padding for safe 16-byte memory alignment 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 den2 = p.l2 * (2.0 * p.m1 + p.m2 - p.m2 * cos(2.0 * delta_t));
let a2 = num2 / den2; let a2 = num2 / den2;
let new_v1 = (v1 + a1 * p.dt) * p.damping; let new_v1 = (v1 + a1 * p.dt) * p.damping + p.impulseM1;
let new_v2 = (v2 + a2 * p.dt) * p.damping; let new_v2 = (v2 + a2 * p.dt) * p.damping + p.impulseM2;
state.v1 = new_v1; state.v1 = new_v1;
state.v2 = new_v2; state.v2 = new_v2;

View File

@@ -423,7 +423,20 @@
"L1_LENGTH": "Länge L1", "L1_LENGTH": "Länge L1",
"L2_LENGTH": "Länge L2", "L2_LENGTH": "Länge L2",
"M1_MASS": "Masse M1", "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": { "ALGORITHM": {
"TITLE": "Algorithmen", "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)." "DESCRIPTION": "3D-Visualisierung von komplexe, geometrische Mustern, die sich selbst in immer kleineren Maßstäben ähneln (Selbstähnlichkeit)."
}, },
"PENDULUM": { "PENDULUM": {
"TITLE": "Pendel", "TITLE": "Doppel-Pendel",
"DESCRIPTION": "Noch ein WebGPU test." "DESCRIPTION": "Visualisierung einer chaotischen Doppel-Pendel-Simulation mit WebGPU."
}, },
"NOTE": "HINWEIS", "NOTE": "HINWEIS",
"GRID_HEIGHT": "Höhe", "GRID_HEIGHT": "Höhe",

View File

@@ -422,7 +422,20 @@
"L1_LENGTH": "Length L1", "L1_LENGTH": "Length L1",
"L2_LENGTH": "Length L2", "L2_LENGTH": "Length L2",
"M1_MASS": "Mass M1", "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": { "ALGORITHM": {
"TITLE": "Algorithms", "TITLE": "Algorithms",
@@ -451,8 +464,8 @@
"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": { "PENDULUM": {
"TITLE": "Pendulum", "TITLE": "Double pendulum",
"DESCRIPTION": "Just a test atm." "DESCRIPTION": "Visualisation of a chaotic double pendulum simulation with WebGPU."
}, },
"NOTE": "Note", "NOTE": "Note",
"GRID_HEIGHT": "Height", "GRID_HEIGHT": "Height",

View File

@@ -314,6 +314,12 @@ canvas {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.slider-control-container {
display: flex;
align-items: center;
gap: 10px;
}
/* Sorting Visualization */ /* Sorting Visualization */
.sorting-visualization-area { .sorting-visualization-area {
display: flex; display: flex;