#version 450

// v1 particle update: one SSBO, per-system dispatch.
// Particles store "remaining life" in pos_age.w and total lifetime in vel_life.w.

layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;

struct Particle
{
    vec4 pos_age;   // xyz = local position, w = remaining life (seconds)
    vec4 vel_life;  // xyz = local velocity, w = lifetime (seconds)
    vec4 color;     // rgba
    vec4 misc;      // x=size, y=seed, z=unused, w=flags
};

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

layout(push_constant) uniform Push
{
    uvec4 header;            // x=base, y=count, z=reset
    vec4  sim;               // x=dt, y=time, z=drag, w=gravity (m/s^2, pulls down -Y)
    vec4  origin_delta;      // xyz origin delta (local)
    vec4  emitter_pos_radius;// xyz emitter pos (local), w=spawn radius
    vec4  emitter_dir_cone;  // xyz emitter dir (local), w=cone angle radians (<=0 => sphere)
    vec4  ranges;            // x=minSpeed, y=maxSpeed, z=minLife, w=maxLife
    vec4  size_range;        // x=minSize, y=maxSize
    vec4  color;             // rgba
} pc;

uint hash_u32(uint x)
{
    x ^= x >> 16;
    x *= 0x7feb352du;
    x ^= x >> 15;
    x *= 0x846ca68bu;
    x ^= x >> 16;
    return x;
}

float rand01(inout uint state)
{
    state = hash_u32(state);
    // 0..1 (exclusive 1)
    return float(state) * (1.0 / 4294967296.0);
}

vec3 random_unit_vector(inout uint state)
{
    float z = rand01(state) * 2.0 - 1.0;
    float a = rand01(state) * 6.28318530718;
    float r = sqrt(max(0.0, 1.0 - z * z));
    return vec3(r * cos(a), z, r * sin(a));
}

vec3 random_in_sphere(inout uint state)
{
    vec3 dir = random_unit_vector(state);
    float u = rand01(state);
    float radius = pow(u, 1.0 / 3.0);
    return dir * radius;
}

vec3 random_in_cone(inout uint state, vec3 axis, float cone_angle)
{
    axis = normalize(axis);

    float cos_max = clamp(cos(cone_angle), -1.0, 1.0);
    float cos_t = mix(cos_max, 1.0, rand01(state));
    float sin_t = sqrt(max(0.0, 1.0 - cos_t * cos_t));

    float phi = rand01(state) * 6.28318530718;
    vec3 local_dir = vec3(cos(phi) * sin_t, sin(phi) * sin_t, cos_t);

    vec3 up = (abs(axis.y) < 0.999) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
    vec3 u = normalize(cross(up, axis));
    vec3 v = cross(axis, u);

    return u * local_dir.x + v * local_dir.y + axis * local_dir.z;
}

void respawn(uint idx)
{
    uint seed = idx ^ (hash_u32(floatBitsToUint(pc.sim.y)) * 1664525u) ^ 1013904223u;

    float life = mix(pc.ranges.z, pc.ranges.w, rand01(seed));
    life = max(life, 0.01);

    float speed = mix(pc.ranges.x, pc.ranges.y, rand01(seed));
    speed = max(speed, 0.0);

    float size = mix(pc.size_range.x, pc.size_range.y, rand01(seed));
    size = max(size, 0.001);

    vec3 spawn_pos = pc.emitter_pos_radius.xyz;
    float radius = max(pc.emitter_pos_radius.w, 0.0);
    if (radius > 0.0)
    {
        spawn_pos += random_in_sphere(seed) * radius;
    }

    vec3 dir;
    float cone = pc.emitter_dir_cone.w;
    if (cone > 0.0)
    {
        dir = random_in_cone(seed, pc.emitter_dir_cone.xyz, cone);
    }
    else
    {
        dir = random_unit_vector(seed);
    }

    Particle p;
    p.pos_age = vec4(spawn_pos, life);
    p.vel_life = vec4(dir * speed, life);
    p.color = pc.color;
    p.misc = vec4(size, rand01(seed), 0.0, 0.0);
    pool.particles[idx] = p;
}

void main()
{
    uint i = gl_GlobalInvocationID.x;
    if (i >= pc.header.y) return;

    uint idx = pc.header.x + i;

    Particle p = pool.particles[idx];

    // Keep particles stable under floating-origin recenters.
    p.pos_age.xyz -= pc.origin_delta.xyz;

    bool reset = (pc.header.z != 0u);
    bool dead = (p.pos_age.w <= 0.0) || (p.vel_life.w <= 0.0);

    if (reset || dead)
    {
        respawn(idx);
        return;
    }

    float dt = pc.sim.x;
    float drag = max(pc.sim.z, 0.0);
    float gravity = pc.sim.w;

    vec3 vel = p.vel_life.xyz;
    vel += vec3(0.0, -gravity, 0.0) * dt;
    vel *= exp(-drag * dt);

    p.pos_age.xyz += vel * dt;
    p.vel_life.xyz = vel;
    p.pos_age.w -= dt;

    pool.particles[idx] = p;
}

