from ursina import color
from ursina.shader import Shader
from ursina.texture_importer import load_texture
from ursina.ursinastuff import Func
from ursina.vec3 import Vec3

ssao_shader = Shader(language=Shader.GLSL, fragment='''
#version 140


vec3 sphere[16] = vec3[](
    vec3( 0.5381, 0.1856,-0.4319), vec3( 0.1379, 0.2486, 0.4430),
    vec3( 0.3371, 0.5679,-0.0057), vec3(-0.6999,-0.0451,-0.0019),
    vec3( 0.0689,-0.1598,-0.8547), vec3( 0.0560, 0.0069,-0.1843),
    vec3(-0.0146, 0.1402, 0.0762), vec3( 0.0100,-0.1924,-0.0344),
    vec3(-0.3577,-0.5301,-0.4358), vec3(-0.3169, 0.1063, 0.0158),
    vec3( 0.0103,-0.5869, 0.0046), vec3(-0.0897,-0.4940, 0.3287),
    vec3( 0.7119,-0.0154,-0.0918), vec3(-0.0533, 0.0596,-0.5411),
    vec3( 0.0352,-0.0631, 0.5460), vec3(-0.4776, 0.2847,-0.0271)
);


uniform sampler2D tex;
uniform sampler2D dtex;
uniform sampler2D random_texture;
uniform mat4 p3d_ViewProjectionMatrix;

in vec2 uv;
out vec4 o_color;

uniform float numsamples;
uniform float radius;
uniform float amount;
uniform float strength;
uniform float falloff;

vec3 get_normal(vec2 texcoords) {
    const vec2 offset1 = vec2(0.0, 0.001);
    const vec2 offset2 = vec2(0.001, 0.0);

    float depth = texture(dtex, texcoords).r;
    float depth1 = texture(dtex, texcoords + offset1).r;
    float depth2 = texture(dtex, texcoords + offset2).r;

    vec3 p1 = vec3(offset1, depth1 - depth);
    vec3 p2 = vec3(offset2, depth2 - depth);

    vec3 normal = cross(p1, p2);
    normal.z = -normal.z;

    return normalize(normal);
}

vec3 reconstructPosition(in vec2 uv, in float z)
{
    float x = uv.x * 2.0f - 1.0f;
    float y = (1.0 - uv.y) * 2.0f - 1.0f;
    vec4 position_s = vec4(x, y, z, 1.0f);
    mat4x4 view_projection_matrix_inverse = inverse(p3d_ViewProjectionMatrix);
    vec4 position_v = view_projection_matrix_inverse * position_s;
    return position_v.xyz / position_v.w;
}


void main() {
    float depth = texture(dtex, uv).r;
    vec3 position = reconstructPosition(uv, depth);
    vec3 normal = get_normal(uv);

    vec2 noiseScale = vec2(800.0/4.0, 600.0/4.0); // screen = 800x600
    vec3 randomVec = texture(random_texture, uv * noiseScale).xyz;
    vec3 tangent   = normalize(randomVec - normal * dot(randomVec, normal));
    vec3 bitangent = cross(normal, tangent);
    mat3 TBN       = mat3(tangent, bitangent, normal);

    vec3 random_vector = normalize((texture(random_texture, uv * 18.0 + depth + normal.xy).xyz * 2.0) - vec3(1.0)).xyz;
    float occlusion = 0.0;
    float radius = radius / depth;
    float depth_difference;
    vec3 sample_normal;
    vec3 ray;

    for(int i = 0; i < numsamples; ++i) {
        vec3 random_vector = texture(random_texture, uv * i * 100).xyz;
        ray = radius * reflect(sphere[i], random_vector);
        sample_normal = get_normal(uv + ray.xy).xzy;

        depth_difference = (depth - texture(dtex, uv + ray.xy).r);
        occlusion += step(falloff, depth_difference) * (.1 - dot(sample_normal.xyz, normal)) * (1.0 - smoothstep(falloff, strength, depth_difference));
    }

    o_color.rgb = normal;
    o_color.rgb = texture(tex, uv).rgb + (occlusion * -(amount/numsamples));
    o_color.a = 1.0;
}
''',

default_input = {
    'numsamples' : 16,
    'radius' : 0.01,
    'amount' : 5.0,
    'strength' : 0.001,
    'falloff' : 0.00005,
    'random_texture' : Func(load_texture, 'noise'),
    'clip_plane_near' : 1,
}
)
if __name__ == '__main__':
    from ursina import *
    app = Ursina()

    e = Entity(model='sphere', color=color.orange)
    e = Entity(model='cube', y=-1)
    e = Entity(model='plane', scale=100, y=-1)
    Sky()
    Button(y=-.4, scale=.1)
    camera.shader = ssao_shader

    EditorCamera()

    def input(key):
        if key == 'space':
            if camera.shader:
                camera.shader = None
            else:
                camera.shader = ssao_shader


    random.seed(2)
    for i in range(20):
        e = Entity(model='cube', position=Vec3(random.random(),random.random(),random.random())*3, rotation=Vec3(random.random(),random.random(),random.random())*360)
        # e.shader = matcap_shader


    app.run()
