Skip to main content

PARTICLE_INTEGRATE_GLSL

Constant PARTICLE_INTEGRATE_GLSL 

Source
pub const PARTICLE_INTEGRATE_GLSL: &str = r#"
#version 430 core

layout(local_size_x = 256) in;

struct Particle {
    vec4 position;  // xyz + lifetime
    vec4 velocity;  // xyz + age
    vec4 color;
    float size;
    float mass;
    uint flags;
    uint attractor;
};

struct Attractor {
    vec4 position;   // xyz + strength
    vec4 params;     // type, falloff_start, falloff_end, rotation
    vec4 color;
    uint atype;
    uint _pad[3];
};

layout(std430, binding = 0) buffer ParticleBuffer {
    Particle particles[];
};

layout(std430, binding = 1) readonly buffer AttractorBuffer {
    Attractor attractors[];
};

layout(std430, binding = 2) buffer DeadList {
    uint dead_count;
    uint dead_indices[];
};

layout(std140, binding = 0) uniform Params {
    float dt;
    float time;
    vec3 gravity;
    float drag;
    uint num_particles;
    uint num_attractors;
    float emit_rate;
    float _pad;
};

// Lorenz attractor vector field
vec3 lorenz(vec3 p, float sigma, float rho, float beta) {
    return vec3(
        sigma * (p.y - p.x),
        p.x * (rho - p.z) - p.y,
        p.x * p.y - beta * p.z
    );
}

// Vortex force
vec3 vortex_force(vec3 particle_pos, vec3 center, float strength, float rotation) {
    vec3 r = particle_pos - center;
    float d = length(r) + 0.001;
    vec3 tangent = cross(r, vec3(0.0, 1.0, 0.0)) / d;
    return tangent * strength * rotation / (d * d + 1.0);
}

vec3 compute_attractor_force(Particle p, Attractor a) {
    vec3 pos = p.position.xyz;
    vec3 apos = a.position.xyz;
    float strength = a.position.w;
    float falloff_start = a.params.y;
    float falloff_end   = a.params.z;

    vec3 delta = apos - pos;
    float dist = length(delta) + 0.001;

    // Falloff
    float t = clamp((dist - falloff_start) / (falloff_end - falloff_start + 0.001), 0.0, 1.0);
    float attenuation = 1.0 - t;

    switch (a.atype) {
        case 0: // Point attractor
            return normalize(delta) * strength * attenuation / (dist * dist + 1.0);
        case 1: // Vortex
            return vortex_force(pos, apos, strength * attenuation, a.params.w);
        case 2: // Lorenz field
            return lorenz(pos * 0.1, 10.0, 28.0, 2.667) * strength * 0.01 * attenuation;
        case 3: // Repulse
            return -normalize(delta) * strength * attenuation / (dist * dist + 0.5);
        default:
            return vec3(0.0);
    }
}

void main() {
    uint idx = gl_GlobalInvocationID.x;
    if (idx >= num_particles) return;

    Particle p = particles[idx];
    if ((p.flags & 1u) == 0u) return; // Skip dead particles

    // Accumulate forces
    vec3 force = gravity * p.mass;

    for (uint i = 0; i < num_attractors; i++) {
        force += compute_attractor_force(p, attractors[i]);
    }

    // Drag
    force -= p.velocity.xyz * drag;

    // Semi-implicit Euler integration
    vec3 new_vel = p.velocity.xyz + (force / p.mass) * dt;
    vec3 new_pos = p.position.xyz + new_vel * dt;

    // Age
    float new_age      = p.velocity.w + dt;
    float lifetime     = p.position.w;

    // Kill if expired
    if (new_age >= lifetime) {
        p.flags &= ~1u; // clear alive bit
        uint dead_idx = atomicAdd(dead_count, 1u);
        dead_indices[dead_idx] = idx;
    } else {
        p.position.xyz = new_pos;
        p.velocity.xyz = new_vel;
        p.velocity.w   = new_age;
    }

    particles[idx] = p;
}
"#;
Expand description

Particle integration compute shader (GLSL 4.30+).