#
# OpenGL 4.1 with GLEW - Example 14
#
# @author   Norbert Nopper norbert@nopper.tv
# @version  1.0
#
# Homepage: https://github.com/McNopper/OpenGL
#
# Copyright Norbert Nopper
#
# Modified for Tcl3D by Paul Obermeier 2011/08/19
# See www.tcl3d.org for the Tcl3D extension.

package require Tk
package require Img
package require tcl3d

# Font to be used in the Tk listbox.
set g_Demo(listFont) {-family {Courier} -size 10}

# Obtain the name of this script file.
set g_Demo(scriptDir) [file dirname [info script]]

# Window size.
set g_Demo(winWidth)  640
set g_Demo(winHeight) 480

# The number of waves. Also has to be changed in the shader.
set g_Demo(NumWaves) 4

set overallSteepness 0.2

# params: speed, amplitude, wavelength, steepness
# dirs  : x, z
set g_Wave(tex,params) [list]
set g_Wave(tex,dirs)   [list]

# Wave One
lappend g_Wave(tex,params) 0.05
lappend g_Wave(tex,params) 0.02
lappend g_Wave(tex,params) 0.3
lappend g_Wave(tex,params) [expr { $overallSteepness / (0.3 * 0.02 * $g_Demo(NumWaves)) }]
lappend g_Wave(tex,dirs)   1.0
lappend g_Wave(tex,dirs)   1.5

# Wave Two
lappend g_Wave(tex,params) 0.1
lappend g_Wave(tex,params) 0.01
lappend g_Wave(tex,params) 0.4
lappend g_Wave(tex,params) [expr { $overallSteepness / (0.4 * 0.01 * $g_Demo(NumWaves)) }]
lappend g_Wave(tex,dirs)   0.8
lappend g_Wave(tex,dirs)   0.2

# Wave Three
lappend g_Wave(tex,params) 0.04
lappend g_Wave(tex,params) 0.035
lappend g_Wave(tex,params) 0.1
lappend g_Wave(tex,params) [expr { $overallSteepness / (0.4 * 0.01 * $g_Demo(NumWaves)) }]
lappend g_Wave(tex,dirs)   -0.2
lappend g_Wave(tex,dirs)   -0.1

# Wave Four
lappend g_Wave(tex,params) 0.05
lappend g_Wave(tex,params) 0.007
lappend g_Wave(tex,params) 0.2
lappend g_Wave(tex,params) [expr { $overallSteepness / (0.4 * 0.01 * $g_Demo(NumWaves)) }]
lappend g_Wave(tex,dirs)   -0.4
lappend g_Wave(tex,dirs)   -0.3

set g_Wave(wat,params) [list]
set g_Wave(wat,dirs)   [list]

# Wave One
lappend g_Wave(wat,params) 1.0
lappend g_Wave(wat,params) 0.01
lappend g_Wave(wat,params) 4.0
lappend g_Wave(wat,params) [expr { $overallSteepness / (4.0 * 0.01 * $g_Demo(NumWaves)) }]
lappend g_Wave(wat,dirs)   1.0
lappend g_Wave(wat,dirs)   1.0

# Wave Two
lappend g_Wave(wat,params) 0.5
lappend g_Wave(wat,params) 0.02
lappend g_Wave(wat,params) 3.0
lappend g_Wave(wat,params) [expr { $overallSteepness / (3.0 * 0.02 * $g_Demo(NumWaves)) }]
lappend g_Wave(wat,dirs)   1.0
lappend g_Wave(wat,dirs)   0.0

# Wave Three
lappend g_Wave(wat,params) 0.1
lappend g_Wave(wat,params) 0.015
lappend g_Wave(wat,params) 2.0
lappend g_Wave(wat,params) [expr { $overallSteepness / (3.0 * 0.02 * $g_Demo(NumWaves)) }]
lappend g_Wave(wat,dirs)   -0.1
lappend g_Wave(wat,dirs)   -0.2

# Wave Four
lappend g_Wave(wat,params) 1.1
lappend g_Wave(wat,params) 0.008
lappend g_Wave(wat,params) 1.0
lappend g_Wave(wat,params) [expr { $overallSteepness / (3.0 * 0.02 * $g_Demo(NumWaves)) }]
lappend g_Wave(wat,dirs)   -0.2
lappend g_Wave(wat,dirs)   -0.1

# Length (width and height) of the water surface.
set g_Demo(WaterPlaneLen) 128

# Size of the normal map texture.
set g_Demo(TexSize) 1024

set g_Demo(passedTime) 0.0

set g_Demo(angle) 0.0

# Projection matrices.
set g_Demo(projection)        [tcl3dVector GLfloat 16]
set g_Demo(projectionTexture) [tcl3dVector GLfloat 16]

# ModelView matrices.
set g_Demo(modelView)        [tcl3dVector GLfloat 16]
set g_Demo(modelViewTexture) [tcl3dVector GLfloat 16]

# Inverse camera needed for the final reflect/refract normal calculation in the shader.
set g_Demo(inverseCamera) [tcl3dVector GLfloat 16]

# A stop watch to get current time.
set g_Demo(stopWatch) [tcl3dNewSwatch]
tcl3dStartSwatch $g_Demo(stopWatch)
set g_Demo(lastTime) [tcl3dLookupSwatch $g_Demo(stopWatch)]

# Show errors occuring in the Togl callbacks.
proc bgerror { msg } {
    tk_messageBox -icon error -type ok -message "Error: $msg\n\n$::errorInfo"
    ExitProg
}

# Print info message into widget a the bottom of the window.
proc PrintInfo { msg } {
    if { [winfo exists .fr.info] } {
        .fr.info configure -text $msg
    }
}

proc Animate {} {
    global g_Demo

    .fr.toglwin postredisplay
    set g_Demo(animateId) [tcl3dAfterIdle Animate]
}

proc StartAnimation {} {
    global g_Demo

    if { ! [info exists g_Demo(animateId)] } {
        Animate
        tcl3dStartSwatch $g_Demo(stopWatch)
    }
}

proc StopAnimation {} {
    global g_Demo

    if { [info exists g_Demo(animateId)] } {
        after cancel $g_Demo(animateId)
        unset g_Demo(animateId)
        tcl3dStopSwatch $g_Demo(stopWatch)
    }
}

proc InitTexture { waterPlaneLength } {
    global g_Demo g_ProgramTexture

    # Load the source of the vertex shader.
    set vertexSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "VertexWaterTexture.vs"]]

    # Load the source of the fragment shader.
    set fragmentSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "FragmentWaterTexture.fs"]]

    set g_ProgramTexture [tcl3dOglBuildProgram $vertexSource "" "" "" $fragmentSource]
    set programTexture [dict get $g_ProgramTexture program]

    set g_Demo(vaoTexture) [tcl3dVector GLuint 1]
    glGenVertexArrays 1 $g_Demo(vaoTexture)
    glBindVertexArray [$g_Demo(vaoTexture) get 0]

    set plane [glusCreatePlane [expr {$g_Demo(TexSize)*0.5}] [expr {$g_Demo(TexSize)*0.5}]]
    set g_Demo(numIndicesTexture) [dict get $plane numIndices]

    set g_Demo(projectionLocationTexture)       [glGetUniformLocation $programTexture "projectionMatrix"]
    set g_Demo(modelViewLocationTexture)        [glGetUniformLocation $programTexture "modelViewMatrix"]
    set g_Demo(waterPlaneLengthLocationTexture) [glGetUniformLocation $programTexture "waterPlaneLength"]
    set g_Demo(passedTimeLocationTexture)       [glGetUniformLocation $programTexture "passedTime"]
    set g_Demo(waveParametersLocationTexture)   [glGetUniformLocation $programTexture "waveParameters"]
    set g_Demo(waveDirectionsLocationTexture)   [glGetUniformLocation $programTexture "waveDirections"]

    set g_Demo(vertexLocationTexture)   [glGetAttribLocation $programTexture "vertex"]
    set g_Demo(texCoordLocationTexture) [glGetAttribLocation $programTexture "texCoord"]

    set g_Demo(verticesTexture) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(verticesTexture)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesTexture) get 0]
    set vertexVec [dict get $plane vertexVec]
    glBufferData GL_ARRAY_BUFFER \
                 [expr [dict get $plane numVertices]*4*[$vertexVec elemsize]] \
                 $vertexVec GL_STATIC_DRAW

    set g_Demo(texCoordsTexture) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(texCoordsTexture)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(texCoordsTexture) get 0]
    set texCoordVec [dict get $plane texCoordVec]
    glBufferData GL_ARRAY_BUFFER \
                 [expr [dict get $plane numVertices]*2*[$texCoordVec elemsize]] \
                 $texCoordVec GL_STATIC_DRAW

    set g_Demo(indicesTexture) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(indicesTexture)
    glBindBuffer GL_ELEMENT_ARRAY_BUFFER [$g_Demo(indicesTexture) get 0]
    set indexVec [dict get $plane indexVec]
    glBufferData GL_ELEMENT_ARRAY_BUFFER \
                 [expr [dict get $plane numIndices]*[$indexVec elemsize]] \
                 $indexVec GL_STATIC_DRAW

    glusDestroyShape $plane

    glUseProgram $programTexture

    tcl3dMatfIdentity $g_Demo(modelViewTexture)
    tcl3dLookAt 0.0 0.0 5.0  0.0 0.0 0.0  0.0 1.0 0.0  $g_Demo(modelViewTexture)

    set modelViewAsList [tcl3dVectorToList $g_Demo(modelViewTexture) 16]
    glUniformMatrix4fv $g_Demo(modelViewLocationTexture) 1 GL_FALSE $modelViewAsList

    glUniform1f $g_Demo(waterPlaneLengthLocationTexture) $waterPlaneLength

    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesTexture) get 0]
    glVertexAttribPointer $g_Demo(vertexLocationTexture) 4 GL_FLOAT GL_FALSE 0 "NULL"
    glEnableVertexAttribArray $g_Demo(vertexLocationTexture)

    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(texCoordsTexture) get 0]
    glVertexAttribPointer $g_Demo(texCoordLocationTexture) 2 GL_FLOAT GL_FALSE 0 "NULL"
    glEnableVertexAttribArray $g_Demo(texCoordLocationTexture)

    glClearColor 0.0 0.0 0.0 0.0
    glClearDepth 1.0
    glEnable GL_DEPTH_TEST
    glEnable GL_CULL_FACE

    set g_Demo(textureTexture) [tcl3dVector GLuint 1]
    glGenTextures 1 $g_Demo(textureTexture)
    glActiveTexture GL_TEXTURE1

    glBindTexture GL_TEXTURE_2D [$g_Demo(textureTexture) get 0]

    glTexImage2D GL_TEXTURE_2D 0 $::GL_RGB \
                 $g_Demo(TexSize) $g_Demo(TexSize) \
                 0 GL_RGB GL_UNSIGNED_BYTE "NULL"

    glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_LINEAR
    glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_LINEAR
    glTexParameterf GL_TEXTURE_2D GL_TEXTURE_WRAP_S $::GL_REPEAT
    glTexParameterf GL_TEXTURE_2D GL_TEXTURE_WRAP_T $::GL_REPEAT

    set g_Demo(fboTexture) [tcl3dVector GLuint 1]
    glGenFramebuffers 1 $g_Demo(fboTexture)
    glBindFramebuffer GL_FRAMEBUFFER [$g_Demo(fboTexture) get 0]

    set g_Demo(depthTexture) [tcl3dVector GLuint 1]
    glGenRenderbuffers 1 $g_Demo(depthTexture)
    glBindRenderbuffer GL_RENDERBUFFER [$g_Demo(depthTexture) get 0]
    glRenderbufferStorage GL_RENDERBUFFER GL_DEPTH_COMPONENT $g_Demo(TexSize) $g_Demo(TexSize)

    glFramebufferTexture2D GL_FRAMEBUFFER GL_COLOR_ATTACHMENT0 GL_TEXTURE_2D [$g_Demo(textureTexture) get 0] 0
    glFramebufferRenderbuffer GL_FRAMEBUFFER GL_DEPTH_ATTACHMENT GL_RENDERBUFFER [$g_Demo(depthTexture) get 0]

    if { [glCheckFramebufferStatus GL_FRAMEBUFFER] != $::GL_FRAMEBUFFER_COMPLETE } {
        puts [format "GL_FRAMEBUFFER_COMPLETE error 0x%x" [glCheckFramebufferStatus GL_FRAMEBUFFER]]
    }

    glBindTexture GL_TEXTURE_2D 0
    glBindFramebuffer GL_FRAMEBUFFER 0

    return [$g_Demo(textureTexture) get 0]
}

# Function for initialization.
proc Init {} {
    global g_Demo g_Program g_ProgramBackground

    # Points of a triangle.
    for { set z 0 } { $z < $g_Demo(WaterPlaneLen) } { incr z } {
        for { set x 0 } { $x < $g_Demo(WaterPlaneLen) } { incr x } {
            lappend pointList [expr { -$g_Demo(WaterPlaneLen)*0.5 + 0.5 + double ($x) }]
            lappend pointList 0.0
            lappend pointList [expr {  $g_Demo(WaterPlaneLen)*0.5 - 0.5 - double ($z) }]
            lappend pointList 1.0
        }
    }
    set pointVec [tcl3dVectorFromList GLfloat $pointList]
    set pointVecSize [expr {[llength $pointList] * [$pointVec elemsize]}]

    for { set k 0 } { $k < [expr {$g_Demo(WaterPlaneLen)-1}] } { incr k } {
        for { set i 0 } { $i < $g_Demo(WaterPlaneLen) } { incr i } {
            if { $k%2 == 0 } {
                lappend indicesList [expr { $i + $k*$g_Demo(WaterPlaneLen) }]
                lappend indicesList [expr { $i + ($k+1)*$g_Demo(WaterPlaneLen) }]
            } else {
                lappend indicesList [expr { $g_Demo(WaterPlaneLen) -1 - $i + $k*$g_Demo(WaterPlaneLen) }]
                lappend indicesList [expr { $g_Demo(WaterPlaneLen) -1 - $i + ($k+1)*$g_Demo(WaterPlaneLen) }]
            }
        }
    }

    set indicesVec [tcl3dVectorFromList GLuint $indicesList]
    set indicesVecSize [expr {[llength $indicesList] * [$indicesVec elemsize]}]

    # Load the source of the vertex shader.
    set vertexSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "Vertex.vs"]]

    # Load the source of the fragment shader.
    set fragmentSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "Fragment.fs"]]

    set g_Program [tcl3dOglBuildProgram $vertexSource "" "" "" $fragmentSource]
    set program [dict get $g_Program program]

    set g_Demo(vao) [tcl3dVector GLuint 1]
    glGenVertexArrays 1 $g_Demo(vao)
    glBindVertexArray [$g_Demo(vao) get 0]

    set g_Demo(projectionLocation)       [glGetUniformLocation $program "projectionMatrix"]
    set g_Demo(modelViewLocation)        [glGetUniformLocation $program "modelViewMatrix"]
    set g_Demo(waterPlaneLengthLocation) [glGetUniformLocation $program "waterPlaneLength"]
    set g_Demo(inverseCameraLocation)    [glGetUniformLocation $program "inverseCameraMatrix"]
    set g_Demo(cubemapLocation)          [glGetUniformLocation $program "cubemap"]
    set g_Demo(waterLocation)            [glGetUniformLocation $program "waterTexture"]
    set g_Demo(passedTimeLocation)       [glGetUniformLocation $program "passedTime"]
    set g_Demo(waveParametersLocation)   [glGetUniformLocation $program "waveParameters"]
    set g_Demo(waveDirectionsLocation)   [glGetUniformLocation $program "waveDirections"]

    set g_Demo(vertexLocation) [glGetAttribLocation $program "vertex"]

    set g_Demo(vertices) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(vertices)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(vertices) get 0]
    glBufferData GL_ARRAY_BUFFER $pointVecSize $pointVec GL_STATIC_DRAW

    set g_Demo(indices) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(indices)
    glBindBuffer GL_ELEMENT_ARRAY_BUFFER [$g_Demo(indices) get 0]
    glBufferData GL_ELEMENT_ARRAY_BUFFER $indicesVecSize $indicesVec GL_STATIC_DRAW

    # Read images and use them as a cubemap.
    set g_Demo(cubemap) [tcl3dVector GLuint 1]
    glGenTextures 1 $g_Demo(cubemap)
    glBindTexture GL_TEXTURE_CUBE_MAP [$g_Demo(cubemap) get 0]

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_pos_x.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_POSITIVE_X 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_neg_x.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_pos_y.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_neg_y.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_pos_z.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) "water_neg_z.tga"]]
    glTexImage2D GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0 [dict get $img format] \
                 [dict get $img width] [dict get $img height] 0 \
                 [dict get $img format] GL_UNSIGNED_BYTE [dict get $img data]
    [dict get $img data] delete

    glTexParameteri GL_TEXTURE_CUBE_MAP GL_TEXTURE_MIN_FILTER $::GL_LINEAR
    glTexParameteri GL_TEXTURE_CUBE_MAP GL_TEXTURE_MAG_FILTER $::GL_LINEAR
    glTexParameteri GL_TEXTURE_CUBE_MAP GL_TEXTURE_WRAP_S $::GL_CLAMP_TO_EDGE
    glTexParameteri GL_TEXTURE_CUBE_MAP GL_TEXTURE_WRAP_T $::GL_CLAMP_TO_EDGE

    glBindTexture GL_TEXTURE_CUBE_MAP 0

    glUseProgram $program

    glUniform1f $g_Demo(waterPlaneLengthLocation) $g_Demo(WaterPlaneLen)
    glVertexAttribPointer $g_Demo(vertexLocation) 4 GL_FLOAT GL_FALSE 0 "NULL"
    glEnableVertexAttribArray $g_Demo(vertexLocation)
    glActiveTexture GL_TEXTURE0

    glBindTexture GL_TEXTURE_CUBE_MAP [$g_Demo(cubemap) get 0]
    glUniform1i $g_Demo(cubemapLocation) 0
    set waterTexture [InitTexture $g_Demo(WaterPlaneLen)]

    glUseProgram $program

    glActiveTexture GL_TEXTURE1
    glBindTexture GL_TEXTURE_2D $waterTexture
    glUniform1i $g_Demo(waterLocation) 1

    # Load the source of the vertex shader.
    set vertexSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "VertexBackground.vs"]]

    # Load the source of the fragment shader.
    set fragmentSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "FragmentBackground.fs"]]

    set g_ProgramBackground [tcl3dOglBuildProgram $vertexSource "" "" "" $fragmentSource]
    set programBackground [dict get $g_ProgramBackground program]

    set g_Demo(vaoBackground) [tcl3dVector GLuint 1]
    glGenVertexArrays 1 $g_Demo(vaoBackground)
    glBindVertexArray [$g_Demo(vaoBackground) get 0]

    set sphere [glusCreateSphere [expr {$g_Demo(WaterPlaneLen)*0.5 + 0.5}] 32]
    set g_Demo(numIndicesBackground) [dict get $sphere numIndices]

    set g_Demo(projectionLocationBackground) [glGetUniformLocation $programBackground "projectionMatrix"]
    set g_Demo(modelViewLocationBackground)  [glGetUniformLocation $programBackground "modelViewMatrix"]
    set g_Demo(cubemapLocationBackground)    [glGetUniformLocation $programBackground "cubemap"]

    set g_Demo(vertexLocationBackground)     [glGetAttribLocation $programBackground "vertex"]

    set g_Demo(verticesBackground) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(verticesBackground)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesBackground) get 0]
    set vertexVec [dict get $sphere vertexVec]
    glBufferData GL_ARRAY_BUFFER \
                 [expr [dict get $sphere numVertices]*4*[$vertexVec elemsize]] \
                 $vertexVec GL_STATIC_DRAW

    set g_Demo(normalsBackground) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(normalsBackground)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(normalsBackground) get 0]
    set normalVec [dict get $sphere normalVec]
    glBufferData GL_ARRAY_BUFFER \
                 [expr [dict get $sphere numVertices]*3*[$normalVec elemsize]] \
                 $normalVec GL_STATIC_DRAW

    set g_Demo(indicesBackground) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(indicesBackground)
    glBindBuffer GL_ELEMENT_ARRAY_BUFFER [$g_Demo(indicesBackground) get 0]
    set indexVec [dict get $sphere indexVec]
    glBufferData GL_ELEMENT_ARRAY_BUFFER \
                 [expr [dict get $sphere numIndices]*[$indexVec elemsize]] \
                 $indexVec GL_STATIC_DRAW

    glusDestroyShape $sphere

    glUseProgram $programBackground

    set modelViewAsList [tcl3dVectorToList $g_Demo(modelView) 16]
    glUniformMatrix4fv $g_Demo(modelViewLocationBackground) 1 GL_FALSE $modelViewAsList

    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesBackground) get 0]
    glVertexAttribPointer $g_Demo(vertexLocationBackground) 4 GL_FLOAT GL_FALSE 0 "NULL"
    glEnableVertexAttribArray $g_Demo(vertexLocationBackground)

    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(normalsBackground) get 0]

    glUniform1i $g_Demo(cubemapLocationBackground) 0
}

proc CreateCallback { toglwin } {
    glClearColor 0.0 0.0 0.0 0.0
    glClearDepth 1.0
    glEnable GL_DEPTH_TEST
}

proc ReshapeTexture { w h } {
    global g_Demo g_ProgramTexture

    glBindFramebuffer GL_FRAMEBUFFER [$g_Demo(fboTexture) get 0]

    # Calculate the projection matrix and set it
    tcl3dOrtho [expr {-$g_Demo(TexSize)*0.5}] [expr {$g_Demo(TexSize)*0.5}] \
               [expr {-$g_Demo(TexSize)*0.5}] [expr {$g_Demo(TexSize)*0.5}] \
               1.0 100.0 $g_Demo(projectionTexture)

    glUseProgram [dict get $g_ProgramTexture program]

    set projectionAsList [tcl3dVectorToList $g_Demo(projectionTexture) 16]
    glUniformMatrix4fv $g_Demo(projectionLocationTexture) 1 GL_FALSE $projectionAsList

    # Store parent/caller width and height.
    set g_Demo(parentWidth)  $w
    set g_Demo(parentHeight) $h

    glBindFramebuffer GL_FRAMEBUFFER 0
}

proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
    global g_Demo g_Program g_ProgramBackground

    set w [$toglwin width]
    set h [$toglwin height]

    glViewport 0 0 $w $h

    if { ! $g_Demo(haveNeededVersion) } {
        return
    }

    # Calculate the projection matrix and set it
    tcl3dPerspective 40.0 [expr {double($w)/double($h) }] \
                     1.0 1000.0 $g_Demo(projection)
    set projectionAsList [tcl3dVectorToList $g_Demo(projection) 16]

    glUseProgram [dict get $g_ProgramBackground program]
    glUniformMatrix4fv $g_Demo(projectionLocationBackground) 1 GL_FALSE $projectionAsList

    glUseProgram [dict get $g_Program program]
    glUniformMatrix4fv $g_Demo(projectionLocation) 1 GL_FALSE $projectionAsList

    ReshapeTexture $w $h
}

proc RenderTexture { passedTime } {
    global g_Demo g_ProgramTexture g_Program g_Wave

    glViewport 0 0 $g_Demo(TexSize) $g_Demo(TexSize)
    glBindFramebuffer GL_FRAMEBUFFER [$g_Demo(fboTexture) get 0]
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]

    glUseProgram [dict get $g_ProgramTexture program]

    glUniform1f $g_Demo(passedTimeLocationTexture) $passedTime

    glUniform4fv $g_Demo(waveParametersLocationTexture) [expr {4*$g_Demo(NumWaves)}] $g_Wave(tex,params)
    glUniform2fv $g_Demo(waveDirectionsLocationTexture) [expr {2*$g_Demo(NumWaves)}] $g_Wave(tex,dirs)

    glBindVertexArray [$g_Demo(vaoTexture) get 0]

    glDrawElements GL_TRIANGLES $g_Demo(numIndicesTexture) GL_UNSIGNED_INT "NULL"

    glBindFramebuffer GL_FRAMEBUFFER 0

    glViewport 0 0 $g_Demo(parentWidth) $g_Demo(parentHeight)
}

proc RenderWater { passedTime angle } {
    global g_Demo g_Program g_Wave

    # Calculate the model/view matrix.
    tcl3dLookAt 0.0 1.0 0.0 \
                [expr {0.5 * sin($g_Demo(angle)) }] 1.0 [expr {-0.5 * cos($g_Demo(angle)) }] \
                0.0 1.0 0.0 \
                $g_Demo(modelView)

    # Copy the original view matrix and inverse it.
    tcl3dMatfInvert $g_Demo(modelView) $g_Demo(inverseCamera)

    glUseProgram [dict get $g_Program program]

    set modelViewAsList [tcl3dVectorToList $g_Demo(modelView) 16]
    glUniformMatrix4fv $g_Demo(modelViewLocation) 1 GL_FALSE $modelViewAsList

    set inverseCameraAsList [tcl3dVectorToList $g_Demo(inverseCamera) 16]
    glUniformMatrix4fv $g_Demo(inverseCameraLocation) 1 GL_FALSE $inverseCameraAsList

    glUniform1f $g_Demo(passedTimeLocation) $passedTime

    glUniform4fv $g_Demo(waveParametersLocation) [expr {4*$g_Demo(NumWaves)}] $g_Wave(wat,params)
    glUniform2fv $g_Demo(waveDirectionsLocation) [expr {2*$g_Demo(NumWaves)}] $g_Wave(wat,dirs)

    glBindVertexArray [$g_Demo(vao) get 0]

    glPolygonMode GL_FRONT_AND_BACK GL_FILL
    glDisable GL_CULL_FACE
    glDrawElements GL_TRIANGLE_STRIP [expr {$g_Demo(WaterPlaneLen)*($g_Demo(WaterPlaneLen)-1)*2}] GL_UNSIGNED_INT "NULL"
}

proc DisplayCallback { toglwin } {
    global g_Demo g_Program g_ProgramBackground

    if { ! $g_Demo(haveNeededVersion) } {
        $toglwin swapbuffer
        return
    }

    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]

    glUseProgram [dict get $g_ProgramBackground program]

    set modelViewAsList [tcl3dVectorToList $g_Demo(modelView) 16]
    glUniformMatrix4fv $g_Demo(modelViewLocationBackground) 1 GL_FALSE $modelViewAsList

    glBindVertexArray [$g_Demo(vaoBackground) get 0]

    glPolygonMode GL_FRONT_AND_BACK GL_FILL
    glEnable GL_CULL_FACE

    glDrawElements GL_TRIANGLES $g_Demo(numIndicesBackground) GL_UNSIGNED_INT "NULL"
    
    RenderTexture $g_Demo(passedTime)

    RenderWater $g_Demo(passedTime) $g_Demo(angle)

    set curTime [tcl3dLookupSwatch $g_Demo(stopWatch)]
    set elapsedTime [expr $curTime - $g_Demo(lastTime)]
    set g_Demo(lastTime) $curTime

    set g_Demo(passedTime) [expr { $g_Demo(passedTime) + $elapsedTime }]

    set PI2 [expr {2.0 * 3.1415926535897932384626433832795 }]
    set g_Demo(angle) [expr { $g_Demo(angle) + $PI2 / 120.0 * $elapsedTime }]

    $toglwin swapbuffer
}

proc CleanupTexture {} {
    global g_Demo g_Program g_ProgramTexture

    if { [info exists g_Demo(verticesTexture)] } {
        glDeleteBuffers 1 [$g_Demo(verticesTexture) get 0]
        $g_Demo(verticesTexture) delete
    }

    if { [info exists g_Demo(texCoordsTexture)] } {
        glDeleteBuffers 1 [$g_Demo(texCoordsTexture) get 0]
        $g_Demo(texCoordsTexture) delete
    }

    if { [info exists g_Demo(indicesTexture)] } {
        glDeleteBuffers 1 [$g_Demo(indicesTexture) get 0]
        $g_Demo(indicesTexture) delete
    }

    if { [info exists g_Demo(textureTexture)] } {
        glDeleteTextures 1 [$g_Demo(textureTexture) get 0]
        $g_Demo(textureTexture) delete
    }

    if { [info exists g_Demo(vaoTexture)] } {
        glDeleteVertexArrays 1 [$g_Demo(vaoTexture) get 0]
        $g_Demo(vaoTexture) delete
    }

    if { [info exists g_ProgramTexture] } {
        tcl3dOglDestroyProgram $g_ProgramTexture
    }

    if { [info exists g_Demo(fboTexture)] } {
        glDeleteFramebuffers 1 [$g_Demo(fboTexture) get 0]
        $g_Demo(fboTexture) delete
    }

    if { [info exists g_Demo(depthTexture)] } {
        glDeleteRenderbuffers 1 [$g_Demo(depthTexture) get 0]
        $g_Demo(depthTexture) delete
    }
}

proc Cleanup {} {
    global g_Demo g_Program g_ProgramBackground

    if { [info exists g_Demo(vertices)] } {
        glDeleteBuffers 1 [$g_Demo(vertices) get 0]
        $g_Demo(vertices) delete
    }

    if { [info exists g_Demo(normals)] } {
        glDeleteBuffers 1 [$g_Demo(normals) get 0]
        $g_Demo(normals) delete
    }

    if { [info exists g_Demo(indices)] } {
        glDeleteBuffers 1 [$g_Demo(indices) get 0]
        $g_Demo(indices) delete
    }

    if { [info exists g_Demo(cubemap)] } {
        glDeleteTextures 1 [$g_Demo(cubemap) get 0]
        $g_Demo(cubemap) delete
    }

    if { [info exists g_Demo(vao)] } {
        glDeleteVertexArrays 1 [$g_Demo(vao) get 0]
        $g_Demo(vao) delete
    }

    if { [info exists g_Program] } {
        tcl3dOglDestroyProgram $g_Program
    }

    if { [info exists g_Demo(verticesBackground)] } {
        glDeleteBuffers 1 [$g_Demo(verticesBackground) get 0]
        $g_Demo(verticesBackground) delete
    }

    if { [info exists g_Demo(normalsBackground)] } {
        glDeleteBuffers 1 [$g_Demo(normalsBackground) get 0]
        $g_Demo(normalsBackground) delete
    }

    if { [info exists g_Demo(indicesBackground)] } {
        glDeleteBuffers 1 [$g_Demo(indicesBackground) get 0]
        $g_Demo(indicesBackground) delete
    }

    if { [info exists g_Demo(vaoBackground)] } {
        glDeleteVertexArrays 1 [$g_Demo(vaoBackground) get 0]
        $g_Demo(vaoBackground) delete
    }

    if { [info exists g_Program] } {
        tcl3dOglDestroyProgram $g_ProgramBackground
    }

    CleanupTexture

    $g_Demo(projection) delete
    $g_Demo(projectionTexture) delete
    $g_Demo(modelView) delete
    $g_Demo(modelViewTexture) delete

    tcl3dDeleteSwatch $g_Demo(stopWatch)

    # Unset all global variables. 
    # Needed when running the demo in the Tcl3D presentation framework.
    foreach var [info globals g_*] {
        uplevel #0 unset $var
    }
}

# Put all exit related code here.
proc ExitProg {} {
    exit
}

# Create the OpenGL window and some Tk helper widgets.
proc CreateWindow {} {
    global g_Demo

    frame .fr
    pack .fr -expand 1 -fill both

    # Create a Togl window with an OpenGL core profile using version 3.3.
    # Reshape and Display callbacks are configured later after knowing if
    # the needed OpenGL profile version is available.
    togl .fr.toglwin -width $g_Demo(winWidth) -height $g_Demo(winHeight) \
                     -double true -depth true -alpha true \
                     -createcommand CreateCallback \
                     -coreprofile true -major 4 -minor 1

    set g_Demo(haveNeededVersion) true
    set numRows 2
    set haveGL4 [tcl3dOglHaveVersion 4]
    if { ! $haveGL4 } {
        set msgStr [format \
            "Demo needs core profile 4.1. Only have GL version %s" \
            [tcl3dOglGetVersion]]
        set g_Demo(haveNeededVersion) false
        incr numRows
    } else {
        set profile [tcl3dOglGetProfile .fr.toglwin]
        if { [dict get $profile "coreprofile"] != true } {
            set msgStr [format \
                "Demo needs core profile 4.1. Only have compatibility profile %d.%d" \
                [dict get $profile "major"] \
                [dict get $profile "minor"]]
            incr numRows
        }
    }
    if { $g_Demo(haveNeededVersion) } {
        # If OpenGL 4.1 or higher is available, initialize the buffers.
        Init
    }

    # Now attach the Reshape and Display callbacks to the Togl window.
    .fr.toglwin configure \
        -reshapecommand ReshapeCallback \
        -displaycommand DisplayCallback

    listbox .fr.usage -font $g_Demo(listFont) -height $numRows
    label .fr.info
    grid .fr.toglwin -row 0 -column 0 -sticky news
    grid .fr.usage   -row 1 -column 0 -sticky news
    grid .fr.info    -row 2 -column 0 -sticky news
    grid rowconfigure .fr 0 -weight 1
    grid columnconfigure .fr 0 -weight 1
    wm title . "Tcl3D demo: Nopper's core profile tutorials (Example 14 - Water)"

    # Watch for Esc key and Quit messages
    wm protocol . WM_DELETE_WINDOW "ExitProg"
    bind . <Key-Escape> "ExitProg"

    bind .fr.toglwin <1> "StartAnimation"
    bind .fr.toglwin <2> "StopAnimation"
    bind .fr.toglwin <3> "StopAnimation"
    bind .fr.toglwin <Control-Button-1> "StopAnimation" 

    .fr.usage insert end "Key-Escape Exit"
    .fr.usage insert end "Mouse-L|MR Start|Stop animation"
    if { [info exists msgStr] } {
        .fr.usage insert end $msgStr
        .fr.usage itemconfigure end -background red
    } else {
        .fr.usage configure -state disabled
    }
}

CreateWindow
ReshapeCallback .fr.toglwin

PrintInfo [tcl3dOglGetInfoString]

if { [file tail [info script]] eq [file tail $::argv0] } {
    # If started directly from tclsh or wish, then start animation.
    update
    StartAnimation
}
