struct TerrainProduceUniforms {
    continental_spline: array<vec4<f32>, 4>,
    erosion_spline: array<vec4<f32>, 4>,
    frequencies: vec4<f32>,
    warp_detail: vec4<f32>,
    erosion_params: vec4<f32>,
    seeds: vec4<u32>,
};

struct TerrainJob {
    data: vec4<i32>,
};

@group(0) @binding(0) var<uniform> uniforms: TerrainProduceUniforms;
@group(0) @binding(1) var<storage, read> jobs: array<TerrainJob>;
@group(0) @binding(2) var height_cache: texture_2d_array<f32>;
@group(0) @binding(3) var ambient_cache: texture_storage_2d_array<r32float, write>;

@compute @workgroup_size(8, 8)
fn produce_ambient(
    @builtin(workgroup_id) workgroup: vec3<u32>,
    @builtin(local_invocation_id) local: vec3<u32>,
) {
    let job = jobs[workgroup.z].data;
    let scale_level = u32(job.z);
    let layer = u32(job.w);
    let texel = vec2<i32>(job.xy) + vec2<i32>(workgroup.xy * 8u + local.xy);
    let texel_size = uniforms.warp_detail.z * f32(1u << scale_level);

    let center = terrain_cache_load(height_cache, layer, texel);

    var occlusion = 0.0;
    var direction_table = array<vec2<i32>, 8>(
        vec2<i32>(1, 0),
        vec2<i32>(-1, 0),
        vec2<i32>(0, 1),
        vec2<i32>(0, -1),
        vec2<i32>(1, 1),
        vec2<i32>(-1, 1),
        vec2<i32>(1, -1),
        vec2<i32>(-1, -1),
    );
    for (var direction_index = 0; direction_index < 8; direction_index++) {
        let direction = direction_table[direction_index];
        var horizon = 0.0;
        for (var tap = 0u; tap < 4u; tap++) {
            let distance_texels = i32(2u << tap);
            let sample = terrain_cache_load(
                height_cache,
                layer,
                texel + direction * distance_texels,
            );
            let slope = (sample - center) / (f32(distance_texels) * texel_size * 1.4142);
            horizon = max(horizon, slope);
        }
        occlusion += clamp(horizon, 0.0, 2.0) * 0.5;
    }
    let ambient = 1.0 - 0.5 * (occlusion / 8.0);

    let wrapped = vec2<i32>(
        ((texel.x % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
        ((texel.y % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
    );
    textureStore(ambient_cache, wrapped, i32(layer), vec4<f32>(ambient, 0.0, 0.0, 0.0));
}
