#version 430

layout(triangles) in;
layout(triangle_strip, max_vertices = 0) out;

#include <shaders/commons_hlsl.glsl>
#include <shaders/materials/noise/noise3d.glsl>
#include <shaders/materials/commons_instancing_buffers.h>

#include <shaders/materials/raytrace_buffers.glsl>

layout(std430) buffer OutVertexBufferRaytrace {
	float data[];
} out_vtx_data_raytrace;

layout(std430) buffer OutVertexBufferVoxelize {
	float data[];
} out_vtx_data_voxelize;

struct TransformGeometryParams
{
	uint index; // which entry to setup in the TransformedDataLocation buffer
	uint voxelize;
	uint raytrace;
};


layout(std140, row_major) uniform TransformGeometryParamsBuffer {
	TransformGeometryParams transform_geometry_params;
};

uniform int  material_index;

uniform int out_vtx_data_coord_stride;
uniform int out_vtx_data_normal_stride;
uniform int out_vtx_data_uv0_stride;
uniform int out_vtx_data_material_stride;
uniform int out_vtx_data_coord_offset;
uniform int out_vtx_data_normal_offset;
uniform int out_vtx_data_uv0_offset;
uniform int out_vtx_data_material_offset;

uniform int out_raytrace_faces_base;
uniform int out_voxelize_faces_base;

uniform int transform_normals = 0;
uniform int export_raytrace = 0;
uniform int export_voxelize = 0;

uniform int faces_per_instance = 0;

// This will never be output, still need to provide linking

out Vertex
{
	vec3 vCoords;
	vec3 vNorm;
	vec3 vWorldNorm;
	vec3 vLocalPos;
	vec3 vWorldPos;
	vec4 vColor;
	vec2 vUV0;
} vtx_output;

in Vertex
{
	vec3 vCoords;
	vec3 vNorm;
	vec3 vWorldNorm;
	vec3 vLocalPos;
	vec3 vWorldPos;
	vec4 vColor;
	vec2 vUV0;
} vtx_inputs[];

in uint instanceID[];

// raytracer export

void put_coords_raytrace(uint idx, vec3 p)
{
	uint coord_offset = out_vtx_data_coord_offset + idx * out_vtx_data_coord_stride;
	out_vtx_data_raytrace.data[coord_offset + 0] = p.x;
	out_vtx_data_raytrace.data[coord_offset + 1] = p.y;
	out_vtx_data_raytrace.data[coord_offset + 2] = p.z;
}

void put_material_raytrace(uint face_idx, float material)
{
	uint material_offset = out_vtx_data_material_offset + face_idx * out_vtx_data_material_stride;
	out_vtx_data_raytrace.data[material_offset] = material;
}

// NOTE: Needs to match rt_get_vertex_normal() in raytrace_commons.glsl 
void put_normal_raytrace(uint idx, vec3 p)
{
	uint normal_offset = out_vtx_data_normal_offset + idx * out_vtx_data_normal_stride;
	//out_vtx_data_raytrace.data[normal_offset + 0] = p.x;
	//out_vtx_data_raytrace.data[normal_offset + 1] = p.y;
	//out_vtx_data_raytrace.data[normal_offset + 2] = p.z;
	uint n1 = packSnorm2x16(p.xy);
	uint n2 = packSnorm2x16(vec2(p.z, 0.0));
	out_vtx_data_raytrace.data[normal_offset + 0] = asfloat(n1);
	out_vtx_data_raytrace.data[normal_offset + 1] = asfloat(n2);
}

void put_uv0_raytrace(uint idx, vec2 p)
{
	uint uv0_offset = out_vtx_data_uv0_offset + idx * out_vtx_data_uv0_stride;
	out_vtx_data_raytrace.data[uv0_offset + 0] = p.x;
	out_vtx_data_raytrace.data[uv0_offset + 1] = p.y;
}

// voxelizer export

void put_coords_voxelize(uint idx, vec3 p)
{
	uint coord_offset = out_vtx_data_coord_offset + idx * out_vtx_data_coord_stride;
	out_vtx_data_voxelize.data[coord_offset + 0] = p.x;
	out_vtx_data_voxelize.data[coord_offset + 1] = p.y;
	out_vtx_data_voxelize.data[coord_offset + 2] = p.z;
}

void put_material_voxelize(uint face_idx, float material)
{
	uint material_offset = out_vtx_data_material_offset + face_idx * out_vtx_data_material_stride;
	out_vtx_data_voxelize.data[material_offset] = material;
}

void put_normal_voxelize(uint idx, vec3 p)
{
	uint normal_offset = out_vtx_data_normal_offset + idx * out_vtx_data_normal_stride;
	out_vtx_data_voxelize.data[normal_offset + 0] = p.x;
	out_vtx_data_voxelize.data[normal_offset + 1] = p.y;
	out_vtx_data_voxelize.data[normal_offset + 2] = p.z;
}

void put_uv0_voxelize(uint idx, vec2 p)
{
	uint uv0_offset = out_vtx_data_uv0_offset + idx * out_vtx_data_uv0_stride;
	out_vtx_data_voxelize.data[uv0_offset + 0] = p.x;
	out_vtx_data_voxelize.data[uv0_offset + 1] = p.y;
}

in int gl_PrimitiveIDIn;

void main()
{
	// NOTE: Either there is a bug, i'm doing something wrong or this is how it should work...
	// PrimitiveID restarts per instance
	uint face_idx = uint(gl_PrimitiveIDIn)+instanceID[0] * faces_per_instance;

	// bookkeeping

	uint prim_idx = transform_geometry_params.index;
	uint out_raytrace_faces_idx = 0;
	uint out_voxelize_faces_idx = 0;

	if (gl_PrimitiveIDIn == 0)
	{
		transformed_data_location[prim_idx].idx = prim_idx;
		transformed_data_location[prim_idx].material_idx = material_index;
	}

	if (prim_idx == 0)
	{
		if (export_raytrace != 0)
			atomicMax(transformed_data_location[prim_idx].rt_last_face_idx, face_idx);
		if (export_voxelize != 0)
			atomicMax(transformed_data_location[prim_idx].vx_last_face_idx, face_idx);

		out_raytrace_faces_idx = face_idx;
		out_voxelize_faces_idx = face_idx;
	}
	else // need to update even if we don't export. don't have to do it for the first face as buffer is zeroed on the start of frame
	{
		if (export_raytrace != 0)
			atomicMax(transformed_data_location[prim_idx].rt_last_face_idx, transformed_data_location[prim_idx - 1].rt_last_face_idx + face_idx);
		else
			transformed_data_location[prim_idx].rt_last_face_idx = transformed_data_location[prim_idx - 1].rt_last_face_idx;

		if (export_voxelize != 0)
			atomicMax(transformed_data_location[prim_idx].vx_last_face_idx, transformed_data_location[prim_idx - 1].vx_last_face_idx + face_idx);
		else
			transformed_data_location[prim_idx].vx_last_face_idx = transformed_data_location[prim_idx - 1].vx_last_face_idx;

		out_raytrace_faces_idx = transformed_data_location[prim_idx - 1].rt_last_face_idx + face_idx;
		out_voxelize_faces_idx = transformed_data_location[prim_idx - 1].vx_last_face_idx + face_idx;
	}

	// output data

	vec3 p0 = vtx_inputs[0].vWorldPos.xyz;
	vec3 p1 = vtx_inputs[1].vWorldPos.xyz;
	vec3 p2 = vtx_inputs[2].vWorldPos.xyz;

	if (export_raytrace != 0)
	{
		put_coords_raytrace(out_raytrace_faces_idx * 3 + 0, p0);
		put_coords_raytrace(out_raytrace_faces_idx * 3 + 1, p1);
		put_coords_raytrace(out_raytrace_faces_idx * 3 + 2, p2);
		put_material_raytrace(out_raytrace_faces_idx, material_index);
	}

	if (export_voxelize != 0)
	{
		put_coords_voxelize(out_voxelize_faces_idx * 3 + 0, p0);
		put_coords_voxelize(out_voxelize_faces_idx * 3 + 1, p1);
		put_coords_voxelize(out_voxelize_faces_idx * 3 + 2, p2);
		put_material_voxelize(out_voxelize_faces_idx, material_index);
	}

	vec3 n0 = vtx_inputs[0].vWorldNorm.xyz;
	vec3 n1 = vtx_inputs[1].vWorldNorm.xyz;
	vec3 n2 = vtx_inputs[2].vWorldNorm.xyz;

	vec2 uv00 = vtx_inputs[0].vUV0;
	vec2 uv01 = vtx_inputs[1].vUV0;
	vec2 uv02 = vtx_inputs[2].vUV0;

	if (transform_normals == 0)
	{
		vec3 n = cross(p1 - p0, p2 - p0);
		n = normalize(n);
		n0 = n;
		n1 = n;
		n2 = n;
	}
	else
	{
		n0 = normalize(n0);
		n1 = normalize(n1);
		n2 = normalize(n2);
	}

	if (export_raytrace != 0)
	{
		put_normal_raytrace(out_raytrace_faces_idx * 3 + 0, n0);
		put_normal_raytrace(out_raytrace_faces_idx * 3 + 1, n1);
		put_normal_raytrace(out_raytrace_faces_idx * 3 + 2, n2);

		put_uv0_raytrace(out_raytrace_faces_idx * 3 + 0, uv00);
		put_uv0_raytrace(out_raytrace_faces_idx * 3 + 1, uv01);
		put_uv0_raytrace(out_raytrace_faces_idx * 3 + 2, uv02);
	}

	if (export_voxelize != 0)
	{
		put_normal_voxelize(out_voxelize_faces_idx * 3 + 0, n0);
		put_normal_voxelize(out_voxelize_faces_idx * 3 + 1, n1);
		put_normal_voxelize(out_voxelize_faces_idx * 3 + 2, n2);

		put_uv0_voxelize(out_voxelize_faces_idx * 3 + 0, uv00);
		put_uv0_voxelize(out_voxelize_faces_idx * 3 + 1, uv01);
		put_uv0_voxelize(out_voxelize_faces_idx * 3 + 2, uv02);
	}
}


