#version 450

// Simple voxel advection + noise injection for volumetric clouds.
// This is not a full fluid sim, but provides "fluid-like" evolving density in a voxel grid.

layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;

layout(std430, set = 0, binding = 0) readonly buffer DensityIn
{
    float density[];
} vox_in;

layout(std430, set = 0, binding = 1) buffer DensityOut
{
    float density[];
} vox_out;

layout(push_constant) uniform Push
{
    vec4 wind_dt;          // xyz windVelocityLocal (units/sec), w dt_sec
    vec4 volume_size_time; // xyz volume size (units), w time_sec
    vec4 sim_params;       // x dissipation (1/sec), y noiseStrength, z noiseScale, w noiseSpeed
    vec4 emitter_params;   // xyz emitterUVW, w emitterRadius
    ivec4 misc;            // x gridResolution, y volumeType (0=cloud,1=smoke,2=flame)
} pc;

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

float hash3_to_unit_float(ivec3 p)
{
    uint h = 0u;
    h ^= hash_u32(uint(p.x) * 73856093u);
    h ^= hash_u32(uint(p.y) * 19349663u);
    h ^= hash_u32(uint(p.z) * 83492791u);
    return float(h & 0x00FFFFFFu) / float(0x01000000u);
}

float smoothstep01(float x)
{
    x = clamp(x, 0.0, 1.0);
    return x * x * (3.0 - 2.0 * x);
}

float value_noise3(vec3 p)
{
    ivec3 i0 = ivec3(floor(p));
    ivec3 i1 = i0 + ivec3(1);

    vec3 f = fract(p);
    f = vec3(smoothstep01(f.x), smoothstep01(f.y), smoothstep01(f.z));

    float c000 = hash3_to_unit_float(ivec3(i0.x, i0.y, i0.z));
    float c100 = hash3_to_unit_float(ivec3(i1.x, i0.y, i0.z));
    float c010 = hash3_to_unit_float(ivec3(i0.x, i1.y, i0.z));
    float c110 = hash3_to_unit_float(ivec3(i1.x, i1.y, i0.z));
    float c001 = hash3_to_unit_float(ivec3(i0.x, i0.y, i1.z));
    float c101 = hash3_to_unit_float(ivec3(i1.x, i0.y, i1.z));
    float c011 = hash3_to_unit_float(ivec3(i0.x, i1.y, i1.z));
    float c111 = hash3_to_unit_float(ivec3(i1.x, i1.y, i1.z));

    float x00 = mix(c000, c100, f.x);
    float x10 = mix(c010, c110, f.x);
    float x01 = mix(c001, c101, f.x);
    float x11 = mix(c011, c111, f.x);

    float y0 = mix(x00, x10, f.y);
    float y1 = mix(x01, x11, f.y);

    return mix(y0, y1, f.z);
}

float fbm3(vec3 p)
{
    float sum = 0.0;
    float amp = 0.55;
    float freq = 1.0;
    for (int i = 0; i < 4; ++i)
    {
        sum += amp * value_noise3(p * freq);
        freq *= 2.02;
        amp *= 0.5;
    }
    return clamp(sum, 0.0, 1.0);
}

int idx3(ivec3 c, int res)
{
    return c.x + c.y * res + c.z * res * res;
}

float sample_density_trilinear(vec3 uvw, int res)
{
    uvw = clamp(uvw, vec3(0.0), vec3(1.0));

    float fres = float(res);
    vec3 g = uvw * (fres - 1.0);

    ivec3 base = ivec3(floor(g));
    base = clamp(base, ivec3(0), ivec3(res - 1));
    vec3 f = fract(g);

    ivec3 b1 = min(base + ivec3(1), ivec3(res - 1));

    float d000 = vox_in.density[idx3(ivec3(base.x, base.y, base.z), res)];
    float d100 = vox_in.density[idx3(ivec3(b1.x,   base.y, base.z), res)];
    float d010 = vox_in.density[idx3(ivec3(base.x, b1.y,   base.z), res)];
    float d110 = vox_in.density[idx3(ivec3(b1.x,   b1.y,   base.z), res)];

    float d001 = vox_in.density[idx3(ivec3(base.x, base.y, b1.z), res)];
    float d101 = vox_in.density[idx3(ivec3(b1.x,   base.y, b1.z), res)];
    float d011 = vox_in.density[idx3(ivec3(base.x, b1.y,   b1.z), res)];
    float d111 = vox_in.density[idx3(ivec3(b1.x,   b1.y,   b1.z), res)];

    float x00 = mix(d000, d100, f.x);
    float x10 = mix(d010, d110, f.x);
    float x01 = mix(d001, d101, f.x);
    float x11 = mix(d011, d111, f.x);

    float y0 = mix(x00, x10, f.y);
    float y1 = mix(x01, x11, f.y);

    return mix(y0, y1, f.z);
}

void main()
{
    int res = max(pc.misc.x, 1);
    int vol_type = pc.misc.y;

    ivec3 c = ivec3(gl_GlobalInvocationID.xyz);
    if (c.x >= res || c.y >= res || c.z >= res)
    {
        return;
    }

    // Voxel center in [0,1].
    vec3 uvw = (vec3(c) + vec3(0.5)) / float(res);

    float dt = max(pc.wind_dt.w, 0.0);

    vec3 volSize = max(pc.volume_size_time.xyz, vec3(0.001));
    vec3 wind_uv = pc.wind_dt.xyz / volSize; // normalized per-second

    // Semi-Lagrangian advection: backtrace.
    vec3 back = uvw - wind_uv * dt;
    if (vol_type == 0)
    {
        back.xz = fract(back.xz);         // wrap XZ for continuous motion (clouds)
        back.y = clamp(back.y, 0.0, 1.0); // clamp Y
    }
    else
    {
        back = clamp(back, vec3(0.0), vec3(1.0)); // clamp for localized effects (smoke/flame)
    }

    float advected = sample_density_trilinear(back, res);

    // Dissipation.
    float dissipation = max(pc.sim_params.x, 0.0);
    advected *= exp(-dissipation * dt);

    // Inject new density from procedural noise to keep volume evolving.
    float time = pc.volume_size_time.w;
    float noise_scale = max(pc.sim_params.z, 0.001);
    float noise_speed = pc.sim_params.w;
    float n = fbm3(uvw * noise_scale + vec3(0.0, time * noise_speed, 0.0));
    float injected = 0.0;

    if (vol_type == 0)
    {
        // Clouds: broad slab with continuous XZ wrapping.
        injected = smoothstep(0.55, 0.80, n);

        // Height shaping (keep density within a slab).
        float low = smoothstep(0.00, 0.18, uvw.y);
        float high = 1.0 - smoothstep(0.78, 1.00, uvw.y);
        injected *= clamp(low * high, 0.0, 1.0);
    }
    else
    {
        // Smoke/flame: inject near an emitter in UVW space.
        vec3 e = clamp(pc.emitter_params.xyz, vec3(0.0), vec3(1.0));
        float r = max(pc.emitter_params.w, 1e-4);

        vec3 dpos = uvw - e;
        dpos.y *= 1.5; // squash vertically for a more column-like source
        float dist = length(dpos);
        float shape = 1.0 - smoothstep(r, r * 1.25, dist);

        if (vol_type == 1)
        {
            // Smoke: softer noise threshold.
            injected = smoothstep(0.45, 0.75, n) * shape;
        }
        else
        {
            // Flame: spikier + stronger flicker.
            float f = smoothstep(0.35, 0.90, n);
            injected = (f * f) * shape;
        }
    }

    float rate = clamp(pc.sim_params.y * dt, 0.0, 1.0);
    float out_d = advected;
    if (vol_type == 2)
    {
        // Flames flicker: blend toward injected field (avoid accumulating a "fog").
        out_d = mix(advected, injected, rate);
    }
    else
    {
        out_d = mix(advected, max(advected, injected), rate);
    }
    out_d = clamp(out_d, 0.0, 1.0);

    vox_out.density[idx3(c, res)] = out_d;
}

