#-----------------------------------------------------------------------------
#           Name: ogl_color_tracking.cpp
#         Author: Kevin Harris (kevin@codesampler.com)
#  Last Modified: 04/28/05
#    Description: This sample demonstrates color-tracking and two-sided 
#                 lighting in OpenGL.
#
#                 Color tracking allows us to substitute the color of our 
#                 vertices for one or more of the material colors used by 
#                 OpenGL's lighting equation. This feature is typically not 
#                 used much anymore as since modelers today use textures to 
#                 color their geometry - not vertex colors. Of course, this 
#                 technique is alive and kicking in a billion lines of legacy 
#                 code so it's good to understand this technique just in case 
#                 you run across it.
#
#                 Two-sided lighting basically means that we want OpenGL to 
#                 light both sides of our geometry instead of just the front 
#                 faces. Again, this feature is typically not used much 
#                 anymore since it's very inefficient to light both sides of 
#                 every triangle but there are some cases where this is 
#                 helpful to know.
#                 
#   Control Keys: c - Toggle between a material color or color tracking the
#                     vertices
#                 l - Toggle two-sided lighting
#-----------------------------------------------------------------------------
#
# Original C++ code by Kevin Harris (kevin@codesampler.com)
# See www.codesampler.com for the original files
# OpenGL samples page 5: Color Tracking and Two-Sided lighting
# http://www.codesampler.com/oglsrc/oglsrc_5.htm#ogl_color_tracking
#
# Modified for Tcl3D by Paul Obermeier 2008/05/01
# 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_listFont {-family {Courier} -size 10}

set g_WinWidth  640
set g_WinHeight 480

set g_bColorTracking false
set g_bTwoSidedLighting true

set g_fSpinX(1) 0.0
set g_fSpinY(1) 0.0

# For testing purposes create a simple quad whose vertices are colored 
# pure green.
# Use GL_T2F_C4F_N3F_V3F for glInterleavedArrays
#   tu  tv   r   g   b   a    nx  ny  nz   x    y     z 
set g_quadVertices [tcl3dVectorFromArgs GLfloat \
    0.0 0.0  0.0 1.0 0.0 1.0  0.0 0.0 1.0  -1.0 -1.0  0.0 \
    1.0 0.0  0.0 1.0 0.0 1.0  0.0 0.0 1.0   1.0 -1.0  0.0 \
    1.0 1.0  0.0 1.0 0.0 1.0  0.0 0.0 1.0   1.0  1.0  0.0 \
    0.0 1.0  0.0 1.0 0.0 1.0  0.0 0.0 1.0  -1.0  1.0  0.0 \
]

# Determine the directory of this script.
set g_scriptDir [file dirname [info script]]

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

# 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 SetMouseInput { btn x y } {
    set ::g_LastMousePosX($btn) $x
    set ::g_LastMousePosY($btn) $y
}

proc GetMouseInput { btn x y } {
    set nXDiff [expr ($x - $::g_LastMousePosX($btn))]
    set nYDiff [expr ($y - $::g_LastMousePosY($btn))]
        
    set ::g_fSpinX($btn) [expr $::g_fSpinX($btn) - $nXDiff]
    set ::g_fSpinY($btn) [expr $::g_fSpinY($btn) - $nYDiff]

    set ::g_LastMousePosX($btn) $x
    set ::g_LastMousePosY($btn) $y
    .fr.toglwin postredisplay
}

proc ToggleColorTracking {} {
    set ::g_bColorTracking [expr ! $::g_bColorTracking]
    .fr.toglwin postredisplay
}

proc ToggleTwoSidedLighting {} {
    set ::g_bTwoSidedLighting [expr ! $::g_bTwoSidedLighting]
    .fr.toglwin postredisplay
}

proc LoadTexture {} {
    set texName [file join $::g_scriptDir "test.bmp"]
    set retVal [catch {set phImg [image create photo -file $texName]} err1]
    if { $retVal != 0 } {
        error "Error reading image $texName ($err1)"
    } else {
        set w [image width  $phImg]
        set h [image height $phImg]
        set n [tcl3dPhotoChans $phImg]
        set pTextureImage [tcl3dVectorFromPhoto $phImg]
        image delete $phImg
    }

    set ::g_textureID [tcl3dVector GLuint 1]
    glGenTextures 1 $::g_textureID

    glBindTexture GL_TEXTURE_2D [$::g_textureID get 0]

    glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER $::GL_LINEAR
    glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER $::GL_LINEAR

    if { $n == 3 } {
        set type $::GL_RGB
    } else {
       set type $::GL_RGBA
    }
    glTexImage2D GL_TEXTURE_2D 0 $n $w $h 0 $type GL_UNSIGNED_BYTE $pTextureImage
    $pTextureImage delete
}

proc CreateCallback { toglwin } {
    glEnable GL_LIGHTING
    glEnable GL_TEXTURE_2D
    glEnable GL_DEPTH_TEST

    LoadTexture

    glClearColor 0.0 0.0 0.0 1.0

    glMatrixMode GL_PROJECTION
    glLoadIdentity
    gluPerspective 45.0 [expr double($::g_WinWidth)/double($::g_WinHeight)] \
                   0.1 100.0

    # Set up some lighting...
    glEnable GL_LIGHTING
    glEnable GL_LIGHT0

    # Set light 0 to be a pure white directional light
    set diffuse_light0  { 1.0 1.0 1.0 1.0 }
    set specular_light0 { 1.0 1.0 1.0 1.0 }
    set position_light0 { 0.0 0.0 1.0 0.0 }

    glLightfv GL_LIGHT0 GL_DIFFUSE  $diffuse_light0
    glLightfv GL_LIGHT0 GL_SPECULAR $specular_light0
    glLightfv GL_LIGHT0 GL_POSITION $position_light0
}

proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
    set w [$toglwin width]
    set h [$toglwin height]

    glViewport 0 0 $w $h
    glMatrixMode GL_PROJECTION
    glLoadIdentity
    gluPerspective 45.0 [expr double($w)/double($h)] 0.1 100.0
}

proc DisplayCallback { toglwin } {
    glClear [expr $::GL_COLOR_BUFFER_BIT | $::GL_DEPTH_BUFFER_BIT]

    # Viewport command is not really needed, but has been inserted for
    # Mac OSX. Presentation framework (Tk) does not send a reshape event,
    # when switching from one demo to another.
    glViewport 0 0 [$toglwin width] [$toglwin height]
 
    glMatrixMode GL_MODELVIEW
    glLoadIdentity
    glTranslatef 0.0 0.0 -4.0
    glRotatef [expr {-1.0 * $::g_fSpinY(1)}] 1.0 0.0 0.0
    glRotatef [expr {-1.0 * $::g_fSpinX(1)}] 0.0 1.0 0.0

    # Enable or disable color tracking...

    if { $::g_bColorTracking } {
        # Instead of having a material provide the ambient and diffuse colors 
        # for the lighting equation, we'll use color-tracking to use the 
        # vertex color. Our test quad has green vertices so if we set color
        # tracking while rendering it, the quad will be considered green for 
        # lighting.

        glEnable GL_COLOR_MATERIAL

        # Now, tell OpenGL which material properties will be set to the vertex
        # color. 
        #
        # Note: If you don't use a vertex array to store your geometry data, 
        # a call to glColor will do the same thing.

        glColorMaterial GL_FRONT_AND_BACK GL_AMBIENT
        glColorMaterial GL_FRONT_AND_BACK GL_DIFFUSE
    } else {
        # Stop using the quad's vertex colors in the lighting equation and use
        # the colors assigned via a material instead.

        glDisable GL_COLOR_MATERIAL

        # Set up a red material for the front side of the quad
        set ambient_mtrl_front { 1.0 0.0 0.0 1.0 }
        set diffuse_mtrl_front { 1.0 0.0 0.0 1.0 }
        glMaterialfv GL_FRONT GL_AMBIENT $ambient_mtrl_front
        glMaterialfv GL_FRONT GL_DIFFUSE $diffuse_mtrl_front

        # Set up a blue material for the backside of the quad
        set ambient_mtrl_back { 0.0 0.0 1.0 1.0 }
        set diffuse_mtrl_back { 0.0 0.0 1.0 1.0 }
        glMaterialfv GL_BACK GL_AMBIENT $ambient_mtrl_back
        glMaterialfv GL_BACK GL_DIFFUSE $diffuse_mtrl_back
    }

    # Enable or disable two-sided lighting...

    if { $::g_bTwoSidedLighting } {
        glLightModeli GL_LIGHT_MODEL_TWO_SIDE $::GL_TRUE
    } else {
        glLightModeli GL_LIGHT_MODEL_TWO_SIDE $::GL_FALSE
    }

    glBindTexture GL_TEXTURE_2D [$::g_textureID get 0]
    glInterleavedArrays GL_T2F_C4F_N3F_V3F 0 $::g_quadVertices
    glDrawArrays GL_QUADS 0 4

    $toglwin swapbuffers
}

proc Cleanup {} {
    if { [info exists ::g_textureID] } {
        glDeleteTextures 1 [$::g_textureID get 0]
        $::g_textureID delete
    }
    $::g_quadVertices delete

    foreach var [info globals g_*] {
        uplevel #0 unset $var
    }
}

proc ExitProg {} {
    exit
}

frame .fr
pack .fr -expand 1 -fill both
togl .fr.toglwin -width $g_WinWidth -height $g_WinHeight \
                 -double true -depth true \
                 -createcommand CreateCallback \
                 -reshapecommand ReshapeCallback \
                 -displaycommand DisplayCallback 
listbox .fr.usage -font $::g_listFont -height 4
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

set appTitle "Tcl3D demo: CodeSampler's Color Tracking And Two-Sided Lighting"
wm title . $appTitle

# Watch For ESC Key And Quit Messages
wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"
bind . <Key-c>      "ToggleColorTracking"
bind . <Key-l>      "ToggleTwoSidedLighting"

bind .fr.toglwin <1>         "SetMouseInput 1 %x %y"
bind .fr.toglwin <B1-Motion> "GetMouseInput 1 %x %y"

.fr.usage insert end "Key-Escape Exit"
.fr.usage insert end "Key-c      Toggle color tracking"
.fr.usage insert end "Key-l      Toggle two sided lighting"
.fr.usage insert end "Mouse-L    Rotate quad"
.fr.usage configure -state disabled

PrintInfo [tcl3dOglGetInfoString]
