# Tuto11.tcl: A billboarding effect
#
# This tutorial will extend the previous one on lighting by adding
# two billboard quads. These are also textured and shaded.
#
# Original C++ code by Franclin Foping.
# See http://www.openscenegraph.org/projects/osg/wiki/Support/Tutorials
# for the original files.
#
# Modified for Tcl3D by Paul Obermeier 2009/06/10.
# 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 7
    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: Foping's OSG tutorial #11 (OSG billboarding example)"

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

    # Propagate key and mouse events down to OSG.
    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 "Key-l      Toggle lighting"
    .fr.usage insert end "Key-t      Toggle texturing"
    .fr.usage insert end "Key-s      Switch statistics displays"
    .fr.usage insert end "Key-w      Switch polygon modes (Fill, Line, Points)"
    .fr.usage insert end "Mouse      Trackball"

    .fr.usage configure -state disabled
}

#
# Start of tutorial specific code.
#

proc myQuad {} {
    osg::Geode geode
    osg::Geometry geometry

    osg::Vec3Array vertices

    vertices push [osg::Vec3 v1 1.0 0.0 0.0]
    vertices push [osg::Vec3 v2 1.0 0.0 1.0]
    vertices push [osg::Vec3 v3 0.0 0.0 1.0]
    vertices push [osg::Vec3 v4 0.0 0.0 0.0]

    geometry setVertexArray vertices

    # All vertices are white this time (it's hard to see that we have two
    # textures with all the colors...)
    osg::Vec4Array colors
    colors push [osg::Vec4 c1 1.0 1.0 1.0 1.0]
    geometry setColorArray colors
    geometry setColorBinding $::osg::Geometry_BIND_OVERALL

    osg::Vec3Array normals
    normals push [osg::Vec3 n1 0.0 -1.0 0.0]
    geometry setNormalArray normals
    geometry setNormalBinding $::osg::Geometry_BIND_OVERALL

    osg::Vec2Array texCoords
 
    texCoords push [osg::Vec2 t1 0.0 0.0]
    texCoords push [osg::Vec2 t2 0.0 1.0]
    texCoords push [osg::Vec2 t3 1.0 1.0]
    texCoords push [osg::Vec2 t4 1.0 0.0]

    # Here, the two texture units (0 and 1) share the same texture coordinates.
    geometry setTexCoordArray 0 texCoords
    geometry setTexCoordArray 1 texCoords

    # Back to the usual: setup a primitive set and add the geometry to the geode.
    geometry addPrimitiveSet [osg::DrawArrays da $::osg::PrimitiveSet_QUADS 0 [vertices size]]
    return geometry
}

osgViewer::ViewerRef viewer [osgViewer::Viewer]

osg::Group root

osg::PositionAttitudeTransform objectPat
osg::PositionAttitudeTransform quadPat
osg::MatrixTransform terrainScaleMAT
osg::Matrix terrainScaleMatrix
terrainScaleMatrix makeScale 0.05 0.05 0.03

osg::Vec3d objectPosTrans -1 3 5
osg::Vec3d quadPos   5 0 5
osg::Vec3d quadPos2 -5 0 0

osg::Group lightGroup
set lightSS [root getOrCreateStateSet]
osg::LightSource lightSource1
osg::LightSource lightSource2

# Create a local light.
osg::Vec4f lightPosition -5.0 -2.0 3.0 1.0
osg::Light myLight
myLight setLightNum 1
myLight setPosition lightPosition
myLight setAmbient [osg::Vec4 amb 0.2 0.2 0.2 1.0]
myLight setDiffuse [osg::Vec4 dif 0.8 0.8 0.8 1.0]
myLight setConstantAttenuation 1.0
lightSource1 setLight myLight

lightSource1 setLocalStateSetModes $::osg::StateAttribute_ON
lightSource1 setStateSetModes $lightSS $::osg::StateAttribute_ON

# Create a local light.
osg::Vec4f lightPosition2 2.0 -1.0 3.0 1.0
osg::Light myLight2
myLight2 setLightNum 0
myLight2 setPosition lightPosition2
myLight2 setAmbient [osg::Vec4 amb2 0.2 0.2 0.2 1.0]
myLight2 setDiffuse [osg::Vec4 dif2 0.8 0.1 0.1 1.0]
myLight2 setConstantAttenuation 1.0

lightSource2 setLight myLight2
lightSource2 setLocalStateSetModes $::osg::StateAttribute_ON
lightSource2 setStateSetModes $lightSS $::osg::StateAttribute_ON

lightGroup addChild lightSource1
lightGroup addChild lightSource2

# Light markers: small spheres
osg::Geode lightMarkerGeode
lightMarkerGeode addDrawable [osg::ShapeDrawable sh1 [osg::Sphere sp1 [osg::Vec3f v1 -5.0 -2.0 3.0] 0.5]]

# Second light marker
lightMarkerGeode addDrawable [osg::ShapeDrawable sh2 [osg::Sphere sp2 [osg::Vec3f v2 2.0 -1.0 3.0] 0.5]]

# The geode of the capsule
osg::Geode myshapegeode

objectPat addChild myshapegeode
objectPat setPosition objectPosTrans

quadPat setPosition quadPos
myshapegeode addDrawable [osg::ShapeDrawable sd [osg::Capsule caps [osg::Vec3f] 1 2]]

# Getting the state set of the geode
set nodess [myshapegeode getOrCreateStateSet]

# Loading texture image object
set imgFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/Fieldstone.bmp"]]
set img [osgDB::readImageFile $imgFile]
if { $img eq "NULL" } {
    puts "Error reading texture \"$imgFile\""
    exit 1
}

# Bind the image to a 2D texture object
osg::Texture2D tex
tex setImage $img

# Applying texture on the object
$nodess setTextureAttributeAndModes 0 tex $::osg::StateAttribute_ON

# Loading the terrain node
set osgFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/JoeDirt/JoeDirt.flt"]]
set terrainnode [osgDB::readNodeFile $osgFile]
terrainScaleMAT addChild  $terrainnode
terrainScaleMAT setMatrix terrainScaleMatrix

# Tutorial 11: Billboarding stuff
osg::Billboard quadBillBoard
set billSS [quadBillBoard getOrCreateStateSet]

# Adding texture to the billboards
set img1File [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/wood.bmp"]]
set img1 [osgDB::readImageFile $img1File]
if { $img1 eq "NULL" } {
    puts "Error loading texture \"$img1File\"."
    exit 1
}

osg::Texture2D texture
texture setImage $img1
$billSS setTextureAttributeAndModes 0 texture $::osg::StateAttribute_ON

root addChild quadBillBoard

quadBillBoard setMode $::osg::Billboard_AXIAL_ROT
quadBillBoard setAxis [osg::Vec3 axis 0.0 0.0 1.0]
quadBillBoard setNormal [osg::Vec3 norm 0.0 -1.0 0.0]

quadBillBoard addDrawable [myQuad] [quadPos asVec3f]
quadBillBoard addDrawable [myQuad] [quadPos2 asVec3f]

# adding the terrain node to the root node
root addChild objectPat
root addChild terrainScaleMAT

# Adding the light marker geode
root addChild lightGroup
root addChild lightMarkerGeode

# add the state manipulator
set camStateSet [osg::Node_getOrCreateStateSet [viewer getCamera]]
viewer addEventHandler [osgGA::StateSetManipulator ssm $camStateSet]

# Stats Event Handler s key
viewer addEventHandler [osgViewer::StatsHandler]

# Windows size handler
viewer addEventHandler [osgViewer::WindowSizeHandler]

# Threading Handler activate with the 'm' key
viewer addEventHandler [osgViewer::ThreadingHandler]

viewer setCameraManipulator [osgGA::TrackballManipulator]

# run optimization over the scene graph
osgUtil::Optimizer optimzer
optimzer optimize root

viewer setSceneData root

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
}
