# plot3d.tcl --
#    Facilities to draw simple 3D plots in a dedicated canvas
#
# Note:
#    This source file contains the private functions for 3D plotting.
#    It is the companion of "plotchart.tcl"
#

# Draw3DAxes --
#    Draw the axes in a 3D plot
# Arguments:
#    w           Name of the canvas
#    xmin        Minimum x coordinate
#    xmax        Maximum x coordinate
#    xstep       Step size
#    ymin        Minimum y coordinate
#    ymax        Maximum y coordinate
#    ystep       Step size
#    zmin        Minimum z coordinate
#    zmax        Maximum z coordinate
#    zstep       Step size
#    names       List of labels for the x-axis (optional)
# Result:
#    None
# Note:
#    To keep the axes in positive orientation, the x-axis appears
#    on the right-hand side and the y-axis appears in front.
#    This may not be the most "intuitive" presentation though.
#
#    If the step for the x-axis is zero or negative, it is not
#    drawn - adopted from Keith Vetter's extension.
#
# Side effects:
#    Axes drawn in canvas
#
proc ::Plotchart::Draw3DAxes { w xmin  ymin  zmin
                                 xmax  ymax  zmax
                                 xstep ystep zstep
                                 {names {}}        } {
   variable config
   variable scaling

   $w delete axis3d

   #
   # Create the support lines first
   #
   foreach {pxxmin pyxmin} [coords3DToPixel $w $scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmin)] {break}
   foreach {pxxmax pyxmax} [coords3DToPixel $w $scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmin)] {break}
   foreach {pxymax pyymax} [coords3DToPixel $w $scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmin)] {break}
   foreach {pxzmax pyzmax} [coords3DToPixel $w $scaling($w,xmax) $scaling($w,ymin) $scaling($w,zmax)] {break}
   foreach {pxzmx2 pyzmx2} [coords3DToPixel $w $scaling($w,xmin) $scaling($w,ymin) $scaling($w,zmax)] {break}
   foreach {pxymx2 pyymx2} [coords3DToPixel $w $scaling($w,xmin) $scaling($w,ymax) $scaling($w,zmin)] {break}
   foreach {pxzymx pyzymx} [coords3DToPixel $w $scaling($w,xmax) $scaling($w,ymax) $scaling($w,zmax)] {break}

   if { $xstep > 0 } {
       $w create line $pxxmax $pyxmax $pxxmin $pyxmin -fill black -tag axis3d
       $w create line $pxxmax $pyxmax $pxymax $pyymax -fill black -tag axis3d
       $w create line $pxymax $pyymax $pxymx2 $pyymx2 -fill black -tag axis3d
       $w create line $pxzmax $pyzmax $pxzymx $pyzymx -fill black -tag axis3d
       $w create line $pxxmax $pyxmax $pxzmax $pyzmax -fill black -tag axis3d
       $w create line $pxzmax $pyzmax $pxzmx2 $pyzmx2 -fill black -tag axis3d
       $w create line $pxymax $pyymax $pxzymx $pyzymx -fill black -tag axis3d
   }
   $w create line $pxxmin $pyxmin $pxymx2 $pyymx2 -fill black -tag axis3d
   $w create line $pxxmin $pyxmin $pxzmx2 $pyzmx2 -fill black -tag axis3d

   #
   # Numbers to the z-axis
   #
   set format $config($w,zaxis,format)
   set z $zmin
   while { $z < $zmax+0.5*$zstep } {
      foreach {xcrd ycrd} [coords3DToPixel $w $xmin $ymin $z] {break}
      set xcrd2 [expr {$xcrd-3}]
      set xcrd3 [expr {$xcrd-5}]

      set zt [format "%.12g" $z]
      if { $format != "" } {
          set zt [FormatNumber $format $z]
      }

      $w create line $xcrd2 $ycrd $xcrd $ycrd -tag axis3d
      $w create text $xcrd3 $ycrd -text $zt -tag axis3d -anchor e
      set z [expr {$z+$zstep}]
   }

   #
   # Numbers or labels to the x-axis (shown on the right!)
   #
   set format $config($w,xaxis,format)
   if { $xstep > 0 } {
       if { $names eq "" } {
           set x $xmin
           while { $x < $xmax+0.5*$xstep } {
               foreach {xcrd ycrd} [coords3DToPixel $w $x $ymax $zmin] {break}
               set xcrd2 [expr {$xcrd+4}]
               set xcrd3 [expr {$xcrd+6}]

               $w create line $xcrd2 $ycrd $xcrd $ycrd -tag axis3d

               set xt [format "%.12g" $x]
               if { $format != "" } {
                   set xt [FormatNumber $format $x]
               }
               $w create text $xcrd3 $ycrd -text $xt -tag axis3d -anchor w
               set x [expr {$x+$xstep}]
           }
       } else {
           set x [expr {$xmin+0.5*$xstep}]
           foreach label $names {
               foreach {xcrd ycrd} [coords3DToPixel $w $x $ymax $zmin] {break}
               set xcrd2 [expr {$xcrd+6}]

               $w create text $xcrd2 $ycrd -text $label -tag axis3d -anchor w
               set x [expr {$x+$xstep}]
           }
       }
   }

   #
   # Numbers to the y-axis (shown in front!)
   #
   set format $config($w,yaxis,format)
   set y $ymin
   while { $y < $ymax+0.5*$ystep } {
      foreach {xcrd ycrd} [coords3DToPixel $w $xmin $y $zmin] {break}
      set ycrd2 [expr {$ycrd+3}]
      set ycrd3 [expr {$ycrd+5}]

      set yt [format "%.12g" $y]
      if { $format != "" } {
          set yt [FormatNumber $format $y]
      }

      $w create line $xcrd $ycrd2 $xcrd $ycrd -tag axis3d
      $w create text $xcrd $ycrd3 -text $yt -tag axis3d -anchor n
      set y [expr {$y+$ystep}]
   }

   set scaling($w,xstep) $xstep
   set scaling($w,ystep) $ystep
   set scaling($w,zstep) $zstep

   #
   # Set the default grid size
   #
   GridSize3D $w 10 10
}

# GridSize3D --
#    Set the grid size for a 3D function plot
# Arguments:
#    w           Name of the canvas
#    nxcells     Number of cells in x-direction
#    nycells     Number of cells in y-direction
# Result:
#    None
# Side effect:
#    Store the grid sizes in the private array
#
proc ::Plotchart::GridSize3D { w nxcells nycells } {
   variable scaling

   set scaling($w,nxcells) $nxcells
   set scaling($w,nycells) $nycells
}

# Draw3DFunction --
#    Plot a function of x and y
# Arguments:
#    w           Name of the canvas
#    function    Name of a procedure implementing the function
# Result:
#    None
# Side effect:
#    The plot of the function - given the grid
#
proc ::Plotchart::Draw3DFunction { w function } {
   variable scaling

   set nxcells $scaling($w,nxcells)
   set nycells $scaling($w,nycells)
   set xmin    $scaling($w,xmin)
   set xmax    $scaling($w,xmax)
   set ymin    $scaling($w,ymin)
   set ymax    $scaling($w,ymax)
   set dx      [expr {($xmax-$xmin)/double($nxcells)}]
   set dy      [expr {($ymax-$ymin)/double($nycells)}]

   foreach {fill border} $scaling($w,colours) {break}

   #
   # Draw the quadrangles making up the plot in the right order:
   # first y from minimum to maximum
   # then x from maximum to minimum
   #
   for { set j 0 } { $j < $nycells } { incr j } {
      set y1 [expr {$ymin + $dy*$j}]
      set y2 [expr {$y1   + $dy}]
      for { set i $nxcells } { $i > 0 } { incr i -1 } {
         set x2 [expr {$xmin + $dx*$i}]
         set x1 [expr {$x2   - $dx}]

         set z11 [$function $x1 $y1]
         set z12 [$function $x1 $y2]
         set z21 [$function $x2 $y1]
         set z22 [$function $x2 $y2]

         foreach {px11 py11} [coords3DToPixel $w $x1 $y1 $z11] {break}
         foreach {px12 py12} [coords3DToPixel $w $x1 $y2 $z12] {break}
         foreach {px21 py21} [coords3DToPixel $w $x2 $y1 $z21] {break}
         foreach {px22 py22} [coords3DToPixel $w $x2 $y2 $z22] {break}

         $w create polygon $px11 $py11 $px21 $py21 $px22 $py22 \
                           $px12 $py12 $px11 $py11 \
                           -fill $fill -outline $border -tags data
      }
   }
}

# Draw3DData --
#    Plot a matrix of data as a function of x and y
# Arguments:
#    w           Name of the canvas
#    data        Nested list of data in the form of a matrix
# Result:
#    None
# Side effect:
#    The plot of the data
#
proc ::Plotchart::Draw3DData { w data } {
   variable scaling

   set  nxcells [llength [lindex $data 0]]
   set  nycells [llength $data]
   incr nxcells -1
   incr nycells -1

   set xmin    $scaling($w,xmin)
   set xmax    $scaling($w,xmax)
   set ymin    $scaling($w,ymin)
   set ymax    $scaling($w,ymax)
   set dx      [expr {($xmax-$xmin)/double($nxcells)}]
   set dy      [expr {($ymax-$ymin)/double($nycells)}]

   foreach {fill border} $scaling($w,colours) {break}

   #
   # Draw the quadrangles making up the data in the right order:
   # first y from minimum to maximum
   # then x from maximum to minimum
   #
   for { set j 0 } { $j < $nycells } { incr j } {
      set z1data [lindex $data $j]
      set z2data [lindex $data [expr {$j+1}]]
      set y1 [expr {$ymin + $dy*$j}]
      set y2 [expr {$y1   + $dy}]
      for { set i $nxcells } { $i > 0 } { incr i -1 } {
         set x2 [expr {$xmin + $dx*$i}]
         set x1 [expr {$x2   - $dx}]

         set z11 [lindex $z1data [expr {$i-1}]]
         set z21 [lindex $z1data $i           ]
         set z12 [lindex $z2data [expr {$i-1}]]
         set z22 [lindex $z2data $i           ]

         foreach {px11 py11} [coords3DToPixel $w $x1 $y1 $z11] {break}
         foreach {px12 py12} [coords3DToPixel $w $x1 $y2 $z12] {break}
         foreach {px21 py21} [coords3DToPixel $w $x2 $y1 $z21] {break}
         foreach {px22 py22} [coords3DToPixel $w $x2 $y2 $z22] {break}

         $w create polygon $px11 $py11 $px21 $py21 $px22 $py22 \
                           $px12 $py12 $px11 $py11 \
                           -fill $fill -outline $border -tags data
      }
   }
}

# InterpolateData3D --
#    Interpolate and plot a function of x and y based on a grid of data
# Arguments:
#    w           Name of the canvas
#    data        Nested list of data in the form of a matrix
#    cont        Contour levels
# Result:
#    None
# Side effect:
#    The plot of the function - given the grid of data
#
proc ::Plotchart::InterpolateData3D { w data cont } {
   variable scaling

   #
   # Store the scale values
   #
   set s(xmin) $scaling($w,xmin)
   set s(xmax) $scaling($w,xmax)
   set s(ymin) $scaling($w,ymin)
   set s(ymax) $scaling($w,ymax)
   set s(nx)   [expr {[llength [lindex $data 0]] - 1}]
   set s(ny)   [expr {[llength $data] - 1}]

   Draw3DFunctionContour $w InterpolateData3DXY $cont
}

# InterpolateData3DXY --
#    Interpolate the data and return the value
# Arguments:
#    x           X-coordinate
#    y           Y-coordinate
# Result:
#    None
# Side effect:
#    The plot of the function - given the grid of data
#
proc ::Plotchart::InterpolateData3DXY { x y } {
   upvar 2 data data
   upvar 2 s    s

   set x [expr {$s(nx) * ($x - $s(xmin)) / ($s(xmax) - $s(xmin))}]
   set y [expr {$s(ny) * ($y - $s(ymin)) / ($s(ymax) - $s(ymin))}]

   set ix1 [expr {int($x)}]
   set ix2 [expr {$ix1 + 1}]
   set iy1 [expr {int($y)}]
   set iy2 [expr {$iy1 + 1}]

   if {$ix2 > $s(nx)-1 } {
      set ix2 $ix1
   }
   if {$iy2 > $s(ny)-1 } {
      set iy2 $iy1
   }

   set wx  [expr {$x - $ix1}]
   set wy  [expr {$y - $iy1}]

   set z11 [lindex $data $iy1 $ix1]
   set z12 [lindex $data $iy1 $ix2]
   set z21 [lindex $data $iy2 $ix1]
   set z22 [lindex $data $iy2 $ix2]

   return [expr {$z11 + $wx * ($z12 - $z11) + $wy * ($z21 - $z11) +
                 $wx * $wy * ($z22 + $z11 - $z12 - $z21)}]
}

# Draw3DRibbon --
#     Plot yz-data as a 3D ribbon
#
# Arguments:
#     w               Widget to draw in
#     yzData          List of duples, each of which is y,z pair
#                     (y is left-to-right, z is up-and-down, x is front-to-back).
#
# Note:
#     Contributed by Keith Vetter (see the Wiki)
#
proc ::Plotchart::Draw3DRibbon { w yzData } { variable scaling

    set  nxcells 1
    set  nycells [llength $yzData]
    incr nxcells -1
    incr nycells -1

    set x1    $scaling($w,xmin)
    set x2    [expr {($scaling($w,xmax) - $x1)/10.0}]

    foreach {fill border} $scaling($w,colours) {break}

    #
    # Draw the quadrangles making up the data in the right order:
    # first y from minimum to maximum
    # then x from maximum to minimum
    #
    for { set j 0 } { $j < $nycells } { incr j } {
        set jj [expr {$j+1}]
        set y1 [lindex $yzData $j 0]
        set y2 [lindex $yzData $jj 0]
        set z1 [lindex $yzData $j 1]
        set z2 [lindex $yzData $jj 1]

        foreach {px11 py11} [::Plotchart::coords3DToPixel $w $x1 $y1 $z1] break
        foreach {px12 py12} [::Plotchart::coords3DToPixel $w $x1 $y2 $z2] break
        foreach {px21 py21} [::Plotchart::coords3DToPixel $w $x2 $y1 $z1] break
        foreach {px22 py22} [::Plotchart::coords3DToPixel $w $x2 $y2 $z2] break
        $w create polygon $px11 $py11 $px21 $py21 $px22 $py22 \
            $px12 $py12 $px11 $py11 \
            -fill $fill -outline $border -tags data
    }
}

# Draw3DLineFrom3Dcoordinates --
#    Plot a line in the three-dimensional axis system
# Arguments:
#    w           Name of the canvas
#    data        List of xyz-coordinates
#    colour      The colour to use
# Result:
#    None
# Side effect:
#    The projected line
#
proc ::Plotchart::Draw3DLineFrom3Dcoordinates { w data colour } {
   variable scaling

   set xmin    $scaling($w,xmin)
   set xmax    $scaling($w,xmax)
   set xprev   {}

   set coords  {}
   set colours {}
   foreach {x y z} $data {
       foreach {px py} [coords3DToPixel $w $x $y $z] {break}

       lappend coords $px $py

       if { $xprev == {} } {
           set xprev $x
       }
       set factor [expr {0.5*(2.0*$xmax-$xprev-$x)/($xmax-$xmin)}]

       lappend colours [GreyColour $colour $factor]
       set xprev $x
   }

   foreach {xb yb} [lrange $coords 0 end-2] {xe ye} [lrange $coords 2 end] c [lrange $colours 0 end-1] {
       $w create line $xb $yb $xe $ye -fill $c -tags line
   }
}

