#include <metal_stdlib>
using namespace metal;

// Fractal Pyramid — bradjamesgrant
// MSL port of fractal_pyramid.vk.glsl.

struct ShadertoyUBO {
    float iTime;
    float2 iResolution;
};

struct PassthroughVOut {
    float4 pos [[position]];
    float2 uv;
};

static float3 palette(float d) {
    return mix(float3(0.2, 0.7, 0.9), float3(1.0, 0.0, 1.0), d);
}

static float2 rotate(float2 p, float a) {
    float c = cos(a);
    float s = sin(a);
    return p * float2x2(c, s, -s, c);
}

static float mapScene(float3 p, float iTime) {
    for (int i = 0; i < 8; ++i) {
        float t = iTime * 0.2;
        p.xz = rotate(p.xz, t);
        p.xy = rotate(p.xy, t * 1.89);
        p.xz = abs(p.xz);
        p.xz -= 0.5;
    }
    return dot(sign(p), p) / 5.0;
}

static float4 rm(float3 ro, float3 rd, float iTime) {
    float t = 0.0;
    float3 col = float3(0.0);
    float d = 1.0;
    for (int i = 0; i < 64; ++i) {
        float3 p = ro + rd * t;
        d = mapScene(p, iTime) * 0.5;
        if (d < 0.02) break;
        if (d > 100.0) break;
        col += palette(length(p) * 0.1) / (400.0 * d);
        t += d;
    }
    return float4(col, 1.0 / (d * 100.0));
}

fragment float4 fractal_pyramid_fs(PassthroughVOut in [[stage_in]],
                                    constant ShadertoyUBO& U [[buffer(0)]]) {
    float2 fragCoord = in.uv * U.iResolution;
    float2 uv = (fragCoord - (U.iResolution * 0.5)) / U.iResolution.x;
    float3 ro = float3(0.0, 0.0, -50.0);
    ro.xz = rotate(ro.xz, U.iTime);
    float3 cf = normalize(-ro);
    float3 cs = normalize(cross(cf, float3(0.0, 1.0, 0.0)));
    float3 cu = normalize(cross(cf, cs));

    float3 uuv = ro + cf * 3.0 + uv.x * cs + uv.y * cu;
    float3 rd = normalize(uuv - ro);

    return rm(ro, rd, U.iTime);
}
