# NPS07_TestSwitchDOF.tcl
#
# Original C++ code by Joseph Sullivan.
# See http://www.openscenegraph.org/projects/osg/wiki/Support/Tutorials
# for the original files.
#
# Modified for Tcl3D by Paul Obermeier 2009/03/20.
# See www.tcl3d.org for the Tcl3D extension.

package require tcl3d

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

# Font to be used in the Tk listbox.
set gDemo(listFont) {-family {Courier} -size 10}

# Window size.
set gDemo(winWidth)  640
set gDemo(winHeight) 480

# Determine the directory and filename of this script.
set gDemo(scriptFile) [info script]
set gDemo(scriptDir)  [file dirname $gDemo(scriptFile)]

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

# Print info message into widget a the bottom of the window.
proc PrintInfo { msg } {
    if { [winfo exists .fr.info] } {
        .fr.info configure -text $msg
    }
}

# Idle callback to redisplay the scene.
proc Animate {} {
    .fr.toglwin postredisplay
    set ::animateId [tcl3dAfterIdle Animate]
}

proc StartAnimation {} {
    if { ! [info exists ::animateId] } {
        Animate
    }
}

proc StopAnimation {} {
    if { [info exists ::animateId] } {
        after cancel $::animateId 
        unset ::animateId
    }
}

proc CreateCallback { toglwin } {
}

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

    # Propagate resize event to embedded OSG window.
    tcl3dOsgWindowResize $toglwin [tcl3dOsgGetOsgWin] $w $h
}

proc DisplayCallback { toglwin } {
    if { [viewer valid] } {
        viewer frame
    }
    $toglwin swapbuffers
}

proc Cleanup {} {
    uplevel #0 unset gDemo
    viewer -delete
}

proc ExitProg {} {
    exit
}

proc SaveOsgToFile {} {
    global gDemo

    set osgRoot [viewer getSceneData]
    set outFile [format "%s.osgt" [file rootname $gDemo(scriptFile)]]
    # Create a name on the file system, if running from within a Starpack.
    set outFile [tcl3dGenExtName $outFile]
    puts "Saving scenegraph to file $outFile"

    if { ! [osgDB::writeNodeFile $osgRoot $outFile] } {
        puts "Failed to write scenegraph to file $outFile"
    }
}

proc CreateWidgets { osgwin } {
    global gDemo

    frame .fr
    pack .fr -expand 1 -fill both
    set toglwin .fr.toglwin
    togl $toglwin -width $gDemo(winWidth) -height $gDemo(winHeight) \
                  -double true -depth true -alpha true \
                  -createcommand CreateCallback \
                  -reshapecommand ReshapeCallback \
                  -displaycommand DisplayCallback 
    listbox .fr.usage -font $::gDemo(listFont) -height 3
    label   .fr.info
    grid $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: Sullivan's OSG tutorial #7 (TestSwitchDOF)"

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

    # Propagate key and mouse events to embedded OSG window.
    bind . <KeyPress> "tcl3dOsgKeyPress $toglwin $osgwin %N"

    tcl3dOsgAddTrackballBindings $toglwin $osgwin

    .fr.usage insert end "Key-Escape Exit"
    .fr.usage insert end "Key-f      Save SceneGraph to file"
    .fr.usage insert end "Mouse      Trackball"

    .fr.usage configure -state disabled
}

#
# Start of tutorial specific code.
#

proc addTextLabel { g s } {
    if { $g eq "NULL" } {
        return false
    }
    osg::Geode textLabelGeode
    osgText::Text textOne
    $g addChild textLabelGeode
    textLabelGeode addDrawable textOne

    textOne setCharacterSize 1
    textOne setFont "./Data/impact.ttf"
    textOne setText $s
    textOne setAxisAlignment $::osgText::Text_SCREEN
    textOne setColor [osg::Vec4 v4 1 0 0 1]
    textOne setPosition [osg::Vec3 v3 2 1 -1]
    textOne setAlignment $::osgText::Text_CENTER_TOP
    return true
}

# Visitor callback procedure to clear the animation of DOF nodes.
proc ClearDOFNodesCB { node args } {
    set nodeType [osg::Object_className $node]
    if { $nodeType eq "DOFTransform" } {
        osgSim::DOFTransform_setAnimationOn $node false
    }
}

proc InitSearch {} {
    global gSearch

    set gSearch(foundList) [list]
}

proc GetSearchResult { { pos 0 } } {
    global gSearch

    return [lindex $gSearch(foundList) $pos]
}

# Visitor callback procedure to search for a node with a specified name.
# Note, that global variable gSearch(foundList) must be reset to an empty list
# before starting a new search via "accept".
proc SearchNodeCB { node args } {
    global gSearch

    set nodeName [osg::Object_getName $node]
    if { $nodeName eq $args } {
        lappend gSearch(foundList) $node
    }
}

osgViewer::ViewerRef viewer [osgViewer::Viewer]

# Declare a group for the root of our tree and 
# three groups to contain individual tank models
osg::Group root

# Load the models from the same file
set tankFile [file join $::gDemo(scriptDir) "Data/Models/t72-tank/t72-tank_des.flt"]
set tankOneGroup   [osg::Node_asGroup [osgDB::readNodeFile $tankFile]]
set tankTwoGroup   [osg::Node_asGroup [osgDB::readNodeFile $tankFile]]
set tankThreeGroup [osg::Node_asGroup [osgDB::readNodeFile $tankFile]]
 
# quit if we didn't successfully load the models
if { $tankOneGroup eq "" || $tankTwoGroup eq "" || $tankThreeGroup eq "" } {
    puts "Could not load tank models"
    exit 1
}

# findNodeVisitor will turn osgSim::DOFTransform nodes 
# animation field to false
osg::tcl3dOsgNodeVisitor nv1 ClearDOFNodesCB
$tankOneGroup accept nv1

# add the first tank as a child of the root node
root addChild $tankOneGroup

# declare a transform for positioning the second tank
osg::PositionAttitudeTransform tankTwoPAT

# move the second tank five units right, five units forward
tankTwoPAT setPosition [osg::Vec3d pos2 5 5 0]

# add the tank as a child of its transform to the scene
root addChild tankTwoPAT
tankTwoPAT addChild $tankTwoGroup

# declare a transform for positioning the third tank
osg::PositionAttitudeTransform tankThreePAT

# move the third tank ten units right
tankThreePAT setPosition [osg::Vec3d pos3 10 0 0]

# rotate the tank model 22.5 degrees to the left
# (to demonstrate that rotation of the turret will be 
#  relative to the tank's heading)
tankThreePAT setAttitude [osg::Quat quat3 [expr 3.14159/8.0] [osg::Vec3 rotAxis 0 0 1]]

# add the tank as a child of its transform to the scene
root addChild tankThreePAT
tankThreePAT addChild $tankThreeGroup

# Declare a new instance of a NodeVisitor class and set its
# searchForName string equal to "sw1"
osg::tcl3dOsgNodeVisitor nv2 SearchNodeCB "sw1"

# Initiate traversal of this NodeVisitor instance starting
# from tankTwoGroup, searching all its children.  Build a list
# of nodes whose names matched the searchForName string above.
InitSearch
$tankTwoGroup accept nv2

# Declare a switch type and assign it to the first node
# in our list of matching nodes.
set tankSwitch [GetSearchResult]
# make sure it's a valid handle. If it is, set the first (only) 
# multi-switch:
if { [osg::Object_className $tankSwitch] eq "MultiSwitch" } {
    osgSim::MultiSwitch_setSingleChildOn $tankSwitch 0 1 ; # bad model
}

# Declare a new instance of a NodeVisitor class; set the name to search
# for to "turret".
osg::tcl3dOsgNodeVisitor nv3 SearchNodeCB "turret"
InitSearch

# Initiate a traversal starting from the subtree that represents 
# the third tank model we loaded.
$tankThreeGroup accept nv3

# Make sure we found a node and it's the correct type 
set turretDOF [GetSearchResult]

# if it's a valid DOF node, set the heading of the turret
# to 22.5 degrees right relative to the tank's heading.
if { [osg::Object_className $turretDOF] eq "DOFTransform" } {
    osgSim::DOFTransform_setCurrentHPR $turretDOF [osg::Vec3 turret [expr -3.14159/4.0] 0.0 0.0]
}

# Declare an instance of 'findNodeVisitor'; set the name to search for to "gun"
osg::tcl3dOsgNodeVisitor nv4 SearchNodeCB "gun"
InitSearch

# Initiate a traversal from the subtree that represents the third
# tank model we loaded.
$tankThreeGroup accept nv4

# Make sure we found a node and it's the correct type
set gunDOF [GetSearchResult]

# If the node we found is a valid  DOF transform, set the pitch
# to 22.5 degrees up relative to the turret's pitch.
if { [osg::Object_className $gunDOF] eq "DOFTransform" } {
    osgSim::DOFTransform_setCurrentHPR $gunDOF [osg::Vec3 gun 0.0 [expr 3.14159/8.0] 0.0]
}

addTextLabel $tankOneGroup   "Original Tank"
addTextLabel $tankTwoGroup   "Damaged State"
addTextLabel $tankThreeGroup "Articulated"

viewer setSceneData root
        
viewer setCameraManipulator [osgGA::TrackballManipulator]

if { $argc >= 1 && [lindex $argv 0] eq "-viewer" } {
    # Only use the standard OSG viewer window without any Tk widgets.
    viewer setUpViewInWindow 50 50 500 400
    viewer run
    exit 0
}

# Use the OSG viewer inside a Togl widget.
set osgwin [viewer setUpViewerAsEmbeddedInWindow 50 50 500 400]
tcl3dOsgSetOsgWin $osgwin

viewer realize

CreateWidgets $osgwin

PrintInfo [tcl3dOsgGetInfoString]

if { [file tail [info script]] eq [file tail $::argv0] } {
    # If started directly from tclsh or wish, then start animation.
    update
    StartAnimation
}
