shader_type spatial;
render_mode blend_mix, cull_back, depth_draw_opaque;

global uniform float global_snow_start_time = -1.0;
global uniform float global_snow_accumulation_speed = 0.005;
global uniform float global_snow_melt_time = -1.0;
global uniform float global_snow_melt_speed = 0.1;
global uniform float global_snow_amount = 0.0;
global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0);

uniform float max_height : hint_range(0.02, 1.0) = 0.2;
uniform float half_width : hint_range(0.01, 20.0) = 1.0;
uniform float half_depth : hint_range(0.01, 20.0) = 1.0;
uniform float noise_scale : hint_range(0.05, 2.0) = 0.35;
uniform float noise_strength : hint_range(0.0, 0.4) = 0.18;
uniform float edge_rounding : hint_range(0.0, 0.3) = 0.12;
uniform float side_shade : hint_range(0.2, 1.0) = 0.82;
uniform float sparkle_strength : hint_range(0.0, 0.1) = 0.03;

varying float v_snow_amount;
varying float v_noise;

float hash(vec2 p) {
	return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}

float value_noise(vec2 p) {
	vec2 i = floor(p);
	vec2 f = fract(p);
	vec2 u = f * f * (3.0 - 2.0 * f);

	float a = hash(i);
	float b = hash(i + vec2(1.0, 0.0));
	float c = hash(i + vec2(0.0, 1.0));
	float d = hash(i + vec2(1.0, 1.0));

	return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

float fbm(vec2 p) {
	float value = 0.0;
	float amplitude = 0.5;
	float frequency = 1.0;

	for (int i = 0; i < 4; i++) {
		value += value_noise(p * frequency) * amplitude;
		frequency *= 2.0;
		amplitude *= 0.5;
	}

	return clamp(value, 0.0, 1.0);
}

float get_snow_amount() {
	float snow_amount = clamp(global_snow_amount, 0.0, 1.0);
	if (global_snow_start_time >= 0.0) {
		float timed_amount = clamp(
			(TIME - global_snow_start_time) * global_snow_accumulation_speed,
			0.0,
			1.0
		);
		snow_amount = max(snow_amount, timed_amount);
	}
	if (global_snow_melt_time >= 0.0) {
		float melt = clamp((TIME - global_snow_melt_time) * global_snow_melt_speed, 0.0, 1.0);
		snow_amount = min(snow_amount, 1.0 - melt);
	}
	return snow_amount;
}

void vertex() {
	vec3 base_vertex = VERTEX;
	vec3 world_pos = (MODEL_MATRIX * vec4(base_vertex, 1.0)).xyz;
	float snow_amount = get_snow_amount();
	float noise_val = fbm(world_pos.xz * noise_scale);
	float thickness = max_height * snow_amount * mix(
		1.0 - noise_strength,
		1.0 + noise_strength,
		noise_val
	);
	float footprint_growth = smoothstep(0.0, 0.35, snow_amount);

	float from_bottom = clamp((base_vertex.y + max_height * 0.5) / max_height, 0.0, 1.0);
	VERTEX.y = -max_height * 0.5 + thickness * from_bottom;
	VERTEX.x *= mix(0.92, 1.0, footprint_growth);
	VERTEX.z *= mix(0.94, 1.0, footprint_growth);

	float edge_x = abs(base_vertex.x) / max(half_width, 0.001);
	float edge_z = abs(base_vertex.z) / max(half_depth, 0.001);
	float edge_mask = smoothstep(0.55, 1.0, max(edge_x, edge_z));
	float top_mask = smoothstep(0.6, 1.0, from_bottom) * snow_amount;
	float round_factor = edge_rounding * edge_mask * top_mask;
	VERTEX.x *= 1.0 - round_factor;
	VERTEX.z *= 1.0 - round_factor;

	v_snow_amount = snow_amount;
	v_noise = noise_val;
}

void fragment() {
	if (v_snow_amount <= 0.001) {
		ALPHA = 0.0;
		ALBEDO = global_snow_color.rgb;
		ROUGHNESS = 1.0;
		SPECULAR = 0.0;
	} else {
		float facing_up = clamp(NORMAL.y, 0.0, 1.0);
		float shade = mix(-sparkle_strength, sparkle_strength, v_noise);
		vec3 snow_albedo = clamp(global_snow_color.rgb + vec3(shade), 0.0, 1.0);

		ALBEDO = mix(snow_albedo * side_shade, snow_albedo, facing_up);
		ROUGHNESS = mix(0.95, 0.72, facing_up);
		SPECULAR = 0.18;
		ALPHA = smoothstep(0.0, 0.28, v_snow_amount);
	}
}
