#ifndef MODERNPSX_END_VOID_GLSL
#define MODERNPSX_END_VOID_GLSL

float endVoidHash12(vec2 value) {
    vec3 p3 = fract(vec3(value.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

float endVoidHash13(vec3 value) {
    value = fract(value * 0.1031);
    value += dot(value, value.zyx + 31.32);
    return fract((value.x + value.y) * value.z);
}

float endVoidNoise(vec3 value) {
    vec3 cell = floor(value);
    vec3 local = fract(value);
    vec3 blend = local * local * (3.0 - 2.0 * local);
    float x00 = mix(endVoidHash13(cell),
                    endVoidHash13(cell + vec3(1.0, 0.0, 0.0)), blend.x);
    float x10 = mix(endVoidHash13(cell + vec3(0.0, 1.0, 0.0)),
                    endVoidHash13(cell + vec3(1.0, 1.0, 0.0)), blend.x);
    float x01 = mix(endVoidHash13(cell + vec3(0.0, 0.0, 1.0)),
                    endVoidHash13(cell + vec3(1.0, 0.0, 1.0)), blend.x);
    float x11 = mix(endVoidHash13(cell + vec3(0.0, 1.0, 1.0)),
                    endVoidHash13(cell + vec3(1.0, 1.0, 1.0)), blend.x);
    return mix(mix(x00, x10, blend.y),
               mix(x01, x11, blend.y), blend.z);
}

float endVoidFbm(vec3 value) {
    // Broad, inexpensive structure patterned after the Nether void rather
    // than a raymarched or photographic nebula.
    float result = endVoidNoise(value) * 0.66;
    value = value * 2.03 + vec3(7.1, 3.7, 5.2);
    result += endVoidNoise(value) * 0.34;
    return result;
}

vec3 endVoidStarPlane(vec2 directionPlane, float seedOffset) {
    vec2 starGrid = (directionPlane * 0.5 + 0.5) * 92.0;
    vec2 cell = floor(starGrid);
    vec2 local = fract(starGrid);
    float exists = step(0.9955, endVoidHash12(cell + seedOffset));
    vec2 position = vec2(
        endVoidHash12(cell + seedOffset + 13.7),
        endVoidHash12(cell + seedOffset + 53.3)
    );
    float footprint = max(fwidth(starGrid.x), fwidth(starGrid.y));
    float radius = clamp(footprint * 0.62, 0.030, 0.12);
    float star = 1.0 - smoothstep(radius, radius * 1.25,
                                  length(local - position));
    float warm = step(0.84, endVoidHash12(cell + seedOffset + 83.4));
    vec3 starColor = mix(vec3(0.52, 0.60, 0.82),
                         vec3(0.82, 0.68, 0.52), warm);
    return starColor * star * exists;
}

vec3 endVoidStars(vec3 direction) {
    vec3 weights = pow(abs(direction), vec3(8.0));
    weights /= max(weights.x + weights.y + weights.z, 0.0001);
    float seedX = direction.x >= 0.0 ? 17.0 : 31.0;
    float seedY = direction.y >= 0.0 ? 47.0 : 61.0;
    float seedZ = direction.z >= 0.0 ? 79.0 : 97.0;
    return endVoidStarPlane(direction.yz, seedX) * weights.x +
           endVoidStarPlane(direction.xz, seedY) * weights.y +
           endVoidStarPlane(direction.xy, seedZ) * weights.z;
}

vec3 endVoidColor(vec3 worldDirection) {
    // Like the Nether implementation, quantise the world direction rather than
    // screen coordinates. The texture remains fixed in the dimension while
    // retaining a deliberately coarse angular resolution.
    vec3 direction = floor(worldDirection * 144.0 + 0.5) / 144.0;
    direction = normalize(direction);

    float broad = endVoidFbm(direction * 2.55 + vec3(1.7, 4.1, 8.3));
    float broken = endVoidFbm(direction * 5.1 + vec3(9.2, 1.3, 5.7));
    float hollow = endVoidFbm(direction * 3.8 + vec3(3.4, 8.8, 2.1));
    broad = floor(broad * 8.0 + 0.5) / 8.0;
    broken = floor(broken * 7.0 + 0.5) / 7.0;

    float violetStain = smoothstep(0.43, 0.72, broad);
    float indigoStain = smoothstep(0.50, 0.76, broken) *
                        smoothstep(0.24, 0.66, broad);
    float dustyBlue = step(0.70, broken) * smoothstep(0.46, 0.78, broad);
    float emptyPocket = smoothstep(0.64, 0.82, hollow);

    vec3 color = vec3(0.009, 0.004, 0.021);
    color += vec3(0.030, 0.010, 0.052) * violetStain;
    color += vec3(0.014, 0.023, 0.060) * indigoStain;
    color += vec3(0.012, 0.030, 0.050) * dustyBlue;
    color *= 1.0 - emptyPocket * 0.48;
    color += endVoidStars(direction) * mix(1.0, 0.58, violetStain);
    return max(color, vec3(0.0));
}

#endif
