
#ifndef VERTEX_MODIFIER_NOISE_H
#define VERTEX_MODIFIER_NOISE_H

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

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

	vec4 noise_phase;
	vec3 noise_scale;
	int falloff_curve_idx;
	vec3 scale;
	int falloff_inverse;
	vec3 position;
	int noise_relative_to_modifier;
	vec3 radius;
	int displacement_mode;  // 0 - along normal; 1 - radial
	float noise_value_base;
	int noise_value_absolute;
};

void vertex_modifier_noise_apply(VertexModifierNoiseParams params, in out VertexInput vtx_input)
{
	// for influence move object to the noise 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 noise_pos = influence_pos;

	if (params.noise_relative_to_modifier == 0)
		noise_pos = vector_transform_by_mat43(noise_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?
		
		if (params.falloff_inverse == 0)
			influence = 1.0 - influence;

		influence = clamp(curve_sample(params.falloff_curve_idx, influence), 0.0, 1.0);

		vec3 ni = noise_pos * params.noise_scale; // noise coords, use same transformed ones as for influence so the noise is rotated
		vec3 n1_grad = vec3(0.0);
		vec3 n2_grad = vec3(0.0);
		float n1 = snoise_grad(ni + params.noise_phase.xyz * 0.1 + params.noise_phase.w * 0.0123, n1_grad);
		float n2 = snoise_grad(0.5 * ni + params.noise_phase.xyz * 0.1 + params.noise_phase.w * 0.0217, n2_grad);

		n1_grad *= params.noise_scale; // scale by the coords scale to normalize. exclude const terms
		n2_grad *= params.noise_scale * 0.5;

		n2 *= 0.5;
		n2_grad *= 0.5;

		float d = 1.0;
		float n = params.noise_value_base + n1 + n2;
		float n_dx = params.noise_value_base + n1 + n1_grad.x * d + n2 + n2_grad.x * d;
		float n_dy = params.noise_value_base + n1 + n1_grad.y * d + n2 + n2_grad.y * d;
		float n_dz = params.noise_value_base + n1 + n1_grad.z * d + n2 + n2_grad.z * d;

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

		vec3 scale = params.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;
		vec3 new_pos_dz = base_pos + vec3(0.0, 0.0, d) + local_norm * n_dz * 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);
		new_pos_dz = mix(base_pos + vec3(0.0, 0.0, d), new_pos_dz, influence);

		vec3 dx, dy, dz;
		dx = (new_pos_dx - new_pos);
		dy = (new_pos_dy - new_pos);
		dz = (new_pos_dz - new_pos);

		// pick the vector to recalculate. this is probably not the proper solution, but seems to work?

		vec3 new_norm = vec3(1.0);
		#if 1
		if (abs(vtx_input.norm.x) >= abs(vtx_input.norm.y) && abs(vtx_input.norm.x) >= abs(vtx_input.norm.z))
		{
			dy = normalize(dy);
			dz = normalize(dz);
			dx = cross(dy, dz);
		}
		else if (abs(vtx_input.norm.y) >= abs(vtx_input.norm.x) && abs(vtx_input.norm.y) >= abs(vtx_input.norm.z))
		{
			dx = normalize(dx);
			dz = normalize(dz);
			dy = cross(dz, dx);
		}
		else
		{
			dx = normalize(dx);
			dy = normalize(dy);
			dz = cross(dx, dy);
		}
		#else
		if (length(dx) <= length(dy) && length(dx) <= length(dz))
		{
			dy = normalize(dy);
			dz = normalize(dz);
			dx = cross(dy, dz);
		}
		else if (length(dy) <= length(dx) && length(dy) <= length(dz))
		{
			dx = normalize(dx);
			dz = normalize(dz);
			dy = cross(dz, dx);
		}
		else
		{
			dx = normalize(dx);
			dy = normalize(dy);
			dz = cross(dx, dy);
		}
		#endif

		new_norm = mat3(dx, dy, dz) * vtx_input.norm;

		vtx_input.pos = new_pos;
		vtx_input.norm = normalize(new_norm);
	}
}

#endif

