#version 430

#include <shaders/materials/commons.glsl>
#include <shaders/materials/commons_rand.glsl>
#include <shaders/materials/raytrace_buffers.glsl>
#include <shaders/materials/raytrace_commons.glsl>
#include <shaders/materials/commons_instancing_buffers.h>

layout (local_size_x = 32, local_size_y = 1) in;

struct InstancingGeneratorParams
{
	mat4 mat_projection;
	mat4 mat_projection_inv;
	mat4 mat_transform;
	vec3 origin;
	int seed;
	int buffer_capacity;
	float surface_distance;
	int modulate_size_by_mask;
	int only_on_mask_edge;

	float range_min_percent;
	float range_max_percent;
};

layout(std140, row_major) uniform InstancingGeneratorParamsBuffer {
	InstancingGeneratorParams instancing_generator_params;
};

uniform sampler2D sDepth;
uniform sampler2D sMask;

float prt_rand(float n){return fract(sin(n) * 43758.5453123);}

float prt_rand(vec2 n) { 
	return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}

bool is_depth_in_emission_range(float depth)
{
	if (depth < instancing_generator_params.range_min_percent)
		return false;

	if (depth > instancing_generator_params.range_max_percent)
		return false;

	return true;
}

void main()
{
	uint id = gl_GlobalInvocationID.x;

	if (id >= instancing_generator_params.buffer_capacity)
		return;

#if 1

	// randomize position on the projection plane

	float fid = float(id);
	float fid_seed = (fid * 0.01317 + instancing_generator_params.seed) * 0.001132;
	float shadowmap_size = 1024.0; // TODO: Export
	float shadowmap_size_half = shadowmap_size * 0.5;

	for(int retry = 0; retry < 4; retry++)
	{
		// randomize position on the projection plane
		vec4 projection_pos;
		projection_pos.x = random(fid_seed);
		projection_pos.y = random(fid_seed + 32.1317);
		projection_pos.z = 1.0;
		projection_pos.w = 1.0;

		float depth = texelFetch(sDepth, ivec2(projection_pos.xy * shadowmap_size), 0).r;
		projection_pos.z = depth;
	
		if (is_depth_in_emission_range(depth) == false)
			continue;

		float mask = 0.0;

		if (instancing_generator_params.only_on_mask_edge == 0)
		{
			//mask = clamp(texelFetch(sMask, ivec2(projection_pos.xy * shadowmap_size), 0).r, 0.0, 1.0);
			mask = clamp(texture(sMask, projection_pos.xy).r, 0.0, 1.0);
			// spawn only on fully covered parts
			if (mask < 0.995)
			{
			//	continue;
			}
		}
		else
		{
			mask = clamp(texture(sMask, projection_pos.xy).r, 0.0, 1.0);
			// spawn only on edges
			if (mask < 0.01) // || mask > 0.9995)
			{
				continue;
			}
		}

		// normal basis
		vec3 b = (instancing_generator_params.mat_projection_inv * vec4(0.5, 0.5, 0.5, 0.0)).xyz;
		
		b.x = instancing_generator_params.mat_projection_inv[0].x / shadowmap_size_half;
		b.y = instancing_generator_params.mat_projection_inv[1].y / shadowmap_size_half;
		b.z = instancing_generator_params.mat_projection_inv[2].z;

		// get depth gradient so we can orientate instance properly

		depth = texture(sDepth, projection_pos.xy).r;
		float dx = texture(sDepth, projection_pos.xy + vec2(1.0 / shadowmap_size_half, 0.0)).r;
		float dy = texture(sDepth, projection_pos.xy + vec2(0.0, 1.0 / shadowmap_size_half)).r;

		if (is_depth_in_emission_range(dx) == false || is_depth_in_emission_range(dy) == false)
			continue;

		dx = dx - depth;
		dy = dy - depth;

		#if 0
		vec3 ndx = normalize(vec3(b.x, 0.0, dx * b.z)); // NOTE: We need proper gradients but this is a kludge!!
		vec3 ndy = normalize(vec3(0.0, b.y, dy * b.z));
		vec3 ndz = cross(ndy, ndx);
		#else
		vec3 ndy = -normalize(vec3(b.x, 0.0, dx * b.z)); // NOTE: We need proper gradients but this is a kludge!!
		vec3 ndx = normalize(vec3(0.0, b.y, dy * b.z));
		vec3 ndz = cross(ndy, ndx);
		ndx = cross(ndz, ndy); 
		#endif

		projection_pos.xyz += ndz * instancing_generator_params.surface_distance;

		// transform to the light coords
		projection_pos = instancing_generator_params.mat_transform * instancing_generator_params.mat_projection_inv * projection_pos;

		ndx = (instancing_generator_params.mat_transform * vec4(ndx, 0.0)).xyz;
		ndy = (instancing_generator_params.mat_transform * vec4(ndy, 0.0)).xyz;
		ndz = (instancing_generator_params.mat_transform * vec4(ndz, 0.0)).xyz;
		ndx = -normalize(ndx);
		ndy = -normalize(ndy);
		ndz = -normalize(ndz);

		if (instancing_generator_params.modulate_size_by_mask != 0)
		{
			ndx *= mask;
			ndy *= mask;
			ndz *= mask;
		}

		// proper instance
		instance_transform[id * 3 + 0] = vec4(ndx.x, ndy.x, ndz.x, projection_pos.x);
		instance_transform[id * 3 + 1] = vec4(ndx.y, ndy.y, ndz.y, projection_pos.y);
		instance_transform[id * 3 + 2] = vec4(ndx.z, ndy.z, ndz.z, projection_pos.z);

		vec4 c = vec4(1.0);
		instance_color[id * 2 + 0] = packHalf2x16(c.rg);
		instance_color[id * 2 + 1] = packHalf2x16(c.ba);

		return;
	}

	// emit null instance
	instance_transform[id * 3 + 0] = vec4(0.0);
	instance_transform[id * 3 + 1] = vec4(0.0);
	instance_transform[id * 3 + 2] = vec4(0.0);
	
#else

	instance_transform[id * 3 + 0] = vec4(1.0 + float(id) * 0.1, 0.0, 0.0, 0.0);
	instance_transform[id * 3 + 1] = vec4(0.0, 1.0, 0.0, 0.0);
	instance_transform[id * 3 + 2] = vec4(0.0, 0.0, 1.0, 0.0);
	
#endif


}

