
#ifndef VERTEX_MODIFIER_TEXTURE_DISPLACE_H
#define VERTEX_MODIFIER_TEXTURE_DISPLACE_H

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

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;
};

#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 sample_avg(sampler2D t, vec2 p, float lod, float d)
{
	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 += textureLod(t, p + dp, lod).r * w;
			t_w += w;
		}
	}

	return v / t_w;
}

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);

		vec2 tpos = texture_sample_pos.xy / (params.radius.xy * 2.0) - 0.5; // texture coords, use same transformed ones as for influence so the position is rotated

		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.y = 1.0 - tpos.y;
		}

		float d = 1.0 / 255.0;
		float b_x = sample_avg(material_textures[material.albedo_sampler], tpos + vec2(d, 0.0), 0.0, d).r;
		float b_y = sample_avg(material_textures[material.albedo_sampler], tpos + vec2(0.0, d), 0.0, d).r;
		float b = sample_avg(material_textures[material.albedo_sampler], tpos, 0.0, d).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);
		}

		vec3 scale = params.value_scale;
		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 + vec3(d, 0.0, 0.0) + local_norm * n_dx * scale;
		vec3 new_pos_dy = base_pos + vec3(0.0, d, 0.0) + local_norm * n_dy * scale;

		new_pos = mix(base_pos, new_pos, influence);
		new_pos_dx = mix(base_pos + vec3(d, 0.0, 0.0), new_pos_dx, influence);
		new_pos_dy = mix(base_pos + vec3(0.0, d, 0.0), new_pos_dy, influence);

		vec3 dx, dy, dz;
		dx = vec3(d, 0.0, (n - n_dx) * influence);
		dy = vec3(0.0, d, (n - n_dy) * influence);
		
		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 * 255.0)); // NOTE: We need proper gradients but this is a kludge!!
		vec3 ndy = normalize(vec3(0.0, d, (b_y - b) * d * 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 = m * vec3(0.0, 1.0, 0.0);//vtx_input.norm;
		new_norm = mat3(dx, dy, dz) * vtx_input.norm;
		
		#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

