#
# OpenGL 4.0 with GLEW - Example 13
#
# Method for Real-Time LOD Terrain Rendering on Modern GPU
#
# @author  Norbert Nopper norbert@nopper.tv
# @version 05.03.2010
#
# Homepage: https://github.com/McNopper/OpenGL
#
# Copyright Norbert Nopper
#
# Modified for Tcl3D by Paul Obermeier 2011/02/03
# 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 space when travelling horizontal form one pixel to the next in meters.
set g_Demo(horizontalPixelSpacing) 60.0

# One turn takes x seconds.
set g_Demo(turnDuration) 20.0

# The space when travelling all pixel colors in meters.
set g_Demo(verticalPixelRange) 10004.0

# The scale to convert the real world meters in virtual worls scale.
set g_Demo(metersToVirtualWorldScale) 5.0

# The circle around radius.
set g_Demo(radius) 6000.0

# The normal map of the terrain. Width and height does not have to be the same,
# but they have to be power of two plus one. 
set g_Demo(normalMap) "grand_canyon_normal.jpg"

# The height map of the terrain. Width and height does not have to be the same,
# but they have to be power of two plus one. 
set g_Demo(heightMap) "grand_canyon_height.jpg"

# The color map of the terrain.
set g_Demo(colorMap) "grand_canyon_color.jpg"

# Flags for switching between viewing modes.
set g_Demo(filled)        true
set g_Demo(animationOn)   true
set g_Demo(topViewActive) true

# The maximum detail level which is 2^s = sMapExtend
set g_Demo(sMaxDetailLevel) 0

# The maximum detail level which is 2^t = tMapExtend
set g_Demo(tMaxDetailLevel) 0

# The overall maximum detail level from s and t.
set g_Demo(overallMaxDetailLevel) 0

# Number of points in s direction.
set g_Demo(sNumPoints) 0

# Number of points in t direction.
set g_Demo(tNumPoints) 0

# FOV radius
set g_Demo(fovRadius) 10000.0

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

# Model view matrix.
set g_Demo(modelView) [tcl3dVector GLfloat 16]

# Texture to world space matrix.
set g_Demo(textureToWorld) [tcl3dVector GLfloat 16]
set g_Demo(textureToWorldNormal) [tcl3dVector GLfloat 16]

# World to texture space matrix.
set g_Demo(worldToTexture) [tcl3dVector GLfloat 16]
set g_Demo(worldToTextureNormal) [tcl3dVector GLfloat 16]

# Detail level at the beginning of the map.
set g_Demo(minimumDetailLevel) 4

# Additional detail level in the first pass. Adjust in GeometryPassOne max_vertices = 4^(firstPassDetailLevel+1)
set g_Demo(firstPassDetailLevel) 2

# Number of quadrants when going to the next detail level.
set g_Demo(quadrantStep) 2

set g_Demo(angle) 0.0

# 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 Min { a b } {
    if { $a < $b } {
        return $a
    } else {
        return $b
    }
}

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)
    }
}

# Function for initialization.
proc Init {} {
    global g_Demo g_ViewData

    # gl_Position will be transformed to the buffer 
    set transformVaryings "vertexTransform"

    set g_ViewData(topView,camPos) [list 0.0 [expr 30000.0 * $g_Demo(metersToVirtualWorldScale)] 0.0 1.0]
    set g_ViewData(topView,camDir) [list 0.0 -1.0 0.0]
    set g_ViewData(topView,camUp)  [list 0.0 0.0 -1.0]
    set g_ViewData(topView,fov)    40.0

    set g_ViewData(personView,camPos) [list 0.0 [expr 4700.0 * $g_Demo(metersToVirtualWorldScale)] 0.0 1.0]
    set g_ViewData(personView,camDir) [list 0.0 0.0 -1.0]
    set g_ViewData(personView,camUp)  [list 0.0 1.0 0.0]
    set g_ViewData(personView,fov)    60.0

    set lightDir [list 1.0 1.0 1.0]

    set g_Demo(activeView) "personView"

    glPixelStorei GL_UNPACK_ALIGNMENT 1

    set g_Demo(normalMapTexture) [tcl3dVector GLuint 1]
    glGenTextures 1 $g_Demo(normalMapTexture)
    glBindTexture GL_TEXTURE_RECTANGLE [$g_Demo(normalMapTexture) get 0]

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) $g_Demo(normalMap)]]
    glTexImage2D GL_TEXTURE_RECTANGLE 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_RECTANGLE GL_TEXTURE_MIN_FILTER $::GL_NEAREST
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_MAG_FILTER $::GL_NEAREST
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_WRAP_S $::GL_CLAMP_TO_EDGE
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_WRAP_T $::GL_CLAMP_TO_EDGE

    set g_Demo(heightMapTexture) [tcl3dVector GLuint 1]
    glGenTextures 1 $g_Demo(heightMapTexture)
    glBindTexture GL_TEXTURE_RECTANGLE [$g_Demo(heightMapTexture) get 0]

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) $g_Demo(heightMap)]]
    glTexImage2D GL_TEXTURE_RECTANGLE 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]
    set sMapExtend [expr double([dict get $img width])]
    set tMapExtend [expr double([dict get $img height])]
    [dict get $img data] delete

    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_MIN_FILTER $::GL_NEAREST
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_MAG_FILTER $::GL_NEAREST
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_WRAP_S $::GL_CLAMP_TO_EDGE
    glTexParameteri GL_TEXTURE_RECTANGLE GL_TEXTURE_WRAP_T $::GL_CLAMP_TO_EDGE

    set sMaxDetailLevel [expr int (floor (log ($sMapExtend) / log (2.0)))]
    set tMaxDetailLevel [expr int (floor (log ($tMapExtend) / log (2.0)))]

    set g_Demo(overallMaxDetailLevel) [Min $sMaxDetailLevel $tMaxDetailLevel]

    if { $g_Demo(minimumDetailLevel) > $g_Demo(overallMaxDetailLevel) } {
        puts [format "Detail level to high %d > %d" \
             $g_Demo(minimumDetailLevel) $g_Demo(overallMaxDetailLevel)]
        return false
    }

    if { $g_Demo(minimumDetailLevel) + $g_Demo(firstPassDetailLevel) > $g_Demo(overallMaxDetailLevel) } {
        puts [format "First pass detail level to high %d > %d" \
              [expr $g_Demo(minimumDetailLevel) + $g_Demo(firstPassDetailLevel)] $g_Demo(overallMaxDetailLevel)]
        return false
    }

    set lev [expr pow (2.0, $g_Demo(overallMaxDetailLevel) - ($g_Demo(minimumDetailLevel) + $g_Demo(firstPassDetailLevel)))]
    if { $lev > 32.0 } {
        puts [format "Tessellation level to high %d > 32" [expr int ($lev)]]
        return false
    }

    set detailStep [expr pow (2.0, [expr $g_Demo(overallMaxDetailLevel) - $g_Demo(minimumDetailLevel)])]

    set g_Demo(sNumPoints) [expr int (ceil ($sMapExtend / $detailStep)) - 1]
    set g_Demo(tNumPoints) [expr int (ceil ($tMapExtend / $detailStep)) - 1]

    set detailStep2 [expr {0.5 + $detailStep / 2.0}]
    for { set t 0 } { $t < $g_Demo(tNumPoints) } { incr t } {
        for { set s 0 } { $s < $g_Demo(sNumPoints) } { incr s } {
            lappend mapList [expr {$detailStep2 + $s * $detailStep}]
            lappend mapList [expr {$detailStep2 + $t * $detailStep}]

            lappend indList [expr {$t * $g_Demo(sNumPoints) + $s}]
        }
    }
    set mapVec [tcl3dVectorFromList GLfloat $mapList]
    set indVec [tcl3dVectorFromList GLuint  $indList]
    set mapVecSize [expr [llength $mapList] * [$mapVec elemsize]]
    set indVecSize [expr [llength $indList] * [$indVec elemsize]]

    #
    # Transfering vertices and indices into GPU
    #

    # Pass one

    # Generating vertex array object and binding to it.
    set g_Demo(vaoPassOne) [tcl3dVector GLuint 1]
    glGenVertexArrays 1 $g_Demo(vaoPassOne)
    glBindVertexArray [$g_Demo(vaoPassOne) get 0]

    # Generating the vertices buffer and binding to it.
    set g_Demo(verticesBufferPassOne) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(verticesBufferPassOne)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesBufferPassOne) get 0]

    # Transfering the vertices.
    glBufferData GL_ARRAY_BUFFER $mapVecSize $mapVec GL_STATIC_DRAW

    # Generating the indices buffer and binding to it.
    set g_Demo(indicesBufferPassOne) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(indicesBufferPassOne)
    glBindBuffer GL_ELEMENT_ARRAY_BUFFER [$g_Demo(indicesBufferPassOne) get 0]

    # Transfering the indices.
    glBufferData GL_ELEMENT_ARRAY_BUFFER $indVecSize $indVec GL_STATIC_DRAW

    # First 0 is the location = 0
    glVertexAttribPointer 0 2 GL_FLOAT GL_FALSE 0 "NULL"

    # Enable location = 0
    glEnableVertexAttribArray 0

    # Pass two.

    # Generating vertex array object and binding to it.
    set g_Demo(vaoPassTwo) [tcl3dVector GLuint 1]
    glGenVertexArrays 1 $g_Demo(vaoPassTwo)
    glBindVertexArray [$g_Demo(vaoPassTwo) get 0]

    # Generating the vertices buffer and binding to it.
    set g_Demo(verticesBufferPassTwo) [tcl3dVector GLuint 1]
    glGenBuffers 1 $g_Demo(verticesBufferPassTwo)
    glBindBuffer GL_ARRAY_BUFFER [$g_Demo(verticesBufferPassTwo) get 0]

    # Reserving space for the incoming vertices.
    glBufferData GL_ARRAY_BUFFER \
                 [expr {$g_Demo(sNumPoints) * $g_Demo(tNumPoints) * \
                        int (pow (4, $g_Demo(firstPassDetailLevel) + 1)) * 2 * [$mapVec elemsize]}] "NULL" GL_STATIC_DRAW

    # First 0 is the location = 0
    glVertexAttribPointer 0 2 GL_FLOAT GL_FALSE 0 "NULL"

    # Enable location = 0
    glEnableVertexAttribArray 0

    $mapVec delete
    $indVec delete

    set g_Demo(colorMapTexture) [tcl3dVector GLuint 1]
    glGenTextures 1 $g_Demo(colorMapTexture)
    glBindTexture GL_TEXTURE_2D [$g_Demo(colorMapTexture) get 0]

    set img [tcl3dReadImg [file join $g_Demo(scriptDir) $g_Demo(colorMap)]]
    glTexImage2D GL_TEXTURE_2D 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_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_REPEAT
    glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T $::GL_REPEAT

    # Creating the shader program.

    # Pass one.

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

    # Load the source of the geometry shader.
    set geometrySource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "GeometryPassOne.gs"]]

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

    # Compile and ...
    set g_Demo(shaderProgramPassOne) [tcl3dOglCompileProgram $vertexSource "" "" $geometrySource $fragmentSource]
    set programPassOne [dict get $g_Demo(shaderProgramPassOne) program]
    
    # ... add the transform variable ...
    glTransformFeedbackVaryings $programPassOne 1 [list $transformVaryings] GL_SEPARATE_ATTRIBS

    # ... and link the program
    tcl3dOglLinkProgram $g_Demo(shaderProgramPassOne)

    set g_Demo(halfDetailStepLocationPassOne)       [glGetUniformLocation $programPassOne "halfDetailStep"]
    set g_Demo(firstPassDetailLevelLocationPassOne) [glGetUniformLocation $programPassOne "firstPassDetailLevel"]
    set g_Demo(positionTextureLocationPassOne)      [glGetUniformLocation $programPassOne "positionTexture"]
    set g_Demo(fovRadiusLocationPassOne)            [glGetUniformLocation $programPassOne "fovRadius"]
    set g_Demo(leftNormalTextureLocationPassOne)    [glGetUniformLocation $programPassOne "leftNormalTexture"]
    set g_Demo(rightNormalTextureLocationPassOne)   [glGetUniformLocation $programPassOne "rightNormalTexture"]
    set g_Demo(backNormalTextureLocationPassOne)    [glGetUniformLocation $programPassOne "backNormalTexture"]

    # Pass two.

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

    # Load the source of the control shader.
    set controlSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "ControlPassTwo.tcs"]]

    # Load the source of the evaluation shader.
    set evaluationSource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "EvaluationPassTwo.tes"]]

    # Load the source of the geometry shader.
    set geometrySource [tcl3dOglReadShaderFile [file join $g_Demo(scriptDir) "GeometryPassTwo.gs"]]

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

    set g_Demo(shaderProgramPassTwo) [tcl3dOglBuildProgram $vertexSource $controlSource $evaluationSource \
                                                           $geometrySource $fragmentSource]
    set programPassTwo [dict get $g_Demo(shaderProgramPassTwo) program]

    # Get the location of the matrix.
    set g_Demo(tmvpLocationPassTwo)                 [glGetUniformLocation $programPassTwo "TMVP"]
    set g_Demo(positionTextureLocationPassTwo)      [glGetUniformLocation $programPassTwo "positionTexture"]
    set g_Demo(maxTessellationLevelLocationPassTwo) [glGetUniformLocation $programPassTwo "maxTessellationLevel"]
    set g_Demo(quadrantStepLocationPassTwo)         [glGetUniformLocation $programPassTwo "quadrantStep"]
    set g_Demo(leftNormalTextureLocationPassTwo)    [glGetUniformLocation $programPassTwo "leftNormalTexture"]
    set g_Demo(rightNormalTextureLocationPassTwo)   [glGetUniformLocation $programPassTwo "rightNormalTexture"]
    set g_Demo(backNormalTextureLocationPassTwo)    [glGetUniformLocation $programPassTwo "backNormalTexture"]
    set g_Demo(normalMapTextureLocationPassTwo)     [glGetUniformLocation $programPassTwo "normalMapTexture"]
    set g_Demo(heightMapTextureLocationPassTwo)     [glGetUniformLocation $programPassTwo "heightMapTexture"]
    set g_Demo(colorMapTextureLocationPassTwo)      [glGetUniformLocation $programPassTwo "colorMapTexture"]
    set g_Demo(lightDirLocationPassTwo)             [glGetUniformLocation $programPassTwo "lightDir"]

    # One time GL settings.
    glPatchParameteri GL_PATCH_VERTICES 4
    set g_Demo(transformFeedbackQuery) [tcl3dVector GLuint 1]
    glGenQueries 1 $g_Demo(transformFeedbackQuery)

    # Matrix calculations
    tcl3dMatfIdentity $g_Demo(textureToWorld)
    tcl3dMatfIdentity $g_Demo(textureToWorldNormal)

    tcl3dScalef \
        [expr {$g_Demo(horizontalPixelSpacing) * $g_Demo(metersToVirtualWorldScale)}] \
        [expr {$g_Demo(verticalPixelRange)     * $g_Demo(metersToVirtualWorldScale)}] \
        [expr {$g_Demo(horizontalPixelSpacing) * $g_Demo(metersToVirtualWorldScale)}] \
        $g_Demo(textureToWorld)

    tcl3dScalef 1.0 1.0 -1.0 $g_Demo(textureToWorld)
    tcl3dScalef 1.0 1.0 -1.0 $g_Demo(textureToWorldNormal)

    tcl3dTranslatef [expr {-$sMapExtend / 2.0}] 0.0 [expr {-$tMapExtend / 2.0}] $g_Demo(textureToWorld)

    tcl3dMatfInvert $g_Demo(textureToWorld) $g_Demo(worldToTexture)
    tcl3dMatfInvert $g_Demo(textureToWorldNormal) $g_Demo(worldToTextureNormal)


    # Pass One
    glUseProgram $programPassOne

    glUniform1f  $g_Demo(halfDetailStepLocationPassOne) [expr {$detailStep / 2.0}]
    glUniform1ui $g_Demo(firstPassDetailLevelLocationPassOne) $g_Demo(firstPassDetailLevel)
    glUniform1f  $g_Demo(fovRadiusLocationPassOne) \
                 [expr {$g_Demo(fovRadius) / $g_Demo(horizontalPixelSpacing) * $g_Demo(metersToVirtualWorldScale)}]

    # Pass Two
    glUseProgram $programPassTwo

    glUniform1ui $g_Demo(maxTessellationLevelLocationPassTwo) \
                 [expr {$g_Demo(overallMaxDetailLevel) - ($g_Demo(minimumDetailLevel) + $g_Demo(firstPassDetailLevel))}]
    glUniform1i $g_Demo(quadrantStepLocationPassTwo) $g_Demo(quadrantStep)

    glActiveTexture GL_TEXTURE0
    glUniform1i $g_Demo(heightMapTextureLocationPassTwo) 0
    glBindTexture GL_TEXTURE_RECTANGLE [$g_Demo(heightMapTexture) get 0]

    glActiveTexture GL_TEXTURE1
    glUniform1i $g_Demo(colorMapTextureLocationPassTwo) 1
    glBindTexture GL_TEXTURE_2D [$g_Demo(colorMapTexture) get 0]

    glActiveTexture GL_TEXTURE2
    glUniform1i $g_Demo(normalMapTextureLocationPassTwo) 2
    glBindTexture GL_TEXTURE_RECTANGLE [$g_Demo(normalMapTexture) get 0]

    glActiveTexture GL_TEXTURE0

    glUniform3fv $g_Demo(lightDirLocationPassTwo) 1 $lightDir

    return true
}

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

proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
    global g_Demo g_ViewData

    set w [$toglwin width]
    set h [$toglwin height]
    set g_Demo(winWidth)  $w
    set g_Demo(winHeight) $h

    glViewport 0 0 $w $h

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

    set activeView $g_Demo(activeView)
    tcl3dLookAt \
        [lindex $g_ViewData($activeView,camPos) 0] \
        [lindex $g_ViewData($activeView,camPos) 1] \
        [lindex $g_ViewData($activeView,camPos) 2] \
        [expr {[lindex $g_ViewData($activeView,camPos) 0] + [lindex $g_ViewData($activeView,camDir) 0]}] \
        [expr {[lindex $g_ViewData($activeView,camPos) 1] + [lindex $g_ViewData($activeView,camDir) 1]}] \
        [expr {[lindex $g_ViewData($activeView,camPos) 2] + [lindex $g_ViewData($activeView,camDir) 2]}] \
        [lindex $g_ViewData($activeView,camUp) 0] \
        [lindex $g_ViewData($activeView,camUp) 1] \
        [lindex $g_ViewData($activeView,camUp) 2] \
        $g_Demo(modelView)

    tcl3dPerspective $g_ViewData($activeView,fov) [expr {double($w)/double($h) }] 1.0 1000000.0 $g_Demo(projection)

    glUseProgram [dict get $g_Demo(shaderProgramPassTwo) program]

    set TMVP [tcl3dVector GLfloat 16]
    tcl3dMatfIdentity $TMVP
    tcl3dMatfMult $TMVP $g_Demo(projection)     $TMVP
    tcl3dMatfMult $TMVP $g_Demo(modelView)      $TMVP
    tcl3dMatfMult $TMVP $g_Demo(textureToWorld) $TMVP

    glUniformMatrix4fv $g_Demo(tmvpLocationPassTwo) 1 GL_FALSE [tcl3dVectorToList $TMVP 16]

    $TMVP delete
}

proc ToggleView { toglwin } {
    global g_Demo

    set g_Demo(topViewActive) [expr ! $g_Demo(topViewActive)]

    if { $g_Demo(topViewActive) } {
        set g_Demo(activeView) "personView"
    } else {
        set g_Demo(activeView) "topView"
    }

    tcl3dPerspective 40.0 [expr {double($g_Demo(winWidth))/double($g_Demo(winHeight)) }] \
                     1.0 1000000.0 $g_Demo(projection)

    $toglwin postredisplay
}

proc ToggleAnimation { toglwin } {
    global g_Demo

    set g_Demo(animationOn) [expr ! $g_Demo(animationOn)]
    $toglwin postredisplay
}

proc ToggleWireframe { toglwin } {
    global g_Demo

    set g_Demo(filled) [expr ! $g_Demo(filled)]

    if { $g_Demo(filled) } {
        glPolygonMode GL_FRONT_AND_BACK GL_LINE
    } else {
        glPolygonMode GL_FRONT_AND_BACK GL_FILL
    }

    $toglwin postredisplay
}

proc DisplayCallback { toglwin } {
    global g_Demo g_ViewData

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

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

    # Animation update
    set PI2 [expr {2.0 * 3.1415926535897932384626433832795 }]
    lset g_ViewData(personView,camPos) 0 [expr -cos($PI2 * $g_Demo(angle)/$g_Demo(turnDuration)) * $g_Demo(radius) * $g_Demo(metersToVirtualWorldScale)]
    lset g_ViewData(personView,camPos) 2 [expr -sin($PI2 * $g_Demo(angle)/$g_Demo(turnDuration)) * $g_Demo(radius) * $g_Demo(metersToVirtualWorldScale)]

    lset g_ViewData(personView,camDir) 0 [expr  sin($PI2 * $g_Demo(angle)/$g_Demo(turnDuration))]
    lset g_ViewData(personView,camDir) 2 [expr -cos($PI2 * $g_Demo(angle)/$g_Demo(turnDuration))]

    if { $g_Demo(animationOn) } {
        set g_Demo(angle) [expr $g_Demo(angle) + $elapsedTime]
    }

    set activeView $g_Demo(activeView)
    tcl3dLookAt \
        [lindex $g_ViewData($activeView,camPos) 0] \
        [lindex $g_ViewData($activeView,camPos) 1] \
        [lindex $g_ViewData($activeView,camPos) 2] \
        [expr {[lindex $g_ViewData($activeView,camPos) 0] + [lindex $g_ViewData($activeView,camDir) 0]}] \
        [expr {[lindex $g_ViewData($activeView,camPos) 1] + [lindex $g_ViewData($activeView,camDir) 1]}] \
        [expr {[lindex $g_ViewData($activeView,camPos) 2] + [lindex $g_ViewData($activeView,camDir) 2]}] \
        [lindex $g_ViewData($activeView,camUp) 0] \
        [lindex $g_ViewData($activeView,camUp) 1] \
        [lindex $g_ViewData($activeView,camUp) 2] \
        $g_Demo(modelView)

    glUseProgram [dict get $g_Demo(shaderProgramPassTwo) program]

    set TMVP [tcl3dVector GLfloat 16]
    tcl3dMatfIdentity $TMVP
    tcl3dMatfMult $TMVP $g_Demo(projection)     $TMVP
    tcl3dMatfMult $TMVP $g_Demo(modelView)      $TMVP
    tcl3dMatfMult $TMVP $g_Demo(textureToWorld) $TMVP

    glUniformMatrix4fv $g_Demo(tmvpLocationPassTwo) 1 GL_FALSE [tcl3dVectorToList $TMVP 16]
    $TMVP delete

    # Position
    set flatPos [tcl3dVector GLfloat 3]
    set positionTexture [tcl3dVector GLfloat 3]
    set directionTexture [tcl3dVector GLfloat 3]
    set leftNormal [tcl3dVector GLfloat 3]
    set leftNormalTexture [tcl3dVector GLfloat 3]
    set rightNormal [tcl3dVector GLfloat 3]
    set rightNormalTexture [tcl3dVector GLfloat 3]
    set backNormal [tcl3dVector GLfloat 3]
    set backNormalTexture [tcl3dVector GLfloat 3]
    set rotationMatrix [tcl3dVector GLfloat 16]

    $flatPos set 0 [lindex $g_ViewData(personView,camPos) 0]
    $flatPos set 1 0.0
    $flatPos set 2 [lindex $g_ViewData(personView,camPos) 2]

    tcl3dMatfTransformPoint $flatPos $g_Demo(worldToTexture) $positionTexture

    # Direction
    tcl3dMatfTransformVector $flatPos $g_Demo(worldToTexture) $directionTexture
    set camDir [tcl3dVectorFromList GLfloat $g_ViewData(personView,camDir)]

    # Left normal of field ov view
    tcl3dMatfIdentity $rotationMatrix
    tcl3dRotatef [expr $g_ViewData(personView,fov) * ($g_Demo(winWidth) / $g_Demo(winHeight)) / 2.0 + 90.0]  0.0 1.0 0.0  $rotationMatrix
    tcl3dMatfTransformVector $camDir $rotationMatrix $leftNormal
    tcl3dMatfTransformVector $leftNormal $g_Demo(worldToTextureNormal) $leftNormalTexture

    # Right normal of field ov view
    tcl3dMatfIdentity $rotationMatrix
    tcl3dRotatef [expr -$g_ViewData(personView,fov) * ($g_Demo(winWidth) / $g_Demo(winHeight)) / 2.0 - 90.0]  0.0 1.0 0.0  $rotationMatrix
    tcl3dMatfTransformVector $camDir $rotationMatrix $rightNormal
    tcl3dMatfTransformVector $rightNormal $g_Demo(worldToTextureNormal) $rightNormalTexture

    # Back normal of field ov views
    tcl3dMatfIdentity $rotationMatrix
    tcl3dRotatef 180.0  0.0 1.0 0.0  $rotationMatrix
    tcl3dMatfTransformVector $camDir $rotationMatrix $backNormal
    tcl3dMatfTransformVector $backNormal $g_Demo(worldToTextureNormal) $backNormalTexture
    $camDir delete

    # OpenGL stuff
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]

    # Pass one.
    glEnable GL_RASTERIZER_DISCARD

    glUseProgram [dict get $g_Demo(shaderProgramPassOne) program]

    glUniform4fv $g_Demo(positionTextureLocationPassOne)    1 [tcl3dVectorToList $positionTexture 4]
    glUniform3fv $g_Demo(leftNormalTextureLocationPassOne)  1 [tcl3dVectorToList $leftNormalTexture 3]
    glUniform3fv $g_Demo(rightNormalTextureLocationPassOne) 1 [tcl3dVectorToList $rightNormalTexture 3]
    glUniform3fv $g_Demo(backNormalTextureLocationPassOne)  1 [tcl3dVectorToList $backNormalTexture 3]

    glBindVertexArray [$g_Demo(vaoPassOne) get 0]
    glBindBufferBase GL_TRANSFORM_FEEDBACK_BUFFER 0 [$g_Demo(verticesBufferPassTwo) get 0]

    glBeginQuery GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN [$g_Demo(transformFeedbackQuery) get 0]
    glBeginTransformFeedback GL_POINTS

    glDrawElements GL_POINTS [expr $g_Demo(sNumPoints) * $g_Demo(tNumPoints)] GL_UNSIGNED_INT "NULL"

    glEndTransformFeedback

    glEndQuery GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN

    glDisable GL_RASTERIZER_DISCARD

    # Pass two
    glUseProgram [dict get $g_Demo(shaderProgramPassTwo) program]

    glUniform4fv $g_Demo(positionTextureLocationPassTwo)    1 [tcl3dVectorToList $positionTexture 4]
    glUniform3fv $g_Demo(leftNormalTextureLocationPassTwo)  1 [tcl3dVectorToList $leftNormalTexture 3]
    glUniform3fv $g_Demo(rightNormalTextureLocationPassTwo) 1 [tcl3dVectorToList $rightNormalTexture 3]
    glUniform3fv $g_Demo(backNormalTextureLocationPassTwo)  1 [tcl3dVectorToList $backNormalTexture 3]

    glBindVertexArray [$g_Demo(vaoPassTwo) get 0]

    set primitivesWritten [tcl3dVector GLuint 1]
    glGetQueryObjectuiv [$g_Demo(transformFeedbackQuery) get 0] GL_QUERY_RESULT $primitivesWritten
    glDrawArrays GL_PATCHES 0 [$primitivesWritten get 0]
    $primitivesWritten delete

    $flatPos delete
    $positionTexture delete
    $directionTexture delete
    $leftNormal delete
    $leftNormalTexture delete
    $rightNormal delete
    $rightNormalTexture delete
    $backNormal delete
    $backNormalTexture delete
    $rotationMatrix delete

    $toglwin swapbuffer
}

proc Cleanup {} {
    global g_Demo g_ViewData

    # Pass one.
    if { [info exists g_Demo(verticesBufferPassOne)] } {
        glDeleteBuffers 1 [$g_Demo(verticesBufferPassOne) get 0]
        $g_Demo(verticesBufferPassOne) delete
    }

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

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

    if { [info exists g_Demo(shaderProgramPassOne)] } {
        tcl3dOglDestroyProgram $g_Demo(shaderProgramPassOne)
    }

    # Pass two.
    if { [info exists g_Demo(verticesBufferPassTwo)] } {
        glDeleteBuffers 1 [$g_Demo(verticesBufferPassTwo) get 0]
        $g_Demo(verticesBufferPassTwo) delete
    }

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

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

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

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

    if { [info exists g_Demo(shaderProgramPassTwo)] } {
        tcl3dOglDestroyProgram $g_Demo(shaderProgramPassTwo)
    }

    if { [info exists g_Demo(transformFeedbackQuery)] } {
        glDeleteQueries 1 [$g_Demo(transformFeedbackQuery) get 0]
        $g_Demo(transformFeedbackQuery) delete
    }

    # 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 \
                     -createcommand CreateCallback \
                     -coreprofile true -major 4 -minor 1

    set g_Demo(haveNeededVersion) true
    set numRows 4
    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 13 - Terrain Rendering)"

    # Watch for Esc key and Quit messages
    wm protocol . WM_DELETE_WINDOW "ExitProg"
    bind . <Key-Escape> "ExitProg"
    bind . <Key-v>      "ToggleView .fr.toglwin"
    bind . <Key-a>      "ToggleAnimation .fr.toglwin"
    bind . <Key-w>      "ToggleWireframe .fr.toglwin"

    .fr.usage insert end "Key-Escape Exit"
    .fr.usage insert end "Key-v      Toggle view"
    .fr.usage insert end "Key-a      Toggle animation"
    .fr.usage insert end "Key-w      Toggle wireframe"
    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
}
