# Demo showing the usage of a 3D mouse for model transformations.
# Tested with a 3Dconnexion SpaceMouse Compact.
#
# This script is based on the following demo:
# Original C++ code by Song Ho Ahn (song.ahn@gmail.com)
# See www.songho.ca/opengl/gl_transform.html for the original files
#
# Copyright Paul Obermeier 2022-2024.
# See www.tcl3d.org for the Tcl3D extension.

package require Tk
package require tcl3d

if { ! [tcl3dHaveSDL] } {
    tk_messageBox -icon error -type ok -title "Missing Tcl3D module" \
                  -message "Demo needs the tcl3dSDL module."
    proc Cleanup {} {}
    exit 1
    return
}

tcl3dAddEvents

array set gState {
    DeadZone  0
    fovY      60.0
    nearPlane  1.0
    farPlane  50.0
    windowWidth  600
    windowHeight 600
    cameraAngleX   30.0
    cameraAngleY  -50.0
    cameraDistance 60.0
    view,pos,x  0.0
    view,pos,y  0.0
    view,pos,z  0.0
    view,rot,x  0.0
    view,rot,y  0.0
    view,rot,z  0.0
    model,pos,x 0.0
    model,pos,y 0.0
    model,pos,z 0.0
    model,rot,x 0.0
    model,rot,y 0.0
    model,rot,z 0.0
    off,pos,x   0.0
    off,pos,y   0.0
    off,pos,z   0.0
    off,rot,x   0.0
    off,rot,y   0.0
    off,rot,z   0.0
    AxisMapping    { "pos,x" "pos,z" "pos,y" "rot,x" "rot,z" "rot,y" }
    AxisSign       { 1.0     1.0     -1.0    1.0     1.0    -1.0 }
    ClearColorSub1 { 0.1 0.1 0.1 0.0 }
    ClearColorSub2 { 0.0 0.0 0.5 0.0 }
}

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

# Update window title message.
proc UpdateTitle { joyName } {
    set appTitle "Tcl3D Viewer using 3D-Mouse $joyName"
    wm title . $appTitle
}

# Set the camera position and rotation.
proc SetViewMatrix { x y z pitch heading roll } {
    global gState

    set gState(view,pos,x) $x
    set gState(view,pos,y) $y
    set gState(view,pos,z) $z
    set gState(view,rot,x) $pitch
    set gState(view,rot,y) $heading
    set gState(view,rot,z) $roll
}

# Set the model position and rotation.
proc SetModelMatrix { x y z rx ry rz } {
    global gState

    set gState(model,pos,x) $x
    set gState(model,pos,y) $y
    set gState(model,pos,z) $z
    set gState(model,rot,x) $rx
    set gState(model,rot,y) $ry
    set gState(model,rot,z) $rz
}

proc SetLeftMouse { x y } {
    global gState

    set gState(mouseX) $x
    set gState(mouseY) $y
}

proc RotateCamera { toglwin x y } {
    global gState

    set diffX [expr {$x - $gState(mouseX)}]
    set diffY [expr {$y - $gState(mouseY)}]
    set gState(cameraAngleY) [expr {$gState(cameraAngleY) + $diffX}]
    set gState(cameraAngleX) [expr {$gState(cameraAngleX) + $diffY}]
    set gState(mouseX) $x
    set gState(mouseY) $y
    $toglwin postredisplay
}

proc SetRightMouse { x y } {
    global gState

    set gState(mouseY) $y
}

proc ZoomCamera { toglwin x y } {
    global gState

    set diffY [expr {($y - $gState(mouseY)) * 0.05}]
    set gState(cameraDistance) [expr {$gState(cameraDistance) + $diffY}]
    set gState(mouseY) $y
    $toglwin postredisplay
}

# Configure projection and viewport of sub window
proc SetViewportSub { x y w h nearPlane farPlane } {
    global gState

    glViewport $x $y $w $h
    glScissor  $x $y $w $h

    glMatrixMode GL_PROJECTION
    glLoadIdentity
    # FOV, AspectRatio, NearClip, FarClip
    gluPerspective $gState(fovY) [expr {double($w)/double($h)}] \
                   $nearPlane $farPlane

    glMatrixMode GL_MODELVIEW
    glLoadIdentity
}

proc DrawCamera {} {
    set shininess 32.0
    set diffuseColor  {1.0 1.0 1.0}
    set specularColor {1.0 1.0 1.0 1.0}

    glMaterialf  GL_FRONT_AND_BACK GL_SHININESS $shininess
    glMaterialfv GL_FRONT_AND_BACK GL_SPECULAR $specularColor

    glColorMaterial GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE
    glColor3fv $diffuseColor

    tcl3dCameraModel
}

proc DrawTeapot {} {
    set shininess 15.0
    set diffuseColor  { 0.929524 0.796542 0.178823 }
    set specularColor { 1.00000  0.980392 0.549020 1.0 }

    glMaterialf  GL_FRONT_AND_BACK GL_SHININESS $shininess
    glMaterialfv GL_FRONT_AND_BACK GL_SPECULAR  $specularColor

    glColorMaterial GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE
    glColor3fv $diffuseColor

    tcl3dTeapotModel
}

# Draw a grid on the xz plane.
proc DrawGrid { size step } {
    glDisable GL_LIGHTING

    glBegin GL_LINES
        glColor3f 0.3 0.3 0.3
        for { set i $step } { $i <= $size } { set i [expr {$i + $step}] } {
            glVertex3f -$size 0  $i ;   # Lines parallel to X-axis
            glVertex3f  $size 0  $i
            glVertex3f -$size 0 -$i ;   # Lines parallel to X-axis
            glVertex3f  $size 0 -$i

            glVertex3f  $i 0 -$size ;   # Lines parallel to Z-axis
            glVertex3f  $i 0  $size
            glVertex3f -$i 0 -$size ;   # Lines parallel to Z-axis
            glVertex3f -$i 0  $size
        }

        # x-axis
        glColor3f 0.5 0 0
        glVertex3f -$size 0 0
        glVertex3f  $size 0 0

        # z-axis
        glColor3f 0 0 0.5
        glVertex3f 0 0 -$size
        glVertex3f 0 0  $size
    glEnd

    glEnable GL_LIGHTING
}

# Draw the local axis of a model.
proc DrawAxis { size } {
    glDepthFunc GL_ALWAYS
    glDisable   GL_LIGHTING

    # Draw axis
    glLineWidth 3
    glBegin GL_LINES
        glColor3f 1 0 0
        glVertex3f 0 0 0
        glVertex3f $size 0 0
        glColor3f 0 1 0
        glVertex3f 0 0 0
        glVertex3f 0 $size 0
        glColor3f 0 0 1
        glVertex3f 0 0 0
        glVertex3f 0 0 $size
    glEnd
    glLineWidth 1

    # Draw arrows (actually big square dots)
    glPointSize 5
    glBegin GL_POINTS
        glColor3f  1 0 0
        glVertex3f $size 0 0
        glColor3f  0 1 0
        glVertex3f 0 $size 0
        glColor3f  0 0 1
        glVertex3f 0 0 $size
    glEnd
    glPointSize 1

    # Restore default settings
    glEnable    GL_LIGHTING
    glDepthFunc GL_LEQUAL
}

proc DrawFrustum { fovY aspectRatio nearPlane farPlane } {
    set tangent    [expr {tan ([tcl3dDegToRad [expr {$fovY/2}]])}]
    set nearHeight [expr {$nearPlane * $tangent}]
    set nearWidth  [expr {$nearHeight * $aspectRatio}]
    set farHeight  [expr {$farPlane * $tangent}]
    set farWidth   [expr {$farHeight * $aspectRatio}]

    # compute 8 vertices of the frustum

    # near top right
    set vertices(0) [list  $nearWidth  $nearHeight -$nearPlane]
    # near top left
    set vertices(1) [list -$nearWidth  $nearHeight -$nearPlane]
    # near bottom left
    set vertices(2) [list -$nearWidth -$nearHeight -$nearPlane]
    # near bottom right
    set vertices(3) [list  $nearWidth -$nearHeight -$nearPlane]
    # far top right
    set vertices(4) [list  $farWidth   $farHeight  -$farPlane]
    # far top left
    set vertices(5) [list -$farWidth   $farHeight  -$farPlane]
    # far bottom left
    set vertices(6) [list -$farWidth  -$farHeight  -$farPlane]
    # far bottom right
    set vertices(7) [list  $farWidth  -$farHeight  -$farPlane]

    set colorLine1 { 0.7 0.7 0.7 0.7 }
    set colorLine2 { 0.2 0.2 0.2 0.7 }
    set colorPlane { 0.5 0.5 0.5 0.5 }

    glDisable GL_LIGHTING
    glDisable GL_CULL_FACE
    glBlendFunc GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA

    glBegin GL_LINES
        glColor4fv $colorLine2
        glVertex3f 0 0 0
        glColor4fv $colorLine1
        glVertex3fv $vertices(4)

        glColor4fv $colorLine2
        glVertex3f 0 0 0
        glColor4fv $colorLine1
        glVertex3fv $vertices(5)

        glColor4fv $colorLine2
        glVertex3f 0 0 0
        glColor4fv $colorLine1
        glVertex3fv $vertices(6)

        glColor4fv $colorLine2
        glVertex3f 0 0 0
        glColor4fv $colorLine1
        glVertex3fv $vertices(7)
    glEnd

    glColor4fv $colorLine1
    glBegin GL_LINE_LOOP
        glVertex3fv $vertices(4)
        glVertex3fv $vertices(5)
        glVertex3fv $vertices(6)
        glVertex3fv $vertices(7)
    glEnd

    glColor4fv $colorLine1
    glBegin GL_LINE_LOOP
        glVertex3fv $vertices(0)
        glVertex3fv $vertices(1)
        glVertex3fv $vertices(2)
        glVertex3fv $vertices(3)
    glEnd

    glColor4fv $colorPlane
    glBegin GL_QUADS
        glVertex3fv $vertices(0)
        glVertex3fv $vertices(1)
        glVertex3fv $vertices(2)
        glVertex3fv $vertices(3)
        glVertex3fv $vertices(4)
        glVertex3fv $vertices(5)
        glVertex3fv $vertices(6)
        glVertex3fv $vertices(7)
    glEnd

    glEnable GL_CULL_FACE
    glEnable GL_LIGHTING
}

# Draw upper window (view from the camera)
proc DrawSub1 {} {
    global gState

    SetViewportSub 0 [expr {$gState(windowHeight)/2}] $gState(windowWidth) \
                     [expr {$gState(windowHeight)/2}] $gState(nearPlane) $gState(farPlane)

    glClearColor [lindex $gState(ClearColorSub1) 0] [lindex $gState(ClearColorSub1) 1] \
                 [lindex $gState(ClearColorSub1) 2] [lindex $gState(ClearColorSub1) 3]
    glClear [expr $::GL_COLOR_BUFFER_BIT | \
                  $::GL_DEPTH_BUFFER_BIT | \
                  $::GL_STENCIL_BUFFER_BIT]

    glPushMatrix
    glLoadIdentity

    # ModelView matrix is product of viewing matrix and modeling matrix
    # ModelView_M = View_M * Model_M
    # First, transform the camera (viewing matrix) from world space to eye space
    # Notice all values are negated, because we move the whole scene with the
    # inverse of camera transform
    glRotatef [expr {-1.0 * $gState(view,rot,x)}] 1 0 0
    glRotatef [expr {-1.0 * $gState(view,rot,y)}] 0 1 0
    glRotatef [expr {-1.0 * $gState(view,rot,z)}] 0 0 1
    glTranslatef [expr {-1.0 * $gState(view,pos,x)}] \
                 [expr {-1.0 * $gState(view,pos,y)}] \
                 [expr {-1.0 * $gState(view,pos,z)}]

    # we have set viewing matrix up to this point. 
    # (Matrix from world space to eye space)
    # save the view matrix only
    set gState(matrixView) [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]

    # always draw the grid at the origin (before any modeling transform)
    DrawGrid 10 1

    # In order to get the modeling matrix only, reset GL_MODELVIEW matrix
    glLoadIdentity

    # transform the object
    # From now, all transform will be for modeling matrix only.
    # (transform from object space to world space)
    glTranslatef $gState(model,pos,x) $gState(model,pos,y) $gState(model,pos,z)
    glRotatef $gState(model,rot,x) 1 0 0
    glRotatef $gState(model,rot,y) 0 1 0
    glRotatef $gState(model,rot,z) 0 0 1

    # save modeling matrix
    set gState(matrixModel) [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]

    # restore GL_MODELVIEW matrix by multiplying matrixView and matrixModel
    # before drawing the object
    # ModelView_M = View_M * Model_M
    glLoadMatrixf $gState(matrixView)  ; # Mmv = Mv
    glMultMatrixf $gState(matrixModel) ; # Mmv *= Mm

    # save ModelView matrix
    set gState(matrixModelView) [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]

    # draw a teapot after ModelView transform
    # v' = Mmv * v
    DrawAxis 4
    DrawTeapot

    glPopMatrix
}

# Draw bottom window (3rd person view)
proc DrawSub2 {} {
    global gState

    # set bottom viewport
    SetViewportSub 0 0 $gState(windowWidth) [expr {$gState(windowHeight)/2}] \
                   $gState(nearPlane) [expr { $gState(farPlane) + 100 }]

    # clear buffer
    glClearColor [lindex $gState(ClearColorSub2) 0] [lindex $gState(ClearColorSub2) 1] \
                 [lindex $gState(ClearColorSub2) 2] [lindex $gState(ClearColorSub2) 3]
    glClear [expr $::GL_COLOR_BUFFER_BIT | \
                  $::GL_DEPTH_BUFFER_BIT | \
                  $::GL_STENCIL_BUFFER_BIT]

    glPushMatrix

    # First, transform the camera (viewing matrix) from world space to eye space
    glTranslatef 0 0 [expr {-1.0 * $gState(cameraDistance)}]
    glRotatef $gState(cameraAngleX) 1 0 0 ; # pitch
    glRotatef $gState(cameraAngleY) 0 1 0 ; # heading

    # draw grid
    DrawGrid 10 1

    # draw a teapot
    glPushMatrix
    glTranslatef $gState(model,pos,x) $gState(model,pos,y) $gState(model,pos,z)
    glRotatef $gState(model,rot,x) 1 0 0
    glRotatef $gState(model,rot,y) 0 1 0
    glRotatef $gState(model,rot,z) 0 0 1
    DrawAxis 4
    DrawTeapot
    glPopMatrix

    # draw the camera
    glPushMatrix
    glTranslatef $gState(view,pos,x) $gState(view,pos,y) $gState(view,pos,z)
    glRotatef $gState(view,rot,x) 1 0 0
    glRotatef $gState(view,rot,y) 0 1 0
    glRotatef $gState(view,rot,z) 0 0 1
    DrawCamera
    DrawFrustum $gState(fovY) 1 $gState(nearPlane) $gState(farPlane)
    glPopMatrix

    glPopMatrix
}

proc InitLights {} {
    # set up light colors (ambient, diffuse, specular)
    set lightKa {0.0 0.0 0.0 1.0} ; # ambient light
    set lightKd {0.9 0.9 0.9 1.0} ; # diffuse light
    set lightKs {1 1 1 1}         ; # specular light
    glLightfv GL_LIGHT0 GL_AMBIENT  $lightKa
    glLightfv GL_LIGHT0 GL_DIFFUSE  $lightKd
    glLightfv GL_LIGHT0 GL_SPECULAR $lightKs

    # position the light
    set lightPos {0 5 5 0}
    glLightfv GL_LIGHT0 GL_POSITION $lightPos

    glEnable GL_LIGHT0
}

proc CreateCallback { toglwin } {
    global gState

    glShadeModel  GL_SMOOTH
    glPixelStorei GL_UNPACK_ALIGNMENT 4

    glHint   GL_PERSPECTIVE_CORRECTION_HINT GL_NICEST
    glEnable GL_DEPTH_TEST
    glEnable GL_LIGHTING
    glEnable GL_TEXTURE_2D
    glEnable GL_CULL_FACE
    glEnable GL_BLEND
    glEnable GL_SCISSOR_TEST

    glColorMaterial GL_FRONT_AND_BACK GL_AMBIENT_AND_DIFFUSE
    glEnable GL_COLOR_MATERIAL

    glClearColor 0.0 0.0 0.0 0.0
    glClearStencil 0
    glClearDepth 1.0
    glDepthFunc GL_LEQUAL

    InitLights

    SetViewMatrix 0 0 10 0 0 0
    set gState(matrixView)      [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]
    set gState(matrixModel)     [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]
    set gState(matrixModelView) [tcl3dOglGetFloatState GL_MODELVIEW_MATRIX 16]
}

proc ReshapeCallback { toglwin { w -1 } { h -1 } } {
    global gState

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

    set gState(windowWidth)  $w
    set gState(windowHeight) $h
}

proc DisplayCallback { toglwin } {
    global gState

    DrawSub1
    DrawSub2

    glPolygonMode GL_FRONT_AND_BACK GL_FILL
    glEnable GL_DEPTH_TEST
    glEnable GL_CULL_FACE

    glFinish
    $toglwin swapbuffers
}

proc ResetMatrix {} {
    global gState

    if { [info exists gState(CurJoystick)] } {
        set numAxes [SDL_JoystickNumAxes $gState(CurJoystick)]
        for { set i 0 } { $i < [Min 6 $numAxes] } { incr i } {
            set val [SDL_JoystickGetAxis $gState(CurJoystick) $i]
            set type [MapAxisToType $i]
            set gState(off,$type) $val
        }
    }
    SetModelMatrix 0 0 0 0 0 0
    Update
}

proc Update { args } {
    DisplayCallback .fr.toglwin
}

proc Cleanup {} {
    uplevel #0 unset gState
    SDL_QuitSubSystem [expr { $::SDL_INIT_VIDEO | $::SDL_INIT_JOYSTICK }]
}

proc StartAnimation {} {
    if { [SDL_NumJoysticks] > 0 } {
        UseJoystick 0
    }
}

proc ExitProg {} {
    global gState

    if { [info exists gState(AfterId)] } {
        after cancel $gState(AfterId)
    }
    exit
}

proc ConvertTranslation { joyVal } {
    return [format "%.1f" [expr { 50.0 * $joyVal / $::SDL_JOYSTICK_AXIS_MAX }]]
}

proc ConvertRotation { joyVal } {
    return [format "%.1f" [expr { 360.0 * $joyVal / $::SDL_JOYSTICK_AXIS_MAX }]]
}

proc MapTypeToAxis { type } {
    global gState

    return [lsearch -exact $gState(AxisMapping) $type]
}

proc MapAxisToType { axisNum } {
    global gState

    return [lindex $gState(AxisMapping) $axisNum]
}

proc MapAxisToSign { axisNum } {
    global gState

    return [lindex $gState(AxisSign) $axisNum]
}

proc PollJoystick { joystick numAxes } {
    global gState

    while { [SDL_PollEvent $gState(SDLEvent)] } {
        set evType [$gState(SDLEvent) cget -type]
        if { $evType == $::SDL_JOYAXISMOTION } {
            for { set i 0 } { $i < $numAxes } { incr i } {
                if { $gState(UseAxis,$i) } {
                    set val  [SDL_JoystickGetAxis $joystick $i]
                    set type [MapAxisToType $i]
                    set newVal [expr { $val - $gState(off,$type) }]
                    puts [format "Axis %d (%s): %6.0f -> %6.0f" $i $type $val $newVal]
                    if { [string first $type "pos"] >= 0 } {
                        set gState(model,[MapAxisToType $i]) [expr { [MapAxisToSign $i] * [ConvertTranslation $newVal] }]
                    } else {
                        set gState(model,[MapAxisToType $i]) [expr { [MapAxisToSign $i] * [ConvertRotation $newVal] }]
                    }
                }
            }
        } elseif { $evType == $::SDL_JOYBUTTONDOWN || $evType == $::SDL_JOYBUTTONUP } {
            set joybuttonevent [$gState(SDLEvent) cget -jbutton]
            set btnNum [$joybuttonevent cget -button]
            if { $evType == $::SDL_JOYBUTTONDOWN } {
                set ::gBtn($btnNum) 1
            } elseif { $evType == $::SDL_JOYBUTTONUP } {
                set ::gBtn($btnNum) 0
            }
        } else {
            # puts "Unhandled event type: $evType"
        }
        Update
        puts ""
    }
    set gState(AfterId) [after idle "PollJoystick $joystick [Min 6 $numAxes]"]
}

proc UseJoystick { joyNum } {
    global gState

    if { [info exists gState(AfterId)] } {
        after cancel $gState(AfterId)
    }
    if { [info exists gState(CurJoystick)] } {
        SDL_JoystickClose $gState(CurJoystick)
    }
    set joystick [SDL_JoystickOpen $joyNum]
    set numAxes  [SDL_JoystickNumAxes $joystick]
    set gState(CurJoystick) $joystick
    UpdateTitle [SDL_JoystickName $joystick]
    for { set i 0 } { $i < $numAxes } { incr i } {
        if { ! [info exists gState(UseAxis,$i)] } {
            set gState(UseAxis,$i) 1
        }
    }
    PollJoystick $joystick [Min 6 $numAxes]
}

proc ChooseJoystick { w } {
    set selList [$w curselection]
    if { [llength $selList] == 0 || [SDL_NumJoysticks] == 0 } {
        return
    }
    set joyNum [lindex $selList 0]
    UseJoystick $joyNum
}

proc ConvertDeadZone {} {
    global gState

    return [expr { int($gState(DeadZone) / 100.0 * $::SDL_JOYSTICK_AXIS_MAX) }]
}

proc Min { a b } {
    if { $a < $b } {
        return $a
    } else {
        return $b
    }
}

proc Max { a b } {
    if { $a > $b } {
        return $a
    } else {
        return $b
    }
}

if { [SDL_Init [expr { $::SDL_INIT_JOYSTICK }]] < 0 } {
    error [format "Couldn't initialize SDL: %s\n" [SDL_GetError]]
    exit 1
}

set gState(SDLEvent) [SDL_Event]

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

togl .fr.toglwin \
     -width $gState(windowWidth) -height $gState(windowHeight) \
     -double true -depth true -stencil true \
     -createcommand CreateCallback \
     -reshapecommand ReshapeCallback \
     -displaycommand DisplayCallback
frame .fr.guiFr
label .fr.info

grid .fr.toglwin -row 0 -column 0 -sticky news
grid .fr.guiFr   -row 0 -column 1 -sticky news
grid .fr.info    -row 1 -column 0 -sticky news -columnspan 2
grid rowconfigure .fr 0 -weight 1
grid columnconfigure .fr 0 -weight 1

labelframe .fr.guiFr.joyFr -foreground blue -text "Joysticks"
labelframe .fr.guiFr.tfmFr -foreground blue -text "Transformations"
grid .fr.guiFr.joyFr -row 0 -column 0 -sticky news
grid .fr.guiFr.tfmFr -row 1 -column 0 -sticky news

set joyListbox .fr.guiFr.joyFr.joys
listbox $joyListbox

set joyDead .fr.guiFr.joyFr.fr
frame $joyDead
pack {*}[winfo children .fr.guiFr.joyFr] -expand 1 -fill x

label $joyDead.l -text "DeadZone (%):"
spinbox $joyDead.s -from 0 -to 100 -width 4 -justify right -textvariable gState(DeadZone) -command ConvertDeadZone
$joyDead.s configure -state disabled
pack {*}[winfo children $joyDead] -side left

set maxLen 0
set numJoysticks [SDL_NumJoysticks]
for { set i 0 } { $i < $numJoysticks } { incr i } {
    set joystick [SDL_JoystickOpen $i]
    set numAxes  [SDL_JoystickNumAxes $joystick]
    if { $numAxes >= 6 } {
        set name [SDL_JoystickName $joystick]
        set maxLen [Max $maxLen [string length $name]]
        $joyListbox insert end $name
    }
}
$joyListbox configure -height [Max $numJoysticks 1] -width [Max 20 $maxLen]
bind $joyListbox <ButtonRelease-1> "ChooseJoystick $joyListbox"

set modelFr .fr.guiFr.tfmFr.frModel
frame $modelFr
pack $modelFr -side left -expand 1 -fill x

ttk::button $modelFr.reset -command ResetMatrix -text "Reset"
grid $modelFr.reset -row 0 -column 0 -sticky ew -columnspan 3
set row 1
set ind 0
foreach type {  "pos,x" "pos,y" "pos,z" "rot,x" "rot,y" "rot,z" } \
        msg  { "Position (X)" "Position (Y)" "Position (Z)" "Rotation (X)" "Rotation (Y)" "Rotation (Z)" } {
    set axisNum [MapTypeToAxis $type]
    ttk::checkbutton $modelFr.b_$row -variable gState(UseAxis,$axisNum) -text $msg
    ttk::label $modelFr.v_$row -textvariable gState(model,$type) -width 6 -anchor e
    grid $modelFr.b_$row -row $row -column 0 -sticky sw
    grid $modelFr.v_$row -row $row -column 1 -sticky news
    incr row
    incr ind
}
grid columnconfigure $modelFr 2 -weight 1

wm protocol . WM_DELETE_WINDOW "ExitProg"
bind . <Key-Escape> "ExitProg"

bind .fr.toglwin <<LeftMousePress>>   "SetLeftMouse %x %y"
bind .fr.toglwin <<LeftMouseMotion>>  "RotateCamera .fr.toglwin %x %y"
bind .fr.toglwin <<RightMousePress>>  "SetRightMouse %x %y"
bind .fr.toglwin <<RightMouseMotion>> "ZoomCamera .fr.toglwin %x %y"

PrintInfo [tcl3dSDLGetInfoString]

update

if { [file tail [info script]] eq [file tail $::argv0] } {
    # If started directly from tclsh or wish, then start animation.
    if { $numJoysticks > 0 } {
        $joyListbox selection set 0
        UseJoystick 0
    }
}
