#ifndef WAVE_NORMALS_GLSL
#define WAVE_NORMALS_GLSL

// "Very fast procedural ocean" by afl_ext
// https://www.shadertoy.com/view/MdXyzX
// https://opensource.org/license/mit

#define DRAG_MULT 0.38 // changes how much waves pull on the water
#define WAVE_E 0.01
#define WAVE_DEPTH 0.3

// Calculates wave value and its derivative, 
// for the wave direction, position in space, wave frequency and time
vec2 wavedx(vec2 position, vec2 direction, float frequency, float timeshift) {
	float x = dot(direction, position) * frequency + timeshift;
    x = mod(x, 2 * PI);
	float wave = exp(sin(x) - 1.0) * 0.5;
	float dx = wave * cos(x);
	return vec2(wave, -dx);
}



// Calculates waves by summing octaves of various waves with various parameters
float waveHeight(vec2 position) {
	float wavePhaseShift = length(position) * 0.1; // this is to avoid every octave having exactly the same phase everywhere
	float iter = 0.0; // this will help generating well distributed wave directions
	float frequency = 1.0; // frequency of the wave, this will change every iteration
	float timeMultiplier = 2.0; // time multiplier for the wave, this will change every iteration
	float weight = 1.0;// weight in final sum for the wave, this will change every iteration
	float sumOfValues = 0.0; // will store final sum of values
	float sumOfWeights = 0.0; // will store final sum of weights
	for(int i=0; i < 16; i++) {
		// generate some wave direction that looks kind of random
		vec2 p = vec2(sin(mod(iter, 2 * PI)), cos(mod(iter, 2 * PI)));
		
		// calculate wave data
		vec2 res = wavedx(position, p, frequency, frameTimeCounter * timeMultiplier + wavePhaseShift);

		// shift position around according to wave drag and derivative of the wave
		position += p * res.y * weight * DRAG_MULT;

		// add the results to sums
		sumOfValues += res.x * weight;
		sumOfWeights += weight;

		// modify next octave ;
		weight = mix(weight, 0.0, 0.2);
		frequency *= 1.18;
		timeMultiplier *= 1.07;

		// add some kind of random value to make next wave look random too
		iter += 1232.399963;
	}
	
	// calculate and return
	return sumOfValues / sumOfWeights;
}

// Calculate normal at point by calculating the height at the pos and 2 additional points very close to pos
// returned value is in world space
vec3 waveNormal(vec2 pos, vec3 worldFaceNormal, float heightmapFactor) {
	if(dot(worldFaceNormal, vec3(0.0, 1.0, 0.0)) <= 0.1){
		return worldFaceNormal;
	}

	#ifdef PIXEL_LOCKED_LIGHTING
	pos = floor(pos * PIXEL_SIZE) / PIXEL_SIZE;
	#endif

	vec2 ex = vec2(WAVE_E, 0);
	float H = waveHeight(pos.xy) * WAVE_DEPTH * heightmapFactor;

	vec3 a = vec3(pos.x, H, pos.y);
	vec3 waveNormal = normalize(
		cross(
			a - vec3(pos.x - WAVE_E, waveHeight(pos.xy - ex.xy) * WAVE_DEPTH * heightmapFactor, pos.y), 
			a - vec3(pos.x, waveHeight(pos.xy + ex.yx) * WAVE_DEPTH * heightmapFactor, pos.y + WAVE_E)
		)
	);

	// rotate to align with face normal since the normal calculation assumes a surface facing straight up
	waveNormal = rotate(waveNormal, vec3(0.0, 1.0, 0.0), worldFaceNormal);
	return normalize(waveNormal);
}

#endif // WAVE_NORMALS_GLSL