# NPS04_TestState.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 #4 (TestState)"

    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 createPyramid {} {
    osg::Geode    pyramidGeode
    osg::Geometry pyramidGeometry

    pyramidGeode addDrawable pyramidGeometry

    # Specify the vertices
    osg::Vec3Array pyramidVertices
    pyramidVertices push [osg::Vec3 p1 0 0 0] ; # front left 
    pyramidVertices push [osg::Vec3 p2 2 0 0] ; # front right 
    pyramidVertices push [osg::Vec3 p3 2 2 0] ; # back right 
    pyramidVertices push [osg::Vec3 p4 0 2 0] ; # back left 
    pyramidVertices push [osg::Vec3 p5 1 1 2] ; # peak
   
    # Associate this set of vertices with the geometry associated with the
    # geode we added to the scene.
    pyramidGeometry setVertexArray pyramidVertices

    osg::DrawElementsUInt pyramidBase $::osg::PrimitiveSet_QUADS 0
    pyramidBase push 3
    pyramidBase push 2
    pyramidBase push 1
    pyramidBase push 0
    pyramidGeometry addPrimitiveSet pyramidBase

    # Repeat the same for each of the four sides. Again, vertices are 
    # specified in counter-clockwise order. 
 
    osg::DrawElementsUInt pyramidFaceOne $::osg::PrimitiveSet_TRIANGLES 0
    pyramidFaceOne push 0
    pyramidFaceOne push 1
    pyramidFaceOne push 4
    pyramidGeometry addPrimitiveSet pyramidFaceOne

    osg::DrawElementsUInt pyramidFaceTwo $::osg::PrimitiveSet_TRIANGLES 0
    pyramidFaceTwo push 1
    pyramidFaceTwo push 2
    pyramidFaceTwo push 4
    pyramidGeometry addPrimitiveSet pyramidFaceTwo

    osg::DrawElementsUInt pyramidFaceThree $::osg::PrimitiveSet_TRIANGLES 0
    pyramidFaceThree push 2
    pyramidFaceThree push 3
    pyramidFaceThree push 4
    pyramidGeometry addPrimitiveSet pyramidFaceThree

    osg::DrawElementsUInt pyramidFaceFour $::osg::PrimitiveSet_TRIANGLES 0
    pyramidFaceFour push 3
    pyramidFaceFour push 0
    pyramidFaceFour push 4
    pyramidGeometry addPrimitiveSet pyramidFaceFour

    osg::Vec4Array colors
    colors push [osg::Vec4f col1 1.0 0.0 0.0 1.0] ; # index 0 red
    colors push [osg::Vec4f col2 0.0 1.0 0.0 1.0] ; # index 1 green
    colors push [osg::Vec4f col3 0.0 0.0 1.0 1.0] ; # index 2 blue
    colors push [osg::Vec4f col4 1.0 1.0 1.0 1.0] ; # index 3 white
    colors setBinding $::osg::Geometry_BIND_PER_VERTEX

    pyramidGeometry setColorArray colors

    osg::Vec2Array texcoords 5
    texcoords set 0 [osg::Vec2 v1 0.00 0.0] ; # tex coord for vertex 0
    texcoords set 1 [osg::Vec2 v2 0.25 0.0] ; # tex coord for vertex 1
    texcoords set 2 [osg::Vec2 v3 0.50 0.0] ; #  ""
    texcoords set 3 [osg::Vec2 v4 0.75 0.0] ; #  ""
    texcoords set 4 [osg::Vec2 v5 0.50 1.0] ; #  ""

    pyramidGeometry setTexCoordArray 0 texcoords
    return pyramidGeode
}

# We have the geometry ready. Now create the viewer and the Tk widgets.
osgViewer::ViewerRef viewer [osgViewer::Viewer]

# Declare a group to act as root node of a scene.
osg::Group root

# Declare a box class (derived from shape class) instance
# This constructor takes an osg::Vec3 to define the center
# and a float to define the height, width and depth.
# (an overloaded constructor allows you to specify unique
#  height, width and height values.)
osg::Box unitCube [osg::Vec3 v1 0 0 0] 1.0
unitCube setDataVariance $::osg::Object_DYNAMIC

# Declare an instance of the shape drawable class and initialize 
# it with the unitCube shape we created above.
# This class is derived from 'drawable' so instances of this
# class can be added to Geode instances.
osg::ShapeDrawable unitCubeDrawable unitCube

# Declare a instance of the geode class: 
osg::Geode basicShapesGeode

# Add the unit cube drawable to the geode:
basicShapesGeode addDrawable unitCubeDrawable

# Add the goede to the scene:
root addChild basicShapesGeode

osg::Sphere unitSphere [osg::Vec3 v2 0 0 0] 1.0
osg::ShapeDrawable unitSphereDrawable unitSphere
unitSphereDrawable setColor [osg::Vec4 v3 0.1 0.1 0.1 0.1]

osg::PositionAttitudeTransform unitSphereXForm
unitSphereXForm setPosition [osg::Vec3d v4 3.0 0 0]

osg::Geode unitSphereGeode
root addChild unitSphereXForm
unitSphereXForm addChild unitSphereGeode
unitSphereGeode addDrawable unitSphereDrawable

set pyramidGeode [createPyramid]

osg::PositionAttitudeTransform pyramidXForm
pyramidXForm setPosition [osg::Vec3d v5 0 -3.0 0]
root addChild pyramidXForm
pyramidXForm addChild pyramidGeode

osg::Texture2D KLN89FaceTexture
# protect from being optimized away as static state:
KLN89FaceTexture setDataVariance $::osg::Object_DYNAMIC

set imgFile [file join $::gDemo(scriptDir) "Data/Textures/KLN89FaceB.tga"]
set klnFace [osgDB::readImageFile $imgFile]
if { $klnFace eq "NULL" } {
    puts " couldn't find texture, quitting."
    exit -1
}

KLN89FaceTexture setImage $klnFace

# Declare a state set for 'BLEND' texture mode
osg::StateSet blendStateSet

# Declare a TexEnv instance, set the mode to 'BLEND'
osg::TexEnv blendTexEnv
blendTexEnv setMode $::osg::TexEnv_BLEND

# Turn the attribute of texture 0 'ON'
blendStateSet setTextureAttributeAndModes 0 KLN89FaceTexture $::osg::StateAttribute_ON
# Set the texture texture environment for texture 0 to the 
# texture envirnoment we declared above:
blendStateSet setTextureAttribute 0 blendTexEnv

osg::StateSet decalStateSet
osg::TexEnv decalTexEnv
decalTexEnv setMode $::osg::TexEnv_DECAL

decalStateSet setTextureAttributeAndModes 0 KLN89FaceTexture $::osg::StateAttribute_ON
decalStateSet setTextureAttribute 0 decalTexEnv

root setStateSet blendStateSet
unitSphereGeode setStateSet decalStateSet

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
}
