# OpenSceneGraph Quick Start Guide
# http://www.skew-matrix.com/OSGQSG
#
# Picking Example, Using the osgUtil Intersection classes and osgGA NodeKit
#
# Code derived from an OSG example. Original comment block follows.
#
# C++ source file - (C) 2003 Robert Osfield, released under the OSGPL.
#
# Simple example of use of osgViewer::GraphicsWindow + SimpleViewer
# that provides the user with control over view position with basic picking.
#
# 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 } } {
    global gDemo

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

    # Propagate resize event to embedded OSG window.
    tcl3dOsgWindowResize $toglwin [tcl3dOsgGetOsgWin] $w $h
    set gDemo(winWidth)  $w
    set gDemo(winHeight) $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 SetMousePos { x y } {
    global gDemo

    set gDemo(mx) $x
    set gDemo(my) $y
}

proc getXnormalized { x } {
    global gDemo

    return [expr {2.0*$x/$gDemo(winWidth)-1.0}]
}

proc getYnormalized { y } {
    global gDemo

    return [expr {2.0*$y/$gDemo(winHeight)-1.0}]
}

proc HandlePick { x y } {
    global gDemo

    if { $gDemo(mx) == $x && $gDemo(my) == $y } {
        # If the mouse hasn't moved since the last
        # button press or move event, perform a
        # pick. (Otherwise, the trackball
        # manipulator will handle it.)
        Pick [getXnormalized $x] [getYnormalized $y]
    }
}

proc SetPickBindings { toglwin osgwin } {
    bind $toglwin <<LeftMousePress>>   "+SetMousePos %x %y"
    bind $toglwin <<LeftMouseRelease>> "+HandlePick  %x %y"
}

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: OSG QuickStartGuide example Picking"

    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
    SetPickBindings $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.
#

# Derive a class from NodeCallback to manipulate a MatrixTransform object's matrix.
proc RotateCB { node args } {
    global gAngle

    if { ! [info exists gAngle] } {
        set gAngle 0.0
    }
    osg::Matrix m
    m makeRotate $gAngle [osg::Vec3 rot 0 0 1]
    $node setMatrix m

    # Increment the angle for the next frame.
    set gAngle [expr {$gAngle + 0.01}]
}

# Create the scene graph. This is a Group root node with two
# MatrixTransform children, which multiply parent a single
# Geode loaded from the cow.osg model file.
proc CreateScene {} {
    global gDemo

    # Load the cow model.
    set osgFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data" "cow.osg"]]
    osg::NodeRef cow [osgDB::readNodeFile $osgFile]
    if { ! [cow valid] } {
        error "Unable to load data file. Exiting."
    }

    # Data variance is STATIC because we won't modify it.
    cow setDataVariance $::osg::Object_STATIC

    # Create a MatrixTransform to display the cow on the left.
    osg::MatrixTransform mtLeft
    mtLeft setName "Left Cow"
    mtLeft setDataVariance $::osg::Object_STATIC

    osg::Matrix m
    m makeTranslate -6.0 0.0 0.0
    mtLeft setMatrix m

    osg::MatrixTransform mtl
    mtl setName "Left Rotation"
    mtl setDataVariance $::osg::Object_STATIC
    m makeIdentity
    mtl setMatrix m

    mtLeft addChild mtl
    mtl addChild [cow get]

    # Create a MatrixTransform to display the cow on the right.
    osg::MatrixTransform mtRight
    mtRight setName "Right Cow"
    mtRight setDataVariance $::osg::Object_STATIC
    m makeTranslate 6 0 0
    mtRight setMatrix m

    osg::MatrixTransform mtr
    mtr setName "Right Rotation"
    mtr setDataVariance $::osg::Object_STATIC
    m makeIdentity
    mtr setMatrix m

    mtRight addChild mtr
    mtr addChild [cow get]

    # Create the Group root node.
    osg::Group root
    root setName "Root Node"
    # Data variance is STATIC because we won't modify it.
    root setDataVariance $::osg::Object_STATIC
    root addChild mtLeft
    root addChild mtRight

    return root
}

# Perform a pick operation.
proc Pick { x y } {
    global gSelectedNode gCounter

    if { [viewer getSceneData] eq "NULL" } {
        # Nothing to pick.
        return false
    }

    if { ! [info exists gCounter] } {
        set gCounter 0
    } else {
        incr gCounter
    }
    set w 0.05
    set h 0.05
    set osgMajorVersion [lindex [split [tcl3dOsgGetVersion] "."] 0]
    if { $osgMajorVersion >= 3 } {
        set pickerId [osgUtil::LineSegmentIntersector osgUtilPicker_$gCounter \
                      $::osgUtil::Intersector_PROJECTION $x $y]
    } else {
        set pickerId [osgUtil::PolytopeIntersector osgUtilPicker_$gCounter \
                      $::osgUtil::Intersector_PROJECTION \
                      [expr {$x-$w}] [expr {$y-$h}] [expr {$x+$w}] [expr {$y+$h}]]
    }

    osgUtil::IntersectionVisitor iv $pickerId
    set cam [viewer getCamera]
    osg::Camera_accept $cam iv

    if { [$pickerId containsIntersections] } {
        set firstIsect [$pickerId getFirstIntersection]
        set nodePath [$firstIsect cget -nodePath]
        set idx [$nodePath size]
        incr idx -1
        while { $idx >= 0 } {
            # Find the LAST MatrixTransform in the node
            # path; this will be the MatrixTransform
            # to attach our callback to.

            set node [osg::NodeStdVec_get $nodePath $idx]
            set mt [$node asMatrixTransform]
            if { $mt eq "NULL" } {
                incr idx -1
                continue
            }

            # If we get here, we just found a
            # MatrixTransform in the nodePath.

            if { $gSelectedNode ne "" } {
                # Clear the previous selected node's
                # callback to make it stop spinning.
                $gSelectedNode setUpdateCallback "NULL"
            }

            set gSelectedNode $mt

            # Define the update callback procedure.
            osg::tcl3dOsgNodeCallback ncRot_$gCounter RotateCB

            $gSelectedNode setUpdateCallback ncRot_$gCounter
            break
        }
        if { $gSelectedNode eq "" } {
            puts "Pick failed."
        }
    } elseif { $gSelectedNode ne "" } {
        $gSelectedNode setUpdateCallback "NULL"
    }
    iv -delete
    $pickerId -delete
    if { $gSelectedNode ne "" } {
        return true
    } else {
        return false
    }
}

set gSelectedNode ""

# Create the viewer and set its scene data to our scene
# graph created above.
osgViewer::ViewerRef viewer [osgViewer::Viewer]

viewer setSceneData [CreateScene]
if { [viewer getSceneData] eq "NULL" } {
    error "No scene data available"
}

# Set the clear color to something other than chalky blue.
set cam [viewer getCamera]
osg::Camera_setClearColor $cam [osg::Vec4 col 1 1 1 1]

viewer setCameraManipulator [osgGA::TrackballManipulator]

if { $argc >= 1 && [lindex $argv 0] eq "-viewer" } {
    # Only use the standard OSG viewer window without any Tk widgets.
    # In this mode, picking is not implemented.
    puts "Picking not implemented in osgViewer mode."
    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
}
