
#ifndef VERTEX_MODIFIER_TEXTURE_DISPLACE_H
#define VERTEX_MODIFIER_TEXTURE_DISPLACE_H

#include <shaders/materials/commons.glsl>
#include <shaders/materials/noise/noise3Dgrad.glsl>
#include <shaders/materials/commons_curves.glsl>

const int PLANE_YZ                = 0;
const int PLANE_XZ                = 1;
const int PLANE_XY                = 2;

struct VertexModifierTextureDisplaceParams
{
	mat4   transform_world_to_local;
	mat4   transform_local_to_world;
	mat4   transform_local;
	mat4   transform_local_inv;
	mat4   transform_parent;
	mat4   transform_parent_inv;

	vec3   value_scale;
	float  value_base;
	vec3   scale;
	int    falloff_inverse;
	vec3   position;
	int    displacement_mode;
	vec3   radius;
	
	int    value_absolute;
	int    falloff_curve_idx;
	int    material_id;
	int    radial;
	int    smooth_displacement;
	int    mapping_plane;			// 0 - YZ, 1 - XZ, 2 - XY
	int    _pad0;
	int    _pad1;
	int    _pad2;

	TextureResourceParams source_txt_params;
};

#define KERNEL_SIZE 5
#if KERNEL_SIZE == 5
const float KERNEL[5] = 
{
	1.0/16.0, 1.0/4.0, 3.0/8.0, 1.0/4.0, 1.0/16.0
};

const int KERNEL_POS[5] = 
{
	-2, -1, 0, 1, 2
};

#endif

#if KERNEL_SIZE == 3
const float KERNEL[3] = 
{
	 0.27901, 0.44198, 0.27901
};

const int KERNEL_POS[3] = 
{
	-1, 0, 1
};
#endif


float vertex_modifier_texture_displace_sample_texture_avg(sampler2D t, vec2 p, float lod, float d, TextureResourceParams t_params)
{
	float t_w = 0.0;
	float v = 0.0;

	//return textureLod(t, p, lod).r;
	ivec2 t_size = textureSize(t, int(lod));
	d = 1.0 / t_size.x;

	for(int y = 0; y < KERNEL_SIZE; y++)
	{
		for(int x = 0; x < KERNEL_SIZE; x++)
		{
			vec2 dp = vec2(KERNEL_POS[x], KERNEL_POS[y]) * d * 1.5;
			float w =  KERNEL[x] * KERNEL[y];
			v += texture_sample_lod_with_params(t, p + dp, lod, t_params).r * w;
			t_w += w;
		}
	}

	return v / t_w;
}

float vertex_modifier_texture_displace_sample_texture(sampler2D t, vec2 p, float lod, float d, bool smooth_displacement, TextureResourceParams t_params)
{
	if (smooth_displacement)
	{
		return vertex_modifier_texture_displace_sample_texture_avg(t, p, lod, d, t_params);
	}
	else
	{
		return texture_sample_lod_with_params(t, p, lod, t_params).r;
	}
}

void vertex_modifier_texture_displace_apply(VertexModifierTextureDisplaceParams params, in out VertexInput vtx_input)
{
	if (params.material_id == 0)
		return;

	// for influence move object to the modifier local space
	vec3 modifier_position = vec3(0.0);

	vec3 base_pos = vtx_input.pos;

	// move to modifier local space
	vec3 influence_pos = vector_transform_by_mat43(vtx_input.pos, params.transform_parent_inv * params.transform_local_inv * transform_params.mModel); // <- when disconnected, globals
	vec3 texture_sample_pos = influence_pos;

	//if (params.noise_relative_to_modifier == 0)
	//	texture_sample_pos = vector_transform_by_mat43(texture_sample_pos, params.transform_local);

	vec3 rel_coords;
	vec3 influence_rel_coords;

	rel_coords = base_pos;
	influence_rel_coords = influence_pos;

	float influence = length( (influence_rel_coords - modifier_position) / params.radius);
	if (influence <= 1.0)
	{
		// influence calculations. maybe we can factor this out?
		MaterialPropertiesGPU material = materials.material_properties[params.material_id];
		if (material.albedo_sampler == -1)
			return;

		if (params.falloff_inverse == 0)
			influence = 1.0 - influence;

		influence = curve_sample(params.falloff_curve_idx, 1.0 - influence);

		float lod = 0.0;
		float d_texel = 1.0 / textureSize(material_textures[material.albedo_sampler], int(lod)).x;
		vec2  d_gradient = vec2(0.025, 0.025);

		vec2 tpos = vec2(0.0);
		vec3 tpos_offset_x = vec3(0.0, 0.0, 0.0);
		vec3 tpos_offset_y = vec3(0.0, 0.0, 0.0);

		if (params.mapping_plane == PLANE_YZ)
		{
			d_gradient =  d_texel * params.radius.yz / (params.scale.xy / 2.0);
			tpos = texture_sample_pos.yz / (params.radius.yz * 2.0); // texture coords, use same transformed ones as for influence so the position is rotated
			tpos_offset_x = vec3(0.0, d_gradient.x, 0.0);
			tpos_offset_y = vec3(0.0, 0.0, d_gradient.y);
		}
		else if (params.mapping_plane == PLANE_XZ)
		{
			d_gradient =  d_texel * params.radius.xz / (params.scale.xy / 2.0);
			tpos = texture_sample_pos.xz / (params.radius.xz * 2.0);
			tpos_offset_x = vec3(d_gradient.x, 0.0, 0.0);
			tpos_offset_y = vec3(0.0, 0.0, d_gradient.y);
		}
		else // (params.mapping_plane == PLANE_XY)
		{
			d_gradient =  d_texel * params.radius.xy / (params.scale.xy / 2.0);
			tpos = texture_sample_pos.xy / (params.radius.xy * 2.0);
			tpos_offset_x = vec3(d_gradient.x, 0.0, 0.0);
			tpos_offset_y = vec3(0.0, d_gradient.y, 0.0);
		}

		if (params.radial != 0)
		{
			float r = length(fract(abs(tpos)) - 0.5);
			vec2 t = fract(tpos) - 0.5;
			if (abs(t.y) < 0.00001)
				t.y = 0.00001;
			tpos.x = 1.0 - abs(atan(t.y, t.x) / (M_PI) + 1.0) * 0.5;
			tpos.y = r * 2.0;

			//vtx_input.color.b = 0.0;
			//vtx_input.color.rgb = vec3(abs(tpos.y));
			tpos *= params.scale.xy;
		}
		else
		{
			tpos *= params.scale.xy;
			tpos -= 0.5;
			tpos.y = 1.0 - (tpos.y);
		}


		// TODO: Why do i have to change direction of the X axis? if anything i would have to change it for Y...
		float b_x = vertex_modifier_texture_displace_sample_texture(
			material_textures[material.albedo_sampler], tpos + vec2(-d_texel, 0.0), lod, d_texel, params.smooth_displacement != 0, params.source_txt_params).r;
		float b_y = vertex_modifier_texture_displace_sample_texture(
			material_textures[material.albedo_sampler], tpos + vec2(0.0, d_texel) , lod, d_texel, params.smooth_displacement != 0, params.source_txt_params).r;
		float b =   vertex_modifier_texture_displace_sample_texture(
			material_textures[material.albedo_sampler], tpos                      , lod, d_texel, params.smooth_displacement != 0, params.source_txt_params).r;
		
		float n =    params.value_base + b;
		float n_dx = params.value_base + b_x;
		float n_dy = params.value_base + b_y;

		if (params.value_absolute != 0)
		{
			n =    abs(n);
			n_dx = abs(n_dx);
			n_dy = abs(n_dy);
		}

		float scale = params.value_scale.z;
		vec3 local_norm = vtx_input.norm;

		if (params.displacement_mode == 1) // along normal
			local_norm = normalize(base_pos - vector_transform_by_mat43(modifier_position, params.transform_local));

		vec3 new_pos =    base_pos + local_norm * n * scale;
		vec3 new_pos_dx = base_pos + tpos_offset_x + local_norm * n_dx * scale;
		vec3 new_pos_dy = base_pos + tpos_offset_y + local_norm * n_dy * scale;

		new_pos =    mix(base_pos                             , new_pos,    influence);
		new_pos_dx = mix(base_pos + tpos_offset_x, new_pos_dx, influence);
		new_pos_dy = mix(base_pos + tpos_offset_y, new_pos_dy, influence);

		vec3 dx, dy, dz;
		if (params.mapping_plane == PLANE_YZ)
		{
			dx = vec3((n_dx - n) * influence * scale, d_gradient.x, 0.0);
			dy = vec3((n_dy - n) * influence * scale, 0.0, d_gradient.y);
		}
		else if (params.mapping_plane == PLANE_XZ)
		{
			dx = vec3(d_gradient.x,-(n_dx - n) * influence * scale, 0.0);
			dy = vec3(0.0,       -(n_dy - n) * influence * scale, d_gradient.y);
		}
		else // (params.mapping_plane == PLANE_XY)
		{
			dx = vec3(d_gradient.x, 0.0, (n_dx - n) * influence * scale);
			dy = vec3(0.0, d_gradient.y, (n_dy - n) * influence * scale);
		}
		
		vec3 new_norm = vec3(1.0);
		dx = normalize(dx);
		dy = normalize(dy);
		dz = cross(dx, dy);
		dx = cross(dz, dy);

		#if 0
		vec3 ndx = normalize(vec3(d, 0.0, (b_x - b) * d_gradient * 255.0)); // NOTE: We need proper gradients but this is a kludge!!
		vec3 ndy = normalize(vec3(0.0, d, (b_y - b) * d_gradient * 255.0));
		vec3 ndz = cross(ndx, ndy);

		mat3 tr;
		tr[0] = vec3(ndx.x, ndy.x, ndz.x);
		tr[1] = vec3(ndx.y, ndy.y, ndz.y);
		tr[2] = vec3(ndx.z, ndy.z, ndz.z);
		//new_norm = mat3(ndx, ndy, ndz) * vtx_input.norm;
		//new_norm = tr * vtx_input.norm;
		#endif

		// NOTE: This is still broken for entities which are scaled. Whole modifier is scaled which 
		// we might not want it to?
		mat3 m;
		m[0] = vec3(dx.x, dy.x, dz.x);
		m[1] = vec3(dx.y, dy.y, dz.y);
		m[2] = vec3(dx.z, dy.z, dz.z);
		//new_norm = vtx_input.norm;

		if (params.mapping_plane == 0) // YZ
			new_norm = mat3(dx, dy, dz) * vtx_input.norm;
		else if (params.mapping_plane == 1) // XZ
			new_norm =-mat3(dx, dz, dy) * vtx_input.norm;
		else // (params.mapping_plane == 2) // XY
			new_norm = mat3(dx, dy, dz) * vtx_input.norm;

		//new_norm = vec3(0.0);

		#if 1
		vtx_input.pos = new_pos;
		#else
		vtx_input.color.rgb = vec3(influence);
		if (influence > 0.9)
		{
			vtx_input.color.r = 1.0;
			vtx_input.color.b = 0.0;
			vtx_input.color.g = 0.0;
		}
		if (influence < 0.1)
		{
			vtx_input.color.g = 1.0;
		}
		#endif
		vtx_input.norm = normalize(new_norm);
		//vtx_input.color.rgb += b * 0.1;
	}
}

#endif

