package require vtk
package require vtkinteraction

#variables
set F "x*y*z"
set dim 20
set minX -10
set maxX 10
set minY -10
set maxY 10
set minZ -10
set maxZ 10
set numContours 10

#graphics and rendering
vtkRenderer ren1
ren1 SetBackground .1 .2 .4

vtkRenderWindow renWin
renWin AddRenderer ren1
renWin SetSize 600 600

vtkRenderWindowInteractor iren
iren SetRenderWindow renWin
iren AddObserver UserEvent {wm deiconify .vtkInteract}
iren Initialize

#wm withdraw .

#Parses the function and generates values
vtkFunctionParser parser

#scalar attributes of the data set
vtkFloatArray scalars

#the data set
vtkStructuredPoints points 

#create contours
vtkContourFilter contours
contours SetInput points 
contours SetComputeNormals 0

#Mapper
vtkDataSetMapper formulaMapper
formulaMapper SetInput [contours GetOutput]

#Actor
vtkActor formulaActor
formulaActor SetMapper formulaMapper

#vtkAxes axes

#Add to scene
ren1 AddActor formulaActor
#ren1 AddActor axes

proc graphIt {} {
    #list variables that are defined in the global scope
    global F dim minX maxX minY maxY minZ maxZ numContours

    #set the function for the parser
    parser SetFunction $F

    #set up the attributes to fit the total values
    #The attributes will be in row major order 
    #(adjacent x's will be next to each other in the array)
    #Then by Y, then by Z
    scalars SetNumberOfTuples [expr $dim * $dim * $dim]

    #compute some usefule local variables
    set rangeX [expr $maxX - $minX]
    set rangeY [expr $maxY - $minY]
    set rangeZ [expr $maxZ - $minZ]

    #used to find the entire range of data to be mapped.
    #required to get contours and colors correct
    set minValue  9999999999
    set maxValue -9999999999

    #loop over the z's
    for {set i 0} {$i < $dim} {incr i} {
	#calculate the part of the array index given by z
	set iOffset [expr $i * $dim * $dim]
	#calculate the value for z
	set z [expr ($i * $rangeZ / double($dim)) + $minZ ]
	#set the z value for the parser
	parser SetScalarVariableValue z $z

	#loop over the y axis
	for {set j 0} {$j < $dim} {incr j} {
	    #calculate the part of the array index given by y
	    set jOffset [expr $j * $dim]
	    #calculate the value for y
	    set y [expr ($j * $rangeY / double($dim)) + $minY ]
	    #set the y value for the parser
	    parser SetScalarVariableValue y $y

	    #loop over the x axis
	    for {set k 0} {$k < $dim} {incr k} {
		#calculate the value for x
		set x [expr ($k * $rangeX / double($dim)) + $minX ]
		#determine the index into the array
		set index [expr $k + $iOffset + $jOffset]
		#set the x value for the parser
		parser SetScalarVariableValue x $x
		#use the parser to generate the value of F(x,y,z)
		set value [parser GetScalarResult]

		#check if the value is the new min or max
                if {$value < $minValue } {set minValue $value}
		if {$value > $maxValue } {set maxValue $value}

		#add the value into the array
		scalars SetTuple1 $index $value
		
		#print it for debugging
		#puts "F($x, $y, $z) = $value"
	    }
	}
    }
    #adjust the size of the data set
    points SetDimensions $dim $dim $dim
    #set the scalar attributes for the data set
    [points GetPointData] SetScalars scalars
    #set up the contour filter
    contours GenerateValues $numContours $minValue $maxValue
    #set the range for the mapper's coloring
    formulaMapper SetScalarRange $minValue $maxValue
    #rerender the window
    renWin Render
}

#set up the user interface
#information for the function: label, text entry, button
#give info about appearance and border
frame .func -relief groove -borderwidth 3
#create a label within this frame
label .func.l1 -text "F(x, y, z) = "
#create a text entry box and associate it with a variable
entry .func.f -width 30 -textvariable F
#create the button and give it a command to run when clicked
button .func.graph -text "Graph It" -command {graphIt}
#pack all the stuff in the frame. Start on the left side
pack .func.l1 .func.f .func.graph -side left

#similar set up as above for x-axis info.
frame .x 
label .x.lx1 -text "Min X"
entry .x.minXent -width 10 -textvariable minX
label .x.lx2 -text "Max X"
entry .x.maxXent -width 10 -textvariable maxX
pack .x.lx1 .x.minXent .x.lx2 .x.maxXent -side left

#similar set up as above for y-axis info.
frame .y 
label .y.ly1 -text "Min Y"
entry .y.minYent -width 10 -textvariable minY
label .y.ly2 -text "Max Y"
entry .y.maxYent -width 10 -textvariable maxY
pack .y.ly1 .y.minYent .y.ly2 .y.maxYent -side left

#similar set up as above for z-axis info.
frame .z 
label .z.lz1 -text "Min Z"
entry .z.minZent -width 10 -textvariable minZ
label .z.lz2 -text "Max Z"
entry .z.maxZent -width 10 -textvariable maxZ
pack .z.lz1 .z.minZent .z.lz2 .z.maxZent -side left

#similar set up as above for contour info.
frame .cont 
label .cont.l1 -text "Contours"
entry .cont.num -width 10 -textvariable numContours
pack .cont.l1 .cont.num -side left

#similar set up as above for resolution info 
#(number of points sampled along each axis)
#We could vary this for each axis, but I don't see why we
#would do that.
frame .res 
label .res.l1 -text "Resolution"
entry .res.res -width 10 -textvariable dim
pack .res.l1 .res.res -side left

#pack all of the subframes into the main window
pack .func .x .y .z .cont .res

#set up so when return is hit in the main window (.)
# the function is graphed
bind . <Key-Return> {
  graphIt
}

#graph it the first time!
graphIt