feature/webGPU #25

Merged
lobo merged 14 commits from feature/webGPU into main 2026-02-21 12:32:04 +01:00
9 changed files with 227 additions and 52 deletions
Showing only changes of commit 34148aade2 - Show all commits

View File

@@ -35,8 +35,8 @@
}
<p>{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms</p>
</div>
<div class="grid-size">
<mat-form-field appearance="outline" class="grid-field">
<div class="input-container">
<mat-form-field appearance="outline" class="input-field">
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
<input
matInput
@@ -47,7 +47,7 @@
(ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="grid-field">
<mat-form-field appearance="outline" class="input-field">
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
<input
matInput
@@ -58,7 +58,7 @@
(ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()"
/>
</mat-form-field>
<mat-form-field appearance="outline" class="grid-field">
<mat-form-field appearance="outline" class="input-field">
<mat-label>{{ 'GOL.SPEED' | translate }}</mat-label>
<input
matInput

View File

@@ -26,8 +26,8 @@
</mat-button-toggle-group>
</div>
<div class="controls-panel">
<div class="grid-size">
<mat-form-field appearance="outline" class="grid-field">
<div class="input-container">
<mat-form-field appearance="outline" class="input-field">
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
<input
matInput
@@ -38,7 +38,7 @@
(ngModelChange)="genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
/> </mat-form-field>
<mat-form-field appearance="outline" class="grid-field">
<mat-form-field appearance="outline" class="input-field">
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
<input
matInput

View File

@@ -1,8 +1,38 @@
<mat-card class="container">
<mat-card-header>
<mat-card-title>Ich bin ein Pendel - blub</mat-card-title>
<mat-card-title>{{ 'PENDULUM.TITLE' | translate }}</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="controls-container">
<div style="display: flex; align-items: center; gap: 10px;">
<p style="white-space: nowrap">{{ 'PENDULUM.TRAIL_DECAY_TIME' | translate }}</p>
<ngx-slider [(value)]="simParams.trailDecay" [options]="trailDecayOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.ATTRACTION' | translate }}</p>
<ngx-slider [(value)]="simParams.g" [options]="gravityOptions" ></ngx-slider>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<p style="white-space: nowrap">{{ 'PENDULUM.L1_LENGTH' | translate }}</p>
<ngx-slider [(value)]="simParams.l1" [options]="lengthOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.L2_LENGTH' | translate }}</p>
<ngx-slider [(value)]="simParams.l2" [options]="lengthOptions" ></ngx-slider>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<p style="white-space: nowrap">{{ 'PENDULUM.M1_MASS' | translate }}</p>
<ngx-slider [(value)]="simParams.m1" [options]="massOptions" ></ngx-slider>
<p style="white-space: nowrap">{{ 'PENDULUM.M2_MASS' | translate }}</p>
<ngx-slider [(value)]="simParams.m2" [options]="massOptions" ></ngx-slider>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<p style="white-space: nowrap">{{ 'PENDULUM.DAMPING' | translate }}</p>
<ngx-slider [(value)]="simParams.damping" [options]="dampingOptions" ></ngx-slider>
</div>
<div class="legend">
<span><span class="legend-color L1"></span> L1</span>
<span><span class="legend-color L2"></span> L2</span>
<span><span class="legend-color M1"></span> M1</span>
<span><span class="legend-color M2"></span> M2</span>
</div>
</div>
<app-babylon-canvas
[config]="renderConfig"
(sceneReady)="onSceneReady($event)"

View File

@@ -3,6 +3,10 @@ import {BabylonCanvas, RenderConfig, SceneEventData} from '../../../shared/rende
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';
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 {TranslatePipe} from '@ngx-translate/core';
@Component({
selector: 'app-pendulum',
@@ -12,6 +16,9 @@ import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PEND
MatCardContent,
MatCardHeader,
MatCardTitle,
FormsModule,
NgxSliderModule,
TranslatePipe,
],
templateUrl: './pendulum.component.html',
styleUrl: './pendulum.component.scss',
@@ -29,17 +36,67 @@ export class PendulumComponent {
uniformBufferNames: []
};
trailDecayOptions: Options = {
floor: MIN_TRAIL_DECAY,
ceil: MAX_TRAIL_DECAY,
logScale: false,
step: 0.001,
showTicks: false,
hideLimitLabels: false,
hidePointerLabels: false
};
gravityOptions: Options = {
floor: MIN_G,
ceil: MAX_G,
logScale: false,
step: 0.01,
showTicks: false,
hideLimitLabels: false,
hidePointerLabels: false
};
dampingOptions: Options = {
floor: MAX_DAMPING,
ceil: MIN_DAMPING,
logScale: false,
step: 0.001,
showTicks: false,
hideLimitLabels: false,
hidePointerLabels: false
};
lengthOptions: Options = {
floor: MIN_LENGTH,
ceil: MAX_LENGTH,
logScale: false,
step: 0.1,
showTicks: false,
hideLimitLabels: false,
hidePointerLabels: false
};
massOptions: Options = {
floor: MIN_MASS,
ceil: MAX_MASS,
logScale: false,
step: 0.1,
showTicks: false,
hideLimitLabels: false,
hidePointerLabels: false
};
// Central management of physics parameters
private readonly simParams = {
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,
trailDecay: 0.98
g: DEFAULT_G,
m1: DEFAULT_M1_MASS,
m2: DEFAULT_M2_MASS,
l1: DEFAULT_L1_LENGTH,
l2: DEFAULT_L2_LENGTH,
damping: DEFAULT_DAMPING,
trailDecay: DEFAULT_TRAIL_DECAY
};
onSceneReady(event: SceneEventData) {

View File

@@ -0,0 +1,21 @@
export const DEFAULT_G = 9.81;
export const MIN_G = 2;
export const MAX_G = 15;
export const DEFAULT_DAMPING = 0.999;
export const MIN_DAMPING = 1;
export const MAX_DAMPING = 0.7;
export const DEFAULT_TRAIL_DECAY = 0.96;
export const MIN_TRAIL_DECAY = 0.2;
export const MAX_TRAIL_DECAY = 0.9999;
export const DEFAULT_L1_LENGTH = 1.5;
export const DEFAULT_L2_LENGTH = 1.2;
export const MIN_LENGTH = 0.2;
export const MAX_LENGTH = 3;
export const DEFAULT_M1_MASS = 2;
export const DEFAULT_M2_MASS = 1;
export const MIN_MASS = 0.1;
export const MAX_MASS = 5;

View File

@@ -62,27 +62,47 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = SHARED_STRUCTS + `
let index = y * width + x;
// --- THE MAGIC DECODING ---
let rawVal = pixelBuffer[index];
var trailVal = rawVal;
var isLine = false;
var val = pixelBuffer[index];
var isLine1 = false;
var isLine2 = false;
// 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
// 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<f32>(0.1, 0.1, 0.15);
let massColor = vec3<f32>(1.0, 1.0, 1.0);
let lineColor = vec3<f32>(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<f32>(0.1, 0.1, 0.15);
let mass1Color = vec3<f32>(1.0, 0.0, 0.0); // Red
let mass2Color = vec3<f32>(0.0, 1.0, 0.0); // Green
let line1Color = vec3<f32>(1.0, 1.0, 0.0); // Yellow
let line2Color = vec3<f32>(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<f32>(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<f32>(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<f32>(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;
}
`;

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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 */
}
}
}
}