#ifndef POI_VORONOI
    #define POI_VORONOI
    
    #include "CGI_PoiRNG.cginc"
    
    uint _VoronoiSpace;
    uint _VoronoiBlend;
    uint _VoronoiType;
    float4 _VoronoiColor0;
    float _VoronoiEmission0;
    float4 _VoronoiColor1;
    float _VoronoiEmission1;
    float2 _VoronoiGradient;
    float _VoronoiScale;
    float3 _VoronoiSpeed;
    int _VoronoiOctaveNumber;
    float _VoronoiOctaveScale;
    float _VoronoiOctaveAttenuation;
    float _VoronoiEnableRandomCellColor;
    float2 _VoronoiRandomMinMaxSaturation;
    float2 _VoronoiRandomMinMaxBrightness;
    float3 randomPoint;
    
    POI_TEXTURE_NOSAMPLER(_VoronoiMask);
    POI_TEXTURE_NOSAMPLER(_VoronoiNoise);
    float _VoronoiNoiseIntensity;
    
    float2 inoise(float3 P, float jitter)
    {
        float3 Pi = mod(floor(P), 289.0);
        float3 Pf = frac(P);
        float3 oi = float3(-1.0, 0.0, 1.0);
        float3 of = float3(-0.5, 0.5, 1.5);
        float3 px = Permutation(Pi.x + oi);
        float3 py = Permutation(Pi.y + oi);
        
        float3 p, ox, oy, oz, dx, dy, dz;
        float2 F = 1e6;
        
        for (int i = 0; i < 3; i ++)
        {
            for (int j = 0; j < 3; j ++)
            {
                p = Permutation(px[i] + py[j] + Pi.z + oi); // pij1, pij2, pij3
                
                ox = frac(p * K) - Ko;
                oy = mod(floor(p * K), 7.0) * K - Ko;
                
                p = Permutation(p);
                
                oz = frac(p * K) - Ko;
                
                dx = Pf.x - of[i] + jitter * ox;
                dy = Pf.y - of[j] + jitter * oy;
                dz = Pf.z - of + jitter * oz;
                
                float3 d = dx * dx + dy * dy + dz * dz; // dij1, dij2 and dij3, squared
                
                //Find lowest and second lowest distances
                for (int n = 0; n < 3; n ++)
                {
                    if (d[n] < F[0])
                    {
                        F[1] = F[0];
                        F[0] = d[n];
                        randomPoint = p;
                    }
                    else if(d[n] < F[1])
                    {
                        F[1] = d[n];
                    }
                }
            }
        }
        
        return F;
    }
    
    float voronoi2D(in float2 x, float scale, float2 speed)
    {
        x *= scale;
        x += speed * _Time.x;
        float2 n = floor(x);
        float2 f = frac(x);
        
        // first pass: regular voronoi
        float2 mg, mr;
        float md = 8.0;
        for (int j = -1; j <= 1; j ++)
        {
            for (int i = -1; i <= 1; i ++)
            {
                float2 g = float2(float(i), float(j));
                float2 o = random2(n + g);
                float2 currentPoint = o;
                
                float2 r = g + o - f;
                float d = dot(r, r);
                
                if (d < md)
                {
                    md = d;
                    mr = r;
                    mg = g;
                    randomPoint.xy = currentPoint;
                }
            }
        }
        
        // second pass: distance to borders
        md = 8.0;
        for (int r = -2; r <= 2; r ++)
        {
            for (int q = -2; q <= 2; q ++)
            {
                float2 g = mg + float2(float(q), float(r));
                float2 o = random2(n + g);
                
                float2 r = g + o - f;
                
                if (dot(mr - r, mr - r) > 0.00001)
                {
                    md = min(md, dot(0.5 * (mr + r), normalize(r - mr)));
                }
            }
        }
        return md;
    }
    
    float voronoi3D(in float3 x, float scale, float3 speed)
    {
        x *= scale;
        x += speed * _Time.x;
        float3 n = floor(x);
        float3 f = frac(x);
        
        // first pass: regular voronoi
        float3 mg, mr;
        float md = 8.0;
        for (int j = -1; j <= 1; j ++)
        {
            for (int i = -1; i <= 1; i ++)
            {
                for (int h = -1; h <= 1; h ++)
                {
                    float3 g = float3(float(h), float(i), float(j));
                    float3 o = random3(n + g);
                    float3 currentPoint = o;
                    
                    float3 r = g + o - f;
                    float d = dot(r, r);
                    
                    if (d < md)
                    {
                        md = d;
                        mr = r;
                        mg = g;
                        randomPoint = currentPoint;
                    }
                }
            }
        }
        
        // second pass: distance to borders
        md = 8.0;
        for (int r = -2; r <= 2; r ++)
        {
            for (int q = -2; q <= 2; q ++)
            {
                for (int p = -2; p <= 2; p ++)
                {
                    float3 g = mg + float3(float(p), float(q), float(r));
                    float3 o = random3(n + g);
                    
                    float3 r = g + o - f;
                    
                    if (dot(mr - r, mr - r) > 0.00001)
                    {
                        md = min(md, dot(0.5 * (mr + r), normalize(r - mr)));
                    }
                }
            }
        }
        return md;
    }
    
    
    
    // fracal sum, range -1.0 - 1.0
    float VoronoiNoise_Octaves(float3 p, float scale, float3 speed, int octaveNumber, float octaveScale, float octaveAttenuation, float jitter, float time)
    {
        float freq = scale;
        float weight = 1.0f;
        float sum = 0;
        for (int i = 0; i < octaveNumber; i ++)
        {
            float2 F = inoise(p * freq + time * speed, jitter) * weight;
            
            sum += sqrt(F[0]);
            
            freq *= octaveScale;
            weight *= 1.0f - octaveAttenuation;
        }
        return sum;
    }
    
    float VoronoiNoiseDiff_Octaves(float3 p, float scale, float3 speed, int octaveNumber, float octaveScale, float octaveAttenuation, float jitter, float time)
    {
        float freq = scale;
        float weight = 1.0f;
        float sum = 0;
        for (int i = 0; i < octaveNumber; i ++)
        {
            float2 F = inoise(p * freq + time * speed, jitter) * weight;
            
            sum += sqrt(F[1]) - sqrt(F[0]);
            
            freq *= octaveScale;
            weight *= 1.0f - octaveAttenuation;
        }
        return sum;
    }
    
    void applyVoronoi(inout float4 finalColor, inout float3 VoronoiEmission)
    {
        _VoronoiOctaveNumber = 1;
        _VoronoiOctaveScale = 1;
        _VoronoiOctaveAttenuation = 1;
        randomPoint = 0;
        
        float voronoi = 0;
        
        float3 position = 0;
        
        UNITY_BRANCH
        if (_VoronoiSpace == 0)
        {
            position = poiMesh.localPos;
        }
        UNITY_BRANCH
        if(_VoronoiSpace == 1)
        {
            position = poiMesh.worldPos;
        }
        UNITY_BRANCH
        if(_VoronoiSpace == 2)
        {
            position = float3(poiMesh.uv[0].x, poiMesh.uv[0].y, 0);
        }
        
        float mask = POI2D_SAMPLER_PAN(_VoronoiMask, _MainTex, poiMesh.uv[_VoronoiMaskUV], _VoronoiMaskPan).r;
        float edgeNoise = POI2D_SAMPLER_PAN(_VoronoiNoise, _MainTex, poiMesh.uv[_VoronoiNoiseUV], _VoronoiNoisePan).r * _VoronoiNoiseIntensity;
        
        UNITY_BRANCH
        if(_VoronoiType == 0) // Basic
        {
            voronoi = voronoi2D(position.xy, _VoronoiScale, _VoronoiSpeed);
        }
        UNITY_BRANCH
        if (_VoronoiType == 1) // Diff
        {
            voronoi = VoronoiNoiseDiff_Octaves(position, _VoronoiScale, _VoronoiSpeed, _VoronoiOctaveNumber, _VoronoiOctaveScale, _VoronoiOctaveAttenuation, 1, _Time.x);
        }
        UNITY_BRANCH
        if (_VoronoiType == 2) // Fixed Border
        {
            voronoi = voronoi3D(position, _VoronoiScale, _VoronoiSpeed);
            // isolines
            //color = c.x * (0.5 + 0.5 * sin(64.0 * c.x)) * 1.0;
        }
        
        if (_VoronoiEnableRandomCellColor == 1)
        {
            float3 rando = random3(randomPoint);
            fixed hue = rando.x;
            fixed saturation = lerp(_VoronoiRandomMinMaxSaturation.x, _VoronoiRandomMinMaxSaturation.y, rando.y);
            fixed value = lerp(_VoronoiRandomMinMaxBrightness.x, _VoronoiRandomMinMaxBrightness.y, rando.z);
            float3 hsv = float3(hue, saturation, value);
            _VoronoiColor1.rgb = HSVtoRGB(hsv);
        }
        _VoronoiGradient.xy += edgeNoise;
        float ramp = smoothstep(_VoronoiGradient.x, _VoronoiGradient.y, voronoi);
        
        UNITY_BRANCH
        if(_VoronoiBlend == 0)
        {
            float4 voronoiColor = lerp(_VoronoiColor0, _VoronoiColor1, ramp);
            finalColor.rgb = lerp(finalColor.rgb, voronoiColor, mask * voronoiColor.a);
        }
        float4 voronoiEmissionColor = lerp(_VoronoiColor0 * _VoronoiEmission0, _VoronoiColor1 * _VoronoiEmission1, ramp);
        VoronoiEmission = voronoiEmissionColor.rgb * mask * voronoiEmissionColor.a;
    }
    
#endif