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