Added trail effect

This commit is contained in:
2026-02-21 10:21:21 +01:00
parent 13f99ac7ae
commit 2bfa8ba9a1
4 changed files with 54 additions and 22 deletions

View File

@@ -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);

View File

@@ -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<f32>(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<f32>(0.0, 0.0, 0.0, 1.0);
return fragmentOutputs;
}
let index = y * width + x;
let val = pixelBuffer[index];
var color = vec3<f32>(0.1, 0.1, 0.15); // Background
if (val > 0.1) { color = vec3<f32>(0.5, 0.5, 0.5); } // Line
if (val > 0.8) { color = vec3<f32>(1.0, 1.0, 1.0); } // Mass
// --- THE MAGIC DECODING ---
let rawVal = pixelBuffer[index];
var trailVal = rawVal;
var isLine = false;
fragmentOutputs.color = vec4<f32>(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<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);
// 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<f32>(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<f32>(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<f32>(0.5 * aspect, 0.3);
let displayScale = 0.15;
// Calculate positions
let p1 = origin + vec2<f32>(sin(state.theta1), cos(state.theta1)) * p.l1 * displayScale;
let p2 = p1 + vec2<f32>(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;
}

View File

@@ -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",

View File

@@ -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",