// https://www.shadertoy.com/view/3d2XRc
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#define GRID_SIZE 10.0
#define SHADING_METHOD_SEED 112.7
#define SHADING_METHOD_DURATION_TIME 8.0
#define SHADING_METHOD_TRANSITION_TIME 2.0
#define OFFSET_FUNTCION_SEED 22.4
#define OFFSET_FUNTCION_DURATION_TIME 16.
#define OFFSET_FUNTCION_TRANSITION_TIME 2.0
#define PALETTE_SEED 324.6
#define PALETTE_DURATION_TIME 10.
#define PALETTE_TRANSITION_TIME 2.0
#define PI 3.14159265

float hash11(float p){p = fract(p * .1031);p *= p + 19.19;p *= p + p;return fract(p);}
float hash12(vec2 p){ vec3 p3  = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 19.19);return fract((p3.x + p3.y) * p3.z);}
vec2 hash21(float p){vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973));p3 += dot(p3, p3.yzx + 19.19);return fract((p3.xx+p3.yz)*p3.zy);}
mat2 rotate2d(float _angle){return mat2(cos(_angle),-sin(_angle),sin(_angle),cos(_angle));}

float panSequence = 0.;
float rotateSequence = 0.;
float shadingMethodTick = 0.;
float shadingMethodTransition = 0.;
float offsetFunctionTick = 0.;
float offsetFunctionTransition = 0.;
float paletteTick = 0.;
float paletteTransition = 0.;

void sequence()
{
  panSequence =    smoothstep(0.0, 4.0, mod(iTime, 20.)) - smoothstep(8.0, 12.0, mod(iTime, 20.));
  rotateSequence = smoothstep(0.0, 3.0, mod(iTime, 10.)) - smoothstep(6.0,  9.0, mod(iTime, 10.));
  shadingMethodTick = floor(iTime / SHADING_METHOD_DURATION_TIME);
  shadingMethodTransition = smoothstep(0.0, SHADING_METHOD_TRANSITION_TIME, mod(iTime, SHADING_METHOD_DURATION_TIME));
  offsetFunctionTick = floor((iTime + OFFSET_FUNTCION_TRANSITION_TIME) / OFFSET_FUNTCION_DURATION_TIME);
  offsetFunctionTransition = smoothstep(0.0, OFFSET_FUNTCION_TRANSITION_TIME, mod((iTime + OFFSET_FUNTCION_TRANSITION_TIME), OFFSET_FUNTCION_DURATION_TIME));
  paletteTick = floor(iTime / PALETTE_DURATION_TIME);
  paletteTransition = smoothstep(0.0, PALETTE_TRANSITION_TIME, mod(iTime, PALETTE_DURATION_TIME));
}
// iq's palette function
vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d)
{
    return a + b * cos(2. * PI * (c * t + d));
}
vec3 randomPalette(float seed, float uniqCellSeed)
{
  switch(int(floor(seed * 4.)))
  {
      case 0: return palette(uniqCellSeed, vec3(0.8, 0.5, 0.4), vec3(0.2, 0.4, 0.2), vec3(2.0, 1.0, 1.0), vec3(0.0, 0.25, 0.25));
      case 1: return palette(uniqCellSeed, vec3(0.2, 0.3, 0.7), vec3(0.7, 0.7, 0.5), vec3(1. , 1. , 0.8), vec3(0.4, 0.33, 0.5 ));
      case 2: return palette(uniqCellSeed, vec3(0.5, 0.4, 0.5), vec3(0.5, 0.5, 0.5), vec3(1. , 1. , 1. ), vec3(0.3, 0.6, 0.6));
      case 3: return palette(uniqCellSeed, vec3(0.5, 0.8, 0.5), vec3(0.6, 0.4, 0.8), vec3(1. , 0.8 , 0.4 ), vec3(1., 1., 0.1));
  }
}
vec3 randomColor(float uniqCellSeed)
{
    return mix(
      randomPalette(hash12(vec2(PALETTE_SEED, paletteTick     )), uniqCellSeed),
      randomPalette(hash12(vec2(PALETTE_SEED, paletteTick + 1.)), uniqCellSeed),
      paletteTransition);
}
float isoLine(float dist, float lineScale){return abs(sin(lineScale * dist));}
// -------------- different ways to render voronoi data  ------------------
vec3 randomShadingMethod(vec4 voronoiData, float seed, float uniqCellSeed)
{
    float angle = abs(dot(normalize(voronoiData.xy), normalize(voronoiData.wz)));
    float determinantValue = abs(voronoiData.x * voronoiData.w - voronoiData.z * voronoiData.y);
    switch (int(floor(seed * 8.)))
    {
        case 0: return vec3(isoLine(length(voronoiData.zw), 20.)) * length(voronoiData.zw);
        case 1: return isoLine(length(voronoiData.xy), 20.) * randomColor(uniqCellSeed);
        case 2: return vec3(mix(0., 1., smoothstep(0.07, 0.14, length(voronoiData.xy)))) * randomColor(uniqCellSeed);
        case 3: return angle * randomColor(uniqCellSeed);
        case 4: return determinantValue * randomColor(uniqCellSeed);
        case 5: return length(voronoiData.xy) / length(voronoiData.wz) * randomColor(uniqCellSeed);\
        case 6: return min(length(voronoiData.xy), length(voronoiData.wz)) * randomColor(uniqCellSeed);
        case 7: return smoothstep(0., 0.1, length(voronoiData.xy) - length(voronoiData.wz)) * randomColor(uniqCellSeed);
    } 
}
vec3 getVoronoiColor(vec4 voronoiData, float uniqCellSeed)
{
   return mix(
      randomShadingMethod(voronoiData, hash12(vec2(SHADING_METHOD_SEED, shadingMethodTick     )), uniqCellSeed),
      randomShadingMethod(voronoiData, hash12(vec2(SHADING_METHOD_SEED, shadingMethodTick + 1.)), uniqCellSeed),
      shadingMethodTransition);
}
vec2 getRandomCellOffsetFunction(vec2 pos, float seed, float uniqCellSeed)
{
   switch (int(floor(seed * 4.0)))
   {
       case 0: return vec2(sin(pos.y + iTime * 2.), cos(pos.x + iTime * 2.)) * normalize(pos + vec2(0.0001));
       case 1: return hash21(uniqCellSeed) * vec2(mod(pos.y, 3.) * cos(iTime * 2.), mod(pos.x, 3.) * sin(iTime * 2.));
       case 2: return vec2(0.4 * sin(iTime + PI * 2. * hash21(uniqCellSeed)));
       case 3: return vec2(uniqCellSeed);
   }
}
vec2 getCellOffsetFunction(vec2 pos, float uniqCellSeed)
{
    vec2 cellOffset = mix(
      getRandomCellOffsetFunction(pos, hash12(vec2(OFFSET_FUNTCION_SEED, offsetFunctionTick     )), uniqCellSeed),
      getRandomCellOffsetFunction(pos, hash12(vec2(OFFSET_FUNTCION_SEED, offsetFunctionTick + 1.)), uniqCellSeed),
      offsetFunctionTransition);
   float halfGridCellSideLength = 0.4999;
   return clamp(cellOffset, -halfGridCellSideLength, halfGridCellSideLength);
}
vec4 voronoiGrid(vec2 pixelCoord, out float outCellSeed)
{
    vec2 centerOfCellFromGridOffset = vec2(0.5, 0.5);
    vec2 gridLocation = floor(pixelCoord);
    vec2 normalizedLocalGridCoord = fract(pixelCoord);

    float minimumDistanceToCenter = GRID_SIZE;
    vec2 minimumgridOffset, minimumVectorToAdjacentVoronoiCellCenter;
    // first pass min distance from cell center
    for(int j=-2; j<=2; j++)
    for(int i=-2; i<=2;  i++)
    {
       vec2 gridOffset = vec2(float(i), float(j));
        
       vec2 adajacentCell = gridLocation + gridOffset;
        
       float cellSeed = hash12(adajacentCell);
        
       vec2 offset = getCellOffsetFunction(adajacentCell, cellSeed) + centerOfCellFromGridOffset;
        
       vec2 vectorToAdjacentVoronoiCellCenter = (gridOffset + offset) - normalizedLocalGridCoord;
       float distanceToCenter = length(vectorToAdjacentVoronoiCellCenter);
       if (distanceToCenter < minimumDistanceToCenter) {
           outCellSeed = cellSeed;
           minimumDistanceToCenter = distanceToCenter;
           minimumgridOffset = gridOffset;
           minimumVectorToAdjacentVoronoiCellCenter = vectorToAdjacentVoronoiCellCenter;
       }
    }
    // second pass min distance to border
    float minimumDistanceToBorder = GRID_SIZE;
    vec2 minimumVectorToVoronoiCellBorder;
    for(int j=-2; j<=2; j++)
    for(int i=-2; i<=2; i++)
    {
       vec2 gridOffset = minimumgridOffset + vec2(float(i), float(j));
        
       vec2 adajacentCell = gridLocation + gridOffset;
        
       float cellSeed = hash12(adajacentCell);
        
       vec2 offset = getCellOffsetFunction(adajacentCell, cellSeed) + centerOfCellFromGridOffset;
        
       vec2 vectorToAdjacentVoronoiCellCenter = (gridOffset + offset) - normalizedLocalGridCoord;
        
       // skip if same point
       if( dot(minimumVectorToAdjacentVoronoiCellCenter - vectorToAdjacentVoronoiCellCenter,
               minimumVectorToAdjacentVoronoiCellCenter - vectorToAdjacentVoronoiCellCenter) > 0.00001)
       {
          vec2 segmentNormal = normalize(vectorToAdjacentVoronoiCellCenter - minimumVectorToAdjacentVoronoiCellCenter);
          float scalarProjection = dot(0.5 * (minimumVectorToAdjacentVoronoiCellCenter + vectorToAdjacentVoronoiCellCenter),
                      segmentNormal);
          if (scalarProjection < minimumDistanceToBorder) {
              vec2 vectorProjection = segmentNormal * scalarProjection;
              minimumVectorToVoronoiCellBorder = vectorProjection;
              minimumDistanceToBorder = scalarProjection;
          }
       }
    }
    return vec4(minimumVectorToVoronoiCellBorder, minimumVectorToAdjacentVoronoiCellCenter);
}

vec2 rotate(vec2 pixelCoord)
{
    float aspect = (iResolution.y / iResolution.x);
    vec2 centerShift = vec2(0.5, 0.5 * aspect);
    float speed = 0.15;
    pixelCoord -= centerShift;
    pixelCoord = rotate2d(rotateSequence * PI * speed) * pixelCoord;
    pixelCoord += centerShift;
    return pixelCoord;
}
vec2 pan(vec2 pixelCoord)
{ 
  float seed = floor(iTime / 20.);
  float speed = mix(0., 1.0, panSequence);
  return pixelCoord + normalize(vec2(hash21(seed)) + 0.0001) * speed;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    sequence();
    
    vec2 pixelCoord = (gl_FragCoord.xy - 0.5 * iResolution.xy)/iResolution.xy;
    pixelCoord.x *= iResolution.x/ iResolution.y;

    pixelCoord = rotate(pixelCoord);
    pixelCoord = pan(pixelCoord);
    pixelCoord *= GRID_SIZE;
    
    float uniqCellSeed;
    vec4 voronoiResult = voronoiGrid(pixelCoord, uniqCellSeed);

    fragColor = vec4(getVoronoiColor(voronoiResult, uniqCellSeed), 1.0f);
}