// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// https://www.shadertoy.com/view/ftyfzt
// By David Hoskins, 2022.

vec3 camPos, camTar, camRay;
float gTime, gTimeDelta;

#define MOUSE_SCRUB
//#define SHOW_BOUNDING

#define UI0 1597334673U
#define UI1 3812015801U
#define UI2 uvec2(UI0, UI1)
#define UI3 uvec3(UI0, UI1, 2798796415U)
#define UI4 uvec4(UI3, 1979697957U)
#define UIF (1.0 / float(0xffffffffU))

#define tri(x) abs(fract(x)-.5)*2.

//---------------------------------------------------------------------------------------------------------------
float hash12(vec2 p)
{
	uvec2 q = uvec2(ivec2(p)) * UI2;
	uint n = (q.x ^ q.y) * UI0;
	return float(n) * UIF;
}

//---------------------------------------------------------------------------------------------------------------
vec3 hash33(vec3 p)
{
	uvec3 q = uvec3(ivec3(p)) * UI3;
	q = (q.x ^ q.y ^ q.z)*UI3;
	return vec3(q) * UIF;
}

//---------------------------------------------------------------------------------------------------------------
float hash13(vec3 p)
{
	uvec3 q = uvec3(ivec3(p)) * UI3;
	uint n = (q.x ^ q.y ^ q.z) * UI0;
	return float(n) * UIF;
}

//----------------------------------------------------------------------------------------------------------
// Flattened torus...
float disc( vec3 p, vec2 t )
{
  vec2 q = vec2(length(p.xz)-t.x,p.y*1.5);
  return length(q)-t.y;
}

//----------------------------------------------------------------------------------------------------------
vec3 getColour(vec3 id)
 {

   return cos(id*13.)*.4+.6;
}

float noise( in vec3 x )
{
    vec3 p = floor(x);
    vec3 f = fract(x);
    f = f*f*f*(f*(f*6.0-15.0)+10.0);
	
    return mix(mix(mix( hash13(p+vec3(0,0,0)), 
                        hash13(p+vec3(1,0,0)),f.x),
                   mix( hash13(p+vec3(0,1,0)), 
                        hash13(p+vec3(1,1,0)),f.x),f.y),
               mix(mix( hash13(p+vec3(0,0,1)), 
                        hash13(p+vec3(1,0,1)),f.x),
                   mix( hash13(p+vec3(0,1,1)), 
                        hash13(p+vec3(1,1,1)),f.x),f.y),f.z);
}

//-------------------------------------------------------------------------------------------------------

float smthU(float k, float d1, float d2)
{
    float h = clamp(0.5 + 0.5*(d2-d1)/k, 0.0, 1.0);
    return mix(d2, d1, h) - k*h*(1.0-h); 
}
//-------------------------------------------------------------------------------------------------------
vec2 boundingSphere(vec3  ro, vec3  rd,vec3 cen, float rad)
{
    ro = (ro - cen)/ rad;
    float d = dot(rd,ro);
    float c = dot(ro,ro) - 1.0;
    float h = d*d - c;

    
    if (h < 0.0 ) return vec2(-1); // Not intersecting
	
    h = sqrt( h );
    float t1 = -d - h;
    float t2 = -d + h;
    
    if( t2 < 0.0) return  vec2(-1);  //...Behind 

    t1 = max( t1, 0.0 );
    t2 = max( t2, 0.0 );

    return vec2(t1, t2) * rad; // ...Return front and back points rescaled.
}

//-------------------------------------------------------------------------------------------------------
float  mapGalaxy(vec3 p)
{
    float d = disc(p, vec2(55,35));
    float d2 = disc(p, vec2(85,15));
    d = min(d, d2);

    float d3 = length(p)-62.;
    d = smthU(20.0, d, d3);
    return d;
}

//----------------------------------------------------------------------------------------------------------
vec2 distanceRayPoint(vec3 ro, vec3 rd, vec3 p)
{
    p -= ro;
    float h = dot(p,rd);
    return vec2(length(p-rd*h), h);
}

//----------------------------------------------------------------------------------------------------------
vec3 getOffset(in vec3 id, float si)
{
    return clamp(tri(id*3.53), si*.25, (1.-si*.25));
}

//----------------------------------------------------------------------------------------------------------
vec3 renderStars(in vec3 ro, in vec3 rd, in vec2 bounds)
{ 
 
    vec2 d;
    ro += rd * bounds.x;
    int dist = int(bounds.y - bounds.x);

   
 	vec3 pos = floor(ro),
         ri = 1./rd,
		 rs = sign(rd),
		 dis = (pos-ro + 0.5 +rs*.5) * ri,
         sum = vec3(0), col = sum;
    
    // Go as far as the back of the bounding sphere
	for( int i = 0; i < dist; i++ )
    {
        vec3 id = hash33(pos*139.31);
        float si = (id.y+id.z)*.2+.1;

        vec3 offset = getOffset(id, si);

        d = distanceRayPoint(ro, rd, pos+offset);
        if (d.y > 0.0)
        {
            float  gal = mapGalaxy(pos+offset);
            if (gal < 0.0)
            {
                vec3 mat = getColour(id);
                float f = pow(smoothstep(si, 0.0,d.x), 20.0)*1.5;
                col = mat * f;
                sum += col;
                sum += vec3(-gal*.0001,-gal*.0001,-gal*.00002 );
                //fade *= .9;
            }
        }


		vec3 st = step(dis.xyz, dis.yzx) * step(dis.xyz, dis.zxy);
		dis += st * rs * ri;
        pos += st * rs;

	}
  
	return sum*.15;
}

//----------------------------------------------------------------------------------------------------------
vec3 getSky(vec3 pos)
{
	float t = 0.0, a = 3.;
    pos *= 1.;
    for (int i = 0; i < 5; i++)
    {
        float v = noise(pos);
        v = pow(abs(v-.5), 4.)*3.;
        t += v * a;
        pos *= 2.1;
        pos.xy *= mat2(.7, .5, -.5, .7);
        a *= .53;
        //pos.x *= pos.z;

    }
    //


	return vec3(t*.7, t*.7, t*.7);
}

//----------------------------------------------------------------------------------------------------------
// Do some basic camera positioning...
void getPath()
{
   float r = cos(gTime*.8+1.0)*50.0 + 80.0;
	camPos = vec3(sin(gTime*2.)*r+10., sin(gTime*1.5)*10.+5., cos(gTime*2.)*r);
    camTar = vec3(0,0,0);
 }

//----------------------------------------------------------------------------------------------------------
vec3 getCamera(vec2 uv)
{
    vec3 cw = normalize(camTar-camPos);
	vec3 cu = normalize(cross(cw,vec3(0,1,0)));
	vec3 cv = normalize(cross(cu,cw));
    return normalize(uv.x*cu + uv.y*cv + cw * .5);
}

//----------------------------------------------------------------------------------------------------------
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float gT = iTime *.25;

    #ifdef MOUSE_SCRUB
    gT += iMouse.x*30./iResolution.x;
    #endif
    // Got to do a single camera pass to find the bounding sphere...
    gTime = gT;
    getPath();
    vec2 uv = (fragCoord-iResolution.xy*.5)/iResolution.y;
    camRay = getCamera(uv);
    vec2 bounds = boundingSphere(camPos, camRay, vec3(0), 95.); 
    
    
    vec3 col = getSky(camRay); // Just a simple FBM background.
    if (bounds.x >= 0.)
    {
        #ifdef SHOW_BOUNDING
        col += vec3(.1,0,0);
        #endif
        for (int aa = 0; aa < 4; aa++)
        {
            vec2 co = (fragCoord*2. + vec2(aa & 1, (aa >> 1)));// Get anti-aliasing position

            
            gTime = gT- hash12(co)*iTimeDelta; //...Scatter the time line
            getPath();
            vec2 uv = (co-iResolution.xy)/iResolution.y;
            camRay = getCamera(uv);
            col += renderStars(camPos, camRay, bounds);
        }
    }
    
    col = clamp(col, 0.0, 1.0);
    col = col*col*(3.0-2.0*col); // ...Stretch for higher contrast
    col *= smoothstep(0.0, 4.0, iTime); //...Fade in

    
    
    fragColor = vec4(sqrt(col),1.0);
}
