#[compute]
#version 450

float clamp01( float value )
{
  return clamp( value, 0.0, 1.0 );
}

float normalizeToRange( float value, float min, float max )
{
  return ( value - min ) / ( max - min );
}

float normalizeToRange01( float value, float min, float max )
{
  return clamp01( normalizeToRange( value, min, max ) ); 
}

float map( float value, float inMin, float inMax, float outMin, float outMax )
{
  return mix( outMin, outMax, normalizeToRange( value, inMin, inMax ) );
}

float mapClamped( float value, float inMin, float inMax, float outMin, float outMax )
{
  return mix( outMin, outMax, normalizeToRange01( value, inMin, inMax ) );
}

vec3 applyMatrix( vec3 v, mat4 m )
{
  return ( m * vec4( v, 1.0 ) ).xyz;
}

vec3 applyMatrixWithoutTranslation( vec3 v, mat4 m )
{
  return ( m * vec4( v, 0.0 ) ).xyz;
}

vec3 applyMatrix( vec3 v, mat3 m )
{
  return ( m * v ).xyz;
}


vec3 localToWorld( vec3 local, mat4 _MODEL_MATRIX )
{
  return applyMatrix( local, _MODEL_MATRIX );
}

vec3 localToWorldDirection( vec3 local, mat4 _MODEL_MATRIX )
{
  return applyMatrixWithoutTranslation( local, _MODEL_MATRIX );
}

vec3 localToView( vec3 local, mat4 _MODELVIEW_MATRIX )
{
  return applyMatrix( local, _MODELVIEW_MATRIX );
}

vec3 localToViewDirection( vec3 local, mat4 _MODELVIEW_MATRIX )
{
  return applyMatrixWithoutTranslation( local, _MODELVIEW_MATRIX );
}

vec3 localToViewDirection( vec3 local, mat3 _MODELVIEW_NORMAL_MATRIX )
{
  return applyMatrix( local, _MODELVIEW_NORMAL_MATRIX );
}

vec3 worldToLocal( vec3 world, mat4 _MODEL_MATRIX )
{
  mat4 inversedMatrix = inverse( _MODEL_MATRIX );
  return applyMatrix( world, inversedMatrix );
}

vec3 worldToLocalDirection( vec3 world, mat4 _MODEL_MATRIX )
{
  mat4 inversedMatrix = inverse( _MODEL_MATRIX );
  return applyMatrixWithoutTranslation( world, inversedMatrix );
}


vec3 worldToView( vec3 world, mat4 _VIEW_MATRIX )
{  
  return applyMatrix( world, _VIEW_MATRIX );
}

vec3 worldToViewDirection( vec3 worldDirection, mat4 _VIEW_MATRIX )
{
  return applyMatrixWithoutTranslation( worldDirection, _VIEW_MATRIX );
}

vec3 viewToWorld( vec3 view, mat4 _INV_VIEW_MATRIX )
{
  return applyMatrix( view, _INV_VIEW_MATRIX );
}

vec3 viewToWorldDirection( vec3 viewDirection, mat4 _INV_VIEW_MATRIX )
{
  return applyMatrixWithoutTranslation( viewDirection, _INV_VIEW_MATRIX );
}

vec3 viewToLocal( vec3 view, mat4 _INV_VIEW_MATRIX, mat4 _MODEL_MATRIX )
{
  vec3 world = viewToWorld( view, _INV_VIEW_MATRIX );
  return worldToLocal( world, _MODEL_MATRIX );
}

vec3 viewToLocal( vec3 view, mat4 _MODELVIEW_MATRIX )
{
  mat4 inversedMatrix = inverse( _MODELVIEW_MATRIX );
  return applyMatrix( view, inversedMatrix );
}

vec3 viewToLocalDirection( vec3 viewDirection, mat4 _MODELVIEW_MATRIX )
{
  mat4 inversedMatrix = inverse( _MODELVIEW_MATRIX );
  return applyMatrixWithoutTranslation( viewDirection, inversedMatrix );
}

vec3 viewToLocalDirection( vec3 view, mat4 _INV_VIEW_MATRIX, mat4 _MODEL_MATRIX )
{
  vec3 world = viewToWorldDirection( view, _INV_VIEW_MATRIX );
  return worldToLocalDirection( world, _MODEL_MATRIX );
}

vec4 viewToClip( vec3 view, mat4 _PROJECTION_MATRIX )
{
  vec4 clip = _PROJECTION_MATRIX * vec4( view, 1.0 ); 
  clip /= clip.w;
  return clip;
}

vec2 clipToScreen( vec4 clip )
{
  return ( clip.xy / clip.w ) * 0.5 + vec2( 0.5 );
}

vec4 screenToClip( vec2 screen, float z )
{
  return vec4( screen * 2.0 - 1.0, z, 1.0 );
}

vec4 clipToView( vec4 clip, mat4 _INV_PROJECTION_MATRIX )
{
  vec4 view = _INV_PROJECTION_MATRIX * clip;
  view /= view.w;
  return view;
}

vec4 screenToView( vec2 screen, float z, mat4 _INV_PROJECTION_MATRIX )
{
  vec4 clip = screenToClip( screen, z );
  vec4 view = clipToView( clip, _INV_PROJECTION_MATRIX );

  return view;
}

vec3 screenToWorld( vec2 screen, float z, mat4 _INV_PROJECTION_MATRIX, mat4 _INV_VIEW_MATRIX  )
{
  vec4 view = screenToView( screen, z, _INV_PROJECTION_MATRIX );

  return viewToWorld( view.xyz, _INV_VIEW_MATRIX );
}

vec3 screenToLocal( vec2 screen_point, float z, mat4 _PROJECTION_MATRIX, mat4 _VIEW_MATRIX, mat4 _MODEL_MATRIX )
{
  vec4 clip = vec4( screen_point * 2.0 - 1.0, z, 1.0 );
  vec4 view4 = inverse( _PROJECTION_MATRIX ) * clip;
  view4 /= view4.w;
  vec3 view = view4.xyz;
  mat4 m = inverse( _VIEW_MATRIX * _MODEL_MATRIX );
  return ( m * vec4( view, 1.0 ) ).xyz;
}

vec2 viewToScreen( vec3 view, mat4 _PROJECTION_MATRIX )
{
  vec4 clip = viewToClip( view, _PROJECTION_MATRIX );

  return clipToScreen( clip );
}

vec2 localToScreen( vec3 local, mat4 _MODELVIEW_MATRIX, mat4 _PROJECTION_MATRIX )
{
  vec3 view = localToView( local, _MODELVIEW_MATRIX );
  return viewToScreen( view, _PROJECTION_MATRIX );
}

vec2 worldToScreen( vec3 world, mat4 _VIEW_MATRIX, mat4 _PROJECTION_MATRIX )
{
  vec3 view   = worldToView( world, _VIEW_MATRIX );
  vec4 clip   = viewToClip( view, _PROJECTION_MATRIX );
  vec2 screen = clipToScreen( clip );

  return screen;
}


vec3 extractTranslation( mat4 matrix )
{
  return vec3( matrix[ 3 ][ 0 ], matrix[ 3 ][ 1 ], matrix[ 3 ][ 2 ] );
}

vec3 extractScale( mat3 matrix )
{
  mat3 m = matrix;
  
  float x = length( vec3( m[ 0 ][ 0 ], m[ 1 ][ 0 ], m[ 2 ][ 0 ] ) ); 
  float y = length( vec3( m[ 0 ][ 1 ], m[ 1 ][ 1 ], m[ 2 ][ 1 ] ) ); 
  float z = length( vec3( m[ 0 ][ 2 ], m[ 1 ][ 2 ], m[ 2 ][ 2 ] ) ); 

  return vec3( x, y, z );
}

vec3 extractScale( mat4 matrix )
{
  mat4 m = matrix;
  
  float x = length( vec3( m[ 0 ][ 0 ], m[ 1 ][ 0 ], m[ 2 ][ 0 ] ) ); 
  float y = length( vec3( m[ 0 ][ 1 ], m[ 1 ][ 1 ], m[ 2 ][ 1 ] ) ); 
  float z = length( vec3( m[ 0 ][ 2 ], m[ 1 ][ 2 ], m[ 2 ][ 2 ] ) ); 

  return vec3( x, y, z );
}

mat3 extractRotationMatrix( mat4 m )
{
  vec3 x = normalize( m[ 0 ].xyz ); 
  vec3 y = normalize( m[ 1 ].xyz ); 
  vec3 z = normalize( m[ 2 ].xyz ); 

  return mat3( x, y, z );
}


layout( local_size_x = 8, local_size_y = 8, local_size_z = 1 ) in;

layout( set = 0, binding = 0 ) 
uniform sampler2D depthSampler;

layout( rgba16, set = 1, binding = 0 ) 
uniform restrict writeonly image2D outputImage;


layout( push_constant, std430 ) 
uniform Parameters 
{
  vec4 m0;
  vec4 m1;
  vec4 m2;
  vec4 m3;

  float inputZMin;
  float inputZMax;
  float outputZMin;
  float outputZMax;

} parameters;

float getZ( vec2 uv, float depth, mat4 INV_PROJ )
{
  vec4 position = screenToView( uv, depth, INV_PROJ );  
  return max( 0.0, -position.z );
}
 
void main() 
{
  ivec2 size = imageSize( outputImage );
	ivec2 xy = ivec2( gl_GlobalInvocationID.xy );

  if ( any( greaterThanEqual( xy, size ) )  ) 
  {
    return;
  }

  vec2 uv = ( vec2( xy ) + vec2( 0.5 ) ) / vec2( size ); 
  
  mat4 INV_PROJ = mat4( parameters.m0, parameters.m1, parameters.m2, parameters.m3 );

  float depth = texture( depthSampler, uv ).r;
  float z = getZ( uv, depth, INV_PROJ );
  
  float mappedZ = mapClamped( z, parameters.inputZMin, parameters.inputZMax, parameters.outputZMin, parameters.outputZMax );

  vec4 color = vec4( mappedZ, mappedZ, mappedZ, 1.0 );

	imageStore( outputImage, xy, color );
}
