#define u_texture0 iChannel0
#define u_time iTime
#define u_canvas iResolution
#define texture2D texture

const float PI = radians(180.);

//Камера
struct Camera {
	//Задаваемые параметры
	float fov, aspect;
	vec3  origin, target, up;
	//Расчетные параметры
	float factor;
	vec3  forward, right, position, coord;
};

//Параметры объекта
struct Object {
	float   distance;	//Последнее приближение к объекту сцены
	float   steps;		//Число приближений к объекту сцены
	float 	id;			//id найденого объека сцены
};

//Луч
struct Ray {
	//Задаваемые параметры
	vec3  origin;		//Начало луча
	vec3  direction;	//Направление луча
	float near;			//Минимальное расстояние от камеры (начало сцены)
	float far;			//Максивальное расстояние от камеры (конец сцены)
	float epsilon;		//Точность обнаружения поверхности
	float swing;		//Колебание шага приближения 0...1
	float steps;		//Максимальное число итераций
	//Вычисляемые параметры
	float distance; 	//Расстояние до объекта сцены от ray.origin в направлении ray.direction
	vec3  position; 	//Точка поверхности
	vec3  normal;		//Нормаль в точке поверхности
	bool  hit;			//Флаг нахождения точки поверхности с заданной точностью
	Object object;		//Параметры объекта (можно формировать непосредственно в карте расстояний)
};
//Формирование луча камеры
Ray lookAt (in vec2 uv, inout Camera cam) {
	//Расчетные характеристики камеры
	cam.factor 		= 1.0/tan(radians(cam.fov/2.));
	cam.forward 	= normalize(cam.target-cam.origin);
	cam.right 		= normalize(cross(cam.up, cam.forward));
	cam.up 			= cross(cam.forward, cam.right);
	cam.position 	= cam.origin + cam.factor * cam.forward;
	cam.coord 		= cam.position + uv.x * cam.right * cam.aspect + uv.y * cam.up;
	//Формирование луча
	Ray ray;
	{
		ray.origin 		= cam.origin;
		ray.direction 	= normalize(cam.coord - cam.origin);
	}
	return ray;
}

//Функция трехмерного смешивания. Основано на старом учебнике Nvidia.
vec3 tex3D( sampler2D tex, in vec3 p, in vec3 n ){
//    n = max((abs(n) - 0.2)*7., 0.001); // max(abs(n), 0.001), etc.
    n = max(abs(n), 0.001); // max(abs(n), 0.001), etc.
    n /= (n.x + n.y + n.z );
	return (texture2D(tex, p.yz)*n.x + texture2D(tex, p.zx)*n.y + texture2D(tex, p.xy)*n.z).xyz;
}

vec2 path(in float z){
	#if 1
		float x = sin(z/24.)*cos(z/12.);
		float y = 0.1*cos(z/24.)*sin(z/12.);
	#else
		//float x = sin(z/24.)*sin(z/12.);
		//float y = 0.1*cos(z/24.)*sin(z/12.);
		float x = sin(z/24.)*cos(z/12.)*sin(z/6.);
		float y = clamp(0.1*sin(z/24.)*sin(z/12.)*sin(z/6.), -0.075,1.);
	#endif
	return vec2(x, y)*12.;
}

bool flagWater = true;

float map(vec3 p, inout Object object) {
	object.distance = 1e6;
	object.id = -1.;

	//Пропорция тоннеля
	#if 0
		vec2 size = vec2(0.5, 1);
	#else
		vec2 size = vec2(1);
	#endif

	float d;
	vec3 q;
	q.xy = (p.xy - path(p.z))*size;
	vec3 q1 = q;
	vec3 q2 = q;

	//Перевернутое сердце
	q.y = q.y + abs(q.x);
	d = 1.0 - length(q.xy);

	if (d < object.distance) {
		object.distance = d;
		object.id = 0.;
	}

	//Вырез
	q1.x = mod(p.z, 4.);
	q1.xy -= vec2(0.2);
	d = 1. - max(abs(q1.x*0.5), abs(q1.y));

	if (d > object.distance) {
		object.distance = d;
		object.id = 0.;
	}

	//Трубы
	vec3 p2 = q2;
	p2.x = abs(p2.x) - 1.;
	p2.y = abs(p2.y) - 1.;
	p2.z = mod(p2.z, 4.);
	d = length(p2)-0.05;
	if (d < object.distance) {
		object.distance = d;
		object.id = 2.;
	}


	if (flagWater) {
		//Поверхность воды
		float h = -1.;
		d = p.y - h;

		if (d < object.distance) {
			object.distance = d;
			object.id = 1.;
		}
	}

	return object.distance;
}
float map(vec3 p ) {
	Object object;
	return map(p, object);
}

vec3 mapNormal(vec3 p, float eps){
	mat3 e = mat3(eps);
	return normalize( vec3(
		map( p + e[0]) - map(p - e[0]),
		map( p + e[1]) - map(p - e[1]),
		map( p + e[2]) - map(p - e[2])
	));
}

void rayMarch(inout Ray ray) {
	ray.distance = ray.near;
	ray.object.steps = 1.;
	for (float i = 0.; i < 200.; ++i) {
		ray.position = ray.origin + ray.direction * ray.distance;
		ray.object.distance = map(ray.position, ray.object);
		ray.hit = abs(ray.object.distance) < ray.epsilon;
		if(ray.hit) break;
		ray.distance += ray.object.distance*ray.swing;
		if(ray.distance>ray.far) break;
		ray.object.steps++;
		if(ray.object.steps>ray.steps) break;
	}
	ray.distance = min(ray.distance, ray.far);
}

//Оттенки серого
float getGrey(vec3 p){
	return p.x*0.299 + p.y*0.587 + p.z*0.114;
}

// Рельеф текстуры. Всего четыре трехплоскостных поиска или 12 текстурных поисков.
vec3 doBumpMap( sampler2D tex, vec3 p, vec3 n){
    const float eps = 0.001;
	float bumpfactor = 0.025;
	mat3 m = mat3(eps);
    float ref = getGrey(tex3D(tex,  p , n));
    vec3 grad = vec3(
		getGrey(tex3D(tex, p-m[0], n)) - ref,
        getGrey(tex3D(tex, p-m[1], n)) - ref,
        getGrey(tex3D(tex, p-m[2], n)) - ref
	)/eps;
    grad -= n*dot(n, grad);
    return normalize( n + grad*bumpfactor );
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	float aspect = u_canvas.x/u_canvas.y;
	vec2 uv = fragCoord.xy/u_canvas.xy;
	uv = uv - 0.5;

	Camera cam;
	{
		cam.fov     = 90.;
		cam.aspect  = aspect;
		cam.origin  = vec3(0,0, u_time*3.);
		cam.target  = cam.origin + vec3(0,0,1);
		cam.target.xy += path(cam.target.z);
		cam.origin.xy += path(cam.origin.z);
		cam.up 		= vec3(0,1,0);
	}

	Ray ray = lookAt(uv, cam);
	{
		ray.near 	= 0.01;
		ray.far  	= 100.;
		ray.epsilon = 0.001;
		ray.swing	= 1.; //0.8 + 0.1 * rnd(uv)
		ray.steps 	= 200.;
	}

	flagWater = true;
	rayMarch(ray);

	vec3 color = vec3(0);
	// Коэффициент масштаба текстуры.
	const float tSize = 0.5;
	//Цвет воды
	const vec3 color_water = vec3(0.1,0.3,0.9);

	if (ray.distance<ray.far) {
		//Нормаль поверхности
		ray.normal = mapNormal(ray.position, 0.01);
		//Цвет поверхности
		vec3 mCol;
		if (ray.object.id==0.) {
			//Стены
			ray.normal = doBumpMap(u_texture0, ray.position*tSize, ray.normal); // Floor.
			mCol = tex3D(u_texture0, ray.position*tSize, ray.normal);
		} else if (ray.object.id==1.) {
			//Вода
			//Рябь
			vec3 normal_water = doBumpMap(u_texture0, ray.position*0.005*sin(0.2 + 0.01*u_time), ray.normal);
			Ray ray1 = ray;
			{
				ray1.origin 	= ray.position;
				ray1.direction 	= refract(ray.direction, normal_water, 0.95);
				ray1.steps = 50.;
			}
			flagWater = false;
			rayMarch(ray1);

			ray1.normal = mapNormal(ray1.position, 0.01);

			if (ray1.object.id==2.) {
				if (ray.position.y>0.) {
					mCol = vec3(0.9,0.2,0.1);
				} else {
					mCol = vec3(0.9,0.9,0.1);
				}
			} else {
				mCol = tex3D(u_texture0, ray1.position*tSize, ray1.normal);
			}
			mCol = mix(color_water, mCol, 0.5);

			ray.normal = ray1.normal;
		} else if (ray.object.id==2.) {
			if (ray.position.y>0.5) {
				mCol = vec3(0.9,0.2,0.1);
			} else {
				mCol = vec3(0.9,0.9,0.1);
			}
		}
		//Направление света
		vec3 ldir = -ray.direction;
		//Диффузия
		float diff = max(0.0, dot(ray.normal, ldir));
		//Цвет фрагмента
		color = mCol * (diff + 0.2);
		color = clamp(color, 0., 1.);
	}

	fragColor = vec4(color, 1);
}
