#-----------------------------------------------------------------------------
# Name: ogl_lighting.cpp
# Author: Kevin Harris (kevin@codesampler.com)
# Last Modified: 02/01/05
# Description: This sample demonstrates the three basic types of lights
# that are available in OpenGL: directional, spot, and point.
#
# Control Keys: l - Changes the light's type
# w - Toggles wire frame mode
#-----------------------------------------------------------------------------
#
# Original C++ code by Kevin Harris (kevin@codesampler.com)
# See www.codesampler.com for the original files
# OpenGL samples page 5: Lighting
# http://www.codesampler.com/oglsrc/oglsrc_5.htm#ogl_lighting
#
# Modified for Tcl3D by Paul Obermeier 2008/05/01
# See www.tcl3d.org for the Tcl3D extension.
package require Tk
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_StopWatch [tcl3dNewSwatch]
set LIGHT_TYPE_DIRECTIONAL 0
set LIGHT_TYPE_SPOT 1
set LIGHT_TYPE_POINT 2
set g_lightType $LIGHT_TYPE_DIRECTIONAL
set g_bRenderInWireFrame false
set g_bAnimStarted false
# Mesh properties...
set g_nNumVertsAlongX 32
set g_nNumVertsAlongZ 32
set g_fMeshLengthAlongX 10.0
set g_fMeshLengthAlongZ 10.0
# GL_C4F_N3F_V3F
set R 0
set G 1
set B 2
set A 3
set NX 4
set NY 5
set NZ 6
set X 7
set Y 8
set Z 9
set SIZE 10
set g_nMeshVertCount [expr {($g_nNumVertsAlongX-1) * \
($g_nNumVertsAlongZ-1) * 6}]
# 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 ChangeLightType { lightType } {
set ::g_lightType $lightType
.fr.toglwin postredisplay
}
proc ToggleWireframe {} {
set ::g_bRenderInWireFrame [expr ! $::g_bRenderInWireFrame]
if { $::g_bRenderInWireFrame } {
glPolygonMode GL_FRONT GL_LINE
} else {
glPolygonMode GL_FRONT GL_FILL
}
.fr.toglwin postredisplay
}
proc InitLights {} {
set mat_ambient { 1.0 1.0 1.0 1.0 }
set mat_diffuse { 1.0 1.0 1.0 1.0 }
glMaterialfv GL_FRONT GL_DIFFUSE $mat_diffuse
glMaterialfv GL_FRONT GL_AMBIENT $mat_ambient
# Set light 0 to be a simple, bright directional light to use
# on the mesh that will represent light 2
set diffuse_light0 { 1.0 1.0 1.0 1.0 }
set position_light0 { 0.5 -0.5 -0.5 0.0 }
glLightfv GL_LIGHT0 GL_DIFFUSE $diffuse_light0
glLightfv GL_LIGHT0 GL_POSITION $position_light0
# Set light 1 to be a simple, faint grey directional light so
# the walls and floor are slightly different shades of grey
set diffuse_light1 { 0.25 0.25 0.25 1.0 }
set position_light1 { 0.3 -0.5 0.2 0.0 }
glLightfv GL_LIGHT1 GL_DIFFUSE $diffuse_light1
glLightfv GL_LIGHT1 GL_POSITION $position_light1
# Light #2 will be the demo light used to light the floor and walls.
# It will be set up in DisplayCallback since its type can be changed at
# run-time.
# Enable some dim, grey ambient lighting so objects that are not lit
# by the other lights are not completely black.
set ambient_lightModel { 0.25 0.25 0.25 1.0 }
glLightModelfv GL_LIGHT_MODEL_AMBIENT $ambient_lightModel
}
proc CreateMesh {} {
# Compute position deltas for moving down the X, and Z axis during mesh
# creation
set dX [expr { (1.0 / ($::g_nNumVertsAlongX-1))}]
set dZ [expr {-(1.0 / ($::g_nNumVertsAlongZ-1))}]
# These are all the same...
# R G B A NX NY NZ X Y Z
set constList {1.0 1.0 1.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0}
for {set i 0 } { $i < $::g_nMeshVertCount } { incr i } {
lappend meshList $constList
}
# Make a flat list.
set meshList [join $meshList]
# Create all the vertex points required by the mesh...
# Note: Mesh tesselation occurs in the X,Z plane.
# For each row of our mesh...
set i 0
for {set z 0 } { $z < [expr {$::g_nNumVertsAlongZ-1}] } { incr z } {
# Fill the row with quads which are composed of two triangles each...
for {set x 0 } { $x < [expr {$::g_nNumVertsAlongX-1}] } { incr x } {
# First triangle of the current quad
# ___ 2
# | /|
# |/__|
# 0 1
# 0
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * $x * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * $z * $dZ}]
incr i
# 1
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * ($x+1.0) * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * $z * $dZ}]
incr i
# 2
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * ($x+1.0) * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * ($z+1.0) * $dZ}]
incr i
# Second triangle of the current quad
# 2 ___ 1
# | /|
# |/__|
# 0
# 0
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * $x * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * $z * $dZ}]
incr i
# 1
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * ($x+1.0) * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * ($z+1.0) * $dZ}]
incr i
# 2
set off [expr {$i*$::SIZE}]
lset meshList [expr {$off + $::X}] \
[expr {$::g_fMeshLengthAlongX * $x * $dX}]
lset meshList [expr {$off + $::Z}] \
[expr {$::g_fMeshLengthAlongZ * ($z+1.0) * $dZ}]
incr i
}
}
set ::g_meshVertices [tcl3dVectorFromList GLfloat $meshList]
}
proc CreateCallback { toglwin } {
glClearColor 0.0 0.0 0.0 1.0
glEnable GL_LIGHTING
glEnable GL_TEXTURE_2D
glEnable GL_DEPTH_TEST
glMatrixMode GL_PROJECTION
glLoadIdentity
gluPerspective 45.0 [expr double($::g_WinWidth)/double($::g_WinHeight)] \
0.1 100.0
InitLights
CreateMesh
}
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
glMatrixMode GL_MODELVIEW
}
proc DisplayCallback { toglwin } {
# Create light 2 at run-time based on the light type the user has
# selected.
if { ! [info exists ::fStartTime] } {
set ::fStartTime [tcl3dLookupSwatch $::g_StopWatch]
}
set fElapsedTime [expr {[tcl3dLookupSwatch $::g_StopWatch] - $::fStartTime}]
set x [expr { sin ($fElapsedTime * 2.000 )}]
set y [expr { sin ($fElapsedTime * 2.246 )}]
set z [expr {-(sin ($fElapsedTime * 2.640 ))}]
# Since we want to reuse GL_LIGHT2 to demonstrate the different types of
# lights at run-time, we'll push the original or default lighting
# attributes of the OpenGL state machine before we start modifying
# anything. This way, we can pop and restore the default setup before we
# setup a new light type.
glPushAttrib GL_LIGHTING_BIT
# While both Direct3D and OpenGL use the same formula for lighting
# attenuation, they call the variables by different names when setting
# them through the API. The following two formulas are the same and
# only differ by the API names used for each variable.
#
# Direct3D:
#
# attenuation = 1 / ( Attenuation0 +
# Attenuation1 * d +
# Attenuation2 * d2 )
#
# OpenGL:
#
# attenuation = 1 / ( GL_CONSTANT_ATTENUATION +
# GL_LINEAR_ATTENUATION * d +
# GL_QUADRATIC_ATTENUATION * d2 )
#
# Where: d = Distance from vertex position to light position
# d2 = d squared
# You should note that GL_POSITION is used for both spot lights and
# directional lights in OpenGL.
#
# If the w component of the position is 0.0, the light is treated
# as a directional source and x, y, and z represent a direction vector.
#
# If the w component of the position is 1.0, the light is treated
# as a positionable light source and x, y, and z represent the lights
# position in eye coordinates as the light's position will be transformed
# by the modelview matrix when glLight is called.
if { $::g_lightType == $::LIGHT_TYPE_DIRECTIONAL } {
set diffuse_light2 [list 1.0 1.0 1.0 1.0]
set position_light2 [list $x $y $z 0.0]
glLightfv GL_LIGHT2 GL_DIFFUSE $diffuse_light2
glLightfv GL_LIGHT2 GL_POSITION $position_light2
} elseif { $::g_lightType == $::LIGHT_TYPE_SPOT } {
set diffuse_light2 [list 1.0 1.0 1.0 1.0]
set position_light2 [list [expr {2.0*$x}] [expr {2.0*$y}] [expr {2.0*$z}] 1.0]
set spotDirection_light2 [list $x $y $z]
glLightfv GL_LIGHT2 GL_DIFFUSE $diffuse_light2
glLightfv GL_LIGHT2 GL_POSITION $position_light2
glLightfv GL_LIGHT2 GL_SPOT_DIRECTION $spotDirection_light2
glLightf GL_LIGHT2 GL_CONSTANT_ATTENUATION 1.0
glLightf GL_LIGHT2 GL_SPOT_CUTOFF 45.0
glLightf GL_LIGHT2 GL_SPOT_EXPONENT 25.0
} elseif { $::g_lightType == $::LIGHT_TYPE_POINT } {
set diffuse_light2 [list 1.0 1.0 1.0 1.0]
set position_light2 [list [expr {4.5*$x}] [expr {4.5*$y}] [expr {4.5*$z}] 1.0]
glLightfv GL_LIGHT2 GL_DIFFUSE $diffuse_light2
glLightfv GL_LIGHT2 GL_POSITION $position_light2
glLightf GL_LIGHT2 GL_LINEAR_ATTENUATION 0.4
}
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
# Camera position, Look-at point, Up vector
gluLookAt -12.0 12.0 12.0 \
0.0 -1.2 0.0 \
0.0 1.0 0.0
# The first thing we draw is our walls. The walls are to be lit by
# lights 1 and 2 only, so we need to turn on lights 1 and 2, and turn off
# light 0. Light 0 will be used later for the 3D primitives.
glDisable GL_LIGHT0
glEnable GL_LIGHT1
glEnable GL_LIGHT2
# Draw the floor
glPushMatrix
glTranslatef -5.0 -5.0 5.0
glInterleavedArrays GL_C4F_N3F_V3F 0 $::g_meshVertices
glDrawArrays GL_TRIANGLES 0 $::g_nMeshVertCount
glPopMatrix
# Draw the back wall
glPushMatrix
glTranslatef 5.0 -5.0 5.0
glRotatef 90.0 0.0 0.0 1.0
glInterleavedArrays GL_C4F_N3F_V3F 0 $::g_meshVertices
glDrawArrays GL_TRIANGLES 0 $::g_nMeshVertCount
glPopMatrix
# Draw the side wall
glPushMatrix
glTranslatef -5.0 -5.0 -5.0
glRotatef 90.0 1.0 0.0 0.0
glInterleavedArrays GL_C4F_N3F_V3F 0 $::g_meshVertices
glDrawArrays GL_TRIANGLES 0 $::g_nMeshVertCount
glPopMatrix
# We're finshed drawing the walls, we'll now draw a simple
# 3D primitive to represent the light's type. We'll use a little cone
# for a directional or spot light and a little sphere for a point light.
# Light 0 is just for our primitives, so turn on light 0, and
# turn off lights 1 and 2 before rendering.
glEnable GL_LIGHT0
glDisable GL_LIGHT1
glDisable GL_LIGHT2
# Draw the correct 3d primitve representing the current light type...
if { $::g_lightType == $::LIGHT_TYPE_DIRECTIONAL } {
glPushMatrix
set position_light2 [tcl3dVector GLfloat 4]
glGetLightfv GL_LIGHT2 GL_POSITION $position_light2
# Light's position (add a 0.25f offset to center our light's cone)
gluLookAt 0.0 0.0 0.25 \
[$position_light2 get 0] \
[$position_light2 get 1] \
[$position_light2 get 2] \
0.0 1.0 0.0
glutSolidCone 0.2 0.6 15 15
glPopMatrix
$position_light2 delete
} elseif { $::g_lightType == $::LIGHT_TYPE_SPOT } {
glPushMatrix
glLoadIdentity
set position_light2 [tcl3dVector GLfloat 4]
set spotDirection_light2 [tcl3dVector GLfloat 4]
glGetLightfv GL_LIGHT2 GL_POSITION $position_light2
glGetLightfv GL_LIGHT2 GL_SPOT_DIRECTION $spotDirection_light2
gluLookAt [$position_light2 get 0] \
[$position_light2 get 1] \
[$position_light2 get 2] \
[expr {[$position_light2 get 0] + [$spotDirection_light2 get 0]}] \
[expr {[$position_light2 get 1] + [$spotDirection_light2 get 1]}] \
[expr {[$position_light2 get 2] + [$spotDirection_light2 get 2]}] \
0.0 1.0 0.0
set modelViewMat [tcl3dVector GLfloat 16]
set invModelViewMat [tcl3dVector GLfloat 16]
glGetFloatv GL_MODELVIEW_MATRIX $modelViewMat
tcl3dMatfInvert $modelViewMat $invModelViewMat
set invModelViewMatAsList [tcl3dVectorToList $invModelViewMat 16]
glLoadMatrixf $invModelViewMatAsList
glutSolidCone 0.2 0.6 15 15
glPopMatrix
$position_light2 delete
$spotDirection_light2 delete
$modelViewMat delete
$invModelViewMat delete
} elseif { $::g_lightType == $::LIGHT_TYPE_POINT } {
glPushMatrix
glLoadIdentity
set position_light2 [tcl3dVector GLfloat 4]
glGetLightfv GL_LIGHT2 GL_POSITION $position_light2
glTranslatef [$position_light2 get 0] \
[$position_light2 get 1] \
[$position_light2 get 2]
glutSolidSphere 0.25 15 15
glPopMatrix
$position_light2 delete
}
# We're finshed with our current light type. Restore the default setup
# for our next frame, which may use a different light type.
glPopAttrib
$toglwin swapbuffers
}
proc StartStopAnimation {} {
if { $::g_bAnimStarted == false } {
StartAnimation
} else {
StopAnimation
}
}
proc StartAnimation {} {
tcl3dStartSwatch $::g_StopWatch
.fr.toglwin postredisplay
set ::animId [tcl3dAfterIdle StartAnimation]
set ::g_bAnimStarted true
}
proc StopAnimation {} {
if { [info exists ::animId] } {
after cancel $::animId
unset ::animId
}
set ::g_bAnimStarted false
tcl3dStopSwatch $::g_StopWatch
}
proc Cleanup {} {
$::g_meshVertices delete
tcl3dDeleteSwatch $::g_StopWatch
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 6
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: CodeSampler's Lighting Demo"
# Watch For ESC Key And Quit Messages
wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"
bind . <Key-s> "StartStopAnimation"
bind . <Key-d> "ChangeLightType $::LIGHT_TYPE_DIRECTIONAL"
bind . <Key-o> "ChangeLightType $::LIGHT_TYPE_SPOT"
bind . <Key-p> "ChangeLightType $::LIGHT_TYPE_POINT"
bind . <Key-w> "ToggleWireframe"
.fr.usage insert end "Key-Escape Exit"
.fr.usage insert end "Key-s Start|Stop Animation"
.fr.usage insert end "Key-d Change to directional light"
.fr.usage insert end "Key-o Change to spot light"
.fr.usage insert end "Key-p Change to point light"
.fr.usage insert end "Key-w Toggle wireframe mode"
.fr.usage configure -state disabled
PrintInfo [tcl3dOglGetInfoString]
|