# NPS02_GeometryTest.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 #2 (GeometryTest)"

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

osg::Group root

osg::Geode    pyramidGeode
osg::Geometry pyramidGeometry
osg::Geode    crossGeode
osg::Geometry crossGeometry

# Associate the pyramid geometry with the pyramid geode 
# Add the pyramid geode to the root node of the scene graph.

pyramidGeode addDrawable pyramidGeometry
root         addChild    pyramidGeode
crossGeode   addDrawable crossGeometry
root         addChild    crossGeode

# Declare an array of vertices. Each vertex will be represented by 
# a triple -- an instances of the vec3 class. An instance of 
# osg::Vec3Array can be used to store these triples. Since 
# osg::Vec3Array is derived from the STL vector class, we can use the
# push_back method to add array elements. Push back adds elements to 
# the end of the vector, thus the index of first element entered is 
# zero, the second entries index is 1, etc.
# Using a right-handed coordinate system with 'z' up, array 
# elements zero..four below represent the 5 points required to create 
# a simple pyramid.

osg::Vec3Array pyramidVertices
pyramidVertices push [osg::Vec3 p1  0  0  0] ; # front left 
pyramidVertices push [osg::Vec3 p2 10  0  0] ; # front right 
pyramidVertices push [osg::Vec3 p3 10 10  0] ; # back right 
pyramidVertices push [osg::Vec3 p4  0 10  0] ; # back left 
pyramidVertices push [osg::Vec3 p5  5  5 10] ; # peak

set pclen  12.0
set mclen -12.0
osg::Vec3Array crossVertices
crossVertices push [osg::Vec3 c1 $mclen 0.0  0.0]
crossVertices push [osg::Vec3 c2 $pclen 0.0  0.0]
crossVertices push [osg::Vec3 c3 0.0    0.0  $mclen]
crossVertices push [osg::Vec3 c4 0.0    0.0  $pclen]

# Associate this set of vertices with the geometry associated with the 
# geode we added to the scene.

pyramidGeometry setVertexArray pyramidVertices
crossGeometry   setVertexArray crossVertices

# Next, create a primitive set and add it to the pyramid geometry. 
# Use the first four points of the pyramid to define the base using an 
# instance of the DrawElementsUint class. Again this class is derived 
# from the STL vector, so the push_back method will add elements in 
# sequential order. To ensure proper backface cullling, vertices 
# should be specified in counterclockwise order. The arguments for the 
# constructor are the enumerated type for the primitive 
# (same as the OpenGL primitive enumerated types), and the index in 
# the vertex array to start from.

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

osg::DrawElementsUInt cross $::osg::PrimitiveSet_LINES 0
cross push 3
cross push 2
cross push 1
cross push 0
crossGeometry addPrimitiveSet cross

# 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

# Declare and load an array of Vec4 elements to store colors. 

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

# The next step is to associate the array of colors with the geometry, 
# assign the color indices created above to the geometry and set the 
# binding mode to _PER_VERTEX.

pyramidGeometry setColorArray colors
crossGeometry   setColorArray colors

# Now that we have created a geometry node and added it to the scene 
# we can reuse this geometry. For example, if we wanted to put a 
# second pyramid 15 units to the right of the first one, we could add 
# this geode as the child of a transform node in our scene graph. 

#  Declare and initialize a transform node.
osg::PositionAttitudeTransform pyramidTwoXForm

#  Use the 'addChild' method of the osg::Group class to
#  add the transform as a child of the root node and the
#  pyramid node as a child of the transform.

root addChild pyramidTwoXForm
pyramidTwoXForm addChild pyramidGeode

#  Declare and initialize a Vec3 instance to change the
#  position of the model in the scene

osg::Vec3d pyramidTwoPosition 15 0 0
pyramidTwoXForm setPosition pyramidTwoPosition

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

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
}
