From 0d2e7c97ecd2d63447d673dc9b2276986ad82893 Mon Sep 17 00:00:00 2001 From: Lobo Date: Fri, 20 Feb 2026 17:14:58 +0100 Subject: [PATCH] See pendulum I can see the pendulum, but something is not correct with the resolution --- .../algorithms/pendulum/pendulum.component.ts | 89 +++++++-- .../algorithms/pendulum/pendulum.shader.ts | 176 +++++++++++++++--- 2 files changed, 220 insertions(+), 45 deletions(-) diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 7292393..3a22779 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -2,7 +2,7 @@ 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'; -import {PENDULUM_COMPUTE_SHADER_WGSL, PENDULUM_FRAGMENT_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'; @Component({ selector: 'app-pendulum', @@ -30,33 +30,66 @@ export class PendulumComponent { const engine = event.engine; const scene = event.scene; + engine.resize(); + 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); + console.log("Width and Height ", width, height); - // Uniform Buffer + // A. Buffer Setup + // 1. Pixel Buffer (wie gehabt) + const pixelBuffer = new StorageBuffer(engine, totalPixels * 4); + + // 2. State Buffer (4 Floats: theta1, theta2, v1, v2) + const stateBuffer = new StorageBuffer(engine, 4 * 4); + stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); + + // 3. Params Uniform Buffer const ubo = new UniformBuffer(engine); ubo.addUniform("resolution", 2); ubo.addUniform("time", 1); + ubo.addUniform("dt", 1); + ubo.addUniform("g", 1); + ubo.addUniform("m1", 1); + ubo.addUniform("m2", 1); + ubo.addUniform("l1", 1); + ubo.addUniform("l2", 1); + ubo.addUniform("damping", 1); + ubo.addUniform("pad1", 1); //Alignment + ubo.addUniform("pad2", 1); //Alignment ubo.update(); - // Compute Shader - const cs = new ComputeShader("Pendulum Compute Shader", engine, { - computeSource: PENDULUM_COMPUTE_SHADER_WGSL + // B. Compute Shaders Setup + + // CS1: Physics + const csPhysics = new ComputeShader("physics", engine, { + computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL + }, { + bindingsMapping: { + "state": { group: 0, binding: 0 }, + "p": { group: 0, binding: 1 } + } + }); + csPhysics.setStorageBuffer("state", stateBuffer); + csPhysics.setUniformBuffer("p", ubo); + + // CS2: Render/Trail + const csRender = new ComputeShader("render", engine, { + computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL }, { bindingsMapping: { "pixelBuffer": { group: 0, binding: 0 }, - "params": { group: 0, binding: 1 } + "p": { group: 0, binding: 1 }, + "state": { group: 0, binding: 2 } } }); - cs.setStorageBuffer("pixelBuffer", pixelBuffer); - cs.setUniformBuffer("params", ubo); + csRender.setStorageBuffer("pixelBuffer", pixelBuffer); + csRender.setUniformBuffer("p", ubo); + csRender.setStorageBuffer("state", stateBuffer); - // Material Setup + // C. Material Setup (Anzeige) const plane = scene.getMeshByName("plane"); if (plane?.material) { const mat = plane.material as any; @@ -64,17 +97,41 @@ export class PendulumComponent { mat.setUniformBuffer("params", ubo); } - // Render Loop + // D. Simulation Loop let time = 0; - scene.onBeforeRenderObservable.add(() => { - time += engine.getDeltaTime() / 1000.0; + // Physik Parameter + const dt = 0.015; + const g = 9.81; + const m1 = 2.0; + const m2 = 1.0; + const l1 = 1.5; + const l2 = 1.2; + const damping = 0.99; + + scene.onBeforeRenderObservable.add(() => { + time += dt; + + // Update Params ubo.updateFloat2("resolution", width, height); ubo.updateFloat("time", time); + ubo.updateFloat("dt", dt); + ubo.updateFloat("g", g); + ubo.updateFloat("m1", m1); + ubo.updateFloat("m2", m2); + ubo.updateFloat("l1", l1); + ubo.updateFloat("l2", l2); + ubo.updateFloat("damping", damping); + ubo.updateFloat("pad1", 0); + ubo.updateFloat("pad2", 0); ubo.update(); + // Do physics (1 thread) + csPhysics.dispatch(1, 1, 1); + + // Paint per pixel const dispatchCount = Math.ceil(totalPixels / 64); - cs.dispatch(dispatchCount, 1, 1); + csRender.dispatch(dispatchCount, 1, 1); }); } } diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 75237a0..c736647 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -14,61 +14,179 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = ` varying vUV : vec2; - var pixelBuffer : array; var params : Params; struct Params { resolution: vec2, - time: f32 + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, // <-- Alignment + pad2: f32 // <-- Alignment }; @fragment fn main(input : FragmentInputs) -> FragmentOutputs { + let width = u32(params.resolution.x); 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 Color (Black) - var color = vec4(0.0, 0.0, 0.0, 1.0); + let val = pixelBuffer[index]; - if (index < total) { - let val = pixelBuffer[index]; - color = vec4(val, val * 0.5, 0.2, 1.0); - } - - //fragmentOutput is provided by babylon - fragmentOutputs.color = color; + // Hintergrund = Dunkelgrau, Linien = Grau, Massen = Weiß + var color = vec3(0.1, 0.1, 0.15); + if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Linie + if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Masse + fragmentOutputs.color = vec4(color, 1.0); return fragmentOutputs; } - `; +`; -export const PENDULUM_COMPUTE_SHADER_WGSL = ` - @group(0) @binding(0) var pixelBuffer : array; - @group(0) @binding(1) var params : Params; +export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = ` + struct State { + theta1: f32, + theta2: f32, + v1: f32, + v2: f32 + }; + + @group(0) @binding(0) var state : State; + @group(0) @binding(1) var p : Params; struct Params { resolution: vec2, - time: f32 + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, // <-- Alignment + pad2: f32 // <-- Alignment }; + @compute @workgroup_size(1) + fn main() { + let t1 = state.theta1; + let t2 = state.theta2; + let v1 = state.v1; + let v2 = state.v2; + + let m1 = p.m1; + let m2 = p.m2; + let l1 = p.l1; + let l2 = p.l2; + let g = p.g; + + let num1 = -g * (2.0 * m1 + m2) * sin(t1) + - m2 * g * sin(t1 - 2.0 * t2) + - 2.0 * sin(t1 - t2) * m2 * (v2 * v2 * l2 + v1 * v1 * l1 * cos(t1 - t2)); + let den1 = l1 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + let a1 = num1 / den1; + + let num2 = 2.0 * sin(t1 - t2) * (v1 * v1 * l1 * (m1 + m2) + g * (m1 + m2) * cos(t1) + v2 * v2 * l2 * m2 * cos(t1 - t2)); + let den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2)); + let a2 = num2 / den2; + + let new_v1 = (v1 + a1 * p.dt) * p.damping; + let new_v2 = (v2 + a2 * p.dt) * p.damping; + + state.v1 = new_v1; + state.v2 = new_v2; + state.theta1 = t1 + new_v1 * p.dt; + state.theta2 = t2 + new_v2 * p.dt; + } +`; + +export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = ` + struct State { + theta1: f32, + theta2: f32, + v1: f32, + v2: f32 + }; + + @group(0) @binding(0) var pixelBuffer : array; + @group(0) @binding(1) var p : Params; + @group(0) @binding(2) var state : State; + + struct Params { + resolution: vec2, + time: f32, + dt: f32, + g: f32, + m1: f32, + m2: f32, + l1: f32, + l2: f32, + damping: f32, + pad1: f32, + pad2: f32 + }; + + fn sdSegment(p: vec2, a: vec2, b: vec2) -> f32 { + let pa = p - a; + let ba = b - a; + let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); + } + @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 index = global_id.x; + let width = u32(p.resolution.x); + let height = u32(p.resolution.y); - let width = u32(params.resolution.x); - let x = f32(index % width); - let y = f32(index / width); + if (index >= width * height) { return; } - // Zeit-Variable nutzen für Animation - let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); + let x = f32(index % width); + let y = f32(index / width); + let uv = vec2(x / p.resolution.x, y / p.resolution.y); - pixelBuffer[index] = value * 0.5 + 0.5; + let aspect = p.resolution.x / p.resolution.y; + let uv_corr = vec2(uv.x * aspect, uv.y); + + var newVal = 0.0; + + let origin = vec2(0.5 * aspect, 0.7); + + let s1 = sin(state.theta1); + let c1 = cos(state.theta1); + let s2 = sin(state.theta2); + let c2 = cos(state.theta2); + + let displayScale = 0.15; + + let p1 = origin + vec2(s1, -c1) * p.l1 * displayScale; + let p2 = p1 + vec2(s2, -c2) * p.l2 * displayScale; + + let dLine1 = sdSegment(uv_corr, origin, p1); + let dLine2 = sdSegment(uv_corr, p1, p2); + let dMass1 = length(uv_corr - p1); + let dMass2 = length(uv_corr - p2); + + // Geometrie zeichnen + let lineThick = 0.003; + let m1Radius = 0.02; + let m2Radius = 0.02; + + if (dLine1 < lineThick || dLine2 < lineThick) { + newVal = 0.5; + } + if (dMass1 < m1Radius || dMass2 < m2Radius) { + newVal = 1.0; + } + + pixelBuffer[index] = newVal; } - `; +`;