
#define main() mainImage( out vec4 fragColor, in vec2 fragCoord )
#define u_canvas iResolution
#define u_mouse iMouse
#define u_time iTime
#define gl_FragCoord fragCoord
#define gl_FragColor fragColor


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 	id;			//id найденого объека сцены
};

//Луч
struct Ray {
	//Задаваемые параметры
	vec3  origin;		//Начало луча
	vec3  direction;	//Направление луча
	float near;			//Минимальное расстояние от камеры (начало сцены)
	float far;			//Максивальное расстояние от камеры (конец сцены)
	float epsilon;		//Точность обнаружения поверхности
	float steps;		//Максимальное число итераций
	float swing;
	//Вычисляемые параметры
	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);
	#if 1
		//Правильно
		cam.right 		= normalize(cross(cam.forward, cam.up));
		cam.up 			= cross(cam.right, cam.forward);
	#else
		//Не правильно
		cam.right 		= normalize(cross(cam.up, cam.forward));
		cam.up 			= cross(cam.forward, cam.right);
	#endif
	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;
		#if 1
			ray.direction 	= normalize(cam.coord - cam.origin);
		#else
			float angle = radians(cam.fov);
			ray.direction = normalize(vec3(sin(angle*0.5) * uv.x * cam.aspect,  sin(angle*0.5) * uv.y,  -cos(angle*0.5)));
		#endif
		//Умолчания
		ray.near		= 0.01;
		ray.far			= 100.;
		ray.epsilon		= 0.001;
		ray.steps		= 200.;
	}
	return ray;
}

//-------------------Контроль времени-------------------
//      -------         -------
//     |       |       |       |
//_____|       |_______|       |
float demoTimer(float time, float intervalCount, float intervalDuration) {
    float interval_id = floor(time/intervalDuration);
	return mod(interval_id, intervalCount);
}
//       -------
//      /
//_____/
float timeStep(float time, float time_start, float duration) {
    return smoothstep(time_start, time_start + duration, time);
}
//       -------
//      /       \
//_____/         \______
// v.x - time_start, v.y - time_stop
float timeClamp(float time, vec2 v, float duration) {
    return smoothstep(v.x, v.x+duration, time) - smoothstep(v.y-duration, v.y, time);
}

//-------------------Функции позиционирования объектов сцены
void translate(inout vec3 p, vec3 dist) {
	p = p - dist;
}
void translate(inout vec2 p, vec2 dist) {
	p = p - dist;
}
void translate(inout float p, float dist) {
	p = p - dist;
}

//Вращение вокруг осей
mat3 rotate(vec3 r) {
	vec3 s = sin(r), c = cos(r);
	mat3 mx = mat3(1.0, 0.0, 0.0,    0.0, c.x, -s.x,   	0.0, s.x, c.x);
	mat3 my = mat3(c.y, 0.0, -s.y,   0.0, 1.0, 0.0,   	s.y, 0.0, c.y);
	mat3 mz = mat3(c.z, -s.z, 0.0,   s.z, c.z, 0.0,   	0.0, 0.0, 1.0);
   	return mx * my * mz;
}
void rotate(inout vec3 p, vec3 r) {
	mat3 tMat = rotate(r);
	p = tMat * p;
}
void rotateOrigin(inout vec3 p, vec3 r) {
	mat3 tMat = rotate(r);
	p = p * tMat;
}
void rotateX(inout vec3 p, float r) {
	mat3 tMat = rotate(vec3(r,0,0));
	p = tMat * p;
}
void rotateY(inout vec3 p, float r) {
	mat3 tMat = rotate(vec3(0,r,0));
	p = tMat * p;
}
/*
void rotateZ(inout vec3 p, float r) {
	mat3 tMat = rotate(vec3(0,0,r));
	p = tMat * p;
}
*/
void rotateZ(inout vec3 p, float a) {
  	float s = sin(a), c = cos(a);
  	p = vec3(c * p.x - s * p.y, s * p.x + c * p.y, p.z);
}

//Отражение / Дублирование
void mirror(inout vec3 p, vec3 dist) {
	p = abs(p) - dist;
}
void mirror(inout vec2 p, vec2 dist) {
	p = abs(p) - dist;
}
void mirror(inout float p, float dist) {
	p = abs(p) - dist;
}
//Размножение. Возвращает центр ячейки с id=0.
float replica(inout float p, float d) {
    float id = floor(p/d + 0.5);
    p = mod(p + 0.5*d, d) - 0.5*d;
    return id;
}

vec2 replica(inout vec2 p, vec2 d) {
    vec2 id = floor(p/d + 0.5);
    p = mod(p + 0.5*d, d) - 0.5*d;
    return id;
}
vec3 replica(inout vec3 p, vec3 d) {
    vec3 id = floor(p/d + 0.5);
    p = mod(p + 0.5*d, d) - 0.5*d;
    return id;
}
float replicaLimit(inout float p, float d, float ida, float idb) {
	float id = floor(p/d + 0.5);
	p = p-d*clamp(floor(p/d + 0.5), ida, idb);
	return id;
}
vec2 replicaLimit(inout vec2 p, vec2 d, vec2 ida, vec2 idb) {
	vec2 id = floor(p/d + 0.5);
	p = p-d*clamp(floor(p/d + 0.5), ida, idb);
	return id;
}
vec3 replicaLimit(inout vec3 p, vec3 d, vec3 ida, vec3 idb) {
	vec3 id = floor(p/d + 0.5);
	p = p-d*clamp(floor(p/d + 0.5), ida, idb);
	return id;
}
float replicaAngle(inout vec2 p, float n, float off) {
	float a = 2.* PI /n;
	//polar
	p = vec2(atan(p.y, p.x), length(p.xy));
	float id = floor(mod(p.x + 0.5*a + off, 2.*PI)/a);
    p.x = mod(p.x + 0.5*a + off, a) - 0.5*a;
	p = p.y * vec2(cos(p.x),sin(p.x));
	return id;
}
//Вытягивание 2D-профиля dist_2D_profile в направлении p (p.x, p.y, p.z) между -h...+h
float extrude2D(float dist_2D_profile, float p, float h) {
	float dist_3D_between = abs(p) - h;
    vec2 w = vec2(dist_2D_profile, dist_3D_between);
  	return min(max(w.x,w.y),0.) + length(max(w,0.));
}
float extrude2D(float dist_2D_profile, float dist_3D_between) {
    vec2 w = vec2(dist_2D_profile, dist_3D_between);
  	return min(max(w.x,w.y),0.) + length(max(w,0.));
}
//Все нечетные id
bool replicaOddID(vec3 id) {
	id = sign(id - floor(id/2.)*2. - 0.5);
	return id.x*id.y*id.z<0.;
}
bool replicaOddID(vec2 id) {
	id = sign(id - floor(id/2.)*2. - 0.5);
	return id.x*id.y<0.;
}
bool replicaOddID(float id) {
	id = sign(id - floor(id/2.)*2. - 0.5);
	return id<0.;
}


//-------------------Функции сочетания расстояний до объектов сцены
//Сложение / Объединение / ИЛИ
float OR(float distA, float distB) {
	return mix(distA, distB, step(distB, distA)); //if (distB<distA) return distB; return distA;
}
//Умножение / Пересечение / И
float AND(float distA, float distB) {
	return mix(distA, distB, step(distA, distB)); //if (distB>distA) return distB; return distA;
}
//Мягкое сложение / Объединение / ИЛИ (k==0 без)
float OR(float distA, float distB, float k) {
	float h = clamp( 0.5 - 0.5*(distA-distB)/k, 0., 1. );
	return mix(distA, distB, 1.-h) - k*h*(1.-h);
}
//Мягкое умножение / Пересечение / И (k==0 без)
float AND(float distA, float distB, float k) {
	float h = clamp( 0.5 + 0.5*(distA-distB)/k, 0., 1. );
	return mix(distA, distB, 1.-h) + k*h*(1.-h);
}
//Исключение / НЕ
float NOT(float dist) {
	return -dist;
}
//-------------------Функции сочетания объектов сцены
//Сложение / Объединение / ИЛИ
Object OR(Object objectA, Object objectB) {
	if (objectB.distance<objectA.distance) return objectB;
	return objectA;
}
Object OR(Object objectA, Object objectB, float k) {
	Object object = objectA;
	if (objectB.distance<objectA.distance) object = objectB;
	if (k!=0.) object.distance = OR(objectA.distance, objectB.distance, k);
	return object;
}

//Умножение / Пересечение / И
Object AND(Object objectA, Object objectB) {
	if (objectB.distance>objectA.distance) return objectB;
	return objectA;
}
Object AND(Object objectA, Object objectB, float k) {
	Object object = objectA;
	if (objectB.distance>objectA.distance) object = objectB;
	if (k!=0.) object.distance = AND(objectA.distance, objectB.distance, k);
	return object;
}
//Исключение / НЕ
Object NOT(Object object) {
	object.distance = -object.distance;
	return object;
}
//-------------------функции расстояний до объектов



float rand(vec2 p) {return fract(sin(dot(p, vec2(12.9898,78.233))) * 43758.5453);}


float dfBefore(float p, float p1) {
	return p - p1;
}
float dfAfter(float p, float p1) {
	return -p + p1;
}
float dfBetween(float p, float p1, float p2) {
	return max(-p + p1, p - p2);
}
float dfBall(vec3 p, float R) {
	return length(p)-R;
}
float dfCircle(vec2 p, float R) {
	return length(p) - R;
}

float dfPlane(vec3 p, vec3 n, float distanceFromOrigin) {
	return dot(p, n) + distanceFromOrigin;
}

float dfBox(vec3 p, vec3 b) {
	p = abs(p) - b;
//	return max(max(p.x,p.y),p.z);
	vec3 v = min(p, vec3(0));
	float d = max(max(v.x, v.y), v.z);
	return length(max(p, vec3(0))) + d;
}

//Цилиндрический отрезок от <a> до <b>, радиусом r
float dfCylSegmentAB(vec3 p, vec3 a, vec3 b, float r) {
	vec3 ab = b - a;
	float t = clamp(dot(p - a, ab) / dot(ab, ab), 0., 1.);
	vec3 p1 = (ab*t + a) - p;
	//return dfBox(p1, vec3(r));
	return dfBall(p1, r);
}

//Тор  в плоскости YZ
float dfTorusX(vec3 p, float R, float r) {
	return length(vec2(length(p.yz) - R, p.x)) - r;
}
//Тор  в плоскости XZ
float dfTorusY(vec3 p, float R, float r) {
	return length(vec2(length(p.xz) - R, p.y)) - r;
}
//Тор  в плоскости XY
float dfTorusZ(vec3 p, float R, float r) {
	return length(vec2(length(p.xy) - R, p.z)) - r;
}

#define ID_GROUND_ODD 0.0
#define ID_GROUND 1.0
#define ID_TOWER 2.0
#define ID_SEAT_1 3.0
#define ID_SEAT_2 4.0
#define ID_SEAT_3 5.0
#define ID_SEAT_4 6.0
#define ID_SEAT_5 7.0
#define ID_SEAT_6 8.0
#define ID_SEAT_7 9.0
#define ID_SEAT_8 10.0

const float sectors = 8.;

//------------------------------------------------------------------------------
float map(vec3 p, inout Object object) {
	vec3 q, qq;

	object = Object(1e6, -1.0);

	p.z += 3.*u_time;

	vec2 idXZ = replica(p.xz, vec2(15.));
	float rnd = rand(idXZ)*2.-1.;
	rotateY(p, rnd);

	Object GROUND = Object(1e6, replicaOddID(idXZ) ? ID_GROUND_ODD : ID_GROUND);
	{
		q = p;
		GROUND.distance = dfBefore(q.y, 0.);
	}
	object = OR(object, GROUND);

	float whellAngle = u_time * -0.5 * rnd;


	Object TOWER = Object(1e6, ID_TOWER);
	{
		q = p;
		// Mirror across x-axis.
		mirror( q.z, 0.0 );

		//Вертикальная опора
		qq = q;
		qq -= vec3( 0.0, 3.6, 1.5 );
		TOWER.distance = OR(TOWER.distance, dfBox(qq, vec3( 0.15, 3.6, 0.15 ) ));
		//Основание
		qq = q;
		qq -= vec3( 0.0, 0.2, 1.5 );
		TOWER.distance = OR(TOWER.distance, dfBox(qq, vec3( 5.0, 0.2, 0.3 ) ));
		//Наклонная опора1
		qq = q;
		TOWER.distance = OR(TOWER.distance, dfCylSegmentAB( qq, vec3( 4.5, 0.0, 1.5 ), vec3( 0.0, 6.0, 1.5 ), 0.1 ));
		//Наклонная опора2
		qq = q;
		TOWER.distance = OR(TOWER.distance, dfCylSegmentAB( qq, vec3( -4.5, 0.0, 1.5 ), vec3( 0.0, 6.0, 1.5 ), 0.1 ));
		//Балка
		qq = q;
		qq -= vec3( 0.0, 0.2, 0.0 );
		TOWER.distance = OR(TOWER.distance, dfBox(qq, vec3( 0.3, 0.2, 2.0 ) ));
		//Ось
		TOWER.distance = OR(TOWER.distance, dfCylSegmentAB( p, vec3( 0.0, 7.0, 1.5 ), vec3( 0.0, 7.0, -1.5 ), 0.2 ));

		q = p;
		q.y -= 7.0;
		rotateZ( q, whellAngle );
		q.y += 7.0;

		//Отражение относительно плоскости XY
		mirror( q.z, 0.0 );

		qq = q;
		qq -= vec3( 0.0, 7.0, 1.0 );
		//Внешний обруч
		TOWER.distance = OR(TOWER.distance, dfTorusZ(qq, 5.0, 0.1 ));
		//Внутренний обруч
		TOWER.distance = OR(TOWER.distance, dfTorusZ(qq, 4.0, 0.05 ));
		//Осевой обруч
		TOWER.distance = OR(TOWER.distance, dfTorusZ(qq, 0.25, 0.05 ));

		//Спицы
		qq = q;
		qq -= vec3( 0.0, 7.0, 1.0 );
		replicaAngle(qq.xy, sectors, 0.);
		float d1 = dfCircle(qq.yz, 0.05);
		float d2 = dfBefore(qq.x, 5.0);
		TOWER.distance = OR(TOWER.distance, AND(d1, d2));
	}
	object = OR(object, TOWER);


	q = p;
	q.y -= 7.0;
	float id = replicaAngle(q.xy, sectors, whellAngle);
	q.x -= 5.;

	Object SEATS = Object(1e6, ID_SEAT_1 + id);

	rotateZ( q, -whellAngle );
	rotateZ(q, (id-2.)*2.*PI/sectors);
	float seat = dfBox(q, vec3( 0.5, 0.8, 0.8 ));
	float seatCutA = dfBox( q-vec3(0.6,0,0), vec3( 0.5, 0.6, 1 ) );
	float seatCutB = dfBox( q-vec3(0.2,0,0), vec3( 0.5, 0.4, 1 ) );
	SEATS.distance = seat;
	SEATS.distance = AND( SEATS.distance, -seatCutA );
	SEATS.distance = AND( SEATS.distance, -seatCutB );
	SEATS.distance *= 0.6;

	object = OR(object, SEATS);

	return object.distance;
}

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

vec3 mapNormal( in vec3 p, float eps ) {
    vec2 e = vec2(eps, -eps);
    return normalize(
		e.xyy * map( p + e.xyy ) +
		e.yyx * map( p + e.yyx ) +
		e.yxy * map( p + e.yxy ) +
		e.xxx * map( p + e.xxx )
	);
}

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

float softShadow( Ray ray, float k ) {
    float shade = 1.0;
    ray.distance = ray.near;
	float steps = 1.;
    for ( int i = 0; i < 50; i++ ) {
		ray.position = ray.origin + ray.direction * ray.distance;
        ray.object.distance = map(ray.position);
        shade = min( shade, smoothstep( 0.0, 1.0, k * ray.object.distance / ray.distance));
		ray.hit = ray.object.distance < ray.epsilon;
		if (ray.hit) break;
        ray.distance += min( ray.object.distance, ray.far / ray.steps * 2. );
        if (ray.distance > ray.far ) break;
		steps++;
		if (steps>ray.steps) break;
    }
    return min( max( shade, 0.0 ) + 0.5, 1.0 );
}

vec3 getObjectColor( vec3 p, float objectID ) {
    vec3 col = vec3( 1.0 );
	if( objectID == ID_GROUND ) {
		col = vec3( 0.0, 1.0, 0.0 );
	} else if ( objectID == ID_GROUND_ODD ) {
		col = vec3( 1.0, 1.0, 1.0 );
	} else if ( objectID == ID_TOWER ) {
		col = vec3( 0.95, 0.95, 1.0 );
	} else if ( objectID == ID_SEAT_1 ) {
		col = vec3( 1.0, 0.0, 0.0 );// seat 1
	} else if ( objectID == ID_SEAT_2 ) {
		col = vec3( 1.0, 0.5, 0.0 );
	} else if ( objectID == ID_SEAT_3 ) {
		col = vec3( 1.0, 1.0, 0.0 );// seat 3
	} else if ( objectID == ID_SEAT_4 ) {
		col = vec3( 0.5, 1.0, 0.0 );
	} else if ( objectID == ID_SEAT_5 ) {
		col = vec3( 0.0, 1.0, 1.0 );// seat 5
	} else if ( objectID == ID_SEAT_6 ) {
		col = vec3( 0.0, 0.0, 1.0 );
	} else if ( objectID == ID_SEAT_7 ) {
		col = vec3( 0.5, 0.0, 1.0 );// seat 7
	} else if ( objectID == ID_SEAT_8 ) {
		col = vec3( 1.0, 0.0, 0.5 );
	}
    return col;
}

vec3 lighting(Ray ray, vec3 lightPos, vec3 mColor) {
    vec3 lightDir = normalize(lightPos - ray.position);
	vec3 lightCol = vec3( 1.0, 0.6, 0.2 );
    float lightDist = max( length( lightPos - ray.position ), 0.001 );
	float light_atten = 0.003;
    float atten = 1.0 / ( lightDist * lightDist * light_atten );
    float diff = max( dot( ray.normal, lightDir ), 0.0 );
    float spec = pow( max( dot( reflect( -lightDir, ray.normal ), -ray.direction ), 0.0 ), 8.0 );
    return ( mColor * ( diff + 0.15 ) + lightCol * spec * 2.0 ) * atten;
}

vec3 sky(vec3 rd) {
	return vec3(1);
}

void main() {
	float aspect = u_canvas.x/u_canvas.y;
	vec2 uv = gl_FragCoord.xy/u_canvas.xy;
	uv = uv*2.-1.;

	vec2 mouse = u_mouse.xy/u_canvas.xy;
	if (mouse==vec2(0)) mouse = vec2(0.5);

	vec3 angle = vec3(
		-mix(-90., 90., mouse.y) + 30. - 30.*sin(u_time),
		 mix(-180.,180., mouse.x) + 30.*cos(u_time),
		 0.
	);

	Camera cam;
	{
		cam.fov     = 45.;
		cam.aspect  = aspect;
		cam.origin  = vec3(0, 10.0, -1.0 );
		cam.target  = vec3(0, 10.0,  0.0 );
		cam.up 		= vec3(0,1,0);
	}

	Ray ray = lookAt(uv, cam);
	{
		ray.near 	= 0.01;
		ray.far  	= 80.;
		ray.epsilon = 0.0025;
		ray.swing	= 1.0; //0.8 + 0.1 * rnd(uv)
		ray.steps 	= 50.;

		rotateOrigin(ray.direction, radians(angle));
	}
	rayMarch(ray);

	vec3 lightPos = ray.origin + vec3( 0.0, 20.0, -10.0 );
	vec3 bgCol = sky(ray.direction);
	vec3 color = bgCol;

	if ( ray.distance <ray.far ) {
		ray.normal = mapNormal( ray.position, 0.005 );

    	vec3 mColor = getObjectColor( ray.position, ray.object.id );

		color = lighting(ray, lightPos, mColor);

		//Тень
		Ray ray_s = ray;
		{
			ray_s.origin 	= ray.position;
			ray_s.direction	= normalize(lightPos - ray.position);
			ray_s.near		= 0.01;
			ray_s.far		= max( length(lightPos - ray.position), 0.001 );
			ray_s.steps		= 20.;
			ray_s.epsilon	= 0.001;
		}
		float shadow = softShadow( ray_s, 24.0 );

	   	if (ray.object.id==ID_GROUND_ODD) {
			//Отражение
			Ray ray_r = ray;
			{
				ray_r.origin 	= ray.position;
				ray_r.direction = reflect( ray.direction, ray.normal );
				ray_r.steps		= 50.;
			}
			rayMarch(ray_r);

			if ( ray_r.distance <ray_r.far ) {
				ray_r.normal = mapNormal( ray_r.position, 0.005 );

				mColor = getObjectColor( ray_r.position, ray_r.object.id );

				color += lighting( ray_r, lightPos, mColor) * 0.45;
			} else {
				color += bgCol * 0.45;
			}
		}

		color *= shadow;
		float fog = smoothstep( ray.far * 0.8, 0.0, ray.distance );
		vec3 fogCol = bgCol;
		color = mix( color, fogCol, 1.0 - fog );

	}
	color = clamp( color, 0.0, 1.0 );

	gl_FragColor = vec4( color, 1.0 );

}
