# OpenSceneGraph Quick Start Guide
# http://www.skew-matrix.com/OSGQSG
#
# Lighting Example, Basic light and material control
#
# 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: OSG QuickStartGuide example Lighting"

    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 CreateLightPoint {} {
    osg::GeometryRef geom [osg::Geometry]

    # Don't throw away single-vertex primitives.
    osg::BoundingBox bbox -0.1 -0.1 -0.1 0.1 0.1 0.1
    geom setInitialBound bbox

    osg::Vec3Array v
    geom setVertexArray v
    v push [osg::Vec3 v1 0.0 0.0 0.0]

    osg::Vec4Array c
    geom setColorArray c
    geom setColorBinding $::osg::Geometry_BIND_OVERALL
    c push [osg::Vec4 c1 1.0 1.0 1.0 1.0]

    geom addPrimitiveSet [osg::DrawArrays da $::GL_POINTS 0 1]

    osg::Geode geode
    geode addDrawable [geom get]

    set state [geode getOrCreateStateSet]
    $state setMode $::GL_LIGHTING \
                   [expr $::osg::StateAttribute_OFF | $::osg::StateAttribute_PROTECTED]
    osg::Point pt
    pt setSize 10.0
    $state setAttribute pt osg::StateAttribute_ON

    return geode
}

proc CreatePlane {} {
    osg::GeometryRef geom [osg::Geometry]

    osg::Vec3Array v
    geom setVertexArray v
    for { set y -10 } { $y <= 10 } { incr y } {
        for { set x -10 } { $x <= 10 } { incr x } {
            v push [osg::Vec3 v_${x}_${y} [expr {$x*0.5}] [expr {$y*0.5}] 0.0]
        }
    }

    osg::Vec3Array n
    geom setNormalArray n
    geom setNormalBinding $::osg::Geometry_BIND_OVERALL
    n push [osg::Vec3 n1 0.0 0.0 1.0]

    osg::Vec4Array c
    geom setColorArray c
    geom setColorBinding $::osg::Geometry_BIND_OVERALL
    c push [osg::Vec4 c1 1.0 1.0 1.0 1.0]

    set len 21
    set idx  0
    set numStrips [expr {$len-1}]
    set indices [tcl3dVector GLushort [expr {$len+$len}]]
    while { $numStrips > 0 } {
        set vert 0
        while { $vert < $len } {
            $indices set [expr {2*$vert+0}] [expr {$idx+$len}]
            $indices set [expr {2*$vert+1}] $idx
            incr vert
            incr idx
        }
        osg::DrawElementsUShort strip_$numStrips $::osg::PrimitiveSet_QUAD_STRIP \
                                [expr {$len+$len}] $indices
        geom addPrimitiveSet strip_$numStrips
        incr numStrips -1
    }

    return [geom get]
}

proc CreateSceneGraph {} {
    global gDemo

    # Create the root node and set state for the entire subgraph.
    osg::GroupRef root [osg::Group]
    set state [root getOrCreateStateSet]
    $state setMode $::GL_LIGHTING $::osg::StateAttribute_ON
    $state setMode $::GL_LIGHT0   $::osg::StateAttribute_ON
    $state setMode $::GL_LIGHT1   $::osg::StateAttribute_ON

    osg::Matrix m

    # Represent the two light sources with a shared point.
    # Each light's position is 0,0,0 in object coordinates. The
    # two parent MatrixTransforms translate the lights, and the
    # shared point, to their actual positions.
    osg::Geode lightPoint [CreateLightPoint]
    osg::MatrixTransform mt0
    m makeTranslate [osg::Vec3 t0 -3.0 2.0 5.0]
    mt0 setMatrix m

    # Create a mostly red light
    osg::Light light0
    light0 setLightNum 0
    light0 setPosition [osg::Vec4 pos0 0.0 0.0 0.0 1.0]
    light0 setDiffuse  [osg::Vec4 dif0 1.0 0.5 0.5 1.0]
    light0 setSpecular [osg::Vec4 spe0 1.0 0.8 0.8 1.0]

    osg::LightSource ls0
    root addChild mt0
    mt0 addChild ls0
    ls0 setLight light0
    ls0 addChild lightPoint

    osg::MatrixTransform mt1
    m makeTranslate [osg::Vec3 t1 3.0 -2.0 3.0]
    mt1 setMatrix m

    # Create a mostly blue light
    osg::Light light1
    light1 setLightNum 1
    light1 setPosition [osg::Vec4 pos1 0.0 0.0 0.0 1.0]
    light1 setDiffuse  [osg::Vec4 dif1 0.5 0.5 1.0 1.0]
    light1 setSpecular [osg::Vec4 spe1 0.8 0.8 1.0 1.0]

    osg::LightSource ls1
    root addChild mt1
    mt1 addChild ls1
    ls1 setLight light1
    ls1 addChild lightPoint

    # Create a single instance of the lozenge geometry (read from disk).
    # Multiply parent it to six MatrixTransform nodes, each with their
    # own StateSet to change the material properties of the lozenge.
    set osgFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data" "lozenge.osg"]]
    osg::NodeRef lozenge [osgDB::readNodeFile $osgFile]
    if { ! [lozenge valid] } {
        error "Unable to load data file. Exiting."
    }

    osg::MatrixTransform mt2
    m makeTranslate [osg::Vec3 t2 -1.0 -1.0 1.0]
    mt2 setMatrix m

    set state [mt2 getOrCreateStateSet]
    osg::Material mat2
    mat2 setDiffuse  $::osg::Material_FRONT [osg::Vec4 dif2 0.0 0.0 0.0 1.0]
    mat2 setSpecular $::osg::Material_FRONT [osg::Vec4 spe2 1.0 1.0 1.0 1.0]
    mat2 setShininess $::osg::Material_FRONT 128.0
    $state setAttribute mat2 osg::StateAttribute_ON

    mt2 addChild [lozenge get]
    root addChild mt2


    osg::MatrixTransform mt3
    m makeTranslate [osg::Vec3 t3 1.0 -1.0 1.0]
    mt3 setMatrix m

    set state [mt3 getOrCreateStateSet]
    osg::Material mat3
    # Just use the object's primary color for ambient and
    # diffuse (uses the OpenGL color material feature).
    mat3 setColorMode $::osg::Material_AMBIENT_AND_DIFFUSE
    $state setAttribute mat3 osg::StateAttribute_ON

    mt3 addChild [lozenge get]
    root addChild mt3


    osg::MatrixTransform mt4
    m makeTranslate [osg::Vec3 t4 -1.0 0.0 1.0]
    mt4 setMatrix m

    set state [mt4 getOrCreateStateSet]
    osg::Material mat4
    mat4 setDiffuse  $::osg::Material_FRONT [osg::Vec4 dif4 0.4 0.3 0.0 1.0]
    mat4 setSpecular $::osg::Material_FRONT [osg::Vec4 spe4 0.8 0.8 0.1 1.0]
    mat4 setShininess $::osg::Material_FRONT 20.0
    $state setAttribute mat4 osg::StateAttribute_ON

    mt4 addChild [lozenge get]
    root addChild mt4


    osg::MatrixTransform mt5
    m makeTranslate [osg::Vec3 t5 1.0 0.0 1.0]
    mt5 setMatrix m

    set state [mt5 getOrCreateStateSet]
    osg::Material mat5
    mat5 setDiffuse  $::osg::Material_FRONT [osg::Vec4 dif5 0.1 0.2 0.5 1.0]
    mat5 setSpecular $::osg::Material_FRONT [osg::Vec4 spe5 0.9 0.9 1.0 1.0]
    mat5 setShininess $::osg::Material_FRONT 10.0
    $state setAttribute mat5 osg::StateAttribute_ON

    mt5 addChild [lozenge get]
    root addChild mt5


    osg::MatrixTransform mt6
    m makeTranslate [osg::Vec3 t6 -1.0 1.0 1.0]
    mt6 setMatrix m

    set state [mt6 getOrCreateStateSet]
    osg::Material mat6
    mat6 setDiffuse  $::osg::Material_FRONT [osg::Vec4 dif6 0.2 0.9 0.9 1.0]
    mat6 setSpecular $::osg::Material_FRONT [osg::Vec4 spe6 1.0 1.0 1.0 1.0]
    mat6 setShininess $::osg::Material_FRONT 96.0
    $state setAttribute mat6 osg::StateAttribute_ON

    mt6 addChild [lozenge get]
    root addChild mt6


    osg::MatrixTransform mt7
    m makeTranslate [osg::Vec3 t7 1.0 1.0 1.0]
    mt7 setMatrix m

    set state [mt7 getOrCreateStateSet]
    osg::Material mat7
    mat7 setDiffuse  $::osg::Material_FRONT [osg::Vec4 dif7 1.0 1.0 1.0 1.0]
    mat7 setSpecular $::osg::Material_FRONT [osg::Vec4 spe7 0.0 0.0 0.0 1.0]
    $state setAttribute mat7 osg::StateAttribute_ON

    mt7 addChild [lozenge get]
    root addChild mt7


    osg::Geode planeGeode
    planeGeode addDrawable [CreatePlane]
    set state [planeGeode getOrCreateStateSet]

    osg::Material mat8
    mat8 setDiffuse   $::osg::Material_FRONT [osg::Vec4 dif8 0.6 0.5 0.2 1.0]
    mat8 setSpecular  $::osg::Material_FRONT [osg::Vec4 spe8 0.4 0.4 0.4 1.0]
    mat8 setShininess $::osg::Material_FRONT 128.0
    $state setAttribute mat8 osg::StateAttribute_ON

    osg::CullFace cf
    $state setAttributeAndModes cf
    root addChild planeGeode

    return [root get]
}

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

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

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
}
