"""
raycube.py

Autor: Mahesh Venkitachalam

Generuje teksturę, która zawiera obliczenia sześcianiu/promieni.
"""

import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *

import numpy, math, sys 
import volreader, glutils

strVS = """
#version 330 core

layout(location = 1) in vec3 cubePos;
layout(location = 2) in vec3 cubeCol;

uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
out vec4 vColor;

void main()
{
    // ustawienie koloru tylnych ścian
    vColor = vec4(cubeCol.rgb, 1.0); 
    
    // transformowana pozycja
    vec4 newPos = vec4(cubePos.xyz, 1.0);
    
    // ustawienie pozycji
    gl_Position = uPMatrix * uMVMatrix * newPos; 

}
"""
strFS = """
#version 330 core

in vec4 vColor;
out vec4 fragColor;

void main()
{
    fragColor = vColor;
}
"""

class RayCube:
    """Klasa używana do generowania promieni wykorzystywanych w algorytmie ray casting"""
    
    def __init__(self, width, height):
        """Konstruktor RayCube"""

        # ustawienie wymiarów
        self.width, self.height = width, height

        # tworzenie shadera
        self.program = glutils.loadShaders(strVS, strFS)

        # wierzchołki sześcianu
        vertices = numpy.array([
                0.0, 0.0, 0.0, 
                1.0, 0.0, 0.0, 
                1.0, 1.0, 0.0, 
                0.0, 1.0, 0.0, 
                0.0, 0.0, 1.0,
                1.0, 0.0, 1.0, 
                1.0, 1.0, 1.0, 
                0.0, 1.0, 1.0 
                ], numpy.float32)
        # kolory sześcianu
        colors = numpy.array([
                0.0, 0.0, 0.0, 
                1.0, 0.0, 0.0,
                1.0, 1.0, 0.0, 
                0.0, 1.0, 0.0,
                0.0, 0.0, 1.0,
                1.0, 0.0, 1.0, 
                1.0, 1.0, 1.0, 
                0.0, 1.0, 1.0 
                ], numpy.float32)

        # poszczególne trójkąty
        indices = numpy.array([ 
                4, 5, 7, 
                7, 5, 6,
                5, 1, 6, 
                6, 1, 2, 
                1, 0, 2, 
                2, 0, 3,
                0, 4, 3, 
                3, 4, 7, 
                6, 2, 7, 
                7, 2, 3, 
                4, 0, 5, 
                5, 0, 1
                ], numpy.int16)
        
        self.nIndices = indices.size

        # konfiguracja obiektu tablicy wierzchołków (VAO)
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        # bufor wierzchołków
        self.vertexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(vertices), vertices, GL_STATIC_DRAW)
 
        # bufor wierzchołków — kolory wierzchołków sześcianu
        self.colorBuffer = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.colorBuffer)
        glBufferData(GL_ARRAY_BUFFER, 4*len(colors), colors, GL_STATIC_DRAW);
    
        # bufor indeksu
        self.indexBuffer = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.indexBuffer);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, 2*len(indices), indices, 
                     GL_STATIC_DRAW)
        
        # włączenie atrybutów przy użyciu indeksów układu w shaderze
        aPosLoc = 1
        aColorLoc = 2

        # wiązanie buforów:
        glEnableVertexAttribArray(1)
        glEnableVertexAttribArray(2)
    
        # wierzchołek
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
        glVertexAttribPointer(aPosLoc, 3, GL_FLOAT, GL_FALSE, 0, None)

        # kolor
        glBindBuffer(GL_ARRAY_BUFFER, self.colorBuffer)
        glVertexAttribPointer(aColorLoc, 3, GL_FLOAT, GL_FALSE, 0, None)
        # indeks
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.indexBuffer)
        
        # usunięcie dowiązania do VAO
        glBindVertexArray(0)

        # FBO
        self.initFBO()

    def renderBackFace(self, pMatrix, mvMatrix):
        """Renderuje tylne ściany sześcianu do tekstury i zwraca ją"""
        # renderowanie do FBO
        glBindFramebuffer(GL_FRAMEBUFFER, self.fboHandle)
        # ustawienie aktywnej tekstury
        glActiveTexture(GL_TEXTURE0)
        # wiązanie do tekstury FBO
        glBindTexture(GL_TEXTURE_2D, self.texHandle)

        # renderowanie sześcianu z włączonym odrzucaniem ścian
        self.renderCube(pMatrix, mvMatrix, self.program, True)
        
        # usunięcie dowiązania do tekstury
        glBindTexture(GL_TEXTURE_2D, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindRenderbuffer(GL_RENDERBUFFER, 0)

        # zwracanie identyfikatora tekstury 
        return self.texHandle

    def renderFrontFace(self, pMatrix, mvMatrix, program):
        """Renderowanie przednich ścian sześcianu"""
        # bez odrzucania ścian
        self.renderCube(pMatrix, mvMatrix, program, False)

    def renderCube(self, pMatrix, mvMatrix, program, cullFace):
        """Metoda renderCube używa odrzucania ścian, jeśli ustawiona jest flaga"""
        
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        
        # ustawienie programu shadera
        glUseProgram(program)
        
        # ustawienie macierzy rzutowania
        glUniformMatrix4fv(glGetUniformLocation(program, b'uPMatrix'), 
                           1, GL_FALSE, pMatrix)

        # ustawienie macierzy widoku modelu
        glUniformMatrix4fv(glGetUniformLocation(program, b'uMVMatrix'), 
                           1, GL_FALSE, mvMatrix)
 
        # włączenie odrzucania ścian
        glDisable(GL_CULL_FACE)
        if cullFace:
            glFrontFace(GL_CCW)
            glCullFace(GL_FRONT)
            glEnable(GL_CULL_FACE)

        # wiązanie VAO
        glBindVertexArray(self.vao)

        # animowany wycinek
        glDrawElements(GL_TRIANGLES, self.nIndices, 
                       GL_UNSIGNED_SHORT, None)

        # usunięcie dowiązania do VAO
        glBindVertexArray(0)

        # resetowanie odrzucania ścian
        if cullFace:
            # wyłączenie odrzucania ścian
            glDisable(GL_CULL_FACE)

    
    def reshape(self, width, height):
        self.width = width
        self.height = height
        self.aspect = width/float(height)
        # ponowne tworzenie FBO
        self.clearFBO()
        self.initFBO()
        
    def initFBO(self): 
        # tworzenie obiektu bufora ramek
        self.fboHandle = glGenFramebuffers(1)
        # tworzenie tekstury
        self.texHandle = glGenTextures(1)    
        # tworzenie bufora głębi
        self.depthHandle = glGenRenderbuffers(1)

        # wiązanie
        glBindFramebuffer(GL_FRAMEBUFFER, self.fboHandle)
    
        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.texHandle)
    
        # ustawienie kilku parametrów do obsługi rysowania obrazu 
        # w rozmiarach mniejszych i większych niż oryginalny
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR) 
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        
        # ustawienie tekstury
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.width, self.height, 
                     0, GL_RGBA, GL_UNSIGNED_BYTE, None)
        
        # wiązanie tekstury do FBO
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 
                               GL_TEXTURE_2D, self.texHandle, 0)
        
        # wiązanie
        glBindRenderbuffer(GL_RENDERBUFFER, self.depthHandle)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 
                              self.width, self.height)
    
        # wiązanie bufora głębi do FBO
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, 
                                  GL_RENDERBUFFER, self.depthHandle)
        # sprawdzanie statusu
        status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
        if status == GL_FRAMEBUFFER_COMPLETE:
            pass
            # print "fbo %d zakończone" % self.fboHandle
        elif status == GL_FRAMEBUFFER_UNSUPPORTED:
            print("fbo %d nieobsługiwane" % self.fboHandle)
        else:
            print("fbo %d Błąd" % self.fboHandle)
            
        glBindTexture(GL_TEXTURE_2D, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindRenderbuffer(GL_RENDERBUFFER, 0)
        return

    def clearFBO(self):
        """Czyści stary obiekt FBO"""
        # usunięcie FBO
        if glIsFramebuffer(self.fboHandle):
            glDeleteFramebuffers(int(self.fboHandle))
    
        # usunięcie tekstury
        if glIsTexture(self.texHandle):
            glDeleteTextures(int(self.texHandle))
            

    def close(self):
        """Metoda wywoływana do zwolnienia zasobów OpenGL"""
        glBindTexture(GL_TEXTURE_2D, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glBindRenderbuffer(GL_RENDERBUFFER, 0)
    
        # usunięcie FBO
        if glIsFramebuffer(self.fboHandle):
            glDeleteFramebuffers(int(self.fboHandle))
    
        # usunięcie tekstury
        if glIsTexture(self.texHandle):
            glDeleteTextures(int(self.texHandle))

        # usunięcie bufora renderowania
        """
        if glIsRenderbuffer(self.depthHandle):
            glDeleteRenderbuffers(1, int(self.depthHandle))
            """
        # usunięcie buforów
        """
        glDeleteBuffers(1, self._vertexBuffer)
        glDeleteBuffers(1, &_indexBuffer)
        glDeleteBuffers(1, &_colorBuffer)
        """
