diff --git a/src/app/pages/algorithms/pendulum/pendulum.component.ts b/src/app/pages/algorithms/pendulum/pendulum.component.ts index 377d590..b446cf5 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.component.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.component.ts @@ -38,7 +38,8 @@ export class PendulumComponent { m2: 1.0, l1: 1.5, l2: 1.2, - damping: 0.999 // Less damping for longer swinging + damping: 0.999, + trailDecay: 0.98 }; onSceneReady(event: SceneReadyEvent) { @@ -55,8 +56,8 @@ export class PendulumComponent { const stateBuffer = new StorageBuffer(engine, 4 * 4); stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0])); // Initial angles - const paramsBuffer = new StorageBuffer(engine, 10 * 4); - const paramsData = new Float32Array(10); + const paramsBuffer = new StorageBuffer(engine, 12 * 4); + const paramsData = new Float32Array(12); // --- 2. SHADERS --- const csPhysics = new ComputeShader("physics", engine, @@ -100,6 +101,8 @@ export class PendulumComponent { paramsData[7] = this.simParams.l1; paramsData[8] = this.simParams.l2; paramsData[9] = this.simParams.damping; + paramsData[10] = this.simParams.trailDecay; + paramsData[11] = 0; // Pad paramsBuffer.update(paramsData); diff --git a/src/app/pages/algorithms/pendulum/pendulum.shader.ts b/src/app/pages/algorithms/pendulum/pendulum.shader.ts index 2ad76f5..e8f4d22 100644 --- a/src/app/pages/algorithms/pendulum/pendulum.shader.ts +++ b/src/app/pages/algorithms/pendulum/pendulum.shader.ts @@ -23,7 +23,9 @@ const SHARED_STRUCTS = ` m2: f32, l1: f32, l2: f32, - damping: f32 + damping: f32, + trailDecay: f32, + pad: f32 // <-- Padding for safe 16-byte memory alignment }; struct State { @@ -44,30 +46,45 @@ export const PENDULUM_FRAGMENT_SHADER_WGSL = SHARED_STRUCTS + ` let width = u32(p.width); let height = u32(p.height); - // Fallback if buffer is not loaded yet if (width == 0u || height == 0u) { fragmentOutputs.color = vec4(0.5, 0.0, 0.0, 1.0); return fragmentOutputs; } - // Direct access to the pixel via screen coordinates let x = u32(input.position.x); let y = u32(input.position.y); - // Boundary check to prevent reading outside the buffer if (x >= width || y >= height) { fragmentOutputs.color = vec4(0.0, 0.0, 0.0, 1.0); return fragmentOutputs; } let index = y * width + x; - let val = pixelBuffer[index]; - var color = vec3(0.1, 0.1, 0.15); // Background - if (val > 0.1) { color = vec3(0.5, 0.5, 0.5); } // Line - if (val > 0.8) { color = vec3(1.0, 1.0, 1.0); } // Mass + // --- THE MAGIC DECODING --- + let rawVal = pixelBuffer[index]; + var trailVal = rawVal; + var isLine = false; - fragmentOutputs.color = vec4(color, 1.0); + // 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 + } + + let bgColor = vec3(0.1, 0.1, 0.15); + let massColor = vec3(1.0, 1.0, 1.0); + let lineColor = vec3(0.5, 0.5, 0.5); + + // Calculate background blending with the trail + var finalColor = mix(bgColor, massColor, clamp(trailVal, 0.0, 1.0)); + + // Overwrite with the grey line if necessary + if (isLine) { + finalColor = lineColor; + } + + fragmentOutputs.color = vec4(finalColor, 1.0); return fragmentOutputs; } `; @@ -87,7 +104,6 @@ export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let delta_t = t1 - t2; - // Equations split for better readability let num1 = -p.g * (2.0 * p.m1 + p.m2) * sin(t1) - p.m2 * p.g * sin(t1 - 2.0 * t2) - 2.0 * sin(delta_t) * p.m2 * (v2 * v2 * p.l2 + v1 * v1 * p.l1 * cos(delta_t)); @@ -98,7 +114,6 @@ 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 a2 = num2 / den2; - // Integration (Semi-Implicit Euler) let new_v1 = (v1 + a1 * p.dt) * p.damping; let new_v2 = (v2 + a2 * p.dt) * p.damping; @@ -137,25 +152,39 @@ export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = SHARED_STRUCTS + ` let aspect = p.width / p.height; let uv_corr = vec2(uv.x * aspect, uv.y); - var newVal = 0.0; // Clear background + // --- TRAIL EXTRACT & DECAY --- + let oldRaw = pixelBuffer[index]; + var oldTrail = oldRaw; + + // 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; + } + var newVal = oldTrail * p.trailDecay; // Pendulum geometry let origin = vec2(0.5 * aspect, 0.3); let displayScale = 0.15; - // Calculate positions let p1 = origin + vec2(sin(state.theta1), cos(state.theta1)) * p.l1 * displayScale; let p2 = p1 + vec2(sin(state.theta2), cos(state.theta2)) * p.l2 * displayScale; - // Distances 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); - // Draw - if (dLine1 < 0.003 || dLine2 < 0.003) { newVal = 0.5; } - if (dMass1 < 0.02 || dMass2 < 0.02) { newVal = 1.0; } + let isLine = (dLine1 < 0.002 || dLine2 < 0.002); + let isMass = (dMass1 < 0.01 || dMass2 < 0.01); + + // --- 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; + } pixelBuffer[index] = newVal; } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index a81a351..91fb19a 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -1,7 +1,7 @@ { "APP": { "TITLE": "Playground", - "COPYRIGHT": "Bilder urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!" + "COPYRIGHT": "Bilder und Sourcecode sind urheberrechtlich geschützt, keine Nutzung ohne Zustimmung!" }, "TOPBAR": { "ABOUT": "Über mich", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 4fbbc63..845b786 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1,7 +1,7 @@ { "APP": { "TITLE": "Playground", - "COPYRIGHT": "Images protected by copyright, no use without permission!" + "COPYRIGHT": "Images and code protected by copyright, no use without permission!" }, "TOPBAR": { "ABOUT": "About me",