uniform sampler2D noisetex;
uniform sampler2D colortex4;
#define noiseCloudHeightMap colortex4

#include "/lib/noise_common.glsl"
#include "/lib/noise_options.glsl"
#include "/lib/noise_render_scale.glsl"
#include "/lib/noise_clouds.glsl"

#if CLOUD_QUALITY > 0 && CLOUD_RESOLUTION == 0 && !defined(NETHER) && !defined(END)
const vec2 workGroupsRender = vec2(0.5, 0.5);
#else
// Other dimensions never sample the cloud image. Keep the program valid while
// reducing their dispatch from half a screen to one harmless workgroup.
const ivec3 workGroups = ivec3(1, 1, 1);
#endif
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;

uniform sampler2D depthtex1;
uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferModelViewInverse;
uniform mat4 gbufferModelView;
uniform vec3 cameraPosition;
uniform vec3 sunPosition;
uniform float rainStrength;
uniform float frameTimeCounter;
uniform int frameCounter;
uniform float viewWidth;
uniform float viewHeight;

layout(rgba16f) writeonly uniform image2D noise_cloud_img;

vec3 noiseCloudProjectAndDivide(mat4 projectionMatrix, vec3 position) {
	vec4 homogeneousPosition = projectionMatrix * vec4(position, 1.0);
	float safeW = abs(homogeneousPosition.w) > 1e-6
		? homogeneousPosition.w
		: (homogeneousPosition.w < 0.0 ? -1e-6 : 1e-6);
	return homogeneousPosition.xyz / safeW;
}

bool noiseCloudPixelCanSeeSky(vec2 logicalUv, ivec2 fullSize) {
	ivec2 sceneSize = noiseRenderViewportSize(fullSize);
	vec2 sourcePosition = logicalUv * vec2(sceneSize) - 0.5;
	ivec2 basePixel = ivec2(floor(sourcePosition));
	// One source-pixel halo covers the bilinear reconstruction footprint and
	// prevents a low-resolution terrain sample from punching a hole in clouds
	// immediately beside a silhouette.
	for (int y = -1; y <= 2; ++y) {
		for (int x = -1; x <= 2; ++x) {
			ivec2 tapPixel = clamp(basePixel + ivec2(x, y),
				ivec2(0), sceneSize - 1);
			if (texelFetch(depthtex1, tapPixel, 0).r >= 0.999999) return true;
		}
	}
	return false;
}

void main() {
	#if CLOUD_QUALITY == 0 || CLOUD_RESOLUTION != 0
	return;
	#else
	ivec2 cloudPixel = ivec2(gl_GlobalInvocationID.xy);
	ivec2 cloudSize = imageSize(noise_cloud_img);
	if (any(greaterThanEqual(cloudPixel, cloudSize))) return;
	#if defined(NETHER) || defined(END)
	imageStore(noise_cloud_img, cloudPixel, vec4(0.0));
	return;
	#endif

	ivec2 fullSize = ivec2(viewWidth, viewHeight);
	vec2 logicalUv = (vec2(cloudPixel) + 0.5) / vec2(cloudSize);
	if (!noiseCloudPixelCanSeeSky(logicalUv, fullSize)) {
		imageStore(noise_cloud_img, cloudPixel, vec4(0.0));
		return;
	}

	vec3 skyViewDirection = normalize(noiseCloudProjectAndDivide(
		gbufferProjectionInverse, vec3(logicalUv, 1.0) * 2.0 - 1.0));
	vec3 skyWorldDirection = normalize(mat3(gbufferModelViewInverse) * skyViewDirection);
	vec3 sunWorldDirection = normalize(mat3(gbufferModelViewInverse) * sunPosition);
	float skyDaylight = noiseDaylight(sunPosition, gbufferModelView);
	vec4 cloudLayer = noiseCloudLayer(skyWorldDirection, cameraPosition,
		sunWorldDirection, skyDaylight, rainStrength, frameTimeCounter,
		vec2(cloudPixel) + 0.5, frameCounter, false);
	// The reduced buffer must be filtered in premultiplied-alpha space.
	// Storing straight RGB made interpolation attenuate colour and opacity twice
	// at silhouettes, producing dark, blocky cloud edges.
	imageStore(noise_cloud_img, cloudPixel,
		vec4(cloudLayer.rgb * cloudLayer.a, cloudLayer.a));
	#endif
}
