#define PI 3.141592653589793

#define MAX_STEPS 300
#define MAX_DIST 100.0
#define SURFACE_DIST 0.001

const bool AA = true;

//A struct that hold both the sdf value and the color
struct Surface {
	float distVal;
	vec4 color;
	int id; //0 = waves, 1 = skyDome
};


float singleWave(vec3 p, float wavelength, float amplitude, float speed, vec2 direction) {
	
	float frequency = 2.0/wavelength;
	float phaseConstant = speed * (2.0/wavelength);
	
	return amplitude * sin(dot(direction, p.xz)*frequency + iTime*phaseConstant);


}

float singleWave2(vec3 p, float wavelength, float amplitude, float speed, vec2 direction) {
	
	float frequency = 2.0/wavelength;
	float phaseConstant = speed * (2.0/wavelength);
	
	const float kkk = 2.5;
	
	return 2.0*amplitude * pow((sin(dot(normalize(p.xz), p.xz)*frequency + iTime*phaseConstant)+1.0)/2.0, kkk);


}

float hashwithoutsine11(float p)
{
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}

vec2 random2(vec2 st){
    st = vec2( dot(st,vec2(127.1,311.7)),
              dot(st,vec2(269.5,183.3)) );
    return -1.0 + 2.0*fract(sin(st)*43758.5453123);
}

// Gradient Noise by Inigo Quilez - iq/2013
// https://www.shadertoy.com/view/XdXGW8
float noise(vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    vec2 u = f*f*(3.0-2.0*f);

    return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
                     dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
                     dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
}

float noise1d(float x) {
	float i = floor(x);
	float f = fract(x);
	
	return mix(hashwithoutsine11(i), hashwithoutsine11(i+1.0), smoothstep(0.0, 1.0, f));
}

Surface wavyPlane(vec3 p, vec4 col, int id) {
	
	
	float wave = singleWave(p, 3.0, 2.0, 2.0, vec2(1.0, 0.0));//a * sin(dot(d, p.xz)*f + iTime*pc);
	
	float w = 0.0;
	
	float waveLength = 5.0;
	float amplitude = 1.0;
	float speed = 0.9;
	vec2 dir = vec2(1.0, 0.0);
	
	//Coordinates from origin and increase equally in all directions from origin
	float fromMiddle = dot(normalize(p.xz), p.xz)*0.2;
	
	for (int i = 0; i < 5; i++) {
		w += singleWave2(p, waveLength, amplitude, speed, dir);// + noise(p.xz*fromMiddle*waveLength*max(0.0, noise1d(fromMiddle)))*0.15;;
		waveLength *= 0.5;
		amplitude *= 0.55;
		speed *= 0.5;
		dir = vec2(1.0, float(i)/1.0);
	}
	
	w += noise(p.xz*5.0+0.5*fromMiddle*9.0*max(0.0, sin(noise1d(fromMiddle))*0.8))*0.25;
	
	//vec4 bottom = vec4(0.6, 0.0, 0.5, 0.1);
	//vec4 top = vec4(0.17, 0.35, 0.95, 0.1);
	
	vec4 bottom = vec4(53.0/255.0, 92.0/255.0, 125.0/255.0, 0.1);
	vec4 top = vec4(192.0/255.0, 108.0/255.0, 132.0/255.0, 0.1);
	
	col = mix(bottom, top, w-1.3);
	
	return Surface((p.y + w)*0.33, col, id);

}


//@Shane - path function
vec2 path(in float z){
    vec2 p1 =vec2(2.38*sin(z * .15)+5.38*cos(z * .15), 3.4*cos(z * .0945));
    vec2 p2 =vec2(3.2*sin(z * .179),4.31*sin(z * .127));
    return (p1 - p2)*0.33;
}

Surface minWithColor(Surface obj1, Surface obj2) {

	if (obj1.distVal < obj2.distVal) {
		return obj1;
	}
	
	return obj2;
}

Surface getDist(vec3 p) {
	
	p.xy += path(p.z*2.0+iTime);
	//p.xz += path(p.x*1.0+iTime);
	
	Surface wavyPlanez = wavyPlane(p*1.0, vec4(1.0), 0);
	
	return wavyPlanez;

}

Surface rayMarch(vec3 rayOrigin, vec3 rayDirection) {
	
	Surface closestObject = Surface(0.0, vec4(1.0, 0.0, 0.0, 1.0), 0);
	
	vec4 glow = vec4(0.0);
	
	for(int i = 0; i < MAX_STEPS; i++) {
		//The current stop (blue point from video). Will in first iteration just be the rayOrigin
		vec3 currentStop = rayOrigin + (closestObject.distVal * rayDirection);
		//Distance to the closes "thing" in our scene
		Surface distToScene = getDist(currentStop);
		closestObject.distVal += distToScene.distVal;
		closestObject.color = distToScene.color;
		closestObject.id = distToScene.id;
		
		
		glow += distToScene.color*(1.0/float(MAX_STEPS))*7.0;
		
		closestObject.color = clamp(glow, 0.0, 1.0);
		
		// we have a hit || The distance is too large, this ray hit nothing. we marched past everything, dont want to march to infinity 
		if (closestObject.distVal > MAX_DIST || distToScene.distVal < SURFACE_DIST) {
			break;
		}
		
	}
	
	return closestObject;

}

/* ChatGPT solutuon for crating a view matrix */
mat3 getViewMatrix(vec3 cameraPos, vec3 cameraTarget, vec3 cameraUp)
{
    // Calculate the view direction vector
    vec3 viewDir = normalize(cameraTarget - cameraPos);

    // Calculate the right and up direction vectors
    vec3 rightDir = normalize(cross(cameraUp, viewDir));
    vec3 upDir = cross(viewDir, rightDir);

    // Create the view matrix
    mat3 viewMatrix = mat3(
        rightDir,
        upDir,
        viewDir
    );

    return viewMatrix;
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy/iResolution.xy;
    vec2 ogUV = uv;
    uv = (uv * 2.0) - 1.0;
    
    //uv *= 1.0;
    
    float ar = iResolution.x / iResolution.y;
    uv.x = uv.x * ar;
    
    //Init raymarch
    vec3 rayOrigin = vec3(0.0, 6.0, -12.0);
    
    //vec3 lookAtPoint = vec3(sin(iTime*0.1)*10.0, cos(iTime*0.1)*13.0, 1.0);
    vec3 lookAtPoint = vec3(sin(iTime*0.1)*10.0, cos(iTime*0.5)*3.0, 0.0);
    
    mat3 viewM = getViewMatrix(rayOrigin, lookAtPoint, vec3(0.0, 1.0, 0.0));
    
    vec3 col = vec3(0.0); 
    
    vec3 rayDirection = normalize(vec3(uv.x, uv.y, 1.0));
    rayDirection = viewM * rayDirection;
    //rayOrigin = viewM * rayOrigin;
    //rayDirection = rayDirRes;
    
    Surface objects = rayMarch(rayOrigin, rayDirection);
    float d = objects.distVal;
    vec3 surfacePoints = rayOrigin + (rayDirection * d);
    
    if (AA) {
        //AA
        vec3 acc = vec3(0.0);
        float offAmount = 0.001;
        //Taking 3 samples up(y-axis) from current pixel and 1 down seems the best?
        vec3[4] offs = vec3[](
            vec3(0.0, offAmount*3.0, 0.0),
            vec3(0.0, offAmount*2.0, 0.0),
            vec3(0.0, offAmount, 0.0),
            vec3(0.0, -offAmount, 0.0)
        );

        //Really expensive AA
        for (int i = 0; i < offs.length(); i++) {
            vec3 off = offs[i];
            vec3 rayDirNeighb = rayDirection + off;
            Surface neighbObjects = rayMarch(rayOrigin, rayDirNeighb);
            acc += neighbObjects.color.rgb;
        }
        
        col = col + ((objects.color.rgb + acc)/5.0);
    } else {
        col = col + objects.color.rgb;
    }
    
     
  	
  	//col = col + objects.color.rgb;
    //col = col + ((objects.color.rgb + acc)/5.0);
    
    //------ PostProcess experiments--------
    //Contrast
    float contrastVal = 1.0;
    col = clamp(contrastVal * (col - 0.5) + 0.5, 0.0, 1.0);
    //brightness
    float brightness = 0.0;
    col = clamp(col + brightness, 0.0, 1.0);
    //Saturation
    float grayscale = dot(col, vec3(0.299, 0.587, 0.114));
    float saturation = 1.15;
    col = clamp(mix(vec3(grayscale), col, saturation), 0.0, 1.0);
    
    //col = vec3(grayscale);
    
    //Gamma correction
    col = pow(col, vec3(1.0/2.2));

    // Output to screen
    fragColor = vec4(col,1.0);
}
