#version 330
// This source code is property of the Computer Graphics and Visualization
// chair of the TU Dresden. Do not distribute!
// Copyright (C) CGV TU Dresden - All Rights Reserved

in vec4 position;

out vec3 n;
out vec2 uv;
out vec2 road_uv;

uniform mat4 mvp;

//Returns the height of the procedural terrain at a given xz position
float getTerrainHeight(vec2 p);

vec3 calculate_normal(vec4 terrain_position);
vec4 offset_point(vec4 base, vec2 offset);

const vec2 offsets[6] = vec2[6](
	vec2(0.0, 0.0),
	vec2(0.0, 1.0),
	vec2(1.0, 0.0),
	vec2(0.0, 1.0),
	vec2(1.0, 0.0),
	vec2(1.0, 1.0)
);

void main()
{
	vec4 terrain_position = vec4(position.x, getTerrainHeight(vec2(position.x, position.z)), position.z, position.w);
	n = calculate_normal(terrain_position);
	//n = vec3(0.0, 1.0, 0.0);

	uv = vec2(terrain_position.x / 255.0, terrain_position.z / 255.0);
	road_uv = offsets[gl_VertexID % 6];

	gl_Position = mvp * terrain_position;
}

//source: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
float rand(vec2 c)
{
	return 2 * fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453) - 1;
}

float perlinNoise(vec2 p )
{
	vec2 ij = floor(p);
	vec2 xy = p - ij;
	xy = 3.*xy*xy-2.*xy*xy*xy;
	//xy = .5*(1.-cos(3.1415926 * xy));
	float a = rand((ij+vec2(0.,0.)));
	float b = rand((ij+vec2(1.,0.)));
	float c = rand((ij+vec2(0.,1.)));
	float d = rand((ij+vec2(1.,1.)));
	float x1 = mix(a, b, xy.x);
	float x2 = mix(c, d, xy.x);
	return mix(x1, x2, xy.y);
}

//based on https://www.seedofandromeda.com/blogs/58-procedural-heightmap-terrain-generation
float getTerrainHeight(vec2 p)
{
	float total = 0.0;
	float maxAmplitude = 0.0;
	float amplitude = 1.0;
	float frequency = 0.02;
	for (int i = 0; i < 11; i++)
	{
		total +=  ((1.0 - abs(perlinNoise(p * frequency))) * 2.0 - 1.0) * amplitude;
		frequency *= 2.0;
		maxAmplitude += amplitude;
		amplitude *= 0.45;
	}
	return 15 * total / maxAmplitude;
}

vec4 offset_point(vec4 base, vec2 offset) {
	vec2 new_pos = vec2(base.x, base.z) + offset;
	float y = getTerrainHeight(new_pos);
	return vec4(new_pos.x, y, new_pos.y, base.w);
}

// calculate the position of the first vertex in this square
vec4 pos_to_base(vec4 pos, int v_id) {
	return vec4(pos.x - offsets[v_id].x, pos.y, pos.z - offsets[v_id].y, pos.w);
}

// calculates the normal of terrain_position
// it generates the complete triangle based on the gl_VertexID and then normal math
vec3 calculate_normal(vec4 terrain_position) {
	int offset_index = gl_VertexID % 6;

	vec4 base_vertex = pos_to_base(terrain_position, offset_index);

	// the other 2 points from the triangle
	int points_index = 0;
	vec4 points[2];

	int iterator_offset = 0;

	// second triangle offset
	if (offset_index > 2) {
		iterator_offset = 3;
	}

	for (int i = iterator_offset; i < (3 + iterator_offset); i++) {
		// skip for current vertex
		if (i == offset_index) {
			continue;
		}

		points[points_index] = offset_point(base_vertex, offsets[i]);
		points_index++;
	}

	// create connection vectors
	vec3 v1 = (points[0] - terrain_position).xyz;
	vec3 v2 = (points[1] - terrain_position).xyz;

	// calculate normal
	vec3 normal = normalize(cross(v1, v2));

	// naively assume that a normal always has to look upwards
	if (normal.y < 0.0) {
		return -normal;
	} else {
		return normal;
	}
}