[HOME][CONTENTS][DOWNLOAD][PREV][NEXT]


3D IMAGE RENDERING

Digital Elevation Model.  The following are two DEM objects: the left one was rendered with a GLW widget and the right one was rendered with a glw image displayed with a canvas widget.  The attached source code highlights a architecture of a molular GUI implementation for scriptive OpenGL rendering with both a GLW widget and a glw image.  It shows how to use a Tcl command, glload, to load an image file into a data array.  It also shows how to use Tcl commands glexSolidDem and glexWireDem to render a DEM data set in solid and wire modes respectively.

 

A DEM of Grand Canyon rendered 
with a GLW widget
A DEM of Grand Canyon rendered
with a glw image displayed
in a canvas widget
#
# Copyright(c) 1999-2001, Chengye Mao, email: chengye.geo@yahoo.com
#
# dem - To render a 3D digital elevation model (DEM) with a GLW 
#     widget/image.  The DEM data set is a subset of Grand 
#     Canyon DEM downloaded from USGS's website http://www.usgs.gov.
#     Key bindings are provided as a simple API for object rotation, 
#     translation, zoom in/out of the 3D rendering. 
#
#     Two entries, dem.create and demcanv.create are provided to 
#     render the DEM view in a GLW widget or a canvas-hosted glw 
#     image respectively. 
#
#     The commands glexSolidDem and glexWireDem are used to calculate
#     normals and colors for each vertex in triangle fans defined by 
#     3x3 vertex subarrays tiled over the DEM grid. These two commands 
#     are Tcl commands written in "C". A vertex's normal is defined 
#     at a plane calculated from four neighboring vertices including 
#     itself with a least-square-error method. Colors are interpolated 
#     with blue for the minimum elevation, green for the middle and red
#     for the maximum.
#
#     Many script lines look like "C" codes required for the same 
#     rendering except for the key bindings which change the 
#     parameters of the rendering states and are much simpler in
#     Tcl scripts. 

#     Variables of a GLW widget/image can be created with 
#     commands such as GLint, GLfloat, GLdouble and etc.  Variables
#     created by these commands have a scope defined by the command
#     glw current pathname, which returns the pathname of the 
#     previous GLW widget/image. The created variables are valid 
#     during the lifetime of a GLW widget/image unless they are 
#     deleted with the command gldel. Variables associated with a GLW
#     widget/image are freed when the GLW widget/image is 
#     destroyed or deleted.
#
#     A command glload is used to read a DEM image into a variable 
#     dem. glload can be used to read a binary data stream of 
#     specified data type into a GLW variable. When an image file 
#     of black/white or RGB is loaded with glload, the image's rows
#     and columns need to be specified separately.
#
#     See the glw document for details of glw commands.
#
# Key bindings:
#     w     display the view in a wire grid
#     W     display the view in a solid surface
#     s     z scale decreases
#     S     z scale increases
#     z     zoom out the view
#     Z     zoom in the view
#     f     field of view decreases
#     F     field of view increases
#     left  view moves left
#     right view moves right
#     up    view moves up
#     down  view moves down
#     Q/q   stop or start rotation
#
# Binding of mouse motion with the left-button pressed:
#      view rotation
#
# Procedures:
#     dem.init     - initialize
#     dem.gen      - generate DEM in a OpenGL list
#     dem.reshape  - configurate a GLW widget
#     dem.display  - display a GLW widget
#     dem.button   - handle mouse button
#     dem.motion   - handle mouse motion 
#     dem.keypress - handle key press
#     dem.rotate   - rotate the DEM object by z
#     dem.create   - entry to ceatte the rendering widget
#
# Procedures to wrap a GLW image into a hosting widget:
#     dem.img          - create a GLW image for rendering
#     dem.img.config   - configurate a GLW image with its hosting widget
#     dem.img.keypress - handle key press in a hosting widget
#     dem.img.bind     - binding a hosting widget to dem proccedures
#     demcanv.create   - entry to create a canvas-hosted GLW image
#

proc dem.init w {
    global gimgHome
    set old [glw current $w]
    glClearColor 0 0 0 0
    glload dem [file join $gimgHome tm canyon.lan] GL_UNSIGNED_BYTE 128

    GLint rows 160
    GLint cols 184
    GLfloat ambient_light {0.2 0.2 0.2 1.0}
    GLfloat source_light {0.8 0.8 0.8 1.0}
    GLfloat light_pos {1 1 0 1}
    GLfloat anglex -70
    GLfloat anglez 193
    GLfloat fov 32
    GLfloat x0 0
    GLfloat y0 -0.1
    GLfloat z0 -1.4
    GLfloat sz 0.2

    GLint moving 
    GLint startx
    GLint starty
    GLuint listid

    # Set up simple lighting model
    glEnable GL_LIGHTING
    glLightModel GL_LIGHT_MODEL_AMBIENT ambient_light
    glLight GL_LIGHT0 GL_DIFFUSE source_light
    glLight GL_LIGHT0 GL_POSITION light_pos
    glEnable GL_LIGHT0
    glEnable GL_AUTO_NORMAL
    glEnable GL_NORMALIZE

    # Enable material properties for lighting
    glEnable GL_COLOR_MATERIAL
    glColorMaterial GL_FRONT GL_AMBIENT_AND_DIFFUSE
    glset listid [glGenLists 1]

    dem.gen $w

    glw current $old
}

proc dem.gen {w {mode Solid}} {
    _busyIcon
    set old [glw current $w]
    glNewList listid GL_COMPILE
    glex${mode}Dem cols rows 1 1 sz dem
    glEndList
    glw current $old
}

proc dem.reshape {w width height} {
    set old [glw current $w]
    glViewport 0 0 $width $height
    glMatrixMode GL_PROJECTION
    glLoadIdentity
    gluPerspective fov [expr $width/double($height)] 0.1 20.0 
    glw current $old
}

proc dem.display w {
    set old [glw current $w]
    glClear GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT

    glColor 1 1 1
    glMatrixMode GL_MODELVIEW
    glLoadIdentity

    glEnable GL_DEPTH_TEST
    glDepthFunc GL_LEQUAL

    glPushMatrix
    glTranslate x0 y0 z0
    glRotate anglex 1 0 0
    glRotate anglez 0 0 1
    glRotate 0 0 1 0
    glCallList listid
    glPopMatrix

    glw draw
    glw current $old
}

proc dem.button {w state x y} {
    set old [glw current $w]
    if {$state == "BUTTON_DOWN"} {
        glset moving 1
        glset startx $x
        glset starty $y
    }

    if {$state == "BUTTON_UP"} {
        glset moving 0
    }
    glw current $old
}

proc dem.motion {w x y} {
    set old [glw current $w]
    if {[glv moving]} {
        glset anglez [expr [glv anglez] + 0.5 * ($x - [glv startx])]
        glset anglex [expr [glv anglex] + 0.5 * ($y - [glv starty])]
        glset startx $x
        glset starty $y
        dem.display $w
    }
    glw current $old
}

proc dem.keypress {w keycode keysym} {
    set old [glw current $w] 

    switch $keysym {
        "Escape" { glw delete $w }
        "w"      { dem.gen $w Wire  }
        "W"      { dem.gen $w Solid }
        "s"      { glset sz [expr [glv sz]-0.05]; dem.gen $w }
        "S"      { glset sz [expr [glv sz]+0.05]; dem.gen $w }
        "z"      { glset z0 [expr [glv z0]-0.01] }
        "Z"      { glset z0 [expr [glv z0]+0.01] }
        "Left"   { glset x0 [expr [glv x0]-0.01] }
        "Right"  { glset x0 [expr [glv x0]+0.01] }
        "Up"     { glset y0 [expr [glv y0]+0.01] }
        "Down"   { glset y0 [expr [glv y0]-0.01] }
        "f"      { glset fov [expr [glv fov]-1.0] \
                   dem.reshape $w [$w cget -width] [$w cget -height]}
        "F"      { glset fov [expr [glv fov]+1.0] \
                   dem.reshape $w [$w cget -width] [$w cget -height]} 
        "q"      -
        "Q"      { dem.queue $w }
    }
    catch {
        dem.display $w
        glw current $old
        update
    }
}

proc dem.rotate w {
    set old [glw current $w]
    glset anglez [expr [glv anglez] + 2.0]
    if { [glv anglez] == 360.0 } {
        glset anglez 0
    }
    dem.display $w
    glw current $old
}

proc dem.queue w {
    set old [glw current $w]
    if [catch "glv qid"] {
         GLint qid [queue.put "dem.rotate $w"]
    } else {
         queue.remove [glv qid]
         gldel qid
    }
    glw current $old 
}

proc dem.create w {
    glw $w -width 340 -height 196
    dem.init $w

    bind $w <Expose>        "dem.display $w"
    bind $w <Configure>     "dem.reshape $w %w %h"
    bind $w <ButtonPress>   "dem.button $w BUTTON_DOWN %x %y"
    bind $w <ButtonRelease> "dem.button $w BUTTON_UP %x %y"
    bind $w <Motion>        "dem.motion $w %x %y"
    bind $w <KeyPress>      "dem.keypress $w %k %K"
    focus $w

    dem.display $w
    return $w
}

proc dem.img img {
    image create glw $img -width 180 -height 90
    dem.init $img

    dem.reshape $img 180 90
    dem.display $img
    return $img
}

proc dem.img.config {w img wwd wht} {
    set bd [$w cget -bd]
    set width [expr $wwd - 2 * $bd]
    set height [expr $wht - 2 * $bd]
    $img config -width $width -height $height
    dem.reshape $img $width $height
    dem.display $img
}

proc dem.img.keypress {w img k K} {
    switch $K {
        "Escape" {destroy $w}
        default {dem.keypress $img $k $K}
    }
}

proc dem.img.bind {w img} {
    bind $w <Configure>     "dem.img.config $w $img %w %h"
    bind $w <ButtonPress>   "dem.button $img BUTTON_DOWN %x %y"
    bind $w <ButtonRelease> "dem.button $img BUTTON_UP %x %y"
    bind $w <Motion>        "dem.motion $img %x %y"
    bind $w <KeyPress>      "dem.img.keypress $w $img %k %K"
    focus $w
}

proc demcanv.create canv {
    canvas $canv -width 320 -height 196
    $canv create image 0 0 -image [dem.img dem$canv] -anchor nw
    $canv create text 10 10 -fill red -anchor nw \
        -text "3D View of Grand Canyon with OpenGL"
    dem.img.bind $canv dem$canv
    bind $canv <Destroy> "catch {image delete dem$canv}"
    return $canv

[HOME][CONTENTS][DOWNLOAD]