
struct NebulaMaterial {
	nebula_size: f32,
	nebula_octaves: i32,
	dust_size: f32,
	dust_octaves: i32,
	seed: f32,
	pixels: f32,
	background_color: vec4<f32>,
	uv_correct: vec2<f32>
};

@group(1) @binding(0) 
var<uniform> material: NebulaMaterial;

@group(1) @binding(1)
var colorscheme: texture_2d<f32>;
@group(1) @binding(2)
var colorscheme_sampler: sampler;

fn rand(coord: vec2<f32>) -> f32 {
    var coord = coord;

    return fract(sin(dot(coord.xy, vec2(12.9898, 78.233))) * (15.5453 + material.seed));
}

fn noise(coord: vec2<f32>) -> f32 {
    var i = floor(coord);
    var f = fract(coord);

    var a = rand(i);
    var b = rand(i + vec2(1.0, 0.0));
    var c = rand(i + vec2(0.0, 1.0));
    var d = rand(i + vec2(1.0, 1.0));

    var cubic = f * f * (3.0 - 2.0 * f);

    return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y;
}

fn fbm(coord: vec2<f32>, octaves: i32) -> f32 {
    var coord = coord;
    var value = 0.0;
    var scale = 0.5;

    for (var i = 0; i < octaves ; i++) {
        value += noise(coord) * scale;
        coord *= 2.0;
        scale *= 0.5;
    }
    return value;
}

fn modulo(a: f32, b: f32) -> f32 {
    return a - b * floor(a / b);
}

fn dither(uv1: vec2<f32>, uv2: vec2<f32>) -> bool {
    return modulo(uv1.y + uv2.x, 2.0 / material.pixels) <= 1.0 / material.pixels;
}

fn circleNoise(uv: vec2<f32>) -> f32 {
    var uv = uv;

    var uv_y = floor(uv.y);
    uv.x += uv_y * .31;
    var f = fract(uv);
    var h = rand(vec2(floor(uv.x), floor(uv_y)));
    var m = (length(f - vec2<f32>(0.25 + (h * 0.5))));
    var r = h * 0.25;
    return smoothstep(0.0, r, m * 0.75);
}

fn rotate(v: vec2<f32>, angle: f32) -> vec2<f32> {
    var v = v;
    v -= vec2<f32>(0.5);
    v *= mat2x2<f32>(vec2<f32>(cos(angle), -sin(angle)), vec2<f32>(sin(angle), cos(angle)));
    v += vec2<f32>(0.5);
    return v;
}

fn cloud_alpha(uv: vec2<f32>, octaves: i32) -> f32 {
    var c_noise = 0.0;
	
	// more iterations for more turbulence
    var iters = 3;
    for (var i = 0; i < iters; i++) {
        c_noise += circleNoise(uv * 0.5 + vec2<f32>(f32(i + 1)) + vec2<f32>(-0.3, 0.0));
    }
    var fbm = fbm(uv + c_noise, octaves);

    return fbm;
}

#import bevy_pbr::mesh_vertex_output MeshVertexOutput



fn nebulae(
    in: MeshVertexOutput
) -> vec4<f32> {
    var num_colors = f32(textureDimensions(colorscheme).x) - 2.0;
	// pixelizing and dithering
    var uv = floor((in.uv) * material.pixels) / material.pixels;
	
	// distance from center
    var d = distance(uv, vec2(0.5)) * 0.4;

    uv *= material.uv_correct;
    var dith = dither(uv, in.uv);
	
	// noise for the inside of the nebulae
    var n = cloud_alpha(uv * material.nebula_size, material.nebula_octaves);
    var n2 = fbm(uv * material.nebula_size + vec2<f32>(1.0), material.nebula_octaves);
    var n_lerp = n2 * n;
    var n_dust = cloud_alpha(uv * material.nebula_size, material.nebula_octaves);
    var n_dust_lerp = n_dust * n_lerp;

	// apply dithering
    if dith {
        n_dust_lerp *= 0.95;
        n_lerp *= 0.95;
        d *= 0.98;
    }

	// slightly offset alpha values to create thin bands around the nebulae
    var a = step(n2, 0.1 + d);
    var a2 = step(n2, 0.115 + d);

	// choose colors
	#ifdef REDUCE_BACKGROUND
    n_dust_lerp = pow(n_dust_lerp, 1.2) * 0.7;
	#endif
    var col_value = 0.0;
    if a2 > a {
        col_value = floor(n_dust_lerp * num_colors * num_colors) / num_colors;
    } else {
        col_value = floor(n_dust_lerp * num_colors * 2.0) / num_colors;
    }

    var background_color = material.background_color;

	// apply colors
    var col = textureSample(colorscheme, colorscheme_sampler, vec2<f32>(col_value, 0.0)).rgb;
	// if (col_value < 0.1) {
	// 	col = background_color.rgb;
	// }

    return vec4(col, a2);
}

fn dust(
    in: MeshVertexOutput
) -> vec4<f32> {
    var num_colors = f32(textureDimensions(colorscheme).x);
	// pixelizing and dithering
    var uv = floor((in.uv) * material.pixels) / material.pixels * material.uv_correct;
    var dith = dither(uv, in.uv);
	
	// noise for the dust
	// the + vec2(x,y) is to create an offset in noise values
    var n_alpha = fbm(uv * ceil(vec2<f32>(material.dust_size) * 0.5) + vec2<f32>(2.0), material.dust_octaves);
    var n_dust = cloud_alpha(uv * material.dust_size, material.dust_octaves);
    var n_dust2 = fbm(uv * ceil(material.dust_size * 0.2) - vec2<f32>(2.0), material.dust_octaves);
    var n_dust_lerp = n_dust2 * n_dust;

	// apply dithering
    if dith {
        n_dust_lerp *= 0.95;
    }

	// choose alpha value
    var a_dust = step(n_alpha, n_dust_lerp * 1.8);
    n_dust_lerp = pow(n_dust_lerp, 3.2) * 56.0;
    if dith {
        n_dust_lerp *= 1.1;
    }
	
	// choose & apply colors
#ifdef REDUCE_BACKGROUND
    n_dust_lerp = pow(n_dust_lerp, 0.8) * 0.7;
#endif

    var col_value = floor(n_dust_lerp) / num_colors;

    var col = textureSample(colorscheme, colorscheme_sampler, vec2<f32>(col_value, 0.0)).rgb;

    return vec4<f32>(col, a_dust);
}

@fragment
fn fragment(
    in: MeshVertexOutput
) -> @location(0) vec4<f32> {

    var col = vec4<f32>(0.0);
    var neb = nebulae(in);
    var dus = dust(in);

    col = mix(col, dus, dus.a);
    col = mix(col, neb, neb.a);

    return col;
}
