shader_type canvas_item;

// Procedural starfield: draws directly over the whole background rect in one
// pass, so there's no repeating tile and therefore no seams (unlike the old
// approach of tiling a single non-seamless skybox render). Star size/glow use
// smoothstep, not texture sampling, so they stay pixel-crisp at any zoom
// instead of getting minified/blurred by texture filtering.

uniform vec4 sky_color: source_color = vec4(0.008, 0.01, 0.025, 1.0);
uniform vec4 nebula_color: source_color = vec4(0.05, 0.08, 0.16, 1.0);
uniform float nebula_scale = 0.0025;
uniform float nebula_strength = 0.5;

uniform float cell_size_far = 56.0;
uniform float density_far = 0.10;
uniform float size_far = 0.7;
uniform float brightness_far = 0.35;

uniform float cell_size_mid = 100.0;
uniform float density_mid = 0.14;
uniform float size_mid = 1.0;
uniform float brightness_mid = 0.5;

uniform float cell_size_near = 200.0;
uniform float density_near = 0.18;
uniform float size_near = 1.4;
uniform float brightness_near = 0.7;

varying vec2 world_pos;

void vertex() {
	world_pos = VERTEX;
}

float hash11(float p) {
	p = fract(p * 0.1031);
	p *= p + 33.33;
	p *= p + p;
	return fract(p);
}

float hash21(vec2 p) {
	vec3 p3 = fract(vec3(p.xyx) * 0.1031);
	p3 += dot(p3, p3.yzx + 33.33);
	return fract((p3.x + p3.y) * p3.z);
}

vec2 hash22(vec2 p) {
	return vec2(hash21(p), hash21(p + 19.19));
}

float value_noise(vec2 p) {
	vec2 i = floor(p);
	vec2 f = fract(p);
	float a = hash21(i);
	float b = hash21(i + vec2(1.0, 0.0));
	float c = hash21(i + vec2(0.0, 1.0));
	float d = hash21(i + vec2(1.0, 1.0));
	vec2 u = f * f * (3.0 - 2.0 * f);
	return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

float fbm(vec2 p) {
	float value = 0.0;
	float amp = 0.5;
	for (int i = 0; i < 4; i++) {
		value += amp * value_noise(p);
		p *= 2.0;
		amp *= 0.5;
	}
	return value;
}

// One depth layer of stars on an independent grid, so far/mid/near layers
// never line up with each other.
vec3 star_layer(vec2 pos, float cell_size, float density, float size, float brightness) {
	vec2 cell = floor(pos / cell_size);
	float roll = hash21(cell);
	float has_star = step(1.0 - density, roll);

	vec2 jitter = hash22(cell + 0.5);
	vec2 star_pos = (cell + jitter) * cell_size;
	float d = length(pos - star_pos);

	float core = smoothstep(size, 0.0, d);
	float halo = smoothstep(size * 3.5, 0.0, d) * 0.15;
	float glow = (core + halo) * has_star * brightness;

	float tint_roll = hash21(cell + 7.7);
	vec3 tint = mix(vec3(0.75, 0.82, 1.0), vec3(1.0, 0.92, 0.75), step(0.5, tint_roll));
	tint = mix(vec3(1.0), tint, 0.5);

	return tint * glow;
}

void fragment() {
	vec2 pos = world_pos;

	float nebula = fbm(pos * nebula_scale);
	vec3 color = mix(sky_color.rgb, nebula_color.rgb, clamp(nebula * nebula_strength, 0.0, 1.0));

	vec3 stars = vec3(0.0);
	stars += star_layer(pos, cell_size_far, density_far, size_far, brightness_far);
	stars += star_layer(pos + vec2(500.0, 0.0), cell_size_mid, density_mid, size_mid, brightness_mid);
	stars += star_layer(pos + vec2(0.0, 900.0), cell_size_near, density_near, size_near, brightness_near);

	COLOR = vec4(color + stars, 1.0);
}
