# ballanim.tcl: demonstration of an animation generating frames
# Gordon Kindlmann

catch {load vtktcl}
# get the interactor ui

# "numsteps" determines the number of frames in the animation
# I'm using a number as large as 180 in order to make a smooth animation
# of what is really a very simple scene
set numsteps 180

# "imagebase" is the beginning of the filenames of all the images
# If it is "bingo" then the first frame will be saved in "bingo001.ppm"
# If the frame images should go in another directory, then that should
# be set here by putting the directory name at the beginning of the base
# The setting here assumes there is a directory called "frames"
set imagebase "frames/"

# "setup" arranges all the parameters of the various objects 
# according to the control variable, which varies smoothly from 0.0 to 1.0
proc setup {t} {
    # these are variables for the three lights
    global lt1 lt2 lt3

    # convert the control variable to an angle in radians
    set angle [expr $t*2*3.141592654]

    # sc controls the distance of the lights to sphere
    set sc 5

    # With each frame we are setting the camera location and the camera
    # direction.  Because the interesting part of the scene is always
    # a fixed distance away, we don't have to use vtkCamera's 
    # SetClippingRange, but you may need to for more complicated
    # camera paths
    
    # the eyepoint travels in a circle which is slightly above z = 0
    set xp [expr 3*cos($angle)]
    set yp [expr 3*sin($angle)]
    set zp 1
    [ren1 GetActiveCamera] SetPosition $xp $yp $zp

    # the normalize eye direction
    set dist [expr sqrt($xp*$xp + $yp*$yp + $zp*$zp)]
    set xn [expr $xp / $dist]
    set yn [expr $yp / $dist]
    set zn [expr $zp / $dist]
    [ren1 GetActiveCamera] SetViewUp 0 0 1
    [ren1 GetActiveCamera] OrthogonalizeViewUp

    # the lights' position vectors are permuted versions of the eye point
    # this was done just so the lights had an interesting looking path
    set l2xp [expr $zp/$sc] 
    set l2yp [expr $xp/$sc] 
    set l2zp [expr $yp/$sc]
    set l3xp [expr -$l2xp]
    set l3yp [expr -$l2yp]
    set l3zp [expr -$l2zp]

    # the first light is the default light, we set it to track
    # the eye point so that it acts as a "headlight"
    $lt1 SetPosition $xp $yp $zp

    # the second and third light are set to the same position as the balls
    $lt2 SetPosition $l2xp $l2yp $l2zp
    $lt3 SetPosition $l3xp $l3yp $l3zp
    light2Actor SetPosition $l2xp $l2yp $l2zp
    light3Actor SetPosition $l3xp $l3yp $l3zp
}

# "animate" contains the mainloop of the animation, calling "setup" for
# each time step
proc animate {} {
    global numsteps imagebase

    for {set i 1} {$i <= $numsteps} {incr i} {
	# compute the control variable
	# Note that $t will never actually reach 1.0.  I did this so
	# that the last frame would smoothly precede the very first
	# frame, if the animation is played in a loop
	# If this is not desired, use:
	# set t [expr 1.0*($i-1)/($numsteps-1)]
	set t [expr 1.0*($i-1)/$numsteps]
	
	puts "step $i of $numsteps (t = $t)..."

	# set up the scene and render
	setup $t
	renWin Render

	# set the image filename using the "format" command
	set name [format "%s%03d.ppm" $imagebase $i]

	# uncomment these next three lines to actually save the frames
        #puts "     saving in $name"
	#renWin SetFileName $name
        #renWin SaveImageAsPPM
    }
}

# basic rendering stuff
vtkRenderer ren1
vtkRenderWindow renWin
    renWin AddRenderer ren1
vtkRenderWindowInteractor iren
    iren SetRenderWindow renWin
    iren Initialize

# a big sphere in the middle of the image
vtkSphereSource sphere
    sphere SetPhiResolution 10
    sphere SetThetaResolution 20
vtkPolyDataMapper sphereMapper
    sphereMapper SetInput [sphere GetOutput]
vtkActor sphereActor
    sphereActor SetMapper sphereMapper
    [sphereActor GetProperty] SetColor 1 1 1
    [sphereActor GetProperty] SetInterpolationToFlat
    [sphereActor GetProperty] SetAmbient 0.0
    [sphereActor GetProperty] SetDiffuse 0.4
    [sphereActor GetProperty] SetSpecular 0.8
    [sphereActor GetProperty] SetSpecularPower 30

# little orbiting lights
vtkPolyDataMapper light2Mapper
    light2Mapper SetInput [sphere GetOutput]
vtkActor light2Actor
    light2Actor SetMapper light2Mapper
    [light2Actor GetProperty] SetColor 0.8 0.0 0.0
    light2Actor SetScale 0.2 0.2 0.2
vtkPolyDataMapper light3Mapper
    light3Mapper SetInput [sphere GetOutput]
vtkActor light3Actor
    light3Actor SetMapper light3Mapper
    [light3Actor GetProperty] SetColor 0.0 0.8 0.0
    light3Actor SetScale 0.2 0.2 0.2

# set up renderer and window
ren1 AddActor sphereActor
ren1 AddActor light2Actor
ren1 AddActor light3Actor
ren1 SetBackground 0 0 0
renWin SetSize 256 256 

# create two more lights in addition to the single default light
# without "ren1 Render", get a core dump, why?
ren1 Render
ren1 CreateLight
ren1 CreateLight
set lights [ren1 GetLights]
$lights InitTraversal
set lt1 [$lights GetNextItem]
set lt2 [$lights GetNextItem]
set lt3 [$lights GetNextItem]
$lt1 SetColor 0.7 0.7 1.0
$lt2 SetColor 0.8 0.0 0.0
$lt3 SetColor 0.0 0.8 0.0

# the very simple interface
frame .f
button .f.p -text "animate" -command animate
button .f.s -text "quit" -command exit
pack .f.p .f.s
pack .f

# make the window show the contents of the first frame
setup 0.0