
// Preintegrated transfer function from https://shadertoy.com/view/tsdcRj
// - Introduce transfer function ( i.e. LUT(dens) ) to shape the look (much like doctors do for scan data)
// - Rely on preintegrated density on segment.
//     inspired by preintegrated segment rendering ( see http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.10.3079&rep=rep1&type=pdf )
//     NB: they store a small texture(_dens,dens), but here I do it analytically.

#define hash(p)  fract(sin(dot(p, vec3(12.9898, 78.233, 311.7))) * 43758.5453) // https://www.shadertoy.com/view/llySRh
#define hash3(p) fract(sin((p)*mat3(127.1,311.7, 74.7,  269.5,183.3,246.1,  113.5,271.9,124.6))*43758.5453123)

//#define T(d)   texture(iChannel0, (i+d+.5)/32. ).x     // noise texture
  #define T(d)   hash(i+d)                               // value noise
//#define T(d) ( .5+ .7*dot( d-F ,  2.*hash3(i+d)-1. ) ) // gradient noise (Perlin noise)

float noise(vec3 x) {  // By default, simple 3D value noise with cubic interpolation
    vec3 i = floor(x), // Switch to gradient noise above if you wish, but little differences
         F = fract(x), e = vec3(1,0,0),
         f = smoothstep(0.,1.,F );
    vec4 T = mix ( vec4(T(e.zzz),T(e.zxz), T(e.zzx), T(e.zxx) ),
                   vec4(T(e.xzz),T(e.xxz), T(e.xzx), T(e.xxx) ),
                   f.x );
    vec2 v = mix( T.xz, T.yw, f.y);
    return mix(v.x,v.y,f.z);
        }

float add_noise(vec3 x) {
    float n = noise(x)/2.;  x *= 2.1; // return n*2.;
         n += noise(x)/4.;  x *= 1.9;
         n += noise(x)/8.;  x *= 2.3;
         n += noise(x)/16.; x *= 1.9;
         n += noise(x)/32.;
    return n; 
}

float mul_noise(vec3 x) {
    float n = 2.*noise(x);  x *= 2.1; // return n/2.;
         n *= 2.*noise(x);  x *= 1.9;
         n *= 2.*noise(x);  x *= 2.3;
         n *= 2.*noise(x);  x *= 1.9;
         n *= 2.*noise(x);
    return n/2.; 
}

float z;
float map(vec3 p )  // bounding sphere (0,0,0), 2.
{
    vec3 q = p*2.+ .6*iTime;
    q /= p.z < 0. ? vec3(1,1,4) :  vec3(4,4,1);
//  float f =  1.2*add_noise(q) -.2 ;// source noise
//  float f =  add_noise(q);
//  float f =  mul_noise(q);
    float f =  p.x < 0. ? add_noise(q) : mul_noise(q);
    f *= smoothstep(1.,.8,length(p)/2.);   // source sphere

    f*= smoothstep(.1,.2,abs(p.x));               // empty slice (derivable ) 
    z = length(p)/2.;                             // depth in sphere
    return f;                        
}

vec3 sundir = normalize( vec3(0,0,-1) );
vec2 coord;

#define SQR(x)   ( (x)*(x) )
#define CUB(x)   ( (x)*(x)*(x) )

  #define sl  200.                               // transition slope transp/opaque
  #define LUT(d) clamp( .5+sl*(d-.5), 0., 1. ) // transfer function

                                               // integral of transfer function
  #define intLUT(d0,d1) ( abs(d1-d0)<1e-5 ? 0. : ( I(d1) - I(d0) ) / (d1-d0) ) 
  #define C(d)    clamp( d, .5-.5/sl, .5+.5/sl )
  #define I0(d) ( .5*d + sl*SQR(d-.5)/2. )
  #define I(d)  ( I0(C(d)) + max(0.,d-(.5+.5/sl)) )

float LUTs( float _d, float d ) { // apply either the simple or integrated transfer function
    return intLUT(_d,d);
/*  return coord.x > 0. 
             ?  LUT(d)        // right: just apply transfert function
             :  intLUT(_d,d); // left: preintegrated transfert function
*/
}

float intersect_sphere( vec3 O, vec3 D, vec3 C, float r )
{
	float b = dot( O-=C, D ),
	      h = b*b - dot( O, O ) + r*r;
	return h < 0. ? -1.             // no intersection
	              : -b - sqrt(h);
}

vec4 raymarch( vec3 ro, vec3 rd, vec3 bgcol, ivec2 px )
{
	vec4 sum = vec4(0);
	float dt = .01,
         den = 0., _den, lut,
           t = intersect_sphere( ro, rd, vec3(0), 2.);
    if ( t == -1. ) return vec4(0); // the ray misses the object 
    t += 1e-5;                      // start on bounding sphere
    
    for(int i=0; i<500; i++) {
        vec3 pos = ro + t*rd;
        if(   sum.a > .99               // end if opaque or...
           || length(pos) > 2. ) break; // ... exit bounding sphere
                                    // --- compute deltaInt-density
        _den = den; den = map(pos); // raw density
        float _z = z;               // depth in object
        lut = LUTs( _den, den );    // shaped through transfer function
        if( lut > .0                // optim
          ) {                       // --- compute shading                  
#if 0                               // finite differences
            vec2 e = vec2(.3,0);
            vec3 n = normalize( vec3( map(pos+e.xyy) - den,
                                      map(pos+e.yxy) - den,
                                      map(pos+e.yyx) - den ) );
         // see also: centered tetrahedron difference: https://iquilezles.org/articles/normalsSDF
            float dif = clamp( -dot(n, sundir), 0., 1.);
#else                               // directional difference https://iquilezles.org/articles/derivative
         // float dif = clamp((lut - LUTs(_den, map(pos+.3*sundir)))/.6, 0., 1. ); // pseudo-diffuse using 1D finite difference in light direction 
            float dif = clamp((den - map(pos+.3*sundir))/.6, 0., 1. );             // variant: use raw density field to evaluate diffuse
#endif
/*
            vec3  lin = vec3(.65,.7,.75)*1.4 + vec3(1,.6,.3)*dif,          // ambiant + diffuse
                  col = vec3(.2 + dif);
            col = mix( col , bgcol, 1.-exp(-.003*t*t) );   // fog
*/            
            vec3 S = pos.x < 0. ? vec3(3,3,2) : vec3(2,3,3);
            vec3 col = exp(-S *(1.-z));                   // dark with shadow
         // vec3 col =   exp(- vec3(3,3,2) *(.8-_z));     // dark with depth
                   //      *  exp(- 1.5 *(1.-z));
            sum += (1.-sum.a) * vec4(col,1)* (lut* dt*5.); // --- blend. Original was improperly just den*.4;
        }
        t += dt;  // stepping
    }

    return sum; 
}

mat3 setCamera( vec3 ro, vec3 ta, float cr )
{
	vec3 cw = normalize(ta-ro),
	     cp = vec3(sin(cr), cos(cr),0),
	     cu = normalize( cross(cw,cp) ),
	     cv = cross(cu,cw);
    return mat3( cu, cv, cw );
}

vec4 render( vec3 ro, vec3 rd, ivec2 px )
{
    // background sky  
	float sun = max( dot(sundir,rd), 0. );
	vec3 col = // vec3(.6,.71,.75) - rd.y*.2*vec3(1,.5,1) + .15*.5
	           //  + .2*vec3(1,.6,.1)*pow( sun, 8. );
            +  vec3( .8 * pow( sun, 8. ) ); // dark variant

    // clouds    
    vec4 res = raymarch( ro, rd, col, px );  // render clouds
    col = res.rgb + col*(1.-res.a);          // blend sky
    
    // sun glare    
	col += .2*vec3(1,.4,.2) * pow( sun,3.);

    return vec4( col, 1. );
}

void mainImage( out vec4 O, vec2 U )
{
    vec2 R = iResolution.xy,
         p = ( 2.*U - R ) / R.y,
         m = iMouse.z>0. ? 2.* ( 1.- iMouse.xy / R )
                         : 1.+cos(.3*iTime+vec2(0,11));
    coord = p;
 // O = vec4( map(vec3(4.*p,0)) ); return;
    
    // camera
    vec3 ro = 4.*normalize(vec3(sin(3.*m.x), m.y, cos(3.*m.x))),
	     ta = vec3(0, 0, 0);
    mat3 ca = setCamera( ro, ta, 0. );
    // ray
    vec3 rd = ca * normalize( vec3(p,1.5) );
    
    O = render( ro, rd, ivec2(U-.5) );
 // if (floor(U.x)==floor(R.x/2.)) O = vec4(1,0,0,1); // red separator
}

#define mainVR(O,U,C,D) O = render( C, D, ivec2(U-.5) )
