# solar.tcl
#
# Original C++ code by Katja Treiber and Matthias Schmidt.
# See www.cuboslocos.com 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 [after idle 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
        Update [viewer elapsedTime]
    }
    $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: CubosLocos tutorial SolarSystem"

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

# Factor for speed of whole animation
set ANIMATION_SPEED 2.0
set gUniqueLightNumber 0

namespace eval CuloGeode {
    variable m_Material

    proc new {} {
        clearMaterial
    }

    proc clearMaterial {} {
        variable m_Material
        set m_Material [osg::Material]
    }

    proc prepareMaterial { givenMaterial } {
        variable m_Material
        set m_Material $givenMaterial
    }

    proc createTexture { texName texRepeat } {
        # load texture file
        set img [osgDB::readImageFile $texName]
        if { $img eq "NULL" } {
            puts "Error reading texture $texName"
            return "NULL"
        }

        # create texture
        osg::Texture2D texture
        texture setDataVariance $::osg::Object_DYNAMIC
        texture setFilter $::osg::Texture_MIN_FILTER $::osg::Texture_LINEAR_MIPMAP_LINEAR
        texture setFilter $::osg::Texture_MAG_FILTER $::osg::Texture_LINEAR
        
        # handle repeat
        if { $texRepeat } {
            texture setWrap $::osg::Texture_WRAP_S $::osg::Texture_REPEAT
            texture setWrap $::osg::Texture_WRAP_T $::osg::Texture_REPEAT
        } else {
            texture setWrap $::osg::Texture_WRAP_S $::osg::Texture_CLAMP
            texture setWrap $::osg::Texture_WRAP_T $::osg::Texture_CLAMP
        }
        texture setImage $img
        
        return texture
    }

    proc createSphere { size texName texRepeat } {
        variable m_Material

        set sphere [osg::Geode]
        $sphere addDrawable [osg::ShapeDrawable dr [osg::Sphere sp [osg::Vec3 v 0 0 4] $size]]
 
        # assign the material to the sphere
        set sphereStateSet [$sphere getOrCreateStateSet]
        $sphereStateSet ref
        $sphereStateSet setAttribute $m_Material osg::StateAttribute_ON
 
        if { [string length $texName] > 0 } {
            $sphereStateSet setTextureAttributeAndModes 0 \
                            [createTexture $texName $texRepeat] $::osg::StateAttribute_ON
        }
 
        set sphereTransform [osg::PositionAttitudeTransform]
        $sphereTransform addChild $sphere
        return $sphereTransform
    }

    proc createPlane {size texName texRepeat } {
        variable m_Material

        # vertex array
        osg::Vec3Array vertexArray
        
        set negSize [expr -1.0 * $size]
        vertexArray push [osg::Vec3 v1 $negSize $negSize 0]
        vertexArray push [osg::Vec3 v2 $size $negSize 0]
        vertexArray push [osg::Vec3 v3 $size $size 0]
        vertexArray push [osg::Vec3 v4 $negSize $size 0]
        
        # face array
        osg::DrawElementsUInt faceArray $::osg::PrimitiveSet_TRIANGLES 0
        
        faceArray push 0 ; # face 1
        faceArray push 1
        faceArray push 2
        faceArray push 2 ; # face 2
        faceArray push 3
        faceArray push 0
        
        # normal array
        osg::Vec3Array normalArray
        normalArray push [osg::Vec3 na 0 0 1]
        
        # normal index
        osg::UIntArray normalIndexArray
        
        normalIndexArray push 0
        normalIndexArray push 0
        normalIndexArray push 0
        normalIndexArray push 0
        
        # texture coordinates
        osg::Vec2Array texCoords
        texCoords push [osg::Vec2 tc1 0.0 0.0]
        texCoords push [osg::Vec2 tc2 4.0 0.0]
        texCoords push [osg::Vec2 tc3 4.0 4.0]
        texCoords push [osg::Vec2 tc4 0.0 4.0]
        
        set geometry [osg::Geometry]
        $geometry setVertexArray vertexArray
        $geometry setNormalArray normalArray
        #$geometry setNormalIndices normalIndexArray
        $geometry setNormalBinding $::osg::Geometry_BIND_PER_VERTEX
        $geometry setTexCoordArray 0 texCoords
        $geometry addPrimitiveSet faceArray
        
        set plane [osg::Geode]
        $plane addDrawable $geometry
        
        # assign the material to the sphere
        set planeStateSet [$plane getOrCreateStateSet]
        $planeStateSet ref
        $planeStateSet setAttribute $m_Material osg::StateAttribute_ON
        
        if { [string length $texName] > 0 } {
            $planeStateSet setTextureAttributeAndModes 0 \
                           [createTexture $texName $texRepeat] $::osg::StateAttribute_ON
        }
        
        set planeTransform [osg::PositionAttitudeTransform]
        $planeTransform addChild $plane
        return $planeTransform
    }
}

proc createLight { color } {       
    global gUniqueLightNumber

    osg::Light light
    # each light must have a unique number
    light setLightNum $gUniqueLightNumber
    incr gUniqueLightNumber
    # we set the light's position via a PositionAttitudeTransform object
    light setPosition [osg::Vec4 lpos 0.0 0.0 0.0 1.0]
    light setDiffuse  $color
    light setSpecular [osg::Vec4 lspec 1.0 1.0 1.0 1.0]
    light setAmbient  [osg::Vec4 lamb  0.0 0.0 0.0 1.0]
    
    return light
}

proc positionStarfieldBox {} {
    global gStarfield

    set DISTANCE 20000
    set SCALE      200
    osg::Quat rotate

    # top
    $gStarfield(0) setPosition [osg::Vec3d pos0 0 0 $DISTANCE]
    $gStarfield(0) setScale    [osg::Vec3d scl0 $SCALE $SCALE 0]
        
    # bottom
    $gStarfield(1) setPosition [osg::Vec3d pos1 0 0 [expr -1.0 *$DISTANCE]]
    $gStarfield(1) setScale    [osg::Vec3d scl1 $SCALE $SCALE 0]

    # left
    rotate makeRotate [tcl3dDegToRad 90.0] 0 1 0
    $gStarfield(2) setAttitude rotate
    $gStarfield(2) setPosition [osg::Vec3d pos2 [expr -1.0 * $DISTANCE] 0 0]
    $gStarfield(2) setScale    [osg::Vec3d scl2 $SCALE $SCALE 0]

    # right
    rotate makeRotate [tcl3dDegToRad 90.0] 0 1 0
    $gStarfield(3) setAttitude rotate
    $gStarfield(3) setPosition [osg::Vec3d pos3 $DISTANCE 0 0]
    $gStarfield(3) setScale    [osg::Vec3d scl3 $SCALE $SCALE 0]

    # front
    rotate makeRotate [tcl3dDegToRad 90.0] 1 0 0
    $gStarfield(4) setAttitude rotate
    $gStarfield(4) setPosition [osg::Vec3d pos4 0 [expr -1.0 *$DISTANCE] 0]
    $gStarfield(4) setScale    [osg::Vec3d scl4 $SCALE $SCALE 0]

    # back
    rotate makeRotate [tcl3dDegToRad 90.0] 1 0 0
    $gStarfield(5) setAttitude rotate
    $gStarfield(5) setPosition [osg::Vec3d pos5 0 $DISTANCE 0]
    $gStarfield(5) setScale    [osg::Vec3d scl5 $SCALE $SCALE 0]
}

proc Startup {} {
    global gStarfield gPlanets
    global gDemo

    # we need the scene's state set to enable the light for the entire scene
    osg::Group scene
    set lightStateSet [scene getOrCreateStateSet]
    $lightStateSet ref

    # create a light
    osg::LightSource lightSource
    lightSource setLight [createLight [osg::Vec4 lgt 0.9 0.9 0.9 1.0]]
    # enable the light for the entire scene
    lightSource setLocalStateSetModes $::osg::StateAttribute_ON
    lightSource setStateSetModes $lightStateSet $::osg::StateAttribute_ON

    osg::PositionAttitudeTransform lightTransform
    lightTransform addChild lightSource
    lightTransform setPosition [osg::Vec3d lt 3 0 0]

    # create Geometry with CuLo-Geo
    CuloGeode::new
            
    # stars / starfield
    osg::Material material
    material setEmission  $::osg::Material_FRONT [osg::Vec4 matEmis 1.0 1.0 1.0 1.0]
    material setAmbient   $::osg::Material_FRONT [osg::Vec4 matAmb  1.0 1.0 1.0 1.0]
    material setShininess $::osg::Material_FRONT 25.0
    CuloGeode::prepareMaterial material
    for { set i 0 } { $i < 6 } { incr i } {
        set gStarfield($i) [CuloGeode::createPlane 100.0 [file join $gDemo(scriptDir) "Data/stars.rgb"] true]
        scene addChild $gStarfield($i)
    }

    # sun
    CuloGeode::prepareMaterial material
    set sunFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/sunmap.rgb"]]
    set gPlanets(sun) [CuloGeode::createSphere 300 $sunFile false]

    # planets
    osg::Material material2
    material2 setEmission $::osg::Material_FRONT [osg::Vec4 me 0.1 0.1 0.1 1.0]
    CuloGeode::prepareMaterial material2

    set mercuryFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/mercurymap.rgb"]]
    set venusFile   [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/venusmap.rgb"]]
    set earthFile   [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/earthmap1k.rgb"]]
    set emoonFile   [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/moonmap1k.rgb"]]
    set marsFile    [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/mars_1k_color.rgb"]]
    set jupiterFile [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/jupitermap.rgb"]]
    set saturnFile  [tcl3dGetExtFile [file join $gDemo(scriptDir) "Data/saturnmap.rgb"]]

    set gPlanets(mercury) [CuloGeode::createSphere   3.8 $mercuryFile false]
    set gPlanets(venus)   [CuloGeode::createSphere   9.5 $venusFile   false]
    set gPlanets(earth)   [CuloGeode::createSphere  10   $earthFile   false]
    set gPlanets(emoon)   [CuloGeode::createSphere   2   $emoonFile   false]
    set gPlanets(mars)    [CuloGeode::createSphere   5.3 $marsFile    false]
    set gPlanets(jupiter) [CuloGeode::createSphere 109   $jupiterFile false]
    set gPlanets(saturn)  [CuloGeode::createSphere  91   $saturnFile  false]
    
    lightTransform addChild $gPlanets(sun)
    scene addChild lightTransform
    scene addChild $gPlanets(mercury)
    scene addChild $gPlanets(venus)
    $gPlanets(earth) addChild $gPlanets(emoon)
    scene addChild $gPlanets(earth)
    scene addChild $gPlanets(mars)
    scene addChild $gPlanets(jupiter)
    scene addChild $gPlanets(saturn)
    
    return scene
}

proc Update { t } {
    global gPlanets

    set t [expr $t * $::ANIMATION_SPEED]
    set SUN_DISTANCE 1500
    set BASE_PERIOD [expr -1.0 * $t / 50]
    set BASE_ROTATION [expr $t / 2]
    osg::Quat rotate
 
    rotate makeRotate [expr -1.0 * $t/100] 0 0 1
    $gPlanets(sun) setAttitude rotate
        
    rotate makeRotate [expr $BASE_ROTATION / 58] 0 0 1
    $gPlanets(mercury) setAttitude rotate
    set sin [expr sin ($BASE_PERIOD / 0.24)]
    set cos [expr cos ($BASE_PERIOD / 0.24)]
    set pos [[osg::Vec3d mercury $sin $cos 0] mul [expr $SUN_DISTANCE * 0.38]]
    $gPlanets(mercury) setPosition $pos

    rotate makeRotate [expr $BASE_ROTATION / -243] 0 0 1
    $gPlanets(venus) setAttitude rotate
    set sin [expr sin ($BASE_PERIOD / 0.61)]
    set cos [expr cos ($BASE_PERIOD / 0.61)]
    set pos [[osg::Vec3d venus $sin $cos 0] mul [expr $SUN_DISTANCE * 0.72]]
    $gPlanets(venus) setPosition $pos

    rotate makeRotate $BASE_ROTATION 0 0 1
    $gPlanets(earth) setAttitude rotate
    set pos [[osg::Vec3d earth [expr sin($BASE_PERIOD)] [expr cos($BASE_PERIOD)] 0] mul $SUN_DISTANCE]
    $gPlanets(earth) setPosition $pos
    set pos [[osg::Vec3d emoon [expr -sin($t)] [expr cos($t)] 0] mul 20]
    $gPlanets(emoon) setPosition $pos

    rotate makeRotate [expr $BASE_ROTATION / 1.02] 0 0 1
    $gPlanets(mars) setAttitude rotate
    set sin [expr sin ($BASE_PERIOD / 1.88)]
    set cos [expr cos ($BASE_PERIOD / 1.88)]
    set pos [[osg::Vec3d mars $sin $cos 0] mul [expr $SUN_DISTANCE * 1.52]]
    $gPlanets(mars) setPosition $pos

    rotate makeRotate [expr $BASE_ROTATION / 0.41] 0 0 1
    $gPlanets(jupiter) setAttitude rotate
    set sin [expr sin ($BASE_PERIOD / 11.86)]
    set cos [expr cos ($BASE_PERIOD / 11.86)]
    set pos [[osg::Vec3d jupiter $sin $cos 0] mul [expr $SUN_DISTANCE * 5.20]]
    $gPlanets(jupiter) setPosition $pos

    rotate makeRotate [expr $BASE_ROTATION / 0.44] 0 0 1
    $gPlanets(saturn) setAttitude rotate
    set sin [expr sin ($BASE_PERIOD / 29.44)]
    set cos [expr cos ($BASE_PERIOD / 29.44)]
    set pos [[osg::Vec3d saturn $sin $cos 0] mul [expr $SUN_DISTANCE * 9.53]]
    $gPlanets(saturn) setPosition $pos
}

# Create the viewer and the Tk widgets.
osgViewer::ViewerRef viewer [osgViewer::Viewer]

# set the scene-graph data the viewer will render
Startup
viewer setSceneData scene

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 realize
    positionStarfieldBox
    while { ! [viewer done] } {
        viewer frame
        Update [viewer elapsedTime]
    }
    exit 0
}

# Use the OSG viewer inside a Togl widget.
set osgwin [viewer setUpViewerAsEmbeddedInWindow 50 50 500 400]
tcl3dOsgSetOsgWin $osgwin

viewer realize
positionStarfieldBox

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
}
