#White #Dark #txt #download

        
#!coding: utf-8
from __future__ import print_function, division

import pygame
import random

# importer la bibliothèque OpenGL !
from OpenGL.GL import *
from OpenGL.GL import shaders

import ctypes
import pygame
from math import sin, cos, degrees, radians, tan

import numpy
from numpy import array, linalg

vertex_shader = """
#version 330
in vec3 position;
in vec3 normal;

uniform mat4 pvMatrix;
uniform mat4 mMatrix;

out vec3 normalVtx;
out vec3 positionVtx;

void main()
{
    gl_Position = pvMatrix * mMatrix * vec4(position, 1);
    normalVtx = normal;
    positionVtx = (mMatrix * vec4(position, 1)).xyz;
}
"""

fragment_shader = """
#version 330
in vec3 normalVtx;
in vec3 positionVtx;

uniform vec3 lightPos;
uniform vec3 color;

out vec4 pixel;

void main()
{
    vec3 c = color * dot(normalVtx, normalize(lightPos - positionVtx));
    pixel = vec4(c, 1);
    //pixel = vec4((normalVtx+1)/2, 1);
    //pixel = vec4(((positionVtx/100)+1)/2, 1);
}
"""

###########
# glutils #
###########

def farray(x):
    return numpy.array(x, numpy.float32)

def cosd(x):
    return cos(radians(x))

def sind(x):
    return sin(radians(x))

def vec2(x, y):
    """Create vector in 2 dimensions of correct type (numpy float32)
    >>> vec2(4,5)
    array([4.0, 5.0], dtype=float32)
    """
    return farray((x, y))

def vec3(*args):
    """Create vector in 3 dimensions of correct type (numpy float32)
    >>> vec3(1,2,3)
    array([1.0, 2.0, 3.0], dtype=float32)
    >>> vec3((1,2),3)
    array([1.0, 2.0, 3.0], dtype=float32)
    """
    if len(args) == 3:
        x, y, z = args
    elif len(args) == 2:
        (x, y), z = args
    else:
        raise TypeError('Accept 2 or 3 arguments')

    return farray((x, y, z))


def polar(*args):
    """Polar coordinate in radians
    >>> polar(pi/3)
    array([0.5, 0.8660254])
    >>> polar(10, pi/3)
    array([5.0, 8.66025404])
    """
    if len(args) == 2:
        r, t = args
    elif len(args) == 1:
        r, t = 1, args[0]
    else:
        raise TypeError('Accept 1 or 2 arguments')

    return r * vec2(cos(t), sin(t))


def polard(*args):
    """Polar coordinate in degrees
    >>> polard(60)
    array([0.5, 0.8660254])
    >>> polar(10, 60)
    array([5.0, 8.66025404])
    """
    if len(args) == 2:
        r, t = args
    elif len(args) == 1:
        r, t = 1, args[0]
    else:
        raise TypeError('Accept 1 or 2 arguments')

    return r * polar(radians(t))

def normalized(v):
    return v / linalg.norm(v)


def PerspectiveMatrix(fovy, aspect, zNear, zFar):
    """PerspectiveMatrix
    PerspectiveMatrix(45, 16/9.0, 100, 2000)
    """
    f = 1.0 / tan(radians(fovy) / 2.0)
    return farray([
        [f / aspect, 0, 0, 0],
        [0, f, 0, 0],
        [0, 0, 1. * (zFar + zNear) / (zNear - zFar), 2.0 * zFar * zNear / (zNear - zFar)],
        [0, 0, -1, 0]
    ])

def LookAtMatrix(eye, target, up, *others):
    """LookAt: I am in "eye" I look at "target" and the up is "up"
    LookAtMatrix(1,2,3, 4,5,6, 7,8,9)
    LookAtMatrix((1,2,3), (4,5,6), (7,8,9))
    """
    if len(others) == 0:
        pass
    elif len(others) == 6:
        eye, target, up = (eye, target, up), others[:3], others[3:]
    else:
        raise TypeError("Accept 3 or 9 arguments")
    target = array(target)

    f = normalized(target - eye)
    s = normalized(numpy.cross(f, up))
    u = numpy.cross(s, f)

    return farray([
        [s[0], s[1], s[2], -s.dot(eye)],
        [u[0], u[1], u[2], -u.dot(eye)],
        [-f[0], -f[1], -f[2], f.dot(eye)],
        [0, 0, 0, 1],
    ]) # corresponds to M(s, u, -f).to4x4 @ Translate(-eye)


def TranslationMatrix(*args):
    """TranslationMatrix
    TranslationMatrix(2,1,0)
    TranslationMatrix(2,1)
    TranslationMatrix((2,1,0))
    """
    if len(args) == 3:
        tx, ty, tz = args
    elif len(args) == 2:
        (tx, ty), tz = args, 0
    elif len(args) == 1:
        tx, ty, tz = args[0]
    else:
        raise TypeError("Accept 1, 2 or 3 arguments")

    return farray([
        [1, 0, 0, tx],
        [0, 1, 0, ty],
        [0, 0, 1, tz],
        [0, 0, 0, 1]
    ])


class Axe:
    X = 0
    Y = 1
    Z = 2

def SimpleRotationMatrix(angle, axe=Axe.Z):
    """Rotation matrix for angle in degree around X Y or Z
    SimpleRotationMatrix(30, Axe.Z)
    """
    if angle % 90 == 0:
        a = angle % 360
        c = 1 if a == 0 else -1 if a == 180 else 0
        s = 1 if a == 90 else -1 if a == 270 else 0
    else:
        t = radians(angle)
        c = cos(t)
        s = sin(t)

    return farray([
         [c, -s, 0, 0],
         [s, c, 0, 0],
         [0, 0, 1, 0],
         [0, 0, 0, 1]
     ] if axe == 2 else [
        [c, 0, s, 0],
        [0, 1, 0, 0],
        [-s, 0, c, 0],
        [0, 0, 0, 1]
    ] if axe == 1 else [
        [1, 0, 0, 0],
        [0, c, -s, 0],
        [0, s, c, 0],
        [0, 0, 0, 1]
    ])


def RotationMatrix(angle, axe):
    """Rotation matrix for angle in degree around any axe
    RotationMatrix(30, (0,0,1))
    """
    x, y, z = normalized(axe)

    if angle % 90 == 0:
        a = angle % 360
        c = 1 if a == 0 else -1 if a == 180 else 0
        s = 1 if a == 90 else -1 if a == 270 else 0
    else:
        t = radians(angle)
        c = cos(t)
        s = sin(t)

    k = 1 - c

    # Rodriguez rotation formula
    return farray([
        [x * x * k + c, x * y * k - z * s, x * z * k + y * s, 0],
        [y * x * k + z * s, y * y * k + c, y * z * k - x * s, 0],
        [x * z * k - y * s, y * z * k + x * s, z * z * k + c, 0],
        [0, 0, 0, 1]
    ])


def ScaleMatrix(kx, ky=None, kz=None):
    """ScaleMatrix
    ScaleMatrix(2) = ScaleMatrix(2,2,2)
    ScaleMatrix(1,2,3)
    """
    if ky is None:
        ky = kx
    if kz is None:
        kz = kx
    return farray([
        [kx, 0, 0, 0],
        [0, ky, 0, 0],
        [0, 0, kz, 0],
        [0, 0, 0, 1]
    ])

def IdentityMatrix():
    """IdentityMatrix
    IdentityMatrix()
    """
    return farray([
        [1, 0, 0, 0],
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1]
    ])

###############
# END glutils #
###############

def couleur01(r,g,b):
    return [r/255, g/255, b/255]

ROUGE = couleur01(255, 0, 0)
VERT = couleur01(0, 255, 0)
BLEU = couleur01(0, 0, 255)
JAUNE = couleur01(255, 255, 0)
BLANC = couleur01(255, 255, 255)
NOIR = couleur01(0, 0, 0)
ORANGE = couleur01(255, 153, 0)
BLEU_CLAIR = couleur01(135, 206, 250)

def nouvel_ecran(W, H):
    e = pygame.display.set_mode([W,H], pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)
    
    glViewport(0, 0, W, H)
    glEnable(GL_DEPTH_TEST)

    return e

tx,ty = taille = [12*60, 500]

sol_points = farray([
    0, 0, 0,
    tx, 0, 0,
    0, ty, 0,
    tx, 0, 0,
    tx, ty, 0,
    0, ty, 0,
])

sol_normals = farray([0,0,1] * 6)

"""
triangles (quads are deprecated)
from (0,0,0) to (1,1,1)
faces are ccw (counter clockwise : sens contraire des aiguilles d'une montre)
"""
cube_points = farray([
    # up
    0, 0, 1,
    1, 0, 1,
    0, 1, 1,
    1, 0, 1,
    1, 1, 1,
    0, 1, 1,

    # down
    0, 0, 0,
    0, 1, 0,
    1, 0, 0,
    0, 1, 0,
    1, 1, 0,
    1, 0, 0,

    # right
    1, 0, 0,
    1, 1, 0,
    1, 0, 1,
    1, 1, 0,
    1, 1, 1,
    1, 0, 1,

    # left
    0, 0, 0,
    0, 0, 1,
    0, 1, 0,
    0, 0, 1,
    0, 1, 1,
    0, 1, 0,

    # back
    0, 1, 0,
    0, 1, 1,
    1, 1, 0,
    0, 1, 1,
    1, 1, 1,
    1, 1, 0,

    # front
    1, 0, 0,
    1, 0, 1,
    0, 0, 0,
    1, 0, 1,
    0, 0, 1,
    0, 0, 0,
])

cube_normals = farray(
    [0, 0, 1] * 6
    + [0, 0, -1] * 6
    + [1, 0, 0] * 6
    + [-1, 0, 0] * 6
    + [0, 1, 0] * 6
    + [0, -1, 0] * 6)


def creer_vao_sol(shader):
    """
    renvoie le vao de nos objets :
    vertices:
    4 vtx : sol
    """
    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    
    position = glGetAttribLocation(shader, 'position')
    if position != -1:
        vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, vbo)
        glBufferData(GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(sol_points), sol_points, GL_STATIC_DRAW)

        glEnableVertexAttribArray(position)
        glVertexAttribPointer(position, 3, GL_FLOAT, False, 0, ctypes.c_void_p())
    else:
        print('Inactive attribute "{}"'.format('position'))
    
    normal = glGetAttribLocation(shader, 'normal')
    if normal != -1:
        vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, vbo)
        glBufferData(GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(sol_normals), sol_normals, GL_STATIC_DRAW)
    
        glEnableVertexAttribArray(normal)
        glVertexAttribPointer(normal, 3, GL_FLOAT, False, 0, ctypes.c_void_p())
    else:
        print('Inactive attribute "{}"'.format('normal'))

    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)

    return vao

def creer_vao_cube(shader):
    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)

    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(cube_points), cube_points, GL_STATIC_DRAW)
    
    position = glGetAttribLocation(shader, 'position')
    if position != -1:
        glEnableVertexAttribArray(position)
        glVertexAttribPointer(position, 3, GL_FLOAT, False, 0, ctypes.c_void_p())
    else:
        print('inactive attribute "{}"'.format('position'))

    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(cube_normals), cube_normals, GL_STATIC_DRAW)
    
    normal = glGetAttribLocation(shader, 'normal')
    if normal != -1:
        glEnableVertexAttribArray(normal)
        glVertexAttribPointer(normal, 3, GL_FLOAT, False, 0, ctypes.c_void_p())
    else:
        print('inactive attribute "{}"'.format('normal'))

    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)

    return vao

class Balle:
    pass

class Brique:
    pass

def creer_briques(briques):

    for i in range(12):
        for j in range(5):
            r = Brique()
            r.vie = 3
            r.taille_x = 60
            r.taille_y = 30
            r.x = i * r.taille_x
            r.y = j * r.taille_y
            briques.append(r)

def main():
    pygame.init()

    ecran = nouvel_ecran(tx, ty)

    clock = pygame.time.Clock()

    balles = []

    for i in range(5):
        b = Balle()
        b.taille = 20
        b.x = random.randrange(700 - b.taille)
        b.y = 300 + random.randrange(200)
        b.sx = random.choice([-1,1])
        b.sy = random.choice([-1,1])
        balles.append(b)

    briques = []

    creer_briques(briques)

    shader = shaders.compileProgram(
        shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
        shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))

    vao_sol = creer_vao_sol(shader)
    vao_cube = creer_vao_cube(shader)

    t = 0

    fini = 0
    while fini == 0:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                fini = 1
            elif event.type == pygame.VIDEORESIZE: # la fenêtre a été redimensionnée
                # on s'adapte à la nouvelle fenêtre
                ecran = nouvel_ecran(event.w, event.h) # re créer l'écran !

        # déplacement

        for b in balles:
            b.x += b.sx * 5
            b.y += b.sy * 5

            i = 0
            while i < len(briques):
                r = briques[i]
                if not(
                    r.x > b.x + b.taille or
                    r.y > b.y + b.taille or
                    r.x + r.taille_x < b.x or
                    r.y + r.taille_y < b.y
                ):
                    b.x -= b.sx * 5
                    b.y -= b.sy * 5

                    dx = r.x - b.x if r.x > b.x else b.x - (r.x + r.taille_x)
                    dy = r.y - b.y if r.y > b.y else b.y - (r.y + r.taille_y)
                    if dx > dy:
                        b.sx *= -1
                    else:
                        b.sy *= -1

                    r.vie -= 1
                    if r.vie == 0:
                        del briques[i]
                        i -= 1

                i += 1

            if b.x > tx - b.taille:
                b.sx = -1
            if b.x < 0:
                b.sx = 1
            if b.y > ty - b.taille:
                b.sy = -1
            if b.y < 0:
                b.sy = 1

        if len(briques) == 0:
            creer_briques(briques)
        t += 1

        # dessin
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glUseProgram(shader)

        P = PerspectiveMatrix(45, tx / ty, 100, 2000) # fov 45°, ratio tx / ty, distance min : 100, distance max : 2000

        # position et orientation de la camera :
        camera_x = tx/2 # au milieu de l'arène
        camera_y = -ty # derrière l'arène
        camera_z = 700 # bien haut

        cible_camera_x, cible_camera_y = tx / 2, ty / 2 # regarde le milieu de l'arène
        cible_camera_z = 0 # regarde par terre
        
        V = LookAtMatrix(camera_x, camera_y, camera_z, cible_camera_x, cible_camera_y, cible_camera_z, 0,0,1)

        # position de la lampe :
        light_x, light_y = tx / 2, ty / 2 # lampe au milieu de l'arène
        light_z = 150 # un peu au dessus des briques (hauteur : 100)
        glUniform3f(glGetUniformLocation(shader, 'lightPos'), light_x, light_y, light_z)

        PV = P @ V # P.dot(V) si Python < 3.5
        # dessin du sol
        glBindVertexArray(vao_sol)

        glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
        glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, IdentityMatrix())
        glUniform3fv(glGetUniformLocation(shader, 'color'), 1, BLEU_CLAIR)

        glDrawArrays(GL_TRIANGLES, 0, 6)

        glBindVertexArray(0)

        # dessin des 4 côtés
        glBindVertexArray(vao_cube)

        glUniform3fv(glGetUniformLocation(shader, 'color'), 1, (0.8, 0.8, 0.8))

        M = TranslationMatrix(0, -50, 0).dot(ScaleMatrix(tx, 50, 50))
        glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
        glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, M)
        glDrawArrays(GL_TRIANGLES, 0, 36)

        M = TranslationMatrix(0, ty, 0).dot(ScaleMatrix(tx, 50, 50))
        glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
        glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, M)
        glDrawArrays(GL_TRIANGLES, 0, 36)

        M = TranslationMatrix(-50, 0, 0).dot(ScaleMatrix(50, ty, 50))
        glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
        glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, M)
        glDrawArrays(GL_TRIANGLES, 0, 36)
        
        M = TranslationMatrix(tx, 0, 0).dot(ScaleMatrix(50, ty, 50))
        glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
        glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, M)
        glDrawArrays(GL_TRIANGLES, 0, 36)

        # dessin des briques
        for r in briques:
            color = (ROUGE if r.vie == 3 else
                     VERT if r.vie == 2 else
                     JAUNE)

            glUniform3fv(glGetUniformLocation(shader, 'color'), 1, color)
        
            d = 5
            T = TranslationMatrix(r.x + d, ty - (r.y + d) - (r.taille_y - 2*d), 0)
            S = ScaleMatrix(r.taille_x - 2*d, r.taille_y - 2*d, 100)
            glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
            glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, T @ S) # T.dot(S) si Python < 3.5
            glDrawArrays(GL_TRIANGLES, 0, 36)
        
        # dessin des balles
        glUniform3fv(glGetUniformLocation(shader, 'color'), 1, ORANGE)
        for b in balles:
            T = TranslationMatrix(b.x, ty - b.y - b.taille, 0)
            S = ScaleMatrix(b.taille, b.taille, b.taille)
            glUniformMatrix4fv(glGetUniformLocation(shader, 'pvMatrix'), 1, True, PV)
            glUniformMatrix4fv(glGetUniformLocation(shader, 'mMatrix'), 1, True, T @ S) # T.dot(S) si Python < 3.5
            glDrawArrays(GL_TRIANGLES, 0, 36)

        glUseProgram(0)
        # dessin du repère en (0,0,100) (sans lighting)
        
        # appliquer les dessins
        pygame.display.flip()
        clock.tick(60)

    pygame.quit()

if __name__ == '__main__':
    main()